Skip to main content

android_abx/encode/
writer.rs

1use std::collections::HashMap;
2use std::io::Write;
3
4use crate::{AbxError, Attribute, AttributeValue, Event, InternedStr, MAGIC, Result};
5use crate::{
6    CMD_ATTRIBUTE, CMD_CDSECT, CMD_COMMENT, CMD_DOCDECL, CMD_END_DOCUMENT, CMD_END_TAG,
7    CMD_ENTITY_REF, CMD_IGNORABLE_WHITESPACE, CMD_PROCESSING_INSTRUCTION, CMD_START_DOCUMENT,
8    CMD_START_TAG, CMD_TEXT,
9};
10use crate::{
11    INTERNED_NEW, TYPE_BOOLEAN_FALSE, TYPE_BOOLEAN_TRUE, TYPE_BYTES_BASE64, TYPE_BYTES_HEX,
12    TYPE_DOUBLE, TYPE_FLOAT, TYPE_INT, TYPE_INT_HEX, TYPE_LONG, TYPE_LONG_HEX, TYPE_NULL,
13    TYPE_STRING, TYPE_STRING_INTERNED,
14};
15
16// Linear scan below this many names, `HashMap` above.
17const LINEAR_SCAN_LIMIT: usize = 32;
18
19const MAX_UNSIGNED_SHORT: usize = 65_535;
20
21/// Interns tag/attribute names only, never values (AOSP `attribute()`).
22struct InternedPool {
23    names: Vec<InternedStr>,
24    index: Option<HashMap<InternedStr, u16>>,
25}
26
27impl InternedPool {
28    fn new() -> Self {
29        InternedPool {
30            names: Vec::new(),
31            index: None,
32        }
33    }
34
35    fn find(&self, s: &InternedStr) -> Option<u16> {
36        match &self.index {
37            Some(index) => index.get(s).copied(),
38            None => self.names.iter().position(|n| n == s).map(|i| i as u16),
39        }
40    }
41
42    fn write(&mut self, out: &mut impl Write, s: &InternedStr) -> Result<()> {
43        if let Some(idx) = self.find(s) {
44            out.write_all(&idx.to_be_bytes())?;
45        } else {
46            out.write_all(&INTERNED_NEW.to_be_bytes())?;
47            write_utf(out, s)?;
48
49            // Past 0xFFFE entries, stop caching (AOSP `writeInternedUTF`).
50            if self.names.len() < INTERNED_NEW as usize {
51                let idx = self.names.len() as u16;
52                self.names.push(s.clone());
53                if let Some(index) = &mut self.index {
54                    index.insert(s.clone(), idx);
55                } else if self.names.len() > LINEAR_SCAN_LIMIT {
56                    self.index = Some(self.names.iter().cloned().zip(0u16..).collect());
57                }
58            }
59        }
60        Ok(())
61    }
62}
63
64fn write_bytes_blob(out: &mut impl Write, bytes: &[u8]) -> Result<()> {
65    if bytes.len() > MAX_UNSIGNED_SHORT {
66        return Err(AbxError::ValueTooLong {
67            len: bytes.len(),
68            max: MAX_UNSIGNED_SHORT,
69        });
70    }
71    out.write_all(&(bytes.len() as u16).to_be_bytes())?;
72    out.write_all(bytes)?;
73    Ok(())
74}
75
76fn write_utf(out: &mut impl Write, s: &str) -> Result<()> {
77    write_bytes_blob(out, s.as_bytes())
78}
79
80/// Encodes [`Event`]s as ABX to any [`Write`] sink.
81///
82/// Tag and attribute names are interned, attribute values are not. Events are
83/// written as given: the writer does not check that tags are balanced. Writes
84/// are small and frequent, so wrap files and sockets in a
85/// [`BufWriter`](std::io::BufWriter).
86///
87/// # Examples
88///
89/// ```
90/// use android_abx::{AbxParser, AbxWriter, Attribute, AttributeValue, Event};
91///
92/// let mut writer = AbxWriter::new(Vec::new())?;
93/// writer.write_event(&Event::StartDocument)?;
94/// writer.write_event(&Event::StartTag {
95///     name: "pkg".into(),
96///     attributes: vec![Attribute {
97///         name: "version".into(),
98///         value: AttributeValue::Int(3),
99///     }],
100/// })?;
101/// writer.write_event(&Event::EndTag { name: "pkg".into() })?;
102/// writer.write_event(&Event::EndDocument)?;
103/// let data = writer.into_inner();
104///
105/// let xml = AbxParser::new(&data)?.to_xml()?;
106/// assert!(xml.ends_with(r#"<pkg version="3"></pkg>"#));
107/// # Ok::<(), android_abx::AbxError>(())
108/// ```
109pub struct AbxWriter<W: Write> {
110    writer: W,
111    pool: InternedPool,
112}
113
114impl<W: Write> AbxWriter<W> {
115    /// Creates a writer and writes the header.
116    ///
117    /// # Errors
118    ///
119    /// Returns [`AbxError::Io`] if writing fails.
120    pub fn new(mut writer: W) -> Result<Self> {
121        writer.write_all(&MAGIC)?;
122        Ok(AbxWriter {
123            writer,
124            pool: InternedPool::new(),
125        })
126    }
127
128    /// Encodes and writes one event.
129    ///
130    /// # Errors
131    ///
132    /// Returns [`AbxError::ValueTooLong`] if a string or byte value is longer than
133    /// 65,535 bytes, or [`AbxError::Io`] if writing fails. After an error, the output
134    /// is incomplete and should be discarded.
135    pub fn write_event(&mut self, ev: &Event) -> Result<()> {
136        match ev {
137            Event::StartDocument => self.writer.write_all(&[CMD_START_DOCUMENT | TYPE_NULL])?,
138            Event::EndDocument => self.writer.write_all(&[CMD_END_DOCUMENT | TYPE_NULL])?,
139            Event::StartTag { name, attributes } => {
140                self.writer
141                    .write_all(&[TYPE_STRING_INTERNED | CMD_START_TAG])?;
142                self.pool.write(&mut self.writer, name)?;
143                for attr in attributes {
144                    self.write_attribute(attr)?;
145                }
146            }
147            Event::EndTag { name } => {
148                self.writer
149                    .write_all(&[TYPE_STRING_INTERNED | CMD_END_TAG])?;
150                self.pool.write(&mut self.writer, name)?;
151            }
152            Event::Text(s) => self.write_text_token(CMD_TEXT, s)?,
153            Event::CdataSection(s) => self.write_text_token(CMD_CDSECT, s)?,
154            Event::Comment(s) => self.write_text_token(CMD_COMMENT, s)?,
155            Event::ProcessingInstruction(s) => {
156                self.write_text_token(CMD_PROCESSING_INSTRUCTION, s)?
157            }
158            Event::EntityReference(s) => self.write_text_token(CMD_ENTITY_REF, s)?,
159            Event::IgnorableWhitespace(s) => self.write_text_token(CMD_IGNORABLE_WHITESPACE, s)?,
160            Event::DocDecl(s) => self.write_text_token(CMD_DOCDECL, s)?,
161        }
162        Ok(())
163    }
164
165    // Always TYPE_STRING: AOSP's parser misreads TYPE_NULL text tokens.
166    fn write_text_token(&mut self, cmd: u8, s: &str) -> Result<()> {
167        self.writer.write_all(&[TYPE_STRING | cmd])?;
168        write_utf(&mut self.writer, s)?;
169        Ok(())
170    }
171
172    fn write_attribute(&mut self, attr: &Attribute) -> Result<()> {
173        let type_nibble = match &attr.value {
174            AttributeValue::Null => TYPE_NULL,
175            AttributeValue::String(_) => TYPE_STRING,
176            AttributeValue::BytesHex(_) => TYPE_BYTES_HEX,
177            AttributeValue::BytesBase64(_) => TYPE_BYTES_BASE64,
178            AttributeValue::Int(_) => TYPE_INT,
179            AttributeValue::IntHex(_) => TYPE_INT_HEX,
180            AttributeValue::Long(_) => TYPE_LONG,
181            AttributeValue::LongHex(_) => TYPE_LONG_HEX,
182            AttributeValue::Float(_) => TYPE_FLOAT,
183            AttributeValue::Double(_) => TYPE_DOUBLE,
184            AttributeValue::Boolean(true) => TYPE_BOOLEAN_TRUE,
185            AttributeValue::Boolean(false) => TYPE_BOOLEAN_FALSE,
186        };
187        self.writer.write_all(&[type_nibble | CMD_ATTRIBUTE])?;
188        self.pool.write(&mut self.writer, &attr.name)?;
189        match &attr.value {
190            AttributeValue::Null | AttributeValue::Boolean(_) => {}
191            AttributeValue::String(s) => write_utf(&mut self.writer, s)?,
192            AttributeValue::BytesHex(b) | AttributeValue::BytesBase64(b) => {
193                write_bytes_blob(&mut self.writer, b)?
194            }
195            AttributeValue::Int(v) => self.writer.write_all(&v.to_be_bytes())?,
196            AttributeValue::IntHex(v) => self.writer.write_all(&v.to_be_bytes())?,
197            AttributeValue::Long(v) => self.writer.write_all(&v.to_be_bytes())?,
198            AttributeValue::LongHex(v) => self.writer.write_all(&v.to_be_bytes())?,
199            AttributeValue::Float(v) => self.writer.write_all(&v.to_be_bytes())?,
200            AttributeValue::Double(v) => self.writer.write_all(&v.to_be_bytes())?,
201        }
202        Ok(())
203    }
204
205    /// Returns the underlying writer, without flushing it.
206    pub fn into_inner(self) -> W {
207        self.writer
208    }
209}