From 585901b899da052f13bef757a9862d50bdc9c5e8 Mon Sep 17 00:00:00 2001 From: Aaron Shaw Date: Tue, 14 Mar 2023 23:46:17 +0000 Subject: [PATCH] feat: add support for multiple possible key locations Syncrobit has multiple potential key locations, change to list and parse list if necessary Relates-to: #221 Relates-to: https://github.com/NebraLtd/helium-syncrobit/issues/2#issuecomment-1426611661 Supersedes: #222 --- hm_pyhelper/hardware_definitions.py | 53 +++++++++--------- hm_pyhelper/miner_param.py | 68 ++++++++++++++++++++-- hm_pyhelper/tests/test_miner_param.py | 81 +++++++++++++++++++++++++-- setup.py | 2 +- 4 files changed, 166 insertions(+), 38 deletions(-) diff --git a/hm_pyhelper/hardware_definitions.py b/hm_pyhelper/hardware_definitions.py index 8f84c90..87df0d4 100644 --- a/hm_pyhelper/hardware_definitions.py +++ b/hm_pyhelper/hardware_definitions.py @@ -26,7 +26,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi3-64'], 'SPIBUS': 'spidev1.2', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 38, 'MAC': 'eth0', 'STATUS': 25, @@ -47,7 +47,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi3-64'], 'SPIBUS': 'spidev1.2', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 38, 'MAC': 'eth0', 'STATUS': 25, @@ -70,7 +70,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['rockpi-4b-rk3399'], 'SPIBUS': 'spidev32766.0', - 'SWARM_KEY_URI': 'ecc://i2c-7:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-7:96?slot=0'], 'RESET': 149, 'MAC': 'eth0', 'STATUS': 156, @@ -95,7 +95,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'armv6hf', 'BALENA_DEVICE_TYPE': ['raspberry-pi'], 'SPIBUS': 'spidev1.2', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'wlan0', 'STATUS': 24, @@ -116,7 +116,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'armv6hf', 'BALENA_DEVICE_TYPE': ['raspberry-pi'], 'SPIBUS': 'spidev1.2', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'wlan0', 'STATUS': 24, @@ -137,7 +137,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['radxa-zero'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-3:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-3:96?slot=0'], 'RESET': 415, 'MAC': 'wlan0', 'STATUS': 421, @@ -158,7 +158,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'armv6hf', 'BALENA_DEVICE_TYPE': ['raspberry-pi'], 'SPIBUS': 'spidev1.2', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 4, 'MAC': 'wlan0', 'STATUS': 26, @@ -179,7 +179,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'armv7hf', 'BALENA_DEVICE_TYPE': ['beaglebone-black'], 'SPIBUS': 'spidev1.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 60, 'MAC': 'eth0', 'STATUS': 31, @@ -200,7 +200,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'armv7hf', 'BALENA_DEVICE_TYPE': ['beaglebone-pocket'], 'SPIBUS': 'spidev1.2', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 60, 'MAC': 'wlan0', 'STATUS': 31, @@ -221,7 +221,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['rockpi-4b-rk3399'], 'SPIBUS': 'spidev32766.0', - 'SWARM_KEY_URI': 'ecc://i2c-7:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-7:96?slot=0'], 'RESET': 149, 'MAC': 'eth0', 'STATUS': 156, @@ -244,7 +244,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'armv7hf', 'BALENA_DEVICE_TYPE': ['asus-tinker-board'], 'SPIBUS': 'spidev2.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 167, 'MAC': 'eth0', 'STATUS': 163, @@ -265,7 +265,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 25, 'MAC': 'wlan0', 'STATUS': 20, @@ -286,8 +286,8 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi3-64', 'raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', - 'ONBOARDING_KEY_URI': 'ecc://i2c-1:96?slot=15', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=15'], 'RESET': 25, 'MAC': 'wlan0', 'STATUS': 20, @@ -308,7 +308,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, @@ -329,7 +329,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 23, 'MAC': 'wlan0', 'STATUS': 22, @@ -350,7 +350,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS_LED': { @@ -376,7 +376,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-0:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-0:96?slot=0'], 'RESET': 23, 'MAC': 'eth0', 'STATUS': 17, @@ -397,7 +397,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 4, 'MAC': 'wlan0', 'STATUS_LED': { @@ -425,7 +425,7 @@ def is_raspberry_pi() -> bool: 'raspberrypi0-2w-64', 'raspberrypi3-64', 'raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'eth0', 'STATUS': 20, @@ -448,7 +448,7 @@ def is_raspberry_pi() -> bool: 'raspberrypi0-2w-64', 'raspberrypi3-64', 'raspberrypi4-64'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'eth0', 'STATUS': 20, @@ -469,7 +469,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, @@ -490,7 +490,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-10:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-10:96?slot=0'], 'RESET': 23, 'MAC': 'wlan0', 'STATUS': 22, @@ -511,7 +511,8 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0', 'ecc://i2c-1:88?slot=2'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0', 'ecc://i2c-1:88?slot=15'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, @@ -532,7 +533,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', # There is a CSN1 pin which is connected to GPIO6 (HAT Pin 31) - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'eth0', 'STATUS': 21, # Stub. There is no status LED on X3. I2C-3 is used for display @@ -553,7 +554,7 @@ def is_raspberry_pi() -> bool: 'CPU_ARCH': 'arm64', 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', - 'SWARM_KEY_URI': 'ecc://i2c-1:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, diff --git a/hm_pyhelper/miner_param.py b/hm_pyhelper/miner_param.py index e99b823..e33cb0e 100644 --- a/hm_pyhelper/miner_param.py +++ b/hm_pyhelper/miner_param.py @@ -3,6 +3,7 @@ import subprocess import json import platform +from urllib.parse import urlparse from packaging.version import Version from retry import retry @@ -111,6 +112,28 @@ def get_gateway_mfr_version() -> Version: raise GatewayMFRInvalidVersion(err_str).with_traceback(e.__traceback__) +def get_ecc_location() -> str: + ecc_list = get_variant_attribute(os.getenv('VARIANT'), 'SWARM_KEY_URI') + + if os.getenv('SWARM_KEY_URI_OVERRIDE'): + ecc_location = os.getenv('SWARM_KEY_URI_OVERRIDE') + elif len(ecc_list) == 1: + ecc_location = ecc_list[0] + else: + for location in ecc_list: + parse_result = urlparse(location) + i2c_bus = parse_i2c_bus(parse_result.hostname) + i2c_address = parse_i2c_address(parse_result.port) + command = f'i2cdetect -y {i2c_bus}' + parameter = f'{i2c_address} --' + + if config_search_param(command, parameter): + ecc_location = location + return ecc_location + + return ecc_location + + def get_gateway_mfr_command(sub_command: str, slot: int = False) -> list: gateway_mfr_path = get_gateway_mfr_path() command = [gateway_mfr_path] @@ -119,14 +142,9 @@ def get_gateway_mfr_command(sub_command: str, slot: int = False) -> list: if gateway_mfr_version >= Version('0.2.0'): try: - if os.getenv('SWARM_KEY_URI_OVERRIDE'): - ecc_location = os.getenv('SWARM_KEY_URI_OVERRIDE') - else: - ecc_location = get_variant_attribute(os.getenv('VARIANT'), 'SWARM_KEY_URI') - device_arg = [ '--device', - ecc_location + get_ecc_location() ] command.extend(device_arg) except (UnknownVariantException, UnknownVariantAttributeException) as e: @@ -334,3 +352,41 @@ def await_spi_available(spi_bus): return True else: raise SPIUnavailableException("SPI bus %s not found!" % spi_bus) + + +@lock_ecc() +def config_search_param(command, param): + """ + input: + command: Command to execute + param: The parameter we are looking for in the response + return: True is exist, or False if doesn't exist + Possible exceptions: + TypeError: If the arguments passed to the function are not strings. + """ + if type(command) is not str: + raise TypeError("The command must be a string value") + if type(param) is not str: + raise TypeError("The param must be a string value") + result = subprocess.Popen(command.split(), stdout=subprocess.PIPE) + out, err = result.communicate() + out = out.decode("UTF-8") + if param in out: + return True + else: + return False + + +def parse_i2c_bus(address): + """ + Takes i2c bus as input parameter, extracts the bus number and returns it. + """ + i2c_bus_pattern = r'i2c-(\d+)' + return re.search(i2c_bus_pattern, address).group(1) + + +def parse_i2c_address(port): + """ + Takes i2c address in decimal as input parameter, extracts the hex version and returns it. + """ + return f'{port:x}' diff --git a/hm_pyhelper/tests/test_miner_param.py b/hm_pyhelper/tests/test_miner_param.py index fbeb25b..de2e7b7 100644 --- a/hm_pyhelper/tests/test_miner_param.py +++ b/hm_pyhelper/tests/test_miner_param.py @@ -1,17 +1,19 @@ import json import unittest import pytest -from unittest.mock import ANY, mock_open, patch +import sys +from unittest.mock import ANY, mock_open, patch, Mock from packaging.version import Version from hm_pyhelper.exceptions import ECCMalfunctionException, \ MinerFailedToFetchMacAddress, GatewayMFRInvalidVersion, GatewayMFRExecutionException, \ GatewayMFRFileNotFoundException, UnsupportedGatewayMfrVersion from hm_pyhelper.lock_singleton import ResourceBusyError from hm_pyhelper.miner_param import retry_get_region, await_spi_available, \ - provision_key, run_gateway_mfr, get_gateway_mfr_path, \ - did_gateway_mfr_test_result_include_miner_key_pass, \ + provision_key, run_gateway_mfr, get_gateway_mfr_path, config_search_param, \ + did_gateway_mfr_test_result_include_miner_key_pass, parse_i2c_address, parse_i2c_bus, \ get_mac_address, get_public_keys_rust, get_gateway_mfr_version, get_gateway_mfr_command +sys.path.append("..") ALL_PASS_GATEWAY_MFR_TESTS = { 'ecdh(0)': {'error': 'decode error\n\nCaused by:\n not a compact key', 'result': 'fail'}, @@ -61,11 +63,16 @@ MOCK_VARIANT_DEFINITIONS = { 'NEBHNT-WITH-ECC-ADDRESS': { 'KEY_STORAGE_BUS': '/dev/i2c-X', - 'SWARM_KEY_URI': 'ecc://i2c-X:96?slot=0', + 'SWARM_KEY_URI': ['ecc://i2c-X:96?slot=0'], }, 'NEBHNT-NO-ECC-ADDRESS': { 'NO_KEY_STORAGE_BUS': '/dev/i2c-X', - 'NO_KEY_SWARM_KEY_URI': 'ecc://i2c-X:96?slot=0', + 'NO_KEY_SWARM_KEY_URI': ['ecc://i2c-X:96?slot=0'], + }, + 'NEBHNT-MULTIPLE-ECC-ADDRESS': { + 'KEY_STORAGE_BUS': '/dev/i2c-2', + 'SWARM_KEY_URI': ['ecc://i2c-3:96?slot=0', 'ecc://i2c-4:88?slot=10'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-3:96?slot=0', 'ecc://i2c-4:88?slot=15'], } } @@ -157,6 +164,31 @@ def test_get_gateway_mfr_command_v021_no_SWARM_KEY_URI(self, mocked_get_gateway_ expected_result = [ANY, 'test'] self.assertListEqual(actual_result, expected_result) + @patch.dict('os.environ', {"VARIANT": "NEBHNT-MULTIPLE-ECC-ADDRESS"}) + @patch('subprocess.Popen') + @patch('hm_pyhelper.miner_param.get_gateway_mfr_version', + return_value=Version('0.2.1')) + def test_get_gateway_mfr_command_v021_multi_SWARM_KEY_URI(self, mocked_get_gateway_mfr_version, + mock_subproc_popen): + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode("60 --"), 'error')} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + + actual_result = get_gateway_mfr_command('key') + expected_result = [ANY, '--device', 'ecc://i2c-3:96?slot=0', 'key'] + self.assertListEqual(actual_result, expected_result) + mocked_get_gateway_mfr_version.assert_called_once() + + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode("58 --"), 'error')} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + + actual_result = get_gateway_mfr_command('test') + expected_result = [ANY, '--device', 'ecc://i2c-4:88?slot=10', 'test'] + self.assertListEqual(actual_result, expected_result) + @patch('hm_pyhelper.miner_param.get_gateway_mfr_version', return_value=Version('0.2.0')) def test_get_gateway_mfr_command_v020(self, mocked_get_gateway_mfr_version): @@ -337,3 +369,42 @@ def test_get_gateway_mfr_path(self, mock_dir, mock_platform): actual_result = get_gateway_mfr_path() expected_result = "/test/this/works/gateway_mfr_aarch64" self.assertEqual(actual_result, expected_result) + + def test_parse_i2c_address(self): + port = 96 + output = parse_i2c_address(port) + hex_i2c_address = '60' + + self.assertEqual(output, hex_i2c_address) + + def test_parse_i2c_bus(self): + bus = 'i2c-1' + output = parse_i2c_bus(bus) + i2c_bus = '1' + + self.assertEqual(output, i2c_bus) + + +class TestConfigSearch(unittest.TestCase): + @patch('subprocess.Popen') + def test_correct_param(self, mock_subproc_popen): + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode("60--"), 'error')} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + result = config_search_param("somecommand", "60--") + self.assertEqual(result, True) + + @patch('subprocess.Popen') + def test_incorrect_param(self, mock_subproc_popen): + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode('output'), 'error')} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + result = config_search_param("somecommand", "60--") + self.assertEqual(result, False) + + def test_types(self): + self.assertRaises(TypeError, config_search_param, 1, 2) + self.assertRaises(TypeError, config_search_param, "123321", 1) + self.assertRaises(TypeError, config_search_param, 1, "123321") diff --git a/setup.py b/setup.py index f4f2112..0ea2567 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='hm_pyhelper', - version='0.13.55', + version='0.13.56', author="Nebra Ltd", author_email="support@nebra.com", description="Helium Python Helper",