From 53ecdb56daae39273b73164b49f1a243fd3616ff Mon Sep 17 00:00:00 2001 From: Juan Ma Date: Mon, 14 Jun 2021 11:17:24 -0400 Subject: [PATCH 1/3] CRC32 implemented --- .../lorawan/ack_on_error_receiver.py | 2 +- .../code/schc_protocols/schc_protocol.py | 16 ++++++++++++---- fragmentation_layer/example/common_methods.py | 3 +-- fragmentation_layer/example/test_receiver.py | 3 ++- .../tests/test_header/__init__.py | 1 - .../tests/test_header/test_rcs.py | 1 - 6 files changed, 16 insertions(+), 10 deletions(-) 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 858b183..4f11d91 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 @@ -146,7 +146,7 @@ def receive_all1_schc_fragment(self, schc_message): self._logger_.debug("Integrity check successful") compressed_bitmap = None self.__success__ = True - self.on_success(self.sm.payload.as_bytes()) + self.sm.on_success(self.sm.payload.as_bytes()) else: self._logger_.error("Integrity check failed:\tSender: {}\tReceiver:{}".format( schc_message.header.rcs.rcs, diff --git a/fragmentation_layer/code/schc_protocols/schc_protocol.py b/fragmentation_layer/code/schc_protocols/schc_protocol.py index de39080..eb43999 100644 --- a/fragmentation_layer/code/schc_protocols/schc_protocol.py +++ b/fragmentation_layer/code/schc_protocols/schc_protocol.py @@ -109,10 +109,18 @@ def calculate_rcs(self, packet): str : Result of Reassembly Check Sequence (RCS) """ - # TODO: self implementation of crc32 - # from binascii import crc32 - # return hex(crc32(SCHCObject.bits_2_bytes(packet))) - return "0xb4cc4a0" + # 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/example/common_methods.py b/fragmentation_layer/example/common_methods.py index 4c39598..10aea40 100644 --- a/fragmentation_layer/example/common_methods.py +++ b/fragmentation_layer/example/common_methods.py @@ -34,8 +34,7 @@ def is_this_loss() -> bool: bool : True if sent does not occur """ - # return random.random() < PROBABILITY_OF_FAILURE - return False + return random.random() < PROBABILITY_OF_FAILURE def send_socket(msg: bytes, port: int) -> None: diff --git a/fragmentation_layer/example/test_receiver.py b/fragmentation_layer/example/test_receiver.py index 2233bc5..2117258 100644 --- a/fragmentation_layer/example/test_receiver.py +++ b/fragmentation_layer/example/test_receiver.py @@ -20,7 +20,8 @@ from schc_protocols import LoRaWAN receiver = AckOnErrorReceiver( - LoRaWAN(LoRaWAN.ACK_ON_ERROR) + LoRaWAN(LoRaWAN.ACK_ON_ERROR), + on_success=print ) messaging_loop(receiver, socket_rx, SENDER_PORT) diff --git a/fragmentation_layer/tests/test_header/__init__.py b/fragmentation_layer/tests/test_header/__init__.py index 2564c98..66966c2 100644 --- a/fragmentation_layer/tests/test_header/__init__.py +++ b/fragmentation_layer/tests/test_header/__init__.py @@ -4,4 +4,3 @@ 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_rcs.py b/fragmentation_layer/tests/test_header/test_rcs.py index c91e820..1b7c898 100644 --- a/fragmentation_layer/tests/test_header/test_rcs.py +++ b/fragmentation_layer/tests/test_header/test_rcs.py @@ -14,7 +14,6 @@ def setUp(self) -> None: ------- None """ - # TODO: Pending unit testing of headers # self.rcs = ReassemblyCheckSequence() def test_wrong_fcn_size(self) -> None: From 39ada84fa77f6ca664f4632f28e05432b8bd6f9b Mon Sep 17 00:00:00 2001 From: Juan Ma Date: Mon, 14 Jun 2021 11:51:32 -0400 Subject: [PATCH 2/3] CRC32 implemented and tested --- .../tests/test_protocols/__init__.py | 3 + .../test_protocols/test_schc_protocol.py | 88 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 fragmentation_layer/tests/test_protocols/__init__.py create mode 100644 fragmentation_layer/tests/test_protocols/test_schc_protocol.py diff --git a/fragmentation_layer/tests/test_protocols/__init__.py b/fragmentation_layer/tests/test_protocols/__init__.py new file mode 100644 index 0000000..53e893b --- /dev/null +++ b/fragmentation_layer/tests/test_protocols/__init__.py @@ -0,0 +1,3 @@ +""" test_protocol: Unit test for schc_protocols package """ + +from test_protocols.test_schc_protocol import TestSCHCProtocol diff --git a/fragmentation_layer/tests/test_protocols/test_schc_protocol.py b/fragmentation_layer/tests/test_protocols/test_schc_protocol.py new file mode 100644 index 0000000..0bcf752 --- /dev/null +++ b/fragmentation_layer/tests/test_protocols/test_schc_protocol.py @@ -0,0 +1,88 @@ +""" test_schc_protocol: SCHC Protocol unit test class """ + +from binascii import crc32 +from random import seed, choices, choice +from unittest import TestCase, main +from schc_base import SCHCObject +from schc_protocols import LoRaWAN + +SEED = 7 +SHORT_MESSAGE = "This is a short message".encode("ascii") +LONG_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") +WORD_LENGTHS = [10, 200, int(1e3)] + + +class TestSCHCProtocol(TestCase): + + def test_crc32_static_message(self) -> None: + short = SCHCObject.bytes_2_bits(SHORT_MESSAGE) + library = hex(crc32(SCHCObject.bits_2_bytes(short))) + local = LoRaWAN().calculate_rcs(short) + self.assertEqual(library, local, "Short message crc32 do not match") + long = SCHCObject.bytes_2_bits(LONG_MESSAGE) + library = hex(crc32(SCHCObject.bits_2_bytes(long))) + local = LoRaWAN().calculate_rcs(long) + self.assertEqual(library, local, "Long message crc32 do not match") + + def test_crc32_random(self) -> None: + seed(SEED) + import string + for _ in range(100): + a_word = "".join( + choices(string.ascii_letters, k=choice(WORD_LENGTHS)) + ).encode("ascii") + word = SCHCObject.bytes_2_bits(a_word) + library = hex(crc32(SCHCObject.bits_2_bytes(word))) + local = LoRaWAN().calculate_rcs(word) + self.assertEqual( + library, local, + "Random message {} crc32 do not match".format(a_word) + ) + + +if __name__ == '__main__': + main() From 94dff7c95ea7d4fd412ab74e6b93feea63ba8bb3 Mon Sep 17 00:00:00 2001 From: Juan Ma Date: Tue, 15 Jun 2021 12:46:25 -0400 Subject: [PATCH 3/3] Add failed fragments handling protocol (not 100% functional) --- fragmentation_layer/code/schc_base/bitmap.py | 30 ++- fragmentation_layer/code/schc_base/tile.py | 23 ++ .../lorawan/ack_on_error_receiver.py | 206 ++++++++++----- .../lorawan/ack_on_error_sender.py | 244 +++++++++++++++--- .../code/schc_machines/schc_fsm.py | 41 ++- .../code/schc_protocols/lorawan.py | 4 +- fragmentation_layer/example/common_methods.py | 3 +- .../tests/test_base/test_bitmap.py | 19 ++ 8 files changed, 437 insertions(+), 133 deletions(-) diff --git a/fragmentation_layer/code/schc_base/bitmap.py b/fragmentation_layer/code/schc_base/bitmap.py index 4cf01d7..12717a2 100644 --- a/fragmentation_layer/code/schc_base/bitmap.py +++ b/fragmentation_layer/code/schc_base/bitmap.py @@ -98,11 +98,26 @@ def is_missing(self): Returns ------- - bool : + bool True if there are missing tiles """ return len(self.__bitmap__) > sum(self.__bitmap__) + 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): """ Gets first index of reported missing tile. If fcn is True, passes @@ -115,7 +130,7 @@ def get_missing(self, fcn=False): Returns ------- - int : + int First index with missing tile """ i = self.__bitmap__.index(False) @@ -124,6 +139,17 @@ def get_missing(self, fcn=False): 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_machines/lorawan/ack_on_error_receiver.py b/fragmentation_layer/code/schc_machines/lorawan/ack_on_error_receiver.py index 4f11d91..ca588be 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 @@ -3,7 +3,7 @@ from machine import Timer from schc_base import Bitmap 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): @@ -32,33 +32,13 @@ 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 : @@ -67,12 +47,31 @@ def generate_message(self, mtu): 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 + if len(self.sm.message_to_send) > 0: + message = self.sm.message_to_send.pop(0) + self._logger_.schc_message(message) + return message + else: + return None 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): """ @@ -85,6 +84,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 @@ -109,8 +109,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[ @@ -138,23 +140,27 @@ def receive_all1_schc_fragment(self, schc_message): 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.on_success(self.sm.payload.as_bytes()) + 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() + 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.on_success(self.sm.payload.as_bytes()) + else: + self._logger_.error("Integrity check failed:\tSender: {}\tReceiver:{}".format( + schc_message.header.rcs.rcs, + rcs + )) + compressed_bitmap = bitmap.generate_compress() + return integrity, compressed_bitmap ack = SCHCAck(self.sm.__rule_id__, self.sm.protocol.id, c=integrity, @@ -165,7 +171,7 @@ def receive_all1_schc_fragment(self, schc_message): self.sm.message_to_send.append(ack) return else: - # TODO + self._logger_.degug("(All-1) Different window received") return def receive_schc_ack_req(self, schc_message): @@ -181,19 +187,26 @@ 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.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 @@ -216,19 +229,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: - return None + return super().generate_message(mtu) def receive_regular_schc_fragment(self, schc_message): """ @@ -265,7 +266,35 @@ def receive_regular_schc_fragment(self, schc_message): if self.sm.bitmaps[self.sm.__cw__].is_missing(): fcn = self.sm.bitmaps[self.sm.__cw__].get_missing(fcn=True) else: - break + 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) + return + 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): @@ -289,16 +318,61 @@ def receive_schc_ack_req(self, schc_message): SCHCAck(self.sm.__rule_id__, self.sm.protocol.id, c=False, w=w, compressed_bitmap=self.sm.bitmaps[w].generate_compress()) ) + self.sm.attempts.increment() else: pass 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 + 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.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() + state.sm.message_to_send.append( + SCHCReceiverAbort( + rule_id=state.sm.__rule_id__, + protocol=state.sm.protocol.id, + dtag=state.sm.__dtag__, + w=state.sm.__cw__ + ) + ) + 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 cd89a06..daf1d3b 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,7 +2,7 @@ from schc_base import Tile, Bitmap from schc_machines import SCHCSender -from schc_messages import RegularSCHCFragment, All1SCHCFragment, SCHCAck, SCHCAckReq +from schc_messages import RegularSCHCFragment, All1SCHCFragment, SCHCAck, SCHCAckReq, SCHCSenderAbort class AckOnErrorSender(SCHCSender): @@ -13,7 +13,6 @@ class AckOnErrorSender(SCHCSender): ---------- protocol state - residue """ __mode__ = "Ack On Error" @@ -65,11 +64,13 @@ 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 @@ -77,9 +78,7 @@ def generate_message(self, mtu): 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( @@ -90,22 +89,22 @@ def generate_message(self, mtu): self.sm.retransmission_timer.reset() self.sm.state.enter_state() self.sm.message_to_send.append(SCHCAckReq( - self.sm.__rule_id__, - self.sm.protocol.id, - self.sm.__dtag__, - self.sm.__cw__ + 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.sent_tiles[self.sm.__fcn__] = last_tile.copy() self.sm.__last_window__ = True all1 = All1SCHCFragment( - self.sm.__rule_id__, - self.sm.protocol.id, - self.sm.__dtag__, - self.sm.__cw__, - self.sm.rcs + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__, + rcs=self.sm.rcs ) all1.add_tile(last_tile) self._logger_.schc_message(all1) @@ -170,46 +169,217 @@ 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() + 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() < self.sm.sent_tiles: + self.sm.retransmission_timer.stop() + self.sm.state = self.sm.states["resending_phase"] + self.sm.state.enter_state() + 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() + 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() + 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() + return + + 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 = All1SCHCFragment( + rule_id=self.sm.__rule_id__, + protocol=self.sm.protocol.id, + dtag=self.sm.__dtag__, + w=self.sm.__cw__, + rcs=self.sm.rcs + ) + all1.add_tile(self.sm.sent_tiles[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() + self.sm.message_to_send.append(SCHCAckReq( + self.sm.__rule_id__, + self.sm.protocol.id, + self.sm.__dtag__, + self.sm.__cw__ + )) + all1.add_padding() + 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() self.tiles = list() - self.sent_tiles = list() + self.sent_tiles = dict() self.state.__generate_tiles__() return diff --git a/fragmentation_layer/code/schc_machines/schc_fsm.py b/fragmentation_layer/code/schc_machines/schc_fsm.py index 1807780..45d875b 100644 --- a/fragmentation_layer/code/schc_machines/schc_fsm.py +++ b/fragmentation_layer/code/schc_machines/schc_fsm.py @@ -196,7 +196,7 @@ def enter_state(self): def generate_message(self, mtu): """ - Generates a SCHC message to send + Message to send by default: Sends enqueue messages Parameters ---------- @@ -210,10 +210,22 @@ def generate_message(self, mtu): Raises ------ - GeneratorExit - No more SCHC Message to send on current state + SystemExit : + Raises a SystemExit with error code -1 """ - raise GeneratorExit("No more 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: + return None def receive_message(self, message): """ @@ -324,27 +336,6 @@ def receive_message(self, message): """ 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 diff --git a/fragmentation_layer/code/schc_protocols/lorawan.py b/fragmentation_layer/code/schc_protocols/lorawan.py index 5a1c2d4..301041f 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 = 5 # 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/example/common_methods.py b/fragmentation_layer/example/common_methods.py index 10aea40..542a96b 100644 --- a/fragmentation_layer/example/common_methods.py +++ b/fragmentation_layer/example/common_methods.py @@ -8,7 +8,7 @@ HOST = "127.0.0.1" MTU = 50 SEED = 8 -PROBABILITY_OF_FAILURE = 0.05 +PROBABILITY_OF_FAILURE = 0.0 random.seed(SEED) @@ -100,6 +100,7 @@ def messaging_loop(machine: SCHCFiniteStateMachine, socket_rx: socket.socket, se lost = is_this_loss() try: print("Sending...") + print("Messages enqueued: {}".format(machine.message_to_send)) message = machine.generate_message(mtu) logging.info("Current mtu: {}".format(mtu)) logging.info("Package sent: {}".format(not lost)) diff --git a/fragmentation_layer/tests/test_base/test_bitmap.py b/fragmentation_layer/tests/test_base/test_bitmap.py index ac8305c..cb53762 100644 --- a/fragmentation_layer/tests/test_base/test_bitmap.py +++ b/fragmentation_layer/tests/test_base/test_bitmap.py @@ -72,6 +72,25 @@ def test_compression(self): ]) % protocol_to_use.L2_WORD) + protocol_to_use.L2_WORD - 1, sum(compressed_bitmap), "Wrong compression") + def test_has_missing(self): + protocol = LoRaWAN(LoRaWAN.ACK_ON_ERROR) + bitmap = Bitmap(protocol) + self.assertFalse(bitmap.has_missing(), "Bitmap full False has missing") + bitmap.tile_received(protocol.WINDOW_SIZE - 1) + self.assertFalse(bitmap.has_missing(), "Bitmap first one has missing") + for i in range(5): + bitmap.tile_received(protocol.WINDOW_SIZE - (i + 2)) + self.assertFalse(bitmap.has_missing(), "Bitmap first ones has missing") + for i in range(5): + bitmap.tile_received(protocol.WINDOW_SIZE - (i + 10)) + self.assertTrue(bitmap.has_missing(), "Bitmap has missing not reported") + for i in range(5): + bitmap.tile_received(protocol.WINDOW_SIZE - (i + 20)) + self.assertTrue(bitmap.has_missing(), "Bitmap has missing not reported (second case)") + for i in range(protocol.WINDOW_SIZE): + bitmap.tile_received(protocol.WINDOW_SIZE - (i + 1)) + self.assertFalse(bitmap.has_missing(), "Bitmap full True has missing") + if __name__ == '__main__': main()