Skip to content

Commit

Permalink
Code reviews:
Browse files Browse the repository at this point in the history
- Moved the defines/enumerators to coap.py
- Changed the send() function to match the SuperSocket declaration
- Unconditionally gives the received packet to the user
- Updated unit tests
  • Loading branch information
eHonnef committed Apr 13, 2024
1 parent aeb746a commit 141a95f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 74 deletions.
67 changes: 55 additions & 12 deletions scapy/contrib/coap.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,71 @@
from scapy.error import warning
from scapy.compat import raw

"""
CoAP message request codes (RFC 7252 @ section-5.8.1)
"""
EMPTY_MESSAGE = 0
GET = 1
POST = 2
PUT = 3
DELETE = 4
COAP_REQ_CODES = [GET, POST, PUT, DELETE]
"""
CoAP message response codes (RFC 7252 @ section-12.1.2)
"""
EMPTY_ACK = EMPTY_MESSAGE
CONTENT_205 = 69
NOT_FOUND_404 = 132
NOT_ALLOWED_405 = 133
NOT_IMPLEMENTED_501 = 161
"""
CoAP content type (RFC 7252 @ section-12.3)
"""
CF_TEXT_PLAIN = b"\x00"
CF_APP_LINK_FORMAT = b"\x28"
CF_APP_XML = b"\x29"
CF_APP_OCTET_STREAM = b"\x2A"
CF_APP_EXI = b"\x2F"
CF_APP_JSON = b"\x32"
"""
CoAP options (RFC 7252 @ section-5.10)
"""
PAYMARK = b"\xff"
URI_PATH = 11
CONTENT_FORMAT = 12
"""
CoAP message type
"""
CON = 0
NON = 1
ACK = 2
RST = 3

coap_codes = {
0: "Empty",
EMPTY_MESSAGE: "Empty",
# Request codes
1: "GET",
2: "POST",
3: "PUT",
4: "DELETE",
GET: "GET",
POST: "POST",
PUT: "PUT",
DELETE: "DELETE",
# Response codes
65: "2.01 Created",
66: "2.02 Deleted",
67: "2.03 Valid",
68: "2.04 Changed",
69: "2.05 Content",
CONTENT_205: "2.05 Content",
128: "4.00 Bad Request",
129: "4.01 Unauthorized",
130: "4.02 Bad Option",
131: "4.03 Forbidden",
132: "4.04 Not Found",
133: "4.05 Method Not Allowed",
NOT_FOUND_404: "4.04 Not Found",
NOT_ALLOWED_405: "4.05 Method Not Allowed",
134: "4.06 Not Acceptable",
140: "4.12 Precondition Failed",
141: "4.13 Request Entity Too Large",
143: "4.15 Unsupported Content-Format",
160: "5.00 Internal Server Error",
161: "5.01 Not Implemented",
NOT_IMPLEMENTED_501: "5.01 Not Implemented",
162: "5.02 Bad Gateway",
163: "5.03 Service Unavailable",
164: "5.04 Gateway Timeout",
Expand Down Expand Up @@ -120,7 +160,8 @@ def _get_opt_val_size(pkt):
class _CoAPOpt(Packet):
fields_desc = [BitField("delta", 0, 4),
BitField("len", 0, 4),
StrLenField("delta_ext", "", length_from=_get_delta_ext_size), # noqa: E501
StrLenField("delta_ext", "", length_from=_get_delta_ext_size),
# noqa: E501
StrLenField("len_ext", "", length_from=_get_len_ext_size),
StrLenField("opt_val", "", length_from=_get_opt_val_size)]

Expand Down Expand Up @@ -149,7 +190,8 @@ class _CoAPOptsField(StrField):
islist = 1

def i2h(self, pkt, x):
return [(coap_options[0][o[0]], o[1]) if o[0] in coap_options[0] else o for o in x] # noqa: E501
return [(coap_options[0][o[0]], o[1]) if o[0] in coap_options[0] else o for o in
x] # noqa: E501

# consume only the coap layer from the wire string
def getfield(self, pkt, s):
Expand Down Expand Up @@ -214,7 +256,8 @@ class CoAP(Packet):
name = "CoAP"

fields_desc = [BitField("ver", 1, 2),
BitEnumField("type", 0, 2, {0: "CON", 1: "NON", 2: "ACK", 3: "RST"}), # noqa: E501
BitEnumField("type", 0, 2, {0: "CON", 1: "NON", 2: "ACK", 3: "RST"}),
# noqa: E501
BitFieldLenField("tkl", None, 4, length_of='token'),
ByteEnumField("code", 0, coap_codes),
ShortField("msg_id", 0),
Expand Down
79 changes: 24 additions & 55 deletions scapy/contrib/coap_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,55 +23,16 @@

from scapy.error import Scapy_Exception
from scapy.packet import Packet
from scapy.contrib.coap import CoAP, coap_options, coap_codes
from scapy.contrib.coap import CoAP, coap_options, coap_codes, EMPTY_MESSAGE, GET, \
POST, PUT, DELETE, COAP_REQ_CODES, CONTENT_205, NOT_FOUND_404, NOT_ALLOWED_405, \
CF_TEXT_PLAIN, CF_APP_LINK_FORMAT, PAYMARK, URI_PATH, CONTENT_FORMAT, CON, NON, ACK
from scapy.contrib.isotp.isotp_soft_socket import TimeoutScheduler
from scapy.data import MTU
from scapy.utils import EDecimal
from scapy.automaton import ObjectPipe, select_objects

from scapy.supersocket import SuperSocket, SimpleSocket

"""
CoAP message request codes (RFC 7252 @ section-5.8.1)
"""
EMPTY_MESSAGE = 0
GET = 1
POST = 2
PUT = 3
DELETE = 4
COAP_REQ_CODES = [GET, POST, PUT, DELETE]
"""
CoAP message response codes (RFC 7252 @ section-12.1.2)
Also, from scapy.contrib.coap.coap_codes
"""
EMPTY_ACK = EMPTY_MESSAGE
CONTENT_205 = 69
NOT_FOUND_404 = 132
NOT_ALLOWED_405 = 133
NOT_IMPLEMENTED_501 = 161
"""
CoAP content type (RFC 7252 @ section-12.3)
"""
CF_TEXT_PLAIN = b"\x00"
CF_APP_LINK_FORMAT = b"\x28"
CF_APP_XML = b"\x29"
CF_APP_OCTET_STREAM = b"\x2A"
CF_APP_EXI = b"\x2F"
CF_APP_JSON = b"\x32"
"""
CoAP options (RFC 7252 @ section-5.10)
"""
PAYMARK = b"\xff"
URI_PATH = 11
CONTENT_FORMAT = 12
"""
CoAP message type
"""
CON = 0
NON = 1
ACK = 2
RST = 3

log_coap_sock = logging.getLogger("scapy.contrib.coap_socket")


Expand All @@ -85,7 +46,7 @@ class CoAPSocket(SuperSocket):
>>> with CoAPSocket("127.0.0.1", 1234) as coap_client:
>>> req = CoAPSocket.make_coap_req_packet(
>>> method=GET, uri="endpoint-uri", payload=b"")
>>> coap_client.send("127.0.0.1", 5683, req)
>>> coap_client.send(IP(dst="192.168.1.1") / UDP(dport=1234) / req)
>>> # Careful, this will block until the coap_client receives something
>>> res = coap_client.recv()
Expand Down Expand Up @@ -172,12 +133,24 @@ def recv(self, x=MTU, **kwargs):
def close(self):
# type: () -> None
if not self.closed:
self.impl.close()
self.closed = True
self.impl.close()

def send(self, ip, port, x):
# type: (str, int, CoAP) -> None
self.impl.send(ip, port, x)
def send(self, x):
# type: (Packet) -> int
"""
Send the packet using this socket.
Should be a CoAP packet with IP and UDP data.
Example:
>>> IP(dst="192.168.1.1") / UDP(dport=1234) / CoAP()
>>> IP(dst="192.168.1.1") / UDP(dport=1234) / CoAPSocket.make_coap_req_packet()
:param x: Concatenated packet with IP / UDP / CoAP
:return: The length of x, which is the amount of bytes sent
"""
self.impl.send(x.dst, x.dport, x[CoAP])
return len(x)

@staticmethod
def make_coap_req_packet(method=GET, uri="", options=None, payload=b""):
Expand Down Expand Up @@ -630,8 +603,10 @@ def _on_pkt_recv(self, pkt, sa_ll):
else:
self._handle_rcv_request(pkt, sa_ll)
else:
# Response, check pending requests
# Response, check pending requests and process internally
self._handle_request_response(pkt, sa_ll)
# Then give the response to the user.
self.rx_queue.send((pkt.build(), pkt.time))

def _post(self):
# type: () -> dict
Expand Down Expand Up @@ -805,8 +780,6 @@ def _handle_request_response(self, pkt, sa_ll):
index[0], index[1],
coap_codes[pkt.code])
del self.pending_requests[index]
# Piggybacked message, give it to the user
self.rx_queue.send((pkt.build(), pkt.time))
elif pkt.type == ACK and pkt.code == EMPTY_MESSAGE:
log_coap_sock.debug(
"Server sent an empty ack, request will be fulfilled later: "
Expand All @@ -824,14 +797,10 @@ def _handle_request_response(self, pkt, sa_ll):
response = CoAPSocketImpl.empty_ack_params()
response["msg_id"] = pkt.msg_id
self._sock_send(sa_ll, CoAP(**response))

# Give the packet to the user
self.rx_queue.send((pkt.build(), pkt.time))
else:
log_coap_sock.info("Not handled message, giving to user: "
log_coap_sock.info("Not handled message: "
"type=%s; code=%s;",
pkt.type, coap_codes[pkt.code])
self.rx_queue.send((pkt.build(), pkt.time))

def _sock_send(self, address, pl):
# type: (tuple[str, int], Packet) -> None
Expand Down
14 changes: 7 additions & 7 deletions test/contrib/coap_socket.uts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ lst_resources = [DummyResource("/dummy"), DelayedResource("delayed")]

with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
req = CoAPSocket.make_coap_req_packet(uri=".well-known/core", payload=b"")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.payload.load == b'</dummy>;ct=0,</delayed>;ct=0'
assert res.type == ACK
Expand All @@ -60,7 +60,7 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,

with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
req = CoAPSocket.make_coap_req_packet(uri="dummy", payload=b"")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.payload.load == responses[0]
assert res.type == ACK
Expand All @@ -72,7 +72,7 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,

with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
req = CoAPSocket.make_coap_req_packet(uri="/dummy", payload=b"")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.payload.load == responses[0]
assert res.type == ACK
Expand All @@ -84,7 +84,7 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,

with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
req = CoAPSocket.make_coap_req_packet(uri="dummy/", payload=b"")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.type == ACK
assert res.code == NOT_FOUND_404
Expand All @@ -95,7 +95,7 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,

with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
req = CoAPSocket.make_coap_req_packet(method=PUT, uri="dummy", payload=b"a payload")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.type == ACK
assert res.code == NOT_ALLOWED_405
Expand All @@ -107,7 +107,7 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,
with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
coap_server.impl._enable_debug = True
req = CoAPSocket.make_coap_req_packet(uri="/dummy", payload=b"")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.payload.load == responses[0]
assert res.type == ACK
Expand All @@ -119,7 +119,7 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,

with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server, CoAPSocket("127.0.0.1", 5684) as coap_client:
req = CoAPSocket.make_coap_req_packet(uri="/delayed", payload=b"")
coap_client.send("127.0.0.1", 5683, req)
coap_client.send(IP(dst="127.0.0.1")/UDP(dport=5683)/req)
res = coap_client.recv()
assert res.payload.load == responses[1]
assert res.type == CON
Expand Down

0 comments on commit 141a95f

Please sign in to comment.