Porting existing code - norwegianblues/norwegianblues GitHub Wiki
In this section, we'll take a look at the steps required to port a CoAP stack, more specifically CoAPy (Constrained Application Protocol in Python) to run inside Parrot.
A first observation is that CoAPy is nicely factored and have all code relating to message transport in class EndPoint
in the file connection.py
. That makes for a nice walkthrough of the porting effort starting from the beginning of the source file and commenting on the required changes in order of appearance.
N.B. This is provided as an example of the kind of changes required to run standard modules in Parrot, not as an example of best practice. A better way would probably be to subclass CoAPy and override EndPoint
– in fact even monkey-patching would be better than this approach.
CoAPy relies functionality in modules socket
, select
, and fcntl
. The intent of the parrot
module is to replace the socket
and select
modules, whereas there is no substitute for fcntl
in Parrot, which may or may not be a problem later on in the process.
Right now, just removing socket
, select
, and fcntl
from the list of imports, and adding parrot
is the right thing to do.
--- coapy/connection.py 2010-07-22 22:07:54.000000000 +0200
+++ coapy/connection.py 2012-10-17 12:18:46.000000000 +0200
@@ -33,10 +33,8 @@
import coapy.options
-import socket
import struct
import binascii
-import fcntl
import random
-import select
import time
import os
+import parrot
The CoAP end-points has a "private" socket instance named __socket
socket and a private poll object, see Python's select.poll
, name __poller
. Clearly __socket
should be assigned a Parrot socket instance and __poller
should be assigned an instance of a Parrot poll object.
The Parrot socket requires a reference to the embedding node, and therefore we must modify the CoAPy API slightly so that coapy.connection.EndPoint()
takes a node as parameter rather than address family, socket type, and socket protocol information.
Open Issue: It could be that parrot.Socket
should accept and require the address family, socket type, and socket protocol information.
--- coapy/connection.py 2010-07-22 22:07:54.000000000 +0200
+++ coapy/connection.py 2012-10-17 12:18:46.000000000 +0200
@@ -627,10 +606,7 @@
- def __init__ (self,
- address_family=socket.AF_INET,
- socket_type=socket.SOCK_DGRAM,
- socket_proto=socket.IPPROTO_UDP):
+ def __init__ (self, node):
self.__transactionId = random.randint(0, 65535)
--- coapy/connection.py 2010-07-22 22:07:54.000000000 +0200
+++ coapy/connection.py 2012-10-17 12:18:46.000000000 +0200
@@ -632,10 +608,7 @@
self.__transactionId = random.randint(0, 65535)
- self.__socket = socket.socket(address_family, socket_type, socket_proto)
+ self.__socket = parrot.Socket(node)
self.__filenoMap = { }
- self.__poller = select.poll()
+ self.__poller = parrot.poll()
self.register(self.__socket)
Well, this time we are lucky and the fcntl
correspond to the default settings for Parrot sockets, and we can thus just delete the line. This has to be judged on a case-by-case basis, and might at some point require some means to set Parrot socket behaviour parameters.
--- coapy/connection.py 2010-07-22 22:07:54.000000000 +0200
+++ coapy/connection.py 2012-10-17 12:18:46.000000000 +0200
@@ -659,3 +635,2 @@
self.__filenoMap[sfd.fileno()] = sfd
- fcntl.fcntl(sfd, fcntl.F_SETFL, os.O_NONBLOCK | fcntl.fcntl(sfd, fcntl.F_GETFL))
In a number of places, the use of module constants (select.POLLIN
, select.POLLOUT
) must be replaced by the corresponding constants in the parrot
module.
--- coapy/connection.py 2010-07-22 22:07:54.000000000 +0200
+++ coapy/connection.py 2012-10-17 12:18:46.000000000 +0200
@@ -661,3 +636,3 @@
self.__filenoMap[sfd.fileno()] = sfd
- self.__poller.register(sfd, select.POLLIN)
+ self.__poller.register(sfd, parrot.POLLIN)
@@ -768,5 +743,5 @@
- evt = select.POLLIN
+ evt = parrot.POLLIN
if transmit_due:
- evt += select.POLLOUT
+ evt += parrot.POLLOUT
poll_timeout_ms = 0
@@ -785,3 +760,3 @@
sock = self.__filenoMap.get(sfd)
- if evt & select.POLLOUT:
+ if evt & parrot.POLLOUT:
assert sock == self.__socket
@@ -798,3 +773,3 @@
raise
- if evt & select.POLLIN:
+ if evt & parrot.POLLIN:
(msg, remote) = sock.recvfrom(8192)
The function is_multicast
depends on the socket.getaddrinfo
method to determine if an address is multicast. That method is not yet implemented in the parrot
module and the ugly fix right now is to always return False
. Now you know it. YMMV.
--- coapy/connection.py 2010-07-22 22:07:54.000000000 +0200
+++ coapy/connection.py 2012-10-17 12:18:46.000000000 +0200
@@ -313,21 +311,2 @@
"""
-
- if isinstance(address, str):
- return False
- if not isinstance(address, tuple):
- raise ValueError()
- if 2 == len(address):
- family = socket.AF_INET
- (host, port) = address
- elif 4 == len(address):
- family = socket.AF_INET6
- (host, port, flowinfo, scopeid) = address
- else:
- raise ValueError()
- for (_, _, _, _, sockaddr) in socket.getaddrinfo(host, port, family):
- inaddr = socket.inet_pton(family, sockaddr[0])
- if socket.AF_INET == family:
- return 0xE0 == (0xF0 & ord(inaddr[0]))
- elif socket.AF_INET6 == family:
- return 0xFF == ord(inaddr[0])
return False
A config file and CoAP server and client nodes based on the examples accompanying CoAPy are provided as gists.
Running the simulation should render output similar to the one below. This test actually makes the client active before the server goes on line, to show the resilience of the CoAP protocol to that kind of situations. The NO MATCHING SERVER
-condition is reported by the backplane, noting the fact that no-one is responding on address 10.1.2.2
.
========================================
test_coap_1: Test a CoAP server/client.
========================================
[Core] Platform running.
NO MATCHING SERVER
Message: ['SENDTO', 'b55d2299-0cb8-4a12-9991-71f47f276781', '46707', '10.1.2.2', '61616', 'A\x01tQ\x9d.well-known/r']
Network: [urn:backplane:subnet:csma_nw] {u'Delay': u'2ms', u'IPv4Base': u'10.1.2. 0', u'type': u'CSMA', u'IPv4Mask': u'255.255.255.0', u'DataRate': u'5Mbps'}
Sender: urn:hodcp:node:client:eth0
-------------
No message received; waiting
No message received; waiting
Lookup Uri-Path: .well-known/r got <coap_server.ResourceService object at 0x10b699c90>
3 resources available at .well-known/r:
<.well-known/r>;ct=40
<counter>
<uptime>
Lookup Uri-Path: counter got <coap_server.CounterService object at 0x10b699cd0>
0
Lookup Uri-Path: uptime got <coap_server.UptimeService object at 0x10b699d10>
3.08074
Lookup Uri-Path: counter got <coap_server.CounterService object at 0x10b699cd0>
1
Lookup Uri-Path: uptime got <coap_server.UptimeService object at 0x10b699d10>
3.09504