Пример #1
0
	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
Пример #2
0
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()