2022-06-19 13:55:50 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
2022-05-21 23:04:17 +00:00
|
|
|
"""
|
2022-06-19 13:55:50 +00:00
|
|
|
Test control frame messages over a high quality simulated audio channel.
|
|
|
|
|
|
|
|
Near end-to-end test for sending / receiving select control frames through the
|
|
|
|
TNC and modem and back through on the other station. Data injection initiates from the
|
|
|
|
queue used by the daemon process into and out of the TNC.
|
|
|
|
|
|
|
|
Can be invoked from CMake, pytest, coverage or directly.
|
|
|
|
|
2023-04-21 13:04:09 +00:00
|
|
|
Uses util_datac13.py in separate process to perform the data transfer.
|
2022-06-19 14:04:46 +00:00
|
|
|
|
|
|
|
@author: N2KIQ
|
2022-05-21 23:04:17 +00:00
|
|
|
"""
|
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
import multiprocessing
|
2022-12-01 09:47:46 +00:00
|
|
|
import numpy as np
|
2022-05-21 23:04:17 +00:00
|
|
|
import sys
|
2022-05-30 20:58:41 +00:00
|
|
|
import time
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
# pylint: disable=wrong-import-position
|
|
|
|
sys.path.insert(0, "..")
|
2023-10-20 12:12:20 +00:00
|
|
|
sys.path.insert(0, "../modem")
|
2022-05-21 23:04:17 +00:00
|
|
|
import data_handler
|
|
|
|
import helpers
|
2023-04-27 19:43:56 +00:00
|
|
|
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
def print_frame(data: bytearray):
|
|
|
|
"""
|
|
|
|
Pretty-print the provided frame.
|
|
|
|
|
|
|
|
:param data: Frame to be output
|
|
|
|
:type data: bytearray
|
|
|
|
"""
|
|
|
|
print(f"Type : {int(data[0])}")
|
|
|
|
print(f"DXCRC : {bytes(data[1:4])}")
|
|
|
|
print(f"CallCRC: {bytes(data[4:7])}")
|
|
|
|
print(f"Call : {helpers.bytes_to_callsign(data[7:13])}")
|
|
|
|
|
|
|
|
|
|
|
|
def t_create_frame(frame_type: int, mycall: str, dxcall: str) -> bytearray:
|
|
|
|
"""
|
|
|
|
Generate the requested frame.
|
|
|
|
|
|
|
|
:param frame_type: The numerical type of the desired frame.
|
|
|
|
:type frame_type: int
|
|
|
|
:param mycall: Callsign of the near station
|
|
|
|
:type mycall: str
|
|
|
|
:param dxcall: Callsign of the far station
|
|
|
|
:type dxcall: str
|
|
|
|
:return: Bytearray of the requested frame
|
|
|
|
:rtype: bytearray
|
|
|
|
"""
|
|
|
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
|
|
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
|
|
|
mycallsign_crc = helpers.get_crc_24(mycallsign)
|
|
|
|
|
|
|
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
|
|
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
|
|
|
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
|
|
|
|
2022-10-21 18:56:25 +00:00
|
|
|
# frame = bytearray(14)
|
|
|
|
# frame[:1] = bytes([frame_type])
|
|
|
|
# frame[1:4] = dxcallsign_crc
|
|
|
|
# frame[4:7] = mycallsign_crc
|
|
|
|
# frame[7:13] = mycallsign_bytes
|
2022-12-01 09:47:46 +00:00
|
|
|
session_id = np.random.bytes(1)
|
2022-05-21 23:04:17 +00:00
|
|
|
frame = bytearray(14)
|
|
|
|
frame[:1] = bytes([frame_type])
|
2022-10-21 18:56:25 +00:00
|
|
|
frame[1:2] = session_id
|
|
|
|
frame[2:5] = dxcallsign_crc
|
|
|
|
frame[5:8] = mycallsign_crc
|
|
|
|
frame[8:14] = mycallsign_bytes
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
return frame
|
|
|
|
|
|
|
|
|
2022-10-21 18:56:25 +00:00
|
|
|
def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
|
2022-05-21 23:04:17 +00:00
|
|
|
"""
|
|
|
|
Generate the session_close frame.
|
|
|
|
|
|
|
|
:param mycall: Callsign of the near station
|
|
|
|
:type mycall: str
|
|
|
|
:param dxcall: Callsign of the far station
|
|
|
|
:type dxcall: str
|
|
|
|
:return: Bytearray of the requested frame
|
|
|
|
:rtype: bytearray
|
|
|
|
"""
|
|
|
|
return t_create_frame(223, mycall, dxcall)
|
|
|
|
|
|
|
|
|
2022-12-09 09:39:34 +00:00
|
|
|
def t_create_session_close(session_id: bytes, dxcall: str) -> bytearray:
|
2022-10-21 18:56:25 +00:00
|
|
|
"""
|
|
|
|
Generate the session_close frame.
|
|
|
|
|
|
|
|
:param session_id: Session to close
|
|
|
|
:type mycall: int
|
|
|
|
:return: Bytearray of the requested frame
|
|
|
|
:rtype: bytearray
|
|
|
|
"""
|
2022-12-09 10:15:41 +00:00
|
|
|
|
2022-12-09 09:31:25 +00:00
|
|
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
|
|
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
|
|
|
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
2022-12-09 10:15:41 +00:00
|
|
|
|
|
|
|
# return t_create_frame(223, mycall, dxcall)
|
2022-10-21 18:56:25 +00:00
|
|
|
frame = bytearray(14)
|
|
|
|
frame[:1] = bytes([223])
|
|
|
|
frame[1:2] = session_id
|
2022-12-09 09:31:25 +00:00
|
|
|
frame[2:5] = dxcallsign_crc
|
2022-10-21 18:56:25 +00:00
|
|
|
|
|
|
|
return frame
|
|
|
|
|
|
|
|
|
2022-05-21 23:04:17 +00:00
|
|
|
def t_create_start_session(mycall: str, dxcall: str) -> bytearray:
|
|
|
|
"""
|
|
|
|
Generate the create_session frame.
|
|
|
|
|
|
|
|
:param mycall: Callsign of the near station
|
|
|
|
:type mycall: str
|
|
|
|
:param dxcall: Callsign of the far station
|
|
|
|
:type dxcall: str
|
|
|
|
:return: Bytearray of the requested frame
|
|
|
|
:rtype: bytearray
|
|
|
|
"""
|
|
|
|
return t_create_frame(221, mycall, dxcall)
|
|
|
|
|
|
|
|
|
|
|
|
def t_tsh_dummy():
|
|
|
|
"""Replacement function for transmit_session_heartbeat"""
|
|
|
|
print("In transmit_session_heartbeat")
|
|
|
|
|
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
def t_foreign_disconnect(mycall: str, dxcall: str):
|
2022-05-21 23:04:17 +00:00
|
|
|
"""
|
|
|
|
Execute test to validate that receiving a session open frame sets the correct machine
|
|
|
|
state.
|
|
|
|
|
|
|
|
:param mycall: Callsign of the near station
|
|
|
|
:type mycall: str
|
|
|
|
:param dxcall: Callsign of the far station
|
|
|
|
:type dxcall: str
|
|
|
|
:return: Bytearray of the requested frame
|
|
|
|
:rtype: bytearray
|
|
|
|
"""
|
|
|
|
# Set the SSIDs we'll use for this test.
|
2023-04-27 19:43:56 +00:00
|
|
|
Station.ssid_list = [0, 1, 2, 3, 4]
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2022-05-07 02:24:57 +00:00
|
|
|
# Setup the static parameters for the connection.
|
2022-05-21 23:04:17 +00:00
|
|
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
|
|
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
2023-04-27 19:43:56 +00:00
|
|
|
Station.mycallsign = mycallsign
|
|
|
|
Station.mycallsign_crc = helpers.get_crc_24(mycallsign)
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
|
|
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
2023-04-27 19:43:56 +00:00
|
|
|
Station.dxcallsign = dxcallsign
|
|
|
|
Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
# Create the TNC
|
|
|
|
tnc = data_handler.DATA()
|
2022-05-29 22:57:12 +00:00
|
|
|
tnc.arq_cleanup()
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# Replace the heartbeat transmit routine with a No-Op.
|
2022-05-21 23:04:17 +00:00
|
|
|
tnc.transmit_session_heartbeat = t_tsh_dummy
|
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# Create frame to be 'received' by this station.
|
2022-05-21 23:04:17 +00:00
|
|
|
create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall)
|
|
|
|
print_frame(create_frame)
|
|
|
|
tnc.received_session_opener(create_frame)
|
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes
|
|
|
|
assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes
|
2022-05-30 20:58:41 +00:00
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert ARQ.arq_session is True
|
|
|
|
assert TNC.tnc_state == "BUSY"
|
|
|
|
assert ARQ.arq_session_state == "connecting"
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# Set up a frame from a non-associated station.
|
2022-10-21 18:56:25 +00:00
|
|
|
# foreigncall_bytes = helpers.callsign_to_bytes("ZZ0ZZ-0")
|
|
|
|
# foreigncall = helpers.bytes_to_callsign(foreigncall_bytes)
|
|
|
|
|
|
|
|
# close_frame = t_create_session_close_old("ZZ0ZZ-0", "ZZ0ZZ-0")
|
|
|
|
open_session = create_frame[1:2]
|
2022-12-01 09:47:46 +00:00
|
|
|
wrong_session = np.random.bytes(1)
|
2022-10-21 18:56:25 +00:00
|
|
|
while wrong_session == open_session:
|
2022-12-01 09:47:46 +00:00
|
|
|
wrong_session = np.random.bytes(1)
|
2022-12-09 10:15:41 +00:00
|
|
|
close_frame = t_create_session_close(wrong_session, dxcall)
|
2022-05-21 23:04:17 +00:00
|
|
|
print_frame(close_frame)
|
2022-05-30 20:58:41 +00:00
|
|
|
|
2022-10-21 18:56:25 +00:00
|
|
|
# assert (
|
2023-04-27 19:43:56 +00:00
|
|
|
# helpers.check_callsign(Station.dxcallsign, bytes(close_frame[4:7]))[0] is False
|
|
|
|
# ), f"{helpers.get_crc_24(Station.dxcallsign)} == {bytes(close_frame[4:7])} but should be not equal."
|
2022-10-21 18:56:25 +00:00
|
|
|
|
|
|
|
# assert (
|
|
|
|
# helpers.check_callsign(foreigncall, bytes(close_frame[4:7]))[0] is True
|
|
|
|
# ), f"{helpers.get_crc_24(foreigncall)} != {bytes(close_frame[4:7])} but should be equal."
|
2022-05-30 20:58:41 +00:00
|
|
|
|
|
|
|
# Send the non-associated session close frame to the TNC
|
2022-05-21 23:04:17 +00:00
|
|
|
tnc.received_session_close(close_frame)
|
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert helpers.callsign_to_bytes(Station.mycallsign) == helpers.callsign_to_bytes(
|
2022-05-30 20:58:41 +00:00
|
|
|
mycall
|
2023-04-27 19:43:56 +00:00
|
|
|
), f"{Station.mycallsign} != {mycall} but should equal."
|
|
|
|
assert helpers.callsign_to_bytes(Station.dxcallsign) == helpers.callsign_to_bytes(
|
2022-05-30 20:58:41 +00:00
|
|
|
dxcall
|
2023-04-27 19:43:56 +00:00
|
|
|
), f"{Station.dxcallsign} != {dxcall} but should equal."
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert ARQ.arq_session is True
|
|
|
|
assert TNC.tnc_state == "BUSY"
|
|
|
|
assert ARQ.arq_session_state == "connecting"
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
def t_valid_disconnect(mycall: str, dxcall: str):
|
2022-05-21 23:04:17 +00:00
|
|
|
"""
|
|
|
|
Execute test to validate that receiving a session open frame sets the correct machine
|
|
|
|
state.
|
|
|
|
|
|
|
|
:param mycall: Callsign of the near station
|
|
|
|
:type mycall: str
|
|
|
|
:param dxcall: Callsign of the far station
|
|
|
|
:type dxcall: str
|
|
|
|
:return: Bytearray of the requested frame
|
|
|
|
:rtype: bytearray
|
|
|
|
"""
|
2022-05-29 22:57:12 +00:00
|
|
|
# Set the SSIDs we'll use for this test.
|
2023-04-27 19:43:56 +00:00
|
|
|
Station.ssid_list = [0, 1, 2, 3, 4]
|
2022-05-29 22:57:12 +00:00
|
|
|
|
2022-05-07 02:24:57 +00:00
|
|
|
# Setup the static parameters for the connection.
|
2022-05-21 23:04:17 +00:00
|
|
|
mycallsign_bytes = helpers.callsign_to_bytes(mycall)
|
|
|
|
mycallsign = helpers.bytes_to_callsign(mycallsign_bytes)
|
2023-04-27 19:43:56 +00:00
|
|
|
Station.mycallsign = mycallsign
|
|
|
|
Station.mycallsign_crc = helpers.get_crc_24(mycallsign)
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
|
|
|
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
|
2023-04-27 19:43:56 +00:00
|
|
|
Station.dxcallsign = dxcallsign
|
|
|
|
Station.dxcallsign_crc = helpers.get_crc_24(dxcallsign)
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
# Create the TNC
|
|
|
|
tnc = data_handler.DATA()
|
2022-05-29 22:57:12 +00:00
|
|
|
tnc.arq_cleanup()
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# Replace the heartbeat transmit routine with our own, a No-Op.
|
2022-05-21 23:04:17 +00:00
|
|
|
tnc.transmit_session_heartbeat = t_tsh_dummy
|
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# Create packet to be 'received' by this station.
|
2022-05-21 23:04:17 +00:00
|
|
|
create_frame = t_create_start_session(mycall=dxcall, dxcall=mycall)
|
|
|
|
print_frame(create_frame)
|
|
|
|
tnc.received_session_opener(create_frame)
|
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert ARQ.arq_session is True
|
|
|
|
assert TNC.tnc_state == "BUSY"
|
|
|
|
assert ARQ.arq_session_state == "connecting"
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# Create packet to be 'received' by this station.
|
2022-10-21 18:56:25 +00:00
|
|
|
# close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
|
|
|
|
open_session = create_frame[1:2]
|
2022-12-09 10:15:41 +00:00
|
|
|
print(dxcall)
|
|
|
|
print("#####################################################")
|
|
|
|
close_frame = t_create_session_close(open_session, mycall)
|
|
|
|
print(close_frame[2:5])
|
2022-05-21 23:04:17 +00:00
|
|
|
print_frame(close_frame)
|
2022-05-30 20:58:41 +00:00
|
|
|
tnc.received_session_close(close_frame)
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert helpers.callsign_to_bytes(Station.mycallsign) == mycallsign_bytes
|
|
|
|
assert helpers.callsign_to_bytes(Station.dxcallsign) == dxcallsign_bytes
|
2022-05-21 23:04:17 +00:00
|
|
|
|
2023-04-27 19:43:56 +00:00
|
|
|
assert ARQ.arq_session is False
|
|
|
|
assert TNC.tnc_state == "IDLE"
|
|
|
|
assert ARQ.arq_session_state == "disconnected"
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
|
2022-05-30 20:58:41 +00:00
|
|
|
# These tests are pushed into separate processes as a workaround. These tests
|
|
|
|
# change the state of one of the static parts of the system. Unfortunately the
|
|
|
|
# specific state(s) maintained across tests in the same interpreter are not yet known.
|
|
|
|
# The other tests affected are: `test_tnc.py` and the ARQ tests.
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "E4AWQ-4"])
|
|
|
|
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
|
2022-10-21 18:56:25 +00:00
|
|
|
# @pytest.mark.flaky(reruns=2)
|
2022-05-30 20:58:41 +00:00
|
|
|
def test_foreign_disconnect(mycall: str, dxcall: str):
|
|
|
|
proc = multiprocessing.Process(target=t_foreign_disconnect, args=(mycall, dxcall))
|
|
|
|
# print("Starting threads.")
|
|
|
|
proc.start()
|
|
|
|
|
2023-01-30 08:20:48 +00:00
|
|
|
time.sleep(5.05)
|
2022-05-30 20:58:41 +00:00
|
|
|
|
|
|
|
# print("Terminating threads.")
|
|
|
|
proc.terminate()
|
|
|
|
proc.join()
|
|
|
|
|
2022-07-04 21:09:08 +00:00
|
|
|
# print(f"\nproc.exitcode={proc.exitcode}")
|
2022-05-30 20:58:41 +00:00
|
|
|
assert proc.exitcode == 0
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("mycall", ["AA1AA-2", "DE2DE-0", "M4AWQ-4"])
|
|
|
|
@pytest.mark.parametrize("dxcall", ["AA9AA-1", "DE2ED-0", "F6QWE-3"])
|
2022-06-12 01:59:31 +00:00
|
|
|
@pytest.mark.flaky(reruns=2)
|
2022-05-30 20:58:41 +00:00
|
|
|
def test_valid_disconnect(mycall: str, dxcall: str):
|
|
|
|
proc = multiprocessing.Process(target=t_valid_disconnect, args=(mycall, dxcall))
|
|
|
|
# print("Starting threads.")
|
|
|
|
proc.start()
|
|
|
|
|
2023-01-30 08:20:48 +00:00
|
|
|
time.sleep(5.05)
|
2022-05-30 20:58:41 +00:00
|
|
|
|
|
|
|
# print("Terminating threads.")
|
|
|
|
proc.terminate()
|
|
|
|
proc.join()
|
|
|
|
|
2022-07-04 21:09:08 +00:00
|
|
|
# print(f"\nproc.exitcode={proc.exitcode}")
|
2022-05-30 20:58:41 +00:00
|
|
|
assert proc.exitcode == 0
|
2022-05-21 23:04:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# Run pytest with the current script as the filename.
|
|
|
|
ecode = pytest.main(["-v", sys.argv[0]])
|
|
|
|
if ecode == 0:
|
|
|
|
print("errors: 0")
|
|
|
|
else:
|
|
|
|
print(ecode)
|