Skip to content

v0.1.16

Choose a tag to compare

@QuiteYellow QuiteYellow released this 06 Sep 15:27
· 94 commits to main since this release
2f727d5

Two changes from #80, both validated against my oven and dryer before release.

Registration answers are no longer mistaken for pushes (#41)

A device answers every OBSERVE register CON with the current representation, and that answer arrives on the observe token exactly as a spontaneous notification does. on_notification carries (href, payload) alone, so a consumer had no way to tell the two apart and could only guess from arrival order. That guess fails on an href carrying several query-qualified relations, where both registration answers look alike.

ObserveDelivery carries the whole relation context, and the new on_observe_delivery callback receives it:

def on_delivery(delivery):
    if delivery.registration:
        seed(delivery.href, delivery.payload)   # answered because we asked
    else:
        record_push(delivery.href, delivery.payload)

sess = DtlsCoapSession(..., on_observe_delivery=on_delivery)

registration is true for the answer to the register CON, on both the compliant path and the optionless one some firmware sends. query completes the relation identity. sequence is the Observe option value, or None where there is none. Setting the callback suppresses on_notification and on_legacy_notification, so a representation is delivered once.

The flag travels with a queued blockwise relation, so a re-read stays a registration answer. Retiring a token clears its recorded sequence, so a reconnect or the periodic refresh registers afresh with nothing tracked in the caller.

This is additive and keyword-only. The callback defaults to None, and a consumer that sets none of it keeps receiving every representation through on_notification exactly as before.

In the reference bridge it closes #41, where Push Active reported an appliance as pushing while it had no route to Samsung's cloud and was emitting nothing.

A failed handshake now names the alert

connect() turned every SSL.Error into a bare SessionError and dropped the exception, so a rejected handshake reached the operator as session operation failed and nothing more.

The alert now goes to the local log, narrowed to the reason strings OpenSSL recorded:

dtls handshake failed at the TLS layer: tlsv1 alert unknown ca

The raised SessionError is unchanged. Its redaction is deliberate, since backend errors elsewhere can carry remote endpoints, local paths or credential metadata. A path or host sitting in another slot of the reason tuple is dropped, so only the protocol vocabulary reaches the log.

Validation

739 tests pass. On hardware, the previous build put both reference appliances online 55s after connect and held them there for the full ten-minute window; this one logged no push_active online event across 35 minutes, with both units at 0 err, 0 ping-fail and 0 timeouts.

One thing recorded in passing while testing reconnects: a bridge killed with SIGKILL, leaving an orphaned association on the device, re-handshaked on the same 5-tuple and was accepted first time on both appliances. That is the RFC 6347 §4.2.8 behaviour the fixed local port depends on, checked before only on the oven.