diff --git a/.gitignore b/.gitignore index 456860b..1051571 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,3 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser - -# Node -fragmentation_layer/code/main.py diff --git a/README.md b/README.md index aed26b5..45ab4be 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,58 @@ Code of SCHC protocol implemented in Python to be loaded on a [PyCOM](https://pycom.io/) device. -## Compression +## Compression Layer - +The compression layer has not been tested in LoPy and should be considered unfinished. The following example indicates how to run an example where the sender and the receiver are on the same machine and the message is a IPv6 packet. There is currently no integration with the fragmentation layer. -## Fragmentation +### Example on local computer -### On Local computer +Before running the example, the `pycryptodome` library must be installed. + +#### Execution of example + +Open two different terminals, one for the receiver and other for the sender. + +For the receiver (execute first): + +````bash +cd compression_layer/examples/local +python compression_receiver.py +```` + +For the sender: + +````bash +cd compression_layer/examples/local +python compression_sender.py +```` + +This will send a SCHC Packet from sender to receiver through the socket `localhost:8099`. The IPv6 packet that is sent is in `packets/demo.txt` and after succesfully being decompressed is saved in `packets/demo2.txt`. + +### Future work + +There are a few main tasks to be adressed before this layer is considered complete: + +1. **Create an installation script**: This layer must be installed as a library, similarly to the fragmentation layer. + +2. **Refactor Protocol Parser**: Currently, this layer only supports an IPv6 for compression (SCHC_Parser.py) with no easy way to specify other protocols. The protocol selection must be dynamic on library initialization (see protocol selection on fragmentation layer). + +3. **Test and adjust LoPy compatibility**: This layer has to run on LoPy alongside the fragmentation layer. + +4. **Integration with fragmentation**: compressed packets must be delivered and received from the fragmetation layer. This is likely more direct if done from the fragmentation layer's side. + + + + +## Fragmentation Layer + +### Example on local computer + +The following steps indicate how to run an example where the sender and the receiver are on the same machine and a message is sent with the `ACK-On-Error` mode specified in the LoRaWAN profile. A successful run means the packet fragmentation/reassembly is working as intended. #### Installation -1. (Opcional) Create a new virtual environment to install Fragmentation package. +1. (Optional) Create a new virtual environment to install Fragmentation package. Using [`conda`](https://docs.conda.io/en/latest/): @@ -35,9 +76,7 @@ Code of SCHC protocol implemented in Python to be loaded on a [PyCOM](https://py .\niclabs-schc\env\Scripts\activate ```` -2. It is supposed that no package is needed, due to code is going to run on `LoPy`. Server side could have further requirements, but local example simulates Gateway/Node just using sockets. - -3. Install `fragmentation_layer` code on virtual environment +2. Install `fragmentation_layer` code on virtual environment. ````bash cd fragmentation_layer/code @@ -53,12 +92,12 @@ Code of SCHC protocol implemented in Python to be loaded on a [PyCOM](https://py #### Execution of example -In two different terminal, one for the receiver and other for the sender. +Open two different terminals, one for the receiver and other for the sender. For the receiver (execute first): ````bash -cd example +cd fragmentation_layer/examples/local python test_receiver.py # To save logs on a file @@ -68,20 +107,147 @@ python test_receiver.py 2> `` For the sender (execute in less than [`INACTIVITY_TIMER`](https://github.com/niclabs/PySCHC/blob/master/fragmentation_layer/code/schc_protocols/lorawan.py) seconds): ````bash -cd example +cd fragmentation_layer/examples/local python test_sender.py # To save logs on a file python test_sender.py 2> `` ```` -This will send SCHC Fragments from sender (`localhost:50006`) to receiver (`localhost:50007`) recovering message defined on [`test_sender.py` file](https://github.com/niclabs/PySCHC/blob/master/fragmentation_layer/example/test_sender.py) and writing it on [`received.txt` text file](https://github.com/niclabs/PySCHC/blob/master/fragmentation_layer/example/received.txt). +This will send SCHC Fragments from sender (`localhost:50006`) to receiver (`localhost:50007`) recovering the message defined on [`test_sender.py` file](https://github.com/niclabs/PySCHC/blob/master/fragmentation_layer/example/test_sender.py) and writing it on [`received.txt` text file](https://github.com/niclabs/PySCHC/blob/master/fragmentation_layer/example/received.txt). -### On LoPy as Node +This example only uses the finite state machines of ACK-On-Error mode. - -### On LoPy as Gateway +### On LoPy (as Node) - +To use this library on **LoPy**, you first need to configure the device according to PyCOM instructions: [https://docs.pycom.io/gettingstarted/](https://docs.pycom.io/gettingstarted/). + +#### Installation + +1. Create a `main.py` and other required files. + + The `main.py` file is the one that is run by the **LoPy** in order to obtain or create the data to be sent, use the library to fragment it, and send the packets. An example of [`main.py`](https://github.com/niclabs/PySCHC/blob/master/fragmentation_layer/examples/lopy/main.py) is available in `fragmentation_layer/examples/lopy` alongside a `message.py` file that is imported. + +2. Copy all needed files to the device. + + This includes: + + The contents of `fragmentation_layer/code`. + + The `main.py` file and other imports. + + The **Pymakr** plugin on [ATOM](https://docs.pycom.io/gettingstarted/software/atom/) or [Visual Studio Code](https://docs.pycom.io/gettingstarted/software/vscode/) can be used for this purpose. + +#### Standard use + +To use the methods and classes of the fragmentation layer, import `SCHCNodeHandler` and the protocol you want to use (currently only LoRaWAN ACK-On-Error is implemented): + +````python +from schc_handlers import SCHCNodeHandler +from schc_protocols import SCHCProtocol + +# First parameter of Node is the protocol, in this case LoRaWAN +node = SCHCNodeHandler(SCHCProtocol.LoRaWAN, mtu=51) # change to use other mtu + +# Load the message you want to send +handler.send_package(b"A message") + +# Define the socket your hardware use +import socket +s = socket#.methods according to each socket +handler.start(s) +```` + +### On Server (as Gateway) + +To use this library on a server, an HTTP Server listening to and processing The Things Network (TTN) requests is required. A small [Flask](https://flask.palletsprojects.com/en/2.0.x/) server with this functionality is provided in `fragmentation_layer/examples/flask_server`. This Readme does not include an explanation of how to configure a TTN account to connect a device and the server. + +#### Installation + +1. (Optional) Create a new virtual environment to install Fragmentation package. + + Using [`conda`](https://docs.conda.io/en/latest/): + + ````bash + conda create -n niclabs-schc python=3.9 + + # Activate + conda activate niclabs-schc + ```` + + Using [`venv`](https://docs.python.org/3.8/library/venv.html): + + ````bash + python3 -m venv niclabs-schc + + # Activate + # On Linux and macOS + source env/bin/activate + # On Windows + .\niclabs-schc\env\Scripts\activate + ```` + +2. Install `fragmentation_layer` code on virtual environment. + + ````bash + cd fragmentation_layer/code + + # As an static package (production) + python setup.py install + + # As an on-deployment package (develop) + python setup.py develop + ```` + +3. (Optional) Install Flask server. + + Copy files in `fragmentation_layer/examples/flask_server` to a desired location and install the Flask library on the virtual enviroment. + +#### Execution of example + +On the location of the Flask server: + +````python +python app_server.py +```` + +The server should now be running and waiting for TTN requests to the `/uplink` subdirectory. All reassembled packets will be saved in and overwrite a `received.bin` file. + +#### Standard use + +On the server, use this API as follows: + +````python +from flask import request +import base64 +import json + +from schc_handlers.schc_gateway_handler import SCHCGatewayHandler +from schc_protocols import SCHCProtocol + +# This function is called after the succesful reassembly of a packet +def example_callback(msg): + print(msg) + +MTU = 51 + +handler = SCHCGatewayHandler(SCHCProtocol.LoRaWAN, MTU, on_receive_callback=example_callback) +# If no callback is given to the handler, reassembled data will be printed on stdout as bytes. + +def receive_uplink(): + # Obtaining data from TTN requests + data = request.get_json() + downlink_url = request.headers["x-downlink-push"] + fport = data["uplink_message"]["f_port"] + dev_id = data["end_device_ids"]["device_id"] + api_key = request.headers["x-downlink-apikey"] + data = request.get_json()["uplink_message"]["frm_payload"] + payload64 = data.encode("ascii") + msg_bytes = base64.b64decode(payload64) + + # Packet processing + handler.handle(msg_bytes, fport, downlink_url, dev_id, api_key) + # This is an example for LoRaWAN. Different protocols may not require all these values + # Only msg_bytes is required by the method signature. + return json.dumps({"Message": "Okay"}), 200 +```` diff --git a/compression_layer/PacketGenerator.py b/compression_layer/PacketGenerator.py deleted file mode 100755 index f91f79e..0000000 --- a/compression_layer/PacketGenerator.py +++ /dev/null @@ -1,129 +0,0 @@ -import struct -import binascii - - -class PacketGenerator: - def __init__(self): - pass - - @staticmethod - def generate(): - - # ****************** IPv6 ************************* - # Enter the value of the headers - ipv6_version = 0x6 - ipv6_traffic_class = 0x95 - ipv6_flow_label = 0xFDFD3 - ipv6_next_header = 0x11 # UDP - ipv6_hop_limit = 0x40 - - # Mask definition - mask_high = 0xF0 - mask_low = 0x0F - mask_flow_label_high = 0xF0000 - mask_flow_label_low = 0x0FFFF - - # byte 0 - ipv6_version = ipv6_version << 4 - traffic_class_hi = ipv6_traffic_class & mask_high - traffic_class_low = ipv6_traffic_class & mask_low - traffic_class_hi = traffic_class_hi >> 4 - first_byte = ipv6_version | traffic_class_hi - first_byte_bf = struct.pack('>B', first_byte) # bit format - # print("byte 0: " + binascii.hexlify(first_byte_bf).__str__()) - - # byte 1 - traffic_class_low = traffic_class_low << 4 - flow_label_hi = mask_flow_label_high & ipv6_flow_label - flow_label_hi = flow_label_hi >> 16 - second_byte = traffic_class_low | flow_label_hi - second_byte_bf = struct.pack('>B', second_byte) # bit format - # print("byte 1: " + binascii.hexlify(second_byte_bf).__str__()) - - # byte 2 and 3 - third_byte = mask_flow_label_low & ipv6_flow_label - third_byte_bf = struct.pack('>H', third_byte) # bit format - # print("byte 2 - 3: " + binascii.hexlify(third_byte_bf).__str__()) - - # byte 6 - sixth_byte_bf = struct.pack('>B', ipv6_next_header) # bit format - # print("byte 6: " + binascii.hexlify(sixth_byte_bf).__str__()) - - # byte 7 - seventh_byte_bf = struct.pack('>B', ipv6_hop_limit) # bit format - # print("byte 7: " + binascii.hexlify(seventh_byte_bf).__str__()) - - # byte 8 - 20 - ipv6_source_address = "fc0c0000000000000000000000000094" - ipv6_source_address_bf = binascii.unhexlify(ipv6_source_address) - - # byte 24 - 36 - ipv6_destination_address = "fc0c0000000000000000000000000008" - ipv6_destination_address_bf = binascii.unhexlify(ipv6_destination_address) - - - # ****************** UDP packet **************************** - udp_source_port = 32513 - udp_source_port_bf = struct.pack('>H', udp_source_port) - - udp_destination_port = 32640 - udp_destination_port_bf = struct.pack('>H', udp_destination_port) - - udp_data = "07025f0128040015040233" - udp_data_bf = binascii.unhexlify(udp_data) - - udp_length = len(udp_data_bf) + 8 - udp_length_bf = struct.pack('>H', udp_length) - - # Calculating UDP checksum - ipv6_pseudo_header = PacketGenerator.build_pseudo_header(ipv6_source_address, ipv6_destination_address, ipv6_next_header, udp_length) - udp_checksum_bf = PacketGenerator.checksum(ipv6_pseudo_header, udp_length, udp_source_port, udp_destination_port, udp_data) - - # Creating UDP packet - udp_packet = b''.join([udp_source_port_bf, udp_destination_port_bf, udp_length_bf, udp_checksum_bf, udp_data_bf]) - - # ********* IPv6 byte 4 and 5 ********* - ipv6_payload_length = len(udp_packet) - fourth_fifth_byte_bf = struct.pack('>H', ipv6_payload_length) # bit format - # print("byte 4 - 5: " + binascii.hexlify(fourth_fifth_byte_bf).__str__()) - - # Creating IPv6 packet - headers = b''.join([first_byte_bf, second_byte_bf, third_byte_bf, fourth_fifth_byte_bf, sixth_byte_bf, seventh_byte_bf, ipv6_source_address_bf, ipv6_destination_address_bf, udp_source_port_bf, udp_destination_port_bf, udp_length_bf, udp_checksum_bf]) - headers_and_data = b''.join([first_byte_bf, second_byte_bf, third_byte_bf, fourth_fifth_byte_bf, sixth_byte_bf, seventh_byte_bf, ipv6_source_address_bf, ipv6_destination_address_bf, udp_packet]) - print("Packet IPv6: " + str(binascii.hexlify(headers_and_data))) - print("Lenght: " + str(len(headers_and_data)) + " bytes") - return (headers_and_data, udp_data_bf, headers) - - @staticmethod - def checksum(pseudo_header, udp_length, udp_source_port, udp_destination_port, udp_data): - if (len(udp_data)%4) == 3: - udp_data = udp_data + '0' - elif (len(udp_data)%4) == 2: - udp_data = udp_data + '00' - elif (len(udp_data)%4) == 1: - udp_data = udp_data + '000' - - udp_data_sum = 0 - for i in range(0, int(len(udp_data) // 4) ): - udp_data_sum = int(udp_data[i * 4:i * 4 + 4], 16) + udp_data_sum - - sum_udp = udp_source_port + udp_destination_port + udp_length + udp_data_sum - sum_total = pseudo_header + sum_udp - sum_sup = sum_total & 0xFFFF0000 - sum_inf = sum_total & 0x0000FFFF - sum_final = (sum_sup >> 16) + sum_inf - result = sum_final ^ int('FFFF', 16) - return struct.pack('>H', result) - - @staticmethod - def build_pseudo_header(src_ip, dest_ip, next_header, payload_len): - sum_ipv6_sa = 0 - for i in range(0,int(len(src_ip)//4)): - sum_ipv6_sa = int(src_ip[i*4:i*4+4],16) + sum_ipv6_sa - - sum_ipv6_da = 0 - for i in range(0,int(len(dest_ip)//4)): - sum_ipv6_da = int(dest_ip[i*4:i*4+4],16) + sum_ipv6_da - - sum_phdr = sum_ipv6_sa + sum_ipv6_da + next_header + payload_len - return sum_phdr diff --git a/compression_layer/README.md b/compression_layer/README.md deleted file mode 100644 index 1a48b58..0000000 --- a/compression_layer/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# PySCHC - -Code of SCHC protocol implemented in Python to be loaded on a [PyCOM](https://pycom.io/) device. - -## Compression - - - diff --git a/compression_layer/README2.md b/compression_layer/README2.md deleted file mode 100755 index 559a7ae..0000000 --- a/compression_layer/README2.md +++ /dev/null @@ -1,22 +0,0 @@ -# Compresión/Descompresión PySCHC - - -### Cambios - -- Implementado Matching Operator MSB. Número de bits en la string MO de la regla (ej: "MSB(6)"). -- Implementados Compression/Decompression Actions LSB, mapping-sent y devIID (aes128_cmac). -- Implementada descompresión con residuo en bits. -- Implementado alineamiento en bits del payload. -- Implementada reconstrucción del paquete IPv6 en el método `decompress`. -- Fix: Ahora método `compress` recibe paquete bruto en lugar de preparseado. -- Fix: Ahora residuo de compresión es armado como array de bits. -- Fix: Problemas varios con la regla por defecto (no compresión). -- Fix: Ahora cuando se descarta una regla por MOs no se termina la iteración por reglas prematuramente. -- Fix: Nombres incorrectos de prefijos e iids. -- Cambio de nombres de variables y comentarios de español a inglés - - -### Detalles no implementados - -- CDA devIID con valores obtenidos del device. -- CDAs/Reglas con headers de largo variable (no requerido para IPv6 + UDP). \ No newline at end of file diff --git a/compression_layer/SCHC_Compressor.py b/compression_layer/code/SCHC_Compressor.py old mode 100755 new mode 100644 similarity index 100% rename from compression_layer/SCHC_Compressor.py rename to compression_layer/code/SCHC_Compressor.py diff --git a/compression_layer/SCHC_Decompressor.py b/compression_layer/code/SCHC_Decompressor.py old mode 100755 new mode 100644 similarity index 100% rename from compression_layer/SCHC_Decompressor.py rename to compression_layer/code/SCHC_Decompressor.py diff --git a/compression_layer/SCHC_Parser.py b/compression_layer/code/SCHC_Parser.py old mode 100755 new mode 100644 similarity index 100% rename from compression_layer/SCHC_Parser.py rename to compression_layer/code/SCHC_Parser.py diff --git a/compression_layer/SCHC_RuleManager.py b/compression_layer/code/SCHC_RuleManager.py old mode 100755 new mode 100644 similarity index 100% rename from compression_layer/SCHC_RuleManager.py rename to compression_layer/code/SCHC_RuleManager.py diff --git a/compression_layer/code/__pycache__/SCHC_Compressor.cpython-37.pyc b/compression_layer/code/__pycache__/SCHC_Compressor.cpython-37.pyc new file mode 100644 index 0000000..6755781 Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_Compressor.cpython-37.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_Compressor.cpython-38.pyc b/compression_layer/code/__pycache__/SCHC_Compressor.cpython-38.pyc new file mode 100644 index 0000000..5b0863f Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_Compressor.cpython-38.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_Decompressor.cpython-37.pyc b/compression_layer/code/__pycache__/SCHC_Decompressor.cpython-37.pyc new file mode 100644 index 0000000..045b3af Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_Decompressor.cpython-37.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_Decompressor.cpython-38.pyc b/compression_layer/code/__pycache__/SCHC_Decompressor.cpython-38.pyc new file mode 100644 index 0000000..9b41305 Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_Decompressor.cpython-38.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_Parser.cpython-37.pyc b/compression_layer/code/__pycache__/SCHC_Parser.cpython-37.pyc new file mode 100644 index 0000000..4301f8a Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_Parser.cpython-37.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_Parser.cpython-38.pyc b/compression_layer/code/__pycache__/SCHC_Parser.cpython-38.pyc new file mode 100644 index 0000000..3c777ab Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_Parser.cpython-38.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_RuleManager.cpython-37.pyc b/compression_layer/code/__pycache__/SCHC_RuleManager.cpython-37.pyc new file mode 100644 index 0000000..f72ae99 Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_RuleManager.cpython-37.pyc differ diff --git a/compression_layer/code/__pycache__/SCHC_RuleManager.cpython-38.pyc b/compression_layer/code/__pycache__/SCHC_RuleManager.cpython-38.pyc new file mode 100644 index 0000000..95a2b00 Binary files /dev/null and b/compression_layer/code/__pycache__/SCHC_RuleManager.cpython-38.pyc differ diff --git a/compression_layer/examples/local/__pycache__/common.cpython-37.pyc b/compression_layer/examples/local/__pycache__/common.cpython-37.pyc new file mode 100644 index 0000000..ba0f055 Binary files /dev/null and b/compression_layer/examples/local/__pycache__/common.cpython-37.pyc differ diff --git a/compression_layer/examples/local/__pycache__/common.cpython-38.pyc b/compression_layer/examples/local/__pycache__/common.cpython-38.pyc new file mode 100644 index 0000000..fbd78f0 Binary files /dev/null and b/compression_layer/examples/local/__pycache__/common.cpython-38.pyc differ diff --git a/compression_layer/common.py b/compression_layer/examples/local/common.py similarity index 100% rename from compression_layer/common.py rename to compression_layer/examples/local/common.py diff --git a/compression_layer/compression_log.txt b/compression_layer/examples/local/compression_log.txt similarity index 100% rename from compression_layer/compression_log.txt rename to compression_layer/examples/local/compression_log.txt diff --git a/compression_layer/compression_sender.py b/compression_layer/examples/local/compression_sender.py similarity index 88% rename from compression_layer/compression_sender.py rename to compression_layer/examples/local/compression_sender.py index 72d9c79..9b17307 100644 --- a/compression_layer/compression_sender.py +++ b/compression_layer/examples/local/compression_sender.py @@ -1,6 +1,7 @@ -import socket, binascii +import socket, binascii, sys + +sys.path.insert(0, "../../code") -from PacketGenerator import PacketGenerator from SCHC_Compressor import SCHC_Compressor from SCHC_RuleManager import SCHC_RuleManager from common import * diff --git a/compression_layer/decompression_log.txt b/compression_layer/examples/local/decompression_log.txt similarity index 100% rename from compression_layer/decompression_log.txt rename to compression_layer/examples/local/decompression_log.txt diff --git a/compression_layer/decompression_receiver.py b/compression_layer/examples/local/decompression_receiver.py similarity index 92% rename from compression_layer/decompression_receiver.py rename to compression_layer/examples/local/decompression_receiver.py index 2d9fb74..c7156b6 100644 --- a/compression_layer/decompression_receiver.py +++ b/compression_layer/examples/local/decompression_receiver.py @@ -1,4 +1,6 @@ -import socket, binascii +import socket, binascii, sys + +sys.path.insert(0, "../../code") from SCHC_Decompressor import SCHC_Decompressor from SCHC_RuleManager import SCHC_RuleManager diff --git a/compression_layer/packets/demo.txt b/compression_layer/examples/local/packets/demo.txt similarity index 100% rename from compression_layer/packets/demo.txt rename to compression_layer/examples/local/packets/demo.txt diff --git a/compression_layer/packets/demo2.txt b/compression_layer/examples/local/packets/demo2.txt similarity index 100% rename from compression_layer/packets/demo2.txt rename to compression_layer/examples/local/packets/demo2.txt diff --git a/fragmentation_layer/code/machine/__init__.py b/fragmentation_layer/code/lopy_machine/__init__.py similarity index 51% rename from fragmentation_layer/code/machine/__init__.py rename to fragmentation_layer/code/lopy_machine/__init__.py index c46b5e0..3c098f6 100644 --- a/fragmentation_layer/code/machine/__init__.py +++ b/fragmentation_layer/code/lopy_machine/__init__.py @@ -1,7 +1,12 @@ -""" -machine: Package implementing PyCom machine modules ( -https://docs.pycom.io/firmwareapi/pycom/machine/ -). Not to be used in production -""" - -from machine.timer import Timer +""" +machine: Package implementing PyCom machine modules ( +https://docs.pycom.io/firmwareapi/pycom/machine/ +). Not to be used in production +""" + +import sys + +if sys.implementation.name == "micropython": + from machine import Timer +else: + from lopy_machine.timer import Timer diff --git a/fragmentation_layer/code/machine/timer.py b/fragmentation_layer/code/lopy_machine/timer.py similarity index 97% rename from fragmentation_layer/code/machine/timer.py rename to fragmentation_layer/code/lopy_machine/timer.py index 2c6f535..1c8bf2d 100644 --- a/fragmentation_layer/code/machine/timer.py +++ b/fragmentation_layer/code/lopy_machine/timer.py @@ -1,100 +1,100 @@ -""" Timer: Equivalent to Timer class (PyCom) """ - -import threading - - -class Timer: - """ - Class implementing desire functionalities of PyCom Timer Class ( - https://docs.pycom.io/firmwareapi/pycom/machine/timer/ - ). Used for testing on local only, - """ - - class Alarm: - """ - Used to get interrupted after a specific interval (PyCom) - """ - - def __init__(self, handler, s=None, *, ms=None, us=None, arg=None, periodic=False): - """ - Constructor - - Parameters - ---------- - handler : Callable - Handler of alarm funcion - s : float, optional - Seconds, if ms and us are not specified this parameter is not optional - ms : float, optional - Milliseconds, if s and us are not specified this parameter is not optional - us : float, optional - Microseconds, if s and ms are not specified this parameter is not optional - arg : Tuple[object, ...], optional - Args of handler function - periodic : bool, optional - Whether the alarm is periodic (start counting again when limit time is reach) - """ - if s is None and ms is None and us is None: - raise TypeError("__init__() missing 1 required keyword only argument: 's', 'ms' or 'us'") - self.__timer__ = None - self.__seconds__ = sum([ - (s if s is not None else 0), - (ms / 1e3 if ms is not None else 0), - (us / 1e6 if us is not None else 0) - ]) - self.__handler__ = handler - self.__args__ = arg - self.__periodic__ = periodic - self.__set_alarm__() - return - - def __set_alarm__(self) -> None: - if self.__handler__ is None: - return - else: - self.__timer__ = threading.Timer(self.__seconds__, self.__run__) - self.__timer__.start() - return - - def __run__(self) -> None: - if self.__args__ is not None: - self.__handler__(self, *self.__args__) - else: - self.__handler__(self) - if self.__periodic__: - self.__set_alarm__() - return - - def callback(self, handler, arg=None): - """ - Specify a callback handler for the alarm. - - Parameters - ---------- - handler : Callable - Callback handler. If set to None, the alarm will be disabled. - arg : Tuple, optional - Argument passed to the callback handler function. - If None is specified, the function will receive the object that triggered the alarm. - - Returns - ------- - None - """ - self.__handler__ = handler - self.__args__ = arg - self.__set_alarm__() - return - - def cancel(self): - """ - Disables the alarm. - - Returns - ------- - None - """ - if self.__timer__ is not None: - self.__timer__.cancel() - self.__periodic__ = False - return +""" Timer: Equivalent to Timer class (PyCom) """ + +import threading + + +class Timer: + """ + Class implementing desire functionalities of PyCom Timer Class ( + https://docs.pycom.io/firmwareapi/pycom/machine/timer/ + ). Used for testing on local only, + """ + + class Alarm: + """ + Used to get interrupted after a specific interval (PyCom) + """ + + def __init__(self, handler, s=None, *, ms=None, us=None, arg=None, periodic=False): + """ + Constructor + + Parameters + ---------- + handler : Callable + Handler of alarm funcion + s : float, optional + Seconds, if ms and us are not specified this parameter is not optional + ms : float, optional + Milliseconds, if s and us are not specified this parameter is not optional + us : float, optional + Microseconds, if s and ms are not specified this parameter is not optional + arg : Tuple[object, ...], optional + Args of handler function + periodic : bool, optional + Whether the alarm is periodic (start counting again when limit time is reach) + """ + if s is None and ms is None and us is None: + raise TypeError("__init__() missing 1 required keyword only argument: 's', 'ms' or 'us'") + self.__timer__ = None + self.__seconds__ = sum([ + (s if s is not None else 0), + (ms / 1e3 if ms is not None else 0), + (us / 1e6 if us is not None else 0) + ]) + self.__handler__ = handler + self.__args__ = arg + self.__periodic__ = periodic + self.__set_alarm__() + return + + def __set_alarm__(self) -> None: + if self.__handler__ is None: + return + else: + self.__timer__ = threading.Timer(self.__seconds__, self.__run__) + self.__timer__.start() + return + + def __run__(self) -> None: + if self.__args__ is not None: + self.__handler__(self, *self.__args__) + else: + self.__handler__(self) + if self.__periodic__: + self.__set_alarm__() + return + + def callback(self, handler, arg=None): + """ + Specify a callback handler for the alarm. + + Parameters + ---------- + handler : Callable + Callback handler. If set to None, the alarm will be disabled. + arg : Tuple, optional + Argument passed to the callback handler function. + If None is specified, the function will receive the object that triggered the alarm. + + Returns + ------- + None + """ + self.__handler__ = handler + self.__args__ = arg + self.__set_alarm__() + return + + def cancel(self): + """ + Disables the alarm. + + Returns + ------- + None + """ + if self.__timer__ is not None: + self.__timer__.cancel() + self.__periodic__ = False + return diff --git a/fragmentation_layer/code/schc_base/bitmap.py b/fragmentation_layer/code/schc_base/bitmap.py index f76d6c7..5225fe3 100644 --- a/fragmentation_layer/code/schc_base/bitmap.py +++ b/fragmentation_layer/code/schc_base/bitmap.py @@ -1,7 +1,5 @@ """ bitmap: Bitmap class """ -from schc_protocols import SCHCProtocol - class Bitmap: """ @@ -100,12 +98,27 @@ def is_missing(self): Returns ------- - bool : + bool True if there are missing tiles """ return len(self.__bitmap__) > sum(self.__bitmap__) - def get_missing(self, fcn=False): + def has_missing(self): + """ + Whether is a missing value so far + + Returns + ------- + bool + True if there are missing tiles between ones + """ + i = 0 + for i, bit in enumerate(self.__bitmap__): + if not bit: + break + return 0 < sum(self.__bitmap__[i+1:]) + + def get_missing(self, fcn=False, after=None): """ Gets first index of reported missing tile. If fcn is True, passes as fcn @@ -114,18 +127,38 @@ def get_missing(self, fcn=False): ---------- fcn : bool, optional If fcn is True, missing as fcn + after : int, optional + Check after a particular fcn Returns ------- - int : + int First index with missing tile """ - i = self.__bitmap__.index(False) + if after is None: + i = self.__bitmap__.index(False) + else: + if fcn: + i = self.__bitmap__[self.protocol.WINDOW_SIZE - after:].index(False) + return after - 1 - i + else: + i = self.__bitmap__[after + 1:].index(False) + after + 1 if fcn: return self.protocol.WINDOW_SIZE - 1 - i else: return i + def get_received_tiles(self): + """ + Gets number of received tiles + + Returns + ------- + int + Tiles received and reported + """ + return sum(self.__bitmap__) + def __repr__(self): return "".join(["1" if i else "0" for i in self.__bitmap__]) diff --git a/fragmentation_layer/code/schc_base/tile.py b/fragmentation_layer/code/schc_base/tile.py index 0c604cf..5485bdc 100644 --- a/fragmentation_layer/code/schc_base/tile.py +++ b/fragmentation_layer/code/schc_base/tile.py @@ -61,3 +61,26 @@ def as_bits(self): Bits sequence as text """ return self.encoded_content + + def __copy__(self): + """ + Copies object + + Returns + ------- + Tile + A copy of this tile + """ + out = Tile(self.as_bytes()[0]) + return out + + def copy(self): + """ + Copies this object + + Returns + ------- + Tile + A copy of this tile + """ + return self.__copy__() diff --git a/fragmentation_layer/code/schc_base/timer.py b/fragmentation_layer/code/schc_base/timer.py index d96515a..e696b6a 100644 --- a/fragmentation_layer/code/schc_base/timer.py +++ b/fragmentation_layer/code/schc_base/timer.py @@ -1,7 +1,6 @@ """ timer: Timer class """ -from machine import Timer - +from lopy_machine import Timer class SCHCTimer: """ diff --git a/fragmentation_layer/code/schc_handlers/__init__.py b/fragmentation_layer/code/schc_handlers/__init__.py index e53b85e..670d3cb 100644 --- a/fragmentation_layer/code/schc_handlers/__init__.py +++ b/fragmentation_layer/code/schc_handlers/__init__.py @@ -1,5 +1,5 @@ """ schc_handlers: Package of handlers classes""" from schc_handlers.schc_handler import SCHCHandler -from schc_handlers.schc_handler_node import SCHCHandlerNode -from schc_handlers.schc_handler_gateway import SCHCHandlerGateway +from schc_handlers.schc_node_handler import SCHCNodeHandler +from schc_handlers.schc_gateway_handler import SCHCGatewayHandler diff --git a/fragmentation_layer/code/schc_handlers/schc_gateway_handler.py b/fragmentation_layer/code/schc_handlers/schc_gateway_handler.py index 515abbf..416fc15 100644 --- a/fragmentation_layer/code/schc_handlers/schc_gateway_handler.py +++ b/fragmentation_layer/code/schc_handlers/schc_gateway_handler.py @@ -1,28 +1,102 @@ -""" schc_handler_gateway: SCHC Handler Gateway Class """ +""" schc_gateway_handler: SCHC Gateway Handler Class """ +import sys + +if sys.implementation.name != 'micropython': + import requests + import base64 + import json from schc_handlers import SCHCHandler -from schc_protocols import LoRaWAN, SCHCProtocol, get_protocol +from schc_protocols import LoRaWAN, SCHCProtocol class SCHCGatewayHandler(SCHCHandler): + """ + SCHC Gateway Handler + To be used as an API on Gateway side of communication + + Attributes + ---------- + callback_creator : Callable + A function to be used on reassembled message + """ + + def __init__(self, protocol, mtu, on_receive_callback=None): + """ + Constructor + + Parameters + ---------- + protocol + mtu + on_receive_callback : Callable + A function to be used when message is reassembled + """ + super().__init__(protocol, mtu) + if on_receive_callback: + used_callback = on_receive_callback + else: + used_callback = lambda msg: print("Message received", msg) + + def create_after_processing_callback(rule_id, dtag): + def after_reassembly_processing(msg_bytes): + # TODO decompress before calling callback + used_callback(msg_bytes) + #self.__sessions__[rule_id].pop(dtag) + return after_reassembly_processing - def __init__(self, protocol): - super().__init__(protocol) + self.callback_creator = create_after_processing_callback def send_package(self, packet): + """ + Load package to send + + Parameters + ---------- + packet : bytes + Packet to be fragmented + + Returns + ------- + None + Parameters + ---------- + packet + + Returns + ------- + + """ if self.__protocol__.id == SCHCProtocol.LoRaWAN: from schc_machines.lorawan import AckAlwaysSender self.assign_session(LoRaWAN.ACK_ALWAYS, None, AckAlwaysSender(LoRaWAN(LoRaWAN.ACK_ALWAYS), packet)) else: raise NotImplementedError("Just LoRaWAN implemented") - self.__sessions__[rule_id][dtag].receive_message(message) - def receive(self, rule_id, dtag, message, f_port=None): + def receive(self, rule_id, dtag, message): + """ + Defines behaviour after receiving a message + + Parameters + ---------- + rule_id : int + Rule id of received message + dtag : int or None + DTag of received message + message : bytes + Message receive + + Returns + ------- + None + """ if self.__protocol__.id == SCHCProtocol.LoRaWAN: if rule_id == LoRaWAN.ACK_ON_ERROR: # message received from schc_machines.lorawan import AckOnErrorReceiver - self.assign_session(rule_id, dtag, AckOnErrorReceiver(LoRaWAN(LoRaWAN.ACK_ON_ERROR))) + self.assign_session(rule_id, dtag, AckOnErrorReceiver( + LoRaWAN(LoRaWAN.ACK_ON_ERROR), + on_success=self.callback_creator(rule_id, dtag))) self.__sessions__[rule_id][dtag].receive_message(message) elif rule_id == LoRaWAN.ACK_ALWAYS: # response received @@ -33,8 +107,64 @@ def receive(self, rule_id, dtag, message, f_port=None): else: raise NotImplementedError("Just LoRaWAN implemented") + def handle(self, message, f_port=None, url=None, dev_id=None, api_key=None): + """ + Handles a reception of SCHCMessages + + Parameters + ---------- + message : bytes + A message received + f_port : int, optional + In case of using LoRaWAN, rule id to handled message + url : str, optional + In case of using LoRaWAN, url to send answer + dev_id : str, optional + In case of using LoRaWAN, unique identifier of device + that sent the received message + + Returns + ------- + None + """ + if self.__protocol__.id == SCHCProtocol.LoRaWAN: + r, d = self.identify_session_from_message(message, f_port) + self.receive(r, d, bytes([f_port]) + message) + response = self.generate_message(r, d) + else: + raise NotImplementedError("Just LoRaWAN implemented") + if url is None: + return response + elif response is not None: + internal_obj = { + "f_port": f_port, + "priority": "NORMAL", + "frm_payload": base64.b64encode(response[1:]).decode("utf-8") + } + post_obj = {"downlinks":[internal_obj]} + headers = {'content-type': 'application/json','Authorization': 'Bearer {}'.format(api_key),'User-Agent':'myint'} + r = requests.post(url, data=json.dumps(post_obj), headers=headers) + def generate_message(self, rule_id, dtag, mtu=512): - try: - return self.__sessions__[rule_id][dtag].generate_message(mtu).as_bytes() - except GeneratorExit: - return b'' + """ + Generates a message to response + + Parameters + ---------- + rule_id : int + Rule id to identify session + dtag : int + Dtag to identify session + mtu : mtu, optional + MTU to use. Default 512 bytes + + Returns + ------- + bytes + Response to be send as bytes + """ + message = self.__sessions__[rule_id][dtag].generate_message(mtu) + if message is None: + return message + else: + return message.as_bytes() diff --git a/fragmentation_layer/code/schc_handlers/schc_handler.py b/fragmentation_layer/code/schc_handlers/schc_handler.py index d837f17..b6bf5c9 100644 --- a/fragmentation_layer/code/schc_handlers/schc_handler.py +++ b/fragmentation_layer/code/schc_handlers/schc_handler.py @@ -4,15 +4,63 @@ from schc_protocols import SCHCProtocol, get_protocol +# TODO: class SCHCHandler: + """ + SCHC Handler super class + Define functions and interface of Node and Gateway concrete class - def __init__(self, protocol): + Attributes + ---------- + __protocol__ : SCHCProtocol + Protocol to be use + __sessions__ : Dict[int, Dict[int, SCHCFiniteStateMachine]] + A dictionary with rule_id and DTag assigned to a SCGHCFiniteStateMachine + __mtu__ : int + MTU to use + + See Also + -------- + SCHCFiniteStateMachine + """ + def __init__(self, protocol, mtu): + """ + Constructor + + Parameters + ---------- + protocol : int + Number indicating SCHCProtocol to use + mtu : int + MTU to use + + See Also + -------- + SCHCProtocol + """ self.__protocol__ = get_protocol(protocol) self.__sessions__ = dict() + self.__mtu__ = mtu def identify_session_from_message(self, message, f_port=None): + """ + Identifies the session to use according to protocol and message + + Parameters + ---------- + message : bytes + A bytes message + f_port : int, optional + In case of using LoRaWAN, rule id to use + + Returns + ------- + Tuple[int, int] + Rule id and Dtag that are going to be use to find + session (and SCHCFiniteStateMachine associated) + """ if self.__protocol__.id == SCHCProtocol.LoRaWAN: - rule_id = int.from_bytes(f_port, "big") + rule_id = f_port else: raise NotImplementedError("Just LoRaWAN implemented") protocol = get_protocol(self.__protocol__.id) @@ -24,17 +72,61 @@ def identify_session_from_message(self, message, f_port=None): dtag = int(dtag, 2) return rule_id, dtag - def send_package(self, rule_id, packet, dtag=None): - return + def send_package(self, packet): + """ + To be implemented by concrete class + Load package to send - def receive(self, rule_id, dtag, message, f_port=None): + Parameters + ---------- + packet : bytes + Packet to be fragmented + + Returns + ------- + None + """ return - def generate_message(self, rule_id, dtag, mtu=512): - raise GeneratorExit("Abstract class cannot generate message") + def receive(self, rule_id, dtag, message): + """ + To be implemented by concrete class + Defines behaviour after receiving a message + + Parameters + ---------- + rule_id : int + Rule id of received message + dtag : int or None + DTag of received message + message : bytes + Message receive + + Returns + ------- + None + """ + return def assign_session(self, rule_id, dtag, machine): + """ + Assigns a SCHCFiniteStateMachine (machine) to the corresponding + rule id and dtag + + Parameters + ---------- + rule_id : int + Rule id defining mode + dtag : int + DTag to differentiates parallel packets + machine : SCHCFiniteStateMachine + Finite State Machine defining behaviour of fragmentation + + Returns + ------- + None, alter self.__session__ + """ if rule_id not in self.__sessions__.keys(): self.__sessions__[rule_id] = dict() - if dtag not in self.__sessions__[rule_id].keys(): + if dtag not in self.__sessions__[rule_id].keys() or self.__sessions__[rule_id][dtag].is_active() == False: self.__sessions__[rule_id][dtag] = machine diff --git a/fragmentation_layer/code/schc_handlers/schc_node_handler.py b/fragmentation_layer/code/schc_handlers/schc_node_handler.py index 9c46016..629ee46 100644 --- a/fragmentation_layer/code/schc_handlers/schc_node_handler.py +++ b/fragmentation_layer/code/schc_handlers/schc_node_handler.py @@ -1,26 +1,67 @@ """ schc_handler_node: SCHC Handler Node Class """ from schc_handlers import SCHCHandler -from schc_protocols import LoRaWAN, SCHCProtocol, get_protocol +from schc_protocols import LoRaWAN, SCHCProtocol class SCHCNodeHandler(SCHCHandler): + """ + SCHC Node Handler + Concrete class to be used as API for sending messages + using SCHC Protocols on a Node + """ - def __init__(self, protocol): - super().__init__(protocol) + def __init__(self, protocol, mtu): + """ + Constructor - def send_package(self, rule_id, packet, dtag=None): + Parameters + ---------- + protocol + mtu + + See Also + -------- + SCHCHandler + """ + super().__init__(protocol, mtu) + + def send_package(self, packet): + """ + Load package to send + + Parameters + ---------- + packet : bytes + Packet to be fragmented + + Returns + ------- + None + """ if self.__protocol__.id == SCHCProtocol.LoRaWAN: - if rule_id == LoRaWAN.ACK_ON_ERROR: - from schc_machines.lorawan import AckOnErrorSender - self.assign_session(rule_id, dtag, AckOnErrorSender(LoRaWAN(LoRaWAN.ACK_ON_ERROR), packet)) - else: - raise ValueError("Rule ID not allowed for sending a message from a end device") + from schc_machines.lorawan import AckOnErrorSender + self.assign_session(LoRaWAN.ACK_ON_ERROR, None, AckOnErrorSender(LoRaWAN(LoRaWAN.ACK_ON_ERROR), packet)) else: raise NotImplementedError("Just LoRaWAN implemented") - self.__sessions__[rule_id][dtag].receive_message(message) - def receive(self, rule_id, dtag, message, f_port=None): + def receive(self, rule_id, dtag, message): + """ + Defines behaviour after receiving a message + + Parameters + ---------- + rule_id : int + Rule id of received message + dtag : int or None + DTag of received message + message : bytes + Message receive + + Returns + ------- + None + """ if self.__protocol__.id == SCHCProtocol.LoRaWAN: if rule_id == LoRaWAN.ACK_ALWAYS: # message received @@ -36,8 +77,49 @@ def receive(self, rule_id, dtag, message, f_port=None): else: raise NotImplementedError("Just LoRaWAN implemented") - def generate_message(self, rule_id, dtag, mtu=512): - try: - return self.__sessions__[rule_id][dtag].generate_message(mtu).as_bytes() - except GeneratorExit: - return b'' + def start(self, s): + """ + Starts communication, by separating message in fragments + and send them to Gateway + + Parameters + ---------- + s : socket + A socket + + Returns + ------- + None + """ + while True: + for rule_id in self.__sessions__.keys(): + for dtag in self.__sessions__[rule_id].keys(): + machine = self.__sessions__[rule_id][dtag] + try: + message = machine.generate_message(self.__mtu__) + except SystemExit as e: + print(e) + self.__sessions__[rule_id].pop(dtag) + return + + if message is not None: + s.setblocking(True) + print(message.as_text()) + + # send some data + if self.__protocol__.id == SCHCProtocol.LoRaWAN: + s.bind(int(message.as_bytes()[0])) + s.send(message.as_bytes()[1:]) + else: + s.send(message.as_bytes()) + + # make the socket non-blocking + # (because if there's no data received it will block forever...) + s.setblocking(False) + + # get any data received (if any...) + data = s.recvfrom(512) + print(data) + if data[0] != b'': + r, d = self.identify_session_from_message(*data) + self.receive(r, d, bytes([data[1]]) + data[0]) diff --git a/fragmentation_layer/code/schc_machines/lorawan/__init__.py b/fragmentation_layer/code/schc_machines/lorawan/__init__.py index 7785b62..147e7e5 100644 --- a/fragmentation_layer/code/schc_machines/lorawan/__init__.py +++ b/fragmentation_layer/code/schc_machines/lorawan/__init__.py @@ -1,7 +1,6 @@ """ lorawan: LoRaWAN state machines """ - from schc_machines.lorawan.ack_on_error_receiver import AckOnErrorReceiver from schc_machines.lorawan.ack_on_error_sender import AckOnErrorSender -from schc_machines.lorawan.ack_always_receiver import AckAlwaysReceiver -from schc_machines.lorawan.ack_always_sender import AckAlwaysSender +# from schc_machines.lorawan.ack_always_receiver import AckAlwaysReceiver +# from schc_machines.lorawan.ack_always_sender import AckAlwaysSender diff --git a/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_receiver.py b/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_receiver.py index bf1e965..7dbc81b 100644 --- a/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_receiver.py +++ b/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_receiver.py @@ -1,9 +1,10 @@ """ ack_on_error_receiver: AckOnError receiver state machine """ -from machine import Timer -from schc_base import Bitmap +from lopy_machine import Timer + +from schc_base import Bitmap, Tile from schc_machines import SCHCReceiver -from schc_messages import RegularSCHCFragment, SCHCAck, All1SCHCFragment, SCHCAckReq +from schc_messages import RegularSCHCFragment, SCHCAck, All1SCHCFragment, SCHCAckReq, SCHCReceiverAbort class AckOnErrorReceiver(SCHCReceiver): @@ -15,6 +16,8 @@ class AckOnErrorReceiver(SCHCReceiver): protocol state """ + __mode__ = "Ack On Error" + class ReceivingPhase(SCHCReceiver.ReceiverState): """ Receiving Phase of Ack on Error @@ -30,53 +33,59 @@ def __init__(self, state_machine): super().__init__(state_machine) self.__success__ = False - def on_expiration_time(self, alarm): - """ - Executed on expiration time - - Parameters - ---------- - alarm : Timer - Timer that triggers expiration - - Returns - ------- - None, alter state to error - """ - self.sm.__exit_msg__ = "Connection timeout" - self.sm.state = self.sm.states["error"] - self.sm.state.enter_state() - return - def generate_message(self, mtu): """ Send messages saved on message_to_send variable - Parameters ---------- mtu : int MTU available - Returns ------- SCHCMessage : A message saved to be send - - Raises - ------ - GeneratorExit - No message to be send """ if self.sm.__last_window__ and self.__success__: self.sm.state = self.sm.states["end"] self.sm.state.enter_state() - message = self.sm.message_to_send.pop(0) - self._logger_.schc_message(message) - return message - raise GeneratorExit("No message to send, keep receiving") + self.sm.inactivity_timer.reset() + self.sm.on_success(self.sm.payload.as_bytes()) + if len(self.sm.message_to_send) > 0: + message = self.sm.message_to_send.pop(0) + self._logger_.schc_message(message) + return message + else: + ack = SCHCAck( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + c=True, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) + ack.add_padding() + return ack + else: + return None + + def on_expiration_time(self, alarm) -> None: + """ + On expiration time behaviour for this phase + + Parameters + ---------- + alarm : Timer + Timer ofg machine that activates the alarm + + Returns + ------- + None + """ + std_on_expiration_time(self, alarm) + return def receive_regular_schc_fragment(self, schc_message): """ + Behaviour when receiving a Regular SCHC Fragment Parameters ---------- @@ -87,6 +96,7 @@ def receive_regular_schc_fragment(self, schc_message): ------- None, alter state """ + self.sm.inactivity_timer.stop() if self.sm.__cw__ == schc_message.header.w: fcn = schc_message.header.fcn.fcn self.sm.__fcn__ = fcn @@ -95,7 +105,9 @@ def receive_regular_schc_fragment(self, schc_message): self._logger_.debug("Window received: {}\tTiles from: {} to {}".format( schc_message.header.w.w, fcn, fcn - tiles_received + 1)) for tile in range(tiles_received): - self.sm.payload.add_content(tiles[0:self.sm.protocol.TILE_SIZE // 8]) + self.sm.add_tile(Tile( + tiles[0:self.sm.protocol.TILE_SIZE // 8] + ), w=self.sm.__cw__, fcn=self.sm.__fcn__) tiles = tiles[self.sm.protocol.TILE_SIZE // 8:] self.sm.bitmaps[ self.sm.__cw__ @@ -111,8 +123,10 @@ def receive_regular_schc_fragment(self, schc_message): ].generate_compress()) ack.add_padding() self.sm.message_to_send.append(ack) + self.sm.attempts.increment() self.sm.state = self.sm.states["waiting_phase"] self.sm.state.enter_state() + self.sm.inactivity_timer.reset() return self._logger_.debug("Current bitmap: {}. Waiting for w={} fcn={} tile".format( self.sm.bitmaps[ @@ -139,23 +153,37 @@ def receive_all1_schc_fragment(self, schc_message): if self.sm.__cw__ == schc_message.header.w: self.sm.__last_window__ = True last_payload = schc_message.payload.as_bytes() - self.sm.payload.add_content(last_payload) - rcs = self.sm.protocol.calculate_rcs( - self.sm.payload.as_bits() - ) - integrity = rcs == schc_message.header.rcs.rcs - if integrity: - self._logger_.debug("Integrity check successful") - compressed_bitmap = None - self.__success__ = True + self.sm.add_tile(Tile(last_payload), w=self.sm.__cw__, fcn=self.sm.__fcn__) + bitmap = self.sm.bitmaps[schc_message.header.w.w] + if bitmap.has_missing(): + integrity = False + compressed_bitmap = bitmap.generate_compress() else: - self._logger_.error("Integrity check failed:\tSender: {}\tReceiver:{}".format( - schc_message.header.rcs.rcs, - rcs - )) - compressed_bitmap = self.sm.bitmaps[ - self.sm.__cw__ - ].generate_compress() + self.sm.reassemble() + rcs = self.sm.protocol.calculate_rcs( + self.sm.payload.as_bits() + ) + integrity = rcs == schc_message.header.rcs.rcs + if integrity: + self._logger_.debug("Integrity check successful") + compressed_bitmap = None + self.__success__ = True + else: + self._logger_.error("Integrity check failed:\tSender: {}\tReceiver: {}".format( + schc_message.header.rcs.rcs, + rcs + )) + abort = SCHCReceiverAbort( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) + abort.add_padding() + self.sm.message_to_send.append(abort) + self.sm.state = self.sm.states["error"] + self.sm.state.enter_state() + return ack = SCHCAck(self.sm.__rule_id__, self.sm.protocol.id, c=integrity, @@ -166,7 +194,7 @@ def receive_all1_schc_fragment(self, schc_message): self.sm.message_to_send.append(ack) return else: - # TODO + self._logger_.debug("(All-1) Different window received") return def receive_schc_ack_req(self, schc_message): @@ -182,19 +210,29 @@ def receive_schc_ack_req(self, schc_message): ------- None, alter state """ - for w in sorted(self.sm.bitmaps.keys()): - bitmap = self.sm.bitmaps[w] - if bitmap.is_missing(): - self._logger_.debug("Window {} has missing tiles".format(w)) - self.sm.message_to_send.append( - SCHCAck(self.sm.__rule_id__, self.sm.protocol.id, - False, w=w, compressed_bitmap=bitmap.generate_compress()) - ) + w = schc_message.header.w.w + if self.sm.__cw__ == w: + try: + bitmap = self.sm.bitmaps[w] + except KeyError: + self._logger_.warning("W is not valid: w received: {}".format(w)) return - bitmap = self.sm.bitmaps[self.sm.__cw__] + elif self.sm.__cw__ > w: + self._logger_.warning( + "SCHCAckReq is for a completed window (current w={} > {}). Discarding message".format( + self.sm.__cw__, w)) + return + else: # self.sm.__cw__ < w: + self._logger_.warning("Incorrect window, discarding") + return + if bitmap.is_missing(): + self._logger_.debug("Window {} has missing tiles".format(w)) + self.sm.state = self.sm.states["waiting_phase"] + self.sm.state.enter_state() + self.sm.inactivity_timer.reset() self.sm.message_to_send.append( SCHCAck(self.sm.__rule_id__, self.sm.protocol.id, - False, w=self.sm.__cw__, compressed_bitmap=bitmap.generate_compress()) + False, w=w, compressed_bitmap=bitmap.generate_compress()) ) return @@ -217,19 +255,7 @@ def generate_message(self, mtu): SCHCMessage : Message to send """ - if len(self.sm.message_to_send) != 0: - message = self.sm.message_to_send.pop(0) - if (message.size // 8) > mtu: - self.sm.message_to_send.insert(0, message) - self._logger_.warning( - "Cannot send message, no bandwidth available. MTU = {} < Message size = {}".format( - mtu, message.size // 8 - ) - ) - self._logger_.schc_message(message) - return message - else: - raise GeneratorExit("No message to send, keep receiving") + return super().generate_message(mtu) def receive_regular_schc_fragment(self, schc_message): """ @@ -254,19 +280,151 @@ def receive_regular_schc_fragment(self, schc_message): self.sm.state.receive_regular_schc_fragment(schc_message) else: self._logger_.debug("Receiving failed ones") + self.sm.__cw__ = schc_message.header.w.w + self.sm.state = self.sm.states["receiving_missing_phase"] + self.enter_state() + self.sm.state.receive_regular_schc_fragment(schc_message) + return + + def receive_all1_schc_fragment(self, schc_message): + """ + Behaviour when receiving All-1 SCHC Fragment + + Parameters + ---------- + schc_message : All1SCHCFragment + Last fragment to be received + + Returns + ------- + None, alter state + """ + self.sm.state = self.sm.states["receiving_phase"] + self.sm.state.enter_state() + self.sm.state.receive_all1_schc_fragment(schc_message) + return + + def receive_schc_ack_req(self, schc_message): + """ + Behaviour when SCHC Ack Request + + Parameters + ---------- + schc_message : SCHCAckReq + SCHC message received + + Returns + ------- + None, alter state + """ + w = schc_message.header.w.w + if w not in self.sm.bitmaps.keys(): + return + ack = SCHCAck( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + c=False, + w=w, + compressed_bitmap=self.sm.bitmaps[w].generate_compress() + ) + ack.add_padding() + self.sm.message_to_send.append(ack) + self.sm.attempts.increment() + return + + def on_expiration_time(self, alarm) -> None: + """ + On expiration time behaviour for this phase + + Parameters + ---------- + alarm : Timer + Timer of machine that activates the alarm + + Returns + ------- + None + """ + std_on_expiration_time(self, alarm) + return + + class ReceivingMissingPhase(SCHCReceiver.ReceiverState): + """ + Receiving Missing Phase, machine receive missing fragments + """ + __name__ = "Receiving Missing Phase" + + def receive_regular_schc_fragment(self, schc_message): + """ + Behaviour when receiving a Regular SCHC Fragment + + Parameters + ---------- + schc_message : RegularSCHCFragment + A regular Fragment received + + Returns + ------- + None, alter state + """ + self.sm.inactivity_timer.stop() + if self.sm.__cw__ == schc_message.header.w: fcn = schc_message.header.fcn.fcn tiles_received = schc_message.payload.size // self.sm.protocol.TILE_SIZE tiles = schc_message.payload.as_bytes() for tile in range(tiles_received): self._logger_.debug("Window received: {}\tTile {}".format( schc_message.header.w.w, fcn)) - self.sm.payload.add_content(tiles[0:self.sm.protocol.TILE_SIZE // 8]) + self.sm.add_tile(Tile( + tiles[0:self.sm.protocol.TILE_SIZE // 8] + ), w=self.sm.__cw__, fcn=fcn) tiles = tiles[self.sm.protocol.TILE_SIZE // 8:] self.sm.bitmaps[self.sm.__cw__].tile_received(fcn) - if self.sm.bitmaps[self.sm.__cw__].is_missing(): - fcn = self.sm.bitmaps[self.sm.__cw__].get_missing(fcn=True) - else: - break + try: + fcn = self.sm.bitmaps[self.sm.__cw__].get_missing(fcn=True, after=fcn) + except ValueError: + try: + fcn = self.sm.bitmaps[self.sm.__cw__].get_missing(fcn=True) + except ValueError: + ack = SCHCAck( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + c=False, + dtag=self.sm.__dtag__, + w=self.sm.__cw__, + compressed_bitmap=self.sm.bitmaps[self.sm.__cw__].generate_compress() + ) + ack.add_padding() + self.sm.message_to_send.append(ack) + self.sm.state = self.sm.states["waiting_phase"] + self.sm.state.enter_state() + self.sm.inactivity_timer.reset() + return + self._logger_.debug("Current bitmap: {}. Waiting for w={} fcn={} tile".format( + self.sm.bitmaps[ + self.sm.__cw__ + ], self.sm.__cw__, fcn) + ) + else: + self._logger_.debug("Different window received") + return + + def receive_all1_schc_fragment(self, schc_message): + """ + Behaviour when receiving All-1 SCHC Fragment + + Parameters + ---------- + schc_message : All1SCHCFragment + Last fragment to be received + + Returns + ------- + None, alter state + """ + self.sm.state = self.sm.states["receiving_phase"] + self.sm.state.enter_state() + self.sm.state.receive_all1_schc_fragment(schc_message) return def receive_schc_ack_req(self, schc_message): @@ -286,19 +444,54 @@ def receive_schc_ack_req(self, schc_message): if w not in self.sm.bitmaps.keys(): return if not self.sm.__last_window__: - self.sm.message_to_send.append( - SCHCAck(self.sm.__rule_id__, self.sm.protocol.id, - c=False, w=w, compressed_bitmap=self.sm.bitmaps[w]) + ack = SCHCAck( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + c=False, + w=w, + compressed_bitmap=self.sm.bitmaps[w].generate_compress() ) + ack.add_padding() + self.sm.message_to_send.append(ack) else: pass return - def __init__(self, protocol, dtag=None): + def __init__(self, protocol, dtag=None, on_success=None): super().__init__(protocol, dtag=dtag) self.states["receiving_phase"] = AckOnErrorReceiver.ReceivingPhase(self) self.states["waiting_phase"] = AckOnErrorReceiver.WaitingPhase(self) + self.states["receiving_missing_phase"] = AckOnErrorReceiver.ReceivingMissingPhase(self) self.state = self.states["receiving_phase"] - self.inactivity_timer.stop() + self.inactivity_timer.reset() self.state.enter_state() + self.on_success = on_success return + + +def std_on_expiration_time(state, alarm): + """ + Standard expiration time (for both phases) + + Parameters + ---------- + state : SCHCReceiver.ReceiverState + State which Inactivity timer expired + alarm : Timer + Timer of machine that activates the alarm + + Returns + ------- + None + """ + state.sm.state = state.sm.states["error"] + state.sm.state.enter_state() + abort = SCHCReceiverAbort( + rule_id=state.sm.__rule_id__, + protocol=state.sm.protocol.id, + dtag=state.sm.__dtag__, + w=state.sm.__cw__ + ) + abort.add_padding() + state.sm.message_to_send.append(abort) + return diff --git a/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_sender.py b/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_sender.py index ce4be0b..e43fb26 100644 --- a/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_sender.py +++ b/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_sender.py @@ -2,19 +2,20 @@ from schc_base import Tile, Bitmap from schc_machines import SCHCSender -from schc_messages import RegularSCHCFragment, All1SCHCFragment, SCHCAck +from schc_messages import RegularSCHCFragment, All1SCHCFragment, SCHCAck, SCHCAckReq, SCHCSenderAbort class AckOnErrorSender(SCHCSender): """ AckOnError Sender State Machine with Ack-on-Error Mode - + Attributes ---------- protocol state - residue """ + __mode__ = "Ack On Error" + class InitialPhase(SCHCSender.SenderState): """ Initial Phase of Ack on Error @@ -42,6 +43,7 @@ def __generate_tiles__(self): self._logger_.debug("{} tiles generated".format(len(sm.tiles))) self.sm.state = self.sm.states["sending_phase"] self.sm.state.enter_state() + self.sm.message_to_send.clear() return class SendingPhase(SCHCSender.SenderState): @@ -63,19 +65,21 @@ def generate_message(self, mtu): RegularSCHCFragment until all tiles are sent, then All1SCHCFragment """ - regular_message = RegularSCHCFragment(self.sm.__rule_id__, - self.sm.__fcn__, - self.sm.protocol.id, - self.sm.__dtag__, - self.sm.__cw__) + regular_message = RegularSCHCFragment( + rule_id=self.sm.__rule_id__, + fcn=self.sm.__fcn__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) mtu_available = (mtu - (regular_message.size // 8)) * 8 + # MTU should not count FPort + mtu_available += regular_message.header.rule_id.size if len(self.sm.tiles) > 1: candid = self.sm.tiles[0] while mtu_available >= candid.size and len(self.sm.tiles) > 1: regular_message.add_tile(candid) - self.sm.sent_tiles.append( - self.sm.tiles.pop(0) - ) + self.sm.sent_tiles[self.sm.__fcn__] = self.sm.tiles.pop(0).copy() mtu_available -= candid.size candid = self.sm.tiles[0] self._logger_.debug("Add tile with fcn {} for windows {}".format( @@ -85,29 +89,38 @@ def generate_message(self, mtu): self.sm.state = self.sm.states["waiting_phase"] self.sm.retransmission_timer.reset() self.sm.state.enter_state() + self.sm.message_to_send.append(SCHCAckReq( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + )) break else: last_tile = self.sm.tiles.pop(0) - self.sm.sent_tiles.append(last_tile) - self.sm.__last_window__ = True - all1 = All1SCHCFragment( - self.sm.__rule_id__, - self.sm.protocol.id, - self.sm.__dtag__, - self.sm.__cw__, - self.sm.rcs - ) - all1.add_tile(last_tile) + self.sm.sent_tiles[self.sm.__fcn__] = last_tile.copy() + all1 = send_all1(self, last_tile) self._logger_.schc_message(all1) - self.sm.state = self.sm.states["waiting_phase"] - self.sm.state.enter_state() - self.sm.retransmission_timer.reset() - all1.add_padding() return all1 regular_message.add_padding() - self._logger_.schc_message(regular_message) + #self._logger_.schc_message(regular_message) return regular_message + def receive_schc_ack(self, schc_message): + """ + Logs SCHC ACK + + Parameters + ---------- + schc_message : SCHCAck + Message received + + Returns + ------- + None + """ + self._logger_.debug("Received SCHC ACK. Ignoring.") + class WaitingPhase(SCHCSender.SenderState): """ Waiting Phase of Ack on Error @@ -125,12 +138,14 @@ def generate_message(self, mtu): ------- SCHCMessage : None - Raises - ------ - GeneratorExit - Awaits for Ack """ - raise GeneratorExit("Awaits for Ack after a windows was sent") + if len(self.sm.message_to_send) != 0: + if self.sm.message_to_send[0].size - self.sm.protocol.FPORT_LENGTH <= mtu * 8: + message = self.sm.message_to_send.pop(0) + self._logger_.schc_message(message) + return message + else: + return None def receive_schc_ack(self, schc_message): """ @@ -138,49 +153,245 @@ def receive_schc_ack(self, schc_message): Parameters ---------- schc_message : SCHCAck - SCHCAck reporting end of transmission or window + SCHCAck reporting end transmission of a window Returns ------- None, alter state """ - if self.sm.__cw__ != schc_message.header.w: - # TODO + if self.sm.__cw__ < schc_message.header.w.w: + self._logger_.warning("Incorrect window, discarding") return - else: + if self.sm.__cw__ > schc_message.header.w.w: + self._logger_.warning( + "SCHCAck is for a previous window (current w={} > {}). Retaking window".format( + self.sm.__cw__, schc_message.header.w.w)) + self.sm.__cw__ = schc_message.header.w.w + self.sm.state = self.sm.states["resending_phase"] + self.sm.state.enter_state() + self.sm.message_to_send.clear() + return + else: # self.sm.__cw__ == schc_message.header.w.w: if schc_message.header.c.c: if self.sm.__last_window__: self.sm.state = self.sm.states["end"] self.sm.state.enter_state() return else: - # TODO + self.sm.state = self.sm.states["error"] + self.sm.state.enter_state() + abort = SCHCSenderAbort( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) + abort.add_padding() + self.sm.message_to_send.append(abort) return else: - self.sm.bitmap = Bitmap.from_compress_bitmap( + bitmap = Bitmap.from_compress_bitmap( schc_message.header.compressed_bitmap.bitmap, self.sm.protocol) - self._logger_.debug("Received bitmap: {}".format(self.sm.bitmap)) - if sum(self.sm.bitmap) == len(self.sm.bitmap): - self.sm.state = self.sm.states["sending_phase"] - self.sm.retransmission_timer.stop() - self.sm.__cw__ += 1 - self.sm.__fcn__ = self.sm.protocol.WINDOW_SIZE - 1 - self.sm.state.enter_state() - return + self._logger_.debug("Received bitmap: {}".format(bitmap)) + self.sm.bitmaps[schc_message.header.w.w] = bitmap + if self.sm.__last_window__: + if bitmap.has_missing() or bitmap.get_received_tiles() < len(self.sm.sent_tiles): + self.sm.retransmission_timer.stop() + self.sm.state = self.sm.states["resending_phase"] + self.sm.state.enter_state() + self.sm.message_to_send.clear() + return + else: + self.sm.state = self.sm.states["error"] + abort = SCHCSenderAbort( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) + abort.add_padding() + self.sm.message_to_send.append(abort) + return else: - # TODO - return + if bitmap.is_missing(): + self.sm.state = self.sm.states["resending_phase"] + self.sm.retransmission_timer.stop() + self.sm.state.enter_state() + self.sm.message_to_send.clear() + return + else: + self.sm.sent_tiles.clear() + self.sm.state = self.sm.states["sending_phase"] + self.sm.retransmission_timer.stop() + self.sm.__cw__ += 1 + self.sm.__fcn__ = self.sm.protocol.WINDOW_SIZE - 1 + self.sm.state.enter_state() + self.sm.message_to_send.clear() + return + + def on_expiration_time(self, alarm): + """ + Behaviour on time expiration + + Parameters + ---------- + alarm : Timer + Timer of machine that activates the alarm + + Returns + ------- + None + """ + if self.sm.attempts.exceeds_max(): + sender_abort = SCHCSenderAbort( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) + sender_abort.add_padding() + self.sm.message_to_send.append(sender_abort) + self.sm.state = self.sm.states["error"] + self.sm.state.enter_state() + return + else: + ack_req = SCHCAckReq( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__ + ) + ack_req.add_padding() + self.sm.message_to_send.append(ack_req) + self.sm.attempts.increment() + self.sm.retransmission_timer.reset() + return - def __init__(self, protocol, payload, residue="", dtag=None): - super().__init__(protocol, payload, residue=residue, dtag=dtag) + class ResendingPhase(SCHCSender.SenderState): + """ + Resending Phase of Ack on Error, resend tiles reported missing + """ + __name__ = "Resending Phase" + + def generate_message(self, mtu): + """ + Generate regular fragment until all tiles are sent + Parameters + ---------- + mtu : int + MTU in bytes + Returns + ------- + SCHCMessage + RegularSCHCFragment or All1SCHCFragment, according + to bitmap received + """ + bitmap = self.sm.bitmaps[self.sm.__cw__] + if not bitmap.is_missing(): + self.sm.state = self.sm.states["waiting_phase"] + self.sm.state.enter_state() + self.sm.retransmission_timer.reset() + return + last_tile = min(self.sm.sent_tiles.keys()) + the_fcn = bitmap.get_missing(fcn=True) + regular_message = RegularSCHCFragment( + rule_id=self.sm.__rule_id__, + fcn=the_fcn, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__) + mtu_available = (mtu - (regular_message.size // 8)) * 8 + # MTU should not count FPort + mtu_available += regular_message.header.rule_id.size + if 0 < last_tile == the_fcn: + all1 = send_all1(self, self.sm.sent_tiles[last_tile]) + self._logger_.schc_message(all1) + return all1 + if the_fcn < last_tile: + self.sm.state = self.sm.states["waiting_phase"] + self.sm.state.enter_state() + self.sm.retransmission_timer.reset() + return + candid = self.sm.sent_tiles[the_fcn] + while mtu_available >= candid.size and len(self.sm.tiles) > 1: + regular_message.add_tile(candid) + self._logger_.debug("Add tile with fcn {} for windows {}".format( + the_fcn, self.sm.__cw__)) + mtu_available -= candid.size + bitmap.tile_received(the_fcn) + if not bitmap.is_missing(): + break + the_fcn = bitmap.get_missing(fcn=True) + if the_fcn < last_tile or (self.sm.__last_window__ and the_fcn <= last_tile): + break + candid = self.sm.sent_tiles[the_fcn] + regular_message.add_padding() + self._logger_.schc_message(regular_message) + return regular_message + + def receive_schc_ack(self, schc_message): + """ + Logs SCHC ACK + + Parameters + ---------- + schc_message : SCHCAck + Message received + + Returns + ------- + None + """ + self._logger_.debug("Received SCHC ACK. Ignoring.") + + def __init__(self, protocol, payload, padding=0, dtag=None): + super().__init__(protocol, payload, padding=padding, dtag=dtag) self.states["initial_phase"] = AckOnErrorSender.InitialPhase(self) self.states["sending_phase"] = AckOnErrorSender.SendingPhase(self) self.states["waiting_phase"] = AckOnErrorSender.WaitingPhase(self) + self.states["resending_phase"] = AckOnErrorSender.ResendingPhase(self) self.state = self.states["initial_phase"] self.state.enter_state() - if len(self.remaining_packet) % self.protocol.L2_WORD != 0: - padding = "0" * (self.protocol.L2_WORD - (len(self.remaining_packet) % self.protocol.L2_WORD)) - self.rcs = self.protocol.calculate_rcs(self.remaining_packet + padding) self.tiles = list() - self.sent_tiles = list() + self.sent_tiles = dict() self.state.__generate_tiles__() return + + +def send_all1(state, last_tile): + """ + Sends All1SCHCFragment + + Parameters + ---------- + state : AckOnErrorSender.SenderState + State to send all-1 from + last_tile : Tile + Last tile to send + + Returns + ------- + All1SCHCFragment + Fragment to send + """ + state.sm.__last_window__ = True + all1 = All1SCHCFragment( + rule_id=state.sm.__rule_id__, + protocol=state.sm.protocol.id, + dtag=state.sm.__dtag__, + w=state.sm.__cw__, + rcs=state.sm.rcs + ) + all1.add_tile(last_tile) + state.sm.state = state.sm.states["waiting_phase"] + state.sm.state.enter_state() + state.sm.retransmission_timer.reset() + ack_req = SCHCAckReq( + rule_id=state.sm.__rule_id__, + protocol=state.sm.protocol.id, + dtag=state.sm.__dtag__, + w=state.sm.__cw__ + ) + ack_req.add_padding() + state.sm.message_to_send.append(ack_req) + all1.add_padding() + return all1 diff --git a/fragmentation_layer/code/schc_machines/schc_fsm.py b/fragmentation_layer/code/schc_machines/schc_fsm.py index dc99e8f..bb34ff6 100644 --- a/fragmentation_layer/code/schc_machines/schc_fsm.py +++ b/fragmentation_layer/code/schc_machines/schc_fsm.py @@ -1,436 +1,480 @@ -""" schc_fsm: SCHC Finite State Machine Abstract Class """ - -import logging -from machine import Timer -from schc_base import AttemptsCounter, Bitmap -from schc_messages import SCHCMessage -from schc_protocols import SCHCProtocol - - -class SCHCFiniteStateMachine: - """ - Finite State Machine of Sender/Receiver (Fragmenter/Ensembler) - behaviours - - Attributes - ---------- - protocol : SCHCProtocol - Protocol to use - __rule_id__ : int - Rule ID to determines behaviour, it cannot (supposedly) change - __dtag__ : int - DTag to determines behaviour, it cannot (supposedly) change. - -1 Implies Dtag is not used and there is just one Packet for Rule ID - __cw__ : int - Current window being processed - __last_window__ : bool - Whether current window is the last window - __fcn__ : int - Current fcn in progress of window - bitmaps : Dict[int, Bitmap] - Bitmaps of all windows - attempts : AttemptsCounter - Attempts Counter that register request for SCHC ACKs - state : State - Current State of State Machine - states: Dict[str, State] - Possible States of FSM - rcs : str - Calculated rcs - __exit_msg__ : str - Message to raise when error state - __end_msg__ : str - Message to raise when end state - message_to_send : List[SCHCMessage] - Queued message to be send prior next state - """ - __mode__ = "None" - __type__ = "None" - - class State: - """ - Define the current state of a SCHC Finite State Machine - - Attributes - ---------- - sm : SCHCFiniteStateMachine - State Machine associated - """ - __name__ = "State" - - class Logger: - """ - SCHC Logger to log SCHC Fragmentation - """ - def __init__(self, state) -> None: - self.__state__ = state - return - - def __log__(self, mode) -> None: - mode("SCHC Fragment on '{}' mode, {} on '{}' state".format( - self.__state__.sm.__mode__, - self.__state__.sm.__type__, - self.__state__.__name__ - )) - second_line = "\tProtocol: {}, Rule ID: {}".format( - self.__state__.sm.protocol.__name__, - self.__state__.sm.__rule_id__ - ) - if self.__state__.sm.__dtag__ is not None: - second_line += ", DTag: {}".format( - self.__state__.sm.__dtag__ - ) - mode(second_line) - return - - def enter_state(self): - """ - Logs entering a new state - - Returns - ------- - None - """ - self.__log__(logging.debug) - return - - def schc_message(self, message): - """ - Logs a SCHC Message - - Parameters - ---------- - message : SCHCMessage - Message to show on logging - - Returns - ------- - None - """ - self.enter_state() - logging.debug("\tMessage:\n{}".format(message.as_text())) - return - - def error(self, message): - """ - Logs an error - - Parameters - ---------- - message : str - Message to show - - Returns - ------- - None - """ - self.__log__(logging.error) - logging.error("\t{}".format(message)) - return - - def warning(self, message): - """ - Logs a warn - - Parameters - ---------- - message : str - Message to show - - Returns - ------- - None - """ - self.__log__(logging.warning) - logging.warning("\t{}".format(message)) - return - - def debug(self, message): - """ - Logs an debug - - Parameters - ---------- - message : str - Message to show - - Returns - ------- - None - """ - self.__log__(logging.debug) - logging.debug("\t{}".format(message)) - return - - def info(self, message): - """ - Logs an error - - Parameters - ---------- - message : str - Message to show - - Returns - ------- - None - """ - self.__log__(logging.info) - logging.info("\t{}".format(message)) - return - - def __init__(self, state_machine): - self.sm = state_machine - self._logger_ = SCHCFiniteStateMachine.State.Logger(self) - return - - def enter_state(self): - """ - Logs enter on this state - - Returns - ------- - None - """ - self._logger_.enter_state() - return - - def generate_message(self, mtu): - """ - Generates a SCHC message to send - - Parameters - ---------- - mtu : int - Size of MTU available (in bytes) - - Returns - ------- - SCHCMessage: - SCHC Message to send - - Raises - ------ - GeneratorExit - No more SCHC Message to send on current state - """ - raise GeneratorExit("No more message to send") - - def receive_message(self, message): - """ - Does something when receiving bytes - - Parameters - ---------- - message : bytes - A message as bytes - - Returns - ------- - None: - Alter state - - Raises - ------ - RuntimeError - Unreachable behaviour - """ - return - - def on_expiration_time(self, alarm) -> None: - """ - Behaviour on time expiration - - Parameters - ---------- - alarm : Timer - Alarm that triggers expiration - - Returns - ------- - None - """ - return - - class ErrorState(State): - """ - Error State, when reached next call is going to raise a RuntimeError - """ - __name__ = "Error State" - - def receive_message(self, message): - """ - Always raise RuntimeError - - Parameters - ---------- - message : bytes - Any message - - Returns - ------- - None - - Raises - ------ - SystemExit : - Raises a SystemExit with error code -1 - """ - raise SystemExit(self.sm.__exit_msg__) - - def generate_message(self, mtu): - """ - Generates a SCHC message to send - - Parameters - ---------- - mtu : int - Size of MTU available (in bytes) - - Returns - ------- - SCHCMessage: - SCHC Message to send - - Raises - ------ - SystemExit : - Raises a SystemExit with error code -1 - """ - raise SystemExit(self.sm.__exit_msg__) - - class EndState(State): - """ - End State, when reached next call is going to stop and exit - """ - __name__ = "End State" - - def receive_message(self, message): - """ - Always raise RuntimeError - - Parameters - ---------- - message : bytes - Any message - - Returns - ------- - None - - Raises - ------ - SystemExit : - Raises a SystemExit with error code -1 - """ - raise SystemExit(self.sm.__end_msg__) - - def generate_message(self, mtu): - """ - Generates a SCHC message to send - - Parameters - ---------- - mtu : int - Size of MTU available (in bytes) - - Returns - ------- - SCHCMessage: - SCHC Message to send - - Raises - ------ - SystemExit : - Raises a SystemExit with error code -1 - """ - raise SystemExit(self.sm.__end_msg__) - - def __init__(self, protocol, dtag=None): - """ - Constructor - - Parameters - ---------- - protocol : SCHCProtocol - Protocol to use - dtag : int, optional - Dtag to identify behaviour, Default: -1 implying there is just - one SCHC Packet for Rule ID at a time - """ - self.protocol = protocol - self.__rule_id__ = protocol.RULE_ID - if protocol.T == 0: - self.__dtag__ = None - else: - self.__dtag__ = dtag - self.__cw__ = 0 - self.__last_window__ = False - self.__fcn__ = self.protocol.WINDOW_SIZE - 1 - self.bitmaps = { - self.__cw__: Bitmap(protocol), - } - self.attempts = AttemptsCounter(protocol.MAX_ACK_REQUEST) - self.states = dict() - self.states["error"] = SCHCFiniteStateMachine.ErrorState(self) - self.states["end"] = SCHCFiniteStateMachine.EndState(self) - self.state = SCHCFiniteStateMachine.State(self) - self.rcs = "" - self.__exit_msg__ = "" - self.__end_msg__ = "" - self.message_to_send = list() - return - - def receive_message(self, message): - """ - Does something when receiving bytes - - Parameters - ---------- - message : bytes - A message as bytes - - Returns - ------- - SCHCMessage: - SCHC Message to send - """ - self.state.receive_message(message) - return - - def generate_message(self, mtu): - """ - Generates a SCHC message to send - - Parameters - ---------- - mtu : int - Size of MTU available (in bytes) - - Returns - ------- - SCHCMessage : - SCHC Messages to send - - Raises - ------ - RuntimeError - No more SCHC Message to send on current state - """ - return self.state.generate_message(mtu) - - def on_expiration_time(self, alarm): - """ - Behaviour on time expiration - - Parameters - ---------- - alarm : Timer - Timer of machine that activates the alarm - - Returns - ------- - None - """ - self.state.on_expiration_time(alarm) - return +""" schc_fsm: SCHC Finite State Machine Abstract Class """ + +import sys + +if sys.implementation.name == "micropython": + import time + now = time.time +else: + import datetime + now = datetime.datetime.now + +from lopy_machine import Timer +from schc_base import AttemptsCounter, Bitmap +from schc_messages import SCHCMessage +from schc_protocols import SCHCProtocol + + +class SCHCFiniteStateMachine: + """ + Finite State Machine of Sender/Receiver (Fragmenter/Reassembler) + behaviours + + Attributes + ---------- + protocol : SCHCProtocol + Protocol to use + __rule_id__ : int + Rule ID to determines behaviour, it cannot (supposedly) change + __dtag__ : int + DTag to determines behaviour, it cannot (supposedly) change. + -1 Implies Dtag is not used and there is just one Packet for Rule ID + __cw__ : int + Current window being processed + __last_window__ : bool + Whether current window is the last window + __fcn__ : int + Current fcn in progress of window + bitmaps : Dict[int, Bitmap] + Bitmaps of all windows + attempts : AttemptsCounter + Attempts Counter that register request for SCHC ACKs + state : State + Current State of State Machine + states: Dict[str, State] + Possible States of FSM + rcs : str + Calculated rcs + __exit_msg__ : str + Message to raise when error state + __end_msg__ : str + Message to raise when end state + message_to_send : List[SCHCMessage] + Queued message to be send prior next state + """ + __mode__ = "None" + __type__ = "None" + + class State: + """ + Define the current state of a SCHC Finite State Machine + + Attributes + ---------- + sm : SCHCFiniteStateMachine + State Machine associated + """ + __name__ = "State" + + class Logger: + """ + SCHC Logger to log SCHC Fragmentation + """ + TAG = "{mode}::[{datetime}]::" + + def __init__(self, state): + self.__state__ = state + return + + def __log__(self, mode): + print( + self.TAG.format(mode=mode, datetime=now()) + + "SCHC Fragment on '{}' mode, {} on '{}' state".format( + self.__state__.sm.__mode__, + self.__state__.sm.__type__, + self.__state__.__name__ + )) + second_line = "\tProtocol: {}, Rule ID: {}".format( + self.__state__.sm.protocol.__name__, + self.__state__.sm.__rule_id__ + ) + if self.__state__.sm.__dtag__ is not None: + second_line += ", DTag: {}".format( + self.__state__.sm.__dtag__ + ) + print(second_line) + return + + def enter_state(self): + """ + Logs entering a new state + + Returns + ------- + None + """ + self.__log__("DEBUG") + return + + def schc_message(self, message): + """ + Logs a SCHC Message + + Parameters + ---------- + message : SCHCMessage + Message to show on logging + + Returns + ------- + None + """ + self.enter_state() + print("\tMessage:\n{}".format(message.as_text())) + return + + def error(self, message): + """ + Logs an error + + Parameters + ---------- + message : str + Message to show + + Returns + ------- + None + """ + self.__log__("ERROR") + print("\t{}".format(message)) + return + + def warning(self, message): + """ + Logs a warn + + Parameters + ---------- + message : str + Message to show + + Returns + ------- + None + """ + self.__log__("WARNING") + print("\t{}".format(message)) + return + + def debug(self, message): + """ + Logs an debug + + Parameters + ---------- + message : str + Message to show + + Returns + ------- + None + """ + self.__log__("DEBUG") + print("\t{}".format(message)) + return + + def info(self, message): + """ + Logs an error + + Parameters + ---------- + message : str + Message to show + + Returns + ------- + None + """ + self.__log__("INFO") + print("\t{}".format(message)) + return + + def __init__(self, state_machine): + self.sm = state_machine + self._logger_ = SCHCFiniteStateMachine.State.Logger(self) + return + + def enter_state(self): + """ + Logs enter on this state + + Returns + ------- + None + """ + self._logger_.enter_state() + return + + def generate_message(self, mtu): + """ + Message to send by default: Sends enqueue messages + + Parameters + ---------- + mtu : int + Size of MTU available (in bytes) + + Returns + ------- + SCHCMessage: + SCHC Message to send + + Raises + ------ + SystemExit : + Raises a SystemExit with error code -1 + """ + if len(self.sm.message_to_send) != 0: + message = self.sm.message_to_send.pop(0) + if (message.size // 8) > mtu: + self.sm.message_to_send.insert(0, message) + self._logger_.warning( + "Cannot send message, no bandwidth available. MTU = {} < Message size = {}".format( + mtu, message.size // 8 + ) + ) + self._logger_.schc_message(message) + return message + else: + return None + + def receive_message(self, message): + """ + Does something when receiving bytes + + Parameters + ---------- + message : bytes + A message as bytes + + Returns + ------- + None: + Alter state + + Raises + ------ + RuntimeError + Unreachable behaviour + """ + return + + def on_expiration_time(self, alarm) -> None: + """ + Behaviour on time expiration + + Parameters + ---------- + alarm : Timer + Alarm that triggers expiration + + Returns + ------- + None + """ + return + + class ErrorState(State): + """ + Error State, when reached next call is going to raise a RuntimeError + """ + __name__ = "Error State" + + def receive_message(self, message): + """ + Always raise RuntimeError + + Parameters + ---------- + message : bytes + Any message + + Returns + ------- + None + + Raises + ------ + SystemExit : + Raises a SystemExit with error code -1 + """ + raise SystemExit(self.sm.__exit_msg__) + + def generate_message(self, mtu): + """ + Generates a SCHC message to send + + Parameters + ---------- + mtu : int + Size of MTU available (in bytes) + + Returns + ------- + SCHCMessage: + SCHC Message to send + + Raises + ------ + SystemExit : + Raises a SystemExit with error code -1 + """ + raise SystemExit(self.sm.__exit_msg__) + + class EndState(State): + """ + End State, when reached next call is going to stop and exit + """ + __name__ = "End State" + + def receive_message(self, message): + """ + Always raise RuntimeError + + Parameters + ---------- + message : bytes + Any message + + Returns + ------- + None + + Raises + ------ + SystemExit : + Raises a SystemExit with error code -1 + """ + raise SystemExit(self.sm.__end_msg__) + + def generate_message(self, mtu): + """ + Generates a SCHC message to send + + Parameters + ---------- + mtu : int + Size of MTU available (in bytes) + + Returns + ------- + SCHCMessage: + SCHC Message to send + + Raises + ------ + SystemExit : + Raises a SystemExit with error code -1 + """ + raise SystemExit(self.sm.__end_msg__) + + def on_expiration_time(self, alarm) -> None: + """ + Behaviour on time expiration + + Parameters + ---------- + alarm : Timer + Alarm that triggers expiration + + Returns + ------- + None + """ + self.sm.__active__ = False + return + + def __init__(self, protocol, dtag=None): + """ + Constructor + + Parameters + ---------- + protocol : SCHCProtocol + Protocol to use + dtag : int, optional + Dtag to identify behaviour, Default: -1 implying there is just + one SCHC Packet for Rule ID at a time + """ + self.protocol = protocol + self.__rule_id__ = protocol.RULE_ID + if protocol.T == 0: + self.__dtag__ = None + else: + self.__dtag__ = dtag + self.__cw__ = 0 + self.__last_window__ = False + self.__fcn__ = self.protocol.WINDOW_SIZE - 1 + self.bitmaps = { + self.__cw__: Bitmap(protocol), + } + self.attempts = AttemptsCounter(protocol.MAX_ACK_REQUEST) + self.states = dict() + self.states["error"] = SCHCFiniteStateMachine.ErrorState(self) + self.states["end"] = SCHCFiniteStateMachine.EndState(self) + self.state = SCHCFiniteStateMachine.State(self) + self.rcs = "" + self.__exit_msg__ = "" + self.__end_msg__ = "" + self.message_to_send = list() + self.__active__ = True + return + + def receive_message(self, message): + """ + Does something when receiving bytes + + Parameters + ---------- + message : bytes + A message as bytes + + Returns + ------- + SCHCMessage: + SCHC Message to send + """ + self.state.receive_message(message) + return + + def generate_message(self, mtu): + """ + Generates a SCHC message to send + + Parameters + ---------- + mtu : int + Size of MTU available (in bytes) + + Returns + ------- + SCHCMessage : + SCHC Messages to send + + Raises + ------ + RuntimeError + No more SCHC Message to send on current state + """ + return self.state.generate_message(mtu) + + def on_expiration_time(self, alarm): + """ + Behaviour on time expiration + + Parameters + ---------- + alarm : Timer + Timer of machine that activates the alarm + + Returns + ------- + None + """ + self.state.on_expiration_time(alarm) + return + + def is_active(self): + return self.__active__ diff --git a/fragmentation_layer/code/schc_machines/schc_receiver.py b/fragmentation_layer/code/schc_machines/schc_receiver.py index 587f3ed..2ae7e38 100644 --- a/fragmentation_layer/code/schc_machines/schc_receiver.py +++ b/fragmentation_layer/code/schc_machines/schc_receiver.py @@ -137,7 +137,42 @@ def receive_schc_sender_abort(self, schc_message): def __init__(self, protocol, dtag=None): super().__init__(protocol, dtag=dtag) - self.payload: SCHCPayload = SCHCPayload() + self.payload = SCHCPayload() + self.__payload__ = dict() self.inactivity_timer = SCHCTimer(self.on_expiration_time, protocol.INACTIVITY_TIMER) self.__end_msg__ = "Message received and resembled" return + + def add_tile(self, tile, w, fcn): + """ + Adds tile to future payload + Parameters + ---------- + tile : Tile + The tile to add + w : int + Window of tile + fcn : int + FCN of tile + + Returns + ------- + None + """ + if w not in self.__payload__.keys(): + self.__payload__[w] = dict() + self.__payload__[w][fcn] = tile.copy() + return + + def reassemble(self): + """ + Reassembles payload and saved on payload + + Returns + ------- + None + """ + for w in sorted(self.__payload__.keys()): + for fcn in reversed(sorted(self.__payload__[w].keys())): + self.payload.add_content(self.__payload__[w][fcn].as_bits()) + return diff --git a/fragmentation_layer/code/schc_machines/schc_sender.py b/fragmentation_layer/code/schc_machines/schc_sender.py index 4e59bfa..ef8fd67 100644 --- a/fragmentation_layer/code/schc_machines/schc_sender.py +++ b/fragmentation_layer/code/schc_machines/schc_sender.py @@ -91,7 +91,7 @@ def receive_schc_receiver_abort(self, schc_message): self.sm.state.enter_state() return - def __init__(self, protocol, payload, residue="", dtag=None): + def __init__(self, protocol, payload, padding=0, dtag=None): """ Constructor @@ -100,16 +100,20 @@ def __init__(self, protocol, payload, residue="", dtag=None): protocol payload : bytes Payload to fragment - residue : str - Bits (as a string) obtained as residue of compression process + padding : int + Padding size dtag """ super().__init__(protocol, dtag=dtag) self.retransmission_timer = SCHCTimer(self.on_expiration_time, protocol.RETRANSMISSION_TIMER) self.retransmission_timer.stop() self.packet = payload - self.residue = residue - self.remaining_packet = residue + SCHCObject.bytes_2_bits(payload) - self.rcs = self.protocol.calculate_rcs(self.remaining_packet) + if padding == 0: + self.remaining_packet = SCHCObject.bytes_2_bits(payload) + self.rcs = self.protocol.calculate_rcs(self.remaining_packet) + else: + payload_as_bits = SCHCObject.bytes_2_bits(payload) + self.remaining_packet = payload_as_bits[0:-padding] + self.rcs = self.protocol.calculate_rcs(payload_as_bits) self.__end_msg__ = "Message sent and acknowledged" return diff --git a/fragmentation_layer/code/schc_protocols/lorawan.py b/fragmentation_layer/code/schc_protocols/lorawan.py index 5a1c2d4..3a37fc2 100644 --- a/fragmentation_layer/code/schc_protocols/lorawan.py +++ b/fragmentation_layer/code/schc_protocols/lorawan.py @@ -69,8 +69,8 @@ def __set_parameters__(self): self.WINDOW_SIZE = 63 # 2^(n=6) = 64 - {All-1 fragment} self.TILE_SIZE = 10 * 8 # 10 bytes = 80 bits self.MAX_ACK_REQUEST = 1e6 # TODO - self.INACTIVITY_TIMER = 10 # in seconds TODO - self.RETRANSMISSION_TIMER = 10 # in seconds TODO + self.INACTIVITY_TIMER = 30 # in seconds TODO + self.RETRANSMISSION_TIMER = 2 # in seconds TODO elif self.RULE_ID == LoRaWAN.ACK_ALWAYS: # Downlink data transfer self.T = 0 # in bits self.M = 1 # in bits diff --git a/fragmentation_layer/code/schc_protocols/schc_protocol.py b/fragmentation_layer/code/schc_protocols/schc_protocol.py index 07f56b7..eb43999 100644 --- a/fragmentation_layer/code/schc_protocols/schc_protocol.py +++ b/fragmentation_layer/code/schc_protocols/schc_protocol.py @@ -109,8 +109,18 @@ def calculate_rcs(self, packet): str : Result of Reassembly Check Sequence (RCS) """ - from binascii import crc32 - return hex(crc32(SCHCObject.bits_2_bytes(packet))) + # CRC32 + # Thanks to https://lxp32.github.io/docs/a-simple-example-crc32-calculation/ + from schc_base import SCHCObject + crc = 0xffffffff + for i in SCHCObject.bits_2_bytes(packet): + for j in range(8): + b = (i ^ crc) & 1 + crc >>= 1 + if b == 1: + crc = crc ^ 0xedb88320 + i >>= 1 + return hex((~crc) & 0xffffffff) def penultimate_tile(self): """ diff --git a/fragmentation_layer/examples/flask_server/app_server.py b/fragmentation_layer/examples/flask_server/app_server.py new file mode 100644 index 0000000..b8c4664 --- /dev/null +++ b/fragmentation_layer/examples/flask_server/app_server.py @@ -0,0 +1,19 @@ +from flask import Flask, make_response, request +import os +import sys +from uplink_test import receive_uplink +import logging +import socket + +app_server = Flask(__name__) + +logging.basicConfig(filename='error.log', level=logging.DEBUG) + +@app_server.route("/uplink", methods=["POST"]) +def uplink(): + return receive_uplink() + +print("Starting...") + +if __name__ == '__main__': + app_server.run(host="0.0.0.0", port=5000) diff --git a/fragmentation_layer/examples/flask_server/uplink_test.py b/fragmentation_layer/examples/flask_server/uplink_test.py new file mode 100644 index 0000000..611a744 --- /dev/null +++ b/fragmentation_layer/examples/flask_server/uplink_test.py @@ -0,0 +1,38 @@ +from flask import request +import base64 +import json + +from schc_handlers.schc_gateway_handler import SCHCGatewayHandler +from schc_protocols import SCHCProtocol + +MTU = 51 + +### After reassembly callbacks ### + +# print received message as ascii text +def process_print(msg): + print("It works!") + print(msg.decode("ascii")) + +# save message to 'received.bin' file +def process_save2file(msg): + with open("received.bin", "wb") as f: + f.write(msg) + print("Saved to received.bin") + +handler = SCHCGatewayHandler(SCHCProtocol.LoRaWAN, MTU, process_save2file) + +def receive_uplink(): + # Obtaining data from TTN requests + data = request.get_json() + downlink_url = request.headers["x-downlink-push"] + fport = data["uplink_message"]["f_port"] + dev_id = data["end_device_ids"]["device_id"] + api_key = request.headers["x-downlink-apikey"] + data = request.get_json()["uplink_message"]["frm_payload"] + payload64 = data.encode("ascii") + msg_bytes = base64.b64decode(payload64) + + # packet processing + handler.handle(msg_bytes, fport, downlink_url, dev_id, api_key) + return json.dumps({"Message": "Okay"}), 200 \ No newline at end of file diff --git a/fragmentation_layer/example/common_methods.py b/fragmentation_layer/examples/local/common_methods.py similarity index 59% rename from fragmentation_layer/example/common_methods.py rename to fragmentation_layer/examples/local/common_methods.py index b77270f..75c7bae 100644 --- a/fragmentation_layer/example/common_methods.py +++ b/fragmentation_layer/examples/local/common_methods.py @@ -1,124 +1,123 @@ -""" common_methods on example """ - -import random -import socket -import logging -from schc_machines import SCHCFiniteStateMachine - -HOST = "127.0.0.1" -MTU = 50 -SEED = 8 -PROBABILITY_OF_FAILURE = 0.05 - -random.seed(SEED) - - -def get_mtu() -> int: - """ - Get MTU size - - Returns - ------- - int : - MTU available - """ - return MTU - - -def is_this_loss() -> bool: - """ - Returns whether the send process was done - - Returns - ------- - bool : - True if sent does not occur - """ - # return random.random() < PROBABILITY_OF_FAILURE - return False - - -def send_socket(msg: bytes, port: int) -> None: - """ - Send using socket - - Parameters - ---------- - msg : bytes - Message to send - port : int - Port to use - - Returns - ------- - None, send message - """ - sock_tx = socket.socket() - sock_tx.connect((HOST, port)) - sock_tx.send(msg) - sock_tx.close() - return - - -def receive_socket(socket_rx: socket.socket) -> bytes: - """ - Receive from socket - - Parameters - ---------- - socket_rx : socket - Receiver socket - - Returns - ------- - bytes : - Message received - """ - conn, addr = socket_rx.accept() - data = conn.recv(1024) - return data - - -def messaging_loop(machine: SCHCFiniteStateMachine, socket_rx: socket.socket, sender_port: int) -> None: - """ - Loop of messages - - Parameters - ---------- - machine : SCHCFiniteStateMachine - Machine to execute on loop - socket_rx : socket - Socket of receiver - sender_port : int - Port to use to sent messages - - Returns - ------- - None - """ - exit_all = False - while True: - while True: - try: - mtu = get_mtu() - lost = is_this_loss() - message = machine.generate_message(mtu) - logging.info("Current mtu: {}".format(mtu)) - logging.info("Package sent: {}".format(not lost)) - if not lost: - send_socket(message.as_bytes(), sender_port) - except GeneratorExit: - break - except SystemExit as e: - print(e) - exit_all = True - break - if exit_all: - break - data = receive_socket(socket_rx) - if data: - try: - machine.receive_message(data) - except SystemExit as e: - print(e) - break +""" common_methods on example """ + +import random +import socket +from schc_machines import SCHCFiniteStateMachine + +HOST = "127.0.0.1" +MTU = 50 +SEED = 7 +PROBABILITY_OF_FAILURE = 0.2 + +random.seed(SEED) + + +def get_mtu() -> int: + """ + Get MTU size + + Returns + ------- + int : + MTU available + """ + return MTU + + +def is_this_loss() -> bool: + """ + Returns whether the send process was done + + Returns + ------- + bool : + True if sent does not occur + """ + return random.random() < PROBABILITY_OF_FAILURE + + +def send_socket(msg: bytes, port: int) -> None: + """ + Send using socket + + Parameters + ---------- + msg : bytes + Message to send + port : int + Port to use + + Returns + ------- + None, send message + """ + sock_tx = socket.socket() + sock_tx.connect((HOST, port)) + sock_tx.send(msg) + sock_tx.close() + return + + +def receive_socket(socket_rx: socket.socket) -> bytes: + """ + Receive from socket + + Parameters + ---------- + socket_rx : socket + Receiver socket + + Returns + ------- + bytes : + Message received + """ + conn, addr = socket_rx.accept() + data = conn.recv(1024) + return data + + +def messaging_loop(machine: SCHCFiniteStateMachine, socket_rx: socket.socket, sender_port: int) -> None: + """ + Loop of messages + + Parameters + ---------- + machine : SCHCFiniteStateMachine + Machine to execute on loop + socket_rx : socket + Socket of receiver + sender_port : int + Port to use to sent messages + + Returns + ------- + None + """ + while True: + mtu = get_mtu() + lost = is_this_loss() + try: + print("Sending...") + print("Messages enqueued: {}".format(machine.message_to_send)) + message = machine.generate_message(mtu) + print("Current mtu: {}".format(mtu)) + print("Package sent: {}".format(not lost)) + if isinstance(machine.state, SCHCFiniteStateMachine.EndState): + send_socket(message.as_bytes(), sender_port) + elif not lost and message is not None: + send_socket(message.as_bytes(), sender_port) + except SystemExit as e: + print(e) + break + try: + print("Receiving...") + data = receive_socket(socket_rx) + if data: + try: + machine.receive_message(data) + except SystemExit as e: + print(e) + break + except socket.timeout: + pass diff --git a/fragmentation_layer/example/received.txt b/fragmentation_layer/examples/local/received.txt similarity index 98% rename from fragmentation_layer/example/received.txt rename to fragmentation_layer/examples/local/received.txt index 606f887..454a602 100644 --- a/fragmentation_layer/example/received.txt +++ b/fragmentation_layer/examples/local/received.txt @@ -42,5 +42,3 @@ Copyright Notice include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License. - -And residue: 0101100 diff --git a/fragmentation_layer/example/test_receiver.py b/fragmentation_layer/examples/local/test_receiver.py similarity index 76% rename from fragmentation_layer/example/test_receiver.py rename to fragmentation_layer/examples/local/test_receiver.py index cd798c8..9186b6f 100644 --- a/fragmentation_layer/example/test_receiver.py +++ b/fragmentation_layer/examples/local/test_receiver.py @@ -1,36 +1,33 @@ -""" test_receiver: Test script Receiver side""" - -import logging -import socket -from common_methods import messaging_loop, HOST - -RECEIVER_PORT = 50006 -SENDER_PORT = 50007 - -socket_rx = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -socket_rx.bind((HOST, RECEIVER_PORT)) -socket_rx.listen(1) - - -if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) - - from schc_machines.lorawan import AckOnErrorReceiver - from schc_protocols import LoRaWAN - - receiver = AckOnErrorReceiver( - LoRaWAN(LoRaWAN.ACK_ON_ERROR) - ) - - messaging_loop(receiver, socket_rx, SENDER_PORT) - - packet = receiver.payload.as_bits() - residue = packet[0:7] - packet = packet[7:-1] - assert len(packet) % 8 == 0 - - from schc_base import SCHCObject - message = SCHCObject.bits_2_bytes(packet) - with open("received.txt", "w", encoding="utf-8") as received_file: - received_file.write(message.decode("ascii")) - received_file.write("\nAnd residue:\t{}\n".format(residue)) +""" test_receiver: Test script Receiver side""" + +import logging +import socket +from common_methods import messaging_loop, HOST + +RECEIVER_PORT = 50006 +SENDER_PORT = 50007 + +socket_rx = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +socket_rx.settimeout(2) +socket_rx.bind((HOST, RECEIVER_PORT)) +socket_rx.listen(1) + + +if __name__ == '__main__': + from schc_machines.lorawan import AckOnErrorReceiver + from schc_protocols import LoRaWAN + + receiver = AckOnErrorReceiver( + LoRaWAN(LoRaWAN.ACK_ON_ERROR), + on_success=print + ) + + messaging_loop(receiver, socket_rx, SENDER_PORT) + + packet = receiver.payload.as_bits() + assert len(packet) % 8 == 0 + + from schc_base import SCHCObject + message = SCHCObject.bits_2_bytes(packet) + with open("received.txt", "w", encoding="utf-8") as received_file: + received_file.write(message.decode("ascii")) diff --git a/fragmentation_layer/example/test_sender.py b/fragmentation_layer/examples/local/test_sender.py similarity index 94% rename from fragmentation_layer/example/test_sender.py rename to fragmentation_layer/examples/local/test_sender.py index 02a5a53..ba35268 100644 --- a/fragmentation_layer/example/test_sender.py +++ b/fragmentation_layer/examples/local/test_sender.py @@ -1,76 +1,74 @@ -""" test_sender: Test script Sender side""" - -import logging -import socket - -from common_methods import messaging_loop, HOST - -RECEIVER_PORT = 50007 -SENDER_PORT = 50006 - -MESSAGE = """ -Abstract - - The Static Context Header Compression (SCHC) specification describes - generic header compression and fragmentation techniques for Low Power - Wide Area Networks (LPWAN) technologies. SCHC is a generic mechanism - designed for great flexibility so that it can be adapted for any of - the LPWAN technologies. - - This document specifies a profile of RFC8724 to use SCHC in - LoRaWAN(R) networks, and provides elements such as efficient - parameterization and modes of operation. - -Status of This Memo - - This Internet-Draft is submitted in full conformance with the - provisions of BCP 78 and BCP 79. - - Internet-Drafts are working documents of the Internet Engineering - Task Force (IETF). Note that other groups may also distribute - working documents as Internet-Drafts. The list of current Internet- - Drafts is at https://datatracker.ietf.org/drafts/current/. - - Internet-Drafts are draft documents valid for a maximum of six months - and may be updated, replaced, or obsoleted by other documents at any - time. It is inappropriate to use Internet-Drafts as reference - material or to cite them other than as "work in progress." - - This Internet-Draft will expire on July 29, 2021. - -Copyright Notice - - Copyright (c) 2021 IETF Trust and the persons identified as the - document authors. All rights reserved. - - This document is subject to BCP 78 and the IETF Trust's Legal - Provisions Relating to IETF Documents - (https://trustee.ietf.org/license-info) in effect on the date of - publication of this document. Please review these documents - carefully, as they describe your rights and restrictions with respect - to this document. Code Components extracted from this document must - include Simplified BSD License text as described in Section 4.e of - the Trust Legal Provisions and are provided without warranty as - described in the Simplified BSD License. -""".encode("ascii") - -RESIDUE = "0101100" - -socket_rx = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -socket_rx.bind((HOST, RECEIVER_PORT)) -socket_rx.listen(1) - - -if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) - - from schc_machines.lorawan import AckOnErrorSender - from schc_protocols import LoRaWAN - - sender = AckOnErrorSender( - LoRaWAN(LoRaWAN.ACK_ON_ERROR), - MESSAGE, - RESIDUE - ) - - messaging_loop(sender, socket_rx, SENDER_PORT) +""" test_sender: Test script Sender side""" + +import logging +import socket + +from common_methods import messaging_loop, HOST + +RECEIVER_PORT = 50007 +SENDER_PORT = 50006 + +MESSAGE = """ +Abstract + + The Static Context Header Compression (SCHC) specification describes + generic header compression and fragmentation techniques for Low Power + Wide Area Networks (LPWAN) technologies. SCHC is a generic mechanism + designed for great flexibility so that it can be adapted for any of + the LPWAN technologies. + + This document specifies a profile of RFC8724 to use SCHC in + LoRaWAN(R) networks, and provides elements such as efficient + parameterization and modes of operation. + +Status of This Memo + + This Internet-Draft is submitted in full conformance with the + provisions of BCP 78 and BCP 79. + + Internet-Drafts are working documents of the Internet Engineering + Task Force (IETF). Note that other groups may also distribute + working documents as Internet-Drafts. The list of current Internet- + Drafts is at https://datatracker.ietf.org/drafts/current/. + + Internet-Drafts are draft documents valid for a maximum of six months + and may be updated, replaced, or obsoleted by other documents at any + time. It is inappropriate to use Internet-Drafts as reference + material or to cite them other than as "work in progress." + + This Internet-Draft will expire on July 29, 2021. + +Copyright Notice + + Copyright (c) 2021 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. +""".encode("ascii") + +RESIDUE = "0101100" + +socket_rx = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +socket_rx.settimeout(1) +socket_rx.bind((HOST, RECEIVER_PORT)) +socket_rx.listen(1) + + +if __name__ == '__main__': + from schc_machines.lorawan import AckOnErrorSender + from schc_protocols import LoRaWAN + + sender = AckOnErrorSender( + LoRaWAN(LoRaWAN.ACK_ON_ERROR), + MESSAGE + ) + + messaging_loop(sender, socket_rx, SENDER_PORT) diff --git a/fragmentation_layer/examples/lopy/main.py b/fragmentation_layer/examples/lopy/main.py new file mode 100644 index 0000000..d5f56a7 --- /dev/null +++ b/fragmentation_layer/examples/lopy/main.py @@ -0,0 +1,45 @@ +from network import LoRa +import socket +import binascii +import struct +import time + +from message import MESSAGE as MESSAGE +from schc_handlers import SCHCNodeHandler +from schc_protocols import SCHCProtocol + +# Initialise LoRa in LORAWAN mode. +# Please pick the region that matches where you are using the device: +# Asia = LoRa.AS923 +# Australia = LoRa.AU915 +# Europe = LoRa.EU868 +# United States = LoRa.US915 +lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915) + +# create an ABP authentication params +dev_addr = struct.unpack(">l", binascii.unhexlify('260138EA'))[0] +nwk_swkey = binascii.unhexlify('133CD7499E57B03FF0430D14FF479D71') +app_swkey = binascii.unhexlify('A9CEF74251EC08F21FBEFBD8E08D3206') + +# Uncomment for US915 / AU915 & Pygate +for i in range(0, 8): + lora.remove_channel(i) +for i in range(9, 65): + lora.remove_channel(i) +for i in range(66, 72): + lora.remove_channel(i) + +# join a network using ABP (Activation By Personalization) +lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey)) + +# create a LoRa socket +s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) + +# set the LoRaWAN data rate +s.setsockopt(socket.SOL_LORA, socket.SO_DR, 0) + +handler = SCHCNodeHandler(SCHCProtocol.LoRaWAN, 51) + +# send message +handler.send_package(MESSAGE) +handler.start(s) diff --git a/fragmentation_layer/examples/lopy/message.py b/fragmentation_layer/examples/lopy/message.py new file mode 100644 index 0000000..4d54f6b --- /dev/null +++ b/fragmentation_layer/examples/lopy/message.py @@ -0,0 +1,46 @@ +MESSAGE = """ +Abstract + + The Static Context Header Compression (SCHC) specification describes + generic header compression and fragmentation techniques for Low Power + Wide Area Networks (LPWAN) technologies. SCHC is a generic mechanism + designed for great flexibility so that it can be adapted for any of + the LPWAN technologies. + + This document specifies a profile of RFC8724 to use SCHC in + LoRaWAN(R) networks, and provides elements such as efficient + parameterization and modes of operation. + +Status of This Memo + + This Internet-Draft is submitted in full conformance with the + provisions of BCP 78 and BCP 79. + + Internet-Drafts are working documents of the Internet Engineering + Task Force (IETF). Note that other groups may also distribute + working documents as Internet-Drafts. The list of current Internet- + Drafts is at https://datatracker.ietf.org/drafts/current/. + + Internet-Drafts are draft documents valid for a maximum of six months + and may be updated, replaced, or obsoleted by other documents at any + time. It is inappropriate to use Internet-Drafts as reference + material or to cite them other than as "work in progress." + + This Internet-Draft will expire on July 29, 2021. + +Copyright Notice + + Copyright (c) 2021 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. +""".encode("ascii") +SHORT_MESSAGE = "Copyright (c) 2021 IETF Trust and the persons identified as the document authors.".encode("ascii") \ No newline at end of file diff --git a/fragmentation_layer/tests/test_base/__init__.py b/fragmentation_layer/tests/test_base/__init__.py deleted file mode 100644 index 21e635d..0000000 --- a/fragmentation_layer/tests/test_base/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" test of schc_base package """ - -from test_base.test_schc_object import SCHCObjectTest diff --git a/fragmentation_layer/tests/test_base/test_bitmap.py b/fragmentation_layer/tests/test_base/test_bitmap.py deleted file mode 100644 index ac8305c..0000000 --- a/fragmentation_layer/tests/test_base/test_bitmap.py +++ /dev/null @@ -1,77 +0,0 @@ -""" test_bitmap: Unit test for Bitmap class """ - -from unittest import TestCase, main -from schc_base import Bitmap -from schc_protocols import LoRaWAN - - -class TestBitmap(TestCase): - - def test_constructor(self): - bitmap = Bitmap(LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR)) - self.assertEqual([False] * LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR).WINDOW_SIZE, bitmap.__bitmap__, - "Wrong bitmap generated") - self.assertEqual(LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR).WINDOW_SIZE, len(bitmap.__bitmap__), "Wrong length of bitmap") - bitmap = Bitmap(LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR), short_size=10) - self.assertEqual([False] * 10, bitmap.__bitmap__, "Wrong bitmap generated (short)") - self.assertEqual(10, len(bitmap.__bitmap__), "Wrong length of bitmap (short)") - - def test_register_tile(self): - bitmap = Bitmap(LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR)) - bitmap.tile_received(LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR).WINDOW_SIZE - 1) - self.assertTrue(bitmap.__bitmap__[0], "Wrong first tile registered") - fcn = 30 - bitmap.tile_received(fcn) - self.assertTrue(bitmap.__bitmap__[LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR).WINDOW_SIZE - fcn - 1], - "Wrong tile registered {}".format(fcn)) - bitmap.tile_received(0) - self.assertTrue(bitmap.__bitmap__[LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR).WINDOW_SIZE - 1], - "Wrong last tile registered") - self.assertEqual(LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR).WINDOW_SIZE, len(bitmap.__bitmap__), - "Length changed") - self.assertEqual(3, sum(bitmap.__bitmap__), "Wrong registration") - - def test_compression_all_one(self): - protocol_to_use = LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR) - bitmap = Bitmap(protocol_to_use) - bitmap.__bitmap__ = [True] * protocol_to_use.WINDOW_SIZE - compressed_bitmap = bitmap.generate_compress() - self.assertEqual( - protocol_to_use.L2_WORD - (sum([ - protocol_to_use.RULE_SIZE, protocol_to_use.T, protocol_to_use.M, 1 - ]) % protocol_to_use.L2_WORD), - len(compressed_bitmap), "Wrong compression") - self.assertEqual( - protocol_to_use.L2_WORD - (sum([ - protocol_to_use.RULE_SIZE, protocol_to_use.T, protocol_to_use.M, 1 - ]) % protocol_to_use.L2_WORD), - sum(compressed_bitmap), "Wrong compression") - - def test_compression_uncompressed(self): - protocol_to_use = LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR) - bitmap = Bitmap(protocol_to_use) - compressed_bitmap = bitmap.generate_compress() - self.assertEqual(protocol_to_use.WINDOW_SIZE, len(compressed_bitmap), "Wrong compression") - self.assertEqual(0, sum(compressed_bitmap), "Wrong compression") - - def test_compression(self): - protocol_to_use = LoRaWAN(rule_id=LoRaWAN.ACK_ON_ERROR) - bitmap = Bitmap(protocol_to_use) - bitmap.__bitmap__ = [True] * protocol_to_use.WINDOW_SIZE - bitmap.__bitmap__[protocol_to_use.L2_WORD] = False - compressed_bitmap = bitmap.generate_compress() - self.assertEqual( - protocol_to_use.L2_WORD - (sum([ - protocol_to_use.RULE_SIZE, protocol_to_use.T, - protocol_to_use.M, 1 - ]) % protocol_to_use.L2_WORD) + protocol_to_use.L2_WORD, - len(compressed_bitmap), "Wrong compression") - self.assertEqual( - protocol_to_use.L2_WORD - (sum([ - protocol_to_use.RULE_SIZE, protocol_to_use.T, protocol_to_use.M, 1 - ]) % protocol_to_use.L2_WORD) + protocol_to_use.L2_WORD - 1, - sum(compressed_bitmap), "Wrong compression") - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_base/test_schc_object.py b/fragmentation_layer/tests/test_base/test_schc_object.py deleted file mode 100644 index 0a17ea2..0000000 --- a/fragmentation_layer/tests/test_base/test_schc_object.py +++ /dev/null @@ -1,40 +0,0 @@ -""" test_schc_object: Unit testing of SCHCObject static methods """ - -from unittest import TestCase, main -from schc_base import SCHCObject - - -class SCHCObjectTest(TestCase): - - def test_bits_2_bytes(self): - actual = SCHCObject.bits_2_bytes("01001000") - expected = b'H' - self.assertEqual(expected, actual, "H wrong decoded") - hello = "01001000" + "01100101" + "01101100" + "01101100" + "01101111" - actual = SCHCObject.bits_2_bytes(hello) - expected = b'Hello' - self.assertEqual(expected, actual, "H wrong decoded") - actual = SCHCObject.bits_2_bytes("1001000") - expected = b'H' - self.assertEqual(expected, actual, "H wrong decoded") - actual = SCHCObject.bits_2_bytes("1001") - expected = b'\t' - self.assertEqual(expected, actual, "\\t character wrong decoded") - actual = SCHCObject.bits_2_bytes("1001") - expected = SCHCObject.bits_2_bytes("00001001") - self.assertEqual(expected, actual, "\\t character wrong decoded") - - def test_bytes_2_bits(self): - actual = SCHCObject.bytes_2_bits(b'Hello') - expected = "01001000" + "01100101" + "01101100" + "01101100" + "01101111" - self.assertEqual(expected, actual, "Hello wrong encoded") - actual = SCHCObject.bytes_2_bits(b'a') - expected = "01100001" - self.assertEqual(expected, actual, "Letter a wrong encoded") - actual = SCHCObject.bytes_2_bits(b'1') - expected = "00110001" - self.assertEqual(expected, actual, "Number 1 wrong encoded") - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_header/__init__.py b/fragmentation_layer/tests/test_header/__init__.py deleted file mode 100644 index 2564c98..0000000 --- a/fragmentation_layer/tests/test_header/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" test of schc_header package """ - -from test_header.test_bitmap import TestCompressedBitmap -from test_header.test_dtag import TestDTag -from test_header.test_fcn import TestFragmentCompressedNumber -from test_header.test_rcs import TestRCS -# TODO: Unittest of Header Objects diff --git a/fragmentation_layer/tests/test_header/test_bitmap.py b/fragmentation_layer/tests/test_header/test_bitmap.py deleted file mode 100644 index 27fa381..0000000 --- a/fragmentation_layer/tests/test_header/test_bitmap.py +++ /dev/null @@ -1,70 +0,0 @@ -"""test_bitmap: Compressed Bitmap Unit Test""" - -from unittest import TestCase, main -from schc_messages.schc_header import CompressedBitmap - - -class TestCompressedBitmap(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - self.bitmap = CompressedBitmap([True, False, False, True], 4) - self.un_bitmap = CompressedBitmap([True, False], 5) - self.empty_bitmap = CompressedBitmap([], 0) - - def test_wrong_bitmap_size(self) -> None: - self.assertRaises(AssertionError, CompressedBitmap, [True] * 10, 8) - - def test_fill_up(self) -> None: - self.assertEqual(2, len(self.un_bitmap.bitmap), "Filled up length wrong") - self.assertEqual( - [True, False], - self.un_bitmap.bitmap, - "Filled up values wrong" - ) - - def test_size(self) -> None: - self.assertEqual(4, self.bitmap.size, "Wrong Size of Bitmap") - self.assertEqual(4, self.bitmap.window_size, "Wrong Window Size of Bitmap") - self.assertEqual(2, self.un_bitmap.size, "Wrong Size of Filled up Bitmap") - self.assertEqual(5, self.un_bitmap.window_size, "Wrong Window Size of Filled up Bitmap") - self.assertEqual(0, self.empty_bitmap.size, "Wrong Size of Empty Bitmap") - self.assertEqual(0, self.empty_bitmap.window_size, "Wrong Window Size of Empty Bitmap") - - def test_as_bits(self) -> None: - self.assertEqual("1001", self.bitmap.as_bits(), "Bitmap bits wrong mapped") - self.assertEqual("10", self.un_bitmap.as_bits(), "Filled Up Bitmap bits wrong mapped") - self.assertEqual("", self.empty_bitmap.as_bits(), "Empty Bitmap bits wrong mapped") - - def test_format_text(self) -> None: - self.assertEqual( - ("", " Compressed Bitmap ", "1001 "), - self.bitmap.format_text(), - "Wrong text generated for Bitmap" - ) - self.assertEqual( - ("", " Compressed Bitmap ", "10 "), - self.un_bitmap.format_text(), - "Wrong text generated for Filled up Bitmap" - ) - self.assertEqual( - ("", "", ""), - self.empty_bitmap.format_text(), - "Wrong text generated for Empty Bitmap" - ) - giant_bitmap = CompressedBitmap([True] * 25, 25) - self.assertEqual( - ("", " Compressed Bitmap ", "1"*25), - giant_bitmap.format_text(), - "Wrong text generated for Empty Bitmap" - ) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_header/test_dtag.py b/fragmentation_layer/tests/test_header/test_dtag.py deleted file mode 100644 index 9793f16..0000000 --- a/fragmentation_layer/tests/test_header/test_dtag.py +++ /dev/null @@ -1,59 +0,0 @@ -"""test_dtag: DTag Unit Test""" - -from unittest import TestCase, main -from schc_messages.schc_header import DTag - - -class TestDTag(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - self.dtag = DTag(1, 2) - self.giant_dtag = DTag(20, 8) - self.empty_dtag = DTag(1502, 0) - - def test_size(self) -> None: - self.assertEqual(2, self.dtag.size, "Wrong Size of DTag") - self.assertEqual(2, self.dtag.t, "T Size of DTag") - self.assertEqual(8, self.giant_dtag.size, "Wrong Size of Giant DTag") - self.assertEqual(8, self.giant_dtag.t, "T Size of Giant Bitmap") - self.assertEqual(0, self.empty_dtag.size, "Wrong Size of Empty DTag") - self.assertEqual(0, self.empty_dtag.t, "T Size of Empty DTag") - - def test_as_bits(self) -> None: - self.assertEqual("01", self.dtag.as_bits(), "DTag bits wrong mapped") - self.assertEqual("00010100", self.giant_dtag.as_bits(), "Giant DTag bits wrong mapped") - self.assertEqual("", self.empty_dtag.as_bits(), "Empty DTag bits wrong mapped") - - def test_format_text(self) -> None: - self.assertEqual( - (" T=2 ", " DTag ", "01 "), - self.dtag.format_text(), - "Wrong text generated for DTag" - ) - self.assertEqual( - (" T=8 ", " DTag ", "00010100"), - self.giant_dtag.format_text(), - "Wrong text generated for Giant DTag" - ) - self.assertEqual( - ("", "", ""), - self.empty_dtag.format_text(), - "Wrong text generated for Empty DTag" - ) - more_giant_dtag = DTag(456, 20) - self.assertEqual( - (" T=20 ", " DTag ", "00000000000111001000"), - more_giant_dtag.format_text(), - "Wrong text generated for Really Giant DTag" - ) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_header/test_fcn.py b/fragmentation_layer/tests/test_header/test_fcn.py deleted file mode 100644 index d75de81..0000000 --- a/fragmentation_layer/tests/test_header/test_fcn.py +++ /dev/null @@ -1,71 +0,0 @@ -"""test_fcn: Fragment Compressed Number Unit Test""" - -from unittest import TestCase, main -from schc_messages.schc_header import FragmentedCompressedNumber - - -class TestFragmentCompressedNumber(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - self.fcn = FragmentedCompressedNumber(56, 6) - self.giant_fcn = FragmentedCompressedNumber(185, 10) - self.mini_fcn = FragmentedCompressedNumber(0, 2) - self.empty_fcn = FragmentedCompressedNumber(0, 0) - - def test_wrong_fcn_size(self) -> None: - self.assertRaises(AssertionError, FragmentedCompressedNumber, 64, 6) - - def test_size(self) -> None: - self.assertEqual(6, self.fcn.size, "Wrong Size of FCN") - self.assertEqual(6, self.fcn.n, "N Size of FCN") - self.assertEqual(10, self.giant_fcn.size, "Wrong Size of Giant FCN") - self.assertEqual(10, self.giant_fcn.n, "N Size of Giant FCN") - self.assertEqual(2, self.mini_fcn.size, "Wrong Size of Mini FCN") - self.assertEqual(2, self.mini_fcn.n, "N Size of Mini FCN") - self.assertEqual(0, self.empty_fcn.size, "Wrong Size of Empty FCN") - self.assertEqual(0, self.empty_fcn.n, "N Size of Empty FCN") - - def test_as_bits(self) -> None: - self.assertEqual("111000", self.fcn.as_bits(), "FCN bits wrong mapped") - self.assertEqual("0010111001", self.giant_fcn.as_bits(), "Giant FCN bits wrong mapped") - self.assertEqual("00", self.mini_fcn.as_bits(), "Mini FCN bits wrong mapped") - self.assertEqual("", self.empty_fcn.as_bits(), "Empty FCN bits wrong mapped") - - def test_format_text(self) -> None: - self.assertEqual( - ("--- N=6 ---", " FCN ", "111000 "), - self.fcn.format_text(), - "Wrong text generated for FCN" - ) - self.assertEqual( - ("--- N=10 ---", " FCN ", "0010111001 "), - self.giant_fcn.format_text(), - "Wrong text generated for Giant FCN" - ) - self.assertEqual( - ("--- N=2 ---", " FCN ", "00 "), - self.mini_fcn.format_text(), - "Wrong text generated for Mini FCN" - ) - self.assertEqual( - ("", "", ""), - self.empty_fcn.format_text(), - "Wrong text generated for Empty FCN" - ) - more_giant_fcn = FragmentedCompressedNumber(185, 13) - self.assertEqual( - ("--- N=13 ---", " FCN ", "0000010111001"), - more_giant_fcn.format_text(), - "Wrong text generated for Really Giant FCN" - ) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_header/test_integrity_check.py b/fragmentation_layer/tests/test_header/test_integrity_check.py deleted file mode 100644 index 7c04995..0000000 --- a/fragmentation_layer/tests/test_header/test_integrity_check.py +++ /dev/null @@ -1,42 +0,0 @@ -"""test_integrity_check: Integrity Check Unit Test""" - -from unittest import TestCase, main -from schc_messages.schc_header import IntegrityCheck - - -class TestIntegrityCheck(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - self.c = IntegrityCheck(True) - self.not_c = IntegrityCheck(False) - - def test_size(self) -> None: - self.assertEqual(1, self.c.size, "Wrong Size of IC") - self.assertEqual(1, self.not_c.size, "Wrong Size of Not IC") - - def test_as_bits(self) -> None: - self.assertEqual("1", self.c.as_bits(), "IC bits wrong mapped") - self.assertEqual("0", self.not_c.as_bits(), "Not IC bits wrong mapped") - - def test_format_text(self) -> None: - self.assertEqual( - (" 1 ", " C ", " 1 "), - self.c.format_text(), - "Wrong text generated for IC" - ) - self.assertEqual( - (" 1 ", " C ", " 0 "), - self.not_c.format_text(), - "Wrong text generated for Not IC" - ) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_header/test_rcs.py b/fragmentation_layer/tests/test_header/test_rcs.py deleted file mode 100644 index c91e820..0000000 --- a/fragmentation_layer/tests/test_header/test_rcs.py +++ /dev/null @@ -1,34 +0,0 @@ -"""test_rcs: Reassembly Check Sequence Unit Test""" - -from unittest import TestCase, main -from schc_messages.schc_header import ReassemblyCheckSequence - - -class TestRCS(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - # TODO: Pending unit testing of headers - # self.rcs = ReassemblyCheckSequence() - - def test_wrong_fcn_size(self) -> None: - self.assertTrue(True) - - def test_size(self) -> None: - self.assertTrue(True) - - def test_as_bits(self) -> None: - self.assertTrue(True) - - def test_format_text(self) -> None: - self.assertTrue(True) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_message/__init__.py b/fragmentation_layer/tests/test_message/__init__.py deleted file mode 100644 index 73bb6db..0000000 --- a/fragmentation_layer/tests/test_message/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""" test of schc_message package """ diff --git a/fragmentation_layer/tests/test_message/test_all1_fragment.py b/fragmentation_layer/tests/test_message/test_all1_fragment.py deleted file mode 100644 index 467f762..0000000 --- a/fragmentation_layer/tests/test_message/test_all1_fragment.py +++ /dev/null @@ -1,132 +0,0 @@ -""" test_all1_fragment: All1SCHCFragment Unit test """ - -from unittest import TestCase, main -from schc_messages import All1SCHCFragment -from schc_base import Tile -from schc_protocols import SCHCProtocol - - -class TestAll1Fragment(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - self.all1 = All1SCHCFragment(20, protocol=SCHCProtocol.LoRaWAN, - dtag=4, w=2, rcs='0xacde3214') - - def test_base(self) -> None: - self.assertEqual(48, self.all1.size, "Wrong size on initialization") - self.assertEqual(0, self.all1.payload.size, "Payload present before added") - self.assertEqual(0, self.all1.padding.size, "Padding present before added") - self.assertEqual(48, self.all1.header.size, "Header size incorrect") - self.assertEqual(8, self.all1.header.rule_id.size, "Wrong RuleID size") - self.assertEqual(0, self.all1.header.dtag.size, "Wrong DTag size") - self.assertEqual(2, self.all1.header.w.size, "Wrong W size") - self.assertEqual(6, self.all1.header.fcn.size, "Wrong FCN size") - self.assertEqual(32, self.all1.header.rcs.size, "Wrong RCS size") - self.assertEqual(0, self.all1.header.c.size, "Wrong IC size") - self.assertEqual(0, self.all1.header.compressed_bitmap.size, "Wrong Bitmap size") - - def test_add_tile(self) -> None: - self.assertEqual(0, self.all1.payload.size, "Payload prior initialization") - init_size = self.all1.size - first_tile = Tile(b'Hello') - second_tile = Tile(b' ') - third_tile = Tile(b'World!') - payload_size = self.all1.add_tile(first_tile) - self.assertEqual(payload_size - init_size, first_tile.size, "First tile size got wrong (on return)") - self.assertEqual(payload_size - init_size, self.all1.payload.size, "First tile size got wrong (on instance)") - self.assertEqual(payload_size, self.all1.size, "Size got wrong (first)") - payload_size = self.all1.add_tile(second_tile) - self.assertEqual(payload_size - init_size, first_tile.size + second_tile.size, - "Second tile size got wrong (on return)") - self.assertEqual(payload_size - init_size, self.all1.payload.size, "Second tile size got wrong (on instance)") - self.assertEqual(payload_size, self.all1.size, "Size got wrong (second)") - payload_size = self.all1.add_tile(third_tile) - self.assertEqual(payload_size - init_size, first_tile.size + second_tile.size + third_tile.size, - "Third tile size got wrong (on return)") - self.assertEqual(payload_size - init_size, self.all1.payload.size, "Third tile size got wrong (on instance)") - self.assertEqual(payload_size, self.all1.size, "Size got wrong (third)") - - def test_add_padding(self) -> None: - self.assertEqual(0, self.all1.padding.size, "Pad added (on init)") - size = self.all1.add_padding() - self.assertEqual(48, size, "Pad added (on return)") - self.assertEqual(48, self.all1.size, "Pad added (on instance)") - self.assertEqual(0, self.all1.padding.size, "Pad added (on padding)") - payload_size = self.all1.add_tile(Tile(b'Hello')) - self.assertEqual(48, size, "Pad added (on return)") - self.assertEqual(payload_size, self.all1.size, "Pad added (on instance)") - self.assertEqual(0, self.all1.padding.size, "Pad added (on padding)") - - def test_as_bits(self) -> None: - self.assertEqual( - "000101001011111110101100110111100011001000010100", - self.all1.as_bits(), - "Just header got wrong" - ) - self.all1.add_tile(Tile(b'Hello')) - self.all1.add_padding() - self.assertEqual( - "0001010010111111101011001101111000110010000101000100100001100101011011000110110001101111", - self.all1.as_bits(), - "Message got wrong" - ) - - def test_as_bytes(self) -> None: - self.assertEqual( - (b'\x14', b'\xbf\xac\xde2\x14'), - self.all1.as_bytes(), - "Just header got wrong" - ) - self.all1.add_tile(Tile(b'Hello')) - self.all1.add_padding() - self.assertEqual( - (b'\x14', b'\xbf\xac\xde2\x14Hello'), - self.all1.as_bytes(), - "Message got wrong" - ) - - def test_as_text(self) -> None: - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---| U=32 |\n" + - "| RuleID | W | FCN | RCS |\n" + - "|00010100|10 |111111 |10101100110111100011001000010100|", - self.all1.as_text(), - "Just header got wrong" - ) - self.all1.add_tile(Tile(b'Hello')) - self.all1.add_padding() - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---| U=32 |\n" + - "| RuleID | W | FCN | RCS |" + - " Fragment Payload |\n" + - "|00010100|10 |111111 |10101100110111100011001000010100|" + - "0100100001100101011011000110110001101111|", - self.all1.as_text(), - "Message got wrong" - ) - - def test_from_bytes(self) -> None: - all1 = All1SCHCFragment.from_bytes(b'\x14\xbf\xac\xde2\x14Hello') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---| U=32 |\n" + - "| RuleID | W | FCN | RCS |" + - " Fragment Payload |\n" + - "|00010100|10 |111111 |10101100110111100011001000010100|" + - "0100100001100101011011000110110001101111|", - all1.as_text(), - "Message parsed wrong" - ) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_message/test_regular_fragment.py b/fragmentation_layer/tests/test_message/test_regular_fragment.py deleted file mode 100644 index a00a02e..0000000 --- a/fragmentation_layer/tests/test_message/test_regular_fragment.py +++ /dev/null @@ -1,129 +0,0 @@ -""" test_regular_fragment: RegularSCHCFragment Unit test """ - -from unittest import TestCase, main -from schc_messages import RegularSCHCFragment -from schc_base import Tile -from schc_protocols import SCHCProtocol - - -class TestRegularFragment(TestCase): - - def setUp(self) -> None: - """ - Sets up unit test - - Returns - ------- - None - """ - self.regular = RegularSCHCFragment(20, protocol=SCHCProtocol.LoRaWAN, - dtag=4, w=3, fcn=17) - - def test_base(self) -> None: - self.assertEqual(16, self.regular.size, "Wrong size on initialization") - self.assertEqual(0, self.regular.payload.size, "Payload present before added") - self.assertEqual(0, self.regular.padding.size, "Padding present before added") - self.assertEqual(16, self.regular.header.size, "Header size incorrect") - self.assertEqual(8, self.regular.header.rule_id.size, "Wrong RuleID size") - self.assertEqual(0, self.regular.header.dtag.size, "Wrong DTag size") - self.assertEqual(2, self.regular.header.w.size, "Wrong W size") - self.assertEqual(6, self.regular.header.fcn.size, "Wrong FCN size") - self.assertEqual(0, self.regular.header.rcs.size, "Wrong RCS size") - self.assertEqual(0, self.regular.header.c.size, "Wrong IC size") - self.assertEqual(0, self.regular.header.compressed_bitmap.size, "Wrong Bitmap size") - - def test_add_tile(self) -> None: - self.assertEqual(0, self.regular.payload.size, "Payload prior initialization") - init_size = self.regular.size - first_tile = Tile(b'Hello') - second_tile = Tile(b' ') - third_tile = Tile(b'World!') - payload_size = self.regular.add_tile(first_tile) - self.assertEqual(payload_size - init_size, first_tile.size, "First tile size got wrong (on return)") - self.assertEqual(payload_size - init_size, self.regular.payload.size, "First tile size got wrong (on instance)") - self.assertEqual(payload_size, self.regular.size, "Size got wrong (first)") - payload_size = self.regular.add_tile(second_tile) - self.assertEqual(payload_size - init_size, first_tile.size + second_tile.size, "Second tile size got wrong (on return)") - self.assertEqual(payload_size - init_size, self.regular.payload.size, "Second tile size got wrong (on instance)") - self.assertEqual(payload_size, self.regular.size, "Size got wrong (second)") - payload_size = self.regular.add_tile(third_tile) - self.assertEqual(payload_size - init_size, first_tile.size + second_tile.size + third_tile.size, - "Third tile size got wrong (on return)") - self.assertEqual(payload_size - init_size, self.regular.payload.size, "Third tile size got wrong (on instance)") - self.assertEqual(payload_size, self.regular.size, "Size got wrong (third)") - - def test_add_padding(self) -> None: - self.assertEqual(0, self.regular.padding.size, "Pad added (on init)") - size = self.regular.add_padding() - self.assertEqual(16, size, "Pad added (on return)") - self.assertEqual(16, self.regular.size, "Pad added (on instance)") - self.assertEqual(0, self.regular.padding.size, "Pad added (on padding)") - payload_size = self.regular.add_tile(Tile(b'Hello')) - self.assertEqual(16, size, "Pad added (on return)") - self.assertEqual(payload_size, self.regular.size, "Pad added (on instance)") - self.assertEqual(0, self.regular.padding.size, "Pad added (on padding)") - - def test_as_bits(self) -> None: - self.assertEqual( - "0001010011010001", - self.regular.as_bits(), - "Just header got wrong" - ) - self.regular.add_tile(Tile(b'Hello')) - self.regular.add_padding() - self.assertEqual( - "00010100110100010100100001100101011011000110110001101111", - self.regular.as_bits(), - "Message got wrong" - ) - - def test_as_bytes(self) -> None: - self.assertEqual( - (b'\x14', b'\xd1'), - self.regular.as_bytes(), - "Just header got wrong" - ) - self.regular.add_tile(Tile(b'Hello')) - self.regular.add_padding() - self.assertEqual( - (b'\x14', b'\xd1Hello'), - self.regular.as_bytes(), - "Message got wrong" - ) - - def test_as_text(self) -> None: - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---|\n" + - "| RuleID | W | FCN |\n" + - "|00010100|11 |010001 |", - self.regular.as_text(), - "Just header got wrong" - ) - self.regular.add_tile(Tile(b'Hello')) - self.regular.add_padding() - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---|\n" + - "| RuleID | W | FCN | Fragment Payload |\n" + - "|00010100|11 |010001 |0100100001100101011011000110110001101111|", - self.regular.as_text(), - "Message got wrong" - ) - - def test_from_bytes(self) -> None: - regular = RegularSCHCFragment.from_bytes(b'\x14\xd1HelloWorld') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---|\n" + - "| RuleID | W | FCN |" + - " Fragment Payload |\n" + - "|00010100|11 |010001 |" + - "01001000011001010110110001101100011011110101011101101111011100100110110001100100|", - regular.as_text(), - "Message parsed wrong" - ) - - -if __name__ == '__main__': - main() diff --git a/fragmentation_layer/tests/test_parser/__init__.py b/fragmentation_layer/tests/test_parser/__init__.py deleted file mode 100644 index 435494e..0000000 --- a/fragmentation_layer/tests/test_parser/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""" test of schc_parser package """ diff --git a/fragmentation_layer/tests/test_parser/test_parser_lo_ra_wan.py b/fragmentation_layer/tests/test_parser/test_parser_lo_ra_wan.py deleted file mode 100644 index fc752c1..0000000 --- a/fragmentation_layer/tests/test_parser/test_parser_lo_ra_wan.py +++ /dev/null @@ -1,152 +0,0 @@ -""" test_parser_lo_ra_wan: Unit test for parsing over LoRaWAN """ - -from unittest import TestCase, main -from schc_parsers import SCHCParser -from schc_protocols import LoRaWAN - - -class TestParserLoRaWAN(TestCase): - - def test_regular(self): - # AckOnError - expected_regular = SCHCParser.from_bytes(LoRaWAN(), b'\x14\xd1HelloWorld') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---|\n" + - "| RuleID | W | FCN |" + - " Fragment Payload |\n" + - "|00010100|11 |010001 |" + - "01001000011001010110110001101100011011110101011101101111011100100110110001100100|", - expected_regular.as_text(), - "SCHCRegular not parsed (ack_on_error mode)" - ) - # Downlink - expected_regular = SCHCParser.from_bytes(LoRaWAN(), b'\x15\x12\x00') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=1 --|--- N=1 ---|\n" + - "| RuleID | W | FCN |" + - " Fragment Payload | padding |\n" + - "|00010101|0 |0 |" + - "01001000 |000000 |", - expected_regular.as_text(), - "SCHCRegular not parsed (downlink mode)" - ) - - def test_all1(self): - # AckOnError - # With Payload - expected_all1 = SCHCParser.from_bytes(LoRaWAN(), b'\x14\xbf\xac\xde2\x14HelloWorld') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---| U=32 |\n" + - "| RuleID | W | FCN | RCS |" + - " Fragment Payload |\n" + - "|00010100|10 |111111 |10101100110111100011001000010100|" + - "01001000011001010110110001101100011011110101011101101111011100100110110001100100|", - expected_all1.as_text(), - "SCHCAll1 not parsed (ack_on_error mode, with payload)" - ) - # AckOnError - # Without Payload - expected_all1 = SCHCParser.from_bytes(LoRaWAN(), b'\x14\xbf\xac\xde2\x14') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=2 --|--- N=6 ---| U=32 |\n" + - "| RuleID | W | FCN | RCS |\n" + - "|00010100|10 |111111 |10101100110111100011001000010100|", - expected_all1.as_text(), - "SCHCAll1 not parsed (ack_on_error mode, without payload)" - ) - # Downlink - expected_all1 = SCHCParser.from_bytes(LoRaWAN(), b'\x15k7\x8c\x85\x12\x00') - self.assertEqual( - "|--- SCHC Fragment Header ---|\n" + - " |-- M=1 --|--- N=1 ---| U=32 |\n" + - "| RuleID | W | FCN | RCS | Fragment Payload | padding |\n" + - "|00010101|0 |1 |10101100110111100011001000010100|0100100000000000 |000000 |", - expected_all1.as_text(), - "SCHCAll1 not parsed (downlink mode)" - ) - - def test_ack(self): - # AckOnError - # Correct - expected_ack = SCHCParser.from_bytes(LoRaWAN(), b'\x14`') - self.assertEqual( - "|-- SCHC ACK Header --|\n" + - " |-- M=2 --| 1 |\n" + - "| RuleID | W | C | padding |\n" + - "|00010100|01 | 1 |00000 |", - expected_ack.as_text(), - "SCHC Ack not parsed (ack_on_error mode, correct one)" - ) - # Incorrect - expected_ack = SCHCParser.from_bytes(LoRaWAN(), b'\x14W') - self.assertEqual( - "|-- SCHC ACK Header --|\n" + - " |-- M=2 --| 1 |\n" + - "| RuleID | W | C | Compressed Bitmap |\n" + - "|00010100|01 | 0 |10111 |", - expected_ack.as_text(), - "SCHC Ack not parsed (ack_on_error mode, incorrect one)" - ) - # Downlink - # Correct - expected_ack = SCHCParser.from_bytes(LoRaWAN(), b'\x15@') - self.assertEqual( - "|-- SCHC ACK Header --|\n" + - " |-- M=1 --| 1 |\n" + - "| RuleID | W | C | padding |\n" + - "|00010101|0 | 1 |000000 |", - expected_ack.as_text(), - "SCHC Ack not parsed (downlink mode, correct one)" - ) - # Incorrect - expected_ack = SCHCParser.from_bytes(LoRaWAN(), b'\x15 ') - self.assertEqual( - "|-- SCHC ACK Header --|\n" + - " |-- M=1 --| 1 |\n" + - "| RuleID | W | C | Compressed Bitmap | padding |\n" + - "|00010101|0 | 0 |1 |00000 |", - expected_ack.as_text(), - "SCHC Ack not parsed (downlink mode, incorrect one)" - ) - - def test_acq_req(self): - # AckOnError - expected_ack_req = SCHCParser.from_bytes(LoRaWAN(), b'\x14@') - self.assertEqual( - "|- SCHC ACK REQ Header -|\n" + - " |-- M=2 --|--- N=6 ---|\n" + - "| RuleID | W | FCN |\n" + - "|00010100|01 |000000 |", - expected_ack_req.as_text(), - "SCHC ACK Request not parsed (ack_on_error mode)" - ) - - def test_receiver_abort(self): - # AckOnError - expected_rec_abort = SCHCParser.from_bytes(LoRaWAN(), b'\x14\xff\xff') - self.assertEqual( - "| Receiver-Abort Header|\n" + - " |-- M=2 --| 1 |\n" + - "| RuleID | W | C |\n" + - "|00010100|11 | 1 |11111|11111111|", - expected_rec_abort.as_text(), - "SCHC Receiver Abort not parsed (ack_on_error mode)" - ) - # Downlink - expected_rec_abort = SCHCParser.from_bytes(LoRaWAN(), b'\x15\xff\xff') - self.assertEqual( - "| Receiver-Abort Header|\n" + - " |-- M=1 --| 1 |\n" + - "| RuleID | W | C |\n" + - "|00010101|1 | 1 |111111|11111111|", - expected_rec_abort.as_text(), - "SCHC Receiver Abort not parsed (downlink mode)" - ) - - -if __name__ == '__main__': - main()