Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ot-rfsim/script/build
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ OT_OPTIONS=(
"-DOT_JOINER=ON"
"-DOT_COAP=ON"
"-DOT_COAPS=ON"
"-DOT_COAP_OBSERVE=ON"
"-DOT_COAP_BLOCK=ON"
"-DOT_COMMISSIONER=ON"
"-DOT_ECDSA=ON"
"-DOT_NETDIAG_CLIENT=ON"
Expand Down
1 change: 0 additions & 1 deletion ot-rfsim/script/build_br
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ OTBR_OPTIONS=(
"-DOT_BORDER_AGENT=ON"
"-DOT_MLR=ON"
"-DOT_UDP_FORWARD=ON"
"-DOT_COAP_BLOCK=ON"
"-DOT_DNSSD_SERVER=ON"
"-DOT_NETDATA_PUBLISHER=ON"
"-DOT_SRP_SERVER=ON"
Expand Down
1 change: 0 additions & 1 deletion ot-rfsim/script/build_br_ccm
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ OTBR_OPTIONS=(
"-DOT_BORDER_AGENT=ON"
"-DOT_MLR=ON"
"-DOT_UDP_FORWARD=ON"
"-DOT_COAP_BLOCK=ON"
"-DOT_DNSSD_SERVER=ON"
"-DOT_NETDATA_PUBLISHER=ON"
"-DOT_SRP_SERVER=ON"
Expand Down
1 change: 0 additions & 1 deletion ot-rfsim/script/build_ccm
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ OT_OPTIONS=(
"-DOT_BORDER_ROUTING_COUNTERS=OFF"
"-DOT_NAT64_BORDER_ROUTING=OFF"
"-DOT_MLR=ON"
"-DOT_COAP_BLOCK=OFF"
"-DOT_DNSSD_SERVER=OFF"
"-DOT_NETDATA_PUBLISHER=ON"
"-DOT_SRP_SERVER=OFF"
Expand Down
1 change: 0 additions & 1 deletion ot-rfsim/script/build_latest
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ OT_OPTIONS=(
"-DOT_BORDER_ROUTING_COUNTERS=OFF"
"-DOT_NAT64_BORDER_ROUTING=OFF"
"-DOT_MLR=ON"
"-DOT_COAP_BLOCK=OFF"
"-DOT_DNSSD_SERVER=OFF"
"-DOT_NETDATA_PUBLISHER=ON"
"-DOT_SRP_SERVER=OFF"
Expand Down
1 change: 0 additions & 1 deletion ot-rfsim/script/build_v13
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ OT_OPTIONS=(
"-DOT_BORDER_ROUTER=OFF"
"-DOT_BORDER_ROUTING=OFF"
"-DOT_MLR=ON"
"-DOT_COAP_BLOCK=OFF"
"-DOT_DNSSD_SERVER=OFF"
"-DOT_NETDATA_PUBLISHER=ON"
"-DOT_SRP_SERVER=OFF"
Expand Down
118 changes: 118 additions & 0 deletions pylibs/case_studies/coap_observe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
# Copyright (c) 2025, The OTNS Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# Case study on use of CoAP observe.

from otns.cli import OTNS
from otns.cli.errors import OTNSExitedError


def main():
ns = OTNS(otns_args=[])
ns.speed = 1000

# CoAP server
srv = ns.add("router")
ns.node_cmd(srv, "coap start")
ns.node_cmd(srv, "coap resource test-resource")
ns.node_cmd(srv, "coap set TestPayload_1")
srv_addr = ns.get_ipaddrs(srv, 'mleid')[0]

# CoAP client
cl = ns.add("router")
ns.node_cmd(cl, "coap start")

# form the network
ns.kpi_start()
ns.go(10)

# client sends observe request
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource")
ns.go(90)

# time passes, and resource changes
ns.node_cmd(srv, "coap set TestPayload_2")
ns.go(100)

ns.node_cmd(srv, "coap set TestPayload_3")
ns.go(100)

ns.node_cmd(srv, "coap set TestPayload_4")
ns.go(100)

# now repeat the observe requests using CON type. This triggers CON notifications to be sent.
ns.node_cmd(cl, "coap cancel")
ns.go(100)
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource con")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_5")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_6")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_7")
ns.go(100)

# send a cancel request
ns.node_cmd(cl, f"coap cancel")
ns.go(100)

# server sends an interspersed CON notification after sending 5 NON notifications
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_8")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_9")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_a")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_b")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_c")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_d") # this one will be sent CON
ns.go(0.001)
# this fast one will be NON. Note that the previous one isn't Ack'ed yet.
ns.node_cmd(srv, "coap set TestPayload_d2")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_e")
ns.go(100)
ns.node_cmd(srv, "coap set TestPayload_e")
ns.go(100)

# observe another resource (a non-existing one)
ns.node_cmd(cl, f"coap observe {srv_addr} resnotfound")
ns.go(10)

ns.kpi_stop()
ns.web_display()

if __name__ == '__main__':
try:
main()
except OTNSExitedError as ex:
if ex.exit_code != 0:
raise
18 changes: 18 additions & 0 deletions pylibs/unittests/OTNSTestCase.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,21 @@ def assertPings(self, pings, n, max_delay=1000, max_fails=0):
else:
self.assertTrue(delay <= max_delay)
self.assertTrue(n_fails <= max_fails)

def assertCoapMessageSent(self, src: int, type: int, code: int, dst_port: int = 5683, number_expected: int = 1, coap_msgs: list = None):
"""
Verify that a CoAP message is sent from the given source node.
:param src: node id
:param type: CoAP type
:param code: CoAP code
:param dst_port: destination port number (default 5683)
:param number_expected: number of messages expected (default 1)
:param coap_msgs: optional list of CoAP messages from ns.coaps()
"""
coap_msgs = self.ns.coaps() if coap_msgs == None else coap_msgs
number_seen = 0
for c in coap_msgs:
if c['src'] == src and c['dst_port'] == dst_port and c['code'] == code:
self.assertEqual(type, c['type'])
number_seen += 1
self.assertEqual(number_expected, number_seen, f"Expected number of CoAP messages is not present: src={src} type={type} code={code} dst_port={dst_port}")
108 changes: 108 additions & 0 deletions pylibs/unittests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,114 @@ def testBackgroundCommands(self):
with self.assertRaises(errors.OTNSCliError):
ns.node_cmd(nid_diag, f'abcdefgdns xquery test.example')

def testCoapObserve(self):
ns: OTNS = self.ns
ns.coaps_enable()

# see https://datatracker.ietf.org/doc/html/rfc7252#section-3
COAP_205_CONTENT = 69
COAP_404_NOT_FOUND = 132
COAP_CON = 0
COAP_NON = 1
COAP_ACK = 2

# CoAP server + resource setup
srv = ns.add("router")
ns.node_cmd(srv, "coap start")
ns.node_cmd(srv, "coap resource test-resource")
ns.node_cmd(srv, "coap set TestPayload_1")
srv_addr = ns.get_ipaddrs(srv, 'mleid')[0]

# CoAP client
cl = ns.add("router")
ns.node_cmd(cl, "coap start")

# form the network
ns.go(20)
self.assertFormPartitions(1)

# client sends NON observe request
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_NON, code=COAP_205_CONTENT)

# resource changes: every change triggers a notification
for i in range(1, 10):
ns.node_cmd(srv, f"coap set TestPayload_{i}")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_CON if i == 6 else COAP_NON, code=COAP_205_CONTENT)

# cancel subscription
ns.node_cmd(cl, "coap cancel")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT)

# now repeat the observe request using CON type. This triggers CON notifications to be sent.
ns.node_cmd(cl, f"coap observe {srv_addr} test-resource con")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT)

# resource changes: every change triggers a notification
for i in range(1, 10):
ns.node_cmd(srv, f"coap set TestPayload_{i}")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_CON, code=COAP_205_CONTENT)

# send a cancel request again
ns.node_cmd(cl, f"coap cancel")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT)

# observe another resource (a non-existing one)
ns.node_cmd(cl, f"coap observe {srv_addr} resnotfound")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_NON, code=COAP_404_NOT_FOUND)

def testCoapBlock(self):
ns: OTNS = self.ns
ns.coaps_enable()

# see https://datatracker.ietf.org/doc/html/rfc7252#section-3
COAP_204_CHANGED = (2 << 5) + 4
COAP_205_CONTENT = (2 << 5) + 5
COAP_231_CONTINUE = (2 << 5) + 31
COAP_404_NOT_FOUND = (4 << 5) + 4
COAP_CON = 0
COAP_NON = 1
COAP_ACK = 2

# CoAP server + resource setup
srv = ns.add("router")
ns.node_cmd(srv, "coap start")
ns.node_cmd(srv, "coap resource test-resource")
srv_addr = ns.get_ipaddrs(srv, 'mleid')[0]

# CoAP client
cl = ns.add("router")
ns.node_cmd(cl, "coap start")

# form the network
ns.go(20)
self.assertFormPartitions(1)

# client sends blockwise GET request
ns.node_cmd(cl, f"coap get {srv_addr} test-resource block-32")
ns.go(10)
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_205_CONTENT, number_expected=3)

# client sends blockwise PUT request
ns.node_cmd(cl, f"coap put {srv_addr} test-resource block-32")
ns.go(10)
ml = self.ns.coaps()
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_231_CONTINUE, coap_msgs=ml, number_expected=2)
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_204_CHANGED, coap_msgs=ml)

# client sends blockwise POST request
ns.node_cmd(cl, f"coap post {srv_addr} test-resource block-32")
ns.go(10)
ml = self.ns.coaps()
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_231_CONTINUE, coap_msgs=ml, number_expected=2)
self.assertCoapMessageSent(src=srv, type=COAP_ACK, code=COAP_204_CHANGED, coap_msgs=ml)

if __name__ == '__main__':
unittest.main()
Loading