From 7c43e0f7ab95f647d4b6951870b66c97ba0bf269 Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Thu, 16 Oct 2025 15:22:22 +0100 Subject: [PATCH] feat: backup restore Signed-off-by: Nikolaos Karaolidis --- src/camera/mod.rs | 250 ++++++++--------------------------- src/camera/ptp/mod.rs | 270 ++++++++++++++++++++++++++++++++++++++ src/camera/ptp/structs.rs | 4 +- 3 files changed, 327 insertions(+), 197 deletions(-) diff --git a/src/camera/mod.rs b/src/camera/mod.rs index a4638be..b430680 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -2,37 +2,24 @@ pub mod devices; pub mod error; pub mod ptp; -use std::{cmp::min, io::Cursor, time::Duration}; +use std::{io::Cursor, time::Duration}; use anyhow::{anyhow, bail}; use byteorder::{LittleEndian, WriteBytesExt}; use devices::SupportedCamera; -use log::{debug, error, trace}; +use log::{debug, error}; use ptp::{ - enums::{CommandCode, ContainerCode, ContainerType, PropCode, ResponseCode, UsbMode}, - structs::{ContainerInfo, DeviceInfo}, + Ptp, + enums::{CommandCode, PropCode, UsbMode}, + structs::DeviceInfo, }; use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE}; const SESSION: u32 = 1; -pub struct Usb { - bus: u8, - address: u8, - interface: u8, -} - -pub struct Ptp { - bulk_in: u8, - bulk_out: u8, - handle: rusb::DeviceHandle, - transaction_id: u32, -} - pub struct Camera { - pub r#impl: Box>, - usb: Usb, - pub ptp: Ptp, + r#impl: Box>, + ptp: Ptp, } impl Camera { @@ -42,9 +29,9 @@ impl Camera { let bus = device.bus_number(); let address = device.address(); - let config_desc = device.active_config_descriptor()?; + let config_descriptor = device.active_config_descriptor()?; - let interface_descriptor = config_desc + let interface_descriptor = config_descriptor .interfaces() .flat_map(|i| i.descriptors()) .find(|x| x.class_code() == LIBUSB_CLASS_IMAGE) @@ -53,12 +40,6 @@ impl Camera { let interface = interface_descriptor.interface_number(); debug!("Found interface {interface}"); - let usb = Usb { - bus, - address, - interface, - }; - let handle = device.open()?; handle.claim_interface(interface)?; @@ -72,20 +53,27 @@ impl Camera { rusb::Direction::Out, rusb::TransferType::Bulk, )?; + let transaction_id = 0; + let chunk_size = r#impl.chunk_size(); + let mut ptp = Ptp { + bus, + address, + interface, bulk_in, bulk_out, handle, transaction_id, + chunk_size, }; debug!("Opening session"); let () = r#impl.open_session(&mut ptp, SESSION)?; debug!("Session opened"); - return Ok(Self { r#impl, usb, ptp }); + return Ok(Self { r#impl, ptp }); } } @@ -117,7 +105,7 @@ impl Camera { } pub fn connected_usb_id(&self) -> String { - format!("{}.{}", self.usb.bus, self.usb.address) + format!("{}.{}", self.ptp.bus, self.ptp.address) } fn prop_value_as_scalar(data: &[u8]) -> anyhow::Result { @@ -187,190 +175,41 @@ impl Drop for Camera { error!("Error closing session: {e}"); } debug!("Session closed"); - - debug!("Releasing interface"); - if let Err(e) = self.ptp.handle.release_interface(self.usb.interface) { - error!("Error releasing interface: {e}"); - } - debug!("Interface released"); } } pub trait CameraImpl { fn supported_camera(&self) -> &'static SupportedCamera

; - fn timeout(&self) -> Option { - None + fn timeout(&self) -> Duration { + Duration::default() } fn chunk_size(&self) -> usize { 1024 * 1024 } - fn send( - &self, - ptp: &mut Ptp, - code: CommandCode, - params: Option<&[u32]>, - data: Option<&[u8]>, - transaction: bool, - ) -> anyhow::Result> { - let transaction_id = if transaction { - let transaction_id = Some(ptp.transaction_id); - ptp.transaction_id += 1; - transaction_id - } else { - None - }; - - let params = params.unwrap_or_default(); - - let mut payload = Vec::with_capacity(params.len() * 4); - for p in params { - payload.write_u32::(*p).ok(); - } - - trace!( - "Sending PTP command: {:?}, transaction: {:?}, parameters ({} bytes): {:x?}", - code, - transaction_id, - payload.len(), - payload, - ); - self.write(ptp, ContainerType::Command, code, &payload, transaction_id)?; - - if let Some(data) = data { - trace!("Sending PTP data: {} bytes", data.len()); - self.write(ptp, ContainerType::Data, code, data, transaction_id)?; - } - - let mut data_payload = Vec::new(); - loop { - let (container, payload) = self.read(ptp)?; - match container.kind { - ContainerType::Data => { - trace!("Data received: {} bytes", payload.len()); - data_payload = payload; - } - ContainerType::Response => { - trace!("Response received: code {:?}", container.code); - match container.code { - ContainerCode::Command(_) | ContainerCode::Response(ResponseCode::Ok) => {} - ContainerCode::Response(code) => { - bail!(ptp::error::Error::Response(code as u16)); - } - } - - trace!( - "Command {:?} completed successfully with data payload of {} bytes", - code, - data_payload.len(), - ); - return Ok(data_payload); - } - _ => { - debug!("Ignoring unexpected container type: {:?}", container.kind); - } - } - } - } - - fn write( - &self, - ptp: &mut Ptp, - kind: ContainerType, - code: CommandCode, - payload: &[u8], - transaction_id: Option, - ) -> anyhow::Result<()> { - let container_info = ContainerInfo::new(kind, code, transaction_id, payload)?; - let first_chunk_len = min(payload.len(), self.chunk_size() - container_info.len()); - - let mut buffer: Vec = container_info.try_into()?; - buffer.extend_from_slice(&payload[..first_chunk_len]); - - trace!( - "Writing PTP {kind:?} container, code: {code:?}, transaction: {transaction_id:?}, first_chunk: {first_chunk_len} bytes", - ); - - let timeout = self.timeout().unwrap_or_default(); - ptp.handle.write_bulk(ptp.bulk_out, &buffer, timeout)?; - - for chunk in payload[first_chunk_len..].chunks(self.chunk_size()) { - trace!("Writing additional chunk ({} bytes)", chunk.len(),); - ptp.handle.write_bulk(ptp.bulk_out, chunk, timeout)?; - } - - trace!( - "Write completed for code {:?}, total payload of {} bytes", - code, - payload.len() - ); - Ok(()) - } - - fn read(&self, ptp: &mut Ptp) -> anyhow::Result<(ContainerInfo, Vec)> { - let timeout = self.timeout().unwrap_or_default(); - - let mut stack_buf = [0u8; 8 * 1024]; - let n = ptp.handle.read_bulk(ptp.bulk_in, &mut stack_buf, timeout)?; - let buf = &stack_buf[..n]; - - trace!("Read {n} bytes from bulk_in"); - - let container_info = ContainerInfo::try_from(buf)?; - - let payload_len = container_info.payload_len(); - if payload_len == 0 { - trace!("No payload in container"); - return Ok((container_info, Vec::new())); - } - - let mut payload = Vec::with_capacity(payload_len); - if buf.len() > ContainerInfo::SIZE { - payload.extend_from_slice(&buf[ContainerInfo::SIZE..]); - } - - while payload.len() < payload_len { - let remaining = payload_len - payload.len(); - let mut chunk = vec![0u8; min(remaining, self.chunk_size())]; - let n = ptp.handle.read_bulk(ptp.bulk_in, &mut chunk, timeout)?; - trace!("Read additional chunk ({n} bytes)"); - if n == 0 { - break; - } - payload.extend_from_slice(&chunk[..n]); - } - - trace!( - "Finished reading container, total payload of {} bytes", - payload.len(), - ); - - Ok((container_info, payload)) - } - fn open_session(&self, ptp: &mut Ptp, session_id: u32) -> anyhow::Result<()> { debug!("Sending OpenSession command"); - _ = self.send( - ptp, + _ = ptp.send( CommandCode::OpenSession, Some(&[session_id]), None, true, + self.timeout(), )?; Ok(()) } fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> { debug!("Sending CloseSession command"); - _ = self.send(ptp, CommandCode::CloseSession, None, None, true)?; + _ = ptp.send(CommandCode::CloseSession, None, None, true, self.timeout())?; Ok(()) } fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result { debug!("Sending GetDeviceInfo command"); - let response = self.send(ptp, CommandCode::GetDeviceInfo, None, None, true)?; + let response = ptp.send(CommandCode::GetDeviceInfo, None, None, true, self.timeout())?; debug!("Received response with {} bytes", response.len()); let info = DeviceInfo::try_from(response.as_slice())?; Ok(info) @@ -378,12 +217,12 @@ pub trait CameraImpl { fn get_prop_value(&self, ptp: &mut Ptp, prop: PropCode) -> anyhow::Result> { debug!("Sending GetDevicePropValue command for property {prop:?}"); - let response = self.send( - ptp, + let response = ptp.send( CommandCode::GetDevicePropValue, Some(&[prop as u32]), None, true, + self.timeout(), )?; debug!("Received response with {} bytes", response.len()); Ok(response) @@ -393,11 +232,23 @@ pub trait CameraImpl { const HANDLE: u32 = 0x0; debug!("Sending GetObjectInfo command for backup"); - let response = self.send(ptp, CommandCode::GetObjectInfo, Some(&[HANDLE]), None, true)?; + let response = ptp.send( + CommandCode::GetObjectInfo, + Some(&[HANDLE]), + None, + true, + self.timeout(), + )?; debug!("Received response with {} bytes", response.len()); debug!("Sending GetObject command for backup"); - let response = self.send(ptp, CommandCode::GetObject, Some(&[HANDLE]), None, true)?; + let response = ptp.send( + CommandCode::GetObject, + Some(&[HANDLE]), + None, + true, + self.timeout(), + )?; debug!("Received response with {} bytes", response.len()); Ok(response) @@ -405,25 +256,34 @@ pub trait CameraImpl { fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> { debug!("Preparing ObjectInfo header for backup"); - let mut obj_info = vec![0u8; 1012]; - let mut cursor = Cursor::new(&mut obj_info[..]); + + let mut header1 = vec![0u8; 1012]; + let mut cursor = Cursor::new(&mut header1[..]); cursor.write_u32::(0x0)?; cursor.write_u16::(0x5000)?; cursor.write_u16::(0x0)?; cursor.write_u32::(u32::try_from(buffer.len())?)?; + let header2 = vec![0u8; 64]; + debug!("Sending SendObjectInfo command for backup"); - let response = self.send( - ptp, + let response = ptp.send_many( CommandCode::SendObjectInfo, Some(&[0x0, 0x0]), - Some(&obj_info), + Some(&[&header1, &header2]), true, + self.timeout(), )?; debug!("Received response with {} bytes", response.len()); debug!("Sending SendObject command for backup"); - let response = self.send(ptp, CommandCode::SendObject, None, Some(buffer), true)?; + let response = ptp.send( + CommandCode::SendObject, + Some(&[0x0]), + Some(buffer), + true, + self.timeout(), + )?; debug!("Received response with {} bytes", response.len()); Ok(()) diff --git a/src/camera/ptp/mod.rs b/src/camera/ptp/mod.rs index 61172d6..c98769b 100644 --- a/src/camera/ptp/mod.rs +++ b/src/camera/ptp/mod.rs @@ -2,3 +2,273 @@ pub mod enums; pub mod error; pub mod read; pub mod structs; + +use std::{cmp::min, time::Duration}; + +use anyhow::bail; +use byteorder::{LittleEndian, WriteBytesExt}; +use enums::{CommandCode, ContainerCode, ContainerType, ResponseCode}; +use log::{debug, error, trace}; +use rusb::GlobalContext; +use structs::ContainerInfo; + +pub struct Ptp { + pub bus: u8, + pub address: u8, + pub interface: u8, + pub bulk_in: u8, + pub bulk_out: u8, + pub handle: rusb::DeviceHandle, + pub transaction_id: u32, + pub chunk_size: usize, +} + +impl Ptp { + pub fn send( + &mut self, + code: CommandCode, + params: Option<&[u32]>, + data: Option<&[u8]>, + transaction: bool, + timeout: Duration, + ) -> anyhow::Result> { + let (params, transaction_id) = self.prepare_send(params, transaction); + self.send_header(code, params, transaction_id, timeout)?; + if let Some(data) = data { + self.write(ContainerType::Data, code, data, transaction_id, timeout)?; + } + self.receive_response(timeout) + } + + pub fn send_many( + &mut self, + code: CommandCode, + params: Option<&[u32]>, + data: Option<&[&[u8]]>, + transaction: bool, + timeout: Duration, + ) -> anyhow::Result> { + let (params, transaction_id) = self.prepare_send(params, transaction); + self.send_header(code, params, transaction_id, timeout)?; + if let Some(data) = data { + self.write_many(ContainerType::Data, code, data, transaction_id, timeout)?; + } + self.receive_response(timeout) + } + + fn prepare_send<'a>( + &mut self, + params: Option<&'a [u32]>, + transaction: bool, + ) -> (&'a [u32], Option) { + let params = params.unwrap_or_default(); + let transaction_id = if transaction { + let transaction_id = Some(self.transaction_id); + self.transaction_id += 1; + transaction_id + } else { + None + }; + (params, transaction_id) + } + + fn send_header( + &self, + code: CommandCode, + params: &[u32], + transaction_id: Option, + timeout: Duration, + ) -> anyhow::Result<()> { + let mut payload = Vec::with_capacity(params.len() * 4); + for p in params { + payload.write_u32::(*p).ok(); + } + + trace!( + "Sending PTP command: {:?}, transaction: {:?}, parameters ({} bytes): {:x?}", + code, + transaction_id, + payload.len(), + payload, + ); + self.write( + ContainerType::Command, + code, + &payload, + transaction_id, + timeout, + )?; + + Ok(()) + } + + fn receive_response(&self, timeout: Duration) -> anyhow::Result> { + let mut response = Vec::new(); + loop { + let (container, payload) = self.read(timeout)?; + match container.kind { + ContainerType::Data => { + trace!("Response received: data ({} bytes)", payload.len()); + response = payload; + } + ContainerType::Response => { + trace!("Response received: code {:?}", container.code); + match container.code { + ContainerCode::Command(_) | ContainerCode::Response(ResponseCode::Ok) => {} + ContainerCode::Response(code) => { + bail!(error::Error::Response(code as u16)); + } + } + + trace!( + "Command completed successfully, response payload of {} bytes", + response.len(), + ); + return Ok(response); + } + _ => { + debug!("Ignoring unexpected container type: {:?}", container.kind); + } + } + } + } + + fn write( + &self, + kind: ContainerType, + code: CommandCode, + payload: &[u8], + transaction_id: Option, + timeout: Duration, + ) -> anyhow::Result<()> { + let container_info = ContainerInfo::new(kind, code, transaction_id, payload.len())?; + let mut buffer: Vec = container_info.try_into()?; + + let first_chunk_len = min(payload.len(), self.chunk_size - container_info.len()); + buffer.extend_from_slice(&payload[..first_chunk_len]); + + trace!( + "Writing PTP {kind:?} container, code: {code:?}, transaction: {transaction_id:?}, first payload chunk ({first_chunk_len} bytes)", + ); + self.handle.write_bulk(self.bulk_out, &buffer, timeout)?; + + for chunk in payload[first_chunk_len..].chunks(self.chunk_size) { + trace!("Writing additional payload chunk ({} bytes)", chunk.len(),); + self.handle.write_bulk(self.bulk_out, chunk, timeout)?; + } + + trace!( + "Write completed for code {:?}, total payload of {} bytes", + code, + payload.len() + ); + Ok(()) + } + + fn write_many( + &self, + kind: ContainerType, + code: CommandCode, + parts: &[&[u8]], + transaction_id: Option, + timeout: Duration, + ) -> anyhow::Result<()> { + if parts.is_empty() { + return self.write(kind, code, &[], transaction_id, timeout); + } + + if parts.len() == 1 { + return self.write(kind, code, parts[0], transaction_id, timeout); + } + + let total_len: usize = parts.iter().map(|c| c.len()).sum(); + let container_info = ContainerInfo::new(kind, code, transaction_id, total_len)?; + let mut buffer: Vec = container_info.try_into()?; + + let first = parts[0]; + let first_part_chunk_len = min(first.len(), self.chunk_size - container_info.len()); + buffer.extend_from_slice(&first[..first_part_chunk_len]); + + trace!( + "Writing PTP {kind:?} container, code: {code:?}, transaction: {transaction_id:?}, first payload part chunk ({first_part_chunk_len} bytes)", + ); + self.handle.write_bulk(self.bulk_out, &buffer, timeout)?; + + for chunk in first[first_part_chunk_len..].chunks(self.chunk_size) { + trace!( + "Writing additional payload part chunk ({} bytes)", + chunk.len(), + ); + self.handle.write_bulk(self.bulk_out, chunk, timeout)?; + } + + for part in &parts[1..] { + trace!("Writing additional payload part"); + for chunk in part.chunks(self.chunk_size) { + trace!( + "Writing additional payload part chunk ({} bytes)", + chunk.len(), + ); + self.handle.write_bulk(self.bulk_out, chunk, timeout)?; + } + trace!( + "Write completed for part, total payload of {} bytes", + part.len() + ); + } + + trace!("Write completed for code {code:?}, total payload of {total_len} bytes"); + Ok(()) + } + + fn read(&self, timeout: Duration) -> anyhow::Result<(ContainerInfo, Vec)> { + let mut stack_buf = [0u8; 8 * 1024]; + + let n = self + .handle + .read_bulk(self.bulk_in, &mut stack_buf, timeout)?; + let buf = &stack_buf[..n]; + trace!("Read chunk ({n} bytes)"); + + let container_info = ContainerInfo::try_from(buf)?; + + let payload_len = container_info.payload_len(); + if payload_len == 0 { + trace!("No payload in container"); + return Ok((container_info, Vec::new())); + } + + let mut payload = Vec::with_capacity(payload_len); + if buf.len() > ContainerInfo::SIZE { + payload.extend_from_slice(&buf[ContainerInfo::SIZE..]); + } + + while payload.len() < payload_len { + let remaining = payload_len - payload.len(); + let mut chunk = vec![0u8; min(remaining, self.chunk_size)]; + let n = self.handle.read_bulk(self.bulk_in, &mut chunk, timeout)?; + trace!("Read additional chunk ({n} bytes)"); + if n == 0 { + break; + } + payload.extend_from_slice(&chunk[..n]); + } + + trace!( + "Finished reading container, total payload of {} bytes", + payload.len(), + ); + + Ok((container_info, payload)) + } +} + +impl Drop for Ptp { + fn drop(&mut self) { + debug!("Releasing interface"); + if let Err(e) = self.handle.release_interface(self.interface) { + error!("Error releasing interface: {e}"); + } + debug!("Interface released"); + } +} diff --git a/src/camera/ptp/structs.rs b/src/camera/ptp/structs.rs index 35e6adf..20eeaa7 100644 --- a/src/camera/ptp/structs.rs +++ b/src/camera/ptp/structs.rs @@ -67,14 +67,14 @@ impl ContainerInfo { kind: ContainerType, code: CommandCode, transaction_id: Option, - payload: &[u8], + payload_len: usize, ) -> anyhow::Result { let mut total_len = if transaction_id.is_some() { Self::SIZE } else { Self::BASE_SIZE }; - total_len += payload.len(); + total_len += payload_len; Ok(Self { total_len: u32::try_from(total_len)?,