1use anyhow::{anyhow, Error};
6use core::mem::size_of;
7use fidl_fuchsia_boot as fboot;
8use fuchsia_zbi::ZbiType::BootloaderFile;
9use fuchsia_zbi::{ZbiParser, ZbiParserError, ZbiResult};
10use futures::prelude::*;
11use std::collections::HashMap;
12use std::str::from_utf8;
13use std::sync::Arc;
14
15pub struct Items {
16 zbi_parser: ZbiParser,
17 bootloader_files: HashMap<String, Vec<u8>>,
18}
19
20impl Items {
21 pub fn new(mut zbi_parser: ZbiParser) -> Result<Arc<Self>, Error> {
22 let bootloader_files = match zbi_parser.try_get_item(BootloaderFile.into_raw(), None) {
26 Ok(result) => {
27 zbi_parser.release_item(BootloaderFile)?;
28 Items::parse_bootloader_items(result)?
29 }
30 Err(_) => HashMap::new(),
31 };
32
33 Ok(Arc::new(Items { zbi_parser, bootloader_files }))
34 }
35
36 pub fn parse_bootloader_items(
37 items: Vec<ZbiResult>,
38 ) -> Result<HashMap<String, Vec<u8>>, Error> {
39 let mut bootloader_result = HashMap::new();
40 for item in items {
41 let length = item.bytes.len();
45 if length < size_of::<u8>() {
46 return Err(anyhow!(
47 "Bootloader ZBI item is too small to contain the size of the name"
48 ));
49 }
50
51 let mut offset = 0;
54 let name_length = item.bytes[offset].try_into()?;
55 offset += size_of::<u8>();
56
57 if length < (offset + name_length) {
58 return Err(anyhow!(
59 "Bootloader ZBI item is too small to contain the reported length of the name"
60 ));
61 }
62
63 let name = from_utf8(&item.bytes[offset..(offset + name_length)])?.to_owned();
64 offset = offset
65 .checked_add(name_length)
66 .ok_or_else(|| anyhow!("Overflow when parsing bootloader ZBI item"))?;
67
68 if bootloader_result.contains_key(&name) {
69 return Err(anyhow!("Bootloader items in ZBI have duplicate filenames: {}", name));
70 }
71
72 bootloader_result.insert(name, item.bytes[offset..].to_vec());
73 }
74
75 Ok(bootloader_result)
76 }
77
78 pub async fn serve(
79 self: Arc<Self>,
80 mut stream: fboot::ItemsRequestStream,
81 ) -> Result<(), Error> {
82 while let Some(request) = stream.try_next().await? {
83 match request {
84 fboot::ItemsRequest::Get { type_, extra, responder } => {
85 match self.zbi_parser.try_get_last_matching_item(type_, extra) {
86 Ok(result) => {
87 let vmo = zx::Vmo::create(result.bytes.len().try_into()?)?;
88 vmo.write(&result.bytes, 0)?;
89 responder.send(Some(vmo), result.bytes.len().try_into()?)?
90 }
91 Err(_) => responder.send(None, 0)?,
92 }
93 }
94 fboot::ItemsRequest::Get2 { type_, extra, responder } => {
95 let extra = if let Some(extra) = extra { Some((*extra).n) } else { None };
96 let item_vec = match self.zbi_parser.try_get_item(type_, extra) {
97 Ok(vec) => vec
98 .iter()
99 .map(|result| -> Result<fboot::RetrievedItems, Error> {
100 let vmo = zx::Vmo::create(result.bytes.len().try_into()?)?;
101 vmo.write(&result.bytes, 0)?;
102 Ok(fboot::RetrievedItems {
103 payload: vmo,
104 length: result.bytes.len().try_into()?,
105 extra: result.extra,
106 })
107 })
108 .collect::<Result<Vec<fboot::RetrievedItems>, Error>>()
109 .map_err(|_| zx::Status::INTERNAL.into_raw()),
110 Err(err) => {
111 match err {
112 ZbiParserError::ItemNotStored { .. } => {
113 Err(zx::Status::NOT_SUPPORTED.into_raw())
114 }
115 _ => {
116 Ok(vec![])
119 }
120 }
121 }
122 };
123
124 responder.send(item_vec)?
125 }
126 fboot::ItemsRequest::GetBootloaderFile { filename, responder } => {
127 match self.bootloader_files.get(&filename) {
128 Some(bytes) => {
129 let vmo = zx::Vmo::create(bytes.len().try_into()?)?;
130 vmo.write(&bytes, 0)?;
131 responder.send(Some(vmo))?
132 }
133 None => responder.send(None)?,
134 }
135 }
136 };
137 }
138 Ok(())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use fuchsia_async as fasync;
146 use fuchsia_zbi::{
147 zbi_header_t, ZbiType, ZBI_CONTAINER_MAGIC, ZBI_FLAGS_VERSION, ZBI_ITEM_MAGIC,
148 ZBI_ITEM_NO_CRC32,
149 };
150 use zerocopy::byteorder::little_endian::U32;
151 use zerocopy::IntoBytes;
152
153 const ZBI_HEADER_SIZE: usize = size_of::<zbi_header_t>();
154
155 fn serve_items(zbi_parser: ZbiParser) -> Result<fboot::ItemsProxy, Error> {
156 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fboot::ItemsMarker>();
157 fasync::Task::local(
158 Items::new(zbi_parser)?
159 .serve(stream)
160 .unwrap_or_else(|e| panic!("Error while serving items service: {}", e)),
161 )
162 .detach();
163 Ok(proxy)
164 }
165
166 struct ZbiBuilder {
167 zbi_bytes: Vec<u8>,
168 }
169
170 impl ZbiBuilder {
171 fn get_bootloader_item(name: &[u8], payload: &[u8]) -> Vec<u8> {
172 let mut result = vec![name.len().try_into().unwrap()];
176 result.extend(name);
177 result.extend(payload);
178 result
179 }
180
181 fn simple_header(zbi_type: ZbiType, extra: u32, length: u32) -> zbi_header_t {
182 zbi_header_t {
183 zbi_type: U32::new(zbi_type as u32),
184 length: U32::new(length),
185 extra: U32::new(extra),
186 flags: U32::new(ZBI_FLAGS_VERSION),
187 reserved_0: U32::new(0),
188 reserved_1: U32::new(0),
189 magic: U32::new(ZBI_ITEM_MAGIC),
190 crc32: U32::new(ZBI_ITEM_NO_CRC32),
191 }
192 }
193
194 fn add_item(mut self, data: &[u8]) -> Self {
195 self.zbi_bytes.extend(data);
196 let padding_amount = ZbiParser::align_zbi_item(data.len().try_into().unwrap()).unwrap()
197 as usize
198 - data.len();
199 if padding_amount > 0 {
200 let padding = vec![0u8; padding_amount];
201 self.zbi_bytes.extend(padding);
202 }
203 self
204 }
205
206 fn add_header(mut self, zbi_type: ZbiType, extra: u32, length: u32) -> Self {
207 self.zbi_bytes.extend(ZbiBuilder::simple_header(zbi_type, extra, length).as_bytes());
208 self
209 }
210
211 fn new() -> Self {
212 Self {
213 zbi_bytes: ZbiBuilder::simple_header(ZbiType::Container, ZBI_CONTAINER_MAGIC, 0)
214 .as_bytes()
215 .to_vec(),
216 }
217 }
218
219 fn calculate_item_length(mut self) -> Self {
220 let item_length =
221 U32::new(u32::try_from(self.zbi_bytes.len() - ZBI_HEADER_SIZE).unwrap());
222 let item_length_bytes = item_length.as_bytes();
223
224 let mut i = 4usize;
225 for x in item_length_bytes {
226 self.zbi_bytes[i] = *x;
227 i += 1;
228 }
229
230 self
231 }
232
233 fn generate(self) -> Result<zx::Vmo, Error> {
234 let vmo = zx::Vmo::create(self.zbi_bytes.len().try_into()?)?;
235 vmo.write(&self.zbi_bytes, 0)?;
236 Ok(vmo)
237 }
238 }
239
240 #[fuchsia::test]
241 async fn get2_untracked_item_not_found() {
242 let zbi_type = ZbiType::Crashlog;
243 let item = b"abcd";
244
245 let zbi = ZbiBuilder::new()
246 .add_header(zbi_type, 0, item.len().try_into().unwrap())
247 .add_item(item)
248 .calculate_item_length()
249 .generate()
250 .expect("failed to create ZBI");
251 let parser = ZbiParser::new(zbi)
252 .set_store_item(ZbiType::Cmdline)
253 .parse()
254 .expect("failed to parse ZBI");
255
256 let item_service = serve_items(parser).expect("failed to serve items");
257
258 let result = item_service
261 .get2(zbi_type.into_raw(), None)
262 .await
263 .expect("failed to query item service");
264 assert_eq!(zx::Status::from_raw(result.unwrap_err()), zx::Status::NOT_SUPPORTED);
265 }
266
267 #[fuchsia::test]
268 async fn get2_multiple_items_same_type_different_extras() {
269 let zbi_type = ZbiType::Crashlog;
270
271 let item1 = b"abcd";
272 let extra1 = 123;
273
274 let item2 = b"efgh";
275 let extra2 = 456;
276
277 let zbi = ZbiBuilder::new()
278 .add_header(zbi_type, extra1, item1.len().try_into().unwrap())
279 .add_item(item1)
280 .add_header(zbi_type, extra2, item2.len().try_into().unwrap())
281 .add_item(item2)
282 .calculate_item_length()
283 .generate()
284 .expect("failed to create ZBI");
285 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
286
287 let item_service = serve_items(parser).expect("failed to serve items");
288
289 let result = item_service
291 .get2(zbi_type.into_raw(), None)
292 .await
293 .expect("failed to query item service")
294 .expect("failed to retrieve items");
295
296 assert_eq!(result.len(), 2);
297
298 let retrieved =
299 result.iter().find(|item| item.extra == extra1).expect("failed to find item");
300 let mut bytes = vec![0; retrieved.length as usize];
301 retrieved.payload.read(&mut bytes, 0).expect("failed to read bytes");
302 assert_eq!(bytes, item1);
303 assert_eq!(retrieved.length, item1.len() as u32);
304
305 let retrieved =
306 result.iter().find(|item| item.extra == extra2).expect("failed to find item");
307 let mut bytes = vec![0; retrieved.length as usize];
308 retrieved.payload.read(&mut bytes, 0).expect("failed to read bytes");
309 assert_eq!(bytes, item2);
310 assert_eq!(retrieved.length, item2.len() as u32);
311
312 let result = item_service
314 .get2(zbi_type.into_raw(), Some(&fidl_fuchsia_boot::Extra { n: extra2 }))
315 .await
316 .expect("failed to query item service")
317 .expect("failed to retrieve items");
318
319 assert!(result.iter().find(|item| item.extra == extra2).is_some());
320 assert!(result.iter().find(|item| item.extra == extra1).is_none());
321 }
322
323 #[fuchsia::test]
324 async fn item_not_found_no_matching_extra() {
325 let actual_extra = 54321;
327 let queried_extra = 12345;
328 let zbi_type = ZbiType::Crashlog;
329
330 let item = b"abcd";
331 let zbi = ZbiBuilder::new()
332 .add_header(zbi_type, actual_extra, item.len().try_into().unwrap())
333 .add_item(item)
334 .calculate_item_length()
335 .generate()
336 .expect("failed to create ZBI");
337 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
338
339 let item_service = serve_items(parser).expect("failed to serve items");
340 let (vmo, length) = item_service
341 .get(zbi_type as u32, queried_extra)
342 .await
343 .expect("failed to query item service");
344
345 assert!(vmo.is_none());
346 assert_eq!(length, 0);
347 }
348
349 #[fuchsia::test]
350 async fn item_not_found_no_matching_zbi_type() {
351 let extra = 54321;
354 let actual_zbi_type = ZbiType::Crashlog;
355 let queried_zbi_type = ZbiType::KernelDriver;
356
357 let item = b"abcd";
358 let zbi = ZbiBuilder::new()
359 .add_header(actual_zbi_type, extra, item.len().try_into().unwrap())
360 .add_item(item)
361 .calculate_item_length()
362 .generate()
363 .expect("failed to create ZBI");
364 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
365
366 let item_service = serve_items(parser).expect("failed to serve items");
367 let (vmo, length) = item_service
368 .get(queried_zbi_type as u32, extra)
369 .await
370 .expect("failed to query item service");
371
372 assert!(vmo.is_none());
373 assert_eq!(length, 0);
374 }
375
376 #[fuchsia::test]
377 async fn get_non_bootloader_item() {
378 let zbi_type = ZbiType::Crashlog;
380 let extra = 12345;
381
382 let item = b"abcd";
383 let zbi = ZbiBuilder::new()
384 .add_header(zbi_type, extra, item.len().try_into().unwrap())
385 .add_item(item)
386 .calculate_item_length()
387 .generate()
388 .expect("failed to create ZBI");
389 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
390
391 let item_service = serve_items(parser).expect("failed to serve items");
392 let (vmo, length) =
393 item_service.get(zbi_type as u32, extra).await.expect("failed to query item service");
394
395 assert!(vmo.is_some());
396 let mut bytes = vec![0; item.len()];
397 vmo.unwrap().read(&mut bytes, 0).expect("failed to read bytes");
398 assert_eq!(bytes, b"abcd");
399 assert_eq!(length, item.len() as u32);
400 }
401
402 #[fuchsia::test]
403 async fn badly_formatted_bootloader_file() {
404 let mut item = ZbiBuilder::get_bootloader_item(b"bootloader_name", b"");
407 item.resize(item.len() / 2, 0);
408
409 let zbi = ZbiBuilder::new()
410 .add_header(BootloaderFile, 0, item.len().try_into().unwrap())
411 .add_item(&item)
412 .calculate_item_length()
413 .generate()
414 .expect("failed to create ZBI");
415 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
416
417 let item_service = serve_items(parser);
418 assert!(item_service.is_err());
419 }
420
421 #[fuchsia::test]
422 async fn duplicate_bootloader_files() {
423 let item1 = ZbiBuilder::get_bootloader_item(b"file", b"abcd");
424 let item2 = ZbiBuilder::get_bootloader_item(b"file", b"efgh");
425
426 let zbi = ZbiBuilder::new()
427 .add_header(BootloaderFile, 0, item1.len().try_into().unwrap())
428 .add_item(&item1)
429 .add_header(BootloaderFile, 0, item2.len().try_into().unwrap())
430 .add_item(&item2)
431 .calculate_item_length()
432 .generate()
433 .expect("failed to create ZBI");
434 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
435
436 let item_service = serve_items(parser);
437 assert!(item_service.is_err());
438 }
439
440 #[fuchsia::test]
441 async fn no_matching_bootloader_file() {
442 let actual_name = b"this_is_a_name";
443 let queried_name = "this_is_NOT_a_name";
444 let item = ZbiBuilder::get_bootloader_item(actual_name, b"this_is_a_payload");
445
446 let zbi = ZbiBuilder::new()
447 .add_header(BootloaderFile, 0, item.len().try_into().unwrap())
448 .add_item(&item)
449 .calculate_item_length()
450 .generate()
451 .expect("failed to create ZBI");
452 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
453
454 let item_service = serve_items(parser).expect("failed to serve items");
455 let vmo = item_service
456 .get_bootloader_file(queried_name)
457 .await
458 .expect("failed to query item service");
459
460 assert!(vmo.is_none());
461 }
462
463 #[fuchsia::test]
464 async fn get_bootloader_files() {
465 let name1 = b"this_is_a_name";
466 let payload1 = b"this_is_a_payload";
467 let item1 = ZbiBuilder::get_bootloader_item(name1, payload1);
468
469 let name2 = b"this_is_another_name";
470 let payload2 = b"this_is_another_payload";
471 let item2 = ZbiBuilder::get_bootloader_item(name2, payload2);
472
473 let zbi = ZbiBuilder::new()
474 .add_header(BootloaderFile, 0, item1.len().try_into().unwrap())
475 .add_item(&item1)
476 .add_header(BootloaderFile, 0, item2.len().try_into().unwrap())
477 .add_item(&item2)
478 .calculate_item_length()
479 .generate()
480 .expect("failed to create ZBI");
481 let parser = ZbiParser::new(zbi).parse().expect("failed to parse ZBI");
482
483 let item_service = serve_items(parser).expect("failed to serve items");
484
485 let response = item_service
486 .get_bootloader_file(from_utf8(name1).unwrap())
487 .await
488 .expect("failed to query item service");
489
490 assert!(response.is_some());
491 let vmo = response.unwrap();
492 let length = vmo.get_content_size().unwrap().try_into().unwrap();
493 let mut bytes = vec![0; length];
494 vmo.read(&mut bytes, 0).expect("failed to read bytes");
495
496 assert_eq!(bytes, payload1);
497 assert_eq!(length, payload1.len());
498
499 let response = item_service
500 .get_bootloader_file(from_utf8(name2).unwrap())
501 .await
502 .expect("failed to query item service");
503
504 assert!(response.is_some());
505 let vmo = response.unwrap();
506 let length = vmo.get_content_size().unwrap().try_into().unwrap();
507 let mut bytes = vec![0; length];
508 vmo.read(&mut bytes, 0).expect("failed to read bytes");
509
510 assert_eq!(bytes, payload2);
511 assert_eq!(length, payload2.len());
512 }
513}