Skip to content

Commit

Permalink
fix: gracefully handle public key failures
Browse files Browse the repository at this point in the history
- Return 'Unavailable' if ECC does not return public keys, instead of
crashing.
- Attempt to retrieve key each time it is requested over bluetooth,
unless the value is already set. Was previously only reading value
once at start.
- Added public_key to shared state so that a single request can
be used for both PublicKeyCharacteristic and
OnboardingKeyCharacteristic.
  • Loading branch information
marvinmarnold committed Nov 25, 2021
1 parent 3b2e0fe commit baeea6b
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@


class OnboardingKeyCharacteristic(Characteristic):
def __init__(self, service, onboarding_key):
def __init__(self, service, shared_state):
Characteristic.__init__(
self, constants.ONBOARDING_KEY_CHARACTERISTIC_UUID,
["read"], service)
self.add_descriptor(OnboardingKeyDescriptor(self))
self.add_descriptor(UTF8FormatDescriptor(self))
self.onboarding_key = onboarding_key
self.shared_state = shared_state

def ReadValue(self, options):
logger.debug("Read Onboarding Key")
logger.debug("Onboarding key: %s" % self.onboarding_key)
return string_to_dbus_encoded_byte_array(self.onboarding_key)
# Onboarding key is always identical to public key
self.shared_state.load_public_key()
logger.debug("Onboarding key: %s" % self.shared_state.public_key)
return string_to_dbus_encoded_byte_array(self.shared_state.public_key)
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@

class PublicKeyCharacteristic(Characteristic):

def __init__(self, service, pub_key):
def __init__(self, service, shared_state):
Characteristic.__init__(
self, constants.PUBLIC_KEY_CHARACTERISTIC_UUID,
["read"], service)
self.add_descriptor(PublicKeyDescriptor(self))
self.add_descriptor(UTF8FormatDescriptor(self))
self.pub_key = pub_key
self.shared_state = shared_state

def ReadValue(self, options):
logger.debug("Read Public Key: %s", self.pub_key)
return string_to_dbus_encoded_byte_array(self.pub_key)
logger.debug("Read Public Key")
self.shared_state.load_public_key()
logger.debug("Read Public Key: %s", self.shared_state.public_key)
return string_to_dbus_encoded_byte_array(self.shared_state.public_key)
6 changes: 3 additions & 3 deletions gatewayconfig/bluetooth/services/helium_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@

class HeliumService(Service):

def __init__(self, index, eth0_mac_address, wlan0_mac_address, onboarding_key, pub_key, firmware_version, ethernet_is_online_filepath, shared_state):
def __init__(self, index, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, shared_state):

Service.__init__(self, index, constants.HELIUM_SERVICE_UUID, True)
self.add_characteristic(OnboardingKeyCharacteristic(self, onboarding_key))
self.add_characteristic(PublicKeyCharacteristic(self, pub_key))
self.add_characteristic(OnboardingKeyCharacteristic(self, shared_state))
self.add_characteristic(PublicKeyCharacteristic(self, shared_state))
self.add_characteristic(WifiServicesCharacteristic(self, shared_state))
self.add_characteristic(WifiConfiguredServicesCharacteristic(self, shared_state))
self.add_characteristic(DiagnosticsCharacteristic(self, eth0_mac_address, wlan0_mac_address))
Expand Down
15 changes: 3 additions & 12 deletions gatewayconfig/gatewayconfig_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,9 @@ def __init__(self, sentry_dsn, balena_app_name, balena_device_uuid, variant, eth
eth0_mac_address = read_eth0_mac_address(eth0_mac_address_filepath)
wlan0_mac_address = read_wlan0_mac_address(wlan0_mac_address_filepath)
logger.debug("Read eth0 mac address %s and wlan0 %s" % (eth0_mac_address, wlan0_mac_address))
self.shared_state.load_public_key()

diagnostics_response = requests.get(diagnostics_json_url)
diagnostics_json = diagnostics_response.json()
pub_key = diagnostics_json['PK']
onboarding_key = diagnostics_json['OK']
animal_name = diagnostics_json['AN']
logger.debug(
"Read onboarding pub_key: %s + animal_name: %s" % (
pub_key, animal_name
)
)

self.bluetooth_services_processor = BluetoothServicesProcessor(eth0_mac_address, wlan0_mac_address, onboarding_key, pub_key, firmware_version, ethernet_is_online_filepath, self.shared_state)
self.bluetooth_services_processor = BluetoothServicesProcessor(eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, self.shared_state)
self.led_processor = LEDProcessor(self.status_led, self.shared_state)
self.diagnostics_processor = DiagnosticsProcessor(
diagnostics_json_url,
Expand Down Expand Up @@ -132,3 +122,4 @@ def get_button_pin(self):

def get_status_led_pin(self):
return self.variant_details['STATUS']

22 changes: 21 additions & 1 deletion gatewayconfig/gatewayconfig_shared_state.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import json
from hm_pyhelper.miner_param import get_public_keys_rust
from hm_pyhelper.logger import get_logger

LOGGER = get_logger(__name__)
PUBLIC_KEY_UNAVAILABLE = 'Unavailable'

# Context is shared between multiple threads/processes.
# This class simplifies and centralizes read/write of the state:
Expand All @@ -10,6 +15,21 @@ def __init__(self):
self.should_advertise_bluetooth = True
self.is_advertising_bluetooth = False
self.are_diagnostics_ok = False
self.public_key = PUBLIC_KEY_UNAVAILABLE

def to_s(self):
return json.dumps(vars(self))
return json.dumps(vars(self))

def load_public_key(self):
"""
Attempt to retrieve the public key unless it has already
completed successfully. Keys are never expected to update.
"""
if self.public_key != PUBLIC_KEY_UNAVAILABLE:
return

try:
public_keys = get_public_keys_rust()
self.public_key = public_keys['PK']
except Exception:
LOGGER.exception("Unable to read public key.")
4 changes: 2 additions & 2 deletions gatewayconfig/processors/bluetooth_services_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@


class BluetoothServicesProcessor(Application):
def __init__(self, eth0_mac_address, wlan0_mac_address, onboarding_key, pub_key, firmware_version, ethernet_is_online_filepath, shared_state):
def __init__(self, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, shared_state):
super().__init__()
self.add_service(DeviceInformationService(0, eth0_mac_address))
self.add_service(HeliumService(1, eth0_mac_address, wlan0_mac_address, onboarding_key, pub_key, firmware_version, ethernet_is_online_filepath, shared_state))
self.add_service(HeliumService(1, eth0_mac_address, wlan0_mac_address, firmware_version, ethernet_is_online_filepath, shared_state))
self.register()

# Unlike the other processors, #run is not defined here. Instead, Application#run is used
Expand Down
16 changes: 15 additions & 1 deletion tests/gatewayconfig/test_gatewayconfig_shared_state.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from unittest import TestCase
from unittest.mock import patch

from gatewayconfig.gatewayconfig_shared_state import GatewayconfigSharedState

Expand All @@ -16,4 +17,17 @@ def test_init(self):
def test_to_s(self):
shared_state = GatewayconfigSharedState()
self.assertEqual(shared_state.to_s(),
'{"wifi_list_cache": [], "should_scan_wifi": false, "should_advertise_bluetooth": true, "is_advertising_bluetooth": false, "are_diagnostics_ok": false}')
'{"wifi_list_cache": [], "should_scan_wifi": false, "should_advertise_bluetooth": true, "is_advertising_bluetooth": false, "are_diagnostics_ok": false, "public_key": "Unavailable"}')

@patch('gatewayconfig.gatewayconfig_shared_state.get_public_keys_rust', return_value={'PK': 'foo'})
def test_load_public_key(self, _):
shared_state = GatewayconfigSharedState()
shared_state.load_public_key()
self.assertEqual(shared_state.public_key, 'foo')

@patch('gatewayconfig.gatewayconfig_shared_state.get_public_keys_rust', return_value={'PK': 'foo'})
def test_load_public_key_dont_override(self, _):
shared_state = GatewayconfigSharedState()
shared_state.public_key = 'already_set'
shared_state.load_public_key()
self.assertEqual(shared_state.public_key, 'already_set')

0 comments on commit baeea6b

Please sign in to comment.