Skip to content

Commit 783bb1d

Browse files
committed
[pylibs][ot-rfsim] enable CoAP-block and CoAP-observe; add CoAP-observe case study
This enables CoAP-block and CoAP-observe by default on all simulated nodes. It allows using the OT CLI commands for testing these features in simulations. A case study Python script is added that exercises these commands. A unit test is also added.
1 parent 5a6be60 commit 783bb1d

9 files changed

Lines changed: 200 additions & 5 deletions

File tree

ot-rfsim/script/build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ OT_OPTIONS=(
5050
"-DOT_JOINER=ON"
5151
"-DOT_COAP=ON"
5252
"-DOT_COAPS=ON"
53+
"-DOT_COAP_OBSERVE=ON"
54+
"-DOT_COAP_BLOCK=ON"
5355
"-DOT_COMMISSIONER=ON"
5456
"-DOT_ECDSA=ON"
5557
"-DOT_NETDIAG_CLIENT=ON"

ot-rfsim/script/build_br

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ OTBR_OPTIONS=(
4444
"-DOT_BORDER_AGENT=ON"
4545
"-DOT_MLR=ON"
4646
"-DOT_UDP_FORWARD=ON"
47-
"-DOT_COAP_BLOCK=ON"
4847
"-DOT_DNSSD_SERVER=ON"
4948
"-DOT_NETDATA_PUBLISHER=ON"
5049
"-DOT_SRP_SERVER=ON"

ot-rfsim/script/build_br_ccm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ OTBR_OPTIONS=(
4444
"-DOT_BORDER_AGENT=ON"
4545
"-DOT_MLR=ON"
4646
"-DOT_UDP_FORWARD=ON"
47-
"-DOT_COAP_BLOCK=ON"
4847
"-DOT_DNSSD_SERVER=ON"
4948
"-DOT_NETDATA_PUBLISHER=ON"
5049
"-DOT_SRP_SERVER=ON"

ot-rfsim/script/build_ccm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ OT_OPTIONS=(
4040
"-DOT_BORDER_ROUTING_COUNTERS=OFF"
4141
"-DOT_NAT64_BORDER_ROUTING=OFF"
4242
"-DOT_MLR=ON"
43-
"-DOT_COAP_BLOCK=OFF"
4443
"-DOT_DNSSD_SERVER=OFF"
4544
"-DOT_NETDATA_PUBLISHER=ON"
4645
"-DOT_SRP_SERVER=OFF"

ot-rfsim/script/build_latest

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ OT_OPTIONS=(
4040
"-DOT_BORDER_ROUTING_COUNTERS=OFF"
4141
"-DOT_NAT64_BORDER_ROUTING=OFF"
4242
"-DOT_MLR=ON"
43-
"-DOT_COAP_BLOCK=OFF"
4443
"-DOT_DNSSD_SERVER=OFF"
4544
"-DOT_NETDATA_PUBLISHER=ON"
4645
"-DOT_SRP_SERVER=OFF"

ot-rfsim/script/build_v13

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ OT_OPTIONS=(
4040
"-DOT_BORDER_ROUTER=OFF"
4141
"-DOT_BORDER_ROUTING=OFF"
4242
"-DOT_MLR=ON"
43-
"-DOT_COAP_BLOCK=OFF"
4443
"-DOT_DNSSD_SERVER=OFF"
4544
"-DOT_NETDATA_PUBLISHER=ON"
4645
"-DOT_SRP_SERVER=OFF"
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2025, The OTNS Authors.
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
# 1. Redistributions of source code must retain the above copyright
8+
# notice, this list of conditions and the following disclaimer.
9+
# 2. Redistributions in binary form must reproduce the above copyright
10+
# notice, this list of conditions and the following disclaimer in the
11+
# documentation and/or other materials provided with the distribution.
12+
# 3. Neither the name of the copyright holder nor the
13+
# names of its contributors may be used to endorse or promote products
14+
# derived from this software without specific prior written permission.
15+
#
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
# POSSIBILITY OF SUCH DAMAGE.
27+
28+
# Case study on use of CoAP observe.
29+
30+
from otns.cli import OTNS
31+
from otns.cli.errors import OTNSExitedError
32+
33+
34+
def main():
35+
ns = OTNS(otns_args=[])
36+
ns.speed = 1000
37+
38+
# CoAP server
39+
srv = ns.add("router")
40+
ns.node_cmd(srv, "coap start")
41+
ns.node_cmd(srv, "coap resource test-resource")
42+
ns.node_cmd(srv, "coap set TestPayload_1")
43+
srv_addr = ns.get_ipaddrs(srv, 'mleid')[0]
44+
45+
# CoAP client
46+
cl = ns.add("router")
47+
ns.node_cmd(cl, "coap start")
48+
49+
# form the network
50+
ns.kpi_start()
51+
ns.go(10)
52+
53+
# client sends observe request
54+
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource")
55+
ns.go(90)
56+
57+
# time passes, and resource changes
58+
ns.node_cmd(srv, "coap set TestPayload_2")
59+
ns.go(100)
60+
61+
ns.node_cmd(srv, "coap set TestPayload_3")
62+
ns.go(100)
63+
64+
ns.node_cmd(srv, "coap set TestPayload_4")
65+
ns.go(100)
66+
67+
# now repeat the observe requests using CON type. This triggers CON notifications to be sent.
68+
ns.node_cmd(cl, "coap cancel")
69+
ns.go(100)
70+
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource con")
71+
ns.go(100)
72+
ns.node_cmd(srv, "coap set TestPayload_5")
73+
ns.go(100)
74+
ns.node_cmd(srv, "coap set TestPayload_6")
75+
ns.go(100)
76+
ns.node_cmd(srv, "coap set TestPayload_7")
77+
ns.go(100)
78+
79+
# send a cancel request
80+
ns.node_cmd(cl, f"coap cancel")
81+
ns.go(100)
82+
83+
# server sends an interspersed CON notification after sending 5 NON notifications
84+
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource")
85+
ns.go(100)
86+
ns.node_cmd(srv, "coap set TestPayload_8")
87+
ns.go(100)
88+
ns.node_cmd(srv, "coap set TestPayload_9")
89+
ns.go(100)
90+
ns.node_cmd(srv, "coap set TestPayload_a")
91+
ns.go(100)
92+
ns.node_cmd(srv, "coap set TestPayload_b")
93+
ns.go(100)
94+
ns.node_cmd(srv, "coap set TestPayload_c")
95+
ns.go(100)
96+
ns.node_cmd(srv, "coap set TestPayload_d") # this one will be sent CON
97+
ns.go(0.001)
98+
# this fast one will be NON. Note that the previous one isn't Ack'ed yet.
99+
ns.node_cmd(srv, "coap set TestPayload_d2")
100+
ns.go(100)
101+
ns.node_cmd(srv, "coap set TestPayload_e")
102+
ns.go(100)
103+
ns.node_cmd(srv, "coap set TestPayload_e")
104+
ns.go(100)
105+
106+
# observe another resource (a non-existing one)
107+
ns.node_cmd(cl, f"coap observe {srv_addr} resnotfound")
108+
ns.go(10)
109+
110+
ns.kpi_stop()
111+
ns.web_display()
112+
113+
if __name__ == '__main__':
114+
try:
115+
main()
116+
except OTNSExitedError as ex:
117+
if ex.exit_code != 0:
118+
raise

pylibs/unittests/OTNSTestCase.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,20 @@ def assertPings(self, pings, n, max_delay=1000, max_fails=0):
8383
else:
8484
self.assertTrue(delay <= max_delay)
8585
self.assertTrue(n_fails <= max_fails)
86+
87+
def assertCoapMessageSent(self, src: int, type: int, code: int, dst_port: int = 5683):
88+
"""
89+
Verify that a CoAP message is sent from the given source node.
90+
:param src: node id
91+
:param type: CoAP type
92+
:param code: CoAP code
93+
:param dst_port: destination port number (default 5683)
94+
"""
95+
coap_msgs = self.ns.coaps()
96+
ok = False
97+
for c in coap_msgs:
98+
if c['src'] == src and c['dst_port'] == dst_port:
99+
self.assertEqual(type, c['type'])
100+
self.assertEqual(code, c['code'])
101+
ok = True
102+
self.assertTrue(ok, f"CoAP message not found: src={src} type={type} code={code} dst_port={dst_port}")

pylibs/unittests/test_basic.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,69 @@ def testPhyStats(self):
965965
self.assertTrue(os.path.isfile('tmp/0_txbytes.csv'))
966966
self.assertTrue(os.path.isfile('tmp/0_chansamples.csv'))
967967

968+
def testCoapObserve(self):
969+
ns: OTNS = self.ns
970+
ns.coaps_enable()
971+
972+
# see https://datatracker.ietf.org/doc/html/rfc7252#section-3
973+
COAP_205_CONTENT = 69
974+
COAP_404_NOT_FOUND = 132
975+
COAP_CON = 0
976+
COAP_NON = 1
977+
COAP_ACK = 2
978+
979+
# CoAP server + resource setup
980+
srv = ns.add("router")
981+
ns.node_cmd(srv, "coap start")
982+
ns.node_cmd(srv, "coap resource test-resource")
983+
ns.node_cmd(srv, "coap set TestPayload_1")
984+
srv_addr = ns.get_ipaddrs(srv, 'mleid')[0]
985+
986+
# CoAP client
987+
cl = ns.add("router")
988+
ns.node_cmd(cl, "coap start")
989+
990+
# form the network
991+
ns.go(20)
992+
self.assertFormPartitions(1)
993+
994+
# client sends NON observe request
995+
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource")
996+
ns.go(10)
997+
self.assertCoapMessageSent(src=srv, type=COAP_NON, code=COAP_205_CONTENT)
998+
999+
# resource changes: every change triggers a notification
1000+
for i in range(1, 10):
1001+
ns.node_cmd(srv, f"coap set TestPayload_{i}")
1002+
ns.go(10)
1003+
self.assertCoapMessageSent(src=srv, type=COAP_CON if i == 6 else COAP_NON, code=COAP_205_CONTENT)
1004+
1005+
# cancel subscription
1006+
ns.node_cmd(cl, "coap cancel")
1007+
ns.go(10)
1008+
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT)
1009+
1010+
# now repeat the observe request using CON type. This triggers CON notifications to be sent.
1011+
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource con")
1012+
ns.go(10)
1013+
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT)
1014+
1015+
# resource changes: every change triggers a notification
1016+
for i in range(1, 10):
1017+
ns.node_cmd(srv, f"coap set TestPayload_{i}")
1018+
ns.go(10)
1019+
self.assertCoapMessageSent(src=srv, type=COAP_CON, code=COAP_205_CONTENT)
1020+
1021+
# send a cancel request again
1022+
ns.node_cmd(cl, f"coap cancel")
1023+
ns.go(10)
1024+
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT)
1025+
1026+
# observe another resource (a non-existing one)
1027+
ns.node_cmd(cl, f"coap observe {srv_addr} resnotfound")
1028+
ns.go(10)
1029+
self.assertCoapMessageSent(src=srv, type=COAP_NON, code=COAP_404_NOT_FOUND)
1030+
9681031

9691032
if __name__ == '__main__':
9701033
unittest.main()

0 commit comments

Comments
 (0)