% coap-lwm2m.tex — CoAP (REST over UDP: methods + codes, CON/NON/ACK/RST, message ID vs
% token, retransmission with backoff, Observe, block-wise, discovery, DTLS/OSCORE), the
% CoAP vs MQTT vs HTTP table, LwM2M (object/instance/resource, the four interfaces,
% registration, firmware update) and when to choose which on constrained devices.
% Source: docs/memos/iot-protocols-coap-lwm2m.md (+ RFC 7252/7641/7959 constants).
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/iot/coap-lwm2m.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=iot kind=concept level=deep platform=general new=no round=missing-2026-09-25 topic=protocols,networking
% @tags: coap, lwm2m, confirmable-message, message-id-vs-token, coap-observe, block-wise-transfer, dtls, oscore, well-known-core, object-instance-resource, firmware-update-object
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}
\usepackage{array}
\usepackage{tabularx}

\tikzset{
  bit/.style={draw=sheetBlue, fill=sheetBlue!8, minimum height=4.5mm, inner sep=0pt, font=\tiny, anchor=west},
  lnv/.style={draw=sheetGrey!70},
  who/.style={font=\tiny\bfseries, text=sheetGrey},
  msg/.style={->, thick, draw=sheetBlue},
  ack/.style={->, thick, draw=sheetGreen},
  lost/.style={->, thick, draw=sheetRed, dashed},
  lbl/.style={font=\tiny, text=black!80, inner sep=0.5pt},
  hd/.style={font=\bfseries\scriptsize, anchor=west},
  sv/.style={box, font=\tiny, inner sep=1.2pt, minimum height=4.5mm},
}
\newcolumntype{L}{>{\raggedright\arraybackslash}X}

\begin{document}

\sheettitle{CoAP \& LwM2M — REST and device management for constrained devices}{iot · memo}

\oneliner{\textbf{CoAP} (RFC 7252) is \textbf{REST over UDP}: GET/POST/PUT/DELETE and
HTTP-like codes in a \textbf{4-byte binary header}, reliability by \textbf{confirmable}
messages with exponential backoff, push by \textbf{Observe}, big bodies by
\textbf{block-wise}, security by \textbf{DTLS} or \textbf{OSCORE}. \textbf{LwM2M} (OMA
SpecWorks) is \textbf{device management on top of CoAP}: a standard
\textbf{object/instance/resource} tree (\texttt{/3/0/3} = firmware version) plus
bootstrap, registration, management and reporting interfaces.}

\noindent\begin{tikzpicture}[sheet]
  % ---------- header ----------
  \node[hd] at (-0.2,3.05) {CoAP message — 4-byte fixed header};
  \node[bit, minimum width=3mm] at (0,2.55) {Ver};
  \node[bit, minimum width=3mm] at (0.3,2.55) {T};
  \node[bit, minimum width=6mm] at (0.6,2.55) {TKL};
  \node[bit, minimum width=12mm] at (1.2,2.55) {Code \texttt{c.dd}};
  \node[bit, minimum width=24mm] at (2.4,2.55) {Message ID (16 bit)};
  \node[bit, minimum width=48mm, fill=sheetOrange!10, draw=sheetOrange] at (0,2.1) {Token (0–8 bytes, length = TKL)};
  \node[bit, minimum width=48mm, fill=sheetGrey!8, draw=sheetGrey] at (0,1.65) {Options (delta-encoded): Uri-Path, Observe, Block2…};
  \node[bit, minimum width=6mm, fill=sheetRed!10, draw=sheetRed] at (0,1.2) {\texttt{0xFF}};
  \node[bit, minimum width=42mm, fill=sheetGreen!10, draw=sheetGreen] at (0.6,1.2) {Payload};
  \node[lbl, anchor=west, align=left] at (0,0.55) {Ver = 1 · T: 0 CON, 1 NON, 2 ACK, 3 RST\\
    Code: 0.01 GET 0.02 POST 0.03 PUT 0.04 DELETE\\
    2.01 Created 2.04 Changed 2.05 Content 4.04 5.00};
  % ---------- reliability ----------
  \tikzset{ml/.style={lbl, fill=white, inner sep=0.6pt}}
  \node[hd] at (5.1,3.05) {CON retransmit};
  \node[who] at (5.3,2.8) {client}; \node[who] at (7.8,2.8) {server};
  \draw[lnv] (5.3,2.65) -- (5.3,0.5); \draw[lnv] (7.8,2.65) -- (7.8,0.5);
  \draw[lost] (5.3,2.5) -- node[ml, pos=0.45]{CON GET MID=7d34} (7.1,2.25);
  \node[lbl, text=sheetRed] at (7.4,2.2) {\textbf{lost}};
  \node[ml, text=sheetBrown] at (6.55,1.88) {no ACK: resend after 2–3 s, then ×2};
  \draw[msg] (5.3,1.6) -- node[ml]{same MID, same token} (7.8,1.35);
  \draw[ack] (7.8,1.05) -- node[ml]{ACK 2.05 MID=7d34 ``21.5''} (5.3,0.8);
  \node[lbl, text=sheetBrown, align=center] at (6.6,0.25) {response piggybacked in the ACK\\a duplicate MID is dropped};
  % ---------- observe ----------
  \node[hd] at (8.2,3.05) {Observe (RFC 7641)};
  \node[who] at (8.5,2.8) {client}; \node[who] at (11.0,2.8) {server};
  \draw[lnv] (8.5,2.65) -- (8.5,0.5); \draw[lnv] (11.0,2.65) -- (11.0,0.5);
  \draw[msg] (8.5,2.5) -- node[ml]{GET Observe=0 tok=4a} (11.0,2.25);
  \draw[ack] (11.0,2.0) -- node[ml]{ACK 2.05 Obs=12 tok=4a} (8.5,1.75);
  \draw[ack] (11.0,1.5) -- node[ml]{NON 2.05 Obs=13 tok=4a} (8.5,1.25);
  \draw[ack] (11.0,1.0) -- node[ml]{CON 2.05 Obs=14 (alive?)} (8.5,0.75);
  \node[lbl, text=sheetBrown, align=center] at (9.75,0.25) {same token on every notification\\RST or Observe=1 cancels};
  % ---------- LwM2M ----------
  \node[hd] at (11.3,3.05) {LwM2M — who talks to whom};
  \node[sv, draw=sheetBrown, fill=sheetBrown!8] (bs) at (12.1,2.45) {Bootstrap srv};
  \node[sv, draw=sheetGreen, fill=sheetGreen!10] (ls) at (15.9,2.45) {LwM2M server};
  \node[sv, draw=sheetOrange, fill=sheetOrange!10, align=center] (cl) at (14.0,1.1) {Client = device\\{\tiny CoAP \emph{server} for its tree}};
  \draw[msg, draw=sheetBrown] (bs.south) -- (cl.west);
  \node[lbl, align=center] at (11.95,1.45) {1 Bootstrap:\\write \texttt{/0}, \texttt{/1}};
  \draw[msg, draw=sheetOrange] (cl.north) -- (ls.west);
  \node[lbl, align=center] at (13.9,2.3) {2 Register\\\texttt{POST /rd}};
  \draw[msg, draw=sheetGreen] (ls.south) to[bend right=20] (cl.east);
  \node[lbl, align=center] at (16.3,1.6) {3 Read\\Write\\Execute};
  \draw[ack, dashed] (cl.south east) to[bend right=30] (ls.south east);
  \node[lbl, align=center] at (16.2,0.75) {4 Notify};
  \node[lbl, anchor=west, align=left, text=sheetBlue] at (11.3,0.25) {\texttt{/3/0/0} Manufacturer · \texttt{/3/0/3} FW version · \texttt{/3/0/4} Reboot (E)\\
    \texttt{/5/0/1} Package URI · \texttt{/5/0/2} Update (E) · \texttt{/3303/0/5700} temp};
\end{tikzpicture}

\begin{multicols}{2}

\section{CoAP — how it works}
\begin{itemize}\raggedright
  \item UDP port \textbf{5683}, DTLS \textbf{5684}; TCP/TLS/WebSocket binding exists
        (RFC 8323) but UDP is the design point: no connection state, no head-of-line blocking.
  \item \textbf{Two layers}: \emph{message} layer (type + 16-bit \textbf{Message ID}:
        dedup, ACK matching) and \emph{request/response} layer (\textbf{Token}, 0–8 bytes:
        matches responses and notifications to a request). Response =
        \textbf{piggybacked} in the ACK, or \textbf{separate} (empty ACK now, CON response later).
  \item \textbf{Reliability}: CON waits for ACK; timeout random in 2–3 s
        (\texttt{ACK\_TIMEOUT} 2 s × factor 1.5), doubled each time, \textbf{4}
        retransmits max. NON = fire-and-forget telemetry. RST = ``I can't handle this''.
  \item \textbf{Options} are delta-encoded numbers: Uri-Path, Uri-Query, Content-Format
        (0 text, 50 JSON, 60 CBOR), Max-Age, ETag.
  \item \textbf{Observe}: best-effort push of the \emph{latest} state (intermediate states
        may be skipped), sequence numbers reorder, at least one CON per 24 h checks the
        observer is alive.
  \item \textbf{Block-wise} (RFC 7959): Block2 = response body, Block1 = request body;
        NUM + More + size $2^{\mathrm{SZX}+4}$ = 16…1024 B — firmware, fits 6LoWPAN MTUs.
  \item \textbf{Discovery}: \texttt{GET /.well-known/core} $\to$ CoRE Link Format
        (\texttt{</temp>;rt="temperature";if="sensor"}).
  \item \textbf{Security}: DTLS with PSK, raw public key or X.509 (handshake RAM + round
        trips hurt); \textbf{OSCORE} (RFC 8613) protects the CoAP message itself
        end-to-end, through proxies.
\end{itemize}

\section{CoAP vs MQTT vs HTTP}
{\scriptsize
\noindent\begin{tabularx}{\linewidth}{@{}>{\bfseries\raggedright\arraybackslash}p{11mm}LLL@{}}
\toprule
 & \textbf{CoAP} & \textbf{MQTT} & \textbf{HTTP/1.1} \\
\midrule
Transport & UDP (+DTLS) & TCP (+TLS) & TCP (+TLS) \\
Pattern & request/response + Observe & pub/sub via broker & request/response \\
Topology & client $\leftrightarrow$ device, no broker & star around broker & client
  $\to$ server \\
Header & 4 B binary & 2 B min & text, 100s of B \\
Sleepy / NAT & no session; NAT binding expires $\to$ server can't reach device &
  keepalive holds TCP & poll \\
Best at & RESTful device mgmt, NB-IoT, 6LoWPAN & fan-out telemetry, many consumers &
  phones, web, cloud APIs \\
\bottomrule
\end{tabularx}}

\columnbreak

\section{LwM2M — the model}
\begin{itemize}\raggedright
  \item Path \texttt{/Object/Instance/Resource}: \textbf{0} Security · \textbf{1} Server ·
        2 Access Control · \textbf{3} Device · 4 Connectivity Monitoring · \textbf{5}
        Firmware Update · 6 Location; IPSO objects (3303 Temperature). Standard IDs =
        interoperable servers.
  \item Resources are R, W or \textbf{E} (execute: \texttt{POST /3/0/4} reboots).
        Formats: TLV, SenML JSON/CBOR, plain text.
  \item Roles are inverted: the device is a CoAP \emph{client} to register, and a CoAP
        \emph{server} for its object tree.
\end{itemize}

\section{The four interfaces}
{\scriptsize
\noindent\begin{tabularx}{\linewidth}{@{}>{\bfseries\raggedright\arraybackslash}p{17mm}L@{}}
\toprule
Bootstrap & factory-known bootstrap server writes \texttt{/0} (server URI + DTLS keys)
  and \texttt{/1} (lifetime, binding) \\
Registration & \texttt{POST /rd?ep=<name>\&lt=86400} + link list
  (\texttt{</3/0>,</5/0>}) $\to$ 2.01 + Location; Update before \texttt{lt} expires;
  \texttt{DELETE} = de-register \\
Device mgmt \& service enablement & Read (GET) · Write (PUT/POST) · Execute (POST) ·
  Create · Delete · Discover · Write-Attributes \\
Information reporting & Observe / Notify / Cancel; \texttt{pmin}/\texttt{pmax} attributes
  bound the notify rate \\
\bottomrule
\end{tabularx}}

\section{Firmware update with Object 5}
Write \texttt{/5/0/1} Package URI $\to$ device downloads (Block2) $\to$ State
\texttt{/5/0/3}: 0 Idle $\to$ 1 Downloading $\to$ 2 Downloaded $\to$ server Executes
\texttt{/5/0/2} $\to$ 3 Updating $\to$ reboot $\to$ Update Result \texttt{/5/0/5}.
\textbf{Queue mode}: a sleeping device's requests wait until its next Update.

\section{Interview traps}
\begin{itemize}\raggedright
  \trap{``CoAP runs over TCP'' — UDP is the whole point.}
  \trap{Message ID $\neq$ Token: dedup/ACK vs request matching.}
  \trap{Observe is not a reliable stream — design for skipped notifications.}
  \trap{``LwM2M replaces MQTT'' — management vs data; they coexist.}
\end{itemize}

\section{Remember}
\emph{CON waits, NON flies · MID for the message, token for the request · Observe = latest ·
LwM2M = /object/instance/resource.}

\section{Likely questions}
\begin{enumerate}\raggedright
  \item Constrained sensor: CoAP or MQTT? — CoAP for sleepy UDP/NB-IoT and REST mgmt;
        MQTT for fan-out through a broker.
  \item Big payload over CoAP? — block-wise, never IP fragmentation.
  \item Where does the phone fit? — behind a gateway/proxy speaking HTTP/MQTT.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} mqtt-for-devices ·
iot-device-security-provisioning · ble-mesh-matter-thread · esp32-wifi-espnow-power-ota ·
api-design}

\end{document}
