1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This library provides APIs for receiving, processing and replying to fastboot commands. To use
16 //! the library:
17 //!
18 //! 1. Provide a transport backend by implementing the `Transport` trait.
19 //!
20 //! ```
21 //!
22 //! struct FastbootTransport {}
23 //!
24 //! impl Transport<MyErrorType> for TestTransport {
25 //! fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize, TransportError> {
26 //! todo!();
27 //! }
28 //!
29 //! fn send_packet(&mut self, packet: &[u8]) -> Result<(), TransportError> {
30 //! todo!();
31 //! }
32 //! }
33 //! ```
34 //!
35 //! 2. Provide a fastboot command backend by implementing the `FastbootImplementation` trait.
36 //! i.e.
37 //!
38 //! ```
39 //!
40 //! struct FastbootCommand {}
41 //!
42 //! impl FastbootImplementation for FastbootTest {
43 //! fn get_var(
44 //! &mut self,
45 //! var: &str,
46 //! args: Split<char>,
47 //! out: &mut [u8],
48 //! ) -> CommandResult<usize> {
49 //! todo!();
50 //! }
51 //!
52 //! ...
53 //! }
54 //!```
55 //!
56 //! 3. Construct a `Fastboot` object with a given download buffer. Pass the transport, command
57 //! implementation and call the `run()` method:
58 //!
59 //! ```
60 //! let mut fastboot_impl: FastbootCommand = ...;
61 //! let mut transport: TestTransport = ...;
62 //! let download_buffer: &mut [u8] = ...;
63 //! let mut fastboot = Fastboot::new();
64 //! let result = run(&mut transport, &mut fastboot_impl, &[]);
65 //! ```
66
67 #![cfg_attr(not(test), no_std)]
68 #![allow(async_fn_in_trait)]
69
70 use core::{
71 ffi::CStr,
72 fmt::{Debug, Display, Formatter, Write},
73 str::{from_utf8, Split},
74 };
75 use gbl_async::{block_on, yield_now};
76 use liberror::{Error, Result};
77 use libutils::{snprintf, FormattedBytes};
78
79 /// Local session module
80 pub mod local_session;
81
82 /// Maximum packet size that can be accepted from the host.
83 ///
84 /// The transport layer may have its own size limits that reduce the packet size further.
85 pub const MAX_COMMAND_SIZE: usize = 4096;
86 /// Maximum packet size that will be sent to the host.
87 ///
88 /// The `fastboot` host tool originally had a 64-byte packet size max, but this was increased
89 /// to 256 in 2020, so any reasonably recent host binary should be able to support 256.
90 ///
91 /// The transport layer may have its own size limits that reduce the packet size further.
92 pub const MAX_RESPONSE_SIZE: usize = 256;
93
94 /// Trait to provide the transport layer for a fastboot implementation.
95 ///
96 /// Fastboot supports these transports:
97 /// * USB
98 /// * TCP
99 /// * UDP
100 pub trait Transport {
101 /// Fetches the next fastboot packet into `out`.
102 ///
103 /// Returns the actual size of the packet on success.
104 ///
105 /// TODO(b/322540167): In the future, we may want to support using `[MaybeUninit<u8>]` as the
106 /// download buffer to avoid expensive initialization at the beginning. This would require an
107 /// interface where the implementation provides the buffer for us to copy instead of us.
receive_packet(&mut self, out: &mut [u8]) -> Result<usize>108 async fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize>;
109
110 /// Sends a fastboot packet.
111 ///
112 /// The method assumes `packet` is sent or at least copied to queue after it returns, where
113 /// the buffer can go out of scope without affecting anything.
send_packet(&mut self, packet: &[u8]) -> Result<()>114 async fn send_packet(&mut self, packet: &[u8]) -> Result<()>;
115 }
116
117 /// For now, we hardcode the expected version, until we need to distinguish between multiple
118 /// versions.
119 const TCP_HANDSHAKE_MESSAGE: &[u8] = b"FB01";
120
121 /// A trait representing a TCP stream reader/writer. Fastboot over TCP has additional handshake
122 /// process and uses a length-prefixed wire message format. It is recommended that caller
123 /// implements this trait instead of `Transport`, and uses the API `Fastboot::run_tcp_session()`
124 /// to perform fastboot over TCP. It internally handles handshake and wire message parsing.
125 pub trait TcpStream {
126 /// Reads to `out` for exactly `out.len()` number bytes from the TCP connection.
read_exact(&mut self, out: &mut [u8]) -> Result<()>127 async fn read_exact(&mut self, out: &mut [u8]) -> Result<()>;
128
129 /// Sends exactly `data.len()` number bytes from `data` to the TCP connection.
write_exact(&mut self, data: &[u8]) -> Result<()>130 async fn write_exact(&mut self, data: &[u8]) -> Result<()>;
131 }
132
133 /// Implements [Transport] on a [TcpStream].
134 pub struct TcpTransport<'a, T: TcpStream>(&'a mut T);
135
136 impl<'a, T: TcpStream> TcpTransport<'a, T> {
137 /// Creates an instance from a newly connected TcpStream and performs handshake.
new_and_handshake(tcp_stream: &'a mut T) -> Result<Self>138 pub fn new_and_handshake(tcp_stream: &'a mut T) -> Result<Self> {
139 let mut handshake = [0u8; 4];
140 block_on(tcp_stream.write_exact(TCP_HANDSHAKE_MESSAGE))?;
141 block_on(tcp_stream.read_exact(&mut handshake[..]))?;
142 match handshake == *TCP_HANDSHAKE_MESSAGE {
143 true => Ok(Self(tcp_stream)),
144 _ => Err(Error::InvalidHandshake),
145 }
146 }
147 }
148
149 impl<'a, T: TcpStream> Transport for TcpTransport<'a, T> {
receive_packet(&mut self, out: &mut [u8]) -> Result<usize>150 async fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize> {
151 let mut length_prefix = [0u8; 8];
152 self.0.read_exact(&mut length_prefix[..]).await?;
153 let packet_size: usize = u64::from_be_bytes(length_prefix).try_into()?;
154 match out.len() < packet_size {
155 true => Err(Error::InvalidInput),
156 _ => {
157 self.0.read_exact(&mut out[..packet_size]).await?;
158 Ok(packet_size)
159 }
160 }
161 }
162
send_packet(&mut self, packet: &[u8]) -> Result<()>163 async fn send_packet(&mut self, packet: &[u8]) -> Result<()> {
164 self.0.write_exact(&mut u64::try_from(packet.len())?.to_be_bytes()[..]).await?;
165 self.0.write_exact(packet).await
166 }
167 }
168
169 const COMMAND_ERROR_LENGTH: usize = MAX_RESPONSE_SIZE - 4;
170
171 /// `CommandError` is the return error type for methods in trait `FastbootImplementation` when
172 /// they fail. It will be converted into string and sent as fastboot error message "FAIL<string>".
173 ///
174 /// Any type that implements `Display` trait can be converted into it. However, because fastboot
175 /// response message is limited to `MAX_RESPONSE_SIZE`. If the final displayed string length
176 /// exceeds it, the rest of the content is ignored.
177 pub struct CommandError(FormattedBytes<[u8; COMMAND_ERROR_LENGTH]>);
178
179 impl CommandError {
180 /// Converts to string.
to_str(&self) -> &str181 pub fn to_str(&self) -> &str {
182 self.0.to_str()
183 }
184
185 /// Clones the error.
clone(&self) -> Self186 pub fn clone(&self) -> Self {
187 self.to_str().into()
188 }
189 }
190
191 impl Debug for CommandError {
fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result192 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
193 write!(f, "{}", self.to_str())
194 }
195 }
196
197 impl<T: Display> From<T> for CommandError {
from(val: T) -> Self198 fn from(val: T) -> Self {
199 let mut res = CommandError(FormattedBytes::new([0u8; COMMAND_ERROR_LENGTH]));
200 write!(res.0, "{}", val).unwrap();
201 res
202 }
203 }
204
205 /// Type alias for Result that wraps a CommandError
206 pub type CommandResult<T> = core::result::Result<T, CommandError>;
207
208 /// Fastboot reboot mode
209 #[derive(Debug, Copy, Clone, PartialEq)]
210 pub enum RebootMode {
211 /// "fastboot reboot". Normal reboot.
212 Normal,
213 /// "fastboot reboot-bootloader". Reboot to bootloader.
214 Bootloader,
215 /// "fastboot reboot-fastboot". Reboot to userspace fastboot.
216 Fastboot,
217 /// "fastboot reboot-recovery". Reboot to recovery.
218 Recovery,
219 }
220
221 /// Implementation for Fastboot command backends.
222 pub trait FastbootImplementation {
223 /// Backend for `fastboot getvar ...`
224 ///
225 /// Gets the value of a variable specified by name and configuration represented by list of
226 /// additional arguments in `args`.
227 ///
228 /// Variable `max-download-size`, `version` are reserved by the library.
229 ///
230 /// # Args
231 ///
232 /// * `var`: Name of the variable.
233 /// * `args`: Additional arguments.
234 /// * `out`: Output buffer for storing the variable value.
235 /// * `responder`: An instance of `InfoSender`.
236 ///
237 /// TODO(b/322540167): Figure out other reserved variables.
get_var( &mut self, var: &CStr, args: impl Iterator<Item = &'_ CStr> + Clone, out: &mut [u8], responder: impl InfoSender, ) -> CommandResult<usize>238 async fn get_var(
239 &mut self,
240 var: &CStr,
241 args: impl Iterator<Item = &'_ CStr> + Clone,
242 out: &mut [u8],
243 responder: impl InfoSender,
244 ) -> CommandResult<usize>;
245
246 /// A helper API for getting the value of a fastboot variable and decoding it into string.
get_var_as_str<'s>( &mut self, var: &CStr, args: impl Iterator<Item = &'_ CStr> + Clone, responder: impl InfoSender, out: &'s mut [u8], ) -> CommandResult<&'s str>247 async fn get_var_as_str<'s>(
248 &mut self,
249 var: &CStr,
250 args: impl Iterator<Item = &'_ CStr> + Clone,
251 responder: impl InfoSender,
252 out: &'s mut [u8],
253 ) -> CommandResult<&'s str> {
254 let size = self.get_var(var, args, out, responder).await?;
255 Ok(from_utf8(out.get(..size).ok_or("Invalid variable size")?)
256 .map_err(|_| "Value is not string")?)
257 }
258
259 /// Backend for `fastboot getvar all`.
260 ///
261 /// Iterates all combinations of fastboot variable, configurations and values that need to be
262 /// included in the response to `fastboot getvar all`.
263 ///
264 /// # Args
265 ///
266 /// * `responder`: An implementation VarInfoSender. Implementation should call
267 /// `VarInfoSender::send` for all combinations of Fastboot variable/argument/value that needs
268 /// to be included in the response to `fastboot getvarl all`:
269 ///
270 /// async fn get_var_all(&mut self, f: F, resp: impl VarInfoSender)
271 /// -> CommandResult<()> {
272 /// resp.send("partition-size", &["boot_a"], /* size of boot_a */).await?;
273 /// resp.send("partition-size", &["boot_b"], /* size of boot_b */).await?;
274 /// resp.send("partition-size", &["init_boot_a"], /* size of init_boot_a */).await?;
275 /// resp.send("partition-size", &["init_boot_b"], /* size of init_boot_b */).await?;
276 /// Ok(())
277 /// }
278 ///
279 /// will generates the following outputs for `fastboot getvar all`:
280 ///
281 /// ...
282 /// (bootloader) partition-size:boot_a: <size of boot_a>
283 /// (bootloader) partition-size:boot_b: <size of boot_b>
284 /// (bootloader) partition-size:init_boot_a: <size of init_boot_a>
285 /// (bootloader) partition-size:init_boot_b: <size of init_boot_b>
286 /// ...
287 ///
288 /// TODO(b/322540167): This and `get_var()` contain duplicated logic. Investigate if there can
289 /// be better solutions for doing the combination traversal.
get_var_all(&mut self, responder: impl VarInfoSender) -> CommandResult<()>290 async fn get_var_all(&mut self, responder: impl VarInfoSender) -> CommandResult<()>;
291
292 /// Backend for getting download buffer
get_download_buffer(&mut self) -> &mut [u8]293 async fn get_download_buffer(&mut self) -> &mut [u8];
294
295 /// Called when a download is completed.
download_complete( &mut self, download_size: usize, responder: impl InfoSender, ) -> CommandResult<()>296 async fn download_complete(
297 &mut self,
298 download_size: usize,
299 responder: impl InfoSender,
300 ) -> CommandResult<()>;
301
302 /// Backend for `fastboot flash ...`
303 ///
304 /// # Args
305 ///
306 /// * `part`: Name of the partition.
307 /// * `responder`: An instance of `InfoSender`.
flash(&mut self, part: &str, responder: impl InfoSender) -> CommandResult<()>308 async fn flash(&mut self, part: &str, responder: impl InfoSender) -> CommandResult<()>;
309
310 /// Backend for `fastboot erase ...`
311 ///
312 /// # Args
313 ///
314 /// * `part`: Name of the partition.
315 /// * `responder`: An instance of `InfoSender`.
erase(&mut self, part: &str, responder: impl InfoSender) -> CommandResult<()>316 async fn erase(&mut self, part: &str, responder: impl InfoSender) -> CommandResult<()>;
317
318 /// Backend for `fastboot get_staged ...`
319 ///
320 /// # Args
321 ///
322 /// * `responder`: An instance of `UploadBuilder + InfoSender` for initiating and uploading
323 /// data. For example:
324 ///
325 /// ```
326 /// async fn upload(
327 /// &mut self,
328 /// responder: impl UploadBuilder + InfoSender,
329 /// ) -> CommandResult<()> {
330 /// let data = ..;
331 /// // Sends a total of 1024 bytes data.
332 /// responder.send_info("About to upload...").await?;
333 /// let mut uploader = responder.initiate_upload(1024).await?;
334 /// // Can upload in multiple batches.
335 /// uploader.upload(&data[..512]).await?;
336 /// uploader.upload(&data[512..]).await?;
337 /// Ok(())
338 /// }
339 /// ```
340 ///
341 /// If implementation fails to upload enough, or attempts to upload more than expected data
342 /// with `Uploader::upload()`, an error will be returned.
upload(&mut self, responder: impl UploadBuilder + InfoSender) -> CommandResult<()>343 async fn upload(&mut self, responder: impl UploadBuilder + InfoSender) -> CommandResult<()>;
344
345 /// Backend for `fastboot fetch ...`
346 ///
347 /// # Args
348 ///
349 /// * `part`: The partition name.
350 /// * `offset`: The offset into the partition for upload.
351 /// * `size`: The number of bytes to upload.
352 /// * `responder`: An instance of `UploadBuilder + InfoSender` for initiating and uploading data.
fetch( &mut self, part: &str, offset: u64, size: u64, responder: impl UploadBuilder + InfoSender, ) -> CommandResult<()>353 async fn fetch(
354 &mut self,
355 part: &str,
356 offset: u64,
357 size: u64,
358 responder: impl UploadBuilder + InfoSender,
359 ) -> CommandResult<()>;
360
361 /// Backend for `fastboot reboot/reboot-bootloader/reboot-fastboot/reboot-recovery`
362 ///
363 /// # Args
364 ///
365 /// * `mode`: An `RebootMode` specifying the reboot mode.
366 /// * `responder`: An instance of `InfoSender + OkaySender`. Implementation should call
367 /// `responder.send_okay("")` right before reboot to notify the remote host that the
368 /// operation is successful.
369 ///
370 /// # Returns
371 ///
372 /// * The method is not expected to return if reboot is successful.
373 /// * Returns `Err(e)` on error.
reboot( &mut self, mode: RebootMode, responder: impl InfoSender + OkaySender, ) -> CommandError374 async fn reboot(
375 &mut self,
376 mode: RebootMode,
377 responder: impl InfoSender + OkaySender,
378 ) -> CommandError;
379
380 /// Method for handling `fastboot continue` clean up.
381 ///
382 /// `run()` and `run_tcp_session()` exit after receiving `fastboot continue.` The method is for
383 /// implementation to perform necessary clean up.
384 ///
385 /// # Args
386 ///
387 /// * `responder`: An instance of `InfoSender`.
388 async fn r#continue(&mut self, responder: impl InfoSender) -> CommandResult<()>;
389
390 /// Backend for `fastboot set_active`.
set_active(&mut self, slot: &str, responder: impl InfoSender) -> CommandResult<()>391 async fn set_active(&mut self, slot: &str, responder: impl InfoSender) -> CommandResult<()>;
392
393 /// Backend for `fastboot boot`
394 ///
395 /// # Args
396 ///
397 /// * `responder`: An instance of `InfoSender + OkaySender`. Implementation should call
398 /// `responder.send_okay("")` right before boot to notify the remote host that the
399 /// operation is successful.
400 ///
401 /// # Returns
402 ///
403 /// * The method is always return OK to let fastboot continue.
404 /// * Returns `Err(e)` on error.
boot(&mut self, responder: impl InfoSender + OkaySender) -> CommandResult<()>405 async fn boot(&mut self, responder: impl InfoSender + OkaySender) -> CommandResult<()>;
406
407 /// Backend for `fastboot oem ...`.
408 ///
409 /// # Args
410 ///
411 /// * `cmd`: The OEM command string that comes after "oem ".
412 /// * `responder`: An instance of `InfoSender`.
413 /// * `res`: The responder buffer. Upon success, implementation can use the buffer to
414 /// construct a valid UTF8 string which will be sent as "OKAY<string>"
415 ///
416 /// # Returns
417 ///
418 /// On success, returns the portion of `res` used by the construction of string message.
oem<'a>( &mut self, cmd: &str, responder: impl InfoSender, res: &'a mut [u8], ) -> CommandResult<&'a [u8]>419 async fn oem<'a>(
420 &mut self,
421 cmd: &str,
422 responder: impl InfoSender,
423 res: &'a mut [u8],
424 ) -> CommandResult<&'a [u8]>;
425
426 // TODO(b/322540167): Add methods for other commands.
427 }
428
429 /// An internal convenient macro helper for `fastboot_okay`, `fastboot_fail` and `fastboot_info`.
430 macro_rules! fastboot_msg {
431 ( $arr:expr, $msg_type:expr, $( $x:expr ),* $(,)? ) => {
432 {
433 let mut formatted_bytes = FormattedBytes::new(&mut $arr[..]);
434 write!(formatted_bytes, $msg_type).unwrap();
435 write!(formatted_bytes, $($x,)*).unwrap();
436 let size = formatted_bytes.size();
437 &mut $arr[..size]
438 }
439 };
440 }
441
442 /// An internal convenient macro that constructs a formatted fastboot OKAY message.
443 macro_rules! fastboot_okay {
444 ( $arr:expr, $( $x:expr ),* $(,)?) => { fastboot_msg!($arr, "OKAY", $($x,)*) };
445 }
446
447 /// An internal convenient macro that constructs a formatted fastboot FAIL message.
448 macro_rules! fastboot_fail {
449 ( $arr:expr, $( $x:expr ),* $(,)?) => { fastboot_msg!($arr, "FAIL", $($x,)*) };
450 }
451
452 /// `VarInfoSender` provide an interface for sending variable/args/value combination during the
453 /// processing of `fastboot getvar all`
454 pub trait VarInfoSender {
455 /// Send a combination of variable name, arguments and value.
456 ///
457 /// The method sends a fastboot message "INFO<var>:<args>:<val>" to the host.
458 ///
459 /// # Args
460 ///
461 /// * `name`: Name of the fastboot variable.
462 /// * `args`: An iterator to additional arguments.
463 /// * `val`: Value of the variable.
send_var_info( &mut self, name: &str, args: impl IntoIterator<Item = &'_ str>, val: &str, ) -> Result<()>464 async fn send_var_info(
465 &mut self,
466 name: &str,
467 args: impl IntoIterator<Item = &'_ str>,
468 val: &str,
469 ) -> Result<()>;
470 }
471
472 /// Provides an API for sending fastboot INFO messages.
473 pub trait InfoSender {
474 /// Sends formatted INFO message.
475 ///
476 /// # Args:
477 ///
478 /// * `cb`: A closure provided by the caller for constructing the formatted messagae.
send_formatted_info<F: FnOnce(&mut dyn Write)>(&mut self, cb: F) -> Result<()>479 async fn send_formatted_info<F: FnOnce(&mut dyn Write)>(&mut self, cb: F) -> Result<()>;
480
481 /// Sends a Fastboot "INFO<`msg`>" packet.
send_info(&mut self, msg: &str) -> Result<()>482 async fn send_info(&mut self, msg: &str) -> Result<()> {
483 self.send_formatted_info(|w| write!(w, "{}", msg).unwrap()).await
484 }
485 }
486
487 /// Provides an API for sending fastboot OKAY messages.
488 pub trait OkaySender {
489 /// Sends formatted Okay message.
490 ///
491 /// # Args:
492 ///
493 /// * `cb`: A closure provided by the caller for constructing the formatted messagae.
send_formatted_okay<F: FnOnce(&mut dyn Write)>(self, cb: F) -> Result<()>494 async fn send_formatted_okay<F: FnOnce(&mut dyn Write)>(self, cb: F) -> Result<()>;
495
496 /// Sends a fastboot OKAY<msg> packet. `Self` is consumed.
send_okay(self, msg: &str) -> Result<()> where Self: Sized,497 async fn send_okay(self, msg: &str) -> Result<()>
498 where
499 Self: Sized,
500 {
501 self.send_formatted_okay(|w| write!(w, "{}", msg).unwrap()).await
502 }
503 }
504
505 /// `UploadBuilder` provides API for initiating a fastboot upload.
506 pub trait UploadBuilder {
507 /// Starts the upload.
508 ///
509 /// In a real fastboot context, the method should send `DATA0xXXXXXXXX` to the remote host to
510 /// start the download. An `Uploader` implementation should be returned for uploading payload.
initiate_upload(self, data_size: u64) -> Result<impl Uploader>511 async fn initiate_upload(self, data_size: u64) -> Result<impl Uploader>;
512 }
513
514 /// `UploadBuilder` provides API for uploading payload.
515 pub trait Uploader {
516 /// Uploads data to the Fastboot host.
upload(&mut self, data: &[u8]) -> Result<()>517 async fn upload(&mut self, data: &[u8]) -> Result<()>;
518 }
519
520 /// `Responder` implements APIs for fastboot backend to send fastboot messages and uploading data.
521 struct Responder<'a, T: Transport> {
522 buffer: [u8; MAX_RESPONSE_SIZE],
523 transport: &'a mut T,
524 transport_error: Result<()>,
525 remaining_upload: u64,
526 }
527
528 impl<'a, T: Transport> Responder<'a, T> {
new(transport: &'a mut T) -> Self529 fn new(transport: &'a mut T) -> Self {
530 Self {
531 buffer: [0u8; MAX_RESPONSE_SIZE],
532 transport,
533 transport_error: Ok(()),
534 remaining_upload: 0,
535 }
536 }
537
538 /// A helper for sending a fastboot message in the buffer.
send_buffer(&mut self, size: usize) -> Result<()>539 async fn send_buffer(&mut self, size: usize) -> Result<()> {
540 self.transport_error?;
541 assert!(size < self.buffer.len());
542 self.transport_error = self.transport.send_packet(&self.buffer[..size]).await;
543 Ok(self.transport_error?)
544 }
545
546 /// Helper for sending a formatted fastboot message.
547 ///
548 /// # Args:
549 ///
550 /// * `cb`: A closure provided by the caller for constructing the formatted messagae.
send_formatted_msg<F: FnOnce(&mut dyn Write)>( &mut self, msg_type: &str, cb: F, ) -> Result<()>551 async fn send_formatted_msg<F: FnOnce(&mut dyn Write)>(
552 &mut self,
553 msg_type: &str,
554 cb: F,
555 ) -> Result<()> {
556 let mut formatted_bytes = FormattedBytes::new(&mut self.buffer);
557 write!(formatted_bytes, "{}", msg_type).unwrap();
558 cb(&mut formatted_bytes);
559 let size = formatted_bytes.size();
560 self.send_buffer(size).await
561 }
562
563 /// Sends a fastboot DATA message.
send_data_message(&mut self, data_size: u64) -> Result<()>564 async fn send_data_message(&mut self, data_size: u64) -> Result<()> {
565 self.send_formatted_msg("DATA", |v| write!(v, "{:08x}", data_size).unwrap()).await
566 }
567 }
568
569 impl<'a, T: Transport> VarInfoSender for &mut Responder<'a, T> {
send_var_info( &mut self, name: &str, args: impl IntoIterator<Item = &'_ str>, val: &str, ) -> Result<()>570 async fn send_var_info(
571 &mut self,
572 name: &str,
573 args: impl IntoIterator<Item = &'_ str>,
574 val: &str,
575 ) -> Result<()> {
576 // Sends a "INFO<var>:<':'-separated args>:<val>" packet to the host.
577 Ok(self
578 .send_formatted_msg("INFO", |v| {
579 write!(v, "{}", name).unwrap();
580 args.into_iter().for_each(|arg| write!(v, ":{}", arg).unwrap());
581 write!(v, ": {}", val).unwrap();
582 })
583 .await?)
584 }
585 }
586
587 /// An internal convenient macro that sends a formatted fastboot OKAY message via a `Responder`
588 macro_rules! reply_okay {
589 ( $resp:expr, $( $x:expr ),* $(,)?) => {
590 {
591 let len = fastboot_okay!($resp.buffer, $($x,)*).len();
592 $resp.send_buffer(len).await
593 }
594 };
595 }
596
597 /// An internal convenient macro that sends a formatted fastboot FAIL message via a `Responder`
598 macro_rules! reply_fail {
599 ( $resp:expr, $( $x:expr ),* $(,)?) => {
600 {
601 let len = fastboot_fail!($resp.buffer, $($x,)*).len();
602 $resp.send_buffer(len).await
603 }
604 };
605 }
606
607 impl<T: Transport> InfoSender for &mut Responder<'_, T> {
send_formatted_info<F: FnOnce(&mut dyn Write)>(&mut self, cb: F) -> Result<()>608 async fn send_formatted_info<F: FnOnce(&mut dyn Write)>(&mut self, cb: F) -> Result<()> {
609 Ok(self.send_formatted_msg("INFO", cb).await?)
610 }
611 }
612
613 impl<T: Transport> OkaySender for &mut Responder<'_, T> {
send_formatted_okay<F: FnOnce(&mut dyn Write)>(self, cb: F) -> Result<()>614 async fn send_formatted_okay<F: FnOnce(&mut dyn Write)>(self, cb: F) -> Result<()> {
615 Ok(self.send_formatted_msg("OKAY", cb).await?)
616 }
617 }
618
619 impl<'a, T: Transport> UploadBuilder for &mut Responder<'a, T> {
initiate_upload(self, data_size: u64) -> Result<impl Uploader>620 async fn initiate_upload(self, data_size: u64) -> Result<impl Uploader> {
621 self.send_data_message(data_size).await?;
622 self.remaining_upload = data_size;
623 Ok(self)
624 }
625 }
626
627 impl<'a, T: Transport> Uploader for &mut Responder<'a, T> {
628 /// Uploads data. Returns error if accumulative amount exceeds `data_size` passed to
629 /// `UploadBuilder::start()`.
upload(&mut self, data: &[u8]) -> Result<()>630 async fn upload(&mut self, data: &[u8]) -> Result<()> {
631 self.transport_error?;
632 self.remaining_upload = self
633 .remaining_upload
634 .checked_sub(data.len().try_into().map_err(|_| "")?)
635 .ok_or(Error::Other(Some("Invalid size of upload data")))?;
636 self.transport_error = self.transport.send_packet(data).await;
637 Ok(())
638 }
639 }
640
641 pub mod test_utils {
642 //! Test utilities to help users of this library write unit tests.
643
644 use crate::{InfoSender, UploadBuilder, Uploader};
645 use core::fmt::Write;
646 use liberror::Error;
647
648 /// A test implementation of `UploadBuilder` for unittesting
649 /// `FastbootImplementation::upload()`.
650 ///
651 /// The test uploader simply uploads to a user provided buffer.
652 pub struct TestUploadBuilder<'a>(pub &'a mut [u8]);
653
654 impl<'a> UploadBuilder for TestUploadBuilder<'a> {
initiate_upload(self, _: u64) -> Result<impl Uploader, Error>655 async fn initiate_upload(self, _: u64) -> Result<impl Uploader, Error> {
656 Ok(TestUploader(0, self.0))
657 }
658 }
659
660 impl<'a> InfoSender for TestUploadBuilder<'a> {
send_formatted_info<F: FnOnce(&mut dyn Write)>( &mut self, _: F, ) -> Result<(), Error>661 async fn send_formatted_info<F: FnOnce(&mut dyn Write)>(
662 &mut self,
663 _: F,
664 ) -> Result<(), Error> {
665 // Not needed currently.
666 Ok(())
667 }
668 }
669
670 // (Bytes sent, upload buffer)
671 struct TestUploader<'a>(usize, &'a mut [u8]);
672
673 impl Uploader for TestUploader<'_> {
upload(&mut self, data: &[u8]) -> Result<(), Error>674 async fn upload(&mut self, data: &[u8]) -> Result<(), Error> {
675 self.1[self.0..][..data.len()].clone_from_slice(data);
676 self.0 = self.0.checked_add(data.len()).unwrap();
677 Ok(())
678 }
679 }
680 }
681
682 const MAX_DOWNLOAD_SIZE_NAME: &'static str = "max-download-size";
683
684 /// Converts a null-terminated command line string where arguments are separated by ':' into an
685 /// iterator of individual argument as CStr.
cmd_to_c_string_args(cmd: &mut [u8]) -> impl Iterator<Item = &CStr> + Clone686 fn cmd_to_c_string_args(cmd: &mut [u8]) -> impl Iterator<Item = &CStr> + Clone {
687 let end = cmd.iter().position(|v| *v == 0).unwrap();
688 // Replace ':' with NULL.
689 cmd.iter_mut().filter(|v| **v == b':').for_each(|v| *v = 0);
690 cmd[..end + 1].split_inclusive(|v| *v == 0).map(|v| CStr::from_bytes_until_nul(v).unwrap())
691 }
692
693 /// Helper for handling "fastboot getvar ..."
get_var( cmd: &mut [u8], transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>694 async fn get_var(
695 cmd: &mut [u8],
696 transport: &mut impl Transport,
697 fb_impl: &mut impl FastbootImplementation,
698 ) -> Result<()> {
699 let mut resp = Responder::new(transport);
700 let mut args = cmd_to_c_string_args(cmd).skip(1);
701 let Some(var) = args.next() else {
702 return reply_fail!(resp, "Missing variable");
703 };
704
705 match var.to_str()? {
706 "all" => return get_var_all(transport, fb_impl).await,
707 MAX_DOWNLOAD_SIZE_NAME => {
708 return reply_okay!(resp, "{:#x}", fb_impl.get_download_buffer().await.len());
709 }
710 _ => {
711 let mut val = [0u8; MAX_RESPONSE_SIZE];
712 match fb_impl.get_var_as_str(var, args, &mut resp, &mut val[..]).await {
713 Ok(s) => reply_okay!(resp, "{}", s),
714 Err(e) => reply_fail!(resp, "{}", e.to_str()),
715 }
716 }
717 }
718 }
719
720 /// A wrapper of `get_var_all()` that first iterates reserved variables.
get_var_all_with_native( fb_impl: &mut impl FastbootImplementation, mut sender: impl VarInfoSender, ) -> CommandResult<()>721 async fn get_var_all_with_native(
722 fb_impl: &mut impl FastbootImplementation,
723 mut sender: impl VarInfoSender,
724 ) -> CommandResult<()> {
725 // Process the built-in MAX_DOWNLOAD_SIZE_NAME variable.
726 let mut size_str = [0u8; 32];
727 let size_str = snprintf!(size_str, "{:#x}", fb_impl.get_download_buffer().await.len());
728 sender.send_var_info(MAX_DOWNLOAD_SIZE_NAME, [], size_str).await?;
729 fb_impl.get_var_all(sender).await
730 }
731
732 /// Method for handling "fastboot getvar all"
get_var_all( transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>733 async fn get_var_all(
734 transport: &mut impl Transport,
735 fb_impl: &mut impl FastbootImplementation,
736 ) -> Result<()> {
737 let mut resp = Responder::new(transport);
738 // Don't allow custom INFO messages because variable values are sent as INFO messages.
739 let get_res = get_var_all_with_native(fb_impl, &mut resp).await;
740 match get_res {
741 Ok(()) => reply_okay!(resp, ""),
742 Err(e) => reply_fail!(resp, "{}", e.to_str()),
743 }
744 }
745
746 /// Helper for handling "fastboot download:...".
download( mut args: Split<'_, char>, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>747 async fn download(
748 mut args: Split<'_, char>,
749 transport: &mut impl Transport,
750 fb_impl: &mut impl FastbootImplementation,
751 ) -> Result<()> {
752 let mut resp = Responder::new(transport);
753 let total_download_size = match (|| -> CommandResult<usize> {
754 usize::try_from(next_arg_u64(&mut args)?.ok_or("Not enough argument")?)
755 .map_err(|_| "Download size overflow".into())
756 })() {
757 Err(e) => return reply_fail!(resp, "{}", e.to_str()),
758 Ok(v) => v,
759 };
760 let download_buffer = &mut fb_impl.get_download_buffer().await;
761 if total_download_size > download_buffer.len() {
762 return reply_fail!(resp, "Download size is too big");
763 } else if total_download_size == 0 {
764 return reply_fail!(resp, "Zero download size");
765 }
766
767 // Starts the download
768 let download_buffer = &mut download_buffer[..total_download_size];
769 // `total_download_size` is parsed from `next_arg_u64` and thus should fit into u64.
770 resp.send_data_message(u64::try_from(total_download_size).unwrap()).await?;
771 let mut downloaded = 0;
772 while downloaded < total_download_size {
773 let (_, remains) = &mut download_buffer.split_at_mut(downloaded);
774 match resp.transport.receive_packet(remains).await? {
775 0 => yield_now().await,
776 v => match downloaded.checked_add(v) {
777 Some(v) if v > total_download_size => {
778 return reply_fail!(resp, "More data received then expected");
779 }
780 Some(v) => downloaded = v,
781 _ => return Err(Error::Other(Some("Invalid read size from transport"))),
782 },
783 };
784 }
785 match fb_impl.download_complete(downloaded, &mut resp).await {
786 Ok(()) => reply_okay!(resp, ""),
787 Err(e) => reply_fail!(resp, "{}", e.to_str()),
788 }
789 }
790
791 /// Helper for handling "fastboot flash ...".
flash( cmd: &str, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>792 async fn flash(
793 cmd: &str,
794 transport: &mut impl Transport,
795 fb_impl: &mut impl FastbootImplementation,
796 ) -> Result<()> {
797 let mut resp = Responder::new(transport);
798 let flash_res =
799 match cmd.strip_prefix("flash:").ok_or::<CommandError>("Missing partition".into()) {
800 Ok(part) => fb_impl.flash(part, &mut resp).await,
801 Err(e) => Err(e),
802 };
803 match flash_res {
804 Err(e) => reply_fail!(resp, "{}", e.to_str()),
805 _ => reply_okay!(resp, ""),
806 }
807 }
808
809 /// Helper for handling "fastboot erase ...".
erase( cmd: &str, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>810 async fn erase(
811 cmd: &str,
812 transport: &mut impl Transport,
813 fb_impl: &mut impl FastbootImplementation,
814 ) -> Result<()> {
815 let mut resp = Responder::new(transport);
816 let flash_res =
817 match cmd.strip_prefix("erase:").ok_or::<CommandError>("Missing partition".into()) {
818 Ok(part) => fb_impl.erase(part, &mut resp).await,
819 Err(e) => Err(e),
820 };
821 match flash_res {
822 Err(e) => reply_fail!(resp, "{}", e.to_str()),
823 _ => reply_okay!(resp, ""),
824 }
825 }
826
827 /// Helper for handling "fastboot get_staged ...".
upload( transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>828 async fn upload(
829 transport: &mut impl Transport,
830 fb_impl: &mut impl FastbootImplementation,
831 ) -> Result<()> {
832 let mut resp = Responder::new(transport);
833 let upload_res = fb_impl.upload(&mut resp).await;
834 match resp.remaining_upload > 0 {
835 true => return Err(Error::InvalidInput),
836 _ => match upload_res {
837 Err(e) => reply_fail!(resp, "{}", e.to_str()),
838 _ => reply_okay!(resp, ""),
839 },
840 }
841 }
842
843 /// Helper for handling "fastboot fetch ...".
fetch( cmd: &str, args: Split<'_, char>, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>844 async fn fetch(
845 cmd: &str,
846 args: Split<'_, char>,
847 transport: &mut impl Transport,
848 fb_impl: &mut impl FastbootImplementation,
849 ) -> Result<()> {
850 let mut resp = Responder::new(transport);
851 let fetch_res = async {
852 let cmd = cmd.strip_prefix("fetch:").ok_or::<CommandError>("Missing arguments".into())?;
853 if args.clone().count() < 3 {
854 return Err("Not enough argments".into());
855 }
856 // Parses backward. Parses size, offset first and treats the remaining string as
857 // partition name. This allows ":" in partition name.
858 let mut rev = args.clone().rev();
859 let sz = next_arg(&mut rev).ok_or("Missing size")?;
860 let off = next_arg(&mut rev).ok_or("Invalid offset")?;
861 let part = &cmd[..cmd.len() - (off.len() + sz.len() + 2)];
862 fb_impl.fetch(part, hex_to_u64(off)?, hex_to_u64(sz)?, &mut resp).await
863 }
864 .await;
865 match resp.remaining_upload > 0 {
866 true => return Err(Error::InvalidInput),
867 _ => match fetch_res {
868 Err(e) => reply_fail!(resp, "{}", e.to_str()),
869 _ => reply_okay!(resp, ""),
870 },
871 }
872 }
873
874 // Handles `fastboot reboot*`
reboot( mode: RebootMode, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>875 async fn reboot(
876 mode: RebootMode,
877 transport: &mut impl Transport,
878 fb_impl: &mut impl FastbootImplementation,
879 ) -> Result<()> {
880 let mut resp = Responder::new(transport);
881 let e = fb_impl.reboot(mode, &mut resp).await;
882 reply_fail!(resp, "{}", e.to_str())
883 }
884
885 // Handles `fastboot boot`
boot( transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>886 async fn boot(
887 transport: &mut impl Transport,
888 fb_impl: &mut impl FastbootImplementation,
889 ) -> Result<()> {
890 let mut resp = Responder::new(transport);
891 let boot_res = async { fb_impl.boot(&mut resp).await }.await;
892 match boot_res {
893 Err(e) => reply_fail!(resp, "{}", e.to_str()),
894 _ => reply_okay!(resp, "boot_command"),
895 }
896 }
897
898 // Handles `fastboot continue`
899 async fn r#continue(
900 transport: &mut impl Transport,
901 fb_impl: &mut impl FastbootImplementation,
902 ) -> Result<()> {
903 let mut resp = Responder::new(transport);
904 match fb_impl.r#continue(&mut resp).await {
905 Ok(_) => reply_okay!(resp, ""),
906 Err(e) => reply_fail!(resp, "{}", e.to_str()),
907 }
908 }
909
910 // Handles `fastboot set_active`
set_active( mut args: Split<'_, char>, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>911 async fn set_active(
912 mut args: Split<'_, char>,
913 transport: &mut impl Transport,
914 fb_impl: &mut impl FastbootImplementation,
915 ) -> Result<()> {
916 let mut resp = Responder::new(transport);
917 let res = async {
918 let slot = next_arg(&mut args).ok_or("Missing slot")?;
919 fb_impl.set_active(slot, &mut resp).await
920 };
921 match res.await {
922 Ok(_) => reply_okay!(resp, ""),
923 Err(e) => reply_fail!(resp, "{}", e.to_str()),
924 }
925 }
926
927 /// Helper for handling "fastboot oem ...".
oem( cmd: &str, transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>928 async fn oem(
929 cmd: &str,
930 transport: &mut impl Transport,
931 fb_impl: &mut impl FastbootImplementation,
932 ) -> Result<()> {
933 let mut resp = Responder::new(transport);
934 let mut oem_out = [0u8; MAX_RESPONSE_SIZE - 4];
935 let oem_res = fb_impl.oem(cmd, &mut resp, &mut oem_out[..]).await;
936 match oem_res {
937 Ok(msg) => match from_utf8(msg) {
938 Ok(s) => reply_okay!(resp, "{}", s),
939 Err(e) => reply_fail!(resp, "Invalid return string {}", e),
940 },
941 Err(e) => reply_fail!(resp, "{}", e.to_str()),
942 }
943 }
944
945 /// Process the next Fastboot command from the transport.
946 ///
947 /// # Returns
948 ///
949 /// * Returns Ok(is_continue) on success where `is_continue` is true if command is
950 /// `fastboot continue`.
951 /// * Returns Err() on errors.
process_next_command( transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<bool>952 pub async fn process_next_command(
953 transport: &mut impl Transport,
954 fb_impl: &mut impl FastbootImplementation,
955 ) -> Result<bool> {
956 let mut packet = [0u8; MAX_COMMAND_SIZE + 1];
957 let cmd_size = match transport.receive_packet(&mut packet[..MAX_COMMAND_SIZE]).await? {
958 0 => return Ok(false),
959 v => v,
960 };
961 let Ok(cmd_str) = from_utf8(&packet[..cmd_size]) else {
962 transport.send_packet(fastboot_fail!(packet, "Invalid Command")).await?;
963 return Ok(false);
964 };
965 let mut args = cmd_str.split(':');
966 let Some(cmd) = args.next() else {
967 return transport.send_packet(fastboot_fail!(packet, "No command")).await.map(|_| false);
968 };
969 match cmd {
970 "boot" => {
971 boot(transport, fb_impl).await?;
972 return Ok(true);
973 }
974 "continue" => {
975 r#continue(transport, fb_impl).await?;
976 return Ok(true);
977 }
978 "download" => download(args, transport, fb_impl).await,
979 "erase" => erase(cmd_str, transport, fb_impl).await,
980 "fetch" => fetch(cmd_str, args, transport, fb_impl).await,
981 "flash" => flash(cmd_str, transport, fb_impl).await,
982 "getvar" => get_var(&mut packet[..], transport, fb_impl).await,
983 "reboot" => reboot(RebootMode::Normal, transport, fb_impl).await,
984 "reboot-bootloader" => reboot(RebootMode::Bootloader, transport, fb_impl).await,
985 "reboot-fastboot" => reboot(RebootMode::Fastboot, transport, fb_impl).await,
986 "reboot-recovery" => reboot(RebootMode::Recovery, transport, fb_impl).await,
987 "set_active" => set_active(args, transport, fb_impl).await,
988 "upload" => upload(transport, fb_impl).await,
989 _ if cmd_str.starts_with("oem ") => oem(&cmd_str[4..], transport, fb_impl).await,
990 _ => transport.send_packet(fastboot_fail!(packet, "Command not found")).await,
991 }?;
992 Ok(false)
993 }
994
995 /// Keeps polling and processing fastboot commands from the transport.
996 ///
997 /// # Returns
998 ///
999 /// * Returns Ok(()) if "fastboot continue" is received.
1000 /// * Returns Err() on errors.
run( transport: &mut impl Transport, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>1001 pub async fn run(
1002 transport: &mut impl Transport,
1003 fb_impl: &mut impl FastbootImplementation,
1004 ) -> Result<()> {
1005 while !process_next_command(transport, fb_impl).await? {}
1006 Ok(())
1007 }
1008
1009 /// Runs a fastboot over TCP session.
1010 ///
1011 /// The method performs fastboot over TCP handshake and then call `run(...)`.
1012 ///
1013 /// Returns Ok(()) if "fastboot continue" is received.
run_tcp_session( tcp_stream: &mut impl TcpStream, fb_impl: &mut impl FastbootImplementation, ) -> Result<()>1014 pub async fn run_tcp_session(
1015 tcp_stream: &mut impl TcpStream,
1016 fb_impl: &mut impl FastbootImplementation,
1017 ) -> Result<()> {
1018 run(&mut TcpTransport::new_and_handshake(tcp_stream)?, fb_impl).await
1019 }
1020
1021 /// A helper to convert a hex string into u64.
hex_to_u64(s: &str) -> CommandResult<u64>1022 pub(crate) fn hex_to_u64(s: &str) -> CommandResult<u64> {
1023 Ok(u64::from_str_radix(s.strip_prefix("0x").unwrap_or(s), 16)?)
1024 }
1025
1026 /// A helper to check and fetch the next non-empty argument.
1027 ///
1028 /// # Args
1029 ///
1030 /// args: A string iterator.
next_arg<'a, T: Iterator<Item = &'a str>>(args: &mut T) -> Option<&'a str>1031 pub fn next_arg<'a, T: Iterator<Item = &'a str>>(args: &mut T) -> Option<&'a str> {
1032 args.next().filter(|v| *v != "")
1033 }
1034
1035 /// A helper to check and fetch the next argument as a u64 hex string.
1036 ///
1037 /// # Args
1038 ///
1039 /// args: A string iterator.
1040 ///
1041 ///
1042 /// # Returns
1043 ///
1044 /// * Returns Ok(Some(v)) is next argument is available and a valid u64 hex.
1045 /// * Returns Ok(None) is next argument is not available
1046 /// * Returns Err() if next argument is present but not a valid u64 hex.
next_arg_u64<'a, T: Iterator<Item = &'a str>>(args: &mut T) -> CommandResult<Option<u64>>1047 pub fn next_arg_u64<'a, T: Iterator<Item = &'a str>>(args: &mut T) -> CommandResult<Option<u64>> {
1048 match next_arg(args) {
1049 Some(v) => Ok(Some(hex_to_u64(v)?)),
1050 _ => Ok(None),
1051 }
1052 }
1053
1054 #[cfg(test)]
1055 mod test {
1056 use super::*;
1057 use core::cmp::min;
1058 use std::collections::{BTreeMap, VecDeque};
1059
1060 #[derive(Default)]
1061 struct FastbootTest {
1062 // A mapping from (variable name, argument) to variable value.
1063 vars: BTreeMap<(&'static str, &'static [&'static str]), &'static str>,
1064 // The partition arg from Fastboot flash command
1065 flash_partition: String,
1066 // The partition arg from Fastboot erase command
1067 erase_partition: String,
1068 // Upload size, batches of data to upload,
1069 upload_config: (u64, Vec<Vec<u8>>),
1070 // A map from partition name to (upload size override, partition data)
1071 fetch_data: BTreeMap<&'static str, (u64, Vec<u8>)>,
1072 // result string, INFO strings.
1073 oem_output: (String, Vec<String>),
1074 oem_command: String,
1075 download_buffer: Vec<u8>,
1076 downloaded_size: usize,
1077 reboot_mode: Option<RebootMode>,
1078 active_slot: Option<String>,
1079 }
1080
1081 impl FastbootImplementation for FastbootTest {
get_var( &mut self, var: &CStr, args: impl Iterator<Item = &'_ CStr> + Clone, out: &mut [u8], _: impl InfoSender, ) -> CommandResult<usize>1082 async fn get_var(
1083 &mut self,
1084 var: &CStr,
1085 args: impl Iterator<Item = &'_ CStr> + Clone,
1086 out: &mut [u8],
1087 _: impl InfoSender,
1088 ) -> CommandResult<usize> {
1089 let args = args.map(|v| v.to_str().unwrap()).collect::<Vec<_>>();
1090 match self.vars.get(&(var.to_str()?, &args[..])) {
1091 Some(v) => {
1092 out[..v.len()].clone_from_slice(v.as_bytes());
1093 Ok(v.len())
1094 }
1095 _ => Err("Not Found".into()),
1096 }
1097 }
1098
get_var_all(&mut self, mut responder: impl VarInfoSender) -> CommandResult<()>1099 async fn get_var_all(&mut self, mut responder: impl VarInfoSender) -> CommandResult<()> {
1100 for ((var, config), value) in &self.vars {
1101 responder.send_var_info(var, config.iter().copied(), value).await?;
1102 }
1103 Ok(())
1104 }
1105
get_download_buffer(&mut self) -> &mut [u8]1106 async fn get_download_buffer(&mut self) -> &mut [u8] {
1107 self.download_buffer.as_mut_slice()
1108 }
1109
download_complete( &mut self, download_size: usize, _: impl InfoSender, ) -> CommandResult<()>1110 async fn download_complete(
1111 &mut self,
1112 download_size: usize,
1113 _: impl InfoSender,
1114 ) -> CommandResult<()> {
1115 self.downloaded_size = download_size;
1116 Ok(())
1117 }
1118
flash(&mut self, part: &str, _: impl InfoSender) -> CommandResult<()>1119 async fn flash(&mut self, part: &str, _: impl InfoSender) -> CommandResult<()> {
1120 self.flash_partition = part.into();
1121 Ok(())
1122 }
1123
erase(&mut self, part: &str, _: impl InfoSender) -> CommandResult<()>1124 async fn erase(&mut self, part: &str, _: impl InfoSender) -> CommandResult<()> {
1125 self.erase_partition = part.into();
1126 Ok(())
1127 }
1128
upload(&mut self, responder: impl UploadBuilder) -> CommandResult<()>1129 async fn upload(&mut self, responder: impl UploadBuilder) -> CommandResult<()> {
1130 let (size, batches) = &self.upload_config;
1131 let mut uploader = responder.initiate_upload(*size).await?;
1132 for ele in batches {
1133 uploader.upload(&ele[..]).await?;
1134 }
1135 Ok(())
1136 }
1137
fetch( &mut self, part: &str, offset: u64, size: u64, responder: impl UploadBuilder + InfoSender, ) -> CommandResult<()>1138 async fn fetch(
1139 &mut self,
1140 part: &str,
1141 offset: u64,
1142 size: u64,
1143 responder: impl UploadBuilder + InfoSender,
1144 ) -> CommandResult<()> {
1145 let (size_override, data) = self.fetch_data.get(part).ok_or("Not Found")?;
1146 let mut uploader = responder.initiate_upload(*size_override).await?;
1147 Ok(uploader
1148 .upload(&data[offset.try_into().unwrap()..][..size.try_into().unwrap()])
1149 .await?)
1150 }
1151
boot(&mut self, mut responder: impl InfoSender + OkaySender) -> CommandResult<()>1152 async fn boot(&mut self, mut responder: impl InfoSender + OkaySender) -> CommandResult<()> {
1153 Ok(responder.send_info("Boot to boot.img...").await?)
1154 }
1155
reboot( &mut self, mode: RebootMode, responder: impl InfoSender + OkaySender, ) -> CommandError1156 async fn reboot(
1157 &mut self,
1158 mode: RebootMode,
1159 responder: impl InfoSender + OkaySender,
1160 ) -> CommandError {
1161 responder.send_okay("").await.unwrap();
1162 self.reboot_mode = Some(mode);
1163 "reboot-return".into()
1164 }
1165
1166 async fn r#continue(&mut self, mut responder: impl InfoSender) -> CommandResult<()> {
1167 Ok(responder.send_info("Continuing to boot...").await?)
1168 }
1169
set_active(&mut self, slot: &str, _: impl InfoSender) -> CommandResult<()>1170 async fn set_active(&mut self, slot: &str, _: impl InfoSender) -> CommandResult<()> {
1171 self.active_slot = Some(slot.into());
1172 Ok(())
1173 }
1174
oem<'b>( &mut self, cmd: &str, mut responder: impl InfoSender, res: &'b mut [u8], ) -> CommandResult<&'b [u8]>1175 async fn oem<'b>(
1176 &mut self,
1177 cmd: &str,
1178 mut responder: impl InfoSender,
1179 res: &'b mut [u8],
1180 ) -> CommandResult<&'b [u8]> {
1181 let (res_str, infos) = &mut self.oem_output;
1182 self.oem_command = cmd.into();
1183 for ele in infos {
1184 responder.send_info(ele.as_str()).await?;
1185 }
1186 Ok(snprintf!(res, "{}", *res_str).as_bytes())
1187 }
1188 }
1189
1190 struct TestTransport {
1191 in_queue: VecDeque<Vec<u8>>,
1192 out_queue: VecDeque<Vec<u8>>,
1193 }
1194
1195 impl TestTransport {
new() -> Self1196 fn new() -> Self {
1197 Self { in_queue: VecDeque::new(), out_queue: VecDeque::new() }
1198 }
1199
add_input(&mut self, packet: &[u8])1200 fn add_input(&mut self, packet: &[u8]) {
1201 self.in_queue.push_back(packet.into());
1202 }
1203 }
1204
1205 impl Transport for TestTransport {
receive_packet(&mut self, out: &mut [u8]) -> Result<usize>1206 async fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize> {
1207 match self.in_queue.pop_front() {
1208 Some(v) => {
1209 let size = min(out.len(), v.len());
1210 out[..size].clone_from_slice(&v[..size]);
1211 // Returns the input length so that we can test bogus download size.
1212 Ok(v.len())
1213 }
1214 _ => Err(Error::Other(Some("No more data"))),
1215 }
1216 }
1217
send_packet(&mut self, packet: &[u8]) -> Result<()>1218 async fn send_packet(&mut self, packet: &[u8]) -> Result<()> {
1219 self.out_queue.push_back(packet.into());
1220 Ok(())
1221 }
1222 }
1223
1224 #[derive(Default)]
1225 struct TestTcpStream {
1226 in_queue: VecDeque<u8>,
1227 out_queue: VecDeque<u8>,
1228 }
1229
1230 impl TestTcpStream {
1231 /// Adds bytes to input stream.
add_input(&mut self, data: &[u8])1232 fn add_input(&mut self, data: &[u8]) {
1233 data.iter().for_each(|v| self.in_queue.push_back(*v));
1234 }
1235
1236 /// Adds a length pre-fixed bytes stream.
add_length_prefixed_input(&mut self, data: &[u8])1237 fn add_length_prefixed_input(&mut self, data: &[u8]) {
1238 self.add_input(&(data.len() as u64).to_be_bytes());
1239 self.add_input(data);
1240 }
1241 }
1242
1243 impl TcpStream for TestTcpStream {
read_exact(&mut self, out: &mut [u8]) -> Result<()>1244 async fn read_exact(&mut self, out: &mut [u8]) -> Result<()> {
1245 for ele in out {
1246 *ele = self.in_queue.pop_front().ok_or(Error::OperationProhibited)?;
1247 }
1248 Ok(())
1249 }
1250
write_exact(&mut self, data: &[u8]) -> Result<()>1251 async fn write_exact(&mut self, data: &[u8]) -> Result<()> {
1252 data.iter().for_each(|v| self.out_queue.push_back(*v));
1253 Ok(())
1254 }
1255 }
1256
1257 #[test]
test_boot()1258 fn test_boot() {
1259 let mut fastboot_impl: FastbootTest = Default::default();
1260 fastboot_impl.download_buffer = vec![0u8; 1024];
1261 let mut transport = TestTransport::new();
1262 transport.add_input(b"boot");
1263 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1264 assert_eq!(
1265 transport.out_queue,
1266 VecDeque::<Vec<u8>>::from([
1267 b"INFOBoot to boot.img...".into(),
1268 b"OKAYboot_command".into()
1269 ])
1270 );
1271 }
1272
1273 #[test]
test_non_exist_command()1274 fn test_non_exist_command() {
1275 let mut fastboot_impl: FastbootTest = Default::default();
1276 fastboot_impl.download_buffer = vec![0u8; 1024];
1277 let mut transport = TestTransport::new();
1278 transport.add_input(b"non_exist");
1279 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1280 assert_eq!(transport.out_queue, [b"FAILCommand not found"]);
1281 }
1282
1283 #[test]
test_non_ascii_command_string()1284 fn test_non_ascii_command_string() {
1285 let mut fastboot_impl: FastbootTest = Default::default();
1286 fastboot_impl.download_buffer = vec![0u8; 1024];
1287 let mut transport = TestTransport::new();
1288 transport.add_input(b"\xff\xff\xff");
1289 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1290 assert_eq!(transport.out_queue, [b"FAILInvalid Command"]);
1291 }
1292
1293 #[test]
test_get_var_max_download_size()1294 fn test_get_var_max_download_size() {
1295 let mut fastboot_impl: FastbootTest = Default::default();
1296 fastboot_impl.download_buffer = vec![0u8; 1024];
1297 let mut transport = TestTransport::new();
1298 transport.add_input(b"getvar:max-download-size");
1299 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1300 assert_eq!(transport.out_queue, [b"OKAY0x400"]);
1301 }
1302
1303 #[test]
test_get_var()1304 fn test_get_var() {
1305 let mut fastboot_impl: FastbootTest = Default::default();
1306 let vars: [((&str, &[&str]), &str); 4] = [
1307 (("var_0", &[]), "val_0"),
1308 (("var_1", &["a", "b"]), "val_1_a_b"),
1309 (("var_1", &["c", "d"]), "val_1_c_d"),
1310 (("var_2", &["e", "f"]), "val_2_e_f"),
1311 ];
1312 fastboot_impl.vars = BTreeMap::from(vars);
1313
1314 fastboot_impl.download_buffer = vec![0u8; 1024];
1315 let mut transport = TestTransport::new();
1316 transport.add_input(b"getvar:var_0");
1317 transport.add_input(b"getvar:var_1:a:b");
1318 transport.add_input(b"getvar:var_1:c:d");
1319 transport.add_input(b"getvar:var_1"); // Not Found
1320 transport.add_input(b"getvar:var_2:e:f");
1321 transport.add_input(b"getvar:var_3"); // Not Found
1322 transport.add_input(b"getvar"); // Not Found
1323
1324 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1325 assert_eq!(
1326 transport.out_queue,
1327 VecDeque::<Vec<u8>>::from([
1328 b"OKAYval_0".into(),
1329 b"OKAYval_1_a_b".into(),
1330 b"OKAYval_1_c_d".into(),
1331 b"FAILNot Found".into(),
1332 b"OKAYval_2_e_f".into(),
1333 b"FAILNot Found".into(),
1334 b"FAILMissing variable".into(),
1335 ])
1336 );
1337 }
1338
1339 #[test]
test_get_var_all()1340 fn test_get_var_all() {
1341 let mut fastboot_impl: FastbootTest = Default::default();
1342 let vars: [((&str, &[&str]), &str); 4] = [
1343 (("var_0", &[]), "val_0"),
1344 (("var_1", &["a", "b"]), "val_1_a_b"),
1345 (("var_1", &["c", "d"]), "val_1_c_d"),
1346 (("var_2", &["e", "f"]), "val_2_e_f"),
1347 ];
1348 fastboot_impl.vars = BTreeMap::from(vars);
1349
1350 fastboot_impl.download_buffer = vec![0u8; 1024];
1351 let mut transport = TestTransport::new();
1352 transport.add_input(b"getvar:all");
1353 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1354 assert_eq!(
1355 transport.out_queue,
1356 VecDeque::<Vec<u8>>::from([
1357 b"INFOmax-download-size: 0x400".into(),
1358 b"INFOvar_0: val_0".into(),
1359 b"INFOvar_1:a:b: val_1_a_b".into(),
1360 b"INFOvar_1:c:d: val_1_c_d".into(),
1361 b"INFOvar_2:e:f: val_2_e_f".into(),
1362 b"OKAY".into(),
1363 ])
1364 );
1365 }
1366
1367 #[test]
test_download()1368 fn test_download() {
1369 let mut fastboot_impl: FastbootTest = Default::default();
1370 fastboot_impl.download_buffer = vec![0u8; 1024];
1371 let download_content: Vec<u8> =
1372 (0..fastboot_impl.download_buffer.len()).into_iter().map(|v| v as u8).collect();
1373 let mut transport = TestTransport::new();
1374 // Splits download into two batches.
1375 let (first, second) = download_content.as_slice().split_at(download_content.len() / 2);
1376 transport.add_input(format!("download:{:#x}", download_content.len()).as_bytes());
1377 transport.add_input(first);
1378 transport.add_input(second);
1379 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1380 assert_eq!(
1381 transport.out_queue,
1382 VecDeque::<Vec<u8>>::from([b"DATA00000400".into(), b"OKAY".into(),])
1383 );
1384 assert_eq!(fastboot_impl.downloaded_size, download_content.len());
1385 assert_eq!(fastboot_impl.download_buffer, download_content);
1386 }
1387
1388 #[test]
test_download_not_enough_args()1389 fn test_download_not_enough_args() {
1390 let mut fastboot_impl: FastbootTest = Default::default();
1391 fastboot_impl.download_buffer = vec![0u8; 1024];
1392 let mut transport = TestTransport::new();
1393 transport.add_input(b"download");
1394 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1395 assert_eq!(transport.out_queue, [b"FAILNot enough argument"]);
1396 }
1397
1398 #[test]
test_download_invalid_hex_string()1399 fn test_download_invalid_hex_string() {
1400 let mut fastboot_impl: FastbootTest = Default::default();
1401 fastboot_impl.download_buffer = vec![0u8; 1024];
1402 let mut transport = TestTransport::new();
1403 transport.add_input(b"download:hhh");
1404 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1405 assert_eq!(transport.out_queue.len(), 1);
1406 assert!(transport.out_queue[0].starts_with(b"FAIL"));
1407 }
1408
test_download_size(download_buffer_size: usize, download_size: usize, msg: &str)1409 fn test_download_size(download_buffer_size: usize, download_size: usize, msg: &str) {
1410 let mut fastboot_impl: FastbootTest = Default::default();
1411 fastboot_impl.download_buffer = vec![0u8; download_buffer_size];
1412 let mut transport = TestTransport::new();
1413 transport.add_input(format!("download:{:#x}", download_size).as_bytes());
1414 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1415 assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([msg.as_bytes().into()]));
1416 }
1417
1418 #[test]
test_download_download_size_too_big()1419 fn test_download_download_size_too_big() {
1420 test_download_size(1024, 1025, "FAILDownload size is too big");
1421 }
1422
1423 #[test]
test_download_zero_download_size()1424 fn test_download_zero_download_size() {
1425 test_download_size(1024, 0, "FAILZero download size");
1426 }
1427
1428 #[test]
test_download_more_than_expected()1429 fn test_download_more_than_expected() {
1430 let mut fastboot_impl: FastbootTest = Default::default();
1431 fastboot_impl.download_buffer = vec![0u8; 1024];
1432 let download_content: Vec<u8> = vec![0u8; fastboot_impl.download_buffer.len()];
1433 let mut transport = TestTransport::new();
1434 transport.add_input(format!("download:{:#x}", download_content.len() - 1).as_bytes());
1435 transport.add_input(&download_content[..]);
1436 // State should be reset to command state.
1437 transport.add_input(b"getvar:max-download-size");
1438 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1439 assert_eq!(
1440 transport.out_queue,
1441 VecDeque::<Vec<u8>>::from([
1442 b"DATA000003ff".into(),
1443 b"FAILMore data received then expected".into(),
1444 b"OKAY0x400".into(),
1445 ])
1446 );
1447 }
1448
1449 #[test]
test_oem_cmd()1450 fn test_oem_cmd() {
1451 let mut fastboot_impl: FastbootTest = Default::default();
1452 fastboot_impl.download_buffer = vec![0u8; 2048];
1453 let mut transport = TestTransport::new();
1454 transport.add_input(b"oem oem-command");
1455 fastboot_impl.oem_output =
1456 ("oem-return".into(), vec!["oem-info-1".into(), "oem-info-2".into()]);
1457 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1458 assert_eq!(fastboot_impl.oem_command, "oem-command");
1459 assert_eq!(
1460 transport.out_queue,
1461 VecDeque::<Vec<u8>>::from([
1462 b"INFOoem-info-1".into(),
1463 b"INFOoem-info-2".into(),
1464 b"OKAYoem-return".into(),
1465 ])
1466 );
1467 }
1468
1469 #[test]
test_flash()1470 fn test_flash() {
1471 let mut fastboot_impl: FastbootTest = Default::default();
1472 fastboot_impl.download_buffer = vec![0u8; 2048];
1473 let mut transport = TestTransport::new();
1474 transport.add_input(b"flash:boot_a:0::");
1475 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1476 assert_eq!(fastboot_impl.flash_partition, "boot_a:0::");
1477 assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([b"OKAY".into()]));
1478 }
1479
1480 #[test]
test_flash_missing_partition()1481 fn test_flash_missing_partition() {
1482 let mut fastboot_impl: FastbootTest = Default::default();
1483 let mut transport = TestTransport::new();
1484 transport.add_input(b"flash");
1485 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1486 assert_eq!(transport.out_queue, [b"FAILMissing partition"]);
1487 }
1488
1489 #[test]
test_erase()1490 fn test_erase() {
1491 let mut fastboot_impl: FastbootTest = Default::default();
1492 fastboot_impl.download_buffer = vec![0u8; 2048];
1493 let mut transport = TestTransport::new();
1494 transport.add_input(b"erase:boot_a:0::");
1495 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1496 assert_eq!(fastboot_impl.erase_partition, "boot_a:0::");
1497 assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([b"OKAY".into()]));
1498 }
1499
1500 #[test]
test_erase_missing_partition()1501 fn test_erase_missing_partition() {
1502 let mut fastboot_impl: FastbootTest = Default::default();
1503 let mut transport = TestTransport::new();
1504 transport.add_input(b"erase");
1505 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1506 assert_eq!(transport.out_queue, [b"FAILMissing partition"]);
1507 }
1508
1509 #[test]
test_upload()1510 fn test_upload() {
1511 let mut fastboot_impl: FastbootTest = Default::default();
1512 let upload_content: Vec<u8> = (0..1024).into_iter().map(|v| v as u8).collect();
1513 let mut transport = TestTransport::new();
1514 transport.add_input(b"upload");
1515 fastboot_impl.upload_config = (
1516 upload_content.len().try_into().unwrap(),
1517 vec![
1518 upload_content[..upload_content.len() / 2].to_vec(),
1519 upload_content[upload_content.len() / 2..].to_vec(),
1520 ],
1521 );
1522 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1523 assert_eq!(
1524 transport.out_queue,
1525 VecDeque::<Vec<u8>>::from([
1526 b"DATA00000400".into(),
1527 upload_content[..upload_content.len() / 2].to_vec(),
1528 upload_content[upload_content.len() / 2..].to_vec(),
1529 b"OKAY".into(),
1530 ])
1531 );
1532 }
1533
1534 #[test]
test_upload_not_enough_data()1535 fn test_upload_not_enough_data() {
1536 let mut fastboot_impl: FastbootTest = Default::default();
1537 fastboot_impl.download_buffer = vec![0u8; 2048];
1538 let mut transport = TestTransport::new();
1539 transport.add_input(b"upload");
1540 fastboot_impl.upload_config = (0x400, vec![vec![0u8; 0x400 - 1]]);
1541 assert!(block_on(run(&mut transport, &mut fastboot_impl)).is_err());
1542 }
1543
1544 #[test]
test_upload_more_data()1545 fn test_upload_more_data() {
1546 let mut fastboot_impl: FastbootTest = Default::default();
1547 fastboot_impl.download_buffer = vec![0u8; 2048];
1548 let mut transport = TestTransport::new();
1549 transport.add_input(b"upload");
1550 fastboot_impl.upload_config = (0x400, vec![vec![0u8; 0x400 + 1]]);
1551 assert!(block_on(run(&mut transport, &mut fastboot_impl)).is_err());
1552 }
1553
1554 #[test]
test_fetch()1555 fn test_fetch() {
1556 let mut fastboot_impl: FastbootTest = Default::default();
1557 fastboot_impl.download_buffer = vec![0u8; 2048];
1558 let mut transport = TestTransport::new();
1559 transport.add_input(b"fetch:boot_a:0:::200:400");
1560 fastboot_impl
1561 .fetch_data
1562 .insert("boot_a:0::", (0x400, vec![vec![0u8; 0x200], vec![1u8; 0x400]].concat()));
1563 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1564 assert_eq!(
1565 transport.out_queue,
1566 VecDeque::<Vec<u8>>::from([
1567 b"DATA00000400".into(),
1568 [1u8; 0x400].to_vec(),
1569 b"OKAY".into(),
1570 ])
1571 );
1572 }
1573
1574 #[test]
test_fetch_not_enough_data()1575 fn test_fetch_not_enough_data() {
1576 let mut fastboot_impl: FastbootTest = Default::default();
1577 fastboot_impl.download_buffer = vec![0u8; 2048];
1578 let mut transport = TestTransport::new();
1579 transport.add_input(b"fetch:boot_a:0:::200:400");
1580 fastboot_impl
1581 .fetch_data
1582 .insert("boot_a:0::", (0x400 - 1, vec![vec![0u8; 0x200], vec![1u8; 0x400]].concat()));
1583 assert!(block_on(process_next_command(&mut transport, &mut fastboot_impl)).is_err());
1584 }
1585
1586 #[test]
test_fetch_more_data()1587 fn test_fetch_more_data() {
1588 let mut fastboot_impl: FastbootTest = Default::default();
1589 fastboot_impl.download_buffer = vec![0u8; 2048];
1590 let mut transport = TestTransport::new();
1591 transport.add_input(b"fetch:boot_a:0:::200:400");
1592 fastboot_impl
1593 .fetch_data
1594 .insert("boot_a:0::", (0x400 + 1, vec![vec![0u8; 0x200], vec![1u8; 0x400]].concat()));
1595 assert!(block_on(process_next_command(&mut transport, &mut fastboot_impl)).is_err());
1596 }
1597
1598 #[test]
test_fetch_invalid_args()1599 fn test_fetch_invalid_args() {
1600 let mut fastboot_impl: FastbootTest = Default::default();
1601 fastboot_impl.download_buffer = vec![0u8; 2048];
1602 let mut transport = TestTransport::new();
1603 transport.add_input(b"fetch");
1604 transport.add_input(b"fetch:");
1605 transport.add_input(b"fetch:boot_a");
1606 transport.add_input(b"fetch:boot_a:200");
1607 transport.add_input(b"fetch:boot_a::400");
1608 transport.add_input(b"fetch:boot_a::");
1609 transport.add_input(b"fetch:boot_a:xxx:400");
1610 transport.add_input(b"fetch:boot_a:200:xxx");
1611 let _ = block_on(run(&mut transport, &mut fastboot_impl));
1612 assert!(transport.out_queue.iter().all(|v| v.starts_with(b"FAIL")));
1613 }
1614
1615 #[test]
test_fastboot_tcp()1616 fn test_fastboot_tcp() {
1617 let mut fastboot_impl: FastbootTest = Default::default();
1618 fastboot_impl.download_buffer = vec![0u8; 1024];
1619 let download_content: Vec<u8> =
1620 (0..fastboot_impl.download_buffer.len()).into_iter().map(|v| v as u8).collect();
1621 let mut tcp_stream: TestTcpStream = Default::default();
1622 tcp_stream.add_input(TCP_HANDSHAKE_MESSAGE);
1623 // Add two commands and verify both are executed.
1624 tcp_stream.add_length_prefixed_input(b"getvar:max-download-size");
1625 tcp_stream.add_length_prefixed_input(
1626 format!("download:{:#x}", download_content.len()).as_bytes(),
1627 );
1628 tcp_stream.add_length_prefixed_input(&download_content[..]);
1629 let _ = block_on(run_tcp_session(&mut tcp_stream, &mut fastboot_impl));
1630 let expected: &[&[u8]] = &[
1631 b"FB01",
1632 b"\x00\x00\x00\x00\x00\x00\x00\x09OKAY0x400",
1633 b"\x00\x00\x00\x00\x00\x00\x00\x0cDATA00000400",
1634 b"\x00\x00\x00\x00\x00\x00\x00\x04OKAY",
1635 ];
1636 assert_eq!(tcp_stream.out_queue, VecDeque::from(expected.concat()));
1637 assert_eq!(fastboot_impl.download_buffer, download_content);
1638 }
1639
1640 #[test]
test_fastboot_tcp_invalid_handshake()1641 fn test_fastboot_tcp_invalid_handshake() {
1642 let mut fastboot_impl: FastbootTest = Default::default();
1643 let mut tcp_stream: TestTcpStream = Default::default();
1644 tcp_stream.add_input(b"ABCD");
1645 assert_eq!(
1646 block_on(run_tcp_session(&mut tcp_stream, &mut fastboot_impl)).unwrap_err(),
1647 Error::InvalidHandshake
1648 );
1649 }
1650
1651 #[test]
test_fastboot_tcp_packet_size_exceeds_maximum()1652 fn test_fastboot_tcp_packet_size_exceeds_maximum() {
1653 let mut fastboot_impl: FastbootTest = Default::default();
1654 let mut tcp_stream: TestTcpStream = Default::default();
1655 tcp_stream.add_input(TCP_HANDSHAKE_MESSAGE);
1656 tcp_stream.add_input(&(MAX_COMMAND_SIZE + 1).to_be_bytes());
1657 assert_eq!(
1658 block_on(run_tcp_session(&mut tcp_stream, &mut fastboot_impl)).unwrap_err(),
1659 Error::InvalidInput
1660 );
1661 }
1662
1663 #[test]
test_reboot()1664 fn test_reboot() {
1665 let mut fastboot_impl: FastbootTest = Default::default();
1666 let mut transport = TestTransport::new();
1667 transport.add_input(b"reboot");
1668 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1669 assert_eq!(fastboot_impl.reboot_mode, Some(RebootMode::Normal));
1670 assert_eq!(transport.out_queue[0], b"OKAY");
1671 // Failure is expected here because test reboot implementation always returns, which
1672 // automatically generates a fastboot failure packet.
1673 assert!(transport.out_queue[1].starts_with(b"FAIL"));
1674 }
1675
1676 #[test]
test_reboot_bootloader()1677 fn test_reboot_bootloader() {
1678 let mut fastboot_impl: FastbootTest = Default::default();
1679 let mut transport = TestTransport::new();
1680 transport.add_input(b"reboot-bootloader");
1681 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1682 assert_eq!(fastboot_impl.reboot_mode, Some(RebootMode::Bootloader));
1683 assert_eq!(transport.out_queue[0], b"OKAY");
1684 // Failure is expected here because test reboot implementation always returns, which
1685 // automatically generates a fastboot failure packet.
1686 assert!(transport.out_queue[1].starts_with(b"FAIL"));
1687 }
1688
1689 #[test]
test_reboot_fastboot()1690 fn test_reboot_fastboot() {
1691 let mut fastboot_impl: FastbootTest = Default::default();
1692 let mut transport = TestTransport::new();
1693 transport.add_input(b"reboot-fastboot");
1694 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1695 assert_eq!(fastboot_impl.reboot_mode, Some(RebootMode::Fastboot));
1696 assert_eq!(transport.out_queue[0], b"OKAY");
1697 // Failure is expected here because test reboot implementation always returns, which
1698 // automatically generates a fastboot failure packet.
1699 assert!(transport.out_queue[1].starts_with(b"FAIL"));
1700 }
1701
1702 #[test]
test_reboot_recovery()1703 fn test_reboot_recovery() {
1704 let mut fastboot_impl: FastbootTest = Default::default();
1705 let mut transport = TestTransport::new();
1706 transport.add_input(b"reboot-recovery");
1707 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1708 assert_eq!(fastboot_impl.reboot_mode, Some(RebootMode::Recovery));
1709 assert_eq!(transport.out_queue[0], b"OKAY");
1710 // Failure is expected here because test reboot implementation always returns, which
1711 // automatically generates a fastboot failure packet.
1712 assert!(transport.out_queue[1].starts_with(b"FAIL"));
1713 }
1714
1715 #[test]
test_continue()1716 fn test_continue() {
1717 let mut fastboot_impl: FastbootTest = Default::default();
1718 fastboot_impl.download_buffer = vec![0u8; 1024];
1719 let mut transport = TestTransport::new();
1720 transport.add_input(b"getvar:max-download-size");
1721 transport.add_input(b"continue");
1722 transport.add_input(b"getvar:max-download-size");
1723 block_on(run(&mut transport, &mut fastboot_impl)).unwrap();
1724 assert_eq!(
1725 transport.out_queue,
1726 VecDeque::<Vec<u8>>::from([
1727 b"OKAY0x400".into(),
1728 b"INFOContinuing to boot...".into(),
1729 b"OKAY".into()
1730 ])
1731 );
1732 }
1733
1734 #[test]
test_continue_run_tcp()1735 fn test_continue_run_tcp() {
1736 let mut fastboot_impl: FastbootTest = Default::default();
1737 let mut tcp_stream: TestTcpStream = Default::default();
1738 tcp_stream.add_input(TCP_HANDSHAKE_MESSAGE);
1739 tcp_stream.add_length_prefixed_input(b"continue");
1740 block_on(run_tcp_session(&mut tcp_stream, &mut fastboot_impl)).unwrap();
1741 }
1742
1743 #[test]
test_set_active()1744 fn test_set_active() {
1745 let mut fastboot_impl: FastbootTest = Default::default();
1746 fastboot_impl.download_buffer = vec![0u8; 1024];
1747 let mut transport = TestTransport::new();
1748 transport.add_input(b"set_active:a");
1749 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1750 assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([b"OKAY".into()]));
1751 assert_eq!(fastboot_impl.active_slot, Some("a".into()));
1752 }
1753
1754 #[test]
test_set_active_missing_slot()1755 fn test_set_active_missing_slot() {
1756 let mut fastboot_impl: FastbootTest = Default::default();
1757 fastboot_impl.download_buffer = vec![0u8; 1024];
1758 let mut transport = TestTransport::new();
1759 transport.add_input(b"set_active");
1760 block_on(process_next_command(&mut transport, &mut fastboot_impl)).unwrap();
1761 assert_eq!(transport.out_queue, VecDeque::<Vec<u8>>::from([b"FAILMissing slot".into()]));
1762 }
1763 }
1764