Index: cloud_sync.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloud_sync.py (.../cloud_sync.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloud_sync.py (.../cloud_sync.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -5,24 +5,31 @@ import socket import sys +import werkzeug +werkzeug.cached_property = werkzeug.utils.cached_property + from flask import Flask, Response, request from flask_restplus import Api, Resource, fields, reqparse from cloudsync.utils.helpers import * from cloudsync.utils.globals import * from cloudsync.utils.reachability import ReachabilityProvider from cloudsync.busses.file_output_bus import FileOutputBus +from cloudsync.handlers.error_handler import ErrorHandler from cloudsync.handlers.cs_mft_dcs_request_handler import NetworkRequestHandler from cloudsync.handlers.ui_cs_request_handler import UICSMessageHandler from cloudsync.busses.file_input_bus import FileInputBus from cloudsync.utils.heartbeat import HeartBeatProvider -VERSION = "0.2.4" +VERSION = "0.2.6" arguments = sys.argv log_level = int(arguments[1]) -logging.basicConfig(filename='cloudsync.log', level=log_level) +logging.basicConfig(filename='cloudsync.log', + level=log_level, + format='[%(asctime)s]: %(levelname)s - %(message)s | {%(pathname)s:%(lineno)d}', + ) app = Flask(__name__) api = Api(app=app, version=VERSION, title="CloudSync Registration API", @@ -35,7 +42,7 @@ default_formatter = logging.Formatter('[%(asctime)s] %(levelname)s in %(module)s: %(message)s') handler.setFormatter(default_formatter) -app.logger.addHandler(handler) +# app.logger.addHandler(handler) app.logger.setLevel(log_level) g_utils.add_logger(app.logger) @@ -46,12 +53,16 @@ g_utils.add_reachability_provider(reachability_provider=reachability_provider) -output_channel = FileOutputBus(logger=app.logger, max_size=1, file_channels_path=UI2CS_FILE_CHANNELS_PATH) +output_channel = FileOutputBus(logger=app.logger, max_size=100, file_channels_path=UI2CS_FILE_CHANNELS_PATH) +error_handler = ErrorHandler(logger=app.logger, max_size=100, output_channel=output_channel) + network_request_handler = NetworkRequestHandler(logger=app.logger, max_size=1, output_channel=output_channel, - reachability_provider=reachability_provider) + reachability_provider=reachability_provider, + error_handler=error_handler) message_handler = UICSMessageHandler(logger=app.logger, max_size=20, network_request_handler=network_request_handler, - output_channel=output_channel, reachability_provider=reachability_provider) + output_channel=output_channel, reachability_provider=reachability_provider, + error_handler=error_handler) ui_cs_bus = FileInputBus(logger=app.logger, file_channels_path=UI2CS_FILE_CHANNELS_PATH, input_channel_name="inp.buf", g_config=g_config, message_handler=message_handler) Index: cloudsync/busses/file_input_bus.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/busses/file_input_bus.py (.../file_input_bus.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/busses/file_input_bus.py (.../file_input_bus.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -36,6 +36,7 @@ self.message_handler = message_handler self.i = inotify.adapters.Inotify() + print(self.file_channels_path) self.i.add_watch(self.file_channels_path) self.thread = Thread(target=self.input_channel_handler, daemon=True) Index: cloudsync/busses/file_output_bus.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/busses/file_output_bus.py (.../file_output_bus.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/busses/file_output_bus.py (.../file_output_bus.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -5,7 +5,9 @@ from threading import Event, Thread import datetime +from cloudsync.utils import helpers + class FileOutputBus: """File Output Bus class that receives, parses and sends downstream all outbound messages""" def __init__(self, @@ -69,7 +71,9 @@ try: filename = datetime.datetime.utcnow().date().strftime("%Y_%m_%d_out.buf") - message_body = str(round(datetime.datetime.now().timestamp())) + ',' + str(self.last_output_message_id) + ',0,' + message_body + message_data = str(round(datetime.datetime.now().timestamp())) + str(self.last_output_message_id) + message_body + message_crc8 = helpers.crc8(message_data.encode('utf-8')) + message_body = str(round(datetime.datetime.now().timestamp())) + ',' + str(self.last_output_message_id) + ',' + str(message_crc8) + ',' + message_body self.logger.debug('Full message: {0}'.format(message_body)) print('Full message: {0}'.format(message_body)) f = open(self.file_channels_path + "/" + filename, "a") Index: cloudsync/common/enums.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/common/enums.py (.../enums.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/common/enums.py (.../enums.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -74,7 +74,10 @@ UI2CS_SEND_DEVICE_STATE = 1006 UI2CS_SEND_TREATMENT_REPORT = 1007 + # INCOMING ERROR + UI2CS_ERROR = 1999 + @unique class OutboundMessageIDs(RootEnum): # REGISTRATION @@ -87,6 +90,8 @@ CS2UI_REQ_DEVICE_STATE = 2006 CS2UI_REQ_TX_CODE_DISPLAY = 2008 + # OUTGOING ERROR + CS2UI_ERROR = 2999 @unique class NetworkRequestType(RootEnum): @@ -96,3 +101,19 @@ CS2MFT_REQ_REGISTRATION = 101 CS2DCS_REQ_SET_DEVICE_STATE = 201 CS2DCS_REQ_SEND_TREATMENT_REPORT = 202 + +@unique +class ErrorIDs(RootEnum): + GENERIC_ERROR = 900 + CS_REQ_REGISTRATION_ERROR = 901 + CS_SEND_DEVICE_STATE_ERROR = 906 + CS_SEND_TREATMENT_REPORT_ERROR = 907 + CS_BAD_CRC_ERROR = 910 + CS_DEVICE_VALIDATION_RESULT_ERROR = 920 + CS_SET_PATIENT_DEVICE_ASSOCIATION_ERROR = 921 + CS_GET_NEW_TOKEN_WITH_CERT_ERROR = 922 + CS_VERIFY_TOKEN_ERROR = 923 + CS_VALIDATE_DEVICE_ERROR = 924 + CS_CHECK_IF_PATIENT_WITH_EMR_ID_EXISTS_ERROR = 925 + CS_CREATE_TEMPORARY_PATIENT_ERROR = 926 + Index: cloudsync/config/config.json =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/config/config.json (.../config.json) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/config/config.json (.../config.json) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -16,4 +16,4 @@ "mode": "registration", "device_state": "INACTIVE_NOT_OK" } -} \ No newline at end of file +} Index: cloudsync/handlers/cs_mft_dcs_request_handler.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/cs_mft_dcs_request_handler.py (.../cs_mft_dcs_request_handler.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/cs_mft_dcs_request_handler.py (.../cs_mft_dcs_request_handler.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -8,6 +8,8 @@ from random import seed, randint from cloudsync.handlers.network_request import NetworkRequest +from cloudsync.handlers.error_handler import ErrorHandler +from cloudsync.handlers.error import Error from cloudsync.utils.helpers import log_func from cloudsync.common.enums import * from cloudsync.utils.globals import * @@ -17,11 +19,12 @@ class NetworkRequestHandler: - def __init__(self, logger: Logger, max_size, output_channel, reachability_provider): + def __init__(self, logger: Logger, max_size, output_channel, reachability_provider, error_handler): self.logger = logger self.reachability_provider = reachability_provider self.logger.info('Created Network Request Handler') self.output_channel = output_channel + self.error_handler = error_handler self.queue = deque(maxlen=max_size) # Thread safe self.thread = Thread(target=self.scheduler, daemon=True) self.event = Event() @@ -61,15 +64,22 @@ :return: None """ if req.request_type == NetworkRequestType.CS2MFT_REQ_REGISTRATION: - response = cmd_outgoing_register(device_name=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_NAME], - hd_serial=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_HD_SERIAL], - dg_serial=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_DG_SERIAL], - sw_version=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_SW_VERSION], - manf_tool_base_url=req.g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_MFT_URL], - data_source_id=req.g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_DIA_ORG_ID], - headers=req.headers) - - self.logger.debug("DRT Request registration resp: {0}".format(response)) + try: + response = cmd_outgoing_register(device_name=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_NAME], + hd_serial=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_HD_SERIAL], + dg_serial=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_DG_SERIAL], + sw_version=req.g_config[CONFIG_DEVICE][CONFIG_DEVICE_SW_VERSION], + manf_tool_base_url=req.g_config[CONFIG_KEBORMED][ + CONFIG_KEBORMED_MFT_URL], + data_source_id=req.g_config[CONFIG_KEBORMED][ + CONFIG_KEBORMED_DIA_ORG_ID], + headers=req.headers) + self.logger.debug("DRT Request registration resp: {0}".format(response)) + except Exception as e: + error = Error("{0},2,{1},{2}".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value, + e)) + self.error_handler.enqueue_error(error=error) elif req.request_type == NetworkRequestType.MFT2CS_REQ_SET_CREDENTIALS: certificate = req.payload.get("certificate") private_key = req.payload.get("private_key") @@ -158,8 +168,8 @@ # Step #2a - Check if patient exists by patient_emr_id - response = cmd_outgoing_check_if_patient_wit_emr_id_exists(access_token=access_token, - url=patient_with_emr_id_exists_url) + response = cmd_outgoing_check_if_patient_with_emr_id_exists(access_token=access_token, + url=patient_with_emr_id_exists_url) # Step #2b - If patient with emr_id doesn't exist, create temporary patient @@ -183,8 +193,10 @@ associate=True) # Step #4 - Send treatment report - treatment_log_json['generatedAt'] = int(round(time() * S_MS_CONVERSION_FACTOR)) + 30000 # TEMPORARY DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX - treatment_log_json['completedAt'] = int(round(time() * S_MS_CONVERSION_FACTOR)) + 31000 # TEMPORAR DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX + treatment_log_json['generatedAt'] = int(round( + time() * S_MS_CONVERSION_FACTOR)) + 30000 # TEMPORARY DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX + treatment_log_json['completedAt'] = int(round( + time() * S_MS_CONVERSION_FACTOR)) + 31000 # TEMPORAR DELAY TO COMPENSATE FOR DEVICE INTERNAL CLOCK ERROR - TODO REMOVE AFTER FIX treatment_log_json = json.dumps(treatment_log_json) Index: cloudsync/handlers/error.py =================================================================== diff -u --- cloudsync/handlers/error.py (revision 0) +++ cloudsync/handlers/error.py (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -0,0 +1,33 @@ +import datetime + + +class Error: + + def __init__(self, error_body: str): + + error_components = error_body.split(',') + if len(error_components) >= 3: + self.timestamp = datetime.datetime.now().timestamp() + self.ID = error_components[0] + self.size = error_components[1] + self.code = error_components[2] + if int(error_components[1]) > 1: + self.parameters = error_components[3:] + else: + self.parameters = [] + else: + self.timestamp = "0" + self.ID = "0" + self.size = "0" + self.code = "0" + self.parameters = [] + + def __str__(self): + data = { + "timestamp": self.timestamp, + "ID": self.ID, + "size": self.size, + "code": self.code, + "parameters": self.parameters + } + return str(data) Index: cloudsync/handlers/error_handler.py =================================================================== diff -u --- cloudsync/handlers/error_handler.py (revision 0) +++ cloudsync/handlers/error_handler.py (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -0,0 +1,63 @@ +from time import sleep +from logging import Logger +from threading import Event, Thread +from collections import deque + +from cloudsync.handlers.error import Error +from cloudsync.common.enums import * +from cloudsync.utils.globals import * +from cloudsync.utils.helpers import * + + +class ErrorHandler: + def __init__(self, logger: Logger, max_size, output_channel): + self.logger = logger + self.output_channel = output_channel + self.queue = deque(maxlen=max_size) # Thread safe + self.thread = Thread(target=self.scheduler, daemon=True) + self.event = Event() + self.thread.start() + + self.logger.info('Created Error Handler') + + def scheduler(self) -> None: + """ + Continuously monitors the event flag to check for new errors + :return: None + """ + while True: + flag = self.event.wait() + if flag: + while len(self.queue) > 0: + # print('queue size: {0}'.format(len(self.queue))) + error = self.queue.popleft() + self.handle_error(error) + self.event.clear() + + def enqueue_error(self, error: Error) -> bool: + """ + :param error: the error to add to the queue + :return: True upon success, False otherwise + """ + if len(self.queue) < self.queue.maxlen: + self.queue.append(error) + self.event.set() + return True + else: + return False + + def handle_error(self, error: Error): + if InboundMessageIDs.mapped_str_value(error.ID) == InboundMessageIDs.UI2CS_ERROR: + self.logger.error('UI App Error {0}: {1}'.format(error.code, error.parameters)) + return # TODO Add specific UI error message handling when necessary + + if OutboundMessageIDs.mapped_str_value(error.ID) == OutboundMessageIDs.CS2UI_ERROR: + self.logger.error('CS App Error {0} - {1}: {2}'.format(error.code, ErrorIDs.mapped_str_value(error.code), + error.parameters)) + parameter_string = "" + for parameter in error.parameters: + parameter_string += "," + parameter_string += parameter + + message_body = str(OutboundMessageIDs.CS2UI_ERROR.value) + ',' + error.size + ',' + error.code + parameter_string + self.output_channel.enqueue_message(message_body) Index: cloudsync/handlers/incoming/handler_mft_to_cs.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/incoming/handler_mft_to_cs.py (.../handler_mft_to_cs.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/incoming/handler_mft_to_cs.py (.../handler_mft_to_cs.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -65,16 +65,22 @@ g_config: dict) -> None: """ Initiates the connectivity test on the device - - :param str url_token: the validation request tok - :param str url_validate: The kebormed request new token url + # Step 6. Device Auth (POST /auth/...) + # Step 7. Device Auth Response (access token) + # Step 8. Validate Device (POST /api/device/validate) + # Step 9. Validate Device Response (list of invalid fields) + # Step 10. Validation Result (POST /validation) (list of invalid fields) + :param url_token: The kebormed request new token url + :param username: The username for requesting a token + :param password: The password for requesting a token :param hd_serial: The hd serial number :param dg_serial: The dg serial number :param sw_version: The software version :param g_config: The configuration dictionary :return: None """ - + # Step 6. Device Auth (POST /auth/...) + # Step 7. Device Auth Response (access token) client_secret = g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_IDP_CLIENT_SECRET] access_token = cmd_outgoing_get_new_token_with_cert(path_certificate=TEMP_CREDENTIALS_CERTIFICATE_X509, path_private_key=TEMP_CREDENTIALS_PRIVATE_KEY, Index: cloudsync/handlers/network_request.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/network_request.py (.../network_request.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/network_request.py (.../network_request.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -26,4 +26,4 @@ "payload": self.payload, "method": self.method, } - return str(data) \ No newline at end of file + return str(data) Index: cloudsync/handlers/outgoing/handler_cs_to_dcs.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/outgoing/handler_cs_to_dcs.py (.../handler_cs_to_dcs.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/outgoing/handler_cs_to_dcs.py (.../handler_cs_to_dcs.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -10,32 +10,40 @@ from cloudsync.utils.helpers import * - @log_func def cmd_outgoing_set_patient_device_association(url: str, access_token: str, associate: bool) -> requests.Response: - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } - payload = {} + try: + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } + payload = {} - if associate: - resp = requests.head(url=urllib.parse.urljoin(url, "/exists"), - headers=headers, - data=payload, - verify=False) + if associate: + resp = requests.head(url=urllib.parse.urljoin(url, "/exists"), + headers=headers, + data=payload, + verify=False) - if resp.status_code == NOT_FOUND: - resp = requests.put(url=url, - headers=headers, - data=payload, - verify=False) - else: - resp = requests.delete(url=url, - headers=headers, - data=payload, - verify=False) - return resp + if resp.status_code == NOT_FOUND: + resp = requests.put(url=url, + headers=headers, + data=payload, + verify=False) + else: + resp = requests.delete(url=url, + headers=headers, + data=payload, + verify=False) + return resp + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_SET_PATIENT_DEVICE_ASSOCIATION_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_SET_PATIENT_DEVICE_ASSOCIATION_ERROR.value)) + self.error_handler.enqueue_error(error=error) @log_func @@ -45,20 +53,29 @@ :return: requests.Response """ - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } + try: + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } - payload = treatment_log + payload = treatment_log - print("paylod: {0}".format(payload)) + print("paylod: {0}".format(payload)) - resp = requests.post(url=url, - headers=headers, - data=payload, - verify=False) - return resp + resp = requests.post(url=url, + headers=headers, + data=payload, + verify=False) + return resp + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_SEND_TREATMENT_REPORT_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_SEND_TREATMENT_REPORT_ERROR.value)) + self.error_handler.enqueue_error(error=error) @log_func @@ -68,16 +85,25 @@ :param current_state: (int) the current device state :return: The response """ - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } - payload = {} - resp = requests.put(url=url, - headers=headers, - data=payload, - verify=False) - return resp + try: + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } + payload = {} + resp = requests.put(url=url, + headers=headers, + data=payload, + verify=False) + return resp + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_SEND_DEVICE_STATE_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_SEND_DEVICE_STATE_ERROR.value)) + self.error_handler.enqueue_error(error=error) @log_func @@ -98,77 +124,63 @@ :param client_secret: The secret client key :return: The new token """ + try: + payload = { + "grant_type": "password", + "scope": "openid profile", + "client_id": "device-client", + "client_secret": client_secret, + } - payload = { - "grant_type": "password", - "scope": "openid profile", - "client_id": "device-client", - "client_secret": client_secret, - } + headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + } + cert_paths = (path_certificate, path_private_key) - headers = { - 'Content-Type': 'application/x-www-form-urlencoded' - } - cert_paths = (path_certificate, path_private_key) + response = requests.post(url=url, + headers=headers, + data=payload, + cert=cert_paths, + verify=False) - response = requests.post(url=url, - headers=headers, - data=payload, - cert=cert_paths, - verify=False) + data = response.json() + if save: + with open(DEVICE_KEBORMED_ACCESS_TOKEN_PATH, 'w') as f: + f.write(json.dumps(data, indent=4)) - data = response.json() - if save: - with open(DEVICE_KEBORMED_ACCESS_TOKEN_PATH, 'w') as f: - f.write(json.dumps(data, indent=4)) + return data.get("access_token", None) + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_GET_NEW_TOKEN_WITH_CERT_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_GET_NEW_TOKEN_WITH_CERT_ERROR.value)) + self.error_handler.enqueue_error(error=error) - return data.get("access_token", None) @log_func def cmd_outgoing_verify_token(url: str, access_token: str) -> requests.Response: - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } + try: + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } - resp = requests.get(url=url, - headers=headers, - verify=False) - return resp + resp = requests.get(url=url, + headers=headers, + verify=False) + return resp + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_VERIFY_TOKEN_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_VERIFY_TOKEN_ERROR.value)) + self.error_handler.enqueue_error(error=error) -@log_func -def cmd_outgoing_get_new_token(username: str, password: str, save: bool, url: str) -> str: - """ - Performs the following steps in the device registration sequence diagram: - Step 6. Device Auth - Step 7. Device Auth Response - :param username: The username required for login - :param password: The password required for login - :param save: If True, save the token - :param url: The kebormed url to request the new token - :return: The new token - """ - - payload = ("grant_type=password&scope=openid%20profile%20email%20" - "offline_access&username={0}&password={1}&client_id=frontend-client").format(username, password) - headers = { - 'Content-Type': 'application/x-www-form-urlencoded' - } - - response = requests.post(url=url, - headers=headers, - data=payload, - verify=False) - data = response.json() - if save: - with open(DEVICE_KEBORMED_ACCESS_TOKEN_PATH, 'w') as f: - f.write(json.dumps(data, indent=4)) - - access_token = data.get("access_token", None) - return access_token - - @log_func def cmd_outgoing_validate_device(access_token: str, hd_serial_number: str, @@ -180,74 +192,118 @@ Step 9. Validate device response (list of invalid fields) :return: The json response """ + try: + payload = json.dumps({ + "hdSerialNumber": hd_serial_number, + "dgSerialNumber": dg_serial_number, + "softwareVersion": sw_version + }) - payload = json.dumps({ - "hdSerialNumber": hd_serial_number, - "dgSerialNumber": dg_serial_number, - "softwareVersion": sw_version - }) + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } + response = requests.post(url=url, + headers=headers, + data=payload, + verify=False) + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_VALIDATE_DEVICE_ERROR.value)) + self.error_handler.enqueue_error(error=error) + return None + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_VALIDATE_DEVICE_ERROR.value)) + self.error_handler.enqueue_error(error=error) + return None - response = requests.post(url=url, - headers=headers, - data=payload, - verify=False) - try: return response.json() except json.decoder.JSONDecodeError as e: - g_utils.logger.error("Could not validate device. Received: {0}:{1}" - .format(response.status_code, - response.reason)) + error = Error("{0},3,{1},Could not validate device. Received: {2}:{3},Exception: {4}".format( + OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_VALIDATE_DEVICE_ERROR.value, + response.status_code, + response.reason, + e)) + self.error_handler.enqueue_error(error=error) return None # Runtime commands @log_func -def cmd_outgoing_check_if_patient_wit_emr_id_exists(access_token: str, - url: str) -> requests.Response: - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } +def cmd_outgoing_check_if_patient_with_emr_id_exists(access_token: str, + url: str) -> requests.Response: + try: + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } - response = requests.get(url=url, - headers=headers, - verify=False) + response = requests.get(url=url, + headers=headers, + verify=False) + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_CHECK_IF_PATIENT_WITH_EMR_ID_EXISTS_ERROR.value)) + self.error_handler.enqueue_error(error=error) + return None + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_CHECK_IF_PATIENT_WITH_EMR_ID_EXISTS_ERROR.value)) + self.error_handler.enqueue_error(error=error) + return None try: return response except json.decoder.JSONDecodeError as e: - g_utils.logger.error("Could not check if the patient exists. Received: {0}:{1}" - .format(response.status_code, - response.reason)) + error = Error("{0},3,{1},Could not check if the patient exists. Received: {2}:{3},Exception: {4}".format( + OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_CHECK_IF_PATIENT_WITH_EMR_ID_EXISTS_ERROR.value, + response.status_code, + response.reason, + e)) + self.error_handler.enqueue_error(error=error) return None @log_func def cmd_outgoing_create_temporary_patient(access_token: str, url: str) -> dict: - headers = { - 'Authorization': "Bearer {0}".format(access_token), - 'Content-Type': 'application/json' - } + try: + headers = { + 'Authorization': "Bearer {0}".format(access_token), + 'Content-Type': 'application/json' + } - payload = {} + payload = {} - response = requests.post(url=url, - headers=headers, - data=payload, - verify=False) + response = requests.post(url=url, + headers=headers, + data=payload, + verify=False) + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_CREATE_TEMPORARY_PATIENT_ERROR.value)) + self.error_handler.enqueue_error(error=error) + return None + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_CREATE_TEMPORARY_PATIENT_ERROR.value)) + self.error_handler.enqueue_error(error=error) + return None try: return response.json() except json.decoder.JSONDecodeError as e: - g_utils.logger.error("Could not create temporary patient. Received: {0}:{1}" - .format(response.status_code, - response.reason)) + error = Error("{0},3,{1},Could not create temporary patient. Received: {2}:{3},Exception: {4}".format( + OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_CREATE_TEMPORARY_PATIENT_ERROR.value, + response.status_code, + response.reason, + e)) + self.error_handler.enqueue_error(error=error) return None Index: cloudsync/handlers/outgoing/handler_cs_to_mft.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/outgoing/handler_cs_to_mft.py (.../handler_cs_to_mft.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/outgoing/handler_cs_to_mft.py (.../handler_cs_to_mft.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -7,6 +7,8 @@ from typing import List import urllib.parse +from cloudsync.handlers.error_handler import ErrorHandler +from cloudsync.handlers.error import Error from cloudsync.utils.globals import * from cloudsync.common.enums import * from cloudsync.utils.helpers import * @@ -34,19 +36,28 @@ :return: requests.Response """ # print('---Registration request made') - url = urllib.parse.urljoin(manf_tool_base_url, "register/") - data = { - "datasourceId": data_source_id, - "name": device_name, - "hdSerialNumber": hd_serial, - "dgSerialNumber": dg_serial, - "softwareVersion": sw_version, - } - resp = requests.post(url=url, - data=data, - headers=headers, - verify=False) - return resp + try: + url = urllib.parse.urljoin(manf_tool_base_url, "register/") + data = { + "datasourceId": data_source_id, + "name": device_name, + "hdSerialNumber": hd_serial, + "dgSerialNumber": dg_serial, + "softwareVersion": sw_version, + } + resp = requests.post(url=url, + data=data, + headers=headers, + verify=False) + return resp + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value)) + self.error_handler.enqueue_error(error=error) @log_func @@ -63,25 +74,37 @@ if (CONFIG_KEBORMED not in g_config.keys() or CONFIG_KEBORMED_MFT_URL not in g_config[CONFIG_KEBORMED]): - g_utils.logger.error("Manufacturing tool url not found.") + error = Error("{0},2,{1},Manufacturing tool url not found".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_DEVICE_VALIDATION_RESULT_ERROR.value)) + self.error_handler.enqueue_error(error=error) return None - url = urllib.parse.urljoin(g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_MFT_URL], - "validation/") - headers = { - "device_ip": g_config[CONFIG_DEVICE][CONFIG_DEVICE_IP], - "device_sn": hd_serial - } - if len(invalid_attributes) == 0: - data = json.dumps({}) - else: - data = { - "invalidAttributes": invalid_attributes, + try: + url = urllib.parse.urljoin(g_config[CONFIG_KEBORMED][CONFIG_KEBORMED_MFT_URL], + "validation/") + + headers = { + "device_ip": g_config[CONFIG_DEVICE][CONFIG_DEVICE_IP], + "device_sn": hd_serial } - g_utils.logger.debug("headers: {0}".format(headers)) - g_utils.logger.debug("data: {0}".format(data)) - resp = requests.post(url=url, - data=data, - headers=headers, - verify=False) - return resp.json() + if len(invalid_attributes) == 0: + data = json.dumps({}) + else: + data = { + "invalidAttributes": invalid_attributes, + } + g_utils.logger.debug("headers: {0}".format(headers)) + g_utils.logger.debug("data: {0}".format(data)) + resp = requests.post(url=url, + data=data, + headers=headers, + verify=False) + return resp.json() + except requests.exceptions.Timeout: + error = Error("{0},2,{1},Registration timeout".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_DEVICE_VALIDATION_RESULT_ERROR.value)) + self.error_handler.enqueue_error(error=error) + except requests.exceptions.TooManyRedirects: + error = Error("{0},2,{1},Too many redirects".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_DEVICE_VALIDATION_RESULT_ERROR.value)) + self.error_handler.enqueue_error(error=error) Index: cloudsync/handlers/ui_cs_request_handler.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/ui_cs_request_handler.py (.../ui_cs_request_handler.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/ui_cs_request_handler.py (.../ui_cs_request_handler.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -6,6 +6,8 @@ from collections import deque from cloudsync.handlers.uics_message import UICSMessage +from cloudsync.handlers.error_handler import ErrorHandler +from cloudsync.handlers.error import Error from cloudsync.common.enums import * from cloudsync.utils.globals import * from cloudsync.utils.helpers import * @@ -17,11 +19,13 @@ class UICSMessageHandler: - def __init__(self, logger: Logger, max_size, network_request_handler, output_channel, reachability_provider): + def __init__(self, logger: Logger, max_size, network_request_handler, output_channel, reachability_provider, + error_handler): self.logger = logger self.reachability_provider = reachability_provider self.network_request_handler = network_request_handler self.output_channel = output_channel + self.error_handler = error_handler self.queue = deque(maxlen=max_size) # Thread safe self.thread = Thread(target=self.scheduler, daemon=True) self.event = Event() @@ -58,25 +62,51 @@ def handle_message(self, message: UICSMessage): self.logger.info('CS Message: {0}'.format(message)) + message_data = message.timestamp + message.sequence + message.ID + message.size + if len(message.parameters) > 0: + for param in message.parameters: + message_data += param + message_calculated_crc8 = crc8(message_data.encode('utf-8')) + + if message.CRC != str(message_calculated_crc8): + error = Error("{0},4,{1},Bad CRC on message {2}, Message CRC: {3}, Calculated CRC: {4}".format( + OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_BAD_CRC_ERROR.value, + message.sequence, + message.CRC, + str(message_calculated_crc8))) + self.error_handler.enqueue_error(error=error) + if (InboundMessageIDs.mapped_str_value(message.ID) == InboundMessageIDs.UI2CS_REQ_REGISTRATION) and \ (message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_MODE] == 'registration'): self.logger.info('UI2CS_REQ_REGISTRATION request received') self.logger.debug('Config: {0}'.format(message.g_config)) - message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_HD_SERIAL] = message.parameters[0] - message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_DG_SERIAL] = message.parameters[1] - message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_SW_VERSION] = message.parameters[2] - message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_NAME] = message.parameters[0] + if len(message.parameters) != 3: + error = Error("{0},2,{1},invalid # of parameters".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value)) + self.error_handler.enqueue_error(error=error) + else: + message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_HD_SERIAL] = message.parameters[0] + message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_DG_SERIAL] = message.parameters[1] + message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_SW_VERSION] = message.parameters[2] + message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_NAME] = message.parameters[0] - helpers_write_config(CONFIG_PATH, message.g_config) + helpers_write_config(CONFIG_PATH, message.g_config) - helpers_add_to_network_queue(network_request_handler=self.network_request_handler, - request_type=NetworkRequestType.CS2MFT_REQ_REGISTRATION, - url='', - payload={}, - method='', - g_config=message.g_config, - success_message='CS2MFT_REQ_REGISTRATION request added to network queue') + try: + helpers_add_to_network_queue(network_request_handler=self.network_request_handler, + request_type=NetworkRequestType.CS2MFT_REQ_REGISTRATION, + url='', + payload={}, + method='', + g_config=message.g_config, + success_message='CS2MFT_REQ_REGISTRATION request added to network queue') + except Exception as e: + error = Error("{0},2,{1},{2}".format(OutboundMessageIDs.CS2UI_ERROR.value, + ErrorIDs.CS_REQ_REGISTRATION_ERROR.value, + e)) + self.error_handler.enqueue_error(error=error) elif InboundMessageIDs.mapped_str_value(message.ID) == InboundMessageIDs.UI2CS_SEND_CREDENTIALS_SAVED and \ (message.g_config[CONFIG_DEVICE][CONFIG_DEVICE_MODE] == 'registration'): self.logger.info('UI2CS_SEND_CREDENTIALS_SAVED request received') @@ -134,6 +164,13 @@ g_config=message.g_config, success_message='CS2DCS_REQ_SEND_TREATMENT_REPORT request added to network ' 'queue') + elif InboundMessageIDs.mapped_str_value(message.ID) == InboundMessageIDs.UI2CS_ERROR: + error_body = "{0},{1},{2}".format(InboundMessageIDs.UI2CS_ERROR.value, message.size, message.parameters[0]) + if len(message.parameters) > 1: + for parameter in message.parameters[1:]: + error_body += ",{0}".format(parameter) + error = Error(error_body=error_body) + self.error_handler.enqueue_error(error=error) def save_secrets(self, source_cert, source_private_key, source_public_key): try: Index: cloudsync/handlers/uics_message.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/handlers/uics_message.py (.../uics_message.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/handlers/uics_message.py (.../uics_message.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -17,11 +17,11 @@ else: self.parameters = {} else: - self.timestamp = 0 - self.sequence = 0 - self.CRC = 0 - self.ID = 0 - self.size = 0 + self.timestamp = "0" + self.sequence = "0" + self.CRC = "0" + self.ID = "0" + self.size = "0" self.parameters = {} def __str__(self): @@ -33,4 +33,4 @@ "number_of_parameters": self.size, "parameters": self.parameters, } - return str(data) \ No newline at end of file + return str(data) Index: cloudsync/utils/helpers.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/utils/helpers.py (.../helpers.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/utils/helpers.py (.../helpers.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -30,7 +30,26 @@ g_utils = GUtils() +CRC_LIST = [ + 0, 49, 98, 83, 196, 245, 166, 151, 185, 136, 219, 234, 125, 76, 31, 46, + 67, 114, 33, 16, 135, 182, 229, 212, 250, 203, 152, 169, 62, 15, 92, 109, + 134, 183, 228, 213, 66, 115, 32, 17, 63, 14, 93, 108, 251, 202, 153, 168, + 197, 244, 167, 150, 1, 48, 99, 82, 124, 77, 30, 47, 184, 137, 218, 235, + 61, 12, 95, 110, 249, 200, 155, 170, 132, 181, 230, 215, 64, 113, 34, 19, + 126, 79, 28, 45, 186, 139, 216, 233, 199, 246, 165, 148, 3, 50, 97, 80, + 187, 138, 217, 232, 127, 78, 29, 44, 2, 51, 96, 81, 198, 247, 164, 149, + 248, 201, 154, 171, 60, 13, 94, 111, 65, 112, 35, 18, 133, 180, 231, 214, + 122, 75, 24, 41, 190, 143, 220, 237, 195, 242, 161, 144, 7, 54, 101, 84, + 57, 8, 91, 106, 253, 204, 159, 174, 128, 177, 226, 211, 68, 117, 38, 23, + 252, 205, 158, 175, 56, 9, 90, 107, 69, 116, 39, 22, 129, 176, 227, 210, + 191, 142, 221, 236, 123, 74, 25, 40, 6, 55, 100, 85, 194, 243, 160, 145, + 71, 118, 37, 20, 131, 178, 225, 208, 254, 207, 156, 173, 58, 11, 88, 105, + 4, 53, 102, 87, 192, 241, 162, 147, 189, 140, 223, 238, 121, 72, 27, 42, + 193, 240, 163, 146, 5, 52, 103, 86, 120, 73, 26, 43, 188, 141, 222, 239, + 130, 179, 224, 209, 70, 119, 36, 21, 59, 10, 89, 104, 255, 206, 157, 172 +] + def helpers_is_int(val: str) -> bool: """ Determines if the value can be converted to an int @@ -402,7 +421,6 @@ def helpers_add_to_network_queue(network_request_handler, request_type, url, payload, method, g_config, success_message): - cycle_duration = REACHABILITY_CYCLE_PAUSE total_cycles = REACHABILITY_CYCLES @@ -428,10 +446,21 @@ def helpers_add_to_output_channel(output_channel, message_body, success_message): - message_added_to_queue = False while not message_added_to_queue: message_added_to_queue = output_channel.enqueue_message(message_body) g_utils.logger.info(success_message) + +def crc8(message_list): + """ + Returns the calculated crc from a message list + @param message_list: is a list of integer numbers containing the message + @return:: integer containing an unsigned byte + """ + crc = 0 + for byte in message_list: + unsigned_byte = byte ^ crc + crc = CRC_LIST[unsigned_byte] + return crc Index: cloudsync/utils/reachability.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cloudsync/utils/reachability.py (.../reachability.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cloudsync/utils/reachability.py (.../reachability.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -4,6 +4,8 @@ from threading import Thread from time import sleep +from cloudsync.utils.globals import * + import requests REACHABILITY_URL = "https://kebormed.com" Index: cs.py =================================================================== diff -u -r3b80d8631090f72584ae2a309efcea2d20b4f62e -r4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47 --- cs.py (.../cs.py) (revision 3b80d8631090f72584ae2a309efcea2d20b4f62e) +++ cs.py (.../cs.py) (revision 4ebf654ab1b0d436ce7d171bfe06c6bdc46aca47) @@ -51,11 +51,11 @@ elif arguments[2] == "info": logging_level = logging.INFO elif arguments[2] == "warning": - logging_level = logging.WARRNING + logging_level = logging.WARNING elif arguments[2] == "error": logging_level = logging.ERROR else: - print("Usage: python cs.py [debug|info|warning|error]") + print("Usage: ./cs.py [debug|info|warning|error]") if arguments[1] == "start": start() elif str(arguments[1]) == "stop": @@ -72,6 +72,6 @@ else: print("CloudSync app IS NOT running") else: - print("Usage: python cs.py [debug|info|warning|error]") + print("Usage: ./cs.py [debug|info|warning|error]") else: - print("Usage: python cs.py [debug|info|warning|error]") + print("Usage: ./cs.py [debug|info|warning|error]")