Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement No Response and pass metadata to resource handlers #28

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion bin/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

try:
# retrieve value of 'test' resource
p = c.GET('coap://[{0}]/test'.format(SERVER_IP),
p,m = c.GET('coap://[{0}]/test'.format(SERVER_IP),
confirmable=True,
options=[objectSecurity])

Expand Down
2 changes: 1 addition & 1 deletion bin/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self):
path = 'test',
)

def GET(self,options=[]):
def GET(self,options=[],metaData={}):

print 'GET received'

Expand Down
6 changes: 3 additions & 3 deletions bin/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
c.respTimeout = 2 # sec

# get status of LED
p = c.GET('coap://[{0}]/l'.format(MOTE_IP),)
p,m = c.GET('coap://[{0}]/l'.format(MOTE_IP),)
print chr(p[0])

# toggle debug LED
p = c.PUT(
p,m = c.PUT(
'coap://[{0}]/l'.format(MOTE_IP),
payload = [ord('2')],
)

# read status of debug LED
p = c.GET('coap://[{0}]/l'.format(MOTE_IP))
p,m = c.GET('coap://[{0}]/l'.format(MOTE_IP))
print chr(p[0])

# close
Expand Down
72 changes: 46 additions & 26 deletions coap/coap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ def emit(self, record):
import coapTransmitter
from socketUdpDispatcher import socketUdpDispatcher
from socketUdpReal import socketUdpReal
from socketUdp import socketUdp as socketUdpAbstract

class coap(object):

def __init__(self,ipAddress='',udpPort=d.DEFAULT_UDP_PORT,testing=False,receiveCallback=None):
def __init__(self,ipAddress='',udpPort=d.DEFAULT_UDP_PORT,testing=False,socketUdp=None):

# store params
self.ipAddress = ipAddress
Expand All @@ -43,23 +44,19 @@ def __init__(self,ipAddress='',udpPort=d.DEFAULT_UDP_PORT,testing=False,receiveC
self.respTimeout = d.DFLT_RESPONSE_TIMEOUT
self.maxRetransmit = d.DFLT_MAX_RETRANSMIT
self.secContextHandler = None
if receiveCallback:
callback = receiveCallback
else:
callback = self._receive
if testing:
self.socketUdp = socketUdpDispatcher(
ipAddress = self.ipAddress,
udpPort = self.udpPort,
callback = callback,
)

if socketUdp is not None:
socketClass = socketUdp
elif testing:
socketClass = socketUdpDispatcher
else:
self.socketUdp = socketUdpReal(
ipAddress = self.ipAddress,
udpPort = self.udpPort,
callback = callback,
)
socketClass = socketUdpReal

self.socketUdp = socketClass(
ipAddress = self.ipAddress,
udpPort = self.udpPort,
callback = self._receive,
)
#======================== public ==========================================

def close(self):
Expand All @@ -76,7 +73,7 @@ def GET(self,uri,confirmable=True,options=[]):
options = options,
)
log.debug('response: {0}'.format(response))
return response['payload']
return response['payload'], response['metaData']

def PUT(self,uri,confirmable=True,options=[],payload=None):
response = self._transmit(
Expand All @@ -87,7 +84,7 @@ def PUT(self,uri,confirmable=True,options=[],payload=None):
payload = payload
)
log.debug('response: {0}'.format(response))
return response['payload']
return response['payload'], response['metaData']

def POST(self,uri,confirmable=True,options=[],payload=None):
response = self._transmit(
Expand All @@ -98,7 +95,7 @@ def POST(self,uri,confirmable=True,options=[],payload=None):
payload = payload
)
log.debug('response: {0}'.format(response))
return response['payload']
return response['payload'], response['metaData']

def DELETE(self,uri,confirmable=True,options=[]):
self._transmit(
Expand Down Expand Up @@ -239,10 +236,20 @@ def _receive(self,timestamp,sender,rawbytes):

srcPort = sender[1]

# meta data useful in testing mode
metaData = {}
metaData['srcIP'] = srcIp
metaData['srcPort'] = srcPort
i = 0
for var in sender[2]:
metaData['generic_{0}'.format(i)] = var
i += 1

# parse messages
try:
message = m.parseMessage(rawbytes)
options = message['options']
message['metaData'] = metaData
except e.messageFormatError as err:
log.warning('malformed message {0}: {1}'.format(u.formatBuf(rawbytes),str(err)))
return
Expand Down Expand Up @@ -326,21 +333,25 @@ def _receive(self,timestamp,sender,rawbytes):
try:
if message['code']==d.METHOD_GET and d.METHOD_GET in authorizedMethods:
(respCode,respOptions,respPayload) = resource.GET(
options=options
options=options,
metaData=metaData,
)
elif message['code']==d.METHOD_POST and d.METHOD_POST in authorizedMethods:
(respCode,respOptions,respPayload) = resource.POST(
options=options,
payload=payload
payload=payload,
metaData=metaData,
)
elif message['code']==d.METHOD_PUT and d.METHOD_PUT in authorizedMethods:
(respCode,respOptions,respPayload) = resource.PUT(
options=options,
payload=payload
payload=payload,
metaData=metaData,
)
elif message['code']==d.METHOD_DELETE and d.METHOD_DELETE in authorizedMethods:
(respCode,respOptions,respPayload) = resource.DELETE(
options=options
options=options,
metaData=metaData,
)
elif message['code'] not in d.METHOD_ALL:
raise SystemError('unexpected code {0}'.format(message['code']))
Expand Down Expand Up @@ -369,11 +380,20 @@ def _receive(self,timestamp,sender,rawbytes):
objectSecurity = o.ObjectSecurity(context=foundContext)
respOptions += [objectSecurity]

# if Stateless-Proxy option was present in the request echo it
# process special options
for option in options:
# if Stateless - Proxy option was present in the request echo it
if isinstance(option, o.StatelessProxy):
respOptions += [option]
break
# if No Response option was present in the request, don't send the response
if isinstance(option, o.NoResponse):
if option.getPayloadBytes() == [d.DFLT_OPTION_NORESPONSE_SUPRESS_ALL]:
# exit without returning any response
log.info("Suppressing a response due to the {0} option in the request.".format(option))
return
else:
# selective suppression not implemented for now
raise NotImplementedError()

# build response packets and pass partialIV from the request for OSCOAP's processing
response = m.buildMessage(
Expand Down Expand Up @@ -406,7 +426,7 @@ def _receive(self,timestamp,sender,rawbytes):
for (k,v) in self.transmitters.items():
# try matching
if (
msgkey[0]==k[0] and
u.ipv6AddrString2Bytes(msgkey[0])==u.ipv6AddrString2Bytes(k[0]) and
msgkey[1]==k[1] and
(
msgkey[2]==k[2] or
Expand Down
5 changes: 5 additions & 0 deletions coap/coapDefines.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
DFLT_EXCHANGE_LIFETIME = 248 # lifetime of a message ID
DFLT_RESPONSE_TIMEOUT = 60 # delay for app-level response

# Option value helpers
DFLT_OPTION_NORESPONSE_SUPRESS_ALL = 26 # RFC7967 value to supress all responses

# CoAP Message Types
TYPE_CON = 0
TYPE_NON = 1
Expand Down Expand Up @@ -115,6 +118,7 @@
OPTION_NUM_PROXYSCHEME = 39
OPTION_NUM_OBJECT_SECURITY = 21 # plugtest value
OPTION_NUM_STATELESSPROXY = 40 # experimental value
OPTION_NUM_NORESPONSE = 258 # RFC7967
OPTION_NUM_ALL = [
OPTION_NUM_IFMATCH,
OPTION_NUM_URIHOST,
Expand All @@ -134,6 +138,7 @@
OPTION_NUM_PROXYSCHEME,
OPTION_NUM_OBJECT_SECURITY,
OPTION_NUM_STATELESSPROXY,
OPTION_NUM_NORESPONSE,
]

# CoAP Content-Format Registry
Expand Down
5 changes: 5 additions & 0 deletions coap/coapException.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ def __str__(self):
class coapDelayedResponse(coapException):
pass

#============================ no response needed ==============================

class coapNoResponseExpected(coapException):
pass

#============================ timeout =========================================

class coapTimeout(coapException):
Expand Down
21 changes: 21 additions & 0 deletions coap/coapOption.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,25 @@ def __repr__(self):

def getPayloadBytes(self):
return self.opaqueValue

# === OPTION_NUM_NORESPONSE
class NoResponse(coapOption):
def __init__(self, value=[d.DFLT_OPTION_NORESPONSE_SUPRESS_ALL]):
# initialize parent
coapOption.__init__(self, d.OPTION_NUM_NORESPONSE, d.OSCOAP_CLASS_E)

# store params
if len(value) != 1:
raise e.messageFormatError

self.supressionValue = value

def __repr__(self):
return 'NoResponse(value={0})'.format(self.supressionValue)

def getPayloadBytes(self):
return self.supressionValue

#============================ functions =======================================

def parseOption(message,previousOptionNumber):
Expand Down Expand Up @@ -377,6 +396,8 @@ def parseOption(message,previousOptionNumber):
option = ProxyScheme(scheme=''.join([chr(b) for b in optionValue]))
elif optionNumber==d.OPTION_NUM_STATELESSPROXY:
option = StatelessProxy(value=optionValue)
elif optionNumber==d.OPTION_NUM_NORESPONSE:
option = NoResponse(value=optionValue)
else:
raise NotImplementedError('option {0} not implemented'.format(optionNumber))

Expand Down
8 changes: 4 additions & 4 deletions coap/coapResource.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ def __init__(self,path):

#======================== abstract methods ================================

def GET(self,options=[]):
def GET(self,options=[],metaData={}):
raise e.coapRcMethodNotAllowed()

def PUT(self,options=[],payload=None):
def PUT(self,options=[],payload=None,metaData={}):
raise e.coapRcMethodNotAllowed()

def POST(self,options=[],payload=None):
def POST(self,options=[],payload=None,metaData={}):
raise e.coapRcMethodNotAllowed()

def DELETE(self,options=[]):
def DELETE(self,options=[],metaData={}):
raise e.coapRcMethodNotAllowed()

#======================== public ==========================================
Expand Down
34 changes: 31 additions & 3 deletions coap/coapTransmitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def emit(self, record):
import coapException as e
import coapUtils as u
import coapMessage as m
import coapOption as o

class coapTransmitter(threading.Thread):
'''
Expand All @@ -36,6 +37,7 @@ class coapTransmitter(threading.Thread):
STATE_WAITFOREXPIRATIONMID = 'WAITFOREXPIRATIONMID'
STATE_WAITFORRESP = 'WAITFORRESP'
STATE_RESPRX = 'RESPRX'
STATE_NORESPEXPECTED = 'NORESPEXPECTED'
STATE_TXACK = 'TXACK'
STATE_ALL = [
STATE_INIT,
Expand Down Expand Up @@ -133,6 +135,7 @@ def __init__(self,sendFunc,srcIp,srcPort,destIp,destPort,confirmable,messageId,c
self.STATE_WAITFORRESP: self._action_WAITFORRESP,
self.STATE_RESPRX: self._action_RESPRX,
self.STATE_TXACK: self._action_TXACK,
self.STATE_NORESPEXPECTED: self._action_NORESPEXPECTED,
}

# initialize parent
Expand Down Expand Up @@ -197,7 +200,7 @@ def getState(self):
return self.state

def receiveMessage(self, timestamp, srcIp, srcPort, message):
assert srcIp==self.destIp
assert u.ipv6AddrString2Bytes(srcIp)==u.ipv6AddrString2Bytes(self.destIp)
assert srcPort==self.destPort
assert (message['token']==self.token) or (message['messageId']==self.messageId)

Expand Down Expand Up @@ -331,8 +334,21 @@ def _action_TXNON(self):
msg = message,
)

# update FSM state
self._setState(self.STATE_WAITFORRESP)
noResponse = False
for option in self.options:
if isinstance(option, o.NoResponse):
if option.getPayloadBytes() == [d.DFLT_OPTION_NORESPONSE_SUPRESS_ALL]:
# do not expect a response
noResponse = True
else:
# selective suppression not implemented for now
raise NotImplementedError()
# in case no response is expected, do not hang waiting for the timeout
if not noResponse:
# update FSM state
self._setState(self.STATE_WAITFORRESP)
else:
self._setState(self.STATE_NORESPEXPECTED)

# kick FSM
self._kickFsm()
Expand Down Expand Up @@ -473,6 +489,18 @@ def _action_RESPRX(self):
# kick FSM
self._kickFsm()

def _action_NORESPEXPECTED(self):

# log
log.debug('_action_NORESPEXPECTED()')

# successful end of FSM
with self.dataLock:
self.coapError = e.coapNoResponseExpected('No Response is expected for this request')

# kick FSM
self._kickFsm()

def _action_TXACK(self):

# log
Expand Down
2 changes: 1 addition & 1 deletion coap/socketUdpDispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def sendUdp(self,destIp,destPort,msg):
# send over dispatcher
dispatcher.send(
signal = (destIp,destPort),
sender = (self.ipAddress,self.udpPort),
sender = (self.ipAddress,self.udpPort, ()),
data = msg
)

Expand Down
2 changes: 1 addition & 1 deletion coap/socketUdpReal.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def run(self):
continue

timestamp = time.time()
source = (conn[0],conn[1])
source = (conn[0],conn[1],())
data = [ord(b) for b in raw]

log.debug("got {2} from {1} at {0}".format(timestamp,source,data))
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def __init__(self):

#======================== parent methods ==================================

def GET(self,options=[]):
def GET(self,options=[],metaData={}):
log.debug('dummyResource GET')

respCode = d.COAP_RC_2_05_CONTENT
Expand Down
Loading