Skip to content

Commit

Permalink
SetupPayload: Python implementation to generate manual and QRCode (#2…
Browse files Browse the repository at this point in the history
…4228)

* SetupPayload: Python implementation to generate manual and qrcode

Added requirements.txt

* Converted to pep8 styling

* removed get_short_discriminator method which seemed to be confusing

* Unit tests for python setup payload generator

* restyled

* use the correct script

* Not using random dataset but the predefined one
  • Loading branch information
shubhamdp authored and pull[bot] committed Feb 6, 2024
1 parent 238efbe commit 1311090
Show file tree
Hide file tree
Showing 6 changed files with 390 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,13 @@ jobs:
scripts/run_in_build_env.sh 'pip3 install ./out/controller/python/chip_repl-0.0-py3-none-any.whl'
scripts/run_in_build_env.sh '(cd src/controller/python/test/unit_tests/ && python3 -m unittest -v)'
- name: Run Python Setup Payload Generator Test
timeout-minutes: 10
run: |
scripts/run_in_build_env.sh 'scripts/examples/gn_build_example.sh examples/chip-tool out/'
scripts/run_in_build_env.sh 'pip3 install -r src/setup_payload/python/requirements.txt'
scripts/run_in_build_env.sh 'python3 src/setup_payload/tests/run_python_setup_payload_gen_test.py out/chip-tool'
build_darwin:
name: Build on Darwin (clang, python_lib, simulated)
timeout-minutes: 200
Expand Down
49 changes: 49 additions & 0 deletions src/setup_payload/python/Base38.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022 Project CHIP Authors
# All rights reserved.
#
# 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.

# TODO: Implement the decode method

CODES = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.']
RADIX = len(CODES)
BASE38_CHARS_NEEDED_IN_CHUNK = [2, 4, 5]
MAX_BYTES_IN_CHUNK = 3


def encode(bytes):
total_bytes = len(bytes)
qrcode = ''

for i in range(0, total_bytes, MAX_BYTES_IN_CHUNK):
if (i + MAX_BYTES_IN_CHUNK) > total_bytes:
bytes_in_chunk = total_bytes - i
else:
bytes_in_chunk = MAX_BYTES_IN_CHUNK

value = 0
for j in range(i, i + bytes_in_chunk):
value = value + (bytes[j] << (8 * (j - i)))

base38_chars_needed = BASE38_CHARS_NEEDED_IN_CHUNK[bytes_in_chunk - 1]
while base38_chars_needed > 0:
qrcode += CODES[int(value % RADIX)]
value = int(value / RADIX)
base38_chars_needed -= 1

return qrcode
24 changes: 24 additions & 0 deletions src/setup_payload/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Python tool to generate Matter onboarding codes

Generates Manual Pairing Code and QR Code

#### example usage:

```
./generate_setup_payload.py -h
./generate_setup_payload.py -d 3840 -p 20202021 -cf 0 -dm 2 -vid 65521 -pid 32768
```

- Output

```
Manualcode : 34970112332
QRCode : MT:Y.K9042C00KA0648G00
```

For more details please refer Matter Specification

---

NOTE: This tool is only capable of generating the payloads and no support to
parse the payloads.
171 changes: 171 additions & 0 deletions src/setup_payload/python/generate_setup_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
#
# Copyright (c) 2022 Project CHIP Authors
# All rights reserved.
#
# 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.
#
import argparse
import enum
import sys

import Base38
from bitarray import bitarray
from bitarray.util import ba2int
from stdnum.verhoeff import calc_check_digit

# See section 5.1.4.1 Manual Pairing Code in the Matter specification v1.0
MANUAL_DISCRIMINATOR_LEN = 4
PINCODE_LEN = 27

MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_LEN = 2
MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_POS = 0
MANUAL_CHUNK1_VID_PID_PRESENT_BIT_POS = MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_POS + MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_LEN
MANUAL_CHUNK1_LEN = 1

MANUAL_CHUNK2_DISCRIMINATOR_LSBITS_LEN = 2
MANUAL_CHUNK2_PINCODE_LSBITS_LEN = 14
MANUAL_CHUNK2_PINCODE_LSBITS_POS = 0
MANUAL_CHUNK2_DISCRIMINATOR_LSBITS_POS = MANUAL_CHUNK2_PINCODE_LSBITS_POS + MANUAL_CHUNK2_PINCODE_LSBITS_LEN
MANUAL_CHUNK2_LEN = 5

MANUAL_CHUNK3_PINCODE_MSBITS_LEN = 13
MANUAL_CHUNK3_PINCODE_MSBITS_POS = 0
MANUAL_CHUNK3_LEN = 4

MANUAL_VID_LEN = 5
MANUAL_PID_LEN = 5

# See section 5.1.3. QR Code in the Matter specification v1.0
QRCODE_VERSION_LEN = 3
QRCODE_DISCRIMINATOR_LEN = 12
QRCODE_VID_LEN = 16
QRCODE_PID_LEN = 16
QRCODE_COMMISSIONING_FLOW_LEN = 2
QRCODE_DISCOVERY_CAP_BITMASK_LEN = 8
QRCODE_PADDING_LEN = 4
QRCODE_VERSION = 0
QRCODE_PADDING = 0

INVALID_PASSCODES = [00000000, 11111111, 22222222, 33333333, 44444444, 55555555,
66666666, 77777777, 88888888, 99999999, 12345678, 87654321]


class CommissioningFlow(enum.IntEnum):
Standard = 0,
UserIntent = 1,
Custom = 2


class SetupPayload:
def __init__(self, discriminator, pincode, rendezvous=4, flow=CommissioningFlow.Standard, vid=0, pid=0):
self.long_discriminator = discriminator
self.short_discriminator = discriminator >> 8
self.pincode = pincode
self.rendezvous = rendezvous
self.flow = flow
self.vid = vid
self.pid = pid

def manual_chunk1(self):
discriminator_shift = (MANUAL_DISCRIMINATOR_LEN - MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_LEN)
discriminator_mask = (1 << MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_LEN) - 1
discriminator_chunk = (self.short_discriminator >> discriminator_shift) & discriminator_mask
vid_pid_present_flag = 0 if self.flow == CommissioningFlow.Standard else 1
return (discriminator_chunk << MANUAL_CHUNK1_DISCRIMINATOR_MSBITS_POS) | (vid_pid_present_flag << MANUAL_CHUNK1_VID_PID_PRESENT_BIT_POS)

def manual_chunk2(self):
discriminator_mask = (1 << MANUAL_CHUNK2_DISCRIMINATOR_LSBITS_LEN) - 1
pincode_mask = (1 << MANUAL_CHUNK2_PINCODE_LSBITS_LEN) - 1
discriminator_chunk = self.short_discriminator & discriminator_mask
return ((self.pincode & pincode_mask) << MANUAL_CHUNK2_PINCODE_LSBITS_POS) | (discriminator_chunk << MANUAL_CHUNK2_DISCRIMINATOR_LSBITS_POS)

def manual_chunk3(self):
pincode_shift = PINCODE_LEN - MANUAL_CHUNK3_PINCODE_MSBITS_LEN
pincode_mask = (1 << MANUAL_CHUNK3_PINCODE_MSBITS_LEN) - 1
return ((self.pincode >> pincode_shift) & pincode_mask) << MANUAL_CHUNK3_PINCODE_MSBITS_POS

def generate_manualcode(self):
payload = str(self.manual_chunk1()).zfill(MANUAL_CHUNK1_LEN)
payload += str(self.manual_chunk2()).zfill(MANUAL_CHUNK2_LEN)
payload += str(self.manual_chunk3()).zfill(MANUAL_CHUNK3_LEN)

if self.flow != CommissioningFlow.Standard:
payload += str(self.vid).zfill(MANUAL_VID_LEN)
payload += str(self.pid).zfill(MANUAL_PID_LEN)

payload += calc_check_digit(payload)
return payload

def generate_qrcode(self):
qrcode_bit_string = '{0:b}'.format(QRCODE_PADDING).zfill(QRCODE_PADDING_LEN)
qrcode_bit_string += '{0:b}'.format(self.pincode).zfill(PINCODE_LEN)
qrcode_bit_string += '{0:b}'.format(self.long_discriminator).zfill(QRCODE_DISCRIMINATOR_LEN)
qrcode_bit_string += '{0:b}'.format(self.rendezvous).zfill(QRCODE_DISCOVERY_CAP_BITMASK_LEN)
qrcode_bit_string += '{0:b}'.format(int(self.flow)).zfill(QRCODE_COMMISSIONING_FLOW_LEN)
qrcode_bit_string += '{0:b}'.format(self.pid).zfill(QRCODE_PID_LEN)
qrcode_bit_string += '{0:b}'.format(self.vid).zfill(QRCODE_VID_LEN)
qrcode_bit_string += '{0:b}'.format(QRCODE_VERSION).zfill(QRCODE_VERSION_LEN)

qrcode_bits = bitarray(qrcode_bit_string)
bytes = list(qrcode_bits.tobytes())
bytes.reverse()
return 'MT:{}'.format(Base38.encode(bytes))


def validate_args(args):
def check_int_range(value, min_value, max_value, name):
if value and ((value < min_value) or (value > max_value)):
print('{} is out of range, should be in range from {} to {}'.format(name, min_value, max_value))
sys.exit(1)

if args.passcode is not None:
if ((args.passcode < 0x0000001 and args.passcode > 0x5F5E0FE) or (args.passcode in INVALID_PASSCODES)):
print('Invalid passcode:' + str(args.passcode))
sys.exit(1)

check_int_range(args.discriminator, 0x0000, 0x0FFF, 'Discriminator')
check_int_range(args.product_id, 0x0000, 0xFFFF, 'Product id')
check_int_range(args.vendor_id, 0x0000, 0xFFFF, 'Vendor id')
check_int_range(args.discovery_cap_bitmask, 0x0001, 0x0007, 'Discovery Capability Mask')


def main():
def any_base_int(s): return int(s, 0)
parser = argparse.ArgumentParser(description='Matter Manual and QRCode Setup Payload Generator Tool')
parser.add_argument('-d', '--discriminator', type=any_base_int, required=True,
help='The discriminator for pairing, range: 0x00-0x0FFF')
parser.add_argument('-p', '--passcode', type=any_base_int, required=True,
help='The setup passcode for pairing, range: 0x01-0x5F5E0FE')
parser.add_argument('-vid', '--vendor-id', type=any_base_int, default=0, help='Vendor id')
parser.add_argument('-pid', '--product-id', type=any_base_int, default=0, help='Product id')
parser.add_argument('-cf', '--commissioning-flow', type=any_base_int, default=0,
help='Device commissioning flow, 0:Standard, 1:User-Intent, 2:Custom. \
Default is 0.', choices=[0, 1, 2])
parser.add_argument('-dm', '--discovery-cap-bitmask', type=any_base_int, default=4,
help='Commissionable device discovery capability bitmask. \
0:SoftAP, 1:BLE, 2:OnNetwork. Default: OnNetwork')
args = parser.parse_args()
validate_args(args)

payloads = SetupPayload(args.discriminator, args.passcode, args.discovery_cap_bitmask,
CommissioningFlow(args.commissioning_flow), args.vendor_id, args.product_id)
manualcode = payloads.generate_manualcode()
qrcode = payloads.generate_qrcode()

print("Manualcode : {}".format(manualcode))
print("QRCode : {}".format(qrcode))


if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions src/setup_payload/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bitarray==2.6.0
python_stdnum==1.18
Loading

0 comments on commit 1311090

Please sign in to comment.