POST /v1/iot-service/api/user/ttcode answers 403 (code 8) unless it
carries the same headers /my/task enforces: X-BBL-Client-Name =
BambuStudio, X-BBL-OS-Type and the PoP pair. Move bbl_headers()/os_type
from cloud_print.cpp into obn::cloud, add obn::signing::add_pop_headers
(never sends blank values), and use both for the camera mint and for
create_task.
The cloud pushes liveview.prepare (with ttcode_enc) to the printer by
itself in response to POST /v1/iot-service/api/user/ttcode, so the
plugin never needs to publish it. Remove the signed prepare from
remote_camera_url, rescue_cloud_liveview and its detached republish
thread, the 84033543 frame filter, and revert the signing generalisation
(signable_root_key, ttcode -> ttcode_enc) back to print-only. This also
stops liveview frames from waiting up to 8 s on wait_for_app_cert.
Replace the manual `developer_mode` obn.conf knob (from PR #84) with
per-printer auto-detection:
- Agent::harvest_developer_mode() reads push_status print.fun bit 29
(clear = Developer Mode on, set = secured) into dev_mode_on_by_dev_,
tracked live (the on-screen toggle can flip mid-session), called at
both LAN and cloud report harvest sites.
- Agent::developer_mode_effective() returns the harvested state; before
the first fun frame it defaults to secured only when full signing
material (slicer key + app cert + CRL) is present, otherwise keeps
cleartext since nothing can be signed anyway.
- Thread the decision into signing as a parameter instead of reading a
global: maybe_sign(payload, device_pub, developer_mode) ->
build_print_dump -> encrypt_print_fields. Both Agent send sites pass
developer_mode_effective(dev_id).
- Drop the developer_mode field/config key and its README row.
Refine wait_for_app_cert per review: block on a condition_variable
(app_cert_cv_) notified from harvest_security_report instead of a 50ms
poll loop, and compute would_sign() once in send_message_to_printer
instead of twice.
Update signing_test to pass developer_mode as an argument and refresh
the STATUS.md field-encryption note. Full test suite passes.
- Updated `sequence_id` generation to use a monotonic counter within the range 20000–29999, aligning with stock plugin behavior.
- Adjusted `md5` field to emit the literal "from_sd_card" for both `sdcard_print` and FTPS local print paths.
- Ensured `ams_mapping` is emitted as an empty array when not in use, even with `task_use_ams` set to true.
- Enhanced handling of optional fields in the `POST /my/task` body to match stock behavior, including omitting `url` and `url_enc` when empty.
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
- Updated the `maybe_sign` function to apply device-cert field encryption to the `print` object before signing, ensuring that the signature covers the encrypted form sent over the wire.
- The cleartext `url` is now replaced by `url_enc`, and for `gcode_line`, the cleartext `param` is replaced by `param_enc`, aligning with hardware verification.
- Introduced `prime_pub_key_from_cert_file` to seed the public key cache from an on-disk device certificate, ensuring encryption is available even after reconnects.
- Updated documentation and tests to reflect these changes, confirming that the cleartext fields are dropped as expected when encryption is applied.
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
Cloud prints now start reliably from Bambu Studio over the cloud path.
Key fixes:
- create_task: send X-BBL-Client-Name (configurable via obn.conf
client_name, e.g. "BambuStudio") and a compile-time X-BBL-OS-Type;
POST /my/task hard-fails on non-2xx instead of silently continuing
with task_id=0. These were the cause of the 403 on /my/task.
- Fix app_certification_id() HTTP serialization (issuer:serial.lower()):
the serial was mis-split when it ended in a hex letter, producing a
malformed x-bbl-app-certification-id and a 403.
- run_cloud_print_job: for a cloud-connected printer collapse the
"local print with record" entry point into a single cloud print.
Studio tries start_local_print_with_record first and only falls back
to start_print on <0; our record path created a cloud task AND then
failed the redundant LAN MQTT publish, so BOTH ran and the printer
rejected the duplicate with 0500_4004 ("can't receive new print jobs
while printing"). The cloud dispatch from /my/task already starts the
print, so the MQTT project_file is dropped for the cloud channel.
- get_subtask: echo the caller-owned BBLModelTask back through the
callback instead of leaking it and leaving Studio's model-info request
latched forever.
Allow obn.conf to point slicer_cert.pem / slicer_crl.pem at explicit paths,
mirroring the existing slicer_key_pem override. Empty values keep the
config_dir/slicer_cert.pem and config_dir/slicer_crl.pem defaults.
Manual "dir + \"/\" + name" concatenation is not portable to Windows. Add
config::path_in_dir() (std::filesystem join on config_dir) and use it for
slicer_key.pem, slicer_cert_id.txt, slicer_cert.pem, slicer_crl.pem and
obn.auth.json. No behavior change on POSIX.
Wire up the previously dead set_printer_pub_key_from_cert_pem: before a
cloud print encrypts url_enc/param_enc, a missing pubkey now triggers
the documented security.app_cert_install MQTT flow (app cert chain +
CRL read from config_dir/slicer_cert.pem, slicer_crl.pem) and the
printer_cert from the printer's report is harvested into the cert_store
cache on both the LAN and cloud message paths. The TLS-leaf snapshot on
disk stays as the LAN-direct TOFU fallback only. Also call
forget_printer on LAN disconnect (immediate and deferred) and on cloud
session teardown so cached EVP_PKEYs are released.
The stock plugin's SignMessage is a pure canonicalise+sign step; it
never rewrites nozzle ids (see the reverse-networking MQTT doc). The
flip existed because Studio's DevMapping numbers dual-nozzle heads
left=1/right=0, but it was applied inside maybe_sign on MQTT
project_file payloads, which carry no ams_mapping_info at all - the
field only appears in the REST /my/task body - so on the live path it
was a no-op. Drop flip_nozzle_ids/is_h2d_printer, the printer_model
argument of maybe_sign, and the dev_model_by_id_ SSDP tracking that
existed only to feed it. If a real H2D mapping bug ever shows up, the
fix belongs in the REST task body, not in the signing layer.
- Introduced new settings for slicer key and certificate ID in the configuration.
- Updated session persistence path handling to prioritize user-defined paths.
- Refactored signing logic to resolve key and certificate paths based on configuration.
- Added a test stub for configuration to facilitate unit testing of signing functionality.
- Updated CMakeLists to include the new test file for signing.
Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
* Add cloud print, session persistence, and LAN multi-device support
Cloud printing:
- Encrypt the url and param fields (RSA PKCS#1 v1.5) in the project_file
command, as required by firmware for cloud prints.
- Cache each printer's public key from its TLS leaf certificate.
Cloud session:
- Persist and reload login sessions on disk (mode 0600), refreshing the
token when needed.
LAN:
- Track per-device model and IP from SSDP so the correct nozzle mapping
is applied before signing, and support multiple printers at once.
- Reconnect the LAN MQTT session automatically with exponential backoff.
Print jobs:
- Normalize 3MF archives to plate_1 (plate_0 rename plus in-place ZIP
rewrite) before upload. Adds miniz as a submodule for the rewrite.
Tests:
- Add agent and cloud_print unit tests.
* Address review: timestamp-signed security header, device-cert key source
cloud_print: x-bbl-device-security-sign signs the current time in
milliseconds with a raw RSA PKCS#1 v1.5 signature (no hash), not the
request body. Add signing::device_security_sign() and use it in
create_task; sign_bytes stays as a general SHA-256 signing helper.
Adds a test that recovers the signature and checks it is a bare ms
timestamp.
cert_store: the authoritative printer public key is the device
certificate obtained over MQTT via the app_cert_install command, which
also works for cloud and proxy connections; the TLS-leaf capture is a
LAN-direct TOFU fallback only. Add set_printer_pub_key_from_cert_pem()
to install the device cert and document the key source.
* Degrade signing gracefully when no slicer key is configured
device_security_sign() threw std::runtime_error when no slicer key was
present. That exception propagated through the cloud-connect path
(bambu_network_start -> connect_cloud -> create_task), aborting cloud
connection for users with a cached login but no slicer_key.pem.
Return "" instead, matching maybe_sign's existing behaviour, and omit the
x-bbl-app-certification-id / x-bbl-device-security-sign headers in
create_task when empty. These headers are only enforced on signed writes.