1216  CoAP Advanced Features: Block Transfer, Discovery, and TCP

1216.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Implement Block-wise Transfer: Handle large payloads (firmware updates) over CoAPโ€™s UDP transport
  • Configure Resource Discovery: Set up .well-known/core with CoRE Link Format
  • Choose CoAP Transport: Decide between UDP, TCP, and WebSockets based on requirements
  • Deploy DTLS Security: Secure CoAP communications with appropriate cipher suites

1216.2 Prerequisites

Before diving into this chapter, you should be familiar with:

1216.3 Block-wise Transfer (RFC 7959)

TipMinimum Viable Understanding: Block-wise Transfer

Core Concept: CoAP messages are limited by UDP MTU (1280-1500 bytes), but firmware updates or images require 100KB+ payloads. Block-wise transfer splits large payloads into sequential blocks with automatic reassembly.

Why It Matters: Without block transfer, CoAP couldnโ€™t handle firmware OTA updates, large configuration files, or image uploads - essential for production IoT deployments.

Key Takeaway: Use Block2 for large response payloads (downloads), Block1 for large request payloads (uploads). Each block includes block number, more-flag, and size.

1216.3.1 Block2: Large Response Payloads

Used when server sends large data to client (e.g., firmware download):

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: GET /firmware/update.bin
    Note right of C: Block2: NUM=0, SIZE=1024

    S->>C: 2.05 Content
    Note left of S: Block2: NUM=0, M=1<br/>Payload: [1024 bytes]

    C->>S: GET /firmware/update.bin
    Note right of C: Block2: NUM=1, SIZE=1024

    S->>C: 2.05 Content
    Note left of S: Block2: NUM=1, M=1<br/>Payload: [1024 bytes]

    Note over C,S: ... continues for 256KB / 1KB = 256 blocks ...

    C->>S: GET /firmware/update.bin
    Note right of C: Block2: NUM=255, SIZE=1024

    S->>C: 2.05 Content
    Note left of S: Block2: NUM=255, M=0<br/>Payload: [final bytes]<br/>(M=0 = last block)

Block2 option format: - NUM: Block number (0-based sequence) - M: More flag (1 = more blocks follow, 0 = last block) - SIZE: Block size in bytes (16, 32, 64, 128, 256, 512, 1024)

1216.3.2 Block1: Large Request Payloads

Used when client sends large data to server (e.g., uploading logs):

Client -> Server: PUT /logs/daily
                  Block1: NUM=0, M=1, SIZE=512
                  Payload: [first 512 bytes]

Server -> Client: 2.31 Continue
                  Block1: NUM=0, M=1, SIZE=512

Client -> Server: PUT /logs/daily
                  Block1: NUM=1, M=1, SIZE=512
                  Payload: [next 512 bytes]

... until final block with M=0 ...

Server -> Client: 2.04 Changed

1216.3.3 Implementation with Reliability

async def download_firmware(uri, output_file):
    block_num = 0
    block_size = 1024
    consecutive_failures = 0

    with open(output_file, 'wb') as f:
        while True:
            # Adaptive timeout with exponential backoff
            timeout = min(2.0 * (1.5 ** consecutive_failures), 30.0)

            try:
                request = Message(
                    code=GET,
                    uri=uri,
                    msg_type=CON,  # Reliable for each block
                    block2=(block_num, False, block_size)
                )
                response = await protocol.request(request, timeout=timeout).response

                # Write block to file
                f.write(response.payload)
                consecutive_failures = 0

                # Check if more blocks available
                if response.opt.block2.more:
                    block_num += 1
                else:
                    break  # Last block received

            except TimeoutError:
                consecutive_failures += 1
                if consecutive_failures > 10:
                    raise TransferFailed(f"Failed at block {block_num}")

    print(f"Downloaded {block_num + 1} blocks")

1216.3.4 Performance Analysis

Scenario: 256 KB firmware update over LoRaWAN (10% packet loss)

Approach Blocks Retransmissions Time Success Rate
Single 256KB payload 1 Many 146s ~65%
1KB blocks (NON) 256 0 51s ~11%
1KB blocks + CON 256 Avg 1.11/block 57s ~99.99%
512 byte blocks + CON 512 Avg 1.05/block 54s ~99.999%

Key insight: Smaller blocks = higher success rate on lossy links, but more overhead.

1216.4 Resource Discovery (RFC 6690)

TipMinimum Viable Understanding: CoAP Resource Discovery

Core Concept: Every CoAP server exposes a standardized /.well-known/core endpoint that returns a machine-readable list of all available resources with their URIs, types, interfaces, and capabilities in Link Format (RFC 6690).

Why It Matters: Resource discovery enables true plug-and-play IoT - new devices can be added to a network and automatically discovered by clients without manual configuration.

Key Takeaway: Always implement /.well-known/core with semantic attributes (rt= for resource type, if= for interface, obs for observability).

1216.4.1 Discovery Request-Response

Client Request:
GET coap://sensor.local/.well-known/core

Server Response (Link Format, Content-Format: 40):
</sensors/temp>;rt="temperature";if="sensor";obs,
</sensors/humidity>;rt="humidity";if="sensor";obs,
</sensors/pressure>;rt="pressure";if="sensor",
</actuators/led>;rt="light";if="actuator",
</config/interval>;rt="config";if="parameter"

1216.4.3 Filtered Discovery

Request only specific resource types:

# Find all temperature sensors
GET coap://sensor.local/.well-known/core?rt=temperature

Response:
</sensors/temp>;rt="temperature";if="sensor";obs,
</sensors/temp_outdoor>;rt="temperature";if="sensor";obs

# Find all observable resources
GET coap://sensor.local/.well-known/core?obs

1216.4.4 Multicast Discovery

Discover all CoAP devices on network simultaneously:

GET coap://[FF02::FD]/.well-known/core

# All CoAP devices respond with their resource list
Device 1: </temp>;rt="temperature"
Device 2: </humidity>;rt="humidity"
Device 3: </pressure>;rt="pressure"

1216.5 CoAP over TCP (RFC 8323)

1216.5.1 Why CoAP-over-TCP?

Problem: UDP works great for constrained devices, but some networks: - Block UDP entirely (corporate firewalls) - Have asymmetric NAT breaking UDP return paths - Require guaranteed ordering (financial transactions)

Solution: RFC 8323 defines CoAP over reliable transports (TCP, TLS, WebSockets).

1216.5.2 Protocol Differences

Feature CoAP/UDP CoAP/TCP
Transport Unreliable UDP Reliable TCP
Message Types CON, NON, ACK, RST Signaling only
Reliability Application (CON/ACK) Transport (TCP)
Message ID Required Optional
Connection None TCP 3-way handshake
Default Port 5683 (coap://) 5683 (coap+tcp://)
Secure Port 5684 (coaps://) 443 (coaps+tcp://)

1216.5.3 Header Format Changes

CoAP/UDP header (4 bytes):

|Ver| T |  TKL  |      Code     |          Message ID           |

CoAP/TCP header (2-4 bytes):

| Len | TKL |      Code     |            Token              |

Key changes: - No Message ID (TCP sequence numbers handle ordering) - No Type field (TCP reliability eliminates CON/NON/ACK) - Length field (for framing over stream)

1216.5.4 When to Use

Use CoAP/TCP when: - Corporate/enterprise networks block UDP - Web dashboard integration (WebSockets) - Guaranteed ordering requirements - Long-lived bidirectional streams

Avoid CoAP/TCP when: - Battery-powered sensors (UDP more efficient) - Multicast scenarios (TCP is unicast only) - Intermittent communication (connection overhead wasteful)

1216.6 DTLS Security

CoAP uses DTLS (Datagram TLS) for security over UDP:

1216.6.1 Security Modes

Mode Description Use Case
NoSec No security Development only
PreSharedKey Symmetric keys pre-installed Factory provisioning
RawPublicKey Asymmetric without certificates Lightweight devices
Certificate Full X.509 certificates Enterprise deployments

1216.6.2 DTLS Handshake

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#ecf0f1'}}}%%
sequenceDiagram
    participant Client as CoAP Client
    participant Server as CoAP Server

    Client->>Server: ClientHello
    Server->>Client: HelloVerifyRequest (cookie)
    Client->>Server: ClientHello (with cookie)
    Server->>Client: ServerHello, Certificate
    Client->>Server: Certificate, ClientKeyExchange
    Server->>Client: ChangeCipherSpec, Finished
    Client->>Server: ChangeCipherSpec, Finished

    Note over Client,Server: Secure DTLS session established

    Client->>Server: Encrypted CoAP GET /sensor
    Server->>Client: Encrypted 2.05 Content

1216.7 Common Pitfalls

CautionPitfall: Block-Wise Transfer Timeout Cascade

The Mistake: Using default CoAP timeout values for block-wise transfers, causing transfer failures at 60-80% completion on lossy links.

Why It Happens: CoAPโ€™s 2-second ACK timeout works for single messages but fails for multi-block transfers. Any single timeout can cause restart.

The Fix: Implement adaptive timeouts and resumable transfers:

def download_with_resume(uri, resume_from=0):
    block_num = resume_from
    consecutive_failures = 0
    base_timeout = 2.0

    while True:
        timeout = min(base_timeout * (1.5 ** consecutive_failures), 30.0)

        try:
            response = coap_get(uri, block2=(block_num, 0, 1024), timeout=timeout)
            save_progress(block_num, response.payload)
            consecutive_failures = 0

            if response.block2.more:
                block_num += 1
            else:
                return assemble_firmware()

        except TimeoutError:
            consecutive_failures += 1
            if consecutive_failures > 10:
                save_resume_point(block_num)
                raise TransferSuspended(block_num)
CautionPitfall: Assuming CoAP Works Through NAT Like HTTP

The Mistake: Deploying CoAP devices behind NAT gateways without considering that UDP NAT mappings expire quickly (30-120 seconds).

The Fix: - Test UDP connectivity before design - Implement NAT keepalive (send messages every 25 seconds) - Consider CoAP over TCP for NAT-hostile networks - Use DTLS session resumption for faster reconnects

1216.8 Summary

CoAPโ€™s advanced features enable production IoT deployments:

  • Block-wise transfer: Split large payloads into reliable blocks
  • Resource discovery: Standardized .well-known/core with Link Format
  • CoAP/TCP: Firewall-friendly alternative when UDP is blocked
  • DTLS security: Encryption and authentication for constrained devices

Key decisions: - Use smaller blocks (256-512 bytes) on lossy networks - Always implement resource discovery with semantic attributes - Choose UDP for battery efficiency, TCP for NAT/firewall traversal - Use PSK for constrained devices, certificates for enterprise

1216.9 Whatโ€™s Next

Now that you understand advanced features: