Skip to content

Commit

Permalink
serial: check for known incorrect rockpi and bobcat serials
Browse files Browse the repository at this point in the history
- check for known incorrect rockpi serials (and similar 10 char non hex serials)
- check for known incorrect bobcat serials
- check for missing or too-short serials
- add more tests for serial number determination
- bump hm-pyhelper

Relates-to: #507
Relates-to: #632
Relates-to: #630
  • Loading branch information
shawaj committed Jul 2, 2023
1 parent 16b483c commit 3626fb5
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 9 deletions.
225 changes: 224 additions & 1 deletion hw_diag/tests/test_get_serial_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@

from unittest.mock import mock_open, patch
from hw_diag.utilities.hardware import get_serial_number, load_serial_number, \
load_cpu_info
load_cpu_info, has_valid_serial

TEST_SERIAL = "00000000a3e7kg80"
TEST_SERIAL_ALL_ZERO = "000000000000000000000000000000"
TEST_SERIAL_ROCKPI = "d18dbe5c2a58cc61"
TEST_SERIAL_BOBCAT = "ba033cbdca6d626f"
TEST_SERIAL_RASPI = "000000009e3cb787"
TEST_SERIAL_ROCKPI_WRONG = "W1EP3DN9PU"
TEST_SERIAL_BOBCAT_WRONG = "c3d9b8674f4b94f6"
TEST_SERIAL_SHORT = "123ABC"
TEST_SERIAL_EMPTY = ""

TEST_CPU_INFO = """
processor : 0
Expand All @@ -22,9 +30,18 @@

TEST_SERIAL_NUMBER_RESULT = {'serial': '00000000a3e7kg80'}
TEST_CPU_INFO_RESULT = {'serial': '912558f1a3ae877d'}
SERIAL_VALID = {'serial': '912558f1a3ae877dabcdef1234567890ABCDEF'}
SERIAL_ALL_ZERO = {'serial': '000000000000000'}
SERIAL_WRONG_ROCKPI = {'serial': 'CKHZ4CHI1P'}
SERIAL_WRONG_BOBCAT = {'serial': 'c3d9b8674f4b94f6'}
SERIAL_NON_HEX_TEN_DIGITS = {'serial': 'XXXYYYZZZZ'}
SERIAL_SHORT = {'serial': 'ABC123abc'}
SERIAL_BLANK = {'serial': ''}
SERIAL_MISSING = {}
FAILED_CPU_INFO_RESULT = {}
FAILED_SERIAL_NUMBER_RESULT = {}


class TestGetSerialNumber(unittest.TestCase):

right_value = {'serial_number': '00000000a3e7kg80'}
Expand Down Expand Up @@ -89,3 +106,209 @@ def test_load_cpuinfo_fail(self):
captured = self.caplog
self.assertTrue('failed to load /proc/cpuinfo' in str(captured.text))
self.assertEqual(cpuinfo, FAILED_CPU_INFO_RESULT)

def test_has_valid_serial_all_zeros(self):
self.assertFalse(has_valid_serial(SERIAL_ALL_ZERO))

def test_has_valid_serial_non_hex(self):
self.assertFalse(has_valid_serial(SERIAL_NON_HEX_TEN_DIGITS))

def test_has_valid_serial_knonwn_wrong(self):
self.assertFalse(has_valid_serial(SERIAL_WRONG_ROCKPI))

def test_has_valid_serial_knonwn_wrong(self):
self.assertFalse(has_valid_serial(SERIAL_WRONG_BOBCAT))

def test_has_valid_serial_mising(self):
self.assertFalse(has_valid_serial(SERIAL_MISSING))

def test_has_valid_serial_blank(self):
self.assertFalse(has_valid_serial(SERIAL_BLANK))

def test_has_valid_serial_short(self):
self.assertFalse(has_valid_serial(SERIAL_SHORT))

def test_has_valid_serial_true(self):
self.assertTrue(has_valid_serial(SERIAL_VALID))

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_rockpi(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = True
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_ROCKPI}
mock_serial.return_value = {'serial': TEST_SERIAL_ROCKPI_WRONG}

right_value = {'serial_number': 'd18dbe5c2a58cc61'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_all_zero(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = True
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_ALL_ZERO}
mock_serial.return_value = {'serial': TEST_SERIAL_ALL_ZERO}

right_value = {'serial_number': 'Serial number not found'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()
self.assertEqual(mock_rockpi.call_count, 2)

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_rockpi_raspi(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = True
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_ROCKPI}
mock_serial.return_value = {'serial': TEST_SERIAL}

right_value = {'serial_number': 'd18dbe5c2a58cc61'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_short_wrong(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = False
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_SHORT}
mock_serial.return_value = {'serial': TEST_SERIAL_BOBCAT_WRONG}

right_value = {'serial_number': 'Serial number not found'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()
self.assertEqual(mock_rockpi.call_count, 2)

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_raspi_rockpi(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = False
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_ROCKPI}
mock_serial.return_value = {'serial': TEST_SERIAL}

right_value = {'serial_number': '00000000a3e7kg80'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_raspi(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = False
mock_cpuinfo.return_value = {'serial': TEST_SERIAL}
mock_serial.return_value = {'serial': TEST_SERIAL}

right_value = {'serial_number': '00000000a3e7kg80'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_bobcat(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = False
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_BOBCAT}
mock_serial.return_value = {'serial': TEST_SERIAL_BOBCAT_WRONG}

right_value = {'serial_number': 'ba033cbdca6d626f'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_rockpi_zero(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = True
mock_cpuinfo.return_value = {'serial': TEST_SERIAL_ALL_ZERO}
mock_serial.return_value = {'serial': TEST_SERIAL_ROCKPI}

right_value = {'serial_number': 'd18dbe5c2a58cc61'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()
self.assertEqual(mock_rockpi.call_count, 2)

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_rockpi_backwards(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = True
mock_cpuinfo.return_value = {'serial': TEST_SERIAL}
mock_serial.return_value = {'serial': TEST_SERIAL_ROCKPI}

right_value = {'serial_number': '00000000a3e7kg80'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
def test_get_serial_number_raspi_empty(self, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = False
mock_cpuinfo.return_value = {'serial': TEST_SERIAL}
mock_serial.return_value = {'serial': TEST_SERIAL_EMPTY}

right_value = {'serial_number': '00000000a3e7kg80'}
get_serial_number(self.diag)
self.assertEqual(self.diag["serial_number"],
right_value["serial_number"])

mock_rockpi.assert_called_once()
mock_cpuinfo.assert_called_once()
mock_serial.assert_called_once()

@patch('hw_diag.utilities.hardware.is_rockpi')
@patch('hw_diag.utilities.hardware.load_cpu_info')
@patch('hw_diag.utilities.hardware.load_serial_number')
@patch('hw_diag.utilities.hardware.has_valid_serial')
def test_has_valid_serial_called(self, mock_valid_serial, mock_serial, mock_cpuinfo, mock_rockpi):
mock_rockpi.return_value = False
mock_cpuinfo.return_value = {'serial': TEST_SERIAL}
mock_serial.return_value = {'serial': TEST_SERIAL_EMPTY}

get_serial_number(self.diag)
mock_valid_serial.assert_called_once()
41 changes: 38 additions & 3 deletions hw_diag/utilities/hardware.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dbus
import os
import psutil
import string
from typing import Union
from urllib.parse import urlparse
from hm_pyhelper.logger import get_logger
Expand Down Expand Up @@ -78,6 +79,20 @@
DTPARAM_CONFIG_VAR_NAMES = ['BALENA_HOST_CONFIG_dtparam', 'RESIN_HOST_CONFIG_dtparam']
EXT_ANT_DTPARAM = '"ant2"'

INCORRECT_BOBCAT_SERIALS = ['c3d9b8674f4b94f6']
INCORRECT_ROCKPI_SERIALS = [
'W1EP3DN9PU',
'0UQMAKIBII',
'PN06893W5V',
'KLOFHWLY95',
'3IT1I4E9TG',
'CKHZ4CHI1P',
'I4YE1UGF5N',
'PERTSKMCT0',
'S63QCF54CJ',
'YYMSYLJWG8'
]


def should_display_lte(diagnostics):
variant = diagnostics.get('VA')
Expand Down Expand Up @@ -255,11 +270,11 @@ def get_serial_number(diagnostics):
cpuinfo = load_cpu_info()
serial = load_serial_number()
serial_number = ""
if has_valid_serial(serial) and not is_rockpi():
if not is_rockpi() and has_valid_serial(serial):
serial_number = serial[CPUINFO_SERIAL_KEY]
elif has_valid_serial(cpuinfo):
serial_number = cpuinfo[CPUINFO_SERIAL_KEY]
elif has_valid_serial(serial) and is_rockpi():
elif is_rockpi() and has_valid_serial(serial):
serial_number = serial[CPUINFO_SERIAL_KEY]
else:
serial_number = "Serial number not found"
Expand All @@ -275,11 +290,31 @@ def has_valid_serial(cpuinfo: dict) -> bool:
if CPUINFO_SERIAL_KEY not in cpuinfo:
return False

# Check if serial number is all 0s...
serial_number = cpuinfo[CPUINFO_SERIAL_KEY]

# Check if serial number is all 0s...
if all(c in '0' for c in str(serial_number)):
return False

# Check if serial number is a known incorrect ROCKPi serial
# in INCORRECT_ROCKPI_SERIALS...
if str(serial_number) in INCORRECT_ROCKPI_SERIALS:
return False

# Check if serial number is a known incorrect Bobcat serial
# in INCORRECT_BOBCAT_SERIALS...
if str(serial_number) in INCORRECT_BOBCAT_SERIALS:
return False

# Check if serial number is 10 characters and non-hexadecimal...
if len(str(serial_number)) == 10 and not all(
c in string.hexdigits for c in str(serial_number)):
return False

# Check if serial number is < 10 characters...
if len(str(serial_number)) < 10:
return False

return True


Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Flask-APScheduler = "~1.12.4"
Flask-Caching = "~2.0.1"
grpcio = "~1.53.0"
gunicorn = "~20.1.0"
hm-pyhelper = "0.14.19"
hm-pyhelper = "0.14.20"
icmplib = "~3.0.3"
ipaddress = "~1.0.23"
password-strength = "~0.0.3.post2"
Expand Down

0 comments on commit 3626fb5

Please sign in to comment.