Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions onboarding_payload_test_suite/onboarding_script_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,77 @@
PROMPT_TIMEOUT = 60
SPL_STR = "[SPL] "

# Matter base38 alphabet
_BASE38_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-."

# Maps base38 chunk length to the number of bytes it represents.
_BASE38_CHUNK_BYTES = {5: 3, 4: 2, 2: 1}


def _base38_decode(encoded: str) -> bytes:
"""Decode a Matter base38-encoded string to raw bytes.

Matter base38 encodes groups of bytes as follows:
- 3 bytes -> 5 characters
- 2 bytes -> 4 characters
- 1 byte -> 2 characters

Characters within each chunk are ordered from least-significant to
most-significant value, matching the Matter spec packing convention.

Args:
encoded: A base38-encoded string using the Matter alphabet
(``0-9``, ``A-Z``, ``-``, ``.``), without the ``MT:`` prefix.

Returns:
The decoded payload as a :class:`bytes` object.

Raises:
ValueError: If ``encoded`` contains a character outside the base38
alphabet, or if a chunk length is not 2, 4, or 5.
"""
result = bytearray()
i = 0
n = len(encoded)

while i < n:
remaining = n - i
if remaining >= 5:
chunk_size = 5
elif remaining == 4:
chunk_size = 4
elif remaining == 2:
chunk_size = 2
else:
raise ValueError(
f"Unexpected remaining character count {remaining} at position {i}; "
"valid trailing chunk sizes are 2 or 4."
)

chunk = encoded[i: i + chunk_size]

# Decode: characters are ordered LSB -> MSB, so accumulate with
# increasing powers of 38.
value = 0
multiplier = 1
for c in chunk:
idx = _BASE38_ALPHABET.find(c)
if idx == -1:
raise ValueError(
f"Character '{c}' at position {i} is not in the base38 alphabet."
)
value += idx * multiplier
multiplier *= 38

# Extract little-endian bytes.
for _ in range(_BASE38_CHUNK_BYTES[chunk_size]):
result.append(value & 0xFF)
value >>= 8

i += chunk_size

return bytes(result)


class ParsedPayload:
def __init__(
Expand Down Expand Up @@ -247,6 +318,50 @@ def vendorid_productid_check(self, vendor_id: int, product_id: int) -> None:
PID:{product_id}"""
)

def payload_padding_check(self, qr_code_str: str) -> None:
"""Verify that the packed binary data structure ends with zero padding bits.

Per the Matter spec, the fixed onboarding payload fields total 84 bits
(3 version + 16 VID + 16 PID + 2 custom-flow + 8 discovery + 12 discriminator
+ 27 passcode). The structure is padded to the nearest byte boundary (88 bits =
11 bytes) by appending 4 zero bits. Those padding bits occupy the upper nibble
of byte 10 of the decoded payload.

Args:
qr_code_str: The raw QR code string including the 'MT:' prefix.
"""
# Strip 'MT:' prefix
encoded = qr_code_str[3:]

# Decode the base38-encoded QR code string
try:
raw_bytes = _base38_decode(encoded)
except ValueError as e:
self.mark_step_failure(f"Failed to base38-decode QR code payload: {e}")
return

# Fail step if decoded payload is less than 11 bytes
if len(raw_bytes) < 11:
self.mark_step_failure(
f"Decoded QR code payload is {len(raw_bytes)} byte(s); "
"expected at least 11 for the fixed-size structure."
)
return

# Fail step if padding bits are not zero
padding_bits = (raw_bytes[10] >> 4) & 0x0F
if padding_bits != 0:
self.mark_step_failure(
f"Packed binary data structure padding bits are not zero: "
f"byte 10 = {bin(raw_bytes[10])} (upper nibble = {hex(padding_bits)})"
)
return
Comment thread
raul-marquez-csa marked this conversation as resolved.

logger.info(
f"Verified packed binary data structure padding: "
f"byte 10 = {bin(raw_bytes[10])}, padding nibble = 0x0"
)

def custom_payload_support_check(self, commissioningFlow: int) -> None:
if not (0x0 <= commissioningFlow <= 0x02):
self.mark_step_failure(
Expand Down
113 changes: 86 additions & 27 deletions onboarding_payload_test_suite/tcdd11/tcdd11.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ class TCDD11(PayloadParsingTestBaseClass):
"public_id": "TC-DD-1.1",
"version": "0.0.1",
"title": "TC-DD-1.1",
"description": """This test case verifies that the
onboarding QR code payload contains the necessary
information to onboard the device onto the CHIP network.""",
"description": """This test case verifies
that the onboarding QR code contains the
necessary information to onboard the
device onto the Matter network.""",
Comment thread
raul-marquez-csa marked this conversation as resolved.
}

@classmethod
Expand All @@ -40,17 +41,43 @@ def pics(cls) -> set[str]:

def create_test_steps(self) -> None:
self.test_steps = [
TestStep("Step1: Scan the DUT’s QR code using a QR code reader"),
TestStep("Step2.a: Verify the QR code payload version"),
TestStep("Step2.b: Verify 8-bit Rendezvous Capabilities bit mask"),
TestStep("Step2.c: Verify the 12-bit discriminator bit mask"),
TestStep(
"Step2.d: Verify the onboarding payload contains a 27-bit Passcode"
),
TestStep("Step2.e: Verify passcode is valid"),
TestStep("Step2.f: Verify QR code prefix"),
TestStep("Step2.g: Verify Vendor ID and Product ID"),
TestStep("Step4: Verify Custom payload support"),
TestStep("""Step1: Scan the DUT's QR code using a QR code reader
- Verify the QR code is scanned successfully."""),

TestStep("""Step2.a: Verify the QR code payload version
- Verify the QR code payload version is '000'."""),

TestStep("""Step2.b: Verify Vendor ID and Product ID
- Verify Vendor ID and Product ID match the values submitted by manufacturer in Distributed Compliance Ledger"""),

TestStep("""Step2.c: Verify the Custom Flow bit
- Verify the Custom Flow bit has one of the following values: 0, 1 or 2"""),

TestStep("""Step2.d: Verify 8-bit Discovery Capabilities bit mask
Verify that the onboarding payload contains an 8-bit Discovery Capabilities bitmask. Each bit must represent the following transport support:

- Bit 0 - Reserved (SHALL be 0)
- Bit 1 - BLE: - 0: Device does not support BLE for discovery or is currently commissioned into one or more fabrics. - 1: Device supports BLE for discovery when not commissioned.
- Bit 2 - On IP network: - 1: Device is already on the IP network
- Bits 3 - Wi-Fi Public Action Frame: - 0: Device does not support Wi-Fi Public Action Frame for discovery or is currently commissioned into one or more fabrics. - 1: Device supports Wi-Fi Public Action Frame for discovery when not commissioned.
- Bits 7-4 - Reserved (SHALL be 0)
- Ensure that the bitmask accurately reflects the DUT's supported commissioning methods and no reserved bits are set."""),

TestStep("""Step2.e: Verify the 12-bit discriminator bit mask
- Verify the 12-bit discriminator matches the value which a device advertises during commissioning."""),

TestStep("""Step2.f: Verify the onboarding payload contains a 27-bit Passcode
- Verify the 27-bit unsigned integer encodes an 8-digit decimal numeric value and shall be a value between 0x0000001 to 0x5f5e0fe (00000001 to 99999998)"""),

TestStep("""Step2.g: Verify passcode is valid
- Verify passcode does not use any trivial values: 00000000, 11111111, 22222222, 33333333, 44444444, 55555555, 66666666, 77777777, 88888888, 99999999, 12345678, 87654321
- Verify Passcode is not derived from public information as serial number, manufacturer date, MAC address, region of origin etc."""),

TestStep("""Step2.h: Verify QR code prefix
- Verify QR code prefix is "MT:"""),

TestStep("""Step3: Verify the packed binary data structure
- Verify the packed binary data structure is padded with '0' bits at the end of the structure to the nearest byte boundary."""),
]

async def setup(self) -> None:
Expand All @@ -62,35 +89,67 @@ async def execute(self) -> None:
prompt_request = self.create_onboarding_code_payload_prompt("QR")
prompt_response = await self.invoke_prompt_and_get_str_response(prompt_request)
logger.info(f"User input : {prompt_response}")

# Parse the onboarding QR code payload response
qr_code_payload = await self.chip_tool_parse_onboarding_code(prompt_response)
logger.info(f"parsed payload : {qr_code_payload}")
self.next_step()

# Test step 2.a
self.payload_version_check(qr_code_payload.version)
# Verify the QR code payload version
self.next_step()
logger.info("Verifying the QR code payload version...")
self.payload_version_check(qr_code_payload.version)

# Test step 2.b
# Verify Vendor ID and Product ID
self.next_step()
logger.info("Verifying Vendor ID and Product ID...")
self.vendorid_productid_check(
qr_code_payload.vendorID, qr_code_payload.productID
)

# Test step 2.c
# Verify the Custom Flow bit
logger.info("Verifying the Custom Flow bit...")
self.next_step()
self.custom_payload_support_check(qr_code_payload.commissioningFlow)

# Test step 2.d
# Verify 8-bit Discovery Capabilities bit mask
self.next_step()
logger.info("Verifying 8-bit Discovery Capabilities bit mask...")
self.payload_rendezvous_capabilities_bit_mask_check(
qr_code_payload.rendezvousInfo
)

# Test step 2.e
# Verify the 12-bit discriminator bit mask
self.next_step()
# Test step 2.c
# TODO Extract discriminator from device advertises frame Issue#186
logger.info("Verifying the 12-bit discriminator bit mask...")
await self.payload_discriminator_check(qr_code_payload.discriminator)
# TODO Extract discriminator from device advertises frame Issue#186

# Test steps 2.f, 2.g
# Verify the onboarding payload contains a 27-bit Passcode
# Verify passcode is valid
self.next_step()
# Test step 2.d
self.next_step()
Comment thread
raul-marquez-csa marked this conversation as resolved.
logger.info("Verifying the onboarding payload contains a 27-bit Passcode and is valid...")
self.payload_passcode_check(qr_code_payload.setUpPINCode)

# Test step 2.h
# Verify QR code prefix
self.next_step()
# Test step 2.f
logger.info("Verifying QR code prefix...")
qr_code_prefix = prompt_response[:3]
self.payload_prefix_check(qr_code_prefix)

# Test step 3
# Verify the packed binary data structure
self.next_step()
# Test step 2.g
self.vendorid_productid_check(
qr_code_payload.vendorID, qr_code_payload.productID
)
self.next_step()
# Test step 4
self.custom_payload_support_check(qr_code_payload.commissioningFlow)
logger.info("Verifying the packed binary data structure...")
self.payload_padding_check(prompt_response)


async def cleanup(self) -> None:
logger.info("TC-DD-1.1 Cleanup")