def __init__(self, msgSocket=None, port=soscoap.COAP_PORT): '''Pass in msgSocket only for unit testing. Pass in port for non-standard CoAP port. ''' self._msgSocket = msgSocket if msgSocket else MessageSocket(port) self._msgSocket.registerForReceive(self._handleMessage) self._resourceGetHook = EventHook() self._resourcePutHook = EventHook() self._resourcePostHook = EventHook() # A random start is recommended in Sec. 4.4. self._nextMessageId = random.randint(0, 0xFFFF)
def __init__(self, msgSocket=None, sourcePort=soscoap.COAP_PORT, dest=None): '''Client initialization, espeically for networking. :param msgSocket: MessageSocket Pass in only for unit testing :param sourcePort: int Port for source socket :param dest: tuple 2-tuple (string,int) for destination host address and port ''' destTuple = None if dest: info = socket.getaddrinfo(dest[0], dest[1], socket.AF_INET6, socket.SOCK_DGRAM) log.debug('getaddrinfo: {0}'.format(info)) # Assume we want the first 5-tuple entry returned destTuple = info[0][4] if msgSocket: self._msgSocket = msgSocket else: self._msgSocket = MessageSocket(localPort=sourcePort, remote=destTuple) self._msgSocket.registerForReceive(self._handleMessage) self._responseHook = EventHook() # A random start is recommended in Sec. 4.4. self._nextMessageId = random.randint(0, 0xFFFF)
class CoapServer(object): '''Server for CoAP requests. Requires another entity to define its use by registering event handlers. Events: Register a handler for an event via the 'registerFor<Event>' method. A handler may raise an IgnoreRequestException to silently ignore a request. :ResourceGet: Server requests the value for the provided resource, to service a client GET request. :ResourcePut: Server forwards the value for the provided resource, to service a client PUT request. :ResourcePost: Server forwards the value for the provided resource, to service a client POST request. Usage: #. cs = CoapServer() -- Create instance #. Register event handlers as needed; for example, cs.registerForResourceGet(). #. cs.start() -- Start to listen for requests Attributes: :_msgSocket: MessageSocket to send/receive messages :_resourceGetHook: EventHook triggered when GET resource requested :_resourcePutHook: EventHook triggered when PUT resource requested :_resourcePostHook: EventHook triggered when POST resource requested :_nextMessageId: Next sequential value for a new Message ID .. automethod:: soscoap.server.CoapServer.__init__ ''' def __init__(self, msgSocket=None, port=soscoap.COAP_PORT): '''Pass in msgSocket only for unit testing. Pass in port for non-standard CoAP port. ''' self._msgSocket = msgSocket if msgSocket else MessageSocket(port) self._msgSocket.registerForReceive(self._handleMessage) self._resourceGetHook = EventHook() self._resourcePutHook = EventHook() self._resourcePostHook = EventHook() # A random start is recommended in Sec. 4.4. self._nextMessageId = random.randint(0, 0xFFFF) def registerForResourceGet(self, handler): self._resourceGetHook.register(handler) def registerForResourcePost(self, handler): self._resourcePostHook.register(handler) def registerForResourcePut(self, handler): self._resourcePutHook.register(handler) def _handleMessage(self, message): resource = None try: resource = SosResourceTransfer(message.absolutePath(), sourceAddress=message.address) # only reads the first query segment queryList = message.findOption(OptionType.UriQuery) resource.pathQuery = queryList[0].value if len(queryList) else None if message.codeDetail == RequestCode.GET: log.debug('Handling resource GET request...') # Retrieve requested resource via event, and send reply self._resourceGetHook.trigger(resource) self._sendGetReply(message, resource) elif message.codeDetail == RequestCode.PUT: log.debug('Handling resource PUT request...') resource.value = message.typedPayload() self._resourcePutHook.trigger(resource) self._sendPutReply(message, resource) elif message.codeDetail == RequestCode.POST: log.debug('Handling resource POST request...') resource.value = message.typedPayload() self._resourcePostHook.trigger(resource) self._sendPostReply(message, resource) except IgnoreRequestException: log.info('Ignoring request') except: log.exception('Error handling message; will send error reply') self._sendErrorReply(message, resource) def _createReplyTemplate(self, request, resource): '''Creates a reply message with common code for any reply :returns: CoapMessage Created reply ''' msg = CoapMessage() msg.address = request.address msg.tokenLength = request.tokenLength msg.token = request.token msg.codeClass = resource.resultClass msg.codeDetail = resource.resultCode if request.messageType == MessageType.CON: msg.messageType = MessageType.ACK msg.messageId = request.messageId elif request.messageType == MessageType.NON: msg.messageType = MessageType.NON msg.messageId = self._popMessageId() else: log.error('Sending Reset due to unexpected messageType: {0}', msg.messageType) msg.messageType = MessageType.RST msg.messageId = request.messageId return msg def _sendGetReply(self, request, resource): '''Sends a reply to a GET request with the content for the provided resource. :param request: CoapMessage ''' if not resource.resultClass: resource.resultClass = CodeClass.Success resource.resultCode = SuccessResponseCode.Content msg = self._createReplyTemplate(request, resource) msg.payload = bytearray(resource.value, soscoap.BYTESTR_ENCODING) \ if resource.type == 'string' \ else resource.value # Only add option for a string, and assume text-plain format. if resource.type == 'string': msg.addOption( CoapOption(OptionType.ContentFormat, MediaType.TextPlain) ) self._msgSocket.send(msg) def _sendPostReply(self, request, resource): '''Sends a reply to a POST request confirming the changes. :param request: CoapMessage ''' if not resource.resultClass: resource.resultClass = CodeClass.Success resource.resultCode = SuccessResponseCode.Changed msg = self._createReplyTemplate(request, resource) self._msgSocket.send(msg) def _sendPutReply(self, request, resource): '''Sends a reply to a PUT request confirming the changes. :param request: CoapMessage ''' if not resource.resultClass: resource.resultClass = CodeClass.Success resource.resultCode = SuccessResponseCode.Changed msg = self._createReplyTemplate(request, resource) self._msgSocket.send(msg) def _sendErrorReply(self, request, resource): '''Sends a reply when an error has occurred in processing. :param request: CoapMessage :param resource: SosResourceTransfer ''' msg = self._createReplyTemplate(request, resource) msg.codeClass = CodeClass.ServerError msg.codeDetail = ServerResponseCode.InternalServerError self._msgSocket.send(msg) def _popMessageId(self): '''Returns the next sequential message ID, and increments''' nextid = self._nextMessageId self._nextMessageId = (self._nextMessageId+1 if self._nextMessageId < 0xFFFF else 1) return nextid def start(self): log.info('Starting asyncore loop') asyncore.loop()
class CoapClient(object): '''Client for CoAP requests. Like a CoAP server, binds to a socket, usually on the standard CoAP port. However, only accepts incoming responses when there is an outstanding request. Events: Register a handler for an event via the 'registerFor<Event>' method. :ResourceGet: Client has received a response for a resource GET request. Usage: #. cc = CoapClient() -- Create instance, using the standard CoAP port. #. cc.start() -- Start networking. *. ... -- Send messages (TBD) *. cc.close() -- Cleanup Attributes: :_msgSocket: MessageSocket to send/receive messages :_responseHook: EventHook triggered when resource response received :_nextMessageId: Next sequential value for a new Message ID .. automethod:: soscoap.server.CoapClient.__init__ ''' def __init__(self, msgSocket=None, sourcePort=soscoap.COAP_PORT, dest=None): '''Client initialization, espeically for networking. :param msgSocket: MessageSocket Pass in only for unit testing :param sourcePort: int Port for source socket :param dest: tuple 2-tuple (string,int) for destination host address and port ''' destTuple = None if dest: info = socket.getaddrinfo(dest[0], dest[1], socket.AF_INET6, socket.SOCK_DGRAM) log.debug('getaddrinfo: {0}'.format(info)) # Assume we want the first 5-tuple entry returned destTuple = info[0][4] if msgSocket: self._msgSocket = msgSocket else: self._msgSocket = MessageSocket(localPort=sourcePort, remote=destTuple) self._msgSocket.registerForReceive(self._handleMessage) self._responseHook = EventHook() # A random start is recommended in Sec. 4.4. self._nextMessageId = random.randint(0, 0xFFFF) def close(self): '''Releases system resources''' self._msgSocket.close() def registerForResponse(self, handler): self._responseHook.register(handler) def send(self, message): '''Send a message''' self._msgSocket.send(message) def _handleMessage(self, message): try: log.debug('Handling resource response...') self._responseHook.trigger(message) except: log.exception('Error handling response') def _popMessageId(self): '''Returns the next sequential message ID, and increments''' nextid = self._nextMessageId self._nextMessageId = (self._nextMessageId+1 if self._nextMessageId < 0xFFFF else 1) return nextid def start(self): '''Start networking, with a one second timeout so client doesn't wait to send.''' log.info('Starting asyncore loop') asyncore.loop(1)