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 scan of the source code

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.

Module imports

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

EndPoint changes

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)

What to do about fcntl calls?

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))

Constants namespace changes

In a number of places, the use of module constants (select.POLLIN, select.POLLOUT) must be replaced by the corresponding constants in the parrotmodule.

--- 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)

A final ugly change

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

Testing the CoAP stack

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
⚠️ **GitHub.com Fallback** ⚠️