Pin a real H2C's SSDP reply as a test fixture
The SSDP parser's header spelling had never been checked against a real printer, because multicast does not reach this container. It turns out a printer answers a *unicast* M-SEARCH sent straight at it, on both 1990 and 2021, so the reply can be captured across the router. The H2C's 200 OK uses exactly the spelling the parser guessed (DevModel/DevName/DevBind.bambu.com, USN as the serial, a bare-IP Location), so no parsing change was needed - this adds the real shape as a regression test (serial, name and address made up) and corrects the doc comment that said the spelling was unchecked. The P1P did not answer a unicast M-SEARCH on any of 1990/2021/1900. An unsolicited NOTIFY over multicast is still unseen.
This commit is contained in:
+35
-4
@@ -409,10 +409,10 @@ pub(crate) fn valid_serial(value: String) -> Option<String> {
|
||||
/// one thing in a datagram that cannot be mistyped.
|
||||
///
|
||||
/// Tolerant on purpose: it extracts whatever headers are present rather than
|
||||
/// requiring a full set. The exact header spelling is from Bambu's published
|
||||
/// discovery format and has not been checked against a real announcement
|
||||
/// here, so a missing vendor header has to mean "one fewer field filled in",
|
||||
/// not "no printer found".
|
||||
/// requiring a full set. The vendor header spelling was checked against a
|
||||
/// real H2C's `200 OK` reply to a unicast M-SEARCH (2026-10-29); an
|
||||
/// unsolicited NOTIFY has not been seen, so a missing vendor header has to
|
||||
/// mean "one fewer field filled in", not "no printer found".
|
||||
pub fn parse_ssdp(message: &str, from: Ipv4Addr) -> Option<Candidate> {
|
||||
let mut lines = message.lines();
|
||||
let start = lines.next()?.trim();
|
||||
@@ -585,6 +585,37 @@ mod tests {
|
||||
DevConnect.bambu.com: lan\r\n\
|
||||
DevBind.bambu.com: free\r\n";
|
||||
|
||||
/// The reply a real H2C sent to a unicast M-SEARCH on 1990 and on 2021
|
||||
/// (2026-10-29), byte for byte in shape; the serial, name and addresses
|
||||
/// are made up. It is a `200 OK`, not a NOTIFY, and carries
|
||||
/// `Devseclink`/`DevInf`/`DevCap`, which `NOTIFY` above does not.
|
||||
#[test]
|
||||
fn a_real_printers_reply_to_an_msearch_parses() {
|
||||
let reply = "HTTP/1.1 200 OK\r\n\
|
||||
Server: UPnP/1.0\r\n\
|
||||
Date: Mon, 26 Oct 2026 17:21:14 GMT\r\n\
|
||||
Location: 192.168.0.99\r\n\
|
||||
ST: urn:bambulab-com:device:3dprinter:1\r\n\
|
||||
EXT: \r\n\
|
||||
USN: 31B8BP5A0000000\r\n\
|
||||
Cache-Control: max-age=1800\r\n\
|
||||
DevModel.bambu.com: O1C2\r\n\
|
||||
DevName.bambu.com: Workshop\r\n\
|
||||
DevConnect.bambu.com: lan\r\n\
|
||||
DevBind.bambu.com: free\r\n\
|
||||
Devseclink.bambu.com: secure\r\n\
|
||||
DevInf.bambu.com: wlan0\r\n\
|
||||
DevVersion.bambu.com: 01.08.00.00\r\n\
|
||||
DevCap.bambu.com: 1\r\n\r\n";
|
||||
let found = parse_ssdp(reply, Ipv4Addr::new(10, 0, 0, 1)).expect("a Bambu reply");
|
||||
assert_eq!(found.host, "192.168.0.99");
|
||||
assert_eq!(found.serial.as_deref(), Some("31B8BP5A0000000"));
|
||||
assert_eq!(found.model.as_deref(), Some("O1C2"));
|
||||
assert_eq!(found.name.as_deref(), Some("Workshop"));
|
||||
assert_eq!(found.bind_state.as_deref(), Some("free"));
|
||||
assert_eq!(found.detail.as_deref(), Some("Bambu firmware 01.08.00.00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_announcement_yields_every_field_it_carries() {
|
||||
let found = parse_ssdp(NOTIFY, Ipv4Addr::new(10, 0, 0, 1)).expect("a Bambu announcement");
|
||||
|
||||
Reference in New Issue
Block a user