Skip to content

Commit

Permalink
Implement kotlin setup-payload phase II (#26963)
Browse files Browse the repository at this point in the history
* Implement kotlin setuppayload phase II

* Address review comments

* Remove extra space for null terminator
  • Loading branch information
yufengwangca authored Jun 2, 2023
1 parent 0d6dfde commit e19d9d8
Show file tree
Hide file tree
Showing 9 changed files with 1,325 additions and 107 deletions.
5 changes: 5 additions & 0 deletions src/controller/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,15 @@ kotlin_library("onboarding_payload") {
}

sources = [
"src/chip/onboardingpayload/CommissioningFlow.kt",
"src/chip/onboardingpayload/DiscoveryCapability.kt",
"src/chip/onboardingpayload/ManualOnboardingPayloadGenerator.kt",
"src/chip/onboardingpayload/ManualOnboardingPayloadParser.kt",
"src/chip/onboardingpayload/OnboardingPayload.kt",
"src/chip/onboardingpayload/OnboardingPayloadParser.kt",
"src/chip/onboardingpayload/OptionalQRCodeInfo.kt",
"src/chip/onboardingpayload/Verhoeff.kt",
"src/chip/onboardingpayload/Verhoeff10.kt",
]
}

Expand Down
74 changes: 0 additions & 74 deletions src/controller/java/OnboardingPayloadParser-JNI.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "lib/core/CHIPError.h"
#include "lib/support/JniTypeWrappers.h"
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <setup_payload/ManualSetupPayloadParser.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadParser.h>

Expand Down Expand Up @@ -31,7 +29,6 @@ static jobject CreateCapabilitiesHashSet(JNIEnv * env, RendezvousInformationFlag
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)
{
Expand Down Expand Up @@ -71,39 +68,6 @@ JNI_METHOD(jobject, fetchPayloadFromQrCode)(JNIEnv * env, jobject self, jstring
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");
Expand Down Expand Up @@ -254,25 +218,6 @@ JNI_METHOD(jstring, getQrCodeFromPayload)(JNIEnv * env, jobject self, jobject se
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");
Expand Down Expand Up @@ -357,22 +302,3 @@ CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj)
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, "<init>", "(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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
*
* Copyright (c) 2023 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package chip.onboardingpayload

/**
* Enum values for possible flows for out-of-box commissioning that a Matter device manufacturer
* may select for a given product.
*/
enum class CommissioningFlow(val value: Int) {
STANDARD(0), // Device automatically enters pairing mode upon power-up
USER_ACTION_REQUIRED(1), // Device requires a user interaction to enter pairing mode
CUSTOM(2) // Commissioning steps should be retrieved from the distributed compliance ledger
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
*
* Copyright (c) 2023 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package chip.onboardingpayload

class ManualOnboardingPayloadGenerator(private val payloadContents: OnboardingPayload) {
private var skipPayloadValidation = false
private var forceShortCode = false

fun setSkipPayloadValidation(allow: Boolean) {
skipPayloadValidation = allow
}

fun setForceShortCode(useShort: Boolean) {
forceShortCode = useShort
}

fun payloadDecimalStringRepresentation(): String {
// One extra char for the check digit.
val decimalString = CharArray(kManualSetupLongCodeCharLength + 1)

if (kManualSetupCodeChunk1CharLength + kManualSetupCodeChunk2CharLength + kManualSetupCodeChunk3CharLength !=
kManualSetupShortCodeCharLength) {
throw OnboardingPayloadException("Manual code length mismatch (short)")
}

if (kManualSetupShortCodeCharLength + kManualSetupVendorIdCharLength + kManualSetupProductIdCharLength !=
kManualSetupLongCodeCharLength) {
throw OnboardingPayloadException("Manual code length mismatch (long)")
}

if (kManualSetupChunk1DiscriminatorMsbitsLength + kManualSetupChunk2DiscriminatorLsbitsLength !=
kManualSetupDiscriminatorFieldLengthInBits) {
throw OnboardingPayloadException("Discriminator length is not valid")
}

if (kManualSetupChunk2PINCodeLsbitsLength + kManualSetupChunk3PINCodeMsbitsLength !=
kSetupPINCodeFieldLengthInBits) {
throw OnboardingPayloadException("PIN code length is not valid")
}

val useLongCode = (payloadContents.commissioningFlow != CommissioningFlow.STANDARD.value) && !forceShortCode

if (!skipPayloadValidation && !payloadContents.isValidManualCode()) {
throw OnboardingPayloadException("The Manual Pairing code is not valid")
}

// Add two for the check digit and null terminator.
if ((useLongCode && decimalString.size < kManualSetupLongCodeCharLength + 1) ||
(!useLongCode && decimalString.size < kManualSetupShortCodeCharLength + 1)) {
throw OnboardingPayloadException("The decimalString has insufficient size")
}

val chunk1 = chunk1PayloadRepresentation(payloadContents)
val chunk2 = chunk2PayloadRepresentation(payloadContents)
val chunk3 = chunk3PayloadRepresentation(payloadContents)

var offset = 0

decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupCodeChunk1CharLength), chunk1)
offset += kManualSetupCodeChunk1CharLength
decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupCodeChunk2CharLength), chunk2)
offset += kManualSetupCodeChunk2CharLength
decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupCodeChunk3CharLength), chunk3)
offset += kManualSetupCodeChunk3CharLength

if (useLongCode) {
decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupVendorIdCharLength), payloadContents.vendorId)
offset += kManualSetupVendorIdCharLength
decimalStringWithPadding(decimalString.sliceArray(offset until offset + kManualSetupProductIdCharLength), payloadContents.productId)
offset += kManualSetupProductIdCharLength
}

val checkDigit = Verhoeff10.charToVal(Verhoeff10.computeCheckChar(decimalString.concatToString()))
decimalStringWithPadding(decimalString.sliceArray(offset until offset + 2), checkDigit)
offset += 1

// Reduce decimalString size to be the size of written data and to not include null-terminator. In Kotlin, there is no direct
// method to resize an array.We use copyOfRange(0, offset) to create a new CharArray that includes only the elements from index
// 0 to offset-1, effectively reducing the size of the buffer.
decimalString.copyOfRange(0, offset)

return decimalString.joinToString()
}

private fun chunk1PayloadRepresentation(payload: OnboardingPayload): Int {
/* <1 digit> Represents:
* - <bits 1..0> Discriminator <bits 11.10>
* - <bit 2> VID/PID present flag
*/
val discriminatorShift = (kManualSetupDiscriminatorFieldLengthInBits - kManualSetupChunk1DiscriminatorMsbitsLength)
val discriminatorMask: Int = (1 shl kManualSetupChunk1DiscriminatorMsbitsLength) - 1

if (kManualSetupChunk1VidPidPresentBitPos <
kManualSetupChunk1DiscriminatorMsbitsPos + kManualSetupChunk1DiscriminatorMsbitsLength) {
throw OnboardingPayloadException("Discriminator won't fit")
}

val discriminatorChunk: Int = (payload.getShortDiscriminatorValue() shr discriminatorShift) and discriminatorMask
val vidPidPresentFlag: Int = if (payload.commissioningFlow != CommissioningFlow.STANDARD.value) 1 else 0

return (discriminatorChunk shl kManualSetupChunk1DiscriminatorMsbitsPos) or
(vidPidPresentFlag shl kManualSetupChunk1VidPidPresentBitPos)
}

private fun chunk2PayloadRepresentation(payload: OnboardingPayload): Int {
/* <5 digits> Represents:
* - <bits 13..0> PIN Code <bits 13..0>
* - <bits 15..14> Discriminator <bits 9..8>
*/
val discriminatorMask: Int = (1 shl kManualSetupChunk2DiscriminatorLsbitsLength) - 1
val pincodeMask: Int = (1 shl kManualSetupChunk2PINCodeLsbitsLength) - 1

val discriminatorChunk: Int = payload.getShortDiscriminatorValue() and discriminatorMask

return ((payload.setupPinCode.toInt() and pincodeMask) shl kManualSetupChunk2PINCodeLsbitsPos) or
(discriminatorChunk shl kManualSetupChunk2DiscriminatorLsbitsPos)
}

private fun chunk3PayloadRepresentation(payload: OnboardingPayload): Int {
/* <4 digits> Represents:
* - <bits 12..0> PIN Code <bits 26..14>
*/
val pincodeShift: Int = (kSetupPINCodeFieldLengthInBits - kManualSetupChunk3PINCodeMsbitsLength)
val pincodeMask: Int = (1 shl kManualSetupChunk3PINCodeMsbitsLength) - 1

return ((payload.setupPinCode.toInt() shr pincodeShift) and pincodeMask) shl kManualSetupChunk3PINCodeMsbitsPos
}

private fun decimalStringWithPadding(buffer: CharArray, number: Int): Unit {
val len = buffer.size - 1
val retval = String.format("%0${len}d", number).toCharArray(buffer, 0, buffer.size)

if (retval.size >= buffer.size) {
throw OnboardingPayloadException("The outBuffer has insufficient size")
}
}
}
Loading

0 comments on commit e19d9d8

Please sign in to comment.