use std::path::Path;
use std::time::Duration;
use serde_json::{json, Value};
use crate::error::{Error, Result};
use crate::wire::{self, DebugConnection, DebugInfo, DebugStatus, Timeout};
fn base64_encode(input: &[u8]) -> String {
const ALPHABET: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
for chunk in input.chunks(3) {
let b0 = chunk[0] as u32;
let b1 = *chunk.get(1).unwrap_or(&0) as u32;
let b2 = *chunk.get(2).unwrap_or(&0) as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(ALPHABET[(n >> 18 & 0x3F) as usize] as char);
out.push(ALPHABET[(n >> 12 & 0x3F) as usize] as char);
out.push(if chunk.len() > 1 {
ALPHABET[(n >> 6 & 0x3F) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
ALPHABET[(n & 0x3F) as usize] as char
} else {
'='
});
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FirmwareKind {
Hex,
Elf,
Bin,
}
impl FirmwareKind {
fn from_path(path: &Path) -> Result<Self> {
match path.extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase) {
Some(ext) if ext == "hex" => Ok(FirmwareKind::Hex),
Some(ext) if ext == "elf" => Ok(FirmwareKind::Elf),
Some(ext) if ext == "bin" => Ok(FirmwareKind::Bin),
_ => Err(Error::Config(format!(
"cannot infer firmware type from '{}'; use flash_with to set it explicitly",
path.display()
))),
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectOptions {
pub speed: Option<String>,
pub force: bool,
pub halt: bool,
pub gdb: bool,
}
impl Default for ConnectOptions {
fn default() -> Self {
ConnectOptions {
speed: None,
force: false,
halt: false,
gdb: true,
}
}
}
const CONNECT_TIMEOUT: Timeout = Timeout::After(Duration::from_secs(30));
const FLASH_TIMEOUT: Timeout = Timeout::After(Duration::from_secs(180));
const ERASE_TIMEOUT: Timeout = Timeout::After(Duration::from_secs(120));
const RESET_TIMEOUT: Timeout = Timeout::After(Duration::from_secs(10));
const MEMRD_TIMEOUT: Timeout = Timeout::After(Duration::from_secs(30));
const QUICK_TIMEOUT: Timeout = Timeout::After(Duration::from_secs(10));
#[derive(Debug, Clone, Copy, Default)]
pub struct RttOptions {
pub channel: u32,
pub search_addr: Option<u64>,
pub search_size: Option<u64>,
}
pub(crate) fn debug_body(net_record: &Value, extra: Value) -> Value {
let mut obj = serde_json::Map::new();
obj.insert("net".to_string(), net_record.clone());
if let Value::Object(map) = extra {
obj.extend(map);
}
Value::Object(obj)
}
pub(crate) mod ops {
use super::*;
pub(crate) fn connect(net: &Value, opts: &ConnectOptions) -> (String, Value, Timeout) {
let mut extra = json!({
"force": opts.force,
"halt": opts.halt,
"gdb": opts.gdb,
});
if let Some(speed) = &opts.speed {
extra["speed"] = json!(speed);
}
("/debug/connect".into(), debug_body(net, extra), CONNECT_TIMEOUT)
}
pub(crate) fn disconnect(net: &Value, keep_running: bool) -> (String, Value, Timeout) {
(
"/debug/disconnect".into(),
debug_body(net, json!({ "keep_jlink_running": keep_running })),
QUICK_TIMEOUT,
)
}
pub(crate) fn reset(net: &Value, halt: bool) -> (String, Value, Timeout) {
(
"/debug/reset".into(),
debug_body(net, json!({ "halt": halt })),
RESET_TIMEOUT,
)
}
pub(crate) fn erase(net: &Value) -> (String, Value, Timeout) {
("/debug/erase".into(), debug_body(net, json!({})), ERASE_TIMEOUT)
}
pub(crate) fn read_memory(net: &Value, address: u64, length: usize) -> (String, Value, Timeout) {
(
"/debug/memrd".into(),
debug_body(net, json!({ "start_addr": address, "length": length })),
MEMRD_TIMEOUT,
)
}
pub(crate) fn info(net: &Value) -> (String, Value, Timeout) {
("/debug/info".into(), debug_body(net, json!({})), QUICK_TIMEOUT)
}
pub(crate) fn status(net: &Value) -> (String, Value, Timeout) {
("/debug/status".into(), debug_body(net, json!({})), QUICK_TIMEOUT)
}
pub(crate) fn flash(
net: &Value,
contents: &[u8],
kind: FirmwareKind,
address: Option<u32>,
) -> (String, Value, Timeout) {
let b64 = base64_encode(contents);
let payload = match kind {
FirmwareKind::Hex => json!({ "hexfile": { "content": b64 } }),
FirmwareKind::Elf => json!({ "elffile": { "content": b64 } }),
FirmwareKind::Bin => json!({
"binfile": { "content": b64, "address": address.unwrap_or(0x0800_0000) }
}),
};
("/debug/flash".into(), debug_body(net, payload), FLASH_TIMEOUT)
}
#[cfg(feature = "blocking")]
pub(crate) fn rtt_body(net: &Value, opts: &RttOptions) -> Value {
let mut extra = json!({ "channel": opts.channel, "timeout": Value::Null });
if let Some(a) = opts.search_addr {
extra["search_addr"] = json!(a);
}
if let Some(s) = opts.search_size {
extra["search_size"] = json!(s);
}
debug_body(net, extra)
}
}
pub(crate) fn find_debug_record(records: Vec<Value>, name: &str) -> Result<Value> {
let mut wrong_role = false;
for rec in records {
if rec.get("name").and_then(Value::as_str) == Some(name) {
if rec.get("role").and_then(Value::as_str) == Some("debug") {
return Ok(rec);
}
wrong_role = true;
}
}
Err(Error::Box {
status: 404,
message: if wrong_role {
format!("net '{name}' exists but is not a debug net")
} else {
format!("debug net '{name}' not found on this box")
},
})
}
pub(crate) fn read_firmware(path: &Path) -> Result<(Vec<u8>, FirmwareKind)> {
let kind = FirmwareKind::from_path(path)?;
let bytes = std::fs::read(path)
.map_err(|e| Error::Config(format!("cannot read firmware '{}': {e}", path.display())))?;
Ok((bytes, kind))
}
#[cfg(feature = "blocking")]
#[derive(Clone)]
pub struct DebugNet<'a> {
pub(crate) client: &'a crate::client::LagerBox,
pub(crate) name: String,
}
#[cfg(feature = "blocking")]
impl DebugNet<'_> {
pub fn name(&self) -> &str {
&self.name
}
fn net_record(&self) -> Result<Value> {
self.client.debug_net_record(&self.name)
}
fn call(&self, path: &str, body: Value, timeout: Timeout) -> Result<Value> {
let req = wire::debug_request(path, body, timeout);
let (status, resp) = self.client.execute_debug(&req)?;
wire::parse_debug(status, resp)
}
pub fn connect(&self) -> Result<DebugConnection> {
self.connect_with(&ConnectOptions::default())
}
pub fn connect_with(&self, opts: &ConnectOptions) -> Result<DebugConnection> {
let net = self.net_record()?;
let (path, body, timeout) = ops::connect(&net, opts);
let resp = self.call(&path, body, timeout)?;
serde_json::from_value(resp).map_err(Into::into)
}
pub fn disconnect(&self, keep_running: bool) -> Result<()> {
let net = self.net_record()?;
let (path, body, timeout) = ops::disconnect(&net, keep_running);
self.call(&path, body, timeout).map(|_| ())
}
pub fn reset(&self, halt: bool) -> Result<()> {
let net = self.net_record()?;
let (path, body, timeout) = ops::reset(&net, halt);
self.call(&path, body, timeout).map(|_| ())
}
pub fn erase(&self) -> Result<()> {
let net = self.net_record()?;
let (path, body, timeout) = ops::erase(&net);
self.call(&path, body, timeout).map(|_| ())
}
pub fn flash(&self, firmware_path: impl AsRef<Path>) -> Result<()> {
let path = firmware_path.as_ref();
let (contents, kind) = read_firmware(path)?;
self.flash_bytes(&contents, kind, None)
}
pub fn flash_bin(&self, firmware_path: impl AsRef<Path>, address: u32) -> Result<()> {
let contents = std::fs::read(firmware_path.as_ref())
.map_err(|e| Error::Config(format!("cannot read firmware: {e}")))?;
self.flash_bytes(&contents, FirmwareKind::Bin, Some(address))
}
pub fn flash_bytes(
&self,
contents: &[u8],
kind: FirmwareKind,
address: Option<u32>,
) -> Result<()> {
let net = self.net_record()?;
let (path, body, timeout) = ops::flash(&net, contents, kind, address);
self.call(&path, body, timeout).map(|_| ())
}
pub fn read_memory(&self, address: u64, length: usize) -> Result<Vec<u8>> {
let net = self.net_record()?;
let (path, body, timeout) = ops::read_memory(&net, address, length);
let resp = self.call(&path, body, timeout)?;
wire::debug_memory_bytes(&resp)
}
pub fn info(&self) -> Result<DebugInfo> {
let net = self.net_record()?;
let (path, body, timeout) = ops::info(&net);
let resp = self.call(&path, body, timeout)?;
serde_json::from_value(resp).map_err(Into::into)
}
pub fn status(&self) -> Result<DebugStatus> {
let net = self.net_record()?;
let (path, body, timeout) = ops::status(&net);
let resp = self.call(&path, body, timeout)?;
serde_json::from_value(resp).map_err(Into::into)
}
pub fn rtt(&self) -> Result<RttStream> {
self.rtt_with(&RttOptions::default())
}
pub fn rtt_with(&self, opts: &RttOptions) -> Result<RttStream> {
let net = self.net_record()?;
let body = ops::rtt_body(&net, opts);
let req = wire::debug_request("/debug/rtt", body, Timeout::Unbounded);
let reader = self.client.stream_debug(&req)?;
Ok(RttStream { reader })
}
}
#[cfg(feature = "blocking")]
pub struct RttStream {
reader: Box<dyn std::io::Read + Send + Sync>,
}
#[cfg(feature = "blocking")]
impl std::io::Read for RttStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.reader.read(buf)
}
}
#[cfg(feature = "async")]
#[derive(Clone)]
pub struct AsyncDebugNet<'a> {
pub(crate) client: &'a crate::async_client::AsyncLagerBox,
pub(crate) name: String,
}
#[cfg(feature = "async")]
impl AsyncDebugNet<'_> {
pub fn name(&self) -> &str {
&self.name
}
async fn net_record(&self) -> Result<Value> {
self.client.debug_net_record(&self.name).await
}
async fn call(&self, path: &str, body: Value, timeout: Timeout) -> Result<Value> {
let req = wire::debug_request(path, body, timeout);
let (status, resp) = self.client.execute_debug(&req).await?;
wire::parse_debug(status, resp)
}
pub async fn connect(&self) -> Result<DebugConnection> {
self.connect_with(&ConnectOptions::default()).await
}
pub async fn connect_with(&self, opts: &ConnectOptions) -> Result<DebugConnection> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::connect(&net, opts);
let resp = self.call(&path, body, timeout).await?;
serde_json::from_value(resp).map_err(Into::into)
}
pub async fn disconnect(&self, keep_running: bool) -> Result<()> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::disconnect(&net, keep_running);
self.call(&path, body, timeout).await.map(|_| ())
}
pub async fn reset(&self, halt: bool) -> Result<()> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::reset(&net, halt);
self.call(&path, body, timeout).await.map(|_| ())
}
pub async fn erase(&self) -> Result<()> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::erase(&net);
self.call(&path, body, timeout).await.map(|_| ())
}
pub async fn flash(&self, firmware_path: impl AsRef<Path>) -> Result<()> {
let (contents, kind) = read_firmware(firmware_path.as_ref())?;
self.flash_bytes(&contents, kind, None).await
}
pub async fn flash_bin(&self, firmware_path: impl AsRef<Path>, address: u32) -> Result<()> {
let contents = std::fs::read(firmware_path.as_ref())
.map_err(|e| Error::Config(format!("cannot read firmware: {e}")))?;
self.flash_bytes(&contents, FirmwareKind::Bin, Some(address)).await
}
pub async fn flash_bytes(
&self,
contents: &[u8],
kind: FirmwareKind,
address: Option<u32>,
) -> Result<()> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::flash(&net, contents, kind, address);
self.call(&path, body, timeout).await.map(|_| ())
}
pub async fn read_memory(&self, address: u64, length: usize) -> Result<Vec<u8>> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::read_memory(&net, address, length);
let resp = self.call(&path, body, timeout).await?;
wire::debug_memory_bytes(&resp)
}
pub async fn info(&self) -> Result<DebugInfo> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::info(&net);
let resp = self.call(&path, body, timeout).await?;
serde_json::from_value(resp).map_err(Into::into)
}
pub async fn status(&self) -> Result<DebugStatus> {
let net = self.net_record().await?;
let (path, body, timeout) = ops::status(&net);
let resp = self.call(&path, body, timeout).await?;
serde_json::from_value(resp).map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base64_matches_reference() {
assert_eq!(base64_encode(b""), "");
assert_eq!(base64_encode(b"f"), "Zg==");
assert_eq!(base64_encode(b"fo"), "Zm8=");
assert_eq!(base64_encode(b"foo"), "Zm9v");
assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
assert_eq!(base64_encode(&[0x00, 0xff, 0x10]), "AP8Q");
}
#[test]
fn firmware_kind_from_extension() {
assert_eq!(
FirmwareKind::from_path(Path::new("a/b/fw.hex")).unwrap(),
FirmwareKind::Hex
);
assert_eq!(
FirmwareKind::from_path(Path::new("FW.ELF")).unwrap(),
FirmwareKind::Elf
);
assert!(FirmwareKind::from_path(Path::new("fw.txt")).is_err());
}
#[test]
fn debug_body_wraps_net_and_params() {
let net = json!({"name": "debug1", "role": "debug", "pin": "nrf52"});
let body = debug_body(&net, json!({"halt": true}));
assert_eq!(body["net"]["name"], "debug1");
assert_eq!(body["halt"], true);
}
}