brother: README for the two seats, the pinned certificate, the admin password and the login (issue 1453)
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
# brother — scan a document, crop it, file it, say so
|
||||
# brother — the Brother multifunction device: scan, printer state, admin pages
|
||||
|
||||
One module for one device (novox/hq ADR 0467, issue 1453): the scanner and the printer are one machine on the
|
||||
network, so one module serves both. It claims two seats, each held once for the mesh: `scanner` (verbs `scan`,
|
||||
`status`, `list`) and `printer` (verbs `status`, `settings`; `restart` follows in a later round). It replaces the
|
||||
`scan` module of issue 1440 (ADR 0464), which ran on no node and is gone from the catalogue.
|
||||
|
||||
## The scanner seat
|
||||
|
||||
Built for issue 1440 as novox/hq ADR 0464 decides. It scans the glass of a network scanner (any scanner that
|
||||
speaks eSCL, also called AirScan or Mopria), cuts the document out of the lid's white, files it as a one-page PDF
|
||||
@@ -26,28 +33,54 @@ A scanner that refuses or gives no page files nothing and says nothing on the bu
|
||||
|---|---|---|
|
||||
| `scanner` | the scanner's eSCL address, such as `http://192.0.2.10/eSCL` (required, trusted) | none |
|
||||
| `scan-folder` | the absolute path the PDFs are filed in (required, trusted) | none |
|
||||
| `printer` | the printer's host, such as `192.0.2.10`, for its admin page and IPP (required, trusted) | none |
|
||||
| `printer-certificate` | the SHA-256 fingerprint of the one certificate the admin page may serve, `AA:BB:...` (required, trusted) | none |
|
||||
| `resolution` | dpi asked: 100, 200, 300 or 600 | 300 |
|
||||
| `color-mode` | `color` or `gray` | `color` |
|
||||
| `crop` | cut the document out of the lid's white | true |
|
||||
|
||||
The values reach the code in one file, `settings.json` in the module's state directory, which the mesh writes
|
||||
from the config; the code reads it at every call. With `scanner` or `scan-folder` unset, every tool says which and
|
||||
writes and asks nothing.
|
||||
from the config; the code reads it at every call. With `scanner` or `scan-folder` unset, every scanner verb says which and
|
||||
writes and asks nothing; `printer` and `printer-certificate` are the printer verbs' to say.
|
||||
|
||||
The scan folder is not a manifest resource: a directory's path cannot take a config value (issue 1441), so the code
|
||||
makes it (mode 0755) when it files the first scan.
|
||||
|
||||
## Tools
|
||||
|
||||
| tool | does |
|
||||
| verb (tool) | does |
|
||||
|---|---|
|
||||
| `scanner.scan` | scans, crops, files and emits; answers the event's body |
|
||||
| `scanner.status` | whether the scanner answers, its state (Idle or Processing) and its document feeder's state; a scanner that does not answer is `answers: false` with why. This is how its health is read: the module has no long-running resource to declare a health check on |
|
||||
| `scanner.list` | the newest filed scans, newest first (`limit`, default 20). Read-only |
|
||||
|
||||
## The printer seat
|
||||
|
||||
| verb (tool) | does |
|
||||
|---|---|
|
||||
| `printer.status` | without a login: over IPP (`Get-Printer-Attributes`, encoded by hand, on port 631) the state (idle, processing, stopped), the state reasons (such as `toner-low-warning`) and the markers' names, levels (percent; below 0 is unknown) and colours; over eSCL the scanner's state and its feeder's. A part that does not answer is `answers: false` with why. Needs `printer` and `scanner` |
|
||||
| `printer.settings` | logs in, reads one admin page, logs out. Argument `page`: a path on the admin site, starting with `/` and ending in `.html` (default `/general/status.html`). Answers the page's title, links (href and text) and forms with their fields (name, type, value, label); a password field's value is never given. Read-only: it sends the login and the logout and nothing else |
|
||||
|
||||
### Trust and the password
|
||||
|
||||
The admin page is HTTPS with a self-signed certificate. The module verifies no chain: it accepts the one certificate
|
||||
whose SHA-256 fingerprint is `printer-certificate` and refuses any other, in the handshake, so no password is sent
|
||||
to it; the error names both fingerprints. Get the fingerprint from the device's certificate page, and give it as 32
|
||||
upper-case hex pairs joined by colons.
|
||||
|
||||
The admin password is the module's own secret `admin-password` (ADR 0467 clause 4; ADR 0283): the operator gives it
|
||||
at their desk, the mesh never generates or rotates it, and it reaches the code as the file `BROTHER_ADMIN_PASSWORD`
|
||||
names (`${dir:state}/admin-password.secret`). While it is not given, `printer.settings` says that the secret is not
|
||||
given and contacts nothing.
|
||||
|
||||
The login reads the form each time: the device generates the password field's name, so the code takes the name from
|
||||
the form, posts it with the form's hidden fields, and keeps the session cookie. A login is failed when the answer is
|
||||
the login page again. The logout follows a logout link or form on the page; a page with none is said in the answer
|
||||
(`logout`), and the session is left to expire on the device.
|
||||
|
||||
## The event
|
||||
|
||||
`scan.added`, on the wire `mesh.mod.brother.event.scan.added`; a consumer declares `brother.scan.added`. Its body:
|
||||
`scan.added`, said by this module, on the wire `mesh.mod.brother.event.scan.added`; a consumer declares `brother.scan.added`. Its body:
|
||||
`path`, `node`, `bytes`, `sha256`, `width-mm`, `height-mm`, `resolution` (dpi, as the scanner gave it),
|
||||
`color-mode`, `cropped`, `scanned-at` (RFC 3339, UTC).
|
||||
|
||||
@@ -63,4 +96,7 @@ Each directory or archive this module declares says how it treats a file it did
|
||||
## Tests
|
||||
|
||||
The unit tests use the captured answers of a real scanner (`cmd/brother/testdata/escl`, captured 2026-10-20) served
|
||||
by a test HTTP server; no test reaches a scanner.
|
||||
by a test HTTP server, the captured IPP exchange (`testdata/ipp`) replayed, and the captured login page
|
||||
(`testdata/admin/login-page.html`) served by a test TLS server with its own certificate. The pages behind the login
|
||||
are synthetic (`testdata/admin/synthetic-*.html`) until round 2 captures the real ones. No test reaches the real
|
||||
device.
|
||||
|
||||
Reference in New Issue
Block a user