Skip to content

Commit 6535b36

Browse files
committed
Unit tests for SR and SR1, added hash functions for the CoAP packet.
1 parent f6b86b5 commit 6535b36

File tree

3 files changed

+89
-33
lines changed

3 files changed

+89
-33
lines changed

scapy/contrib/coap.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ def post_dissect(self, pay):
278278
self.content_format = k[1]
279279
return pay
280280

281+
def hashret(self):
282+
return struct.pack('I', self.msg_id) + self.token
283+
284+
def answers(self, other):
285+
return True
286+
281287

282288
bind_layers(UDP, CoAP, sport=5683)
283289
bind_layers(UDP, CoAP, dport=5683)

scapy/contrib/coap_socket.py

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from scapy.data import MTU
3131
from scapy.utils import EDecimal
3232
from scapy.automaton import ObjectPipe, select_objects
33-
33+
from scapy.layers.inet import UDP, IP
3434
from scapy.supersocket import SuperSocket, SimpleSocket
3535

3636
log_coap_sock = logging.getLogger("scapy.contrib.coap_socket")
@@ -123,8 +123,8 @@ def recv_raw(self, x=0xffff):
123123
if not self.closed:
124124
tup = self.impl.recv()
125125
if tup is not None:
126-
return self.basecls, tup[0], float(tup[1])
127-
return self.basecls, None, None
126+
return IP, tup[0], float(tup[1])
127+
return IP, None, None
128128

129129
def recv(self, x=MTU, **kwargs):
130130
# type: (int, **Any) -> Optional[Packet]
@@ -152,6 +152,29 @@ def send(self, x):
152152
self.impl.send(x.dst, x.dport, x[CoAP])
153153
return len(x)
154154

155+
def sr(self, *args, **kargs):
156+
args[0].sport = self.impl.port
157+
return super(CoAPSocket, self).sr(*args, **kargs)
158+
159+
def sr1(self, *args, **kargs):
160+
args[0].sport = self.impl.port
161+
return super(CoAPSocket, self).sr1(*args, **kargs)
162+
163+
@staticmethod
164+
def select(sockets, remain=None):
165+
# type: (list[SuperSocket], Optional[float]) -> list[SuperSocket]
166+
"""
167+
This function is called during sendrecv() routine to wait for
168+
sockets to be ready to receive.
169+
"""
170+
obj_pipes = [x.impl.rx_queue for x in sockets if
171+
isinstance(x, CoAPSocket) and not x.closed]
172+
173+
ready_pipes = select_objects(obj_pipes, 0)
174+
175+
return [x for x in sockets if isinstance(x, CoAPSocket) and
176+
not x.closed and x.impl.rx_queue in ready_pipes]
177+
155178
@staticmethod
156179
def make_coap_req_packet(method=GET, uri="", options=None, payload=b""):
157180
# type: (int, str, list[tuple], bytes) -> Packet
@@ -572,7 +595,7 @@ def _recv(self):
572595

573596
if self.sock.select([self.sock], 0):
574597
pkt, sa_ll = self.sock.ins.recvfrom(MTU)
575-
pkt = CoAP(bytes(pkt))
598+
pkt = IP(src=sa_ll[0], dst=self.ip) / UDP(sport=sa_ll[1], dport=self.port) / CoAP(bytes(pkt))
576599
if pkt:
577600
if not self._debug_drop_package():
578601
self._on_pkt_recv(pkt, sa_ll)
@@ -629,15 +652,16 @@ def _delete(self, resource):
629652
def _handle_rcv_request(self, pkt, sa_ll):
630653
# type: (CoAP, tuple[str, int]) -> None
631654
"""Process a received request"""
655+
coap_pkt = pkt[CoAP]
632656
req_uri = "/"
633-
token = int.from_bytes(pkt.token, "big") # Can be up to 8 bytes
634-
message_id = pkt.msg_id
657+
token = int.from_bytes(coap_pkt.token, "big") # Can be up to 8 bytes
658+
message_id = coap_pkt.msg_id
635659
lst_options = []
636660
response = {"type": ACK, "code": NOT_FOUND_404,
637661
"options": [(CONTENT_FORMAT, CF_TEXT_PLAIN)],
638662
"payload": coap_codes[NOT_FOUND_404].encode("utf8")}
639663

640-
for option in pkt.options:
664+
for option in coap_pkt.options:
641665
option_type_id = coap_options[1].get(option[0], -1)
642666
option_value = option[1]
643667

@@ -658,14 +682,14 @@ def _handle_rcv_request(self, pkt, sa_ll):
658682
resource = self.resources.get(req_uri, None)
659683
if resource is not None:
660684
if not resource.check_duplicated(message_id, token):
661-
if pkt.code == GET:
662-
response = resource.get(pkt.payload, lst_options, token, sa_ll)
663-
elif pkt.code == POST:
685+
if coap_pkt.code == GET:
686+
response = resource.get(coap_pkt.payload, lst_options, token, sa_ll)
687+
elif coap_pkt.code == POST:
664688
# @todo: handle existing resource POST: RFC 7252 @ section-5.8.2
665689
pass
666-
elif pkt.code == PUT:
667-
response = resource.put(pkt.payload, lst_options, token, sa_ll)
668-
elif pkt.code == DELETE:
690+
elif coap_pkt.code == PUT:
691+
response = resource.put(coap_pkt.payload, lst_options, token, sa_ll)
692+
elif coap_pkt.code == DELETE:
669693
response = self._delete(resource)
670694

671695
resource._register_request_response(message_id, token, response)
@@ -677,16 +701,16 @@ def _handle_rcv_request(self, pkt, sa_ll):
677701
req_uri,
678702
message_id, token)
679703
else:
680-
if pkt.code == POST:
704+
if coap_pkt.code == POST:
681705
response = self._post()
682706
else:
683707
log_coap_sock.warning("Unknown resource: URI=%s", req_uri)
684708

685-
response["tkl"] = pkt.tkl
686-
response["token"] = pkt.token
709+
response["tkl"] = coap_pkt.tkl
710+
response["token"] = coap_pkt.token
687711
response["msg_id"] = message_id
688712

689-
if pkt.type == NON:
713+
if coap_pkt.type == NON:
690714
response["type"] = NON
691715

692716
# Add paymark (separator between options and payload)
@@ -751,13 +775,14 @@ def _handle_request_response(self, pkt, sa_ll):
751775
Handles a received response. Will check if there is the valid request.
752776
Otherwise, it will put in the rx_queue for the user to handle it
753777
via the recv() function.
754-
:param pkt: The CoAP packet to be processed
778+
:param coap_pkt: The CoAP packet to be processed
755779
:param sa_ll: The ip/port tuple of the sender
756780
"""
757-
token = int.from_bytes(pkt.token, "big")
758-
index = (pkt.msg_id, token)
781+
coap_pkt = pkt[CoAP]
782+
token = int.from_bytes(coap_pkt.token, "big")
783+
index = (coap_pkt.msg_id, token)
759784
request = self.pending_requests.get(index, None)
760-
if request is None and (pkt.type == ACK or pkt.type == CON or pkt.type == NON):
785+
if request is None and (coap_pkt.type == ACK or coap_pkt.type == CON or coap_pkt.type == NON):
761786
for key in self.pending_requests.keys():
762787
if index[0] == key[0] or index[1] == key[1]:
763788
log_coap_sock.info("Found request by using %s",
@@ -770,38 +795,40 @@ def _handle_request_response(self, pkt, sa_ll):
770795
if request is None:
771796
log_coap_sock.warning(
772797
"Request for received response not found: msg_id=%s; token=0x%x",
773-
pkt.msg_id, token)
798+
coap_pkt.msg_id, token)
774799
return
775800

776-
if pkt.type == ACK and pkt.code != EMPTY_MESSAGE:
801+
if coap_pkt.type == ACK and coap_pkt.code != EMPTY_MESSAGE:
777802
log_coap_sock.debug("Request fulfilled: msg_id=%s; token=0x%x; code=%s",
778803
index[0], index[1],
779-
coap_codes[pkt.code])
804+
coap_codes[coap_pkt.code])
805+
pkt.sport = self.pending_requests[index].port
780806
del self.pending_requests[index]
781-
self.rx_queue.send((pkt.build(), pkt.time))
782-
elif pkt.type == ACK and pkt.code == EMPTY_MESSAGE:
807+
self.rx_queue.send((pkt.build(), coap_pkt.time))
808+
elif coap_pkt.type == ACK and coap_pkt.code == EMPTY_MESSAGE:
783809
log_coap_sock.debug(
784810
"Server sent an empty ack, request will be fulfilled later: "
785811
"msg_id=%s; token=0x%x; code=%s",
786-
index[0], index[1], coap_codes[pkt.code])
812+
index[0], index[1], coap_codes[coap_pkt.code])
787813
request.empty_ack_set()
788-
elif pkt.type == CON and pkt.code == CONTENT_205:
814+
elif coap_pkt.type == CON and coap_pkt.code == CONTENT_205:
789815
log_coap_sock.debug(
790816
"Received a delayed content for a previous request: msg_id=%s; "
791817
"token=0x%x; code=%s",
792-
index[0], index[1], coap_codes[pkt.code])
818+
index[0], index[1], coap_codes[coap_pkt.code])
793819

794820
# We need to respond with an empty ACK
795821
request.empty_ack_fulfilled = True
796822
response = CoAPSocketImpl.empty_ack_params()
797-
response["msg_id"] = pkt.msg_id
823+
response["msg_id"] = coap_pkt.msg_id
798824
self._sock_send(sa_ll, CoAP(**response))
799-
self.rx_queue.send((pkt.build(), pkt.time))
825+
pkt.sport = request.port
826+
self.rx_queue.send((pkt.build(), coap_pkt.time))
800827
else:
801828
log_coap_sock.info("Not handled message: "
802829
"type=%s; code=%s;",
803-
pkt.type, coap_codes[pkt.code])
804-
self.rx_queue.send((pkt.build(), pkt.time))
830+
coap_pkt.type, coap_codes[coap_pkt.code])
831+
self.rx_queue.send((pkt.build(), coap_pkt.time))
805832

806833
def _sock_send(self, address, pl):
807834
# type: (tuple[str, int], Packet) -> None

test/contrib/coap_socket.uts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,26 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,
127127
# assert res.msg_id == req.msg_id - This assert doesn't make sense because it will send with another msg_id
128128
assert res.token == req.token
129129
assert res.payload.load == responses[1]
130+
131+
= SR1
132+
133+
with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
134+
req = CoAPSocket.make_coap_req_packet(uri="/dummy", payload=b"")
135+
res = coap_client.sr1(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
136+
assert res.payload.load == responses[0]
137+
assert res.type == ACK
138+
assert res.code == CONTENT_205
139+
assert res.msg_id == req.msg_id
140+
assert res.token == req.token
141+
142+
= SR
143+
144+
with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
145+
pkt = CoAPSocket.make_coap_req_packet(uri="/dummy", payload=b"")
146+
ans, _ = coap_client.sr(IP(dst="127.0.0.1")/UDP(dport=5683)/pkt)
147+
for _, rcv in ans:
148+
assert rcv.payload.load == responses[0]
149+
assert rcv.type == ACK
150+
assert rcv.code == CONTENT_205
151+
assert rcv.msg_id == pkt.msg_id
152+
assert rcv.token == pkt.token

0 commit comments

Comments
 (0)