diff --git a/fragmentation_layer/code/schc_base/bitmap.py b/fragmentation_layer/code/schc_base/bitmap.py index 12717a2..5225fe3 100644 --- a/fragmentation_layer/code/schc_base/bitmap.py +++ b/fragmentation_layer/code/schc_base/bitmap.py @@ -118,7 +118,7 @@ def has_missing(self): break return 0 < sum(self.__bitmap__[i+1:]) - def get_missing(self, fcn=False): + def get_missing(self, fcn=False, after=None): """ Gets first index of reported missing tile. If fcn is True, passes as fcn @@ -127,13 +127,22 @@ 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 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: 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 3bb413c..f7e8f5c 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,7 +1,7 @@ """ ack_on_error_receiver: AckOnError receiver state machine """ from machine import Timer -from schc_base import Bitmap +from schc_base import Bitmap, Tile from schc_machines import SCHCReceiver from schc_messages import RegularSCHCFragment, SCHCAck, All1SCHCFragment, SCHCAckReq, SCHCReceiverAbort @@ -84,6 +84,7 @@ def on_expiration_time(self, alarm) -> None: def receive_regular_schc_fragment(self, schc_message): """ + Behaviour when receiving a Regular SCHC Fragment Parameters ---------- @@ -103,7 +104,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__ @@ -149,12 +152,13 @@ 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) + 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.sm.reassemble() rcs = self.sm.protocol.calculate_rcs( self.sm.payload.as_bits() ) @@ -164,12 +168,21 @@ def receive_all1_schc_fragment(self, schc_message): compressed_bitmap = None self.__success__ = True else: - self._logger_.error("Integrity check failed:\tSender: {}\tReceiver:{}".format( + self._logger_.error("Integrity check failed:\tSender: {}\tReceiver: {}".format( schc_message.header.rcs.rcs, rcs )) - compressed_bitmap = bitmap.generate_compress() - return integrity, compressed_bitmap + 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, @@ -180,7 +193,7 @@ def receive_all1_schc_fragment(self, schc_message): self.sm.message_to_send.append(ack) return else: - self._logger_.degug("(All-1) Different window received") + self._logger_.debug("(All-1) Different window received") return def receive_schc_ack_req(self, schc_message): @@ -213,6 +226,9 @@ def receive_schc_ack_req(self, schc_message): 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=w, compressed_bitmap=bitmap.generate_compress()) @@ -263,29 +279,10 @@ def receive_regular_schc_fragment(self, schc_message): self.sm.state.receive_regular_schc_fragment(schc_message) else: self._logger_.debug("Receiving failed ones") - 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]) - 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: - 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 + 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): @@ -322,14 +319,16 @@ def receive_schc_ack_req(self, schc_message): w = schc_message.header.w.w 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].generate_compress()) - ) - self.sm.attempts.increment() - else: - pass + 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: @@ -348,10 +347,120 @@ def on_expiration_time(self, alarm) -> 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.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) + 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): + """ + 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 + if not self.sm.__last_window__: + 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, 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.reset() self.state.enter_state() @@ -376,12 +485,12 @@ def std_on_expiration_time(state, alarm): """ 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__ - ) + 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 fd9bb9e..26be139 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 @@ -8,7 +8,7 @@ class AckOnErrorSender(SCHCSender): """ AckOnError Sender State Machine with Ack-on-Error Mode - + Attributes ---------- protocol @@ -99,26 +99,8 @@ def generate_message(self, mtu): else: last_tile = self.sm.tiles.pop(0) self.sm.sent_tiles[self.sm.__fcn__] = last_tile.copy() - self.sm.__last_window__ = True - 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(last_tile) + 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() - 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 regular_message.add_padding() self._logger_.schc_message(regular_message) @@ -160,6 +142,7 @@ def generate_message(self, mtu): 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 @@ -211,7 +194,7 @@ def receive_schc_ack(self, schc_message): 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: + 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() @@ -280,6 +263,7 @@ def on_expiration_time(self, alarm): ack_req.add_padding() self.sm.message_to_send.append(ack_req) self.sm.attempts.increment() + self.sm.retransmission_timer.reset() return class ResendingPhase(SCHCSender.SenderState): @@ -319,25 +303,8 @@ def generate_message(self, mtu): # 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]) + all1 = send_all1(self, 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"] @@ -388,3 +355,43 @@ def __init__(self, protocol, payload, padding=0, dtag=None): 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 9fc774d..db0dd63 100644 --- a/fragmentation_layer/code/schc_machines/schc_fsm.py +++ b/fragmentation_layer/code/schc_machines/schc_fsm.py @@ -1,5 +1,6 @@ """ schc_fsm: SCHC Finite State Machine Abstract Class """ +import datetime from machine import Timer from schc_base import AttemptsCounter, Bitmap from schc_messages import SCHCMessage @@ -8,7 +9,7 @@ class SCHCFiniteStateMachine: """ - Finite State Machine of Sender/Receiver (Fragmenter/Ensembler) + Finite State Machine of Sender/Receiver (Fragmenter/Reassembler) behaviours Attributes @@ -61,15 +62,19 @@ class Logger: """ SCHC Logger to log SCHC Fragmentation """ - def __init__(self, state) -> None: + TAG = "{mode}::[{datetime}]::" + + def __init__(self, state): 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__ + def __log__(self, mode): + print( + self.TAG.format(mode=mode, datetime=datetime.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__, @@ -79,7 +84,7 @@ def __log__(self, mode) -> None: second_line += ", DTag: {}".format( self.__state__.sm.__dtag__ ) - mode(second_line) + print(second_line) return def enter_state(self): @@ -90,7 +95,7 @@ def enter_state(self): ------- None """ - self.__log__(print) + self.__log__("DEBUG") return def schc_message(self, message): @@ -123,7 +128,7 @@ def error(self, message): ------- None """ - self.__log__(print) + self.__log__("ERROR") print("\t{}".format(message)) return @@ -140,7 +145,7 @@ def warning(self, message): ------- None """ - self.__log__(print) + self.__log__("WARNING") print("\t{}".format(message)) return @@ -157,7 +162,7 @@ def debug(self, message): ------- None """ - self.__log__(print) + self.__log__("DEBUG") print("\t{}".format(message)) return @@ -174,7 +179,7 @@ def info(self, message): ------- None """ - self.__log__(print) + self.__log__("INFO") print("\t{}".format(message)) return diff --git a/fragmentation_layer/code/schc_machines/schc_receiver.py b/fragmentation_layer/code/schc_machines/schc_receiver.py index 8ab7e87..2ae7e38 100644 --- a/fragmentation_layer/code/schc_machines/schc_receiver.py +++ b/fragmentation_layer/code/schc_machines/schc_receiver.py @@ -138,6 +138,41 @@ def receive_schc_sender_abort(self, schc_message): def __init__(self, protocol, dtag=None): super().__init__(protocol, dtag=dtag) 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_protocols/lorawan.py b/fragmentation_layer/code/schc_protocols/lorawan.py index 301041f..3a37fc2 100644 --- a/fragmentation_layer/code/schc_protocols/lorawan.py +++ b/fragmentation_layer/code/schc_protocols/lorawan.py @@ -70,7 +70,7 @@ def __set_parameters__(self): self.TILE_SIZE = 10 * 8 # 10 bytes = 80 bits self.MAX_ACK_REQUEST = 1e6 # TODO self.INACTIVITY_TIMER = 30 # in seconds TODO - self.RETRANSMISSION_TIMER = 5 # 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/example/common_methods.py b/fragmentation_layer/example/common_methods.py index 542a96b..1004590 100644 --- a/fragmentation_layer/example/common_methods.py +++ b/fragmentation_layer/example/common_methods.py @@ -2,13 +2,12 @@ import random import socket -import logging from schc_machines import SCHCFiniteStateMachine HOST = "127.0.0.1" MTU = 50 -SEED = 8 -PROBABILITY_OF_FAILURE = 0.0 +SEED = 7 +PROBABILITY_OF_FAILURE = 0.2 random.seed(SEED) @@ -102,9 +101,11 @@ def messaging_loop(machine: SCHCFiniteStateMachine, socket_rx: socket.socket, se 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)) - if not lost and message is not None: + 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) diff --git a/fragmentation_layer/example/test_receiver.py b/fragmentation_layer/example/test_receiver.py index 2117258..8dac5db 100644 --- a/fragmentation_layer/example/test_receiver.py +++ b/fragmentation_layer/example/test_receiver.py @@ -8,14 +8,12 @@ SENDER_PORT = 50007 socket_rx = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -socket_rx.settimeout(10) +socket_rx.settimeout(2) 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 diff --git a/fragmentation_layer/example/test_sender.py b/fragmentation_layer/example/test_sender.py index 86a2b5d..c302140 100644 --- a/fragmentation_layer/example/test_sender.py +++ b/fragmentation_layer/example/test_sender.py @@ -63,8 +63,6 @@ if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) - from schc_machines.lorawan import AckOnErrorSender from schc_protocols import LoRaWAN diff --git a/fragmentation_layer/tests/test_base/test_bitmap.py b/fragmentation_layer/tests/test_base/test_bitmap.py index cb53762..7673826 100644 --- a/fragmentation_layer/tests/test_base/test_bitmap.py +++ b/fragmentation_layer/tests/test_base/test_bitmap.py @@ -91,6 +91,21 @@ def test_has_missing(self): bitmap.tile_received(protocol.WINDOW_SIZE - (i + 1)) self.assertFalse(bitmap.has_missing(), "Bitmap full True has missing") + def test_get_missing(self): + protocol = LoRaWAN(LoRaWAN.ACK_ON_ERROR) + bitmap = Bitmap(protocol) + self.assertEqual(0, bitmap.get_missing(), "fcn = False, after = None") + bitmap.tile_received(protocol.WINDOW_SIZE - 5) # 63 - 5 = 58, index = 4 + self.assertEqual(0, bitmap.get_missing(), "fcn = False, after = None (with one tile)") + self.assertEqual(7, bitmap.get_missing(after=6), "fcn = False, after = 6 (with one tile)") + self.assertEqual(56, bitmap.get_missing(fcn=True, after=57), "fcn = True, after = 57 (with one tile)") + for i in range(5): + bitmap.tile_received(protocol.WINDOW_SIZE - (i + 1)) + self.assertEqual(5, bitmap.get_missing(), "fcn = False, after = None (with six tiles)") + self.assertEqual(57, bitmap.get_missing(fcn=True), "fcn = True, after = None (with six tiles)") + self.assertEqual(5, bitmap.get_missing(after=4), "fcn = False, after = 4 (with one tile)") + self.assertEqual(57, bitmap.get_missing(fcn=True, after=62), "fcn = True, after = 55 (with one tile)") + if __name__ == '__main__': main()