예제 #1
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())
예제 #2
0
def hello_get(request):
    """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>.
    """

    # 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"]

        # Initialize Cloud Storage variables.
        BUCKET_NAME = config.BUCKET_NAME

        # Parse fragment into "fragment = [header, payload]
        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

        if bytearray.fromhex(fragment[2:]).decode() == "CLEAN":
            try:
                _ = requests.post(url=config.CLEAN_URL,
                                  json={
                                      "header_bytes": header_bytes,
                                      "not_delete_dl_losses": "True"
                                  },
                                  timeout=0.1)
            except requests.exceptions.ReadTimeout:
                pass
            return '', 204
        elif bytearray.fromhex(fragment[2:]).decode() == "CLEAN_ALL":
            try:
                _ = requests.post(url=config.CLEAN_URL,
                                  json={
                                      "header_bytes": header_bytes,
                                      "not_delete_dl_losses": "False"
                                  },
                                  timeout=0.1)
            except requests.exceptions.ReadTimeout:
                pass
            return '', 204

        data = [header, payload]

        # Initialize SCHC variables.
        profile = Sigfox("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

        # If the folder named "all windows" does not exist, create it along with all subdirectories.
        if not exists_blob("all_windows/"):
            print("INITIALIZING... (be patient)")
            create_folder("all_windows/")

            # For each window in the SCHC Profile, create its blob.
            for i in range(2**m):
                create_folder("all_windows/window_%d/" % i)

                # For each fragment in the SCHC Profile, create its blob.
                for j in range(2**n - 1):
                    upload_blob(
                        "", "all_windows/window_%d/fragment_%d_%d" % (i, i, j))

                # Create the blob for each bitmap.
                if not exists_blob(
                        "all_windows/window_%d/bitmap_%d" %
                    (i, i) or size_blob("all_windows/window_%d/bitmap_%d" %
                                        (i, i)) == 0):
                    bitmap = ""
                    for b in range(profile.BITMAP_SIZE):
                        bitmap += "0"
                    upload_blob(bitmap,
                                "all_windows/window_%d/bitmap_%d" % (i, i))

        print("BLOBs created")

        # Find current experiment number
        current_experiment = 0
        for blob in blob_list():
            if blob.startswith("DL_LOSSES_"):
                current_experiment += 1
        print(f"This is the {current_experiment}th experiment.")

        # 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)

        if 'enable_losses' in request_dict and not (
                fragment_message.is_all_0() or fragment_message.is_all_1()):
            if request_dict['enable_losses']:
                loss_rate = request_dict["loss_rate"]
                # loss_rate = 10
                coin = random.random()
                print(f'loss rate: {loss_rate}, random toss:{coin * 100}')
                if coin * 100 < loss_rate:
                    print("[LOSS] The fragment was lost.")
                    if fragment_message.is_all_1():
                        last_sequence_number = read_blob("SSN")
                        print("SSN is {} and last SSN is {}".format(
                            sigfox_sequence_number, last_sequence_number))
                        if int(sigfox_sequence_number) - int(
                                last_sequence_number) == 1:
                            # We do that to save the last SSN value for future use (when the next All-1 Arrives)
                            # In a Real Loss Scenario we will not know the SSN...
                            upload_blob(sigfox_sequence_number, "SSN")
                    return 'fragment lost', 204

        # 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 getting the fragment number from the FCN dictionary.
        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("[RECV] This corresponds to the " + str(fragment_number) +
                  "th fragment of the " + str(current_window) + "th window.")
            print("[RECV] Sigfox sequence number: " +
                  str(sigfox_sequence_number))

            # Controlled Errors check
            # losses_mask = read_blob(BUCKET_NAME, "all_windows/window_%d/losses_mask_%d" % (current_window, current_window))
            # if (losses_mask[fragment_number]) != '0':
            #     losses_mask = replace_bit(losses_mask, fragment_number, str(int(losses_mask[fragment_number])-1))
            #     upload_blob(BUCKET_NAME, losses_mask, "all_windows/window_%d/losses_mask_%d" % (current_window, current_window))
            #     print("[LOSS] The fragment was lost.")
            #     return 'fragment lost', 204

            # Update bitmap and upload it.
            bitmap = replace_bit(bitmap, fragment_number, '1')
            upload_blob(
                bitmap, "all_windows/window_%d/bitmap_%d" %
                (current_window, current_window))

            # Upload the fragment data.
            upload_blob(
                data[0].decode("ISO-8859-1") + data[1].decode("utf-8"),
                "all_windows/window_%d/fragment_%d_%d" %
                (current_window, current_window, fragment_number))

        # 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.")
            # Upload current timestamp.
            time_received = int(request_dict["time"])
            upload_blob(time_received, "timestamp")
            print("is All-1:{}, is All-0:{}".format(
                fragment_message.is_all_1(), fragment_message.is_all_0()))
            # print("RULE_ID: {}, W:{}, FCN:{}".format(fragment.header.RULE_ID, fragment.header.W, fragment.header.FCN))
            # Update bitmap and upload it.
            bitmap = replace_bit(bitmap, len(bitmap) - 1, '1')
            upload_blob(
                bitmap, "all_windows/window_%d/bitmap_%d" %
                (current_window, current_window))

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

        # 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")

        # 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:
                if 'enable_dl_losses' in request_dict:
                    if request_dict['enable_dl_losses'] == "True":
                        loss_rate = request_dict["loss_rate"]
                        coin = random.random()
                        print('loss rate: {}, random toss:{}'.format(
                            loss_rate, coin * 100))
                        if coin * 100 < loss_rate:
                            print("[LOSS-ALL0] The Downlink NACK was lost.")

                            upload_blob(
                                read_blob(f"DL_LOSSES_{current_experiment}") +
                                "\n Lost DL message in window {}".format(
                                    current_window),
                                f"DL_LOSSES_{current_experiment}")
                            return 'Downlink lost', 204
                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")
                # print("[ALLX] Sending ACK after window...")

                # Create an ACK message and send it.
                # ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '0')
                # response_json = send_ack(request_dict, ack)
                # print("200, Response content -> {}".format(response_json))
                # Response to continue, no ACK is sent Back.
                return '', 204
                # return response_json, 200

            # If the fragment is an ALL-1
            if fragment_message.is_all_1():
                # response = {request_dict['device']: {'downlinkData': '080fffffffffffff'}}
                # print("response -> {}".format(json.dumps(response)))
                # return json.dumps(response), 200
                # 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.

                pattern2 = re.compile("0*1")
                if pattern2.fullmatch(bitmap_ack):
                    if 'enable_dl_losses' in request_dict:
                        if request_dict['enable_dl_losses'] == "True":
                            loss_rate = request_dict["loss_rate"]
                            coin = random.random()
                            print('loss rate: {}, random toss:{}'.format(
                                loss_rate, coin * 100))
                            if coin * 100 < loss_rate:
                                print("[LOSS-ALL1] The Downlink ACK was lost.")
                                upload_blob(
                                    read_blob(
                                        f"DL_LOSSES_{current_experiment}") +
                                    "\n Lost DL message in window {}".format(
                                        current_window),
                                    f"DL_LOSSES_{current_experiment}")
                                return 'Downlink lost', 204
                    print("SSN is {} and last SSN is {}".format(
                        sigfox_sequence_number, last_sequence_number))
                    # Downlink Controlled Errors
                    dl_errors = int(read_blob("dl_errors"))
                    if dl_errors == 0:
                        last_index = 0
                        upload_blob(
                            data[0].decode("ISO-8859-1") +
                            data[1].decode("utf-8"),
                            "all_windows/window_%d/fragment_%d_%d" %
                            (current_window, current_window, last_index))
                        print(
                            "Info for reassemble: last_index:{}, current_window:{}"
                            .format(last_index, current_window))
                        try:
                            print('Activating reassembly process...')
                            _ = requests.post(url=config.REASSEMBLE_URL,
                                              json={
                                                  "last_index": last_index,
                                                  "current_window":
                                                  current_window,
                                                  "header_bytes": header_bytes
                                              },
                                              timeout=0.1)
                        except requests.exceptions.ReadTimeout:
                            pass

                        # 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:
                        dl_errors -= 1
                        upload_blob(dl_errors, "dl_errors")
                        print(
                            "[DL-ERROR] We simulate a downlink error. We don't send an ACK"
                        )
                        return '', 204

                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 'enable_dl_losses' in request_dict:
                        if request_dict['enable_dl_losses'] == "True":
                            loss_rate = request_dict["loss_rate"]
                            coin = random.random()
                            print('loss rate: {}, random toss:{}'.format(
                                loss_rate, coin * 100))
                            if coin * 100 < loss_rate:
                                print("[LOSS-ALL1] The Downlink ACK was lost.")
                                upload_blob(
                                    read_blob(
                                        f"DL_LOSSES_{current_experiment}") +
                                    "\n Lost DL message in window {}".format(
                                        current_window),
                                    f"DL_LOSSES_{current_experiment}")
                                return 'Downlink lost', 204
                    if int(sigfox_sequence_number) - int(
                            last_sequence_number) == 1:
                        # Downlink Controlled Errors
                        dl_errors = int(read_blob("dl_errors"))
                        if dl_errors == 0:
                            last_index = int(read_blob("fragment_number")) + 1
                            upload_blob(
                                data[0].decode("ISO-8859-1") +
                                data[1].decode("utf-8"),
                                "all_windows/window_%d/fragment_%d_%d" %
                                (current_window, current_window, last_index))
                            print(
                                "Info for reassemble: last_index:{}, current_window:{}"
                                .format(last_index, current_window))
                            try:
                                print('Activating reassembly process...')
                                _ = requests.post(url=config.REASSEMBLE_URL,
                                                  json={
                                                      "last_index": last_index,
                                                      "current_window":
                                                      current_window,
                                                      "header_bytes":
                                                      header_bytes
                                                  },
                                                  timeout=0.1)
                            except requests.exceptions.ReadTimeout:
                                pass

                            # 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:
                            dl_errors -= 1
                            upload_blob(dl_errors, "dl_errors")
                            print(
                                "[DL-ERROR] We simulate a downlink error. We don't send an ACK"
                            )
                            return '', 204
                    else:
                        # Send NACK at the end of the window.
                        if 'enable_dl_losses' in request_dict:
                            if request_dict['enable_dl_losses'] == "True":
                                loss_rate = request_dict["loss_rate"]
                                coin = random.random()
                                print('loss rate: {}, random toss:{}'.format(
                                    loss_rate, coin * 100))
                                if coin * 100 < loss_rate:
                                    print(
                                        "[LOSS-ALL1] The Downlink NACK was lost."
                                    )
                                    upload_blob(
                                        read_blob(
                                            f"DL_LOSSES_{current_experiment}")
                                        + "\n Lost DL message in window {}".
                                        format(current_window),
                                        f"DL_LOSSES_{current_experiment}")
                                    return 'Downlink lost', 204
                        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.
                    if 'enable_dl_losses' in request_dict:
                        if request_dict['enable_dl_losses'] == "True":
                            loss_rate = request_dict["loss_rate"]
                            coin = random.random()
                            print('loss rate: {}, random toss:{}'.format(
                                loss_rate, coin * 100))
                            if coin * 100 < loss_rate:
                                print(
                                    "[LOSS-ALL1] The Downlink NACK was lost.")
                                upload_blob(
                                    read_blob(
                                        f"DL_LOSSES_{current_experiment}") +
                                    "\n Lost DL message in window {}".format(
                                        current_window),
                                    f"DL_LOSSES_{current_experiment}")
                                return 'Downlink lost', 204
                    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)
예제 #3
0
def schc_post():
    """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>.
    """

    REASSEMBLER_URL = "http://localhost:5000/reassembler"
    CLEANUP_URL = "http://localhost:5000/cleanup"

    # 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.
        raw_data = request_dict["data"]
        sigfox_sequence_number = request_dict["seqNumber"]
        ack_req = request_dict["ack"]

        # Initialize Cloud Storage variables.
        BUCKET_NAME = config.BUCKET_NAME

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

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

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

        # If the folder named "all windows" does not exist, create it along with all subdirectories.
        initialize_blobs(BUCKET_NAME, profile)

        # 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:], 3)] = j

        # Parse raw_data into "data = [header, payload]
        # Convert to a Fragment class for easier manipulation.
        header = bytes.fromhex(raw_data[:2])
        payload = bytearray.fromhex(raw_data[2:])
        data = [header, payload]
        fragment_message = Fragment(profile, data)

        if fragment_message.is_sender_abort():
            print("Sender-Abort received")
            try:
                print("Cleaning")
                _ = requests.post(url=CLEANUP_URL,
                                  json={"header_bytes": header_bytes},
                                  timeout=0.1)
            except requests.exceptions.ReadTimeout:
                pass
            return 'Sender-Abort received', 204

        # Get data from this fragment.
        fcn = fragment_message.header.FCN
        rule_id = fragment_message.header.RULE_ID
        dtag = fragment_message.header.DTAG
        current_window = int(fragment_message.header.W, 2)

        # Get the current bitmap.
        bitmap = read_blob(
            BUCKET_NAME,
            f"all_windows/window_{current_window}/bitmap_{current_window}")

        # 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}")

        # Controlling random losses.
        if 'enable_losses' in request_dict and not (
                fragment_message.is_all_0() or fragment_message.is_all_1()):
            if request_dict['enable_losses']:
                loss_rate = request_dict["loss_rate"]
                # loss_rate = 10
                coin = random.random()
                print(f'loss rate: {loss_rate}, random toss:{coin * 100}')
                if coin * 100 < loss_rate:
                    print("[LOSS] The fragment was lost.")
                    return 'fragment lost', 204

        # Check if the fragment is an All-1
        if is_monochar(fcn) and fcn[0] == '1':
            print("[RECV] This is an All-1.")

            # Check if fragment is to be lost (All-1 is the very last fragment)
            if loss_mask[-1] != 0:
                loss_mask[-1] -= 1
                with open("loss_mask_modified.txt", "w") as fd:
                    for i in loss_mask:
                        fd.write(str(i))
                print(f"[RECV] Fragment lost.")
                return 'fragment lost', 204

            # Inactivity timer validation
            time_received = int(request_dict["time"])

            if exists_blob(BUCKET_NAME, "timestamp"):
                # Check time validation.
                last_time_received = int(read_blob(BUCKET_NAME, "timestamp"))
                print(f"[RECV] Previous timestamp: {last_time_received}")
                print(f"[RECV] This timestamp: {time_received}")

                # If the inactivity timer has been reached, abort communication.
                if 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}")
                    try:
                        print("Cleaning Inactivity timer reached")
                        _ = requests.post(url=CLEANUP_URL,
                                          json={"header_bytes": header_bytes},
                                          timeout=0.1)
                    except requests.exceptions.ReadTimeout:
                        pass
                    return response_json, 200

            # Update timestamp
            upload_blob(BUCKET_NAME, time_received, "timestamp")

            # Update bitmap and upload it.
            bitmap = replace_bit(bitmap, len(bitmap) - 1, '1')
            print(f"Bitmap is now {bitmap}")
            upload_blob(
                BUCKET_NAME, bitmap,
                f"all_windows/window_{current_window}/bitmap_{current_window}")
            # Upload the fragment data.
            upload_blob(
                BUCKET_NAME, data[0].decode("utf-8") + data[1].decode("utf-8"),
                f"all_windows/window_{current_window}/fragment_{current_window}_{profile.WINDOW_SIZE - 1}"
            )

        # Else, it is a normal fragment.
        else:
            fragment_number = fcn_dict[fragment_message.header.FCN]

            # Check if fragment is to be lost
            position = current_window * profile.WINDOW_SIZE + fragment_number
            if loss_mask[position] != 0:
                loss_mask[position] -= 1
                with open(config.LOSS_MASK_MODIFIED, "w") as fd:
                    for i in loss_mask:
                        fd.write(str(i))
                print(f"[RECV] Fragment lost.")
                return 'fragment lost', 204

            # Inactivity timer validation
            time_received = int(request_dict["time"])

            if exists_blob(BUCKET_NAME, "timestamp"):
                # Check time validation.
                last_time_received = int(read_blob(BUCKET_NAME, "timestamp"))
                print(f"[RECV] Previous timestamp: {last_time_received}")
                print(f"[RECV] This timestamp: {time_received}")

                # If the inactivity timer has been reached, abort communication.
                if 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}")
                    try:
                        _ = requests.post(url=CLEANUP_URL,
                                          json={"header_bytes": header_bytes},
                                          timeout=0.1)
                    except requests.exceptions.ReadTimeout:
                        pass
                    return response_json, 200

            # Update timestamp
            upload_blob(BUCKET_NAME, time_received, "timestamp")

            # Update Sigfox sequence number JSON
            sequence_numbers = json.loads(read_blob(BUCKET_NAME, "SSN"))
            sequence_numbers[position] = request_dict["seqNumber"]
            print(sequence_numbers)
            upload_blob(BUCKET_NAME, json.dumps(sequence_numbers), "SSN")

            upload_blob(BUCKET_NAME, fragment_number, "fragment_number")

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

            # Update bitmap and upload it.
            bitmap = replace_bit(bitmap, fragment_number, '1')
            print(f"Bitmap is now {bitmap}")
            upload_blob(
                BUCKET_NAME, bitmap,
                f"all_windows/window_{current_window}/bitmap_{current_window}")

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

        # If the fragment requests an ACK...
        if ack_req:

            # Prepare the ACK bitmap. Find the first bitmap with a 0 in it.
            # This bitmap corresponds to the lowest-numered window with losses.
            bitmap_ack = None
            window_ack = None
            for i in range(current_window + 1):
                bitmap_ack = read_blob(BUCKET_NAME,
                                       f"all_windows/window_{i}/bitmap_{i}")
                print(bitmap_ack)
                window_ack = i
                if '0' in bitmap_ack:
                    break

            # The final window is only accessible through All-1.
            # If All-0, check non-final windows
            if fragment_message.is_all_0():
                # If the ACK bitmap has a 0 at a non-final window, a fragment has been lost.
                if '0' in bitmap_ack:
                    print(
                        "[ALL0] Lost fragments have been detected. Preparing ACK."
                    )
                    print(f"[ALL0] Bitmap with errors -> {bitmap_ack}")
                    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(f"Response content -> {response_json}")
                    print("[ALL0] ACK sent.")
                    return response_json, 200
                # If all bitmaps are complete up to this point, no losses are detected.
                else:
                    print("[ALL0] No losses have been detected.")
                    print("Response content -> ''")
                    return '', 204

            # If the fragment is All-1, the last window should be considered.
            if fragment_message.is_all_1():

                # First check for 0s in the bitmap. If the bitmap is of a non-final window, send corresponding ACK.
                if current_window != window_ack and '0' in bitmap_ack:
                    print(
                        "[ALL1] Lost fragments have been detected. Preparing ACK."
                    )
                    print(f"[ALL1] Bitmap with errors -> {bitmap_ack}")
                    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(f"Response content -> {response_json}")
                    print("[ALL1] ACK sent.")
                    return response_json, 200

                # If the bitmap is of the final window, check the following regex.
                else:
                    # 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.
                    # The bitmap may or may not contain the 0s.
                    pattern = re.compile("1*0*1")

                    # If the bitmap matches the regex, check if there are still lost fragments.
                    if pattern.fullmatch(bitmap_ack) is not None:
                        # The idea is the following:
                        # Assume a fragment has been lost, but the regex has been matched.
                        # For example, we want a bitmap 1111111 but due to a loss we have 1111101.
                        # This incomplete bitmap matches the regex.
                        # We should note that here the SSN of the All-1 and the penultimate fragment received
                        # are not consecutive.
                        # Retransmitting the lost fragment and resending the All-1 solves that problem.
                        # There is another problematic case: we want a bitmap 1111111 but due to losses we have 1111001.
                        # If the second of those two lost fragments is retransmitted, the new bitmap, 1111011, does not
                        # match the regex. If, instead, the first of those fragments is retransmitted, the new bitmap
                        # 1111101 does match the regex. As the sender should retransmit these messages sequentially,
                        # the SSN of the resent All-1 and the penultimate fragment are still not consecutive.
                        # The only way for these two SSNs to be consecutive in these cases
                        # is that the penultimate fragment fills the bitmap in the 6th bit,
                        # and the last fragment is the All-1.
                        # This reasoning is still valid when the last window does not contain WINDOW_SIZE fragments.
                        # These particular cases validate the use for this regex matching.
                        # Remember that 1111011 is NOT a valid bitmap.
                        # In conclusion, AFTER the regex matching,
                        # we should check if the SSNs of the two last received fragments are consecutive.
                        # The second to last fragment has the highest SSN registered in the JSON.
                        # TODO: What happens when the All-0 prior to the last window is lost and is retransmitted with the All-1?
                        # We should consider only the SSNs of the last window. If there is a retransmission in a window
                        # prior to the last, the reasoning fails since the All-1 is always consecutive to a
                        # retransmitted fragment of a non-final window.
                        # If the All-1 is the only fragment of the last window (bitmap 0000001), and bitmap check of
                        # prior windows has passed, check the consecutiveness of the last All-0 and the All-1.

                        sequence_numbers = json.loads(
                            read_blob(BUCKET_NAME, "SSN"))

                        # This array has the SSNs of the last window.
                        # last_window_ssn = list(sequence_numbers.values())[current_window * profile.WINDOW_SIZE + 1:]

                        # If this array is empty, no messages have been received in the last window. Check if the
                        # last All-0 and the All-1 are consecutive. If they are not, there are lost fragments. If they
                        # are, the All-0 may have been retransmitted.
                        # print(last_window_ssn)

                        # The last sequence number should be the highest of these values.
                        last_sequence_number = max(
                            list(map(int, list(sequence_numbers.values()))))

                        # TODO: If the All-0 has the highest of these values, it may have been retransmitted using the All-1

                        print(
                            f"All-1 sequence number {sigfox_sequence_number}")
                        print(f"Last sequence number {last_sequence_number}")

                        if int(sigfox_sequence_number) - int(
                                last_sequence_number) == 1:
                            print(
                                "[ALL1] Integrity checking complete, launching reassembler."
                            )
                            # All-1 does not define a fragment number, so its fragment number must be the next
                            # of the higest registered fragment number.
                            last_index = max(
                                list(map(int, list(
                                    sequence_numbers.keys())))) + 1
                            upload_blob_using_threads(
                                BUCKET_NAME, data[0].decode("ISO-8859-1") +
                                data[1].decode("utf-8"),
                                f"all_windows/window_{current_window}/"
                                f"fragment_{current_window}_{last_index}")
                            try:
                                _ = requests.post(url=REASSEMBLER_URL,
                                                  json={
                                                      "last_index": last_index,
                                                      "current_window":
                                                      current_window,
                                                      "header_bytes":
                                                      header_bytes
                                                  },
                                                  timeout=0.1)
                            except requests.exceptions.ReadTimeout:
                                pass

                            # Send last ACK to end communication (on receiving an All-1, if no fragments are lost,
                            # if it has received at least one tile, return an ACK for the highest numbered window we
                            # currently have tiles for).
                            print("[ALL1] Preparing 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_ack)
                            response_json = send_ack(request_dict, last_ack)
                            print(f"200, Response content -> {response_json}")
                            print("[ALL1] Last ACK has been sent.")

                            return response_json, 200
                    # If the last two fragments are not consecutive, or the bitmap didn't match the regex,
                    # send an ACK reporting losses.
                    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)
예제 #4
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)