mirror of
https://github.com/n0mjs710/HBlink4.git
synced 2026-11-08 04:19:26 +00:00
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>
109 lines
4.2 KiB
Python
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()
|