666 lines
29 KiB
Python
666 lines
29 KiB
Python
#!/usr/bin/env python3.7
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
TFC - Onion-routed, endpoint secure messaging system
|
|
Copyright (C) 2013-2020 Markus Ottela
|
|
|
|
This file is part of TFC.
|
|
|
|
TFC is free software: you can redistribute it and/or modify it under the terms
|
|
of the GNU General Public License as published by the Free Software Foundation,
|
|
either version 3 of the License, or (at your option) any later version.
|
|
|
|
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TFC. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import base64
|
|
import os
|
|
import unittest
|
|
import socket
|
|
|
|
from datetime import datetime
|
|
from unittest import mock
|
|
from unittest.mock import MagicMock
|
|
from typing import Any
|
|
|
|
from serial import SerialException
|
|
|
|
from src.common.crypto import blake2b
|
|
from src.common.gateway import gateway_loop, Gateway, GatewaySettings
|
|
from src.common.misc import ensure_dir
|
|
from src.common.reed_solomon import RSCodec
|
|
from src.common.statics import (DIR_USER_DATA, GATEWAY_QUEUE, NC, PACKET_CHECKSUM_LENGTH, QUBES_RX_IP_ADDR_FILE,
|
|
RX, TX, US_BYTE)
|
|
|
|
from tests.mock_classes import Settings
|
|
from tests.utils import cd_unit_test, cleanup, gen_queue_dict, tear_queues, TFCTestCase
|
|
|
|
|
|
class TestGatewayLoop(unittest.TestCase):
|
|
|
|
def setUp(self) -> None:
|
|
"""Pre-test actions."""
|
|
self.unit_test_dir = cd_unit_test()
|
|
self.queues = gen_queue_dict()
|
|
|
|
def tearDown(self) -> None:
|
|
"""Post-test actions."""
|
|
cleanup(self.unit_test_dir)
|
|
tear_queues(self.queues)
|
|
|
|
@mock.patch('multiprocessing.connection.Listener',
|
|
return_value=MagicMock(accept=lambda: MagicMock(recv=MagicMock(return_value='message'))))
|
|
def test_loop(self, _: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
|
|
self.assertIsNone(gateway_loop(self.queues, gateway, unit_test=True))
|
|
|
|
data = self.queues[GATEWAY_QUEUE].get()
|
|
self.assertIsInstance(data[0], datetime)
|
|
self.assertEqual(data[1], 'message')
|
|
|
|
|
|
class TestGatewaySerial(TFCTestCase):
|
|
|
|
def setUp(self) -> None:
|
|
"""Pre-test actions."""
|
|
self.unit_test_dir = cd_unit_test()
|
|
self.settings = Settings(session_usb_serial_adapter=True)
|
|
|
|
def tearDown(self) -> None:
|
|
"""Post-test actions."""
|
|
cleanup(self.unit_test_dir)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('serial.Serial', return_value=MagicMock())
|
|
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']])
|
|
@mock.patch('builtins.input', side_effect=['Yes'])
|
|
def test_search_and_establish_serial(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
self.assertIsInstance(gateway.rs, RSCodec)
|
|
self.assertIs(gateway.tx_serial, gateway.rx_serial)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('serial.Serial', side_effect=SerialException)
|
|
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']])
|
|
@mock.patch('builtins.input', side_effect=['Yes'])
|
|
def test_serial_exception_during_establish_exists(self, *_: Any) -> None:
|
|
with self.assertRaises(SystemExit):
|
|
Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('serial.Serial', return_value=MagicMock(write=MagicMock(side_effect=[SerialException, None])))
|
|
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0'], ['ttyUSB0']])
|
|
@mock.patch('builtins.input', side_effect=['Yes'])
|
|
def test_write_serial_(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
self.assertIsNone(gateway.write(b"message"))
|
|
|
|
@mock.patch("time.sleep", return_value=None)
|
|
@mock.patch("serial.Serial", return_value=MagicMock(read_all=MagicMock(
|
|
side_effect=[KeyboardInterrupt, SerialException, b"", b"1", b"2", b""])))
|
|
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]])
|
|
@mock.patch("builtins.input", side_effect=["Yes"])
|
|
def test_serial_uninitialized_serial_interface_for_read_raises_critical_error(self, *_) -> None:
|
|
# Setup
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
gateway.rx_serial = None
|
|
|
|
# Test
|
|
with self.assertRaises(SystemExit):
|
|
gateway.read()
|
|
|
|
@mock.patch("multiprocessing.connection.Listener", MagicMock())
|
|
@mock.patch("time.sleep", return_value=None)
|
|
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]])
|
|
@mock.patch("builtins.input", side_effect=["Yes"])
|
|
def test_serial_uninitialized_socket_interface_for_read_raises_critical_error(self, *_) -> None:
|
|
# Setup
|
|
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
|
|
gateway.rx_socket = None
|
|
|
|
# Test
|
|
with self.assertRaises(SystemExit):
|
|
gateway.read()
|
|
|
|
@mock.patch("multiprocessing.connection.Listener", return_value=MagicMock(
|
|
accept=MagicMock(return_value=MagicMock(recv=MagicMock(return_value=b"12")))))
|
|
@mock.patch("time.monotonic", side_effect=[1, 2, 3])
|
|
@mock.patch("time.sleep", return_value=None)
|
|
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]])
|
|
@mock.patch("builtins.input", side_effect=["Yes"])
|
|
def test_read_socket(self, *_) -> None:
|
|
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
|
|
data = gateway.read()
|
|
self.assertEqual(data, b"12")
|
|
|
|
@mock.patch("time.monotonic", side_effect=[1, 2, 3])
|
|
@mock.patch("time.sleep", return_value=None)
|
|
@mock.patch("serial.Serial", return_value=MagicMock(
|
|
read_all=MagicMock(side_effect=[KeyboardInterrupt, SerialException, b"", b"1", b"2", b""])))
|
|
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"], ["ttyUSB0"]])
|
|
@mock.patch("builtins.input", side_effect=["Yes"])
|
|
def test_read_serial(self, *_) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
data = gateway.read()
|
|
self.assertEqual(data, b"12")
|
|
|
|
@mock.patch("time.sleep", return_value=None)
|
|
@mock.patch("serial.Serial", return_value=MagicMock())
|
|
@mock.patch("os.listdir", side_effect=[["ttyUSB0"], ["ttyUSB0"]])
|
|
@mock.patch("builtins.input", side_effect=["Yes"])
|
|
def test_add_error_correction(self, *_) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
packet = b"packet"
|
|
|
|
# Test BLAKE2b based checksum
|
|
gateway.settings.session_serial_error_correction = 0
|
|
self.assertEqual(gateway.add_error_correction(packet),
|
|
packet + blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH))
|
|
|
|
# Test Reed-Solomon erasure code
|
|
gateway.settings.session_serial_error_correction = 5
|
|
gateway.rs = RSCodec(gateway.settings.session_serial_error_correction)
|
|
self.assertEqual(gateway.add_error_correction(packet),
|
|
gateway.rs.encode(packet))
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('serial.Serial', return_value=MagicMock())
|
|
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0']])
|
|
@mock.patch('builtins.input', side_effect=['Yes'])
|
|
def test_detect_errors(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
packet = b'packet'
|
|
|
|
# Test BLAKE2b based checksum
|
|
gateway.settings.session_serial_error_correction = 0
|
|
self.assertEqual(gateway.detect_errors(gateway.add_error_correction(packet)),
|
|
packet)
|
|
|
|
# Test unrecoverable error raises SoftError
|
|
self.assert_se("Warning! Received packet had an invalid checksum.",
|
|
gateway.detect_errors, 300 * b'a')
|
|
|
|
# Test Reed-Solomon erasure code
|
|
gateway.settings.session_serial_error_correction = 5
|
|
gateway.rs = RSCodec(gateway.settings.session_serial_error_correction)
|
|
self.assertEqual(gateway.detect_errors(gateway.add_error_correction(packet)),
|
|
packet)
|
|
|
|
# Test unrecoverable error raises SoftError
|
|
self.assert_se("Error: Reed-Solomon failed to correct errors in the received packet.",
|
|
gateway.detect_errors, 300 * b'a')
|
|
|
|
# Qubes
|
|
|
|
# Test with B58 encoding
|
|
gateway.settings.qubes = True
|
|
packet_with_error_correction = base64.b85encode(gateway.add_error_correction(packet))
|
|
self.assertEqual(gateway.detect_errors(packet_with_error_correction), packet)
|
|
|
|
# Test invalid B85 encoding raises SoftError
|
|
packet_with_error_correction = base64.b85encode(gateway.add_error_correction(packet))
|
|
packet_with_error_correction += b'\x00'
|
|
self.assert_se("Error: Received packet had invalid Base85 encoding.",
|
|
gateway.detect_errors, packet_with_error_correction)
|
|
gateway.settings.qubes = False
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('serial.Serial', return_value=MagicMock())
|
|
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyUSB0'], [''], ['ttyUSB0'], ['ttyS0'], ['']])
|
|
@mock.patch('builtins.input', side_effect=['Yes'])
|
|
def test_search_serial_interfaces(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
|
|
|
|
interface = gateway.search_serial_interface()
|
|
self.assertEqual(interface, '/dev/ttyUSB0')
|
|
|
|
# Test unavailable system serial exits:
|
|
gateway.settings.session_usb_serial_adapter = False
|
|
|
|
interface = gateway.search_serial_interface()
|
|
self.assertEqual(interface, '/dev/ttyS0')
|
|
|
|
with self.assertRaises(SystemExit):
|
|
gateway.search_serial_interface()
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('multiprocessing.connection.Client', MagicMock())
|
|
@mock.patch('multiprocessing.connection.Listener', MagicMock())
|
|
def test_establish_local_testing_gateway(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=NC, local_test=True, dd_sockets=False, qubes=False)
|
|
self.assertIsInstance(gateway.rs, RSCodec)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('multiprocessing.connection.Client', MagicMock(side_effect=KeyboardInterrupt))
|
|
def test_keyboard_interrupt_exits(self, *_: Any) -> None:
|
|
with self.assertRaises(SystemExit):
|
|
Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('multiprocessing.connection.Client', MagicMock(
|
|
side_effect=[socket.error, ConnectionRefusedError, MagicMock()]))
|
|
def test_socket_client(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
|
|
self.assertIsInstance(gateway, Gateway)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('multiprocessing.connection.Listener', MagicMock(
|
|
side_effect=[MagicMock(), KeyboardInterrupt]))
|
|
def test_socket_server(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
|
|
self.assertIsInstance(gateway, Gateway)
|
|
|
|
with self.assertRaises(SystemExit):
|
|
Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('multiprocessing.connection.Listener', return_value=MagicMock(
|
|
accept=lambda: MagicMock(recv=MagicMock(side_effect=[KeyboardInterrupt, b'data', EOFError]))))
|
|
def test_local_testing_read(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
|
|
self.assertEqual(gateway.read(), b'data')
|
|
with self.assertRaises(SystemExit):
|
|
gateway.read()
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('multiprocessing.connection.Client', return_value=MagicMock(
|
|
send=MagicMock(side_effect=[None, BrokenPipeError])))
|
|
def test_local_testing_write(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
|
|
self.assertIsNone(gateway.write(b'data'))
|
|
|
|
with self.assertRaises(SystemExit):
|
|
gateway.write(b'data')
|
|
|
|
# Qubes
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(
|
|
recv=MagicMock(side_effect=[EOFError, b'data', US_BYTE]))))
|
|
def test_qubes_socket_server(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
|
|
self.assertIsInstance(gateway, Gateway)
|
|
self.assertEqual(gateway.read(), b'data')
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(
|
|
recv=MagicMock(side_effect=[EOFError, b'data', US_BYTE]))))
|
|
def test_qubes_socket_server_raises_critical_error_if_interface_is_not_initialized(self, *_: Any) -> None:
|
|
# Setup
|
|
gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
|
|
gateway.rxq_socket = None
|
|
|
|
# Test
|
|
with self.assertRaises(SystemExit):
|
|
self.assertEqual(gateway.read(), b'data')
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
|
|
@mock.patch('socket.socket', MagicMock())
|
|
def test_qubes_socket_client(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
|
|
self.assertIsInstance(gateway, Gateway)
|
|
self.assertIsNone(gateway.write(b'data'))
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('socket.socket', MagicMock())
|
|
def test_qubes_auto_config_from_file(self, *_: Any) -> None:
|
|
# Setup
|
|
test_ip = '10.137.0.17'
|
|
open(QUBES_RX_IP_ADDR_FILE, 'w+').write(test_ip)
|
|
|
|
# Test
|
|
self.assertTrue(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
|
|
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
|
|
self.assertEqual(gateway.settings.rx_udp_ip, test_ip)
|
|
self.assertFalse(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
|
|
@mock.patch('socket.socket', MagicMock(return_value=MagicMock(connect=MagicMock(side_effect=[socket.error]))))
|
|
def test_socket_error_raises_critical_error(self, *_: Any) -> None:
|
|
gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
|
|
with self.assertRaises(SystemExit):
|
|
gateway.get_local_ip_addr()
|
|
|
|
class TestGatewaySettings(TFCTestCase):
|
|
|
|
def setUp(self) -> None:
|
|
"""Pre-test actions."""
|
|
self.unit_test_dir = cd_unit_test()
|
|
self.default_serialized = """\
|
|
{
|
|
"serial_baudrate": 19200,
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"rx_udp_ip": ""
|
|
}"""
|
|
|
|
def tearDown(self) -> None:
|
|
"""Post-test actions."""
|
|
cleanup(self.unit_test_dir)
|
|
|
|
@mock.patch('os.listdir', side_effect=[['ttyUSB0'], ['ttyS0'], ['ttyUSB0'], ['ttyS0']])
|
|
@mock.patch('builtins.input', side_effect=['yes', 'yes', 'no', 'no'])
|
|
def test_gateway_setup(self, *_: Any) -> None:
|
|
settings = GatewaySettings(operation=TX, local_test=False, dd_sockets=True, qubes=False)
|
|
self.assertIsNone(settings.setup())
|
|
|
|
def test_store_and_load_of_settings(self) -> None:
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}/{TX}_serial_settings.json'))
|
|
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
settings.serial_baudrate = 115200
|
|
settings.use_serial_usb_adapter = False
|
|
|
|
self.assertIsNone(settings.store_settings())
|
|
settings2 = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
|
|
self.assertEqual(settings2.serial_baudrate, 115200)
|
|
self.assertEqual(settings.use_serial_usb_adapter, False)
|
|
|
|
def test_manually_edited_settings_are_loaded(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 9600,
|
|
"serial_error_correction": 1,
|
|
"use_serial_usb_adapter": false,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"rx_udp_ip": "10.137.0.17"
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 9600)
|
|
self.assertEqual(settings.serial_error_correction, 1)
|
|
self.assertEqual(settings.use_serial_usb_adapter, False)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
|
|
|
|
def test_missing_values_are_set_to_default_and_database_is_overwritten(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_error_correction": 1,
|
|
"use_serial_usb_adapter": false,
|
|
"relay_usb_serial_adapter": false
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 1)
|
|
self.assertEqual(settings.use_serial_usb_adapter, False)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
def test_invalid_format_is_replaced_with_defaults(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": false,
|
|
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
with open(settings.file_name) as f:
|
|
data = f.read()
|
|
|
|
self.assertEqual(data, self.default_serialized)
|
|
|
|
def test_invalid_serial_baudrate_is_replaced_with_default(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 19201,
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"rx_udp_ip": ""
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
with open(settings.file_name) as f:
|
|
data = f.read()
|
|
|
|
self.assertEqual(data, self.default_serialized)
|
|
|
|
def test_invalid_serial_error_correction_is_replaced_with_default(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 19200,
|
|
"serial_error_correction": -1,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"rx_udp_ip": ""
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
with open(settings.file_name) as f:
|
|
data = f.read()
|
|
|
|
self.assertEqual(data, self.default_serialized)
|
|
|
|
def test_invalid_serial_interface_is_replaced_with_default(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 19200,
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "does_not_exist",
|
|
"rx_udp_ip": ""
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
with open(settings.file_name) as f:
|
|
data = f.read()
|
|
|
|
self.assertEqual(data, self.default_serialized)
|
|
|
|
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
|
|
def test_invalid_rx_udp_ip_is_replaced_with_user_input(self, _) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 19200,
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"rx_udp_ip": "256.256.256.256"
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=True)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
|
|
|
|
@mock.patch('builtins.input', side_effect=['10.137.0.17'])
|
|
def test_invalid_rx_udp_ip_type_is_replaced_with_user_input(self, _) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 19200,
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"rx_udp_ip": 5
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=True)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
self.assertEqual(settings.rx_udp_ip, '10.137.0.17')
|
|
|
|
def test_invalid_type_is_replaced_with_default(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": "115200",
|
|
"serial_error_correction": "5",
|
|
"use_serial_usb_adapter": "true",
|
|
"built_in_serial_interface": true,
|
|
"rx_udp_ip": ""
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
with open(settings.file_name) as f:
|
|
data = f.read()
|
|
|
|
self.assertEqual(data, self.default_serialized)
|
|
|
|
def test_unknown_kv_pair_is_removed(self) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write("""\
|
|
{
|
|
"serial_baudrate": 19200,
|
|
"serial_error_correction": 5,
|
|
"use_serial_usb_adapter": true,
|
|
"built_in_serial_interface": "ttyS0",
|
|
"this_should_not_be_here": 1
|
|
}""")
|
|
# Test
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assertEqual(settings.serial_baudrate, 19200)
|
|
self.assertEqual(settings.serial_error_correction, 5)
|
|
self.assertEqual(settings.use_serial_usb_adapter, True)
|
|
self.assertEqual(settings.built_in_serial_interface, 'ttyS0')
|
|
|
|
with open(settings.file_name) as f:
|
|
data = f.read()
|
|
|
|
self.assertEqual(data, self.default_serialized)
|
|
|
|
@mock.patch('os.listdir', side_effect=[['ttyS0'], ['ttyUSB0'], ['ttyUSB0'], ['ttyS0']])
|
|
@mock.patch('builtins.input', side_effect=['Yes', 'Yes', 'No', 'No'])
|
|
def test_setup(self, *_: Any) -> None:
|
|
# Setup
|
|
ensure_dir(DIR_USER_DATA)
|
|
with open(f"{DIR_USER_DATA}{TX}_serial_settings.json", 'w+') as f:
|
|
f.write(self.default_serialized)
|
|
|
|
settings = GatewaySettings(operation=TX, local_test=False, dd_sockets=True, qubes=False)
|
|
|
|
# Test
|
|
self.assertIsNone(settings.setup())
|
|
self.assertIsNone(settings.setup())
|
|
|
|
@mock.patch('time.sleep', return_value=None)
|
|
def test_change_setting(self, _: Any) -> None:
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assert_se("Error: Invalid setting value 'Falsee'.",
|
|
settings.change_setting, 'serial_baudrate', 'Falsee')
|
|
self.assert_se("Error: Invalid setting value '1.1'.",
|
|
settings.change_setting, 'serial_baudrate', '1.1', )
|
|
self.assert_se("Error: Invalid setting value '18446744073709551616'.",
|
|
settings.change_setting, 'serial_baudrate', str(2 ** 64))
|
|
self.assert_se("Error: Invalid setting value 'Falsee'.",
|
|
settings.change_setting, 'use_serial_usb_adapter', 'Falsee')
|
|
|
|
self.assertIsNone(settings.change_setting('serial_baudrate', '9600'))
|
|
self.assertEqual(GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False).serial_baudrate, 9600)
|
|
|
|
settings.serial_baudrate = b'bytestring'
|
|
with self.assertRaises(SystemExit):
|
|
settings.change_setting('serial_baudrate', '9600')
|
|
|
|
def test_validate_key_value_pair(self) -> None:
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assert_se("Error: The specified baud rate is not supported.",
|
|
settings.validate_key_value_pair, 'serial_baudrate', 0)
|
|
self.assert_se("Error: The specified baud rate is not supported.",
|
|
settings.validate_key_value_pair, 'serial_baudrate', 10)
|
|
self.assert_se("Error: The specified baud rate is not supported.",
|
|
settings.validate_key_value_pair, 'serial_baudrate', 9601)
|
|
self.assert_se("Error: Invalid value for error correction ratio.",
|
|
settings.validate_key_value_pair, 'serial_error_correction', -1)
|
|
|
|
self.assertIsNone(settings.validate_key_value_pair("serial_baudrate", 9600))
|
|
self.assertIsNone(settings.validate_key_value_pair("serial_error_correction", 20))
|
|
self.assertIsNone(settings.validate_key_value_pair("use_serial_usb_adapter", True))
|
|
|
|
@mock.patch('shutil.get_terminal_size', return_value=(64, 64))
|
|
def test_too_narrow_terminal_raises_fr_when_printing_settings(self, _: Any) -> None:
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assert_se("Error: Screen width is too small.", settings.print_settings)
|
|
|
|
def test_print_settings(self) -> None:
|
|
settings = GatewaySettings(operation=TX, local_test=True, dd_sockets=True, qubes=False)
|
|
self.assert_prints("""\
|
|
|
|
Serial interface setting Current value Default value Description
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
serial_baudrate 19200 19200 The speed of
|
|
serial interface
|
|
in bauds per
|
|
second
|
|
|
|
serial_error_correction 5 5 Number of byte
|
|
errors serial
|
|
datagrams can
|
|
recover from
|
|
|
|
|
|
""", settings.print_settings)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main(exit=False)
|