You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
FreeDMR/tests/test_auxiliary_tools.py

651 lines
21 KiB

import importlib
import io
import sys
import types
import unittest
from contextlib import redirect_stdout
class AuxiliaryToolTests(unittest.TestCase):
def test_report_receiver_bool_flag(self):
import report_receiver
self.assertTrue(report_receiver.bool_flag("1"))
self.assertTrue(report_receiver.bool_flag("true"))
self.assertTrue(report_receiver.bool_flag("yes"))
self.assertFalse(report_receiver.bool_flag("0"))
self.assertFalse(report_receiver.bool_flag(""))
self.assertFalse(report_receiver.bool_flag(None))
def test_ami_factory_builds_protocol_with_instance_state(self):
try:
import AMI
except ModuleNotFoundError as exc:
self.skipTest(str(exc))
factory = AMI.AMI.AMIClientFactory(
AMI.AMI.AMIClient,
b"user",
b"secret",
b"1234",
b"ilink 3 2350",
)
protocol = factory.buildProtocol(None)
self.assertEqual(protocol.username, b"user")
self.assertEqual(protocol.secret, b"secret")
self.assertEqual(protocol.nodenum, b"1234")
self.assertEqual(protocol.command, b"ilink 3 2350")
def test_report_sql_uses_factory_db_and_parameterized_insert(self):
self._install_mysql_stub()
try:
import report_sql
report_sql = importlib.reload(report_sql)
except ModuleNotFoundError as exc:
self.skipTest(str(exc))
fake_db = _FakeDB()
fake_reactor = object()
factory = report_sql.reportClientFactory(report_sql.reportClient, fake_db, fake_reactor)
with redirect_stdout(io.StringIO()):
client = factory.buildProtocol(None)
self.assertIs(client.db, fake_db)
self.assertIs(client.reactor, fake_reactor)
event = {
"type": "GROUP VOICE",
"event": "START",
"trx": "RX",
"system": "SYSTEM",
"streamid": "1234",
"peerid": "5678",
"subid": "9012",
"slot": "2",
"dstid": "2350",
"duration": "0",
}
with redirect_stdout(io.StringIO()):
client.send_mysql(event)
statement, params = fake_db.cursor_obj.executed
self.assertIn("%s", statement)
self.assertEqual(params[0], "GROUP VOICE")
self.assertEqual(params[8], "2350")
self.assertTrue(fake_db.committed)
self.assertTrue(fake_db.cursor_obj.closed)
def test_proxy_environment_bool_parser(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
self.assertTrue(hotspot_proxy_v2.bool_from_env("1"))
self.assertTrue(hotspot_proxy_v2.bool_from_env("true"))
self.assertTrue(hotspot_proxy_v2.bool_from_env("yes"))
self.assertFalse(hotspot_proxy_v2.bool_from_env("0"))
self.assertFalse(hotspot_proxy_v2.bool_from_env(""))
self.assertFalse(hotspot_proxy_v2.bool_from_env(None))
finally:
self._restore_modules(saved_modules)
def test_proxy_default_destination_range_matches_generated_masters(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
default_ports = range(
hotspot_proxy_v2.DEFAULT_DESTPORT_START,
hotspot_proxy_v2.DEFAULT_DESTPORT_END + 1,
)
self.assertEqual(hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT, 100)
self.assertEqual(len(default_ports), hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT)
finally:
self._restore_modules(saved_modules)
def test_proxy_routes_login_and_master_dmrd(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
fake_reactor = _FakeReactor()
hotspot_proxy_v2.reactor = fake_reactor
transport = _FakeTransport()
peer_id = b"\x00\x00\x03\xe9"
conn_track = {54000: False}
peer_track = {}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
conn_track,
peer_track,
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"RPTL" + peer_id, ("198.51.100.10", 40000))
proxy.datagramReceived(_dmrd_packet(peer_id), ("127.0.0.1", 54000))
self.assertEqual(conn_track[54000], peer_id)
self.assertEqual(peer_track[peer_id]["shost"], "198.51.100.10")
self.assertEqual(transport.writes[0], (b"RPTL" + peer_id, ("127.0.0.1", 54000)))
self.assertEqual(transport.writes[-1], (_dmrd_packet(peer_id), ("198.51.100.10", 40000)))
finally:
self._restore_modules(saved_modules)
def test_proxy_ignores_short_client_dmrd(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
conn_track = {54000: False}
peer_track = {}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
conn_track,
peer_track,
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"DMRD", ("198.51.100.10", 40000))
self.assertEqual(conn_track[54000], False)
self.assertEqual(peer_track, {})
self.assertEqual(transport.writes, [])
finally:
self._restore_modules(saved_modules)
def test_proxy_ignores_unknown_master_rptack_port(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
{54000: False},
{},
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"RPTACK" + b"\x00\x00\x03\xe9", ("127.0.0.1", 59999))
self.assertEqual(transport.writes, [])
finally:
self._restore_modules(saved_modules)
def test_proxy_ignores_new_client_when_all_destination_ports_are_in_use(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
existing_peer = b"\x00\x00\x03\xe9"
new_peer = b"\x00\x00\x03\xea"
conn_track = {54000: existing_peer}
peer_track = {
existing_peer: {
"dport": 54000,
"sport": 40000,
"shost": "198.51.100.10",
"timer": _FakeTimer(),
}
}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
conn_track,
peer_track,
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"RPTL" + new_peer, ("198.51.100.11", 40001))
self.assertEqual(conn_track, {54000: existing_peer})
self.assertNotIn(new_peer, peer_track)
self.assertEqual(transport.writes, [])
finally:
self._restore_modules(saved_modules)
def test_proxy_allocates_each_default_destination_port_once_before_exhaustion(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
conn_track = {
port: False
for port in range(
hotspot_proxy_v2.DEFAULT_DESTPORT_START,
hotspot_proxy_v2.DEFAULT_DESTPORT_END + 1,
)
}
peer_track = {}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
hotspot_proxy_v2.DEFAULT_LISTEN_PORT,
conn_track,
peer_track,
[],
{},
30,
False,
False,
hotspot_proxy_v2.DEFAULT_DESTPORT_START,
hotspot_proxy_v2.DEFAULT_DESTPORT_END,
None,
{},
)
proxy.transport = transport
peer_ids = []
for index in range(hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT):
peer_id = (1000 + index).to_bytes(4, "big")
peer_ids.append(peer_id)
proxy.datagramReceived(
b"RPTL" + peer_id,
("198.51.100.{}".format(index + 1), 40000 + index),
)
write_count_before_exhaustion = len(transport.writes)
proxy.datagramReceived(b"RPTL" + (9999).to_bytes(4, "big"), ("198.51.100.200", 40100))
self.assertEqual(len(peer_track), hotspot_proxy_v2.DEFAULT_DESTPORT_COUNT)
self.assertEqual(set(conn_track.values()), set(peer_ids))
self.assertNotIn(False, conn_track.values())
self.assertEqual(len(transport.writes), write_count_before_exhaustion)
finally:
self._restore_modules(saved_modules)
def test_proxy_updates_client_source_port_for_existing_peer(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
peer_id = b"\x00\x00\x03\xe9"
conn_track = {54000: False}
peer_track = {}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
conn_track,
peer_track,
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"RPTL" + peer_id, ("198.51.100.10", 40000))
proxy.datagramReceived(b"RPTPING" + peer_id, ("198.51.100.10", 40001))
proxy.datagramReceived(b"MSTPONG" + peer_id, ("127.0.0.1", 54000))
self.assertEqual(peer_track[peer_id]["sport"], 40001)
self.assertEqual(transport.writes[-1], (b"MSTPONG" + peer_id, ("198.51.100.10", 40001)))
finally:
self._restore_modules(saved_modules)
def test_proxy_reaper_releases_port_and_is_idempotent(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
transport = _FakeTransport()
peer_id = b"\x00\x00\x03\xe9"
conn_track = {54000: peer_id}
peer_track = {
peer_id: {
"dport": 54000,
"sport": 40000,
"shost": "198.51.100.10",
"timer": _FakeTimer(),
}
}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
conn_track,
peer_track,
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.reaper(peer_id)
write_count = len(transport.writes)
proxy.reaper(peer_id)
self.assertEqual(conn_track[54000], False)
self.assertNotIn(peer_id, peer_track)
self.assertEqual(transport.writes[0], (b"RPTCL" + peer_id, ("127.0.0.1", 54000)))
self.assertEqual(transport.writes[1:], [(b"MSTCL", ("198.51.100.10", 40000))] * 3)
self.assertEqual(len(transport.writes), write_count)
finally:
self._restore_modules(saved_modules)
def test_proxy_blacklists_known_peer_from_proxy_control_packet(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
peer_id = b"\x00\x00\x03\xe9"
ip_blacklist = {}
peer_track = {
peer_id: {
"dport": 54000,
"sport": 40000,
"shost": "198.51.100.10",
"timer": _FakeTimer(),
}
}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
{54000: peer_id},
peer_track,
[],
ip_blacklist,
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"PRBL" + peer_id + b"12345.0", ("127.0.0.1", 54000))
self.assertEqual(ip_blacklist["198.51.100.10"], 12345.0)
finally:
self._restore_modules(saved_modules)
def test_proxy_rptl_flood_blacklists_source_ip(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
peer_id = b"\x00\x00\x03\xe9"
ip_blacklist = {}
rptl_track = {}
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
{54000: False},
{},
[],
ip_blacklist,
30,
False,
False,
54000,
54000,
None,
rptl_track,
)
proxy.transport = transport
for index in range(21):
proxy.datagramReceived(b"RPTL" + peer_id, ("198.51.100.10", 40000 + index))
write_count = len(transport.writes)
proxy.datagramReceived(b"RPTPING" + peer_id, ("198.51.100.10", 40022))
self.assertIn("198.51.100.10", ip_blacklist)
self.assertNotIn("198.51.100.10", rptl_track)
self.assertEqual(len(transport.writes), write_count)
finally:
self._restore_modules(saved_modules)
def test_proxy_treats_packets_from_master_ip_as_master_side(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
{54000: False},
{},
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"RPTL" + b"\x00\x00\x03\xe9", ("127.0.0.1", 40000))
self.assertEqual(proxy.peerTrack, {})
self.assertEqual(transport.writes, [])
finally:
self._restore_modules(saved_modules)
def test_proxy_ignores_malformed_proxy_blacklist_packet(self):
hotspot_proxy_v2, saved_modules = self._import_proxy_module()
try:
hotspot_proxy_v2.reactor = _FakeReactor()
transport = _FakeTransport()
proxy = hotspot_proxy_v2.Proxy(
"127.0.0.1",
62031,
{54000: False},
{},
[],
{},
30,
False,
False,
54000,
54000,
None,
{},
)
proxy.transport = transport
proxy.datagramReceived(b"PRBL" + b"\x00\x00\x03\xe9" + b"not-a-float", ("127.0.0.1", 54000))
self.assertEqual(transport.writes, [])
finally:
self._restore_modules(saved_modules)
def _import_proxy_module(self):
saved_modules = self._install_proxy_stubs()
try:
import hotspot_proxy_v2
return importlib.reload(hotspot_proxy_v2), saved_modules
except Exception:
self._restore_modules(saved_modules)
raise
def _install_mysql_stub(self):
mysql_module = types.ModuleType("mysql")
connector_module = types.ModuleType("mysql.connector")
class ConnectorError(Exception):
pass
connector_module.Error = ConnectorError
connector_module.errorcode = types.SimpleNamespace(
ER_ACCESS_DENIED_ERROR=1045,
ER_BAD_DB_ERROR=1049,
)
mysql_module.connector = connector_module
sys.modules["mysql"] = mysql_module
sys.modules["mysql.connector"] = connector_module
def _install_proxy_stubs(self):
stubbed = [
"Pyro5",
"Pyro5.api",
"setproctitle",
"twisted",
"twisted.internet",
"twisted.internet.protocol",
"twisted.internet.task",
]
saved_modules = {name: sys.modules.get(name) for name in stubbed + ["hotspot_proxy_v2"]}
pyro5_module = types.ModuleType("Pyro5")
pyro5_api_module = types.ModuleType("Pyro5.api")
pyro5_api_module.Proxy = object
pyro5_module.api = pyro5_api_module
sys.modules["Pyro5"] = pyro5_module
sys.modules["Pyro5.api"] = pyro5_api_module
setproctitle_module = types.ModuleType("setproctitle")
setproctitle_module.setproctitle = lambda title: None
sys.modules["setproctitle"] = setproctitle_module
twisted_module = types.ModuleType("twisted")
twisted_internet_module = types.ModuleType("twisted.internet")
twisted_protocol_module = types.ModuleType("twisted.internet.protocol")
twisted_task_module = types.ModuleType("twisted.internet.task")
twisted_protocol_module.DatagramProtocol = object
twisted_task_module.LoopingCall = object
twisted_internet_module.protocol = twisted_protocol_module
twisted_internet_module.reactor = _FakeReactor()
twisted_internet_module.task = twisted_task_module
twisted_module.internet = twisted_internet_module
sys.modules["twisted"] = twisted_module
sys.modules["twisted.internet"] = twisted_internet_module
sys.modules["twisted.internet.protocol"] = twisted_protocol_module
sys.modules["twisted.internet.task"] = twisted_task_module
sys.modules.pop("hotspot_proxy_v2", None)
return saved_modules
def _restore_modules(self, saved_modules):
for name, module in saved_modules.items():
if module is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = module
class _FakeCursor:
def __init__(self):
self.executed = None
self.closed = False
def execute(self, statement, params):
self.executed = (statement, params)
def close(self):
self.closed = True
class _FakeDB:
def __init__(self):
self.cursor_obj = _FakeCursor()
self.committed = False
def is_connected(self):
return True
def cursor(self):
return self.cursor_obj
def commit(self):
self.committed = True
class _FakeTimer:
def __init__(self):
self.resets = []
self.cancelled = False
def reset(self, value=None):
self.resets.append(value)
def cancel(self):
self.cancelled = True
class _FakeReactor:
def __init__(self):
self.timers = []
self.thread_calls = []
def callLater(self, timeout, func, *args):
timer = _FakeTimer()
self.timers.append((timeout, func, args, timer))
return timer
def callInThread(self, func, *args):
self.thread_calls.append((func, args))
func(*args)
class _FakeTransport:
def __init__(self):
self.writes = []
def write(self, data, addr):
self.writes.append((data, addr))
def _dmrd_packet(peer_id):
return b"".join(
[
b"DMRD",
b"\x01",
b"\x00\x00\x01",
b"\x00\x00\x02",
peer_id,
b"\x80",
b"\x01\x02\x03\x04",
b"\x55" * 33,
]
)
if __name__ == "__main__":
unittest.main()

Powered by TurnKey Linux.