docs(04-03): complete printer driver + HTTP endpoints plan — MockDriver, SSE, rate limiter
This commit is contained in:
parent
9f57cbdf6c
commit
4d2d35e277
1 changed files with 146 additions and 0 deletions
146
.planning/phases/04-usb-manager-label-printing/04-03-SUMMARY.md
Normal file
146
.planning/phases/04-usb-manager-label-printing/04-03-SUMMARY.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
phase: 04-usb-manager-label-printing
|
||||
plan: "03"
|
||||
subsystem: printer
|
||||
tags: [go, printer, serial, sse, http, tdd]
|
||||
dependency_graph:
|
||||
requires:
|
||||
- internal/usb/manager.go (DeviceEvent, Manager.Events)
|
||||
- internal/labels/renderer.go (RenderStandard, RenderCable, IsCableDevice)
|
||||
- internal/netbox/types.go (Device, CustomFields)
|
||||
provides:
|
||||
- internal/printer package (PrinterDriver, MockDriver, PrtQutieDriver)
|
||||
- POST /api/labels/:deviceID/print endpoint
|
||||
- GET /api/usb/events SSE endpoint
|
||||
affects:
|
||||
- cmd/hwlab/main.go (USB Manager start, MockDriver, new handler wiring)
|
||||
- internal/api/router.go (two new parameters + routes)
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "PrinterDriver interface: Connect/Print(bitmap,w,h)/Disconnect — driver-agnostic bitmap protocol"
|
||||
- "MockDriver: saves PNG to SaveDir for visual inspection without hardware"
|
||||
- "PrtQutieDriver: stub returns ErrNoDevice — safe before hardware arrives"
|
||||
- "SSE handler: select on r.Context().Done() for goroutine-leak-safe teardown"
|
||||
- "Rate limiter: sync.Mutex + lastPrintTime guards 1/s print cadence (T-04-09)"
|
||||
- "TDD: RED commit → GREEN commit for both tasks"
|
||||
key_files:
|
||||
created:
|
||||
- internal/printer/driver.go
|
||||
- internal/printer/mock_driver.go
|
||||
- internal/printer/prt_qutie.go
|
||||
- internal/printer/driver_test.go
|
||||
- internal/api/handlers/label.go
|
||||
- internal/api/handlers/label_test.go
|
||||
- internal/api/handlers/usb_events.go
|
||||
modified:
|
||||
- internal/api/router.go
|
||||
- cmd/hwlab/main.go
|
||||
decisions:
|
||||
- "Print() signature is Print(bitmap []byte, width, height int) not Print(image.Image) — driver-agnostic; bitmap conversion happens in handler via ImageToRawBitmap()"
|
||||
- "Rate limit implemented inline (sync.Mutex + lastPrintTime) rather than chi middleware — simpler for single-driver use case, avoids middleware ordering complexity"
|
||||
- "MockDriver.SaveDir is exported so tests can inject t.TempDir() without /tmp pollution"
|
||||
- "PrtQutieDriver.Connect() returns ErrNoDevice stub — real port resolution deferred to hardware arrival day 2026-04-13"
|
||||
- "USBEventsHandler uses 30s ticker keepalive to prevent proxy timeouts on quiet periods"
|
||||
metrics:
|
||||
duration_minutes: 4
|
||||
completed_date: "2026-04-10"
|
||||
tasks_completed: 2
|
||||
tasks_total: 2
|
||||
files_created: 7
|
||||
files_modified: 2
|
||||
---
|
||||
|
||||
# Phase 4 Plan 03: Printer Driver Interface + HTTP Endpoints Summary
|
||||
|
||||
**One-liner:** PrinterDriver interface with MockDriver (PNG-to-/tmp) and PrtQutieDriver stub, plus POST /api/labels/:id/print (rate-limited) and GET /api/usb/events (SSE with goroutine-safe teardown).
|
||||
|
||||
## What Was Built
|
||||
|
||||
### internal/printer/driver.go
|
||||
`PrinterDriver` interface with `Connect() error`, `Print(bitmap []byte, width, height int) error`, `Disconnect() error`. `ImageToRawBitmap(img image.Image) ([]byte, int, int)` converts `image.Image` to 1-bit packed row-major bitmap (dark pixels → 1). Sentinel errors: `ErrNoDevice`, `ErrNotConnected`, `ErrEmptyBitmap`.
|
||||
|
||||
### internal/printer/mock_driver.go
|
||||
`MockDriver` implements `PrinterDriver`. `Print()` reconstructs a grayscale PNG from the 1-bit bitmap and saves it to `SaveDir` (default `/tmp`) as `hwlab-label-<timestamp>.png`. Logs path to stdout. No hardware required — default driver until PRT Qutie arrives.
|
||||
|
||||
### internal/printer/prt_qutie.go
|
||||
`PrtQutieDriver` implements `PrinterDriver` using `go.bug.st/serial`. `Connect()` returns `ErrNoDevice` (stub) — real port resolution deferred to hardware characterization day 2026-04-13. Print protocol unknown; `TODO(hardware)` comments reference Wireshark capture approach.
|
||||
|
||||
### internal/api/handlers/label.go
|
||||
`LabelHandler` handles `POST /api/labels/:deviceID/print`. Fetches device from NetBox, routes to `labels.RenderCable` or `labels.RenderStandard` based on `IsCableDevice()`, converts to bitmap via `printer.ImageToRawBitmap`, calls `LabelPrinter.Print()`. Rate-limited to 1 print/second via `sync.Mutex + lastPrintTime` (T-04-09 mitigation).
|
||||
|
||||
### internal/api/handlers/usb_events.go
|
||||
`USBEventsHandler` handles `GET /api/usb/events`. Streams `usb.DeviceEvent` values as Server-Sent Events. 30-second keepalive ticker prevents proxy timeouts. Returns on `r.Context().Done()` — no goroutine leak (T-04-11 mitigation verified by Test 5).
|
||||
|
||||
### internal/api/router.go
|
||||
Updated `NewRouter` signature to accept `*handlers.LabelHandler` and `*handlers.USBEventsHandler`. Routes: `POST /api/labels/{deviceID}/print` and `GET /api/usb/events`.
|
||||
|
||||
### cmd/hwlab/main.go
|
||||
USB Manager created (`usb.NewManager(2*time.Second)`), started via `go usbManager.Start(ctx)`, stopped via `defer usbManager.Stop()`. `printer.NewMockDriver()` constructed and connected. `LabelHandler` and `USBEventsHandler` constructed and passed to `NewRouter`.
|
||||
|
||||
## Commits
|
||||
|
||||
| Task | Commit | Description |
|
||||
|------|---------|-------------|
|
||||
| Task 1 RED | b223b54 | Failing printer driver tests (TDD RED) |
|
||||
| Task 1 GREEN | 1156eff | PrinterDriver interface, MockDriver, PrtQutieDriver stub |
|
||||
| Task 2 RED | dd381ee | Failing handler tests for label print and USB SSE (TDD RED) |
|
||||
| Task 2 GREEN | 9f57cbd | LabelHandler, USBEventsHandler, router wiring, main.go init |
|
||||
|
||||
## Test Results
|
||||
|
||||
```
|
||||
=== RUN TestMockDriverPrintSavesPNG PASS
|
||||
=== RUN TestMockDriverPrintNilBitmap PASS
|
||||
=== RUN TestMockDriverConnectDisconnect PASS
|
||||
=== RUN TestImageToRawBitmapLength PASS
|
||||
=== RUN TestPrtQutieConnectReturnsErrNoDevice PASS
|
||||
=== RUN TestMockDriverSaveDirHonoured PASS
|
||||
=== RUN TestLabelPrintSuccess PASS
|
||||
=== RUN TestLabelPrintDeviceNotFound PASS
|
||||
=== RUN TestLabelPrintPrinterError PASS
|
||||
=== RUN TestUSBEventsSSEContentType PASS
|
||||
=== RUN TestUSBEventsNoGoroutineLeak PASS
|
||||
PASS — go build ./... PASS
|
||||
```
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 2 - Missing Critical] Rate limiting on print endpoint (T-04-09)**
|
||||
- **Found during:** Task 2 — threat model review
|
||||
- **Issue:** Threat register marks T-04-09 (DoS via runaway print calls / label waste) as `mitigate`. Plan code snippet had no rate limiting.
|
||||
- **Fix:** Added `sync.Mutex + lastPrintTime + printCooldown(1s)` to `LabelHandler`. Returns HTTP 429 when violated.
|
||||
- **Files modified:** internal/api/handlers/label.go
|
||||
- **Commit:** 9f57cbd
|
||||
|
||||
## Known Stubs
|
||||
|
||||
| File | Line | Item | Reason |
|
||||
|------|------|------|--------|
|
||||
| internal/printer/prt_qutie.go | 35 | `Connect()` returns `ErrNoDevice` | Real VID/PID port enumeration deferred to hardware arrival 2026-04-13 |
|
||||
| internal/printer/prt_qutie.go | 43–50 | `Print()` is a no-op | PRT Qutie protocol unknown — requires Wireshark capture on hardware day |
|
||||
| internal/api/handlers/label.go | 92–99 | `USBVersion: "Unknown"` in cable label | Cable field parsing from `TestData` JSON deferred to Phase 5 |
|
||||
|
||||
These stubs do not prevent the plan's goal from being achieved: MockDriver renders and saves real PNG labels; the PrtQutieDriver stub is intentionally non-functional until hardware arrives.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None — no new network endpoints beyond the two planned (`POST /api/labels/:id/print`, `GET /api/usb/events`). Both are LAN-only per project constraints.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- internal/printer/driver.go: FOUND
|
||||
- internal/printer/mock_driver.go: FOUND
|
||||
- internal/printer/prt_qutie.go: FOUND
|
||||
- internal/printer/driver_test.go: FOUND
|
||||
- internal/api/handlers/label.go: FOUND
|
||||
- internal/api/handlers/label_test.go: FOUND
|
||||
- internal/api/handlers/usb_events.go: FOUND
|
||||
- internal/api/router.go: FOUND (modified)
|
||||
- cmd/hwlab/main.go: FOUND (modified)
|
||||
- Commit b223b54: FOUND
|
||||
- Commit 1156eff: FOUND
|
||||
- Commit dd381ee: FOUND
|
||||
- Commit 9f57cbd: FOUND
|
||||
Loading…
Add table
Reference in a new issue