Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SetupPayload: Python implementation to generate manual and QRCode #24228

Merged
merged 7 commits into from
Jan 13, 2023
Merged
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
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