| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- #
- # Copyright (c) 2022 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.
- #
- import argparse
- import datetime
- import os
- import subprocess
- class FactoryDataWriter:
- script_dir = os.path.dirname(__file__)
- # CONSTANTS
- TEMP_FILE = script_dir + "/tmp_nvm3.s37"
- OUT_FILE = script_dir + "/matter_factorydata.s37" # Final output file containing the nvm3 data to flash to the device
- BASE_MG12_FILE = script_dir + "/base_matter_mg12_nvm3.s37"
- BASE_MG24_FILE = script_dir + "/base_matter_mg24_nvm3.s37"
- # nvm3 keys to set
- SERIAL_NUMBER_NVM3_KEY = "0x87200:"
- MANUFACTURING_DATE_NVM3_KEY = "0x87204:"
- SETUP_PAYLOAD_NVM3_KEY = "0x87205:"
- DISCRIMINATOR_NVM3_KEY = "0x87207:"
- ITERATIONCOUNT_NVM3_KEY = "0x87208:"
- SALT_NVM3_KEY = "0x87209:"
- VERIFIER_NVM3_KEY = "0x8720A:"
- PRODUCT_ID_NVM3_KEY = "0x8720B:"
- VENDOR_ID_NVM3_KEY = "0x8720C:"
- VENDOR_NAME_NVM3_KEY = "0x8720D:"
- PRODUCT_NAME_NVM3_KEY = "0x8720E:"
- HW_VER_STR_NVM3_KEY = "0x8720F:"
- UNIQUE_ID_NVM3_KEY = "0x8721F:"
- HW_VER_NVM3_KEY = "0x87308:"
- PRODUCT_LABEL_NVM3_KEY = "0x87210:"
- PRODUCT_URL_NVM3_KEY = "0x87211:"
- PART_NUMBER_NVM3_KEY = "0x87212:"
- def generate_spake2p_verifier(self):
- """ Generate Spake2+ verifier using the external spake2p tool
- Args:
- The whole set of args passed to the script. The required one are:
- gen_spake2p_path: path to spake2p executable
- spake2_iteration: Iteration counter for Spake2+ verifier generation
- passcode: Pairing passcode using in Spake2+
- spake2_salt: Salt used to generate Spake2+ verifier
- Returns:
- The generated verifier string
- """
- cmd = [
- self._args.gen_spake2p_path, 'gen-verifier',
- '--iteration-count', str(self._args.spake2_iteration),
- '--salt', self._args.spake2_salt,
- '--pin-code', str(self._args.passcode),
- '--out', '-',
- ]
- output = subprocess.check_output(cmd)
- output = output.decode('utf-8').splitlines()
- generation_results = dict(zip(output[0].split(','), output[1].split(',')))
- return generation_results["Verifier"]
- # Populates numberOfBits starting from LSB of input into bits, which is assumed to be zero-initialized
- def WriteBits(self, bits, offset, input, numberOfBits, totalPayloadSizeInBits):
- if ((offset + numberOfBits) > totalPayloadSizeInBits):
- print("THIS IS NOT VALID")
- return
- # input < 1u << numberOfBits);
- index = offset
- offset += numberOfBits
- while (input != 0):
- if (input & 1):
- bits[int(index / 8)] |= (1 << (index % 8))
- index += 1
- input >>= 1
- return offset
- def generateQrCodeBitSet(self):
- kVersionFieldLengthInBits = 3
- kVendorIDFieldLengthInBits = 16
- kProductIDFieldLengthInBits = 16
- kCommissioningFlowFieldLengthInBits = 2
- kRendezvousInfoFieldLengthInBits = 8
- kPayloadDiscriminatorFieldLengthInBits = 12
- kSetupPINCodeFieldLengthInBits = 27
- kPaddingFieldLengthInBits = 4
- kTotalPayloadDataSizeInBits = (kVersionFieldLengthInBits + kVendorIDFieldLengthInBits + kProductIDFieldLengthInBits +
- kCommissioningFlowFieldLengthInBits + kRendezvousInfoFieldLengthInBits + kPayloadDiscriminatorFieldLengthInBits +
- kSetupPINCodeFieldLengthInBits + kPaddingFieldLengthInBits)
- offset = 0
- fillBits = [0] * int(kTotalPayloadDataSizeInBits / 8)
- offset = self.WriteBits(fillBits, offset, 0, kVersionFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, self._args.vendor_id, kVendorIDFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, self._args.product_id, kProductIDFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, self._args.commissioning_flow,
- kCommissioningFlowFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, self._args.rendezvous_flag,
- kRendezvousInfoFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, self._args.discriminator,
- kPayloadDiscriminatorFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, self._args.passcode, kSetupPINCodeFieldLengthInBits, kTotalPayloadDataSizeInBits)
- offset = self.WriteBits(fillBits, offset, 0, kPaddingFieldLengthInBits, kTotalPayloadDataSizeInBits)
- return str(bytes(fillBits).hex())
- def __init__(self, arguments) -> None:
- """ Do some checks on the received arguments.
- Generate the Spake2+ verifier if needed and assign the values
- to the global variables
- Args:
- The whole set of args passed to the script.
- """
- kMaxVendorNameLength = 32
- kMaxProductNameLength = 32
- kMaxHardwareVersionStringLength = 64
- kMaxSerialNumberLength = 32
- kUniqueIDLength = 16
- kMaxProductUrlLenght = 256
- kMaxPartNumberLength = 32
- kMaxProductLabelLength = 64
- INVALID_PASSCODES = [00000000, 11111111, 22222222, 33333333, 44444444,
- 55555555, 66666666, 77777777, 88888888, 99999999, 12345678, 87654321]
- assert (bool(arguments.gen_spake2p_path) != bool(arguments.spake2_verifier)
- ), "Provide either the spake2_verifier string or the path to the spake2 generator"
- assert not (arguments.passcode in INVALID_PASSCODES), "The provided passcode is invalid"
- self._args = arguments
- if self._args.unique_id:
- assert (len(bytearray.fromhex(self._args.unique_id)) == kUniqueIDLength), "Provide a 16 bytes unique id"
- if self._args.product_name:
- assert (len(self._args.product_name) <= kMaxProductNameLength), "Product name exceeds the size limit"
- if self._args.vendor_name:
- assert (len(self._args.vendor_name) <= kMaxVendorNameLength), "Vendor name exceeds the size limit"
- if self._args.hw_version_str:
- assert (len(self._args.hw_version_str) <= kMaxHardwareVersionStringLength), "Hardware version string exceeds the size limit"
- if self._args.serial_number:
- assert (len(self._args.serial_number) <= kMaxSerialNumberLength), "Serial number exceeds the size limit"
- if self._args.manufacturing_date:
- try:
- datetime.datetime.strptime(self._args.manufacturing_date, '%Y-%m-%d')
- except ValueError:
- raise ValueError("Incorrect manufacturing data format, should be YYYY-MM-DD")
- if self._args.commissioning_flow:
- assert (self._args.commissioning_flow <= 3), "Invalid commissioning flow value"
- if self._args.rendezvous_flag:
- assert (self._args.rendezvous_flag <= 7), "Invalid rendez-vous flag value"
- if self._args.gen_spake2p_path:
- self._args.spake2_verifier = self.generate_spake2p_verifier()
- if self._args.product_label:
- assert (len(self._args.product_label) <= kMaxProductLabelLength), "Product Label exceeds the size limit"
- if self._args.product_url:
- assert (len(self._args.product_url) <= kMaxProductUrlLenght), "Product URL exceeds the size limit"
- if self._args.part_number:
- assert (len(self._args.part_number) <= kMaxPartNumberLength), "Part number exceeds the size limit"
- def add_SerialNo_To_CMD(self, cmdList):
- """ Add the jtag serial command to the commander command
- Args:
- The commander command in list format
- """
- if self._args.jtag_serial:
- cmdList.extend(["--serialno", self._args.jtagSerial])
- def create_nvm3injected_image(self):
- """ Use commander command lines create a binary flashable to the EFR32
- containing the factory commissioning data in NVM3 section
- """
- isDeviceConnected = True
- # Retrieve the device current nvm3 data in a binary file
- # It will be used as base to add the new credentials
- inputImage = self.TEMP_FILE
- cmd = ['commander', 'nvm3', 'read', '-o', inputImage, ]
- self.add_SerialNo_To_CMD(cmd)
- results = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
- if results.returncode != 0:
- # No nvm3 section found. Retrieve the device info
- cmd = ['commander', 'device', 'info', ]
- self.add_SerialNo_To_CMD(cmd)
- try:
- output = subprocess.check_output(cmd)
- output = output.decode('utf-8').splitlines()
- deviceInfo = dict(map(str.strip, lines.split(':')) for lines in output[0:len(output)-1])
- # Only MG12 and MG24 are supported in matter currently
- if "EFR32MG12" in deviceInfo["Part Number"]:
- inputImage = self.BASE_MG12_FILE
- elif "EFR32MG24" in deviceInfo["Part Number"]:
- inputImage = self.BASE_MG24_FILE
- else:
- raise Exception('Invalid MCU')
- except Exception:
- isDeviceConnected = False
- print("Device not connected")
- # When no device is connected user needs to provide the mcu family for which those credentials are to be created
- if self._args.mcu_family:
- if "EFR32MG12" == self._args.mcu_family:
- inputImage = self.BASE_MG12_FILE
- elif "EFR32MG24" == self._args.mcu_family:
- inputImage = self.BASE_MG24_FILE
- else:
- print("Connect debug port or provide the mcu_family")
- return
- # Convert interger to little endian hex format and strings to hex byte array format for nvm3 storage
- spake2pIterationCount = self._args.spake2_iteration.to_bytes(4, 'little').hex()
- discriminator = self._args.discriminator.to_bytes(2, 'little').hex()
- saltByteArray = bytes(self._args.spake2_salt, 'utf-8').hex()
- verifierByteArray = bytes(self._args.spake2_verifier, 'utf-8').hex()
- productId = self._args.product_id.to_bytes(2, "little").hex()
- vendorId = self._args.vendor_id.to_bytes(2, "little").hex()
- # create the binary containing the new nvm3 data
- cmd = [
- "commander", "nvm3", "set", inputImage,
- "--object", self.DISCRIMINATOR_NVM3_KEY + str(discriminator),
- "--object", self.SETUP_PAYLOAD_NVM3_KEY + self.generateQrCodeBitSet(),
- "--object", self.ITERATIONCOUNT_NVM3_KEY + str(spake2pIterationCount),
- "--object", self.SALT_NVM3_KEY + str(saltByteArray),
- "--object", self.VERIFIER_NVM3_KEY + str(verifierByteArray),
- "--object", self.PRODUCT_ID_NVM3_KEY + str(productId),
- "--object", self.VENDOR_ID_NVM3_KEY + str(vendorId),
- ]
- if self._args.product_name:
- productNameByteArray = bytes(self._args.product_name, 'utf-8').hex()
- cmd.extend(["--object", self.PRODUCT_NAME_NVM3_KEY + str(productNameByteArray)])
- if self._args.vendor_name:
- vendorNameByteArray = bytes(self._args.vendor_name, 'utf-8').hex()
- cmd.extend(["--object", self.VENDOR_NAME_NVM3_KEY + str(vendorNameByteArray)])
- if self._args.hw_version:
- hwVersionByteArray = self._args.hw_version.to_bytes(2, "little").hex()
- cmd.extend(["--object", self.HW_VER_NVM3_KEY + str(hwVersionByteArray)])
- if self._args.hw_version_str:
- hwVersionByteArray = bytes(self._args.hw_version_str, 'utf-8').hex()
- cmd.extend(["--object", self.HW_VER_STR_NVM3_KEY + str(hwVersionByteArray)])
- if self._args.unique_id:
- cmd.extend(["--object", self.UNIQUE_ID_NVM3_KEY + self._args.unique_id])
- if self._args.manufacturing_date:
- dateByteArray = bytes(self._args.manufacturing_date, 'utf-8').hex()
- cmd.extend(["--object", self.MANUFACTURING_DATE_NVM3_KEY + str(dateByteArray)])
- if self._args.serial_number:
- serialNumberByteArray = bytes(self._args.serial_number, 'utf-8').hex()
- cmd.extend(["--object", self.SERIAL_NUMBER_NVM3_KEY + str(serialNumberByteArray)])
- if self._args.part_number:
- partNumberByteArray = bytes(self._args.part_number, 'utf-8').hex()
- cmd.extend(["--object", self.PART_NUMBER_NVM3_KEY + str(partNumberByteArray)])
- if self._args.product_label:
- productLabelByteArray = bytes(self._args.product_label, 'utf-8').hex()
- cmd.extend(["--object", self.PRODUCT_LABEL_NVM3_KEY + str(productLabelByteArray)])
- if self._args.product_url:
- productUrlByteArray = bytes(self._args.product_url, 'utf-8').hex()
- cmd.extend(["--object", self.PRODUCT_URL_NVM3_KEY + str(productUrlByteArray)])
- cmd.extend(["--outfile", self.OUT_FILE])
- results = subprocess.run(cmd)
- # A tempfile was create/used, delete it.
- if inputImage == self.TEMP_FILE:
- cmd = ['rm', '-rf', 'tmp_nvm3.s37', ]
- subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
- results.check_returncode
- # Flash the binary if the device is connected
- if isDeviceConnected:
- cmd = ['commander', 'flash', self.OUT_FILE, ]
- self.add_SerialNo_To_CMD(cmd)
- results = subprocess.run(cmd)
- def main():
- def all_int_format(i): return int(i, 0)
- parser = argparse.ArgumentParser(description='EFR32 NVM3 Factory data provider')
- parser.add_argument("--discriminator", type=all_int_format, required=True,
- help="[int| hex] Provide BLE pairing discriminator.")
- parser.add_argument("--passcode", type=all_int_format, required=True,
- help="[int | hex] Provide the default PASE session passcode.")
- parser.add_argument("--spake2_iteration", type=all_int_format, required=True,
- help="[int | hex int] Provide Spake2+ iteration count.")
- parser.add_argument("--spake2_salt", type=str, required=True,
- help="[string] Provide Spake2+ salt.")
- parser.add_argument("--spake2_verifier", type=str,
- help="[string] Provide Spake2+ verifier without generating it.")
- parser.add_argument("--gen_spake2p_path", type=str,
- help="[string] Provide a path to spake2p generator. It can be built from connectedhomeip/src/tools/spake2p")
- parser.add_argument("--mcu_family", type=str,
- help="[string] mcu Family target. Only need if your board isn't plugged in")
- parser.add_argument("--jtag_serial", type=str,
- help="[string] Provide the serial number of the jtag if you have more than one board connected")
- parser.add_argument("--product_id", type=all_int_format, default=32773,
- help="[int | hex int] Provide the product ID")
- parser.add_argument("--vendor_id", type=all_int_format, default=65521,
- help="[int | hex int] Provide the vendor ID")
- parser.add_argument("--product_name", type=str,
- help="[string] Provide the product name [optional]")
- parser.add_argument("--vendor_name", type=str,
- help="[string] Provide the vendor name [optional]")
- parser.add_argument("--hw_version", type=all_int_format,
- help="[int | hex int] Provide the hardware version value[optional]")
- parser.add_argument("--hw_version_str", type=str,
- help="[string] Provide the hardware version string[optional]")
- parser.add_argument("--product_label", type=str,
- help="[string] Provide the product label [optional]")
- parser.add_argument("--product_url", type=str,
- help="[string] Provide the product url [optional]")
- parser.add_argument("--unique_id", type=str,
- help="[hex_string] A 128 bits hex string unique id (without 0x) [optional]")
- parser.add_argument("--serial_number", type=str,
- help="[string] Provide serial number of the device")
- parser.add_argument("--manufacturing_date", type=str,
- help="[string] Provide Manufacturing date in YYYY-MM-DD format [optional]")
- parser.add_argument("--part_number", type=str,
- help="[string] Provide part number [optional]")
- parser.add_argument("--commissioning_flow", type=all_int_format, default=0,
- help="[int| hex] Provide Commissioning Flow: 0=Standard, 1=kUserActionRequired, 2=Custom (Default:Standard)")
- parser.add_argument("--rendezvous_flag", type=all_int_format, default=2,
- help="[int| hex] Provide Rendez-vous flag: 1=SoftAP, 2=BLE 4=OnNetwork (Default=BLE Only)")
- args = parser.parse_args()
- writer = FactoryDataWriter(args)
- writer.create_nvm3injected_image()
- if __name__ == "__main__":
- main()
|