15  CoAP Security and Applications

coap
security

15.1 Start With the Private Reading

Picture a wearable sensor sending a heart-rate value through a hospital gateway. The payload is sensitive, but so are the resource path, timing, identity, and repeated access pattern. CoAP security has to protect the exchange without spending so much energy that the device cannot do its job.

This page follows that practical tension: secure the datagrams, choose credentials, keep sessions alive when possible, and decide where application policy belongs in real deployments.

Chapter Roadmap

This is a long chapter, so read it as a sequence of design decisions:

  1. First we decide what DTLS protects, why CoAPs uses port 5684, and where payload-only encryption falls short.
  2. Then we size the security cost with the chapter’s handshake, header, MAC, and battery examples.
  3. Next we connect those choices to smart energy, building automation, industrial, healthcare, agriculture, and asset-tracking deployments.
  4. After that we turn to implementation failure modes: tokens, retransmission backoff, MTU limits, response codes, and CON/NON misuse.
  5. Finally we compare DTLS, OSCORE, credentials, proxies, and deep-sleep recovery before the summary quizzes.

Checkpoints recap the required decisions as you go. Deep-dive and interactive sections are optional on a first read, but they show the calculations behind the recommendations.

15.2 Learning Objectives

  • Explain how DTLS (Datagram TLS) provides encryption, authentication (PSK or certificates), and integrity protection for CoAP on port 5684
  • Distinguish between application-layer payload encryption and DTLS transport encryption, and justify why only DTLS satisfies HIPAA compliance for full metadata protection
  • Apply CoAP in real-world deployments including smart energy metering, building automation (HVAC/lighting), and industrial sensor networks
  • Select appropriate CoAP message types (CON vs NON) based on message criticality, battery constraints, and reliability requirements, and justify your choice
  • Diagnose common CoAP implementation issues including token matching failures, retransmission backoff misconfiguration, MTU size violations, and response code misinterpretation
  • CoAP: Constrained Application Protocol — REST-style request/response protocol using UDP instead of TCP
  • Confirmable Message (CON): Requires ACK from recipient — provides reliable delivery over UDP at the cost of one roundtrip
  • Non-confirmable Message (NON): Fire-and-forget UDP datagram — lowest latency, no delivery guarantee
  • Observe Option: CoAP extension enabling publish/subscribe: client registers to receive notifications on resource changes
  • Block-wise Transfer: Fragmentation mechanism for transferring payloads larger than a single CoAP datagram
  • Token: Client-generated value matching responses to requests — enables concurrent request/response pairing
  • DTLS: Datagram TLS — CoAP’s security layer providing encryption and authentication over UDP

15.3 For Beginners: CoAP Security

Securing CoAP means protecting the data that tiny IoT devices exchange. Since CoAP runs over UDP, it uses DTLS for encryption rather than the TLS used by web browsers. This chapter also covers real-world CoAP applications in smart buildings, industrial sensors, and wearable devices where security is essential.

“Wait – if CoAP sends data over UDP, can’t anyone listen in?” Sammy the Sensor asked nervously. “My medical sensor readings are private!”

Max the Microcontroller nodded seriously. “That’s why we have DTLS – it’s like TLS (the lock on websites) but designed for UDP. It wraps your CoAP messages in encryption so nobody can read them in transit. Think of it as putting your letter in a locked box before mailing it.”

“But locking costs energy,” Bella the Battery pointed out. “The DTLS handshake – where devices exchange keys – takes several extra messages. That’s why you keep the connection open after the handshake so you don’t have to redo it every time. One handshake, then many encrypted messages.”

Lila the LED shared a real example: “In a smart hospital, CoAP with DTLS protects patient data from sensors to the nurse’s station. Without encryption, a hacker with a radio could intercept heart rate data. With DTLS, all they see is scrambled nonsense. Security isn’t optional when lives are at stake!”

In 60 Seconds

CoAP uses DTLS (Datagram TLS) for security over UDP, providing encryption, authentication (via pre-shared keys or certificates), and integrity protection on port 5684. Unlike application-layer encryption which only protects the payload, DTLS encrypts the entire CoAP message including headers, URIs, and options – critical for compliance scenarios like healthcare (HIPAA) where metadata exposure is itself a privacy violation.

Phoebe the physics guide

Phoebe’s Why

At a fixed data rate and a fixed transmit current, radio energy per message is set by one thing: how long the radio stays keyed up, which is set by how many bytes it has to move. That makes the DTLS-versus-plain energy comparison a byte-counting exercise before it is anything else – a 53-byte secured record and a 20-byte plain message, sent at the same rate and current, should cost close to their own byte ratio in energy, not a smaller number that quietly hides a mismatch somewhere in the chain. The one-time handshake is genuinely close to free once it is spread across a day of readings, which is the chapter’s real point about session reuse. But “security is cheap” and “self-discharge and derating are still there” are two different claims, and only one of them survives contact with a real coin cell over a multi-year deployment.

The Derivation

Transmit time and per-message charge at a fixed data rate and current:

\[t_{msg} = \frac{\text{bytes}\times 8}{\text{rate}}, \qquad Q_{msg}\,(\mathrm{mAh}) = \frac{I_{tx}\times t_{msg}}{3600}\]

Daily energy adds the amortized one-time handshake to the per-message cost times message count:

\[Q_{day} = Q_{handshake} + N_{msg/day}\times Q_{msg}\]

Usable charge after self-discharge over service life \(T\) and a derating margin \(\delta\), spent by active use plus self-discharge together:

\[Q_{usable} = Q_0(1-\delta), \qquad T_{realistic} = \frac{Q_{usable}}{365\times Q_{day} + k\,Q_0}\]

Worked Numbers: This Chapter’s Own DTLS Parameters

Reworking this chapter’s own raw figures end to end (53-byte secured message, 20-byte plain message, 250 kbps, 20 mA TX, 1,440 messages/day, 0.064 µAh handshake):

  • Per message: secured \(t=53\times8/250{,}000=1.696\) ms \(\to Q=20\times0.001696/3600=9.42\times10^{-6}\) mAh; plain \(t=0.640\) ms \(\to Q=3.56\times10^{-6}\) mAh. Ratio \(=9.42/3.56=2.65\) – matching the \(53/20=2.65\) byte ratio exactly, as fixed-rate, fixed-current transmission must.
  • Daily totals: secured \(=1{,}440\times9.42\times10^{-6}+0.000064=0.01363\) mAh/day; plain \(=1{,}440\times3.56\times10^{-6}=0.00512\) mAh/day – a \(2.66\times\) (\(166\%\) more) energy tax from security, tracking the byte ratio once the handshake is properly amortized.
  • On this chapter’s own 220 mAh, 3.0 V CR2032 calculator default, idealised (active-only) life is \(44.2\) yr secured versus \(118\) yr plain – both absurd, confirming active DTLS overhead alone is negligible against a full cell.
  • Realistic life (catalog-typical \(1\%\)/yr self-discharge, \(20\%\) cutoff/temperature derating, \(Q_{usable}=176\) mAh): secured \(=176/(4.98+2.20)=24.5\) yr; plain \(=176/(1.87+2.20)=43.3\) yr – a \(43.3\%\) shorter realistic life from DTLS, far larger than the naive “handshake is negligible” story suggests, because the fixed \(2.20\) mAh/yr self-discharge tax no longer swamps the now-larger secured active load the way it swamps the tiny plain load.

The security overhead was never really about the handshake – it is the byte-for-byte cost of encrypting every routine reading, and it only shows up as a meaningful fraction of realistic battery life once self-discharge is priced into the same budget.

15.4 Continue: CoAP DTLS and OSCORE Security Contracts

The main chapter below stays focused on CoAP security and application practice. For the deeper contract behind DTLS security modes, proxy termination, OSCORE inner and outer options, and replay protection, continue to CoAP DTLS and OSCORE Security Contracts.

15.5 Security with DTLS

Start with the security boundary. If the deployment needs privacy, the first question is not which CoAP method to call, but which fields must be hidden from the network.

CoAP uses DTLS (Datagram TLS) for security:

Features:

  • Encryption of messages
  • Authentication (Pre-Shared Keys or Certificates)
  • Integrity protection

DTLS vs TLS:

  • TLS = for TCP (HTTP)
  • DTLS = for UDP (CoAP)

Port:

  • CoAP: 5683
  • CoAPs (secure): 5684
Try It: DTLS Handshake Step Explorer

Explore each step of the DTLS handshake process. Adjust the security mode and network latency to see how the handshake timing and energy cost change for constrained IoT devices.

15.6 DTLS Setup: Pre-Shared Key Example

Setting up secure CoAP (CoAPs) with DTLS-PSK authentication using Python:

# pip install aiocoap[dtls]
# Requires: tinydtls library (pip install DTLSSocket)
import asyncio
import aiocoap
import aiocoap.credentials

# --- Server side: CoAPs on port 5684 ---
async def create_secure_server():
    """Start a CoAP server with DTLS-PSK on port 5684."""
    root = aiocoap.resource.Site()
    root.add_resource(['temperature'],
                      TemperatureResource())

    # Define server credentials: map client identities to PSKs
    server_credentials = aiocoap.credentials.CredentialsMap()
    server_credentials.load_from_dict({
        # Identity "sensor-042" uses this 16-byte pre-shared key
        ':dtls-psk': {
            'sensor-042': b'MySecret16ByteK!',
            'sensor-043': b'AnotherKey16Byt!',
        }
    })

    context = await aiocoap.Context.create_server_context(
        root,
        bind=('::', 5684),  # Secure CoAP port
    )
    context.client_credentials = server_credentials
    return context

# --- Client side: connect with PSK identity ---
async def secure_get_temperature():
    """GET coaps://server/temperature with DTLS-PSK."""
    context = await aiocoap.Context.create_client_context()

    # Set client identity and PSK
    context.client_credentials.load_from_dict({
        'coaps://server/*': {
            'dtls': {
                'psk': b'MySecret16ByteK!',
                'client-identity': b'sensor-042'
            }
        }
    })

    request = aiocoap.Message(
        code=aiocoap.GET,
        uri='coaps://server/temperature'  # Note: coaps:// not coap://
    )
    response = await context.request(request).response
    print(f"Secure response: {response.payload.decode()}")

# What to observe:
# - URI scheme is coaps:// (port 5684) vs coap:// (port 5683)
# - DTLS handshake adds ~200ms on first request (key exchange)
# - Subsequent requests reuse session (~5ms overhead)
# - If PSK doesn't match: handshake fails, no data exchanged

Testing DTLS with CLI tools:

# Generate a PSK identity file for coap-client
# coap-client supports DTLS-PSK natively

# Secure GET request
coap-client -m get coaps://localhost/temperature \
  -k "MySecret16ByteK!" \
  -u "sensor-042"

# Verify encryption with Wireshark:
# Filter: dtls && udp.port == 5684
# You should see:
#   ClientHello -> ServerHello -> ChangeCipherSpec
#   Then encrypted ApplicationData (CoAP payload not readable)

DTLS overhead comparison:

Plain CoAP (UDP port 5683):
  Request:  4-byte header + payload
  Response: 4-byte header + payload
  Latency:  1 RTT (~20ms LAN)

Secure CoAP (DTLS port 5684):
  First request:  DTLS handshake (4-6 messages) + encrypted payload
  Latency:  3 RTTs for handshake + 1 RTT for data = ~80ms first request
  Overhead: 13 bytes DTLS record header per message

  Subsequent requests (session reuse):
  Latency:  1 RTT + ~5ms DTLS overhead = ~25ms
  Overhead: 13 bytes header + 8-16 bytes MAC (integrity)

Energy cost of DTLS handshake (one-time per session):
  6 messages x 100 bytes avg x 10 mA TX = 0.6 mAs
  Amortized over 1000 messages: 0.0006 mAs each (negligible)
  Keep DTLS sessions alive to avoid repeated handshakes.

DTLS security has real cost, but it’s manageable with session reuse. Let’s quantify:

DTLS handshake energy (PSK mode): - ClientHello: 120 bytes @ 20 mA TX for 4.8 ms = 0.027 µAh - ServerHello + ChangeCipherSpec: 140 bytes RX = 0.024 µAh - Client Finished: 60 bytes TX = 0.013 µAh - Total handshake: 0.064 µAh (one-time cost)

Per-message overhead (after handshake): - DTLS record header: 13 bytes - Encrypted CoAP message: 20 bytes → 32 bytes (16-byte AES-CCM block alignment) - MAC tag: 8 bytes - Total: \(13 + 32 + 8 = 53\) bytes vs 20 bytes plain CoAP

Amortization over 24 hours (1 reading/minute): \[\text{Messages/day} = 24 \times 60 = 1{,}440\] \[\text{Handshake cost/message} = \frac{0.064}{1{,}440} = 0.0000444 \text{ µAh (negligible)}\]

Per-message transmission (53 bytes secured vs 20 bytes plain, both @ 250 kbps, 20 mA TX; energy in µAh \(= I_{mA}\times t_{ms}/3{,}600\)): \[t_{secure} = \frac{53 \times 8}{250{,}000} = 1.696 \text{ ms} \Rightarrow \frac{20\times1.696}{3{,}600} = 0.009422 \text{ µAh}\] \[t_{plain} = \frac{20 \times 8}{250{,}000} = 0.640 \text{ ms} \Rightarrow \frac{20\times0.640}{3{,}600} = 0.003556 \text{ µAh}\]

Daily DTLS cost: \(0.064 + (1{,}440 \times 0.009422) = 13.63\) µAh \(= 0.01363\) mAh Daily plain CoAP: \(1{,}440 \times 0.003556 = 5.12\) µAh \(= 0.00512\) mAh (20-byte messages)

Security overhead: \(\frac{0.01363}{0.00512} = 2.66\times\) or 166% more energy – tracking the 53:20 byte ratio, since energy at fixed rate/current is set by bytes moved. Still an acceptable trade-off for HIPAA compliance: on the chapter’s own 220 mAh CR2032 default, idealised life is 44.2 yr secured vs 118 yr plain – both far past any realistic deployment target.

Broker BexCheckpoint: Secure Transport

You now know:

  • Plain CoAP uses port 5683, while CoAPs uses DTLS on port 5684.
  • DTLS protects headers, URIs, options, payload, integrity, and authentication; application-layer encryption protects only the payload.
  • Session reuse matters: the chapter’s DTLS example amortizes a 0.064 µAh handshake over 1,440 messages/day, while each protected 20-byte reading grows to 53 bytes.

15.7 Interactive Security Calculators

Explore the energy costs and trade-offs of CoAP security options with these interactive calculators:

15.7.1 DTLS Energy Cost Calculator

15.7.2 Battery Life Estimator: CON vs NON

15.7.3 Protocol Overhead Comparison

15.7.4 Block Transfer Size Optimizer

15.7.5 Security vs Battery Trade-off Analyzer

Key Insights from Calculators:

  • DTLS Energy: Handshake is expensive (~0.064 mAh) but amortized over many messages becomes negligible
  • CON vs NON: Confirmable messages use 2.5× more energy due to ACK requirement - use NON for telemetry
  • Protocol Overhead: CoAP has 16.7% overhead vs HTTP’s 90.9% for small payloads - massive difference for IoT
  • Block Transfer: Smaller MTUs (6LoWPAN) require smaller blocks but increase round trips - find the balance
  • Security Trade-off: DTLS adds 15-80% overhead depending on message frequency - but required for compliance

15.8 Real-World Applications

With the transport and battery costs in mind, the next step is matching message type and security mode to the application domain.

15.8.1 Smart Energy

Smart Meters:

  • Read consumption (GET)
  • Update tariffs (PUT)
  • Control load (POST)
coap://meter.home/consumption
coap://meter.home/tariff
coap://meter.home/switch

15.8.2 Building Automation

HVAC Control:

  • Read temperature
  • Set thermostat
  • Schedule modes

Lighting:

  • Query status
  • Control brightness
  • Manage groups (multicast)

15.8.3 Industrial IoT

Sensor Networks:

  • Poll sensor values
  • Configure sampling rates
  • Firmware updates

Asset Tracking:

  • Location queries
  • Status updates
  • Alert notifications
Try It: CoAP Application Scenario Builder

Select a real-world IoT application domain and configure CoAP parameters. The tool recommends optimal message types, security modes, and observe patterns based on your deployment requirements.

Broker BexCheckpoint: Application Fit

You now know:

  • Smart energy readings need CON when missed billing data would cause financial errors.
  • Building automation can mix NON readings with CON setpoint changes, while industrial firmware blocks need CON with Block2.
  • The scenario builder keeps the same deployment questions visible: device count, messages per day, security mode, observe pattern, and battery estimate.

15.9 Lab Exercise

Goal: Build CoAP temperature monitoring system

Hardware (or simulate):

  • ESP32 board
  • DHT22 sensor

Tasks:

  1. Setup CoAP Server (on Raspberry Pi or PC):
    • Install aiocoap
    • Create temperature resource
    • Add humidity resource
    • Implement GET and PUT methods
  2. Create ESP32 CoAP Client:
    • Connect to Wi-Fi
    • Send GET requests every 30s
    • Display responses on Serial Monitor
  3. Add Observe Pattern:
    • Server notifies on temperature change
    • Client receives push updates
    • No polling needed
  4. Implement Multicast Discovery:
    • Client broadcasts to find servers
    • Servers respond with capabilities

Challenge: Add CoAP-to-MQTT bridge for cloud integration

15.10 When to Use CoAP

15.10.1 Choose CoAP when:

  • Constrained devices (8-bit MCU, limited RAM)
  • Low power critical (battery-operated)
  • Request/Response pattern fits your needs
  • RESTful API desired
  • Multicast needed for device discovery
  • IPv6 networks (6LoWPAN)
  • Direct device-to-device communication

15.10.2 Choose HTTP when:

  • Web browser access needed
  • Infrastructure already HTTP-based
  • Devices not constrained
  • Complex authentication required

15.10.3 Choose MQTT when:

  • Publish/Subscribe pattern needed
  • Central broker acceptable
  • Many-to-many communication
  • QoS levels important
  • Topic-based routing preferred

15.11 Common Implementation Pitfalls

Once the protocol choice is made, reliability depends on small implementation details that UDP will not fix for you.

Pitfall: Forgetting to Handle CoAP Token Matching in Observe Responses

The Mistake: Implementing CoAP Observe without properly tracking tokens, causing clients to misattribute notifications to the wrong resources when observing multiple endpoints simultaneously.

Why It Happens: Developers familiar with HTTP assume response correlation is automatic. In CoAP, the token (0-8 bytes) links requests to responses. When observing /temperature and /humidity concurrently, both notification streams arrive on the same UDP socket and must be matched by token.

The Fix: Maintain a token-to-resource mapping for all active observations:

# BAD: Ignoring tokens
def on_notification(response):
    print(f"Got value: {response.payload}")  # Which resource?

# GOOD: Token-based dispatch
observations = {}  # token -> resource_uri mapping

def observe_resource(uri):
    token = generate_unique_token()
    observations[token] = uri
    send_observe_request(uri, token)

def on_notification(response):
    uri = observations.get(response.token)
    if uri:
        print(f"Resource {uri} = {response.payload}")
    else:
        print(f"Unknown token, sending RST")
        send_reset(response.mid)

Use unique tokens per observation (e.g., 4-byte random values) and clean up mappings when observations are cancelled or reset.

Pitfall: CoAP Retransmission Backoff Flooding the Network

The Mistake: Using aggressive fixed-interval retransmissions for CON messages instead of exponential backoff, causing network congestion when packet loss occurs.

Why It Happens: Developers implement simple “retry every 2 seconds” logic without realizing that CoAP specifies exponential backoff (RFC 7252). On a lossy wireless network with 20% packet loss, aggressive retries amplify traffic by 5x and worsen congestion.

The Fix: Implement RFC 7252 compliant retransmission with exponential backoff:

# BAD: Fixed interval retry (network flooding)
RETRY_INTERVAL = 2.0  # seconds
for attempt in range(MAX_RETRIES):
    send_message(msg)
    if wait_for_ack(RETRY_INTERVAL):
        break

# GOOD: Exponential backoff per RFC 7252
ACK_TIMEOUT = 2.0      # Initial timeout (2 seconds)
ACK_RANDOM_FACTOR = 1.5  # Randomization factor
MAX_RETRANSMIT = 4      # Maximum retries

def coap_transmit(msg):
    timeout = ACK_TIMEOUT * (1 + random.random() * (ACK_RANDOM_FACTOR - 1))
    for attempt in range(MAX_RETRANSMIT + 1):
        send_message(msg)
        if wait_for_ack(timeout):
            return True
        timeout *= 2  # Double timeout each retry: 2s, 4s, 8s, 16s
    return False  # Give up after ~45 seconds total

With proper backoff: first retry at 2-3s, second at 4-6s, third at 8-12s, fourth at 16-24s. Total worst-case wait: ~45 seconds before declaring failure.

15.12 CoAP Implementation Patterns

Understanding CoAP implementation through message exchanges, resource patterns, and performance characteristics:

Diagram showing CoAP message types (CON, NON, ACK, RST), RESTful methods (GET, POST, PUT, DELETE), and the Observe pattern for push notifications with sequence numbers
Figure 15.1: CoAP Message Types, RESTful Methods, and Observe Pattern Overview
CoAP implementation involves message type selection, RESTful operations, and the Observe pattern for subscriptions

15.12.1 CoAP vs HTTP vs MQTT: Performance Comparison

Overhead Analysis (20-byte temperature reading):

  • CoAP: 4-byte header, 24 total bytes, 16.7% overhead, UDP transport, ~50 ms latency
  • HTTP/1.1: ~200-byte header, 220 total bytes, 90.9% overhead, TCP transport, ~200 ms latency
  • MQTT: 2-byte header, 22 total bytes, 9.1% overhead, TCP transport, ~150 ms latency

Energy Consumption (24 hours, readings every 30s):

  • CoAP: 2,880 messages/day, 153 mJ per message, 441 mWh total, baseline battery life
  • HTTP: 2,880 messages/day, 190 mJ per message, 547 mWh total, about 24% less battery life
  • MQTT: 2,880 messages/day, 163 mJ per message, 468 mWh total, about 6% less battery life
Practical CoAP Implementation Resources

Official Libraries and Tools:

Testing and Debugging Tools:

  • coap-client (libcoap): Command-line CoAP client for testing

    # GET request
    coap-client -m get coap://localhost/temperature
    
    # POST with payload
    coap-client -m post coap://localhost/sensor -e "22.5"
    
    # Observe resource
    coap-client -m get -s 60 coap://localhost/temperature
  • Copper (Cu): Firefox/Chrome plugin for CoAP browsing (deprecated but useful for learning)

  • Wireshark: CoAP dissector included (filter: coap)

  • nRF Connect CoAP: Mobile app for testing CoAP servers

Example Implementation Patterns:

Pattern 1: Sensor Reading (NON message)

Sensor → Gateway:  CON GET /temperature
Gateway → Sensor:  ACK 2.05 Content
                   Payload: 22.5°C

Overhead: 2 messages, ~50 bytes total
Latency: 1 RTT (~20-50ms on local network)
Reliability: Guaranteed (CON requires ACK)

Pattern 2: Frequent Updates (Observe)

Client → Server:   CON GET /temperature, Observe: 0
Server → Client:   ACK 2.05 Content, Observe: 12
                   Initial value: 22.5°C

[30 seconds later]
Server → Client:   CON 2.05 Content, Observe: 13
                   Updated value: 23.1°C

[30 seconds later]
Server → Client:   CON 2.05 Content, Observe: 14
                   Updated value: 22.9°C

Overhead: 1 subscribe + N notifications
Battery savings: Avoid polling every 30s

Pattern 3: Multicast Discovery

Client → FF02::FD: NON GET /.well-known/core
Device1 → Client:  NON 2.05 Content
                   </temperature>,</humidity>
Device2 → Client:  NON 2.05 Content
                   </pressure>,</light>

Use case: Discover all CoAP devices on local network
Result: List of available resources from all devices

Broker BexCheckpoint: Operational Debugging

You now know:

  • Observe clients must map each 0-8 byte token to the resource being observed, or concurrent notifications can be misattributed.
  • CON retransmission should follow exponential backoff with ACK_TIMEOUT 2.0, ACK_RANDOM_FACTOR 1.5, MAX_RETRANSMIT 4, and about 45 seconds worst-case wait.
  • Response code classes divide the first digit by role: 2.xx success, 4.xx client error, and 5.xx server error.

15.12.2 CoAP Response Code Categories

  • Success (2.xx): 2.01-2.05, meaning the request succeeded. Examples: 2.05 Content for a successful GET and 2.04 Changed for a successful PUT.
  • Client Error (4.xx): 4.00-4.15, meaning the client made an error. Examples: 4.04 Not Found for a bad URI and 4.01 Unauthorized when authentication is required.
  • Server Error (5.xx): 5.00-5.05, meaning the server failed. Examples: 5.00 Internal Server Error and 5.03 Service Unavailable.
Try It: CoAP Response Code Reference

Look up CoAP response codes interactively. Select a category or type a code to see its meaning, HTTP equivalent, and when you would encounter it in practice.

The troubleshooting material below reuses the same pattern: identify the failed layer first, then choose the smallest fix that matches that layer.

15.12.3 Hands-On Learning Resources

Interactive Tutorials:

  1. Eclipse Californium CoAP Demo Server: coap://californium.eclipseprojects.io:5683/
    • Try: coap-client -m get coap://californium.eclipseprojects.io:5683/.well-known/core
  2. CoAP.me Public Test Server: coap://coap.me:5683/
    • Test GET, POST, PUT, DELETE without setting up your own server

Code Examples:

Video Tutorials:

  • Building IoT with CoAP - IoT Developer Conference
  • CoAP Deep Dive - Eclipse Foundation
  • Constrained Devices and CoAP - IETF Educational Series

15.13 Troubleshooting Common Issues

Common Problems and Solutions
  • Request times out: UDP packet loss or no ACK. Increase retransmission timeout, use CON messages instead of NON, and check network quality.
  • Confirmable message never acknowledged: Server down or wrong endpoint. Verify the server is running and confirm the CoAP URI format coap://host:port/path.
  • Block transfer fails mid-stream: MTU too large or packet fragmentation. Reduce block size from 1024 bytes to 512 or 256 and check network MTU settings.
  • Observe notifications stop: Server crashed or network partition. Re-establish the observe relationship and add client-side timeout detection.
  • Multicast discovery finds no devices: Wrong multicast address or routing. Use FF02::FD for link-local or FF05::FD for site-local and check IPv6 multicast routing.
  • DTLS handshake fails: PSK mismatch or certificate error. Verify pre-shared keys match exactly and check certificate validity for certificate mode.
  • Response code 4.04 Not Found: Wrong resource path. Check URI case sensitivity and verify the resource exists on the server.
  • Response code 4.05 Method Not Allowed: Unsupported method on the resource. Confirm that the resource supports GET, POST, PUT, or DELETE as expected.

Debug Checklist:

  • Connection and Discovery Issues:
  • Request/Response Problems:
  • Observe and Block Transfer Issues:
  • Performance and Reliability:
  • Security (DTLS) Issues:

Common Error Codes:

  • 2.01 Created: Resource successfully created (POST response)
  • 2.02 Deleted: Resource successfully deleted
  • 2.03 Valid: Resource still valid (cache validation)
  • 2.04 Changed: Resource successfully updated (PUT response)
  • 2.05 Content: Resource content returned (GET response)
  • 4.00 Bad Request: Malformed request or invalid options
  • 4.01 Unauthorized: Authentication required or failed
  • 4.04 Not Found: Resource does not exist
  • 4.05 Method Not Allowed: HTTP method not supported on resource
  • 5.00 Internal Server Error: Server encountered error processing request
  • 5.03 Service Unavailable: Server temporarily unable to handle request

Tools for Debugging:

  • libcoap tools: coap-client and coap-server for testing
  • Copper (Cu): Firefox/Chrome plugin for CoAP browsing (deprecated but useful)
  • Wireshark: CoAP dissector for packet analysis (filter: coap)
  • tcpdump: Capture UDP packets (tcpdump -i any port 5683 -vv)
  • Eclipse Californium: Java-based CoAP library with extensive logging
  • aiocoap: Python library with good debugging output

15.14 Common Pitfalls

Common Pitfall: CoAP Message Size Exceeds MTU

The mistake: Sending CoAP payloads larger than the network MTU (typically 1280 bytes for IPv6, often much smaller for constrained networks like 6LoWPAN with 127-byte frames), causing silent packet drops or fragmentation failures.

Symptoms:

  • Large GET responses never arrive at the client
  • PUT/POST requests with substantial payloads fail intermittently
  • Works on local Wi-Fi but fails over 6LoWPAN or constrained networks
  • Wireshark shows fragmented packets but no reassembled response

Why it happens: CoAP runs over UDP, which doesn’t handle fragmentation gracefully: - IPv6 minimum MTU: 1280 bytes, but CoAP payload should be much smaller - 6LoWPAN frame: 127 bytes maximum, ~80 bytes after headers - UDP fragmentation: If any fragment is lost, entire message is lost - No automatic retransmission of individual fragments

The fix:

# Use CoAP Block-wise Transfer (RFC 7959) for large payloads
from aiocoap import Message, Context
from aiocoap.numbers.codes import GET

async def get_large_resource(uri):
    context = await Context.create_client_context()

    # Request with Block2 option - library handles chunking
    request = Message(code=GET, uri=uri)
    # Block size: 64 bytes (szx=2), 128 bytes (szx=3), 256 bytes (szx=4)
    # Smaller blocks = more round trips but works on constrained networks

    response = await context.request(request).response
    return response.payload  # Library reassembles blocks automatically
// Embedded C: Check payload size before sending
#define COAP_MAX_PAYLOAD_6LOWPAN  64   // Safe for 802.15.4
#define COAP_MAX_PAYLOAD_UDP      1024 // Safe for most networks

size_t max_payload = is_constrained_network() ?
    COAP_MAX_PAYLOAD_6LOWPAN : COAP_MAX_PAYLOAD_UDP;

if (payload_len > max_payload) {
    // Use Block1 (request) or Block2 (response) transfer
    return coap_send_blockwise(payload, payload_len, max_payload);
}

Prevention: Design payloads to fit in 64-128 bytes for 6LoWPAN networks. Use Block-wise Transfer for firmware updates, large configurations, or file downloads. Test on the actual constrained network, not just Wi-Fi.

Common Pitfall: Misusing CON vs NON Message Types

The mistake: Using Confirmable (CON) messages for all communications because “reliability is important,” leading to excessive battery drain and network congestion, or using Non-Confirmable (NON) for critical commands where delivery must be guaranteed.

Symptoms:

  • Battery-powered sensors lasting weeks instead of years
  • Unnecessary retransmission storms when network is lossy
  • Commands occasionally not reaching actuators (lights, locks)
  • High latency due to waiting for ACKs on every message

Why it happens: Developers often misunderstand the trade-offs: - CON overuse: HTTP background makes developers expect reliability for everything - NON overuse: Trying to maximize battery life without considering message importance - No hybrid strategy: Treating all messages the same regardless of criticality

The fix:

from aiocoap import Message, Context
from aiocoap.numbers.codes import GET, PUT
from aiocoap.numbers.types import CON, NON

# NON for periodic telemetry (loss acceptable, battery critical)
async def send_temperature_reading(temp):
    request = Message(
        code=PUT,
        mtype=NON,  # Fire-and-forget, ~5x better battery life
        uri='coap://server/sensors/temp',
        payload=f'{temp}'.encode()
    )
    await context.request(request).response

# CON for critical commands (must know if it worked)
async def unlock_door():
    request = Message(
        code=PUT,
        mtype=CON,  # Need acknowledgment for security
        uri='coap://door/lock',
        payload=b'unlock'
    )
    try:
        response = await context.request(request).response
        return response.code.is_successful()
    except Exception:
        return False  # Command failed, alert user

# CON for configuration changes (must be applied)
async def update_reporting_interval(seconds):
    request = Message(
        code=PUT,
        mtype=CON,  # Config must be confirmed
        uri='coap://sensor/config/interval',
        payload=str(seconds).encode()
    )
    return await context.request(request).response

Decision matrix: - Periodic sensor readings: Use NON for routine telemetry; do not use CON. - Alert or alarm notifications: Use CON so the receiver must acknowledge delivery. - Device commands (on/off): Use CON because actuation commands need confirmation. - Configuration updates: Use CON so applied settings are confirmed. - Status queries: Depends on how critical the response is. - Firmware chunks: Use CON with Block2 acknowledgements.

Prevention: Default to NON for periodic telemetry. Use CON only for commands, configurations, and alerts. Implement a message priority system that selects type based on criticality.

Try It: CON vs NON Message Type Advisor

Describe your IoT scenario and this tool recommends whether to use Confirmable (CON) or Non-confirmable (NON) messages, with energy and reliability analysis.

15.15 Under the Hood: CoAP Security Trade-offs

The final deep-dive shifts from syntax and debugging to lifecycle cost: what happens when devices sleep, reboot, move through gateways, or need stronger end-to-end protection than a single DTLS hop can provide.

Scenario: A hospital deploys 200 wireless vital signs monitors (heart rate, SpO2, temperature) on patients. Each device uses CoAP to report readings every 30 seconds to a centralized gateway. HIPAA compliance requires encryption of all patient data in transit.

Comparing security options:

Option A: Application-layer encryption (AES-128 on payload):

CoAP message structure:
  Plain CoAP header: 4 bytes
  Token: 2 bytes
  Uri-Path option: 12 bytes ("/patient/042/vitals")
  Content-Format option: 2 bytes
  Payload marker: 1 byte
  Encrypted JSON: 64 bytes (AES-128-CBC encrypted {"hr":72,"spo2":98,"temp":37.1})
  Total: 85 bytes

Security analysis:
  ✓ Payload encrypted
  ✗ URI path visible: "/patient/042/vitals" exposes patient ID
  ✗ Content-Format visible: Reveals JSON structure
  ✗ Message timing visible: Attacker knows when patient 042 has events
  ✗ HIPAA violation: Metadata is PHI (Protected Health Information)

Option B: DTLS with PSK (Transport-layer encryption):

DTLS handshake (one-time per session):
  ClientHello: 120 bytes
  ServerHello: 140 bytes
  ChangeCipherSpec: 40 bytes
  Finished: 60 bytes
  Total handshake: 360 bytes (amortized over 2,880 messages/day = 0.125 bytes/message)

Encrypted CoAP message:
  DTLS record header: 13 bytes
  Encrypted CoAP message: 85 bytes (entire message encrypted)
  DTLS MAC: 16 bytes (integrity check)
  Total: 114 bytes per message

Security analysis:
  ✓ Entire message encrypted (header, URI, options, payload)
  ✓ No metadata leakage
  ✓ HIPAA compliant
  ✓ Replay protection (DTLS sequence numbers)
  ✓ Mutual authentication (optional with certificates)

Energy cost comparison (CR2032 coin cell, 220 mAh):

Application-layer AES:

Per message energy:
  CoAP NON message: 1.2 mJ (radio time)
  AES-128 encryption: 0.3 mJ (software, no hw acceleration)
  Total: 1.5 mJ per message

Daily energy: 2,880 messages × 1.5 mJ = 4.32 J/day
Battery life: (220 mAh × 3V × 3600 s/h) ÷ 4.32 J/day = ~550 days

DTLS with PSK:

Per message energy:
  DTLS handshake (amortized): 45 mJ / 2,880 = 0.016 mJ
  CoAP NON message (encrypted): 1.3 mJ (radio time, +8% for larger message)
  DTLS encryption/MAC: 0.4 mJ
  Total: 1.72 mJ per message

Daily energy: 2,880 × 1.72 mJ = 4.95 J/day
Battery life: 2,376 J ÷ 4.95 J/day = ~480 days

Decision: DTLS with PSK

Reasoning:

  • HIPAA compliant: Encrypts all PHI including metadata
  • Battery cost acceptable: 15% more energy for full protection
  • Simplified key management: PSK easier than distributing certificates to 200 devices
  • Industry standard: DTLS is the recommended CoAP security mechanism (RFC 7252)

Implementation:

# Server setup (Python aiocoap with tinydtls)
import aiocoap
import aiocoap.credentials

server_credentials = aiocoap.credentials.CredentialsMap()
server_credentials[':dtls-psk'] = {
    'device-042': b'32BytePresharedKeyForPatient042!!',  # 32-byte PSK
    'device-043': b'32BytePresharedKeyForPatient043!!',
}

context = await aiocoap.Context.create_server_context(
    bind=('::', 5684),  # CoAPS port
)
context.server_credentials = server_credentials

15.16 Decision Framework: CoAP Security Configuration Selection

Choose the right security configuration based on deployment requirements:

Factor No Security (CoAP) DTLS-PSK (Pre-Shared Key) DTLS-Cert (Certificates) OSCORE (Object Security)
Encryption strength None AES-128 AES-128/256 AES-128
Authentication None Symmetric key Asymmetric (PKI) Symmetric key
Handshake overhead 0 bytes 360 bytes (one-time) 2-4 KB (one-time) 0 bytes (pre-provisioned)
Per-message overhead 0 bytes +29 bytes (header+MAC) +29 bytes +8-16 bytes
Key distribution N/A Manual or secure channel PKI infrastructure Pre-provisioned
End-to-end security No No (broker can decrypt) No (broker can decrypt) Yes (survives proxies)
NAT traversal Easy (UDP) Easy (UDP) Easy (UDP) Easy (UDP)
Best for Lab testing only IoT devices, constrained networks Enterprise, device identity critical Proxy/gateway networks

Decision tree:

  1. Is data sensitive or regulated (PII, HIPAA, financial)? → No: Consider plain CoAP (but use encryption anyway as best practice) → Yes: Continue

  2. Do you have PKI infrastructure (CA, certificate management)? → Yes: DTLS with Certificates (strong identity verification) → No: Continue

  3. Can you securely pre-provision keys to devices during manufacturing? → Yes: DTLS-PSK or OSCORE (both use symmetric keys) → No: You need to set up key distribution mechanism first

  4. Do messages pass through untrusted proxies or gateways? → Yes: OSCORE (end-to-end, proxy can’t decrypt) → No: DTLS-PSK (simpler, transport-layer security)

  5. Is per-message overhead critical (<10 bytes headroom)? → Yes: OSCORE (8-16 bytes vs DTLS’s 29 bytes) → No: DTLS-PSK (easier to debug, standard TLS tools work)

Hybrid approach: Many deployments use DTLS from device to gateway (transport security), then OSCORE from gateway to cloud (end-to-end security). This balances ease of debugging (DTLS is standard TLS) with proxy security (OSCORE protects against compromised gateways).

Common Mistake: Reusing DTLS Session Across Deep Sleep Cycles

The Error: Configuring battery-powered IoT devices to establish a DTLS session once, then sleep/wake multiple times expecting the session to remain valid.

Why It Happens: Developers assume DTLS sessions persist like HTTP cookies. The device wakes, sends an encrypted CoAP message, and expects the server to accept it. However, DTLS sessions have state (sequence numbers, cipher state) that’s lost when the device power-cycles RAM.

Real-World Impact: A smart agriculture deployment of 500 soil moisture sensors (ESP32, deep sleep mode):

Attempted implementation (broken):

// WRONG: Trying to reuse DTLS session after deep sleep
void setup() {
  dtls_session = dtls_new_session();
  dtls_connect(dtls_session, SERVER_IP, 5684);  // Handshake: 360 bytes, 45 mJ
}

void loop() {
  float moisture = read_sensor();
  dtls_send(dtls_session, coap_message);  // Sends with stale sequence number
  esp_deep_sleep(3600 * 1000000);  // Sleep 1 hour (RAM lost!)
}
// After waking, `dtls_session` pointer is invalid, sequence numbers reset
// Server rejects messages with "Bad MAC" or "Decrypt error"

Symptoms:

  • 95% of messages after first wake rejected by server
  • Logs show: DTLS decrypt error: sequence number mismatch
  • Devices retry handshake, draining battery
  • Battery life: 3 months (expected: 18 months)

Root cause: DTLS maintains per-session state in RAM: - Cipher context: Encryption keys derived from handshake - Sequence numbers: Anti-replay protection (both sides increment per message) - Epoch: Changes on renegotiation

When ESP32 deep sleeps, RAM is powered off. On wake, all session state is lost. The device can’t resume the DTLS session.

The Fix (Option 1): Full handshake on every wake:

void loop() {
  // Establish NEW DTLS session every wake
  dtls_session_t *session = dtls_new_session();
  dtls_connect(session, SERVER_IP, 5684);  // Handshake cost: 45 mJ

  float moisture = read_sensor();
  dtls_send(session, coap_message);  // Message cost: 1.7 mJ

  dtls_close(session);  // Clean shutdown
  esp_deep_sleep(3600 * 1000000);  // Sleep 1 hour
}
// Battery cost: (45 + 1.7) mJ × 24/day = 1,121 mJ/day
// Battery life: ~6 months (acceptable for rechargeable solar)

The Fix (Option 2): OSCORE (session-less security):

// OSCORE: Pre-provisioned keys, no handshake needed
void loop() {
  float moisture = read_sensor();

  // Encrypt with OSCORE (uses pre-shared master secret)
  coap_message = oscore_encrypt(moisture, MASTER_SECRET, sequence_number++);
  coap_send(coap_message, SERVER_IP, 5683);  // Plain CoAP port, encrypted payload

  esp_deep_sleep(3600 * 1000000);  // Sleep 1 hour
}
// Battery cost: 1.8 mJ × 24/day = 43 mJ/day (no handshake!)
// Battery life: ~18 months (26× improvement vs full DTLS handshake every wake)

The Fix (Option 3): DTLS session resumption with stored state:

// Store DTLS session state in RTC memory (survives deep sleep on ESP32)
RTC_DATA_ATTR uint8_t session_state[256];  // RTC_DATA_ATTR = retained during deep sleep

void loop() {
  dtls_session_t *session;

  if (is_first_boot()) {
    // Full handshake on first boot only
    session = dtls_new_session();
    dtls_connect(session, SERVER_IP, 5684);
    dtls_save_session(session, session_state);  // Persist to RTC memory
  } else {
    // Resume from RTC memory on subsequent wakes
    session = dtls_restore_session(session_state);
  }

  float moisture = read_sensor();
  dtls_send(session, coap_message);

  dtls_save_session(session, session_state);  // Update sequence numbers
  esp_deep_sleep(3600 * 1000000);
}
// Battery cost: 45 mJ (first boot) + 1.8 mJ × 24/day = ~43 mJ/day amortized
// Battery life: ~18 months (resumption avoids handshake overhead)

Decision matrix:

  • Handshake every wake: Handshake on every message (about 24/day), ~45 mJ per message, about 6 months of battery life, not recommended.
  • OSCORE: No handshake after provisioning, ~1.8 mJ per message, about 18 months of battery life, best for deep-sleep devices.
  • Session resumption (RTC memory): Handshake once per reboot, ~1.8 mJ per message, about 18 months of battery life, best for ESP32 with RTC memory.
  • Plain CoAP (no security): No handshake, ~1.2 mJ per message, about 24 months of battery life, suitable only for lab testing.

Prevention: For deep-sleep devices, default to OSCORE unless you have a specific reason to use DTLS. If DTLS is required, implement session resumption with RTC-persisted state.

Broker BexCheckpoint: Security Trade-offs

You now know:

  • In the hospital wearable example, DTLS-PSK protects metadata that payload-only AES leaves visible, trading about 480 days of battery life for HIPAA-suitable protection instead of about 550 days.
  • For 500 ESP32 soil sensors in deep sleep, replay and sequence state can be lost; the chapter shows 95% rejection after first wake when state is reused incorrectly.
  • OSCORE or RTC-backed DTLS resumption can avoid a 45 mJ handshake on every wake and recover about 18 months of battery life instead of about 6 months.

15.17 Concept Relationships

How CoAP security and applications connect to broader IoT security concepts:

DTLS Security Builds On:

  • TLS Fundamentals - Transport Layer Security adapted for UDP datagrams
  • Symmetric Cryptography - AES-128/256 cipher suites
  • Key Management - Pre-shared keys vs certificate infrastructure

DTLS vs Application Encryption:

  • End-to-End Security - Transport vs payload-only protection
  • Metadata Privacy - HIPAA compliance requires URI encryption
  • Security Layering - Multiple protection levels

Real-World Applications:

  • Smart Energy AMI - Meter reading with DTLS authentication
  • Building Automation - HVAC/lighting RESTful control
  • Industrial IoT - Asset tracking and sensor networks
  • Healthcare Wearables - HIPAA-compliant vital signs

Implementation Pitfalls Connect To:

  • Token Management - Matching Observe notifications
  • Retransmission Logic - Exponential backoff vs fixed intervals
  • MTU Awareness - Block transfer for large payloads

CoAP Security Enables:

  • Zero Trust IoT - Mutual authentication with certificates
  • Privacy-Preserving Analytics - Encrypted telemetry aggregation
  • Secure OTA Updates - DTLS-protected block transfers

15.18 See Also

Core CoAP Learning:

Security Deep Dives:

  • DTLS Protocol - Handshake, cipher suites, session management
  • OSCORE for CoAP - End-to-end object security (RFC 8613)
  • Pre-Shared Key Management - Device manufacturing and distribution
  • Certificate-Based Authentication - X.509 for device identity

Implementation Resources:

Application Examples:

  • Smart Meter CoAP - DTLS-PSK for utilities
  • Medical Device Gateway - HIPAA-compliant CoAPs
  • Industrial Sensor Network - Certificate-based mutual auth

Protocol Comparisons:

  • MQTT TLS vs CoAP DTLS - TCP vs UDP security trade-offs
  • HTTPS vs CoAPS - Web vs IoT security patterns
  • Security Protocol Selection - Decision frameworks

Debugging & Testing:

Advanced Topics:

  • DTLS Session Resumption - Avoiding handshake overhead
  • Hardware Security Modules - Protecting private keys
  • DTLS Connection ID - RFC 9146 for NAT rebinding

Specifications:

Knowledge Check: Matching and Ordering

Test your understanding of CoAP security concepts with the quizzes below.

Label the Diagram

15.19 What’s Next

Now that you understand CoAP security and implementation patterns, continue with these related chapters:

  • CoAP DTLS and OSCORE Security Contracts: Security-mode selection, proxy termination, OSCORE inner/outer options, replay guards, and credential tradeoffs.
  • CoAP Practice and Exercises: Visual reference gallery, worked examples, and hands-on exercises. Reinforce and test everything covered in this chapter with real calculations and problem sets.
  • CoAP Observe Extension: Server-push notifications and subscription lifecycle. Understand how observe patterns interact with DTLS sessions and token management.
  • CoAP Methods and Features: RESTful operations, block transfer, and multicast. Deepen knowledge of the CoAP features used in the real-world applications in this chapter.
  • CoAP Fundamentals and Architecture: Message types, reliability, and header structure. Review the foundations of CON vs NON and token matching discussed in the implementation pitfalls.
  • IoT Security Threats: Attack vectors and threat modelling for IoT. Learn what DTLS protects against: eavesdropping, replay, and man-in-the-middle attacks.
  • Cryptography for IoT: AES, key management, and cipher suites. Understand the cryptographic primitives that underpin DTLS-PSK and DTLS-Certificate modes.

15.20 Summary

CoAP security decisions depend on the device, transport, and deployment model. DTLS, OSCORE, credentials, gateways, and provisioning all need to be matched to battery budget, multicast needs, and operational update paths.

15.21 Key Takeaway

Do not treat CoAP security as a final wrapper. Choose the security model while designing resources and deployment flow, because credential provisioning, group communication, and gateway translation affect the whole architecture.