Files
HBlink4/tests/test_padding_tolerance.py
Cort BuffingtonandClaude Opus 5 4024413c4c Pad the outbound RPTC blob like DMRGateway; tolerate NUL pads on ingest
utils.py: pad_text/pad_num/pad_decimal, one per convention in DMRGateway's
config-blob sprintf -- text left/space, numerics right/zero, lat-long
right/space. _send_outbound_config uses them; _send_outbound_options sends
the string unpadded.

safe_decode_bytes strips NUL as well as whitespace, so a client that
NUL-pads its fields matches a callsign ACL and reports cleanly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 09:00:25 -05:00

109 lines
4.2 KiB
Python

"""
Config-field padding, both directions.
Receive: a client may pad the fixed-width RPTC blob with NUL rather than the
space DMRGateway emits. str.strip() takes whitespace and not NUL, so unstripped
padding reaches dashboard JSON, float() conversions and callsign ACL matching
(where a pattern anchored as ^W0UK$ stops matching) with the fill attached.
Send: the blob this server emits, per DMRGateway's three padding conventions.
"""
import unittest
from hblink4.access_control import RepeaterMatcher
from hblink4.utils import pad_decimal, pad_num, pad_text, safe_decode_bytes
# Exact callsign pattern (no wildcard): re.escape + ^...$ anchoring is the case
# trailing padding breaks. A "W0UK*" pattern absorbs the fill into its .*.
_CONFIG = {
"repeaters": {
"patterns": [
{
"name": "Bridge endpoints",
"match": {"callsigns": ["W0UK"]},
"config": {
"passphrase": "bridge-key",
"slot1_talkgroups": [2, 9],
"slot2_talkgroups": [3120],
},
},
],
"default": {
"passphrase": "passw0rd",
"slot1_talkgroups": [1],
"slot2_talkgroups": [2],
},
}
}
class TestConfigFieldPadding(unittest.TestCase):
def test_nul_padding_is_stripped(self):
self.assertEqual(safe_decode_bytes(b'W0UK' + b'\x00' * 4), 'W0UK')
self.assertEqual(
safe_decode_bytes(b'Lawrence, KS' + b'\x00' * 8), 'Lawrence, KS')
def test_space_padding_still_stripped(self):
self.assertEqual(safe_decode_bytes(b'WA0EDA '), 'WA0EDA')
self.assertEqual(safe_decode_bytes(b'Lawrence, KS '), 'Lawrence, KS')
def test_padded_numeric_field_parses(self):
self.assertEqual(float(safe_decode_bytes(b'38.8500' + b'\x00')), 38.85)
def test_empty_and_all_padding(self):
self.assertEqual(safe_decode_bytes(b''), '')
self.assertEqual(safe_decode_bytes(b'\x00' * 124), '')
self.assertEqual(safe_decode_bytes(b' ' * 124), '')
class TestOutboundFieldEncoders(unittest.TestCase):
# Each mirrors one conversion in DMRGateway's config-blob sprintf,
# "%-8.8s%09u%09u%02u%02u%8.8s%9.9s%03d%-20.20s%-19.19s%c%-124.124s%-40.40s%-40.40s".
def test_text_is_left_justified_and_space_filled(self):
self.assertEqual(pad_text('W0UK', 8), b'W0UK ')
self.assertEqual(pad_text('Lawrence, KS', 20), b'Lawrence, KS' + b' ' * 8)
def test_numeric_is_right_justified_and_zero_filled(self):
self.assertEqual(pad_num(5, 2), b'05') # "%02u" -- not b'5 '
self.assertEqual(pad_num('1', 2), b'01')
self.assertEqual(pad_num(10, 3), b'010') # "%03d"
self.assertEqual(pad_num(444000000, 9), b'444000000')
def test_decimal_is_right_justified_and_space_filled(self):
self.assertEqual(pad_decimal('38.8500', 8), b' 38.8500')
self.assertEqual(pad_decimal('-097.6114', 9), b'-097.6114')
def test_no_encoder_emits_nul(self):
for field in (pad_text('', 124), pad_num('', 9), pad_decimal('', 8)):
self.assertNotIn(0, field)
def test_overlong_values_are_truncated_to_the_field_width(self):
self.assertEqual(pad_text('WAYTOOLONGCALL', 8), b'WAYTOOLO')
self.assertEqual(len(pad_num('1234567890123', 9)), 9)
class TestCallsignACLWithPaddedCallsign(unittest.TestCase):
def setUp(self):
self.matcher = RepeaterMatcher(_CONFIG)
def test_nul_padded_callsign_matches_exact_pattern(self):
callsign = safe_decode_bytes(b'W0UK' + b'\x00' * 4)
config = self.matcher.get_repeater_config(3120666, callsign)
self.assertEqual(config.passphrase, 'bridge-key')
self.assertEqual(config.slot2_talkgroups, [3120])
def test_space_padded_callsign_matches_exact_pattern(self):
callsign = safe_decode_bytes(b'W0UK ')
config = self.matcher.get_repeater_config(3120666, callsign)
self.assertEqual(config.passphrase, 'bridge-key')
def test_unpadded_callsign_still_falls_through_to_default(self):
config = self.matcher.get_repeater_config(3120666, 'KB1ABC')
self.assertEqual(config.passphrase, 'passw0rd')
if __name__ == '__main__':
unittest.main()