From 8c2c6a8ce039ab8297986bb42005498a6344e144 Mon Sep 17 00:00:00 2001 From: Pedro Date: Wed, 22 Nov 2023 14:56:52 +0100 Subject: [PATCH] Explicit args on modem primitive methods. API error format. --- modem/api_validations.py | 5 +++++ modem/data_handler.py | 5 +++-- modem/data_handler_ping.py | 21 ++++++++++--------- modem/server.py | 33 ++++++++++++++++++++++------- modem/server_commands.py | 43 ++++++++++---------------------------- modem/websocket_manager.py | 9 +++++++- 6 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 modem/api_validations.py diff --git a/modem/api_validations.py b/modem/api_validations.py new file mode 100644 index 00000000..81f9d2d3 --- /dev/null +++ b/modem/api_validations.py @@ -0,0 +1,5 @@ +import re + +def validate_freedata_callsign(callsign): + regexp = "^[a-zA-Z]+\d+\w+-\d{1,2}$" + return re.compile(regexp).match(callsign) is not None diff --git a/modem/data_handler.py b/modem/data_handler.py index ac8a6c5d..956cd0e8 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -165,9 +165,10 @@ class DATA: self.arq.arq_session_handler(data[1], data[2]) elif data[0] == "PING": - # [1] mycallsign + # [1] mycallsign // this is being injected as None # [2] dxcallsign - self.ping.transmit_ping(data[1], data[2]) + mycallsign = f"{self.config['STATION']['mycall']}-{self.config['STATION']['myssid']}" + self.ping.transmit_ping(mycallsign, data[2]) elif data[0] == "BEACON": # [1] INTERVAL int diff --git a/modem/data_handler_ping.py b/modem/data_handler_ping.py index 8b533873..3c158a58 100644 --- a/modem/data_handler_ping.py +++ b/modem/data_handler_ping.py @@ -13,12 +13,12 @@ class PING: self.config = config # ---------- PING - def transmit_ping(self, mycallsign: bytes, dxcallsign: bytes) -> None: + def transmit_ping(self, mycallsign: str, dxcallsign: str) -> None: """ - Funktion for controlling pings + Function for controlling pings Args: - mycallsign:bytes: - dxcallsign:bytes: + mycallsign + dxcallsign """ # check if specific callsign is set with different SSID than the Modem is initialized @@ -40,14 +40,15 @@ class PING: self.dxcallsign = dxcallsign self.dxcallsign_crc = helpers.get_crc_24(self.dxcallsign) - self.send_data_to_socket_queue( - freedata="modem-message", - ping="transmitting", - dxcallsign=str(dxcallsign, "UTF-8"), - ) + self.event_queue.put({ + 'freedata': "modem-message", + 'ping': "transmitting", + 'dxcallsign': str(dxcallsign, "UTF-8"), + }) + self.log.info( "[Modem] PING REQ [" - + mycallsign + + str(mycallsign, "UTF-8") + "] >>> [" + str(dxcallsign, "UTF-8") + "]" diff --git a/modem/server.py b/modem/server.py index 5007c83e..62b92916 100644 --- a/modem/server.py +++ b/modem/server.py @@ -1,4 +1,4 @@ -from flask import Flask, request, jsonify, make_response +from flask import Flask, request, jsonify, make_response, abort, Response from flask_sock import Sock from flask_cors import CORS import os @@ -12,6 +12,7 @@ import state_manager import threading import ujson as json import websocket_manager as wsm +import api_validations as validations app = Flask(__name__) CORS(app) @@ -55,9 +56,22 @@ service_manager.SM(app) app.modem_service.put("start") # returns a standard API response -def api_response(data): - return make_response(jsonify(data), 200) +def api_response(data, status = 200): + return make_response(jsonify(data), status) +def api_abort(message, code): + jsonError = json.dumps({'error': message}) + abort(Response(jsonError, code)) + +# validates a parameter +def validate(req, param, validator, isRequired = True): + if param not in req: + if isRequired: + api_abort(f"Required parameter '{param}' is missing.", 400) + else: + return True + if not validator(req[param]): + api_abort(f"Value of '{param}' is invalid.", 400) ## REST API @app.route('/', methods=['GET']) @@ -104,23 +118,26 @@ def post_cqcqcq(): if request.method not in ['POST']: return api_response({"info": "endpoint for triggering a CQ via POST"}) if app.states.is_modem_running: - server_commands.cqcqcq(request.json) + server_commands.cqcqcq() return api_response({"cmd": "cqcqcq"}) @app.route('/modem/beacon', methods=['POST']) def post_beacon(): if request.method not in ['POST']: return api_response({"info": "endpoint for controlling BEACON STATE via POST"}) - if app.states.is_modem_running: - server_commands.beacon(request.json) + if not app.states.is_modem_running: + api_abort('Modem not running', 503) + server_commands.beacon(request.json['enable_beacon']) return api_response(request.json) @app.route('/modem/ping_ping', methods=['POST']) def post_ping(): if request.method not in ['POST']: return api_response({"info": "endpoint for controlling PING via POST"}) - if app.states.is_modem_running: - server_commands.ping_ping(request.json) + if not app.states.is_modem_running: + api_abort('Modem not running', 503) + validate(request.json, 'dxcall', validations.validate_freedata_callsign) + server_commands.ping_ping(request.json['dxcall']) return api_response(request.json) @app.route('/modem/send_test_frame', methods=['POST']) diff --git a/modem/server_commands.py b/modem/server_commands.py index 2343d28e..967fe321 100644 --- a/modem/server_commands.py +++ b/modem/server_commands.py @@ -5,28 +5,23 @@ import threading from random import randrange log = structlog.get_logger("COMMANDS") -def cqcqcq(data): +def cqcqcq(): try: DATA_QUEUE_TRANSMIT.put(["CQ"]) return except Exception as err: - log.warning("[CMD] error while transmiting CQ", e=err, command=data) + log.warning("[CMD] error while transmiting CQ", e=err) -def ping_ping(data): +def ping_ping(dxcall): try: - dxcallsign = data["dxcall"] - if not str(dxcallsign).strip(): - return - DATA_QUEUE_TRANSMIT.put(["PING", None, dxcallsign]) + DATA_QUEUE_TRANSMIT.put(["PING", None, dxcall]) except Exception as err: log.warning( - "[CMD] PING command execution error", e=err, command=data + "[CMD] PING command execution error", e=err ) -def beacon(data, interval=300): - beacon_state = data['enabled'] in [True] - +def beacon(beacon_state, interval=300): log.info( "[CMD] Changing beacon state", state=beacon_state ) @@ -41,18 +36,12 @@ def modem_send_test_frame(): ) DATA_QUEUE_TRANSMIT.put(["SEND_TEST_FRAME"]) -def modem_arq_send_raw(data): +def modem_arq_send_raw(mycallsign, dxcallsign, payload, arq_uuid = "no-uuid"): # wait some random time threading.Event().wait(randrange(5, 25, 5) / 10.0) - base64data = data["data"] - - # check if transmission uuid provided else set no-uuid - try: - arq_uuid = data["uuid"] - except Exception: - arq_uuid = "no-uuid" + base64data = payload if len(base64data) % 4: raise TypeError @@ -60,36 +49,26 @@ def modem_arq_send_raw(data): binarydata = base64.b64decode(base64data) DATA_QUEUE_TRANSMIT.put( - ["ARQ_RAW", binarydata, arq_uuid, data["mycallsign"], data["dxcallsign"]] + ["ARQ_RAW", binarydata, arq_uuid, mycallsign, dxcallsign] ) -def modem_fec_transmit(data): +def modem_fec_transmit(mode, wakeup, base64data, mycallsign = None): log.info( "[CMD] Send fec frame" ) - mode = data["mode"] - wakeup = data["wakeup"] - base64data = data["payload"] if len(base64data) % 4: raise TypeError payload = base64.b64decode(base64data) - try: - mycallsign = data["mycallsign"] - except: - mycallsign = None - DATA_QUEUE_TRANSMIT.put(["FEC", mode, wakeup, payload, mycallsign]) -def modem_fec_is_writing(data): +def modem_fec_is_writing(mycallsign): try: - mycallsign = data["mycallsign"] DATA_QUEUE_TRANSMIT.put(["FEC_IS_WRITING", mycallsign]) except Exception as err: log.warning( "[SCK] Send fec frame command execution error", e=err, - command=data, ) diff --git a/modem/websocket_manager.py b/modem/websocket_manager.py index ec4ed6da..330a8391 100644 --- a/modem/websocket_manager.py +++ b/modem/websocket_manager.py @@ -26,10 +26,17 @@ def handle_connection(sock, client_list, event_queue): def transmit_sock_data_worker(client_list, event_queue): while True: event = event_queue.get() + + if isinstance(event, str): + print(f"WARNING: Queue event:\n'{event}'\n still in string format") + json_event = event + else: + json_event = json.dumps(event) + clients = client_list.copy() for client in clients: try: - client.send(event) + client.send(json_event) except Exception: client_list.remove(client)