blob: d717aec0c9d493dbeb5975c1221f4721666d6328 [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::anyhow,
std::convert::{From, TryFrom},
std::fmt,
};
// Client response with a single packet no greater than 64 bytes. The first four bytes of the
// response are “OKAY”, “FAIL”, “DATA”, or “INFO”. Additional bytes may contain an (ascii)
// informative message.
#[derive(PartialEq, Clone)]
pub enum Reply {
// INFO -> the remaining 60 bytes are an informative message (providing progress or diagnostic
// messages).
Info(String),
// FAIL -> the requested command failed. The remaining 60 bytes of the response (if present)
// provide a textual failure message to present to the user.
Fail(String),
// OKAY -> the requested command completed successfully.
Okay(String),
// DATA -> the requested command is ready for the data phase. A DATA response packet will be 12
// bytes long, in the form of DATA00000000 where the 8 digit hexadecimal number represents the
// total data size to transfer.
Data(u32),
}
const MIN_REPLY_LENGTH: usize = 4;
const MAX_REPLY_LENGTH: usize = 64;
const DATA_SIZE_LENGTH: usize = 8;
impl TryFrom<Vec<u8>> for Reply {
type Error = anyhow::Error;
fn try_from(byte_vec: Vec<u8>) -> Result<Self, Self::Error> {
if byte_vec.len() < MIN_REPLY_LENGTH {
return Err(anyhow!("Fastboot reply must have {} bytes at least!", MIN_REPLY_LENGTH));
}
if byte_vec.len() > MAX_REPLY_LENGTH {
return Err(anyhow!(
"Fastboot reply must not have more than {} bytes",
MAX_REPLY_LENGTH
));
}
let (reply_type, reply_data) = byte_vec.split_at(MIN_REPLY_LENGTH);
let reply_type_str = String::from_utf8_lossy(reply_type).to_string().to_ascii_uppercase();
let reply_data_str = String::from_utf8_lossy(reply_data).to_string();
match reply_type_str.as_ref() {
"INFO" => Ok(Reply::Info(reply_data_str)),
"FAIL" => Ok(Reply::Fail(reply_data_str)),
"OKAY" => Ok(Reply::Okay(reply_data_str)),
"DATA" => {
if reply_data_str.len() != DATA_SIZE_LENGTH {
return Err(anyhow!(
"DATA response packet size is {} expected {}",
reply_data_str.len(),
DATA_SIZE_LENGTH
));
}
match u32::from_str_radix(&reply_data_str, 16) {
Ok(ds) => Ok(Reply::Data(ds)),
Err(e) => Err(anyhow!("Error parsing DATA reply size: {}", e)),
}
}
_ => Err(anyhow!("Unknown reply type: {}", String::from_utf8_lossy(reply_type))),
}
}
}
impl From<Reply> for Vec<u8> {
fn from(reply: Reply) -> Vec<u8> {
match reply {
Reply::Info(s) => [b"INFO", &s.into_bytes()[..]].concat(),
Reply::Fail(s) => [b"FAIL", &s.into_bytes()[..]].concat(),
Reply::Okay(s) => [b"OKAY", &s.into_bytes()[..]].concat(),
Reply::Data(s) => [b"DATA", &format!("{:08X}", s).into_bytes()[..]].concat(),
}
}
}
fn write_output(reply: &Reply, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match reply {
Reply::Info(s) => write!(f, "INFO {}", s),
Reply::Fail(s) => write!(f, "FAIL {}", s),
Reply::Okay(s) => write!(f, "OKAY {}", s),
Reply::Data(s) => write!(f, "DATA - size {}", s),
}
}
impl fmt::Display for Reply {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_output(self, f)
}
}
impl fmt::Debug for Reply {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_output(self, f)
}
}
////////////////////////////////////////////////////////////////////////////////
// tests
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_info() {
let info_prefix = vec![b'I', b'N', b'F', b'O'];
let reply = Reply::try_from(info_prefix.clone()).unwrap();
assert_eq!(reply, Reply::Info("".to_string()));
let info_prefix_mix_case = vec![b'I', b'n', b'f', b'O'];
let reply_mix_case = Reply::try_from(info_prefix_mix_case.clone()).unwrap();
assert_eq!(reply_mix_case, Reply::Info("".to_string()));
let info_message = vec![b'T', b'e', b's', b't'];
let concat = [&info_prefix[..], &info_message[..]].concat();
let reply_with_message = Reply::try_from(concat).unwrap();
assert_eq!(reply_with_message, Reply::Info("Test".to_string()));
let max_message = vec![b'X'; MAX_REPLY_LENGTH - MIN_REPLY_LENGTH];
let max = [&info_prefix[..], &max_message[..]].concat();
let reply_with_max = Reply::try_from(max);
assert!(
!reply_with_max.is_err(),
format!(
"Max message length of {} bytes should not be an error",
MAX_REPLY_LENGTH - MIN_REPLY_LENGTH
)
);
assert_eq!(reply_with_max.unwrap(), Reply::Info(String::from_utf8(max_message).unwrap()));
let overflow_message = vec![b'X'; MAX_REPLY_LENGTH - MIN_REPLY_LENGTH + 1];
let overflow = [&info_prefix[..], &overflow_message[..]].concat();
let reply_with_overflow = Reply::try_from(overflow);
assert!(
reply_with_overflow.is_err(),
format!(
"Messages over {} bytes should throw an error",
MAX_REPLY_LENGTH - MIN_REPLY_LENGTH
)
);
}
#[test]
fn test_fail() {
let fail_prefix = vec![b'F', b'A', b'I', b'L'];
let reply = Reply::try_from(fail_prefix.clone()).unwrap();
assert_eq!(reply, Reply::Fail("".to_string()));
let fail_prefix_mix_case = vec![b'f', b'A', b'i', b'L'];
let reply_mix_case = Reply::try_from(fail_prefix_mix_case.clone()).unwrap();
assert_eq!(reply_mix_case, Reply::Fail("".to_string()));
let fail_message = vec![b'T', b'e', b's', b't'];
let concat = [&fail_prefix[..], &fail_message[..]].concat();
let reply_with_message = Reply::try_from(concat).unwrap();
assert_eq!(reply_with_message, Reply::Fail("Test".to_string()));
let max_message = vec![b'X'; MAX_REPLY_LENGTH - MIN_REPLY_LENGTH];
let max = [&fail_prefix[..], &max_message[..]].concat();
let reply_with_max = Reply::try_from(max);
assert!(
!reply_with_max.is_err(),
format!(
"Max message length of {} bytes should not be an error",
MAX_REPLY_LENGTH - MIN_REPLY_LENGTH
)
);
assert_eq!(reply_with_max.unwrap(), Reply::Fail(String::from_utf8(max_message).unwrap()));
let overflow_message = vec![b'X'; MAX_REPLY_LENGTH - MIN_REPLY_LENGTH + 1];
let overflow = [&fail_prefix[..], &overflow_message[..]].concat();
let reply_with_overflow = Reply::try_from(overflow);
assert!(
reply_with_overflow.is_err(),
format!(
"Messages over {} bytes should throw an error",
MAX_REPLY_LENGTH - MIN_REPLY_LENGTH
)
);
}
#[test]
fn test_okay() {
let okay_prefix = vec![b'O', b'K', b'A', b'Y'];
let reply = Reply::try_from(okay_prefix.clone()).unwrap();
assert_eq!(reply, Reply::Okay("".to_string()));
let okay_prefix_mix_case = vec![b'O', b'k', b'A', b'y'];
let reply_mix_case = Reply::try_from(okay_prefix_mix_case.clone()).unwrap();
assert_eq!(reply_mix_case, Reply::Okay("".to_string()));
let okay_message = vec![b'T', b'e', b's', b't'];
let concat = [&okay_prefix[..], &okay_message[..]].concat();
let reply_with_message = Reply::try_from(concat).unwrap();
assert_eq!(reply_with_message, Reply::Okay("Test".to_string()));
let max_message = vec![b'X'; MAX_REPLY_LENGTH - MIN_REPLY_LENGTH];
let max = [&okay_prefix[..], &max_message[..]].concat();
let reply_with_max = Reply::try_from(max);
assert!(
!reply_with_max.is_err(),
format!(
"Max message length of {} bytes should not be an error",
MAX_REPLY_LENGTH - MIN_REPLY_LENGTH
)
);
assert_eq!(reply_with_max.unwrap(), Reply::Okay(String::from_utf8(max_message).unwrap()));
let overflow_message = vec![b'X'; MAX_REPLY_LENGTH - MIN_REPLY_LENGTH + 1];
let overflow = [&okay_prefix[..], &overflow_message[..]].concat();
let reply_with_overflow = Reply::try_from(overflow);
assert!(
reply_with_overflow.is_err(),
format!(
"Messages over {} bytes should throw an error",
MAX_REPLY_LENGTH - MIN_REPLY_LENGTH
)
);
}
#[test]
fn test_data() {
let data_prefix = vec![b'D', b'A', b'T', b'A'];
let reply_with_no_size = Reply::try_from(data_prefix.clone());
assert!(
reply_with_no_size.is_err(),
format!("DATA replies must have an {} byte size associated", DATA_SIZE_LENGTH)
);
let zero = vec![b'0'; DATA_SIZE_LENGTH];
let concat_zero = [&data_prefix[..], &zero[..]].concat();
let reply = Reply::try_from(concat_zero.clone()).unwrap();
assert_eq!(reply, Reply::Data(0));
let data_prefix_mix_case = vec![b'd', b'A', b'T', b'A'];
let concat_zero_mix_case = [&data_prefix_mix_case[..], &zero[..]].concat();
let reply_mix_case = Reply::try_from(concat_zero_mix_case.clone()).unwrap();
assert_eq!(reply_mix_case, Reply::Data(0));
let data_max = vec![b'f'; DATA_SIZE_LENGTH];
let concat = [&data_prefix[..], &data_max[..]].concat();
let reply_with_message = Reply::try_from(concat).unwrap();
assert_eq!(reply_with_message, Reply::Data(0xffffffff));
let data_message_uppercase = vec![b'F'; DATA_SIZE_LENGTH];
let concat_uppercase = [&data_prefix[..], &data_message_uppercase[..]].concat();
let reply_with_message_uppercase = Reply::try_from(concat_uppercase).unwrap();
assert_eq!(reply_with_message_uppercase, Reply::Data(0xffffffff));
}
#[test]
fn test_unknown() {
let unknown_prefix = vec![b'T', b'e', b's', b't'];
let unknown_reply = Reply::try_from(unknown_prefix.clone());
assert!(unknown_reply.is_err(), "Unknown replies throw an error");
}
}