Ejemplo n.º 1
0
 def test_to_bytes(self):
     profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
     rule_id = "0" * profile.RULE_ID_SIZE
     dtag = "0" * profile.T
     w = "0" * profile.M
     fcn = "1" * profile.N
     header = FragmentHeader(profile, rule_id, dtag, w, fcn)
     b = header.to_bytes()
     self.assertEqual(b, b"\x07")
Ejemplo n.º 2
0
    def test_to_string(self):
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
        rule_id = "0" * profile.RULE_ID_SIZE
        dtag = "0" * profile.T
        w = "0" * profile.M
        c = "0"
        header = ACKHeader(profile, rule_id, dtag, w, c)
        s = header.to_string()

        self.assertEqual(s, "000000")
Ejemplo n.º 3
0
    def test_is_all_1(self):
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
        rule_id = "0" * profile.RULE_ID_SIZE
        dtag = "0" * profile.T
        w = "0" * profile.M
        fcn = "1" * profile.N
        header = bitstring_to_bytes(rule_id + dtag + w + fcn)
        payload = bytearray.fromhex("3131313231333134313531")
        fragment = Fragment(profile, [header, payload])

        self.assertTrue(fragment.is_all_1())
Ejemplo n.º 4
0
 def test_from_hex(self):
     ack = ACK.parse_from_hex(SigfoxProfile("UPLINK", "ACK ON ERROR", 1),
                              "07ff800000000000")
     self.assertEqual(
         ack.to_string(),
         "0000011111111111100000000000000000000000000000000000000000000000")
     self.assertEqual(ack.HEADER.RULE_ID, "00")
     self.assertEqual(ack.HEADER.DTAG, "0")
     self.assertEqual(ack.HEADER.W, "00")
     self.assertEqual(ack.HEADER.C, "1")
     self.assertTrue(ack.is_receiver_abort())
Ejemplo n.º 5
0
 def test_from_hex(self):
     profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
     h = "0f08000000000000"
     ack = ACK.parse_from_hex(profile, h)
     self.assertEqual(
         ack.to_string(),
         "0000111100001000000000000000000000000000000000000000000000000000")
     self.assertEqual(ack.HEADER.RULE_ID, "00")
     self.assertEqual(ack.HEADER.DTAG, "0")
     self.assertEqual(ack.HEADER.W, "01")
     self.assertEqual(ack.HEADER.C, "1")
     self.assertEqual(ack.BITMAP, "1100001")
     self.assertTrue(is_monochar(ack.PADDING) and ack.PADDING[0] == '0')
Ejemplo n.º 6
0
def reassemble():

    if request.method == "POST":
        # Get request JSON.
        print("[RSMB] POST RECEIVED")
        request_dict = request.get_json()
        print('Received HTTP message: {}'.format(request_dict))

        current_window = int(request_dict["current_window"])
        # last_index = int(request_dict["last_index"])
        header_bytes = int(request_dict["header_bytes"])

        # Initialize SCHC variables.
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", header_bytes)

        print("[RSMB] Loading fragments")

        # Get all the fragments into an array in the format "fragment = [header, payload]"
        fragments = []

        # For each window, load every fragment into the fragments array
        # Note: This assumes all fragments are ordered
        for i in range(current_window + 1):
            for j in range(profile.WINDOW_SIZE):
                if not exists_blob(f"all_windows/window_{i}/fragment_{i}_{j}"):
                    continue
                fragment_file = read_blob(
                    f"all_windows/window_{i}/fragment_{i}_{j}")
                header = fragment_file[:header_bytes].encode()
                payload = fragment_file[header_bytes:].encode()
                fragment = [header, payload]
                fragments.append(fragment)

        print(len(fragments))
        # Instantiate a Reassembler and start reassembling.
        reassembler = Reassembler(profile, fragments)
        payload = bytearray(reassembler.reassemble())
        # Upload the full message.
        upload_blob(payload.decode("utf-8"), "SCHC_PACKET")
        with open(config.RECEIVED, "w") as f:
            f.write(payload.decode())

        if filecmp.cmp(config.PAYLOAD, config.RECEIVED):
            print("The reassembled file is equal to the original message.")
        else:
            print("The reassembled file is corrupt.")

        # start_request(url=config.LOCAL_CLEAN_URL,
        #               body={"header_bytes": header_bytes})

        return '', 204
Ejemplo n.º 7
0
def clean():
    # Wait for an HTTP POST request.
    if request.method == 'POST':

        # Get request JSON.
        print("POST RECEIVED")
        request_dict = request.get_json()
        print(request_dict)
        print('Received Sigfox message: {}'.format(request_dict))

        # Get data and Sigfox Sequence Number.
        header_bytes = int(request_dict["header_bytes"])
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", header_bytes)
        bitmap = '0' * (2**profile.N - 1)
        for i in range(2**profile.M):
            upload_blob(bitmap, "all_windows/window_%d/bitmap_%d" % (i, i))
            upload_blob(bitmap,
                        "all_windows/window_%d/losses_mask_%d" % (i, i))
            # For each fragment in the SCHC Profile, create its blob.

            start_request(url=config.LOCAL_CLEAN_WINDOW_URL,
                          body={
                              "header_bytes":
                              header_bytes,
                              "window_number":
                              i,
                              "clear":
                              request_dict["clear"]
                              if "clear" in request_dict else "False"
                          })

        if exists_blob("SCHC_PACKET"):
            delete_blob("SCHC_PACKET")

        upload_blob("", "SSN")

        print(f"not_delete_dl_losses? {request_dict['not_delete_dl_losses']}")
        if request_dict["not_delete_dl_losses"] == "False":
            for blob in blob_list():
                if blob.startswith("DL_LOSSES_"):
                    delete_blob(blob)
        else:
            current_experiment = 1
            for blob in blob_list():
                if blob.startswith("DL_LOSSES_"):
                    current_experiment += 1
            print(f"Preparing for the {current_experiment}th experiment")
            upload_blob("", f"DL_LOSSES_{current_experiment}")

        return '', 204
Ejemplo n.º 8
0
    def test_init(self):
        hex_data = "053131313231333134313531"
        header = bytes.fromhex(hex_data[:2])
        payload = bytearray.fromhex(hex_data[2:])
        data = [header, payload]
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
        fragment = Fragment(profile, data)
        abort = SenderAbort(profile, fragment.HEADER)

        self.assertEqual(type(abort.PROFILE), SigfoxProfile)
        self.assertEqual(abort.HEADER.RULE_ID, fragment.HEADER.RULE_ID)
        self.assertEqual(abort.HEADER.DTAG, fragment.HEADER.DTAG)
        self.assertEqual(abort.HEADER.W, fragment.HEADER.W)
        self.assertTrue(
            abort.HEADER.FCN[0] == '1' and all(abort.HEADER.FCN),
            msg=f"{abort.HEADER.FCN[0] == '1'} and {all(abort.HEADER.FCN)}")
        self.assertTrue(
            abort.PAYLOAD.decode()[0] == '0' and all(abort.PAYLOAD.decode()),
            msg=f"{abort.PAYLOAD[0] == '0'} and {all(abort.PAYLOAD)}")
        self.assertFalse(abort.is_all_1())
        self.assertTrue(abort.is_sender_abort())

        hex_data = "1f353235"
        header = bytes.fromhex(hex_data[:2])
        payload = bytearray.fromhex(hex_data[2:])
        data = [header, payload]
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
        fragment = Fragment(profile, data)

        self.assertFalse(fragment.is_sender_abort())

        hex_string = "1f3030303030303030303030"
        fragment_sent = Fragment.from_hex(
            SigfoxProfile("UPLINK", "ACK ON ERROR", 1), hex_string)
        abort = SenderAbort(fragment_sent.PROFILE, fragment_sent.HEADER)

        self.assertTrue(abort.is_sender_abort())
Ejemplo n.º 9
0
    def test_init(self):
        hex_data = "053131313231333134313531"
        header = bytes.fromhex(hex_data[:2])
        payload = bytearray.fromhex(hex_data[2:])
        data = [header, payload]
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
        fragment = Fragment(profile, data)
        abort = ReceiverAbort(profile, fragment.HEADER)

        self.assertEqual(type(abort.PROFILE), SigfoxProfile)
        self.assertEqual(abort.HEADER.RULE_ID, fragment.HEADER.RULE_ID)
        self.assertEqual(abort.HEADER.DTAG, fragment.HEADER.DTAG)
        self.assertEqual(abort.HEADER.W, fragment.HEADER.W)
        self.assertEqual(len(abort.to_string()), 64)
        self.assertTrue(issubclass(type(abort), ACK))
        self.assertTrue(abort.is_receiver_abort())
Ejemplo n.º 10
0
    def set_session(self, mode, message):
        self.HEADER_BYTES = 1 if len(message) <= 300 else 2
        self.PROFILE = SigfoxProfile("UPLINK", mode, self.HEADER_BYTES)
        self.MESSAGE = message
        fragmenter = Fragmenter(self.PROFILE, message)
        self.FRAGMENTS = fragmenter.fragment()

        if self.LOGGER is not None:
            self.LOGGER.FRAGMENTATION_TIME = self.LOGGER.CHRONO.read()
            self.LOGGER.debug("fragmentation time -> {}".format(self.LOGGER.FRAGMENTATION_TIME))

        if len(self.FRAGMENTS) > (2 ** self.PROFILE.M) * self.PROFILE.WINDOW_SIZE:
            raise RuleSelectionError

        self.LAST_WINDOW = (len(self.FRAGMENTS) - 1) // self.PROFILE.WINDOW_SIZE
        self.ATTEMPTS = 0
Ejemplo n.º 11
0
    def test_receive(self):
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", 1)
        ack = "0000111111111111100000000000000000000000000000000000000000000000"
        ack_index_dtag = profile.RULE_ID_SIZE
        ack_index_w = ack_index_dtag + profile.T
        ack_index_c = ack_index_w + profile.M
        ack_index_bitmap = ack_index_c + 1
        ack_index_padding = ack_index_bitmap + profile.BITMAP_SIZE

        received_ack = ACK(profile,
                           rule_id=ack[:ack_index_dtag],
                           dtag=ack[ack_index_dtag:ack_index_w],
                           w=ack[ack_index_w:ack_index_c],
                           c=ack[ack_index_c],
                           bitmap=ack[ack_index_bitmap:ack_index_padding],
                           padding=ack[ack_index_padding:])

        self.assertTrue(received_ack.is_receiver_abort())
Ejemplo n.º 12
0
def clean_window():
    request_dict = request.get_json()
    header_bytes = int(request_dict["header_bytes"])
    window_number = int(request_dict["window_number"])
    profile = SigfoxProfile("UPLINK", "ACK ON ERROR", header_bytes)

    print(f"header_bytes: {header_bytes}, window_number: {window_number}")

    for j in range(2**profile.N - 1):
        if request_dict["clear"] == "False":
            upload_blob(
                "",
                f"all_windows/window_{window_number}/fragment_{window_number}_{j}"
            )
        else:
            delete_blob(
                f"all_windows/window_{window_number}/fragment_{window_number}_{j}"
            )

    return '', 204
Ejemplo n.º 13
0
def receiver():
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # File where we will store authentication credentials after acquiring them.
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = config.CLIENT_SECRETS_FILE
    # Wait for an HTTP POST request.
    if request.method == 'POST':

        # Get request JSON.
        print("POST RECEIVED")
        request_dict = request.get_json()
        print('Received Sigfox message: {}'.format(request_dict))

        # Get data and Sigfox Sequence Number.
        fragment = request_dict["data"]
        sigfox_sequence_number = request_dict["seqNumber"]

        header_first_hex = fragment[0]

        if header_first_hex == '0' or header_first_hex == '1':
            header = bytes.fromhex(fragment[:2])
            payload = bytearray.fromhex(fragment[2:])
            header_bytes = 1
        elif header_first_hex == 'f' or header_first_hex == '2':
            header = bytearray.fromhex(fragment[:4])
            payload = bytearray.fromhex(fragment[4:])
            header_bytes = 2
        else:
            print("Wrong header in fragment")
            return 'wrong header', 204

        data = [header, payload]

        # Initialize SCHC variables.
        profile = SigfoxProfile("UPLINK", "ACK ON ERROR", header_bytes)
        buffer_size = profile.UPLINK_MTU
        n = profile.N
        m = profile.M

        # If fragment size is greater than buffer size, ignore it and end function.
        if len(fragment
               ) / 2 * 8 > buffer_size:  # Fragment is hex, 1 hex = 1/2 byte
            return json.dumps(
                {"message":
                 "Fragment size is greater than buffer size D:"}), 200

        # Initialize empty window
        window = []
        for i in range(2**n - 1):
            window.append([b"", b""])

        # Compute the fragment compressed number (FCN) from the Profile
        fcn_dict = {}
        for j in range(2**n - 1):
            fcn_dict[zfill(bin((2**n - 2) - (j % (2**n - 1)))[2:], n)] = j

        # Convert to a Fragment class for easier manipulation.
        fragment_message = Fragment(profile, data)

        # Get current window for this fragment.
        current_window = int(fragment_message.HEADER.W, 2)

        # Get the current bitmap.
        bitmap = read_blob("all_windows/window_%d/bitmap_%d" %
                           (current_window, current_window))

        if fragment_message.is_sender_abort():
            print("Sender-Abort received")
            return 'Sender-Abort received', 204

        try:
            fragment_number = fcn_dict[fragment_message.HEADER.FCN]
            upload_blob(fragment_number, "fragment_number")

            time_received = int(request_dict["time"])
            if exists_blob("timestamp"):
                # Check time validation.
                last_time_received = int(read_blob("timestamp"))

                # If this is not the very first fragment and the inactivity timer has been reached, ignore the message.
                if str(fragment_number) != "0" and str(
                        current_window
                ) != "0" and time_received - last_time_received > profile.INACTIVITY_TIMER_VALUE:
                    print("[RECV] Inactivity timer reached. Ending session.")
                    receiver_abort = ReceiverAbort(profile,
                                                   fragment_message.HEADER)
                    print("Sending Receiver Abort")
                    response_json = send_ack(request_dict, receiver_abort)
                    print(f"Response content -> {response_json}")
                    return response_json, 200

            # Upload current timestamp.
            upload_blob(time_received, "timestamp")

            # Print some data for the user.
            print(
                f"[RECV] This corresponds to the {ordinal(fragment_number)} fragment "
                f"of the {ordinal(current_window)} window.")
            print(f"[RECV] Sigfox sequence number: {sigfox_sequence_number}")

            # Update bitmap and upload it.
            bitmap = replace_bit(bitmap, fragment_number, '1')

        # If the FCN could not been found, it almost certainly is the final fragment.
        except KeyError:
            print("[RECV] This seems to be the final fragment.")
            fragment_number = profile.WINDOW_SIZE - 1
            # Upload current timestamp.
            time_received = int(request_dict["time"])
            upload_blob(time_received, "timestamp")
            print(
                f"is All-1:{fragment_message.is_all_1()}, is All-0:{fragment_message.is_all_0()}"
            )
            # Update bitmap and upload it.
            bitmap = replace_bit(bitmap, len(bitmap) - 1, '1')

        # Upload the fragment data.
        upload_blob(
            bitmap,
            f"all_windows/window_{current_window}/bitmap_{current_window}")
        upload_blob(
            data[0].decode("ISO-8859-1") + data[1].decode("utf-8"),
            f"all_windows/window_{current_window}/fragment_{current_window}_{fragment_number}"
        )

        # Get some SCHC values from the fragment.
        rule_id = fragment_message.HEADER.RULE_ID
        dtag = fragment_message.HEADER.DTAG

        # Get last and current Sigfox sequence number (SSN)
        last_sequence_number = 0
        if exists_blob("SSN"):
            last_sequence_number = read_blob("SSN")
        upload_blob(sigfox_sequence_number, "SSN")

        # # Controlling deterministic losses. This loads the file "loss_mask.txt" which states when should a fragment be
        # # lost, separated by windows.
        # fd = None
        # try:
        #     fd = open(config.LOSS_MASK_MODIFIED, "r")
        # except FileNotFoundError:
        #     fd = open(config.LOSS_MASK, "r")
        # finally:
        #     loss_mask = []
        #     for line in fd:
        #         if not line.startswith("#"):
        #             for char in line:
        #                 try:
        #                     loss_mask.append(int(char))
        #                 except ValueError:
        #                     pass
        #     fd.close()
        #
        # print(f"Loss mask: {loss_mask}")

        # If the fragment is at the end of a window (ALL-0 or ALL-1)
        if fragment_message.is_all_0() or fragment_message.is_all_1():

            # Prepare the ACK bitmap. Find the first bitmap with a 0 in it.
            for i in range(current_window + 1):
                bitmap_ack = read_blob("all_windows/window_%d/bitmap_%d" %
                                       (i, i))
                print(bitmap_ack)
                window_ack = i
                if '0' in bitmap_ack:
                    break

            # If the ACK bitmap has a 0 at the end of a non-final window, a fragment has been lost.
            if fragment_message.is_all_0() and '0' in bitmap_ack:
                print("[ALL0] Sending ACK for lost fragments...")
                print("bitmap with errors -> {}".format(bitmap_ack))
                # Create an ACK message and send it.
                ack = ACK(profile=profile,
                          rule_id=rule_id,
                          dtag=dtag,
                          w=zfill(format(window_ack, 'b'), m),
                          c='0',
                          bitmap=bitmap_ack)
                response_json = send_ack(request_dict, ack)
                print("Response content -> {}".format(response_json))
                return response_json, 200

            # If the ACK bitmap is complete and the fragment is an ALL-0, don't send an ACK
            if fragment_message.is_all_0() and bitmap[0] == '1' and all(
                    bitmap):
                print("[ALL0] All Fragments of current window received")
                print("[ALL0] No need to send an ACK")
                return '', 204

            # If the fragment is an ALL-1
            if fragment_message.is_all_1():

                # The bitmap in the last window follows the following regular expression: "1*0*1*"
                # Since the ALL-1, if received, changes the least significant bit of the bitmap.
                # For a "complete" bitmap in the last window, there shouldn't be non-consecutive zeroes:
                # 1110001 is a valid bitmap, 1101001 is not.

                pattern = re.compile("1*0*1")
                # If the bitmap matches the regex, check if the last two received fragments are consecutive.
                if pattern.fullmatch(bitmap_ack):
                    print("SSN is {} and last SSN is {}".format(
                        sigfox_sequence_number, last_sequence_number))
                    # If the last two received fragments are consecutive, accept the ALL-1 and start reassembling
                    if int(sigfox_sequence_number) - int(
                            last_sequence_number) == 1:
                        last_index = profile.WINDOW_SIZE - 1
                        print(
                            "Info for reassemble: last_index:{}, current_window:{}"
                            .format(last_index, current_window))
                        print('Activating reassembly process...')
                        start_request(url=config.LOCAL_REASSEMBLE_URL,
                                      body={
                                          "current_window": current_window,
                                          "header_bytes": header_bytes
                                      })

                        # Send last ACK to end communication.
                        print("[ALL1] Reassembled: Sending last ACK")
                        bitmap = ''
                        for k in range(profile.BITMAP_SIZE):
                            bitmap += '0'
                        last_ack = ACK(profile=profile,
                                       rule_id=rule_id,
                                       dtag=dtag,
                                       w=zfill(format(window_ack, 'b'), m),
                                       c='1',
                                       bitmap=bitmap)
                        response_json = send_ack(request_dict, last_ack)
                        # return response_json, 200
                        # response_json = send_ack(request_dict, last_ack)
                        print("200, Response content -> {}".format(
                            response_json))
                        return response_json, 200
                    else:
                        # Send NACK at the end of the window.
                        print(
                            "[ALLX] Sending NACK for lost fragments because of SSN..."
                        )
                        ack = ACK(profile=profile,
                                  rule_id=rule_id,
                                  dtag=dtag,
                                  w=zfill(format(window_ack, 'b'), m),
                                  c='0',
                                  bitmap=bitmap_ack)
                        response_json = send_ack(request_dict, ack)
                        return response_json, 200
                    # If they are not, there is a gap between two fragments: a fragment has been lost.
                # The same happens if the bitmap doesn't match the regex.
                else:
                    # Send NACK at the end of the window.
                    print("[ALLX] Sending NACK for lost fragments...")
                    ack = ACK(profile=profile,
                              rule_id=rule_id,
                              dtag=dtag,
                              w=zfill(format(window_ack, 'b'), m),
                              c='0',
                              bitmap=bitmap_ack)
                    response_json = send_ack(request_dict, ack)
                    return response_json, 200
        return '', 204
    else:
        print(
            'Invalid HTTP Method to invoke Cloud Function. Only POST supported'
        )
        return abort(405)