def before_request(): g.start = time.time() g.current_fragment = {} print('[before_request]: ' + request.endpoint) if request.endpoint == 'wyschc_get': if request.method == 'POST': print("[before_request]: POST RECEIVED") # BUCKET_NAME = config.BUCKET_NAME request_dict = request.get_json() print('[before_request]: Received Sigfox message: {}'.format( request_dict)) # Get data and Sigfox Sequence Number. fragment = request_dict["data"] sigfox_sequence_number = request_dict["seqNumber"] device = request_dict['device'] print('[before_request]: Data received from device id:{}, data:{}'. format(device, request_dict['data'])) # Parse fragment into "fragment = [header, payload] header_bytes = None header_first_hex = fragment[:1] if (header_first_hex) == '0' or '1': header = bytes.fromhex(fragment[:2]) payload = bytearray.fromhex(fragment[2:]) header_bytes = 1 elif (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_uplink = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) profile_downlink = Sigfox("DOWNLINK", "NO ACK", header_bytes) buffer_size = profile_uplink.MTU n = profile_uplink.N m = profile_uplink.M # Convert to a Fragment class for easier manipulation. fragment_message = Fragment(profile_uplink, data) # Get some SCHC values from the fragment. rule_id = fragment_message.header.RULE_ID dtag = fragment_message.header.DTAG w = fragment_message.header.W g.current_fragment['s-downlink_enable'] = request_dict['ack'] g.current_fragment['s-sending_start'] = time.time() g.current_fragment['s-data'] = request_dict["data"] g.current_fragment['FCN'] = fragment_message.header.FCN g.current_fragment['s-fragment_size'] = len(data) g.current_fragment['RULE_ID'] = fragment_message.header.RULE_ID g.current_fragment['W'] = fragment_message.header.W g.current_fragment['seqNumber'] = sigfox_sequence_number print('[before_request]: seqNum:{}, RULE_ID: {} W: {}, FCN: {}'. format(sigfox_sequence_number, fragment_message.header.RULE_ID, fragment_message.header.W, fragment_message.header.FCN)) print('[before_request]: {}'.format(g.current_fragment))
def clean(): """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") BUCKET_NAME = config.BUCKET_NAME request_dict = request.get_json() print('Received Request message: {}'.format(request_dict)) header_bytes = int(request_dict["header_bytes"]) profile = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) bitmap = '' for i in range(2**profile.N - 1): bitmap += '0' for i in range(2**profile.M): upload_blob(BUCKET_NAME, bitmap, "all_windows/window_%d/bitmap_%d" % (i, i)) upload_blob(BUCKET_NAME, bitmap, "all_windows/window_%d/losses_mask_%d" % (i, i)) return '', 204
def clean(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. header_bytes = int(request_dict["header_bytes"]) profile = Sigfox("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. try: _ = requests.post( url=config.CLEAN_WINDOW_URL, json={"header_bytes": header_bytes, "window_number": i, "clear": request_dict["clear"] if "clear" in request_dict else "False"}, timeout=0.1) except requests.ReadTimeout: pass if exists_blob("Reassembled_Packet"): delete_blob("Reassembled_Packet") upload_blob("", "SSN") print(f"from lopy? {request_dict['from_lopy']}") if request_dict["from_lopy"] == "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
def http_reassemble(): if request.method == "POST": # Get request JSON. print("[REASSEMBLE] 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 Cloud Storage variables. # BUCKET_NAME = 'sigfoxschc' # BUCKET_NAME = 'wyschc-niclabs' BUCKET_NAME = config.BUCKET_NAME # Initialize SCHC variables. profile_uplink = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) n = profile_uplink.N # Find the index of the first empty blob: print("[REASSEMBLE] Reassembling...") # Get all the fragments into an array in the format "fragment = [header, payload]" fragments = [] # TODO: This assumes that the last received message is in the last window. for i in range(current_window + 1): for j in range(2**n - 1): print("Loading fragment {}".format(j)) fragment_file = read_blob( BUCKET_NAME, "all_windows/window_%d/fragment_%d_%d" % (i, i, j)) print(fragment_file) ultimate_header = fragment_file[:header_bytes] ultimate_payload = fragment_file[header_bytes:] ultimate_fragment = [ ultimate_header.encode(), ultimate_payload.encode() ] fragments.append(ultimate_fragment) if i == current_window and j == last_index: break # Instantiate a Reassembler and start reassembling. reassembler = Reassembler(profile_uplink, fragments) payload = bytearray(reassembler.reassemble()) # Upload the full message. upload_blob(BUCKET_NAME, payload.decode("utf-8"), "Reassembled_message") return '', 204
def clean_window(request): request_dict = request.get_json() header_bytes = int(request_dict["header_bytes"]) window_number = int(request_dict["window_number"]) profile = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) 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}")
def cleanup(): os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = config.CLIENT_SECRETS_FILE bucket_name = config.BUCKET_NAME header_bytes = request.get_json()["header_bytes"] profile = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) # print("[CLN] Deleting timestamp blob") # delete_blob(bucket_name, "timestamp") # # print("[CLN] Deleting modified loss mask") # try: # os.remove(config.LOSS_MASK_MODIFIED) # except FileNotFoundError: # pass # # print("[CLN] Resetting SSN") # upload_blob(bucket_name, "{}", "SSN") # # print("[CLN] Initializing fragments...") # delete_blob(bucket_name, "all_windows/") # initialize_blobs(bucket_name, profile) return '', 204
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_bytes = None header_first_hex = fragment[:1] 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) == '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, "from_lopy": "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, "from_lopy": "True"}, timeout=0.1) except requests.exceptions.ReadTimeout: pass return '', 204 data = [header, payload] # Initialize SCHC variables. profile_uplink = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) profile_downlink = Sigfox("DOWNLINK", "NO ACK", header_bytes) buffer_size = profile_uplink.MTU n = profile_uplink.N m = profile_uplink.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_uplink.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_uplink, data) if 'enable_losses' in request_dict: if request_dict['enable_losses'] == "True": loss_rate = request_dict["loss_rate"] # loss_rate = 10 coin = random.random() print('loss rate: {}, random toss:{}'.format(loss_rate, 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)) # 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. # TODO: Send SCHC abort message. if str(fragment_number) != "0" and str( current_window) != "0" and time_received - last_time_received > profile_uplink.INACTIVITY_TIMER_VALUE: return json.dumps({"message": "Inactivity timer reached. Message ignored."}), 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": 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_downlink, rule_id, dtag, zfill(format(window_ack, 'b'), m), bitmap_ack, '0') 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, send an ACK # This is to be modified, as ACK-on-Error does not need an ACK for every window. 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": 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_uplink.BITMAP_SIZE): bitmap += '0' last_ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '1') 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": 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_uplink.BITMAP_SIZE): bitmap += '0' last_ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '1') 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": 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_downlink, rule_id, dtag, zfill(format(window_ack, 'b'), m), bitmap_ack, '0') 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": 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_downlink, rule_id, dtag, zfill(format(window_ack, 'b'), m), bitmap_ack, '0') 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)
# Read the file to be sent. with open(filename, "rb") as data: f = data.read() payload = bytearray(f) # Initialize variables. total_size = len(payload) current_size = 0 percent = round(0, 2) ack = None last_ack = None i = 0 current_window = 0 profile_uplink = Sigfox("UPLINK", "ACK ON ERROR") profile_downlink = Sigfox("DOWNLINK", "NO ACK") # Fragment the file. fragmenter = Fragmenter(profile_uplink, payload) fragment_list = fragmenter.fragment() # The fragment sender MUST initialize the Attempts counter to 0 for that Rule ID and DTag value pair # (a whole SCHC packet) attempts = 0 retransmitting = False fragment = None if len(fragment_list) > (2 ** profile_uplink.M) * profile_uplink.WINDOW_SIZE: print(len(fragment_list)) print((2 ** profile_uplink.M) * profile_uplink.WINDOW_SIZE)
def post_message(): global counter_w0 global counter_w1 if request.method == 'POST': print("POST RECEIVED") # BUCKET_NAME = config.BUCKET_NAME 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"] device = request_dict['device'] print('Data received from device id:{}, data:{}'.format( device, request_dict['data'])) # Parse fragment into "fragment = [header, payload] header = bytes.fromhex(fragment[:2]) payload = bytearray.fromhex(fragment[2:]) data = [header, payload] # Initialize SCHC variables. profile_uplink = Sigfox("UPLINK", "ACK ON ERROR") profile_downlink = Sigfox("DOWNLINK", "NO ACK") buffer_size = profile_uplink.MTU n = profile_uplink.N m = profile_uplink.M # Convert to a Fragment class for easier manipulation. fragment_message = Fragment(profile_uplink, data) # Get some SCHC values from the fragment. rule_id = fragment_message.header.RULE_ID dtag = fragment_message.header.DTAG w = fragment_message.header.W print('RULE_ID: {} W: {}, FCN: {}'.format( fragment_message.header.RULE_ID, fragment_message.header.W, fragment_message.header.FCN)) if 'ack' in request_dict: if request_dict['ack'] == 'true': print('w:{}'.format(w)) if w == '00': # print('ACK already send for this window, move along') # counter_w0 = 0 # return '', 204 if counter_w0 == 1: # print('ACK already send for this window, move along') print("This time send an ACK for window 1") counter_w0 = 0 bitmap = '0000001' ack = ACK(profile_downlink, rule_id, dtag, "01", bitmap, '0') response_json = send_ack(request_dict, ack) print("200, Response content -> {}".format( response_json)) return 'fragment lost', 204 counter_w0 += 1 print('lets say we lost the All-0, so move along') return 'fragment lost', 204 # return str(counter) # Create an ACK message and send it. bitmap = '1011111' bitmap = '1000000' bitmap = '0100001' ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '1') response_json = send_ack(request_dict, ack) print("200, Response content -> {}".format(response_json)) # response = {request_dict['device']: {'downlinkData': '07f7ffffffffffff'}} # print("response -> {}".format(response)) return response_json, 200 elif w == '01': if counter_w1 == 1: print("This time send an ACK for window 1") # counter_w0 = 0 counter_w1 += 1 bitmap = '0000001' ack = ACK(profile_downlink, rule_id, dtag, "01", bitmap, '0') response_json = send_ack(request_dict, ack) print("200, Response content -> {}".format( response_json)) return '', 204 elif counter_w1 == 2: print('Resend an ACK for window 1') counter_w1 += 1 bitmap = '0000001' 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 = {request_dict['device']: {'downlinkData': '07f7ffffffffffff'}} # print("response -> {}".format(response)) return response_json, 200 elif counter_w1 == 3: print( 'ACK already send for this window, send last ACK') counter_w1 = 0 bitmap = '0100001' ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '1') response_json = send_ack(request_dict, ack) print("200, Response content -> {}".format( response_json)) # response = {request_dict['device']: {'downlinkData': '07f7ffffffffffff'}} # print("response -> {}".format(response)) return response_json, 200 bitmap = '0100001' ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '1') response_json = send_ack(request_dict, ack) print("200, Response content -> {}".format( response_json)) counter_w1 += 1 # Create an ACK message and send it. bitmap = '0000001' ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '0') # Test for loss of All-0 in window 0 bitmap = '1010110' ack = ACK(profile_downlink, rule_id, dtag, '00', bitmap, '0') # ack = ACK(profile_downlink, rule_id, dtag, w, bitmap, '1') response_json = send_ack(request_dict, ack) print("200, Response content -> {}".format(response_json)) # response = {request_dict['device']: {'downlinkData': '07f7ffffffffffff'}} # print("response -> {}".format(response)) return response_json, 200 else: return '', 204 return '', 204 else: return f'Not a correct format message', 404
def reassemble(): CLEANUP_URL = "http://localhost:5000/cleanup" if request.method == "POST": print("[RSMB] The reassembler has been launched.") # Get request JSON. request_dict = request.get_json() print(f'[RSMB] Received HTTP message: {request_dict}') current_window = int(request_dict["current_window"]) last_index = int(request_dict["last_index"]) header_bytes = int(request_dict["header_bytes"]) # Initialize Cloud Storage variables. BUCKET_NAME = config.BUCKET_NAME # Initialize SCHC variables. profile = Sigfox("UPLINK", "ACK ON ERROR", header_bytes) window_size = profile.WINDOW_SIZE 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 for i in range(current_window + 1): for j in range(window_size): print(f"[RSMB] Loading fragment {j}") fragment_file = read_blob( BUCKET_NAME, f"all_windows/window_{i}/fragment_{i}_{j}") print(f"[RSMB] Fragment data: {fragment_file}") header = fragment_file[:header_bytes] payload = fragment_file[header_bytes:] fragment = [header.encode(), payload.encode()] fragments.append(fragment) if i == current_window and j == last_index: break # Instantiate a Reassembler and start reassembling. print("[RSMB] Reassembling") reassembler = Reassembler(profile, fragments) payload = bytearray(reassembler.reassemble()).decode("utf-8") print("[RSMB] Uploading result") with open(config.PAYLOAD, "w") as file: file.write(payload) # Upload the full message. upload_blob(BUCKET_NAME, payload, "PAYLOAD") if filecmp.cmp(config.PAYLOAD, config.MESSAGE): print("The reassembled file is equal to the original message.") else: print("The reassembled file is corrupt.") try: _ = requests.post(url=CLEANUP_URL, json={"header_bytes": header_bytes}, timeout=0.1) except requests.exceptions.ReadTimeout: pass return '', 204
def schc_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>. """ REASSEMBLER_URL = "http://localhost:5000/reassemble" 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 == "true": # 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: # 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) % profile.WINDOW_SIZE upload_blob( 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}") print( "[ALL1] Integrity checking complete, launching reassembler." ) 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)