Bambu TLS trust config + finish PrusaLink/PrusaSerial split

Certs:
- Vendor Bambu's shared LAN-mode root CA (certs/bambu_ca2.pem, verified
  self-signed CA:TRUE, see certs/README.md for provenance/fingerprint).
- Config gains bambu_ca_cert_path (per-printer override) and
  bambu_require_valid_cert (the allow/reject flag); printer::bambu::BambuTls
  turns those into BundledCa/Custom/Insecure. connect() doesn't perform a
  real handshake yet (no TLS-capable MQTT client wired in), just reports
  which trust mode it would use.

Also reconciles a rename in flight (Printer -> GenericPrinter trait) and
finishes the PrusaPrinter -> PrusaLinkPrinter/PrusaSerialPrinter split:
added the missing GenericPrinter impl for PrusaSerialPrinter, fixed
PrinterHandle's variant payload types, updated mod.rs's pub use list and
doc comments, and updated the example to the new 5-variant shape.

Verified with cargo check --all-targets (0 errors) and a full run of
cargo run --example printer_polymorphism.
This commit is contained in:
2026-08-28 20:38:10 +00:00
parent d0a17ee50e
commit 9eab5e5a04
8 changed files with 302 additions and 45 deletions
+68 -11
View File
@@ -1,37 +1,94 @@
use super::{Printer, PrinterBase, PrinterError};
use std::path::Path;
use super::{GenericPrinter, PrinterBase, PrinterError};
/// Bambu's shared LAN-mode root CA ("BBL CA2 RSA") — see certs/README.md
/// for how this was obtained and verified. `include_bytes!` embeds it into
/// the compiled binary at build time, so there's no file to ship alongside
/// the executable.
const BUNDLED_CA: &[u8] = include_bytes!("../../certs/bambu_ca2.pem");
/// Which certificate to trust when connecting to a Bambu printer's MQTTS
/// port (8883 — Bambu's LAN mode is TLS-only, there's no unencrypted
/// fallback). This replaces a pair of booleans (`use_tls`/`use_ca`) with
/// one type that can only represent states that actually make sense.
pub enum BambuTls {
/// Bambu's shared root CA. Correct for current-generation printers.
BundledCa,
/// A specific certificate instead — for a printer whose firmware
/// doesn't chain to the shared CA.
Custom(Vec<u8>),
/// Skip certificate verification entirely. The least safe option, only
/// reasonable because LAN mode never leaves your local network.
Insecure,
}
impl BambuTls {
/// Turns `Config`'s two raw settings into one of the three states
/// above. `require_valid_cert = false` always means `Insecure`,
/// regardless of whether a custom cert path was also given.
pub fn from_config(cert_path: Option<&Path>, require_valid_cert: bool) -> anyhow::Result<Self> {
if !require_valid_cert {
return Ok(BambuTls::Insecure);
}
match cert_path {
Some(path) => Ok(BambuTls::Custom(std::fs::read(path)?)),
None => Ok(BambuTls::BundledCa),
}
}
/// The CA certificate bytes to hand to the TLS layer — empty when
/// `Insecure`, since there's nothing to verify against.
pub fn ca_bytes(&self) -> &[u8] {
match self {
BambuTls::BundledCa => BUNDLED_CA,
BambuTls::Custom(bytes) => bytes,
BambuTls::Insecure => &[],
}
}
}
pub struct BambuPrinter {
base: PrinterBase,
pub access_code: String,
tls: BambuTls,
}
impl BambuPrinter {
pub fn new(id: &str, name: &str, host: &str, access_code: &str) -> Self {
pub fn new(id: &str, name: &str, host: &str, access_code: &str, tls: BambuTls) -> Self {
Self {
base: PrinterBase { id: id.into(), name: name.into(), host: host.into() },
access_code: access_code.into(),
tls,
}
}
/// Only `BambuPrinter` has this — it's not on the `Printer` trait at
/// all, so `PrusaPrinter`/`KlipperPrinter` simply don't have it. This
/// is what "adding a printer-specific method" looks like: just define
/// it in this struct's own `impl` block.
/// Only `BambuPrinter` has this — it's not on the `GenericPrinter`
/// trait at all, so the Prusa/Klipper structs simply don't have it.
/// This is what "adding a printer-specific method" looks like: just
/// define it in this struct's own `impl` block.
pub fn dispatch_gcode(&self, file_name: &str) {
println!("[{}] uploading {file_name} over FTPS", self.base.name);
}
}
impl Printer for BambuPrinter {
impl GenericPrinter for BambuPrinter {
fn base(&self) -> &PrinterBase {
&self.base
}
/// OVERRIDE: Bambu's real connect logic opens an MQTTS session (see
/// `adapters::bambu::run_telemetry` for that). Here we just print what
/// it would do, so the focus stays on how dispatch works.
/// OVERRIDE: Bambu's real connect logic opens an MQTTS session on
/// 8883, presenting `self.tls.ca_bytes()` to the TLS layer to verify
/// (or, for `BambuTls::Insecure`, skipping verification). Wiring that
/// up needs a TLS-capable MQTT client (e.g. rumqttc + rustls) — not
/// added back yet, so this just reports which trust mode it would use.
async fn connect(&mut self) -> Result<(), PrinterError> {
println!("[{}] connecting over MQTT with access code {}", self.base.name, self.access_code);
let mode = match &self.tls {
BambuTls::BundledCa => "bundled Bambu CA",
BambuTls::Custom(_) => "custom CA cert",
BambuTls::Insecure => "no certificate verification",
};
println!("[{}] connecting over MQTTS:8883 ({mode})", self.base.name);
Ok(())
}
}