def __init__(self, mcl): self.parser = MessageParser() self.request_in_flight = 0 self.mcl = mcl self.csp = CSPStateMachine(self, self.mcl) self.pending_active_mdl = None self.pending_passive_mdl = None
class MCLStateMachine: def __init__(self, mcl): self.parser = MessageParser() self.request_in_flight = 0 self.mcl = mcl self.csp = CSPStateMachine(self, self.mcl) self.pending_active_mdl = None self.pending_passive_mdl = None ## SEND METHODS def send_raw_message(self, message): ''' For testing purposes only: sends an arbitrary stream of bytes via MCL control channel ''' if self.request_in_flight: raise InvalidOperation('Still waiting for response') self.request_in_flight = ord(message[0]) # Hack to keep a copy of last request try: request = None if self.request_in_flight % 2: request = self.parser.parse(message) self.last_request = request except InvalidMessage: self.last_request = None ok = self.mcl.write(message) if not ok: print "CANNOT WRITE: " + str(msg) return ok def send_message(self, message): if (message.opcode % 2) != 0: return self.send_request(message) else: return self.send_response(message) def send_request(self, request): if self.mcl.state in (MCAP_MCL_STATE_IDLE, MCAP_MCL_STATE_WAITING): raise InvalidOperation('MCL in idle state') if self.request_in_flight: raise InvalidOperation('Still waiting for response') opcode = request.opcode if self.csp.is_mine(opcode): # not our problem return self.csp.send_request(request) if self.mcl.state == MCAP_MCL_STATE_PENDING and \ type(request) is not AbortMDLRequest: raise InvalidOperation('MCL in PENDING state') self.request_in_flight = opcode self.last_request = request return self.send_mcap_command(request) def send_response(self, response): success = self.send_mcap_command(response) return success def send_mcap_command(self, message): self.last_sent = message return self.mcl.write(message.encode()) ## RECEIVE METHODS def receive_message(self, message): self.last_received = message try: opcode, rspcode = self.parser.get_opcode(message) if self.csp.is_mine(opcode): # shunt processing to CSP machine return self.csp.receive_message(opcode, message) if (opcode % 2): return self.receive_request(opcode, message) else: return self.receive_response(opcode, message) except InvalidMessage: # we assume that higher-level errors are caught by # receive_request/response methods errorResponse = ErrorMDLResponse() return self.send_response(errorResponse) def receive_request(self, opcode, request): # if a request is received when a response is expected, only process if # it is received by the Acceptor; otherwise, just ignore if self.request_in_flight: if (self.mcl.role == MCAP_MCL_ROLE_INITIATOR): return False else: return self.process_request(opcode, request) else: return self.process_request(opcode, request) def receive_response(self, opcode, response): # if a response is received when no request is outstanding, just ignore if self.request_in_flight: return self.process_response(opcode, response) else: return False ## PROCESS RESPONSE METHODS def process_response(self, opcode, response): expected = self.request_in_flight + 1 if not self.request_in_flight: expected = -1 self.request_in_flight = 0 responseMessage = self.parser.parse(response) if not expected or opcode != expected: print "Expected response for %d, got %d" % \ (expected, opcode) return self.pending_active_mdl = None # TODO make this look more like a state machine if opcode == MCAP_MD_CREATE_MDL_RSP: return self.process_create_response(responseMessage) elif opcode == MCAP_MD_RECONNECT_MDL_RSP: return self.process_reconnect_response(responseMessage) elif opcode == MCAP_MD_DELETE_MDL_RSP: return self.process_delete_response(responseMessage) elif opcode == MCAP_MD_ABORT_MDL_RSP: return self.process_abort_response(responseMessage) elif opcode == MCAP_ERROR_RSP: self.print_error_message(responseMessage.rspcode) else: raise InvalidOperation("Should not happen") def process_create_response(self, response, reconn=False): if response.rspcode == MCAP_RSP_SUCCESS: mdlid = response.mdlid if reconn: mdl = self.mcl.get_mdl(response.mdlid) if not mdl: print "Reconn resp to unknown MDLID" schedule(self.mcl.observer.mdlgranted_mcl, self.mcl, None, -1) return else: config = response.config reliable = self.last_request.reliable if self.last_request.mdlid != mdlid: print "Conn resp of different MDLID" schedule(self.mcl.observer.mdlgranted_mcl, self.mcl, None, -2) return if self.last_request.config and \ config != self.last_request.config: print "Conn resp of different config" schedule(self.mcl.observer.mdlgranted_mcl, self.mcl, None, -3) return self.mcl.delete_mdl(response.mdlid) mdl = MDL(self.mcl, mdlid, self.last_request.mdepid, config, reliable) self.mcl.add_mdl(mdl, reconn) self.pending_active_mdl = mdl self.reconn = reconn self.mcl.state = MCAP_MCL_STATE_PENDING schedule(self.mcl.observer.mdlgranted_mcl, self.mcl, mdl, 0) else: if self.mcl.has_mdls(): self.mcl.state = MCAP_MCL_STATE_ACTIVE else: self.mcl.state = MCAP_MCL_STATE_CONNECTED self.print_error_message(response.rspcode) # notify application about the error schedule(self.mcl.observer.mdlgranted_mcl, self.mcl, None, response.rspcode) return True def process_reconnect_response(self, response): return self.process_create_response(response, True) def process_delete_response(self, response): if response.rspcode == MCAP_RSP_SUCCESS: self.mcl.delete_mdl(response.mdlid) if not self.mcl.has_mdls(): self.mcl.state = MCAP_MCL_STATE_CONNECTED else: self.mcl.state = MCAP_MCL_STATE_ACTIVE else: self.print_error_message(response.rspcode) return True def process_abort_response(self, response): if response.rspcode == MCAP_RSP_SUCCESS: if not self.mcl.has_mdls(): self.mcl.state = MCAP_MCL_STATE_CONNECTED else: self.mcl.state = MCAP_MCL_STATE_ACTIVE if self.contains_mdlid(response.mdlid): mdl = self.mcl.get_mdl(response.mdlid) schedule(self.mcl.observer.mdlaborted_mcl, self.mcl, mdl) self.mcl.delete_mdl_if_no_reconn(mdl) else: self.print_error_message(response.rspcode) return True def mdl_crossing(self): psv = self.pending_passive_mdl act = self.pending_active_mdl return psv is not None and act is not None and \ psv.mdlid == act.mdlid def incoming_mdl_socket(self, sk): # Called by DPSM listener ok = self.mcl.state == MCAP_MCL_STATE_PENDING \ and self.pending_passive_mdl \ and not self.mdl_crossing() mdl = self.pending_passive_mdl self.pending_passive_mdl = None if ok: self.mcl.state = MCAP_MCL_STATE_ACTIVE mdl.accept(sk) watch_fd_err(sk, self.mdl_socket_error, mdl) schedule(self.mcl.observer.mdlconnected_mcl, mdl, self.reconn, 0) else: try: sk.shutdown(2) except IOError: pass sk.close() return ok def connected_mdl_socket(self, mdl, err): # Called by MDL object itself if err: self.pending_active_mdl = None schedule(self.mcl.observer.mdlconnected_mcl, mdl, self.reconn, err) return False ok = self.mcl.state == MCAP_MCL_STATE_PENDING ok = ok and self.pending_active_mdl.mdlid == mdl.mdlid self.pending_active_mdl = None if ok: self.mcl.state = MCAP_MCL_STATE_ACTIVE watch_fd_err(mdl.sk, self.mdl_socket_error, mdl) schedule(self.mcl.observer.mdlconnected_mcl, mdl, self.reconn, 0) else: schedule(self.mcl.observer.mdlconnected_mcl, mdl, self.reconn, -1) # MDL is responsible by closing socket if not ok return ok def mdl_socket_error(self, sk, event, mdl): mdl.close() return False def closed_mdl(self, mdl): ''' called back by MDL itself ''' schedule(self.mcl.observer.mdlclosed_mcl, mdl) self.mcl.delete_mdl_if_no_reconn(mdl) ## PROCESS REQUEST METHODS def process_request(self, opcode, request): try: # TODO improve this, making it more like a state machine request = self.parser.parse(request) if opcode == MCAP_MD_CREATE_MDL_REQ: return self.process_create_request(request) elif opcode == MCAP_MD_RECONNECT_MDL_REQ: return self.process_reconnect_request(request) elif opcode == MCAP_MD_DELETE_MDL_REQ: return self.process_delete_request(request) elif opcode == MCAP_MD_ABORT_MDL_REQ: return self.process_abort_request(request) else: raise InvalidOperation("Should not happen") except InvalidMessage: opcodeRsp = opcode + 1 rsp = MDLResponse(opcodeRsp, MCAP_RSP_INVALID_PARAMETER_VALUE, 0x0000) return self.send_response(rsp) def process_create_request(self, request, reconn=False): rspcode = MCAP_RSP_SUCCESS mdlid = request.mdlid config = 0x00 mdl = None reliable = True if reconn and not self.mcl.observer.reconn_enabled: rspcode = MCAP_RSP_REQUEST_NOT_SUPPORTED elif not self.is_valid_mdlid(request.mdlid, False): rspcode = MCAP_RSP_INVALID_MDL elif self.mcl.state == MCAP_MCL_STATE_PENDING: print "Pending MDL connection", rspcode = MCAP_RSP_INVALID_OPERATION else: if reconn: mdl = self.mcl.get_mdl(mdlid) if mdl: reliable = mdl.reliable else: rspcode = MCAP_RSP_INVALID_MDL else: if not self.support_more_mdls(): rspcode = MCAP_RSP_MDL_BUSY elif not self.is_valid_mdepid(request.mdepid): rspcode = MCAP_RSP_INVALID_MDEP elif not self.support_more_mdeps(): rspcode = MCAP_RSP_MDEP_BUSY else: ok, reliable, config = \ self.inquire_mdep(request.mdepid, request.config) if not ok: rspcode = MCAP_RSP_CONFIGURATION_REJECTED if rspcode != MCAP_RSP_SUCCESS: self.print_error_message(rspcode) if reconn: response = ReconnectMDLResponse(rspcode, mdlid) else: response = CreateMDLResponse(rspcode, mdlid, config) success = self.send_response(response) if success and (rspcode == MCAP_RSP_SUCCESS): if not reconn: mdl = self.mcl.get_mdl(mdlid) if mdl: self.mcl.delete_mdl(mdl) mdl = MDL(self.mcl, mdlid, request.mdepid, request.config, reliable) self.mcl.add_mdl(mdl, reconn) self.pending_passive_mdl = mdl self.reconn = reconn self.mcl.state = MCAP_MCL_STATE_PENDING if reconn: schedule(self.mcl.observer.mdlreconn_mcl, self.mcl, mdl) else: schedule(self.mcl.observer.mdlrequested_mcl, self.mcl, mdl, request.mdepid, config) return success def process_reconnect_request(self, request): return self.process_create_request(request, True) def process_delete_request(self, request): rspcode = MCAP_RSP_SUCCESS mdlid = request.mdlid if not self.is_valid_mdlid(mdlid, True): rspcode = MCAP_RSP_INVALID_MDL elif mdlid != MCAP_MDL_ID_ALL and \ not self.contains_mdlid(mdlid): rspcode = MCAP_RSP_INVALID_MDL elif self.mcl.state == MCAP_MCL_STATE_PENDING: print "Pending MDL connection" rspcode = MCAP_RSP_INVALID_OPERATION deleteResponse = DeleteMDLResponse(rspcode, mdlid) success = self.send_response(deleteResponse) if success and (rspcode == MCAP_RSP_SUCCESS): self.pending_passive_mdl = None self.mcl.delete_mdl(mdlid) if not self.mcl.has_mdls(): self.mcl.state = MCAP_MCL_STATE_CONNECTED def process_abort_request(self, request): rspcode = MCAP_RSP_SUCCESS if not self.is_valid_mdlid(request.mdlid, False): rspcode = MCAP_RSP_INVALID_MDL abortResponse = AbortMDLResponse(rspcode, request.mdlid) success = self.send_response(abortResponse) if success and (rspcode == MCAP_RSP_SUCCESS): self.pending_passive_mdl = None if self.mcl.has_mdls(): self.mcl.state = MCAP_MCL_STATE_ACTIVE else: self.mcl.state = MCAP_MCL_STATE_CONNECTED mdl = self.mcl.get_mdl(request.mdlid) schedule(self.mcl.observer.mdlaborted_mcl, self.mcl, mdl) self.mcl.delete_mdl_if_no_reconn(mdl) ## UTILITY METHODS def contains_mdlid(self, mdlid): return self.mcl.contains_mdl(mdlid) def is_mdlid_all(self, mdlid): return mdlid == MCAP_MDL_ID_ALL def is_valid_mdlid(self, mdlid, accept_all): # has 16 bits if (mdlid & 0x0000) != 0: return False if (mdlid == MCAP_MDL_ID_ALL) and accept_all: return True if mdlid < MCAP_MDL_ID_INITIAL or mdlid > MCAP_MDL_ID_FINAL: return False return True def support_more_mdls(self): # TODO return True def is_valid_mdepid(self, mdepid): # has 8 bits if (mdepid & 0x00) != 0: return False if mdepid < MCAP_MDEP_ID_INITIAL or mdepid > MCAP_MDEP_ID_FINAL: return False return True def support_more_mdeps(self): return True def inquire_mdep(self, mdepid, config): ok, reliable, accepted_config = \ self.mcl.observer.mdlinquire_mcl(mdepid, config) if config and config != accepted_config: print "ERROR: app returned different configuration" ok = False if not accepted_config: print "ERROR: app returned null configuration" ok = False return ok, reliable, accepted_config def print_error_message(self, error_rsp_code): if error_rsp_code in error_rsp_messages: print error_rsp_messages[error_rsp_code] else: print "Unknown error rsp code %d" % error_rsp_code def get_timestamp(self): if not self.mcl.observer.csp_enabled: raise InvalidOperation("CSP not enabled") return self.csp.get_timestamp() def get_btclock(self): if not self.mcl.observer.csp_enabled: raise InvalidOperation("CSP not enabled") return self.csp.get_btclock() def stop(self): self.csp.stop()