A consumer that subscribes to many hrefs on one session needs to tell "this registration never took" from "this resource just hasn't changed yet". Right now it can't: subscribe() returns as soon as the register CON is written, and the only per-href signal that ever reaches the caller is on_notification. Both of the wire-level signals that would answer the question are dropped inside _dispatch_coap.
1. A non-2.05 on an observe token is logged and swallowed
dtls_session.py:697
if code != 0x45:
logger.warning("observe %s: non-2.05 %s", href, fmt_code(code))
return
That's a definitive "this registration failed", and it's the strongest
evidence available anywhere in the stack — but it goes to the log and
never to the caller, so acting on it means scraping log lines.
2. The Observe option is never read from a response
Option 6 is only ever written — register at dtls_session.py:1100,
deregister at :465. Nothing inspects it on the way back in; the
notification path reads only BLOCK2 out of ropts.
Two consequences:
- A declined registration is indistinguishable from a successful one.
RFC 7641 §4.1: a server that does not add the client to its observer
list still answers the GET, just without the Observe option. Since any
2.05 on that token is treated as a notification, the caller records the
href as push-covered and stops polling something that will never push
again. This is the dangerous direction — it fails toward stale data.
- The initial registration response can't be told from a state change.
The option's value is a sequence number (§3.4). Without it, "this href
notified" means "it said something once", not "it pushes changes" —
and every healthy href says something once, at registration. A caller
wanting the stronger property has no way to compute it.
Why silence isn't a workable substitute
Inferring from "never notified" conflates at least three causes:
- registration refused or errored (cases 1 and 2 above),
- the register CON was lost —
subscribe() is fire-and-forget and has no
retransmit (unlike _blockwise_get's _BLOCK_MAX_ATTEMPTS), so a
dropped datagram means no response ever arrives,
- a large resource whose initial representation is legitimately deferred:
_dispatch_coap withholds any notification with Block2 M=1 or NUM>0 and
hands it to the refetch worker, which is serialized, paced at 5 req/s
and bounded by _REFETCH_TIMEOUT_S = 15.0.
Only the third recovers on its own, and the caller can't tell which it is.
Suggested shape
Matching the existing self.on_notification = on_notification # fn(href, payload_bytes)
attribute at :251:
on_observe_error = None # fn(href, code) — invoked where the bare
warning is today.
- Something that surfaces registration acceptance and the sequence number.
Either an extra argument on the notification callback or a separate
on_observe_registered(href, accepted: bool); the useful part is
distinguishing "2.05 with no Observe option" and exposing the seq, so
the exact shape is yours to pick.
Both default to None, so current behavior is unchanged for existing
callers.
Happy to send a PR if the direction looks right.
A consumer that subscribes to many hrefs on one session needs to tell "this registration never took" from "this resource just hasn't changed yet". Right now it can't:
subscribe()returns as soon as the register CON is written, and the only per-href signal that ever reaches the caller ison_notification. Both of the wire-level signals that would answer the question are dropped inside_dispatch_coap.1. A non-2.05 on an observe token is logged and swallowed
dtls_session.py:697That's a definitive "this registration failed", and it's the strongest
evidence available anywhere in the stack — but it goes to the log and
never to the caller, so acting on it means scraping log lines.
2. The Observe option is never read from a response
Option 6 is only ever written — register at
dtls_session.py:1100,deregister at
:465. Nothing inspects it on the way back in; thenotification path reads only BLOCK2 out of
ropts.Two consequences:
RFC 7641 §4.1: a server that does not add the client to its observer
list still answers the GET, just without the Observe option. Since any
2.05 on that token is treated as a notification, the caller records the
href as push-covered and stops polling something that will never push
again. This is the dangerous direction — it fails toward stale data.
The option's value is a sequence number (§3.4). Without it, "this href
notified" means "it said something once", not "it pushes changes" —
and every healthy href says something once, at registration. A caller
wanting the stronger property has no way to compute it.
Why silence isn't a workable substitute
Inferring from "never notified" conflates at least three causes:
subscribe()is fire-and-forget and has noretransmit (unlike
_blockwise_get's_BLOCK_MAX_ATTEMPTS), so adropped datagram means no response ever arrives,
_dispatch_coapwithholds any notification with Block2 M=1 or NUM>0 andhands it to the refetch worker, which is serialized, paced at 5 req/s
and bounded by
_REFETCH_TIMEOUT_S = 15.0.Only the third recovers on its own, and the caller can't tell which it is.
Suggested shape
Matching the existing
self.on_notification = on_notification # fn(href, payload_bytes)attribute at
:251:on_observe_error = None # fn(href, code)— invoked where the barewarning is today.
Either an extra argument on the notification callback or a separate
on_observe_registered(href, accepted: bool); the useful part isdistinguishing "2.05 with no Observe option" and exposing the seq, so
the exact shape is yours to pick.
Both default to
None, so current behavior is unchanged for existingcallers.
Happy to send a PR if the direction looks right.