From bc57cd7b528e885c85144d63416683c36faa7ddb Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Tue, 23 May 2023 10:14:23 -0700 Subject: [PATCH] Add initial kotlin setuppayload implementation (#26676) * Add initial kotlin setuppayload implementation * Address review comments --- examples/java-matter-controller/BUILD.gn | 2 +- examples/java-matter-controller/Manifest.txt | 2 +- src/controller/java/BUILD.gn | 35 +- .../java/OnboardingPayloadParser-JNI.cpp | 378 ++++++++++++++++++ .../onboardingpayload/DiscoveryCapability.kt | 10 + .../onboardingpayload/OnboardingPayload.kt | 67 ++++ .../OnboardingPayloadParser.kt | 106 +++++ .../onboardingpayload/OptionalQRCodeInfo.kt | 17 + 8 files changed, 614 insertions(+), 3 deletions(-) create mode 100644 src/controller/java/OnboardingPayloadParser-JNI.cpp create mode 100644 src/controller/java/src/chip/onboardingpayload/DiscoveryCapability.kt create mode 100644 src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt create mode 100644 src/controller/java/src/chip/onboardingpayload/OnboardingPayloadParser.kt create mode 100644 src/controller/java/src/chip/onboardingpayload/OptionalQRCodeInfo.kt diff --git a/examples/java-matter-controller/BUILD.gn b/examples/java-matter-controller/BUILD.gn index 92ae40df938568..d61a4cf1fcc3a9 100644 --- a/examples/java-matter-controller/BUILD.gn +++ b/examples/java-matter-controller/BUILD.gn @@ -22,7 +22,6 @@ java_library("java") { output_name = "JavaMatterController.jar" deps = [ "${chip_root}/src/controller/java", - "${chip_root}/src/setup_payload/java", "${chip_root}/third_party/java_deps:annotation", ] @@ -39,6 +38,7 @@ kotlin_binary("java-matter-controller") { deps = [ ":java", "${chip_root}/src/controller/java:json_to_tlv_to_json_test", + "${chip_root}/src/controller/java:onboarding_payload", "${chip_root}/src/controller/java:tlv_read_write_test", "${chip_root}/src/controller/java:tlv_reader_test", "${chip_root}/src/controller/java:tlv_writer_test", diff --git a/examples/java-matter-controller/Manifest.txt b/examples/java-matter-controller/Manifest.txt index d221d20b748873..f9f5a70c109c59 100644 --- a/examples/java-matter-controller/Manifest.txt +++ b/examples/java-matter-controller/Manifest.txt @@ -1,3 +1,3 @@ Main-Class: com.matter.controller.MainKt -Class-Path: ../lib/third_party/connectedhomeip/src/controller/java/CHIPController.jar ../lib/third_party/connectedhomeip/src/setup_payload/java/SetupPayloadParser.jar ../lib/third_party/connectedhomeip/third_party/java_deps/stub_src/Android.jar ../lib/third_party/connectedhomeip/third_party/java_deps/json-20220924.jar ../lib/third_party/connectedhomeip/third_party/java_deps/jsr305-3.0.2.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlin-stdlib-1.8.10.jar +Class-Path: ../lib/third_party/connectedhomeip/src/controller/java/CHIPController.jar ../lib/third_party/connectedhomeip/src/setup_payload/java/OnboardingPayload.jar ../lib/third_party/connectedhomeip/third_party/java_deps/stub_src/Android.jar ../lib/third_party/connectedhomeip/third_party/java_deps/json-20220924.jar ../lib/third_party/connectedhomeip/third_party/java_deps/jsr305-3.0.2.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlin-stdlib-1.8.10.jar diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 1f911dd0bd61cb..4a31761dd35c49 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -225,7 +225,6 @@ kotlin_library("json_to_tlv_to_json_test") { deps = [ ":jsontlv", - ":tlv", "${chip_root}/third_party/java_deps:gson", "${chip_root}/third_party/java_deps:junit-4", "${chip_root}/third_party/java_deps:kotlin-test", @@ -237,6 +236,40 @@ kotlin_library("json_to_tlv_to_json_test") { kotlinc_flags = [ "-Xlint:deprecation" ] } +shared_library("jni_for_onboarding_payload") { + output_name = "libOnboardingPayload" + if (build_java_matter_controller) { + include_dirs = java_matter_controller_dependent_paths + output_dir = "${root_out_dir}/lib/jni" + } else { + output_dir = "${root_out_dir}/lib/jni/${android_abi}" + } + + sources = [ "OnboardingPayloadParser-JNI.cpp" ] + + deps = [ + "${chip_root}/src/lib", + "${chip_root}/src/setup_payload", + ] +} + +kotlin_library("onboarding_payload") { + output_name = "OnboardingPayload.jar" + + data_deps = [ ":jni_for_onboarding_payload" ] + + if (!build_java_matter_controller) { + data_deps += [ "${chip_root}/build/chip/java:shared_cpplib" ] + } + + sources = [ + "src/chip/onboardingpayload/DiscoveryCapability.kt", + "src/chip/onboardingpayload/OnboardingPayload.kt", + "src/chip/onboardingpayload/OnboardingPayloadParser.kt", + "src/chip/onboardingpayload/OptionalQRCodeInfo.kt", + ] +} + android_library("java") { output_name = "CHIPController.jar" diff --git a/src/controller/java/OnboardingPayloadParser-JNI.cpp b/src/controller/java/OnboardingPayloadParser-JNI.cpp new file mode 100644 index 00000000000000..c07e4b2a7060c5 --- /dev/null +++ b/src/controller/java/OnboardingPayloadParser-JNI.cpp @@ -0,0 +1,378 @@ +#include "lib/core/CHIPError.h" +#include "lib/support/JniTypeWrappers.h" +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +using namespace chip; + +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_MIN 10 // avoiding collision with CHIPJNIError.h +#define _SETUP_PAYLOAD_PARSER_JNI_ERROR(e) CHIP_APPLICATION_ERROR(SETUP_PAYLOAD_PARSER_JNI_ERROR_MIN + (e)) + +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_EXCEPTION_THROWN _SETUP_PAYLOAD_PARSER_JNI_ERROR(0) +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_TYPE_NOT_FOUND _SETUP_PAYLOAD_PARSER_JNI_ERROR(1) +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_METHOD_NOT_FOUND _SETUP_PAYLOAD_PARSER_JNI_ERROR(2) +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_FIELD_NOT_FOUND _SETUP_PAYLOAD_PARSER_JNI_ERROR(3) + +#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_chip_setuppayload_SetupPayloadParser_##METHOD_NAME + +static jobject TransformSetupPayload(JNIEnv * env, SetupPayload & payload); +static jobject CreateCapabilitiesHashSet(JNIEnv * env, RendezvousInformationFlags flags); +static void TransformSetupPayloadFromJobject(JNIEnv * env, jobject jPayload, SetupPayload & payload); +static void CreateCapabilitiesFromHashSet(JNIEnv * env, jobject discoveryCapabilitiesObj, RendezvousInformationFlags & flags); +static CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj); +static CHIP_ERROR ThrowInvalidManualPairingCodeFormatException(JNIEnv * env, jstring manualPairingCodeObj); + +jint JNI_OnLoad(JavaVM * jvm, void * reserved) +{ + ChipLogProgress(SetupPayload, "JNI_OnLoad() called"); + chip::Platform::MemoryInit(); + return JNI_VERSION_1_6; +} + +JNI_METHOD(jobject, fetchPayloadFromQrCode)(JNIEnv * env, jobject self, jstring qrCodeObj, jboolean skipPayloadValidation) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + const char * qrString = NULL; + SetupPayload payload; + + qrString = env->GetStringUTFChars(qrCodeObj, 0); + + err = QRCodeSetupPayloadParser(qrString).populatePayload(payload); + env->ReleaseStringUTFChars(qrCodeObj, qrString); + + if (skipPayloadValidation == JNI_FALSE && !payload.isValidQRCodePayload()) + { + jclass exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$SetupPayloadException"); + JniReferences::GetInstance().ThrowError(env, exceptionCls, CHIP_ERROR_INVALID_ARGUMENT); + return nullptr; + } + + if (err != CHIP_NO_ERROR) + { + err = ThrowUnrecognizedQRCodeException(env, qrCodeObj); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SetupPayload, "Error throwing UnrecognizedQRCodeException: %" CHIP_ERROR_FORMAT, err.Format()); + } + return nullptr; + } + + return TransformSetupPayload(env, payload); +} + +JNI_METHOD(jobject, parsePayloadFromManualPairingCode) +(JNIEnv * env, jobject self, jstring manualPairingCode, jboolean skipPayloadValidation) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + const char * manualPairingCodeString = NULL; + SetupPayload payload; + + manualPairingCodeString = env->GetStringUTFChars(manualPairingCode, 0); + + err = ManualSetupPayloadParser(manualPairingCodeString).populatePayload(payload); + env->ReleaseStringUTFChars(manualPairingCode, manualPairingCodeString); + + if (skipPayloadValidation == JNI_FALSE && !payload.isValidManualCode()) + { + jclass exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$SetupPayloadException"); + JniReferences::GetInstance().ThrowError(env, exceptionCls, CHIP_ERROR_INVALID_ARGUMENT); + return nullptr; + } + + if (err != CHIP_NO_ERROR) + { + err = ThrowInvalidManualPairingCodeFormatException(env, manualPairingCode); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SetupPayload, "Error throwing ThrowInvalidManualPairingCodeFormatException: %" CHIP_ERROR_FORMAT, + err.Format()); + } + return nullptr; + } + + return TransformSetupPayload(env, payload); +} + +jobject TransformSetupPayload(JNIEnv * env, SetupPayload & payload) +{ + jclass setupPayloadClass = env->FindClass("chip/setuppayload/SetupPayload"); + jmethodID setupConstr = env->GetMethodID(setupPayloadClass, "", "()V"); + jobject setupPayload = env->NewObject(setupPayloadClass, setupConstr); + + jfieldID version = env->GetFieldID(setupPayloadClass, "version", "I"); + jfieldID vendorId = env->GetFieldID(setupPayloadClass, "vendorId", "I"); + jfieldID productId = env->GetFieldID(setupPayloadClass, "productId", "I"); + jfieldID commissioningFlow = env->GetFieldID(setupPayloadClass, "commissioningFlow", "I"); + jfieldID discriminator = env->GetFieldID(setupPayloadClass, "discriminator", "I"); + jfieldID hasShortDiscriminator = env->GetFieldID(setupPayloadClass, "hasShortDiscriminator", "Z"); + jfieldID setUpPinCode = env->GetFieldID(setupPayloadClass, "setupPinCode", "J"); + jfieldID discoveryCapabilities = env->GetFieldID(setupPayloadClass, "discoveryCapabilities", "Ljava/util/Set;"); + + env->SetIntField(setupPayload, version, payload.version); + env->SetIntField(setupPayload, vendorId, payload.vendorID); + env->SetIntField(setupPayload, productId, payload.productID); + env->SetIntField(setupPayload, commissioningFlow, static_cast(payload.commissioningFlow)); + uint16_t discriminatorValue; + bool isShortDiscriminator = payload.discriminator.IsShortDiscriminator(); + if (isShortDiscriminator) + { + discriminatorValue = static_cast(payload.discriminator.GetShortValue()) + << (SetupDiscriminator::kLongBits - SetupDiscriminator::kShortBits); + } + else + { + discriminatorValue = payload.discriminator.GetLongValue(); + } + env->SetIntField(setupPayload, discriminator, discriminatorValue); + env->SetBooleanField(setupPayload, hasShortDiscriminator, isShortDiscriminator); + env->SetLongField(setupPayload, setUpPinCode, payload.setUpPINCode); + + env->SetObjectField(setupPayload, discoveryCapabilities, + CreateCapabilitiesHashSet(env, payload.rendezvousInformation.ValueOr(RendezvousInformationFlag::kNone))); + + jmethodID addOptionalInfoMid = + env->GetMethodID(setupPayloadClass, "addOptionalQRCodeInfo", "(Lchip/setuppayload/OptionalQRCodeInfo;)V"); + + std::vector optional_info = payload.getAllOptionalVendorData(); + for (OptionalQRCodeInfo & info : optional_info) + { + + jclass optionalInfoClass = env->FindClass("chip/setuppayload/OptionalQRCodeInfo"); + jobject optionalInfo = env->AllocObject(optionalInfoClass); + jfieldID tag = env->GetFieldID(optionalInfoClass, "tag", "I"); + jfieldID type = env->GetFieldID(optionalInfoClass, "type", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + jfieldID data = env->GetFieldID(optionalInfoClass, "data", "Ljava/lang/String;"); + jfieldID int32 = env->GetFieldID(optionalInfoClass, "int32", "I"); + + env->SetIntField(optionalInfo, tag, info.tag); + + jclass enumClass = env->FindClass("chip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType"); + jfieldID enumType = nullptr; + + switch (info.type) + { + case optionalQRCodeInfoTypeString: + enumType = + env->GetStaticFieldID(enumClass, "TYPE_STRING", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + break; + case optionalQRCodeInfoTypeInt32: + enumType = + env->GetStaticFieldID(enumClass, "TYPE_INT32", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + break; + case optionalQRCodeInfoTypeInt64: + enumType = + env->GetStaticFieldID(enumClass, "TYPE_INT64", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + break; + case optionalQRCodeInfoTypeUInt32: + enumType = + env->GetStaticFieldID(enumClass, "TYPE_UINT32", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + break; + case optionalQRCodeInfoTypeUInt64: + enumType = + env->GetStaticFieldID(enumClass, "TYPE_UINT64", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + break; + case optionalQRCodeInfoTypeUnknown: + default: // Optional Type variable has to set any value. + enumType = + env->GetStaticFieldID(enumClass, "TYPE_UNKNOWN", "Lchip/setuppayload/OptionalQRCodeInfo$OptionalQRCodeInfoType;"); + break; + } + + if (enumType != nullptr) + { + jobject enumObj = env->GetStaticObjectField(enumClass, enumType); + env->SetObjectField(optionalInfo, type, enumObj); + } + + env->SetObjectField(optionalInfo, data, env->NewStringUTF(info.data.c_str())); + env->SetIntField(optionalInfo, int32, info.int32); + + env->CallVoidMethod(setupPayload, addOptionalInfoMid, optionalInfo); + } + + return setupPayload; +} + +jobject CreateCapabilitiesHashSet(JNIEnv * env, RendezvousInformationFlags flags) +{ + jclass hashSetClass = env->FindClass("java/util/HashSet"); + jmethodID hashSetConstructor = env->GetMethodID(hashSetClass, "", "()V"); + jobject capabilitiesHashSet = env->NewObject(hashSetClass, hashSetConstructor); + + jmethodID hashSetAddMethod = env->GetMethodID(hashSetClass, "add", "(Ljava/lang/Object;)Z"); + jclass capabilityEnum = env->FindClass("chip/setuppayload/DiscoveryCapability"); + + if (flags.Has(chip::RendezvousInformationFlag::kBLE)) + { + jfieldID bleCapability = env->GetStaticFieldID(capabilityEnum, "BLE", "Lchip/setuppayload/DiscoveryCapability;"); + jobject enumObj = env->GetStaticObjectField(capabilityEnum, bleCapability); + env->CallBooleanMethod(capabilitiesHashSet, hashSetAddMethod, enumObj); + } + if (flags.Has(chip::RendezvousInformationFlag::kSoftAP)) + { + jfieldID softApCapability = env->GetStaticFieldID(capabilityEnum, "SOFT_AP", "Lchip/setuppayload/DiscoveryCapability;"); + jobject enumObj = env->GetStaticObjectField(capabilityEnum, softApCapability); + env->CallBooleanMethod(capabilitiesHashSet, hashSetAddMethod, enumObj); + } + if (flags.Has(chip::RendezvousInformationFlag::kOnNetwork)) + { + jfieldID onNetworkCapability = + env->GetStaticFieldID(capabilityEnum, "ON_NETWORK", "Lchip/setuppayload/DiscoveryCapability;"); + jobject enumObj = env->GetStaticObjectField(capabilityEnum, onNetworkCapability); + env->CallBooleanMethod(capabilitiesHashSet, hashSetAddMethod, enumObj); + } + return capabilitiesHashSet; +} + +JNI_METHOD(jstring, getQrCodeFromPayload)(JNIEnv * env, jobject self, jobject setupPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + SetupPayload payload; + std::string qrString; + + TransformSetupPayloadFromJobject(env, setupPayload, payload); + + err = QRCodeSetupPayloadGenerator(payload).payloadBase38Representation(qrString); + if (err != CHIP_NO_ERROR) + { + jclass exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$SetupPayloadException"); + JniReferences::GetInstance().ThrowError(env, exceptionCls, err); + return nullptr; + } + + return env->NewStringUTF(qrString.c_str()); +} + +JNI_METHOD(jstring, getManualPairingCodeFromPayload)(JNIEnv * env, jobject self, jobject setupPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + SetupPayload payload; + std::string outDecimalString; + + TransformSetupPayloadFromJobject(env, setupPayload, payload); + + err = ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(outDecimalString); + if (err != CHIP_NO_ERROR) + { + jclass exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$SetupPayloadException"); + JniReferences::GetInstance().ThrowError(env, exceptionCls, err); + return nullptr; + } + + return env->NewStringUTF(outDecimalString.c_str()); +} + +void TransformSetupPayloadFromJobject(JNIEnv * env, jobject jPayload, SetupPayload & payload) +{ + jclass setupPayloadClass = env->FindClass("chip/setuppayload/SetupPayload"); + + jfieldID version = env->GetFieldID(setupPayloadClass, "version", "I"); + jfieldID vendorId = env->GetFieldID(setupPayloadClass, "vendorId", "I"); + jfieldID productId = env->GetFieldID(setupPayloadClass, "productId", "I"); + jfieldID commissioningFlow = env->GetFieldID(setupPayloadClass, "commissioningFlow", "I"); + jfieldID discriminator = env->GetFieldID(setupPayloadClass, "discriminator", "I"); + jfieldID hasShortDiscriminatorFieldId = env->GetFieldID(setupPayloadClass, "hasShortDiscriminator", "Z"); + jfieldID setUpPinCode = env->GetFieldID(setupPayloadClass, "setupPinCode", "J"); + jfieldID discoveryCapabilities = env->GetFieldID(setupPayloadClass, "discoveryCapabilities", "Ljava/util/Set;"); + + payload.version = env->GetIntField(jPayload, version); + payload.vendorID = env->GetIntField(jPayload, vendorId); + payload.productID = env->GetIntField(jPayload, productId); + payload.commissioningFlow = static_cast(env->GetIntField(jPayload, commissioningFlow)); + jboolean hasShortDiscriminator = env->GetBooleanField(jPayload, hasShortDiscriminatorFieldId); + if (hasShortDiscriminator) + { + payload.discriminator.SetShortValue(env->GetShortField(jPayload, discriminator)); + } + else + { + payload.discriminator.SetLongValue(env->GetIntField(jPayload, discriminator)); + } + payload.setUpPINCode = static_cast(env->GetLongField(jPayload, setUpPinCode)); + + jobject discoveryCapabilitiesObj = env->GetObjectField(jPayload, discoveryCapabilities); + CreateCapabilitiesFromHashSet(env, discoveryCapabilitiesObj, + payload.rendezvousInformation.Emplace(RendezvousInformationFlag::kNone)); +} + +void CreateCapabilitiesFromHashSet(JNIEnv * env, jobject discoveryCapabilitiesObj, RendezvousInformationFlags & flags) +{ + jclass hashSetClass = env->FindClass("java/util/HashSet"); + jmethodID hashSetContainsMethod = env->GetMethodID(hashSetClass, "contains", "(Ljava/lang/Object;)Z"); + + jboolean contains; + jclass capabilityEnum = env->FindClass("chip/setuppayload/DiscoveryCapability"); + + jfieldID bleCapability = env->GetStaticFieldID(capabilityEnum, "BLE", "Lchip/setuppayload/DiscoveryCapability;"); + jobject bleObj = env->GetStaticObjectField(capabilityEnum, bleCapability); + contains = env->CallBooleanMethod(discoveryCapabilitiesObj, hashSetContainsMethod, bleObj); + if (contains) + { + flags.Set(chip::RendezvousInformationFlag::kBLE); + } + + jfieldID softApCapability = env->GetStaticFieldID(capabilityEnum, "SOFT_AP", "Lchip/setuppayload/DiscoveryCapability;"); + jobject softApObj = env->GetStaticObjectField(capabilityEnum, softApCapability); + contains = env->CallBooleanMethod(discoveryCapabilitiesObj, hashSetContainsMethod, softApObj); + if (contains) + { + flags.Set(chip::RendezvousInformationFlag::kSoftAP); + } + + jfieldID onNetworkCapability = env->GetStaticFieldID(capabilityEnum, "ON_NETWORK", "Lchip/setuppayload/DiscoveryCapability;"); + jobject onNetworkObj = env->GetStaticObjectField(capabilityEnum, onNetworkCapability); + contains = env->CallBooleanMethod(discoveryCapabilitiesObj, hashSetContainsMethod, onNetworkObj); + if (contains) + { + flags.Set(chip::RendezvousInformationFlag::kOnNetwork); + } +} + +CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj) +{ + jclass exceptionCls = nullptr; + jmethodID exceptionConstructor = nullptr; + jthrowable exception = nullptr; + + env->ExceptionClear(); + + exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$UnrecognizedQrCodeException"); + VerifyOrReturnError(exceptionCls != NULL, SETUP_PAYLOAD_PARSER_JNI_ERROR_TYPE_NOT_FOUND); + exceptionConstructor = env->GetMethodID(exceptionCls, "", "(Ljava/lang/String;)V"); + VerifyOrReturnError(exceptionConstructor != NULL, SETUP_PAYLOAD_PARSER_JNI_ERROR_METHOD_NOT_FOUND); + exception = (jthrowable) env->NewObject(exceptionCls, exceptionConstructor, qrCodeObj); + VerifyOrReturnError(exception != NULL, SETUP_PAYLOAD_PARSER_JNI_ERROR_EXCEPTION_THROWN); + + env->Throw(exception); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThrowInvalidManualPairingCodeFormatException(JNIEnv * env, jstring manualPairingCodeObj) +{ + jclass exceptionCls = nullptr; + jmethodID exceptionConstructor = nullptr; + jthrowable exception = nullptr; + + env->ExceptionClear(); + + exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$InvalidEntryCodeFormatException"); + VerifyOrReturnError(exceptionCls != NULL, SETUP_PAYLOAD_PARSER_JNI_ERROR_TYPE_NOT_FOUND); + exceptionConstructor = env->GetMethodID(exceptionCls, "", "(Ljava/lang/String;)V"); + VerifyOrReturnError(exceptionConstructor != NULL, SETUP_PAYLOAD_PARSER_JNI_ERROR_METHOD_NOT_FOUND); + exception = (jthrowable) env->NewObject(exceptionCls, exceptionConstructor, manualPairingCodeObj); + VerifyOrReturnError(exception != NULL, SETUP_PAYLOAD_PARSER_JNI_ERROR_EXCEPTION_THROWN); + + env->Throw(exception); + return CHIP_NO_ERROR; +} diff --git a/src/controller/java/src/chip/onboardingpayload/DiscoveryCapability.kt b/src/controller/java/src/chip/onboardingpayload/DiscoveryCapability.kt new file mode 100644 index 00000000000000..89b1a6c3ce5a94 --- /dev/null +++ b/src/controller/java/src/chip/onboardingpayload/DiscoveryCapability.kt @@ -0,0 +1,10 @@ +package chip.onboardingpayload + +/** + * Enum values for possible bits in the onboarding paylod's discovery capabilities bitmask. + */ +enum class DiscoveryCapability { + SOFT_AP, + BLE, + ON_NETWORK +} diff --git a/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt b/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt new file mode 100644 index 00000000000000..b8f8b811ec174d --- /dev/null +++ b/src/controller/java/src/chip/onboardingpayload/OnboardingPayload.kt @@ -0,0 +1,67 @@ +package chip.onboardingpayload + +/** Class to hold the data from the scanned QR code or Manual Pairing Code. */ +class OnboardingPayload( + /** Version info of the OnboardingPayload: version SHALL be 0 */ + var version: Int = 0, + + /** The CHIP device vendor ID: Vendor ID SHALL be between 1 and 0xFFF4. */ + var vendorId: Int = 0, + + /** The CHIP device product ID: Product ID SHALL BE greater than 0. */ + var productId: Int = 0, + + /** Commissioning flow: 0 = standard, 1 = requires user action, 2 = custom */ + var commissioningFlow: Int = 0, + + /** + * The CHIP device supported rendezvous flags: At least one DiscoveryCapability must be included. + */ + var discoveryCapabilities: Set = emptySet(), + + /** The CHIP device discriminator: */ + var discriminator: Int = 0, + + /** + * If hasShortDiscriminator is true, the discriminator value contains just the high 4 bits of the + * full discriminator. For example, if hasShortDiscriminator is true and discriminator is 0xA, + * then the full discriminator can be anything in the range 0xA00 to 0xAFF. + */ + var hasShortDiscriminator: Boolean = false, + + /** + * The CHIP device setup PIN code: setupPINCode SHALL be greater than 0. Also invalid setupPINCode + * is {000000000, 11111111, 22222222, 33333333, 44444444, 55555555, 66666666, 77777777, 88888888, + * 99999999, 12345678, 87654321}. + */ + var setupPinCode: Long = 0 +) { + var optionalQRCodeInfo: HashMap + + init { + optionalQRCodeInfo = HashMap() + } + + constructor( + version: Int, + vendorId: Int, + productId: Int, + commissioningFlow: Int, + discoveryCapabilities: Set, + discriminator: Int, + setupPinCode: Long + ) : this( + version, + vendorId, + productId, + commissioningFlow, + discoveryCapabilities, + discriminator, + false, + setupPinCode + ) + + fun addOptionalQRCodeInfo(info: OptionalQRCodeInfo) { + optionalQRCodeInfo[info.tag] = info + } +} diff --git a/src/controller/java/src/chip/onboardingpayload/OnboardingPayloadParser.kt b/src/controller/java/src/chip/onboardingpayload/OnboardingPayloadParser.kt new file mode 100644 index 00000000000000..0f8946910f5afd --- /dev/null +++ b/src/controller/java/src/chip/onboardingpayload/OnboardingPayloadParser.kt @@ -0,0 +1,106 @@ +package chip.onboardingpayload + +import java.util.logging.Level +import java.util.logging.Logger + +/** Parser for scanned QR code or Manual Pairing Code. */ +class OnboardingPayloadParser { + /** + * Returns [OnboardingPayload] parsed from the QR code string. If an invalid element is included + * in the QRCode Parse result, OnboardingPayloadException occurs. Refer to [OnboardingPayload] for the + * description of the invalid element. + */ + @Throws(UnrecognizedQrCodeException::class, OnboardingPayloadException::class) + fun parseQrCode(qrCodeString: String): OnboardingPayload { + return fetchPayloadFromQrCode(qrCodeString, false) + } + + /** + * Returns [OnboardingPayload] parsed from the QR code string. + * + * @param qrCodeString the QRCode for commissioning device. + * @param skipPayloadValidation If this value is true, payload element validation is not checked. + * Consider saying that the payload must still parse correctly, but this skips validation of the + * content past parsing (i.e. it does not validate ranges for individual elements). + * Refer to [OnboardingPayload] for the description of the invalid element. + */ + @Throws(UnrecognizedQrCodeException::class, OnboardingPayloadException::class) + fun parseQrCode(qrCodeString: String, skipPayloadValidation: Boolean): OnboardingPayload { + return fetchPayloadFromQrCode(qrCodeString, skipPayloadValidation) + } + + /** + * Returns [OnboardingPayload] parsed from the Manual Pairing Code string. If an SetupPINCode has + * invalid value, OnboardingPayloadException occurs. Refer to [OnboardingPayload] for the description + * of the invalid element. + */ + @Throws(InvalidManualPairingCodeFormatException::class, OnboardingPayloadException::class) + fun parseManualPairingCode(manualPairingCodeString: String): OnboardingPayload { + return parsePayloadFromManualPairingCode(manualPairingCodeString, false) + } + + /** + * Returns [OnboardingPayload] parsed from the Manual Pairing Code string. + * + * @param manualPairingCodeString the manual Pairing Code for commissioning device. + * @param skipPayloadValidation If this value is true, payload element validation is not checked. + * Consider saying that the payload must still parse correctly, but this skips validation of the + * content past parsing (i.e. it does not validate ranges for individual elements). + * Refer to [OnboardingPayload] for the description of the invalid element. + */ + @Throws(InvalidManualPairingCodeFormatException::class, OnboardingPayloadException::class) + fun parseManualPairingCode(manualPairingCodeString: String, skipPayloadValidation: Boolean): OnboardingPayload { + return parsePayloadFromManualPairingCode(manualPairingCodeString, skipPayloadValidation) + } + + /** Get QR code string from [OnboardingPayload]. */ + @Throws(OnboardingPayloadException::class) + external fun getQrCodeFromPayload(payload: OnboardingPayload): String? + + /** Get Manual Pairing Code string from [OnboardingPayload]. */ + @Throws(OnboardingPayloadException::class) + external fun getManualPairingCodeFromPayload(payload: OnboardingPayload): String? + + @Throws(UnrecognizedQrCodeException::class, OnboardingPayloadException::class) + private external fun fetchPayloadFromQrCode( + qrCodeString: String, skipPayloadValidation: Boolean + ): OnboardingPayload + + @Throws(InvalidManualPairingCodeFormatException::class, OnboardingPayloadException::class) + private external fun parsePayloadFromManualPairingCode( + manualPairingCodeString: String, skipPayloadValidation: Boolean + ): OnboardingPayload + + class UnrecognizedQrCodeException(qrCode: String) : + Exception(String.format("Invalid QR code string: %s", qrCode), null) { + companion object { + private const val serialVersionUID = 1L + } + } + + class InvalidManualPairingCodeFormatException(manualPairingCode: String) : + Exception(String.format("Invalid format for manual pairing code string: %s", manualPairingCode), null) { + companion object { + private const val serialVersionUID = 1L + } + } + + class OnboardingPayloadException(var errorCode: Int, message: String?) : + Exception(message ?: String.format("Error Code %d", errorCode)) { + companion object { + private const val serialVersionUID = 1L + } + } + + companion object { + private val LOGGER: Logger = Logger.getLogger(OnboardingPayloadParser::class.java.getSimpleName()) + + init { + try { + System.loadLibrary("OnboardingPayload") + } catch (e: UnsatisfiedLinkError) { + LOGGER.log(Level.SEVERE, "Cannot load library.", e) + } + } + } +} diff --git a/src/controller/java/src/chip/onboardingpayload/OptionalQRCodeInfo.kt b/src/controller/java/src/chip/onboardingpayload/OptionalQRCodeInfo.kt new file mode 100644 index 00000000000000..33ca3e5f77a343 --- /dev/null +++ b/src/controller/java/src/chip/onboardingpayload/OptionalQRCodeInfo.kt @@ -0,0 +1,17 @@ +package chip.onboardingpayload + +class OptionalQRCodeInfo { + enum class OptionalQRCodeInfoType { + TYPE_UNKNOWN, + TYPE_STRING, + TYPE_INT32, + TYPE_INT64, + TYPE_UINT32, + TYPE_UINT64 + } + + var tag = 0 + var type: OptionalQRCodeInfoType? = null + var data: String? = null + var int32 = 0 +}