use super::protocol::*; use std::{ io::{BufRead, BufReader, Read}, str::FromStr, }; use quick_xml::{ events::{attributes::Attributes, Event}, Reader, }; pub fn parse(stream: S) -> Protocol { let mut reader = Reader::from_reader(BufReader::new(stream)); let reader_config = reader.config_mut(); reader_config.trim_text(true); reader_config.expand_empty_elements = true; parse_protocol(reader) } fn decode_utf8_or_panic(txt: Vec) -> String { match String::from_utf8(txt) { Ok(txt) => txt, Err(e) => panic!("Invalid UTF8: '{}'", String::from_utf8_lossy(&e.into_bytes())), } } fn parse_or_panic(txt: &[u8]) -> T { match std::str::from_utf8(txt).ok().and_then(|val| val.parse().ok()) { Some(version) => version, None => panic!( "Invalid value '{}' for parsing type '{}'", String::from_utf8_lossy(txt), std::any::type_name::() ), } } fn init_protocol(reader: &mut Reader) -> Protocol { // Check two firsts lines for protocol tag for _ in 0..3 { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Decl(_) | Event::DocType(_) | Event::Comment(_)) => { continue; } Ok(Event::Start(bytes)) => { assert!(bytes.name().into_inner() == b"protocol", "Missing protocol toplevel tag"); if let Some(attr) = bytes .attributes() .filter_map(|res| res.ok()) .find(|attr| attr.key.into_inner() == b"name") { return Protocol::new(decode_utf8_or_panic(attr.value.into_owned())); } else { panic!("Protocol must have a name"); } } _ => panic!("Ill-formed protocol file"), } } panic!("Ill-formed protocol file"); } fn parse_protocol(mut reader: Reader) -> Protocol { let mut protocol = init_protocol(&mut reader); loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => { match bytes.name().into_inner() { b"copyright" => { // parse the copyright let mut copyright = String::new(); loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Text(text)) => { if let Ok(text) = text.decode() { copyright.push_str(&text); } } Ok(Event::CData(cdata)) => { if let Ok(cdata) = String::from_utf8(cdata.into_inner().into()) { copyright.push_str(&cdata); } } Ok(Event::GeneralRef(byte_ref)) => { if let Ok(Some(c)) = byte_ref.resolve_char_ref() { copyright.push(c); } else if let Ok(content) = byte_ref.xml10_content() { if let Some(s) = quick_xml::escape::resolve_xml_entity(&content) { copyright.push_str(s); } } } Ok(Event::End(bytes)) => { assert!( bytes.name().into_inner() == "copyright".as_bytes(), "Ill-formed protocol file" ); break; } e => { panic!("Ill-formed protocol file: {e:?}"); } } } protocol.copyright = Some(copyright) } b"interface" => { protocol.interfaces.push(parse_interface(&mut reader, bytes.attributes())); } b"description" => { protocol.description = Some(parse_description(&mut reader, bytes.attributes())); } name => panic!( "Ill-formed protocol file: unexpected token `{}` in protocol {}", String::from_utf8_lossy(name), protocol.name ), } } Ok(Event::End(bytes)) => { let name = bytes.name().into_inner(); assert!( name == b"protocol", "Unexpected closing token `{}`", String::from_utf8_lossy(name) ); break; } // ignore comments Ok(Event::Comment(_)) => {} e => panic!("Ill-formed protocol file: unexpected token {e:?}"), } } protocol } fn parse_interface(reader: &mut Reader, attrs: Attributes) -> Interface { let mut interface = Interface::new(); for attr in attrs.filter_map(|res| res.ok()) { match attr.key.into_inner() { b"name" => interface.name = decode_utf8_or_panic(attr.value.into_owned()), b"version" => interface.version = parse_or_panic(&attr.value), _ => {} } } loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => match bytes.name().into_inner() { b"description" => { interface.description = Some(parse_description(reader, bytes.attributes())) } b"request" => interface.requests.push(parse_request(reader, bytes.attributes())), b"event" => interface.events.push(parse_event(reader, bytes.attributes())), b"enum" => interface.enums.push(parse_enum(reader, bytes.attributes())), name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)), }, Ok(Event::End(bytes)) if bytes.name().into_inner() == b"interface" => break, _ => {} } } interface } fn parse_description(reader: &mut Reader, attrs: Attributes) -> (String, String) { let mut summary = String::new(); for attr in attrs.filter_map(|res| res.ok()) { if attr.key.into_inner() == b"summary" { summary = String::from_utf8_lossy(&attr.value) .split_whitespace() .collect::>() .join(" "); } } let mut description = String::new(); // Some protocols have comments inside their descriptions, so we need to parse them in a loop and // concatenate the parts into a single block of text loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Text(bytes)) => { if !description.is_empty() { description.push_str("\n\n"); } description.push_str(&bytes.decode().unwrap_or_default()) } Ok(Event::End(bytes)) if bytes.name().into_inner() == b"description" => break, Ok(Event::Comment(_)) => {} e => panic!("Ill-formed protocol file: {e:?}"), } } (summary, description) } fn parse_request(reader: &mut Reader, attrs: Attributes) -> Message { let mut request = Message::new(); for attr in attrs.filter_map(|res| res.ok()) { match attr.key.into_inner() { b"name" => request.name = decode_utf8_or_panic(attr.value.into_owned()), b"type" => request.typ = Some(parse_type(&attr.value)), b"since" => request.since = parse_or_panic(&attr.value), _ => {} } } loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => match bytes.name().into_inner() { b"description" => { request.description = Some(parse_description(reader, bytes.attributes())) } b"arg" => request.args.push(parse_arg(reader, bytes.attributes())), name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)), }, Ok(Event::End(bytes)) if bytes.name().into_inner() == b"request" => break, _ => {} } } request } fn parse_enum(reader: &mut Reader, attrs: Attributes) -> Enum { let mut enu = Enum::new(); for attr in attrs.filter_map(|res| res.ok()) { match attr.key.into_inner() { b"name" => enu.name = decode_utf8_or_panic(attr.value.into_owned()), b"since" => enu.since = parse_or_panic(&attr.value), b"bitfield" if attr.value.as_ref() == b"true" => enu.bitfield = true, b"bitfield" => {} _ => {} } } loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => match bytes.name().into_inner() { b"description" => { enu.description = Some(parse_description(reader, bytes.attributes())) } b"entry" => enu.entries.push(parse_entry(reader, bytes.attributes())), name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)), }, Ok(Event::End(bytes)) if bytes.name().into_inner() == b"enum" => break, _ => {} } } enu } fn parse_event(reader: &mut Reader, attrs: Attributes) -> Message { let mut event = Message::new(); for attr in attrs.filter_map(|res| res.ok()) { match attr.key.into_inner() { b"name" => event.name = decode_utf8_or_panic(attr.value.into_owned()), b"type" => event.typ = Some(parse_type(&attr.value)), b"since" => event.since = parse_or_panic(&attr.value), _ => {} } } loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => match bytes.name().into_inner() { b"description" => { event.description = Some(parse_description(reader, bytes.attributes())) } b"arg" => event.args.push(parse_arg(reader, bytes.attributes())), name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)), }, Ok(Event::End(bytes)) if bytes.name().into_inner() == b"event" => break, _ => {} } } event } fn parse_arg(reader: &mut Reader, attrs: Attributes) -> Arg { let mut arg = Arg::new(); for attr in attrs.filter_map(|res| res.ok()) { match attr.key.into_inner() { b"name" => arg.name = decode_utf8_or_panic(attr.value.into_owned()), b"type" => arg.typ = parse_type(&attr.value), b"summary" => { arg.summary = Some( String::from_utf8_lossy(&attr.value) .split_whitespace() .collect::>() .join(" "), ) } b"interface" => arg.interface = Some(parse_or_panic(&attr.value)), b"allow-null" if attr.value.as_ref() == b"true" => arg.allow_null = true, b"allow-null" => {} b"enum" => arg.enum_ = Some(decode_utf8_or_panic(attr.value.into_owned())), _ => {} } } loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => match bytes.name().into_inner() { b"description" => { arg.description = Some(parse_description(reader, bytes.attributes())) } name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)), }, Ok(Event::End(bytes)) if bytes.name().into_inner() == b"arg" => break, _ => {} } } arg } fn parse_type(txt: &[u8]) -> Type { match txt { b"int" => Type::Int, b"uint" => Type::Uint, b"fixed" => Type::Fixed, b"string" => Type::String, b"object" => Type::Object, b"new_id" => Type::NewId, b"array" => Type::Array, b"fd" => Type::Fd, b"destructor" => Type::Destructor, e => panic!("Unexpected type: {}", String::from_utf8_lossy(e)), } } fn parse_entry(reader: &mut Reader, attrs: Attributes) -> Entry { let mut entry = Entry::new(); for attr in attrs.filter_map(|res| res.ok()) { match attr.key.into_inner() { b"name" => entry.name = decode_utf8_or_panic(attr.value.into_owned()), b"value" => { entry.value = if attr.value.starts_with(b"0x") { if let Some(val) = std::str::from_utf8(&attr.value[2..]) .ok() .and_then(|s| u32::from_str_radix(s, 16).ok()) { val } else { panic!("Invalid number: {}", String::from_utf8_lossy(&attr.value)) } } else { parse_or_panic(&attr.value) }; } b"since" => entry.since = parse_or_panic(&attr.value), b"summary" => { entry.summary = Some( String::from_utf8_lossy(&attr.value) .split_whitespace() .collect::>() .join(" "), ) } _ => {} } } loop { match reader.read_event_into(&mut Vec::new()) { Ok(Event::Start(bytes)) => match bytes.name().into_inner() { b"description" => { entry.description = Some(parse_description(reader, bytes.attributes())) } name => panic!("Unexpected token: `{}`", String::from_utf8_lossy(name)), }, Ok(Event::End(bytes)) if bytes.name().into_inner() == b"entry" => break, _ => {} } } entry } #[cfg(test)] mod tests { #[test] fn xml_parse() { let protocol_file = std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap(); let _ = crate::parse::parse(protocol_file); } #[test] fn headerless_xml_parse() { let protocol_file = std::fs::File::open("./tests/scanner_assets/test-headerless-protocol.xml").unwrap(); let _ = crate::parse::parse(protocol_file); } }