blob: 1ba9b6025b3b2831f90fea9ce0115600317d0f23 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{bail, Result},
command::Command,
reply::Reply,
std::convert::TryFrom,
std::io::{Read, Write},
};
pub mod command;
pub mod reply;
const MAX_PACKET_SIZE: usize = 64;
const READ_RETRY_MAX: usize = 100;
fn read_from_interface<T: Read>(interface: &mut T) -> Result<Reply> {
let mut buf: [u8; MAX_PACKET_SIZE] = [0; MAX_PACKET_SIZE];
let size = interface.read(&mut buf)?;
let (trimmed, _) = buf.split_at(size);
Reply::try_from(trimmed.to_vec())
}
fn read_and_log_info<T: Read>(interface: &mut T) -> Result<Reply> {
let mut retry = 0;
loop {
match read_from_interface(interface) {
Ok(reply) => match reply {
Reply::Info(msg) => log::info!("{}", msg),
_ => return Ok(reply),
},
Err(e) => {
log::warn!("error reading fastboot reply from usb interface: {:?}", e);
retry += 1;
if retry >= READ_RETRY_MAX {
log::error!("could not read reply: {:?}", e);
return Err(e);
}
}
}
}
}
pub fn send<T: Read + Write>(cmd: Command, interface: &mut T) -> Result<Reply> {
interface.write(&Vec::<u8>::try_from(cmd)?)?;
read_and_log_info(interface)
}
pub fn upload<T: Read + Write>(data: &[u8], interface: &mut T) -> Result<Reply> {
let reply = send(Command::Download(u32::try_from(data.len())?), interface)?;
match reply {
Reply::Data(s) => {
if s != u32::try_from(data.len())? {
bail!(
"Target responded with wrong data size - received:{} expected:{}",
s,
data.len()
);
}
// TODO - possibly split the data based on the speed of the USB connection.
// Max packet size must be 64 bytes for full-speed, 512 bytes for high-speed and
// 1024 bytes for Super Speed USB
// TODO (fxb/60416) - try to use BufWriter instead - currently the usb_bulk library
// does not support the Copy trait necessary to get that working.
let buffer_length = 64;
let upload_length = data.len();
let mut buffer: &[u8];
let mut cursor = 0;
while cursor < upload_length {
let remaining = upload_length - cursor;
buffer = if remaining < buffer_length {
&data[cursor..]
} else {
&data[cursor..cursor + buffer_length]
};
interface.write(&buffer)?;
cursor += buffer_length;
}
read_and_log_info(interface)
}
_ => bail!("Did not get expected Data reply: {:?}", reply),
}
}
////////////////////////////////////////////////////////////////////////////////
// tests
#[cfg(test)]
mod test {
use super::*;
use command::ClientVariable;
struct TestTransport {
replies: Vec<Reply>,
}
impl Read for TestTransport {
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
match self.replies.pop() {
Some(r) => {
let reply = Vec::<u8>::from(r);
buf[..reply.len()].copy_from_slice(&reply);
Ok(reply.len())
}
None => Ok(0),
}
}
}
impl Write for TestTransport {
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
Ok(buf.len())
}
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
unimplemented!()
}
}
impl TestTransport {
pub fn new() -> Self {
TestTransport { replies: Vec::new() }
}
pub fn push(&mut self, reply: Reply) {
self.replies.push(reply);
}
}
#[test]
fn test_send_does_not_return_info_replies() {
let mut test_transport = TestTransport::new();
test_transport.push(Reply::Okay("0.4".to_string()));
let response = send(Command::GetVar(ClientVariable::Version), &mut test_transport);
assert!(!response.is_err());
assert_eq!(response.unwrap(), Reply::Okay("0.4".to_string()));
test_transport.push(Reply::Okay("0.4".to_string()));
test_transport.push(Reply::Info("Test".to_string()));
let response_with_info =
send(Command::GetVar(ClientVariable::Version), &mut test_transport);
assert!(!response_with_info.is_err());
assert_eq!(response_with_info.unwrap(), Reply::Okay("0.4".to_string()));
test_transport.push(Reply::Okay("0.4".to_string()));
for i in 0..10 {
test_transport.push(Reply::Info(format!("Test {}", i).to_string()));
}
let response_with_info =
send(Command::GetVar(ClientVariable::Version), &mut test_transport);
assert!(!response_with_info.is_err());
assert_eq!(response_with_info.unwrap(), Reply::Okay("0.4".to_string()));
}
#[test]
fn test_uploading_data_to_partition() {
let data: [u8; 1024] = [0; 1024];
let mut test_transport = TestTransport::new();
test_transport.push(Reply::Okay("Done Writing".to_string()));
test_transport.push(Reply::Info("Writing".to_string()));
test_transport.push(Reply::Data(1024));
let response = upload(&data, &mut test_transport);
assert!(!response.is_err());
assert_eq!(response.unwrap(), Reply::Okay("Done Writing".to_string()));
}
#[test]
fn test_uploading_data_with_unexpected_reply() {
let data: [u8; 1024] = [0; 1024];
let mut test_transport = TestTransport::new();
test_transport.push(Reply::Info("Writing".to_string()));
let response = upload(&data, &mut test_transport);
assert!(response.is_err());
}
#[test]
fn test_uploading_data_with_unexpected_data_size_reply() {
let data: [u8; 1024] = [0; 1024];
let mut test_transport = TestTransport::new();
test_transport.push(Reply::Data(1000));
let response = upload(&data, &mut test_transport);
assert!(response.is_err());
}
}