Skip to content

Commit

Permalink
Add checksum-md5 command (#536)
Browse files Browse the repository at this point in the history
* Add `checksum-md5` command

* fmt

* clippy

* CHANGELOG.md entry

* `checksum-md5` for cargo-espflash, add timeout
  • Loading branch information
bjoernQ authored Dec 28, 2023
1 parent 77fd5b4 commit 5123911
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add --chip argument for flash and write-bin commands (#514)
- Add --partition-table-offset argument for specifying the partition table offset (#516)
- Add `Serialize` and `Deserialize` to `FlashFrequency`, `FlashMode` and `FlashSize`. (#528)
- Add `checksum-md5` command (#536)

### Fixed

Expand Down
31 changes: 0 additions & 31 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use cargo_metadata::Message;
use clap::{Args, CommandFactory, Parser, Subcommand};
use espflash::{
cli::{
self, board_info, completions, config::Config, connect, erase_flash, erase_partitions,
erase_region, flash_elf_image, monitor::monitor, parse_partition_table, partition_table,
print_board_info, save_elf_as_image, serial_monitor, CompletionsArgs, ConnectArgs,
EraseFlashArgs, EraseRegionArgs, EspflashProgress, FlashConfigArgs, MonitorArgs,
PartitionTableArgs,
self, board_info, checksum_md5, completions, config::Config, connect, erase_flash,
erase_partitions, erase_region, flash_elf_image, monitor::monitor, parse_partition_table,
partition_table, print_board_info, save_elf_as_image, serial_monitor, ChecksumMd5Args,
CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress,
FlashConfigArgs, MonitorArgs, PartitionTableArgs,
},
error::Error as EspflashError,
image_format::ImageFormatKind,
Expand Down Expand Up @@ -106,6 +106,8 @@ enum Commands {
/// Otherwise, each segment will be saved as individual binaries, prefixed
/// with their intended addresses in flash.
SaveImage(SaveImageArgs),
/// Calculate the MD5 checksum of the given region
ChecksumMd5(ChecksumMd5Args),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -217,6 +219,7 @@ fn main() -> Result<()> {
Commands::Monitor(args) => serial_monitor(args, &config),
Commands::PartitionTable(args) => partition_table(args),
Commands::SaveImage(args) => save_image(args),
Commands::ChecksumMd5(args) => checksum_md5(&args, &config),
}
}

Expand Down
1 change: 0 additions & 1 deletion espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ required-features = ["cli"]
[dependencies]
addr2line = { version = "0.21.0", optional = true }
base64 = "0.21.4"
binrw = "0.12.0"
bytemuck = { version = "1.14.0", features = ["derive"] }
clap = { version = "4.4.6", features = ["derive", "env", "wrap_help"], optional = true }
clap_complete = { version = "4.4.3", optional = true }
Expand Down
13 changes: 8 additions & 5 deletions espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use std::{
use clap::{Args, CommandFactory, Parser, Subcommand};
use espflash::{
cli::{
self, board_info, completions, config::Config, connect, erase_flash, erase_partitions,
erase_region, flash_elf_image, monitor::monitor, parse_partition_table, parse_uint32,
partition_table, print_board_info, save_elf_as_image, serial_monitor, CompletionsArgs,
ConnectArgs, EraseFlashArgs, EraseRegionArgs, EspflashProgress, FlashConfigArgs,
MonitorArgs, PartitionTableArgs,
self, board_info, checksum_md5, completions, config::Config, connect, erase_flash,
erase_partitions, erase_region, flash_elf_image, monitor::monitor, parse_partition_table,
parse_uint32, partition_table, print_board_info, save_elf_as_image, serial_monitor,
ChecksumMd5Args, CompletionsArgs, ConnectArgs, EraseFlashArgs, EraseRegionArgs,
EspflashProgress, FlashConfigArgs, MonitorArgs, PartitionTableArgs,
},
error::Error,
image_format::ImageFormatKind,
Expand Down Expand Up @@ -83,6 +83,8 @@ enum Commands {
SaveImage(SaveImageArgs),
/// Write a binary file to a specific address in a target device's flash
WriteBin(WriteBinArgs),
/// Calculate the MD5 checksum of the given region
ChecksumMd5(ChecksumMd5Args),
}

/// Erase named partitions based on provided partition table
Expand Down Expand Up @@ -176,6 +178,7 @@ fn main() -> Result<()> {
Commands::PartitionTable(args) => partition_table(args),
Commands::SaveImage(args) => save_image(args),
Commands::WriteBin(args) => write_bin(args, &config),
Commands::ChecksumMd5(args) => checksum_md5(&args, &config),
}
}

Expand Down
28 changes: 28 additions & 0 deletions espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,24 @@ pub struct MonitorArgs {
pub log_format: LogFormat,
}

#[derive(Debug, Args)]
#[non_exhaustive]
pub struct ChecksumMd5Args {
/// Start address
#[clap(short, long, value_parser=parse_u32)]
address: u32,
/// Length
#[clap(short, long, value_parser=parse_u32)]
length: u32,
/// Connection configuration
#[clap(flatten)]
connect_args: ConnectArgs,
}

pub fn parse_u32(input: &str) -> Result<u32, ParseIntError> {
parse_int::parse(input)
}

/// Select a serial port and establish a connection with a target device
pub fn connect(args: &ConnectArgs, config: &Config) -> Result<Flasher> {
let port_info = get_serial_port_info(args, config)?;
Expand Down Expand Up @@ -284,6 +302,16 @@ pub fn board_info(args: &ConnectArgs, config: &Config) -> Result<()> {
Ok(())
}

/// Connect to a target device and calculate the checksum of the given region
pub fn checksum_md5(args: &ChecksumMd5Args, config: &Config) -> Result<()> {
let mut flasher = connect(&args.connect_args, config)?;

let checksum = flasher.checksum_md5(args.address, args.length)?;
println!("0x{:x}", checksum);

Ok(())
}

/// Generate shell completions for the given shell
pub fn completions(args: &CompletionsArgs, app: &mut clap::Command, bin_name: &str) -> Result<()> {
clap_complete::generate(args.shell, app, bin_name, &mut std::io::stdout());
Expand Down
18 changes: 18 additions & 0 deletions espflash/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ERASE_CHIP_TIMEOUT: Duration = Duration::from_secs(120);
const MEM_END_TIMEOUT: Duration = Duration::from_millis(50);
const SYNC_TIMEOUT: Duration = Duration::from_millis(100);
const FLASH_DEFLATE_END_TIMEOUT: Duration = Duration::from_secs(10);
const FLASH_MD5_TIMEOUT: Duration = Duration::from_secs(8);

/// Types of commands that can be sent to a target device
#[derive(Copy, Clone, Debug, Display)]
Expand Down Expand Up @@ -51,6 +52,7 @@ impl CommandType {
CommandType::Sync => SYNC_TIMEOUT,
CommandType::EraseFlash => ERASE_CHIP_TIMEOUT,
CommandType::FlashDeflateEnd => FLASH_DEFLATE_END_TIMEOUT,
CommandType::FlashMd5 => FLASH_MD5_TIMEOUT,
_ => DEFAULT_TIMEOUT,
}
}
Expand Down Expand Up @@ -159,6 +161,10 @@ pub enum Command<'a> {
offset: u32,
size: u32,
},
FlashMd5 {
offset: u32,
size: u32,
},
}

impl<'a> Command<'a> {
Expand All @@ -184,6 +190,7 @@ impl<'a> Command<'a> {
Command::FlashDetect => CommandType::FlashDetect,
Command::EraseFlash { .. } => CommandType::EraseFlash,
Command::EraseRegion { .. } => CommandType::EraseRegion,
Command::FlashMd5 { .. } => CommandType::FlashMd5,
}
}

Expand Down Expand Up @@ -361,6 +368,17 @@ impl<'a> Command<'a> {
writer.write_all(&offset.to_le_bytes())?;
writer.write_all(&size.to_le_bytes())?;
}
Command::FlashMd5 { offset, size } => {
// length
writer.write_all(&(16u16.to_le_bytes()))?;
// checksum
writer.write_all(&(0u32.to_le_bytes()))?;
// data
writer.write_all(&offset.to_le_bytes())?;
writer.write_all(&size.to_le_bytes())?;
writer.write_all(&(0u32.to_le_bytes()))?;
writer.write_all(&(0u32.to_le_bytes()))?;
}
};
Ok(())
}
Expand Down
88 changes: 82 additions & 6 deletions espflash/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use std::{
time::Duration,
};

use binrw::{io::Cursor, BinRead, BinReaderExt};
use log::debug;
use serialport::UsbPortInfo;
use slip_codec::SlipDecoder;
Expand All @@ -34,13 +33,41 @@ const MAX_CONNECT_ATTEMPTS: usize = 7;
const MAX_SYNC_ATTEMPTS: usize = 5;
pub(crate) const USB_SERIAL_JTAG_PID: u16 = 0x1001;

#[derive(Debug, Copy, Clone)]
pub enum CommandResponseValue {
ValueU32(u32),
ValueU128(u128),
}

impl TryInto<u32> for CommandResponseValue {
type Error = crate::error::Error;

fn try_into(self) -> Result<u32, Self::Error> {
match self {
CommandResponseValue::ValueU32(value) => Ok(value),
CommandResponseValue::ValueU128(_) => Err(crate::error::Error::InternalError),
}
}
}

impl TryInto<u128> for CommandResponseValue {
type Error = crate::error::Error;

fn try_into(self) -> Result<u128, Self::Error> {
match self {
CommandResponseValue::ValueU32(_) => Err(crate::error::Error::InternalError),
CommandResponseValue::ValueU128(value) => Ok(value),
}
}
}

/// A response from a target device following a command
#[derive(Debug, Copy, Clone, BinRead)]
#[derive(Debug, Copy, Clone)]
pub struct CommandResponse {
pub resp: u8,
pub return_op: u8,
pub return_length: u16,
pub value: u32,
pub value: CommandResponseValue,
pub error: u8,
pub status: u8,
}
Expand Down Expand Up @@ -195,8 +222,56 @@ impl Connection {
match self.read(10)? {
None => Ok(None),
Some(response) => {
let mut cursor = Cursor::new(response);
let header = cursor.read_le()?;
// here is what esptool does: https://github.com/espressif/esptool/blob/master/esptool/loader.py#L458
// from esptool: things are a bit weird here, bear with us

// we rely on the known and expected response sizes which should be fine for now - if that changes we need to pass the command type
// we are parsing the response for
// for most commands the response length is 10 (for the stub) or 12 (for ROM code)
// the MD5 command response is 44 for ROM loader, 26 for the stub
// see https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html?highlight=md5#response-packet
// see https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html?highlight=md5#status-bytes
// see https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/serial-protocol.html?highlight=md5#verifying-uploaded-data
let status_len = if response.len() == 10 || response.len() == 26 {
2
} else {
4
};

let value = match response.len() {
10 | 12 => CommandResponseValue::ValueU32(u32::from_le_bytes(
response[4..][..4].try_into().unwrap(),
)),
44 => {
// MD5 is in ASCII
CommandResponseValue::ValueU128(
u128::from_str_radix(
std::str::from_utf8(&response[8..][..32]).unwrap(),
16,
)
.unwrap(),
)
}
26 => {
// MD5 is BE bytes
CommandResponseValue::ValueU128(u128::from_be_bytes(
response[8..][..16].try_into().unwrap(),
))
}
_ => {
return Err(Error::InternalError);
}
};

let header = CommandResponse {
resp: response[0],
return_op: response[1],
return_length: u16::from_le_bytes(response[2..][..2].try_into().unwrap()),
value,
error: response[response.len() - status_len],
status: response[response.len() - status_len + 1],
};

Ok(Some(header))
}
}
Expand All @@ -217,7 +292,7 @@ impl Connection {
}

/// Write a command and reads the response
pub fn command(&mut self, command: Command) -> Result<u32, Error> {
pub fn command(&mut self, command: Command) -> Result<CommandResponseValue, Error> {
let ty = command.command_type();
self.write_command(command).for_command(ty)?;

Expand Down Expand Up @@ -247,6 +322,7 @@ impl Connection {
self.with_timeout(CommandType::ReadReg.timeout(), |connection| {
connection.command(Command::ReadReg { address: reg })
})
.map(|v| v.try_into().unwrap())
}

/// Write a register command with a timeout
Expand Down
18 changes: 3 additions & 15 deletions espflash/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ pub enum Error {
#[error(transparent)]
#[diagnostic(transparent)]
Defmt(#[from] DefmtError),

#[error("Internal Error")]
InternalError,
}

impl From<io::Error> for Error {
Expand All @@ -187,12 +190,6 @@ impl From<io::Error> for Error {
}
}

impl From<binrw::Error> for Error {
fn from(err: binrw::Error) -> Self {
Self::Connection(err.into())
}
}

impl From<serialport::Error> for Error {
fn from(err: serialport::Error) -> Self {
Self::Connection(err.into())
Expand Down Expand Up @@ -261,15 +258,6 @@ impl From<io::Error> for ConnectionError {
}
}

impl From<binrw::Error> for ConnectionError {
fn from(err: binrw::Error) -> Self {
match err {
binrw::Error::Io(e) => ConnectionError::from(e),
_ => unreachable!(),
}
}
}

impl From<serialport::Error> for ConnectionError {
fn from(err: serialport::Error) -> Self {
use serialport::ErrorKind;
Expand Down
Loading

0 comments on commit 5123911

Please sign in to comment.