Compare commits
2 Commits
1f26a91dcd
...
7c43e0f7ab
Author | SHA1 | Date | |
---|---|---|---|
7c43e0f7ab
|
|||
4825b699a6
|
@@ -2,37 +2,24 @@ pub mod devices;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod ptp;
|
pub mod ptp;
|
||||||
|
|
||||||
use std::{cmp::min, io::Cursor, time::Duration};
|
use std::{io::Cursor, time::Duration};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use devices::SupportedCamera;
|
use devices::SupportedCamera;
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error};
|
||||||
use ptp::{
|
use ptp::{
|
||||||
enums::{CommandCode, ContainerType, PropCode, ResponseCode, UsbMode},
|
Ptp,
|
||||||
structs::{ContainerInfo, DeviceInfo},
|
enums::{CommandCode, PropCode, UsbMode},
|
||||||
|
structs::DeviceInfo,
|
||||||
};
|
};
|
||||||
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
||||||
|
|
||||||
const SESSION: u32 = 1;
|
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<GlobalContext>,
|
|
||||||
transaction_id: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
pub r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
||||||
usb: Usb,
|
ptp: Ptp,
|
||||||
pub ptp: Ptp,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Camera {
|
impl Camera {
|
||||||
@@ -42,9 +29,9 @@ impl Camera {
|
|||||||
let bus = device.bus_number();
|
let bus = device.bus_number();
|
||||||
let address = device.address();
|
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()
|
.interfaces()
|
||||||
.flat_map(|i| i.descriptors())
|
.flat_map(|i| i.descriptors())
|
||||||
.find(|x| x.class_code() == LIBUSB_CLASS_IMAGE)
|
.find(|x| x.class_code() == LIBUSB_CLASS_IMAGE)
|
||||||
@@ -53,12 +40,6 @@ impl Camera {
|
|||||||
let interface = interface_descriptor.interface_number();
|
let interface = interface_descriptor.interface_number();
|
||||||
debug!("Found interface {interface}");
|
debug!("Found interface {interface}");
|
||||||
|
|
||||||
let usb = Usb {
|
|
||||||
bus,
|
|
||||||
address,
|
|
||||||
interface,
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle = device.open()?;
|
let handle = device.open()?;
|
||||||
handle.claim_interface(interface)?;
|
handle.claim_interface(interface)?;
|
||||||
|
|
||||||
@@ -72,20 +53,27 @@ impl Camera {
|
|||||||
rusb::Direction::Out,
|
rusb::Direction::Out,
|
||||||
rusb::TransferType::Bulk,
|
rusb::TransferType::Bulk,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let transaction_id = 0;
|
let transaction_id = 0;
|
||||||
|
|
||||||
|
let chunk_size = r#impl.chunk_size();
|
||||||
|
|
||||||
let mut ptp = Ptp {
|
let mut ptp = Ptp {
|
||||||
|
bus,
|
||||||
|
address,
|
||||||
|
interface,
|
||||||
bulk_in,
|
bulk_in,
|
||||||
bulk_out,
|
bulk_out,
|
||||||
handle,
|
handle,
|
||||||
transaction_id,
|
transaction_id,
|
||||||
|
chunk_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Opening session");
|
debug!("Opening session");
|
||||||
let () = r#impl.open_session(&mut ptp, SESSION)?;
|
let () = r#impl.open_session(&mut ptp, SESSION)?;
|
||||||
debug!("Session opened");
|
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 {
|
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<u32> {
|
fn prop_value_as_scalar(data: &[u8]) -> anyhow::Result<u32> {
|
||||||
@@ -187,200 +175,41 @@ impl Drop for Camera {
|
|||||||
error!("Error closing session: {e}");
|
error!("Error closing session: {e}");
|
||||||
}
|
}
|
||||||
debug!("Session closed");
|
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<P: rusb::UsbContext> {
|
pub trait CameraImpl<P: rusb::UsbContext> {
|
||||||
fn supported_camera(&self) -> &'static SupportedCamera<P>;
|
fn supported_camera(&self) -> &'static SupportedCamera<P>;
|
||||||
|
|
||||||
fn timeout(&self) -> Option<Duration> {
|
fn timeout(&self) -> Duration {
|
||||||
None
|
Duration::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chunk_size(&self) -> usize {
|
fn chunk_size(&self) -> usize {
|
||||||
1024 * 1024
|
1024 * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(
|
|
||||||
&self,
|
|
||||||
ptp: &mut Ptp,
|
|
||||||
code: CommandCode,
|
|
||||||
params: Option<&[u32]>,
|
|
||||||
data: Option<&[u8]>,
|
|
||||||
transaction: bool,
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
|
||||||
let transaction_id = if transaction {
|
|
||||||
Some(ptp.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::<LittleEndian>(*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);
|
|
||||||
let code = ResponseCode::try_from(container.code)?;
|
|
||||||
if code != ResponseCode::Ok {
|
|
||||||
bail!(ptp::error::Error::Response(container.code));
|
|
||||||
}
|
|
||||||
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],
|
|
||||||
// Fuji, for the love of God don't ever write code again.
|
|
||||||
transaction_id: Option<u32>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
// Look at what you made me do. Fuck.
|
|
||||||
let header_len = ContainerInfo::SIZE
|
|
||||||
- if transaction_id.is_none() {
|
|
||||||
size_of::<u32>()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_chunk_len = min(payload.len(), self.chunk_size() - header_len);
|
|
||||||
let total_len = u32::try_from(payload.len() + header_len)?;
|
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity(first_chunk_len + header_len);
|
|
||||||
buffer.write_u32::<LittleEndian>(total_len)?;
|
|
||||||
buffer.write_u16::<LittleEndian>(kind as u16)?;
|
|
||||||
buffer.write_u16::<LittleEndian>(code as u16)?;
|
|
||||||
if let Some(transaction_id) = transaction_id {
|
|
||||||
buffer.write_u32::<LittleEndian>(transaction_id)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<u8>)> {
|
|
||||||
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::parse(buf)?;
|
|
||||||
if container_info.payload_len == 0 {
|
|
||||||
trace!("No payload in container");
|
|
||||||
return Ok((container_info, Vec::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let payload_len = container_info.payload_len as usize;
|
|
||||||
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<()> {
|
fn open_session(&self, ptp: &mut Ptp, session_id: u32) -> anyhow::Result<()> {
|
||||||
debug!("Sending OpenSession command");
|
debug!("Sending OpenSession command");
|
||||||
_ = self.send(
|
_ = ptp.send(
|
||||||
ptp,
|
|
||||||
CommandCode::OpenSession,
|
CommandCode::OpenSession,
|
||||||
Some(&[session_id]),
|
Some(&[session_id]),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> {
|
fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> {
|
||||||
debug!("Sending CloseSession command");
|
debug!("Sending CloseSession command");
|
||||||
_ = self.send(ptp, CommandCode::CloseSession, None, None, true)?;
|
_ = ptp.send(CommandCode::CloseSession, None, None, true, self.timeout())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
||||||
debug!("Sending GetDeviceInfo command");
|
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());
|
debug!("Received response with {} bytes", response.len());
|
||||||
let info = DeviceInfo::try_from(response.as_slice())?;
|
let info = DeviceInfo::try_from(response.as_slice())?;
|
||||||
Ok(info)
|
Ok(info)
|
||||||
@@ -388,12 +217,12 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
|
|
||||||
fn get_prop_value(&self, ptp: &mut Ptp, prop: PropCode) -> anyhow::Result<Vec<u8>> {
|
fn get_prop_value(&self, ptp: &mut Ptp, prop: PropCode) -> anyhow::Result<Vec<u8>> {
|
||||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||||
let response = self.send(
|
let response = ptp.send(
|
||||||
ptp,
|
|
||||||
CommandCode::GetDevicePropValue,
|
CommandCode::GetDevicePropValue,
|
||||||
Some(&[prop as u32]),
|
Some(&[prop as u32]),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
Ok(response)
|
Ok(response)
|
||||||
@@ -403,11 +232,23 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
const HANDLE: u32 = 0x0;
|
const HANDLE: u32 = 0x0;
|
||||||
|
|
||||||
debug!("Sending GetObjectInfo command for backup");
|
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!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
debug!("Sending GetObject command for backup");
|
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());
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
@@ -415,30 +256,33 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
|
|
||||||
fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> {
|
fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> {
|
||||||
debug!("Preparing ObjectInfo header for backup");
|
debug!("Preparing ObjectInfo header for backup");
|
||||||
let mut obj_info = vec![0u8; 1088];
|
|
||||||
let mut cursor = Cursor::new(&mut obj_info[..]);
|
let mut header1 = vec![0u8; 1012];
|
||||||
|
let mut cursor = Cursor::new(&mut header1[..]);
|
||||||
cursor.write_u32::<LittleEndian>(0x0)?;
|
cursor.write_u32::<LittleEndian>(0x0)?;
|
||||||
cursor.write_u16::<LittleEndian>(0x5000)?;
|
cursor.write_u16::<LittleEndian>(0x5000)?;
|
||||||
cursor.write_u16::<LittleEndian>(0x0)?;
|
cursor.write_u16::<LittleEndian>(0x0)?;
|
||||||
cursor.write_u32::<LittleEndian>(u32::try_from(buffer.len())?)?;
|
cursor.write_u32::<LittleEndian>(u32::try_from(buffer.len())?)?;
|
||||||
|
|
||||||
|
let header2 = vec![0u8; 64];
|
||||||
|
|
||||||
debug!("Sending SendObjectInfo command for backup");
|
debug!("Sending SendObjectInfo command for backup");
|
||||||
let response = self.send(
|
let response = ptp.send_many(
|
||||||
ptp,
|
|
||||||
CommandCode::SendObjectInfo,
|
CommandCode::SendObjectInfo,
|
||||||
Some(&[0x0, 0x0]),
|
Some(&[0x0, 0x0]),
|
||||||
Some(&obj_info),
|
Some(&[&header1, &header2]),
|
||||||
true,
|
true,
|
||||||
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
debug!("Sending SendObject command for backup");
|
debug!("Sending SendObject command for backup");
|
||||||
let response = self.send(
|
let response = ptp.send(
|
||||||
ptp,
|
|
||||||
CommandCode::SendObject,
|
CommandCode::SendObject,
|
||||||
Some(&[0x0]),
|
Some(&[0x0]),
|
||||||
Some(buffer),
|
Some(buffer),
|
||||||
false,
|
true,
|
||||||
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ impl TryFrom<u16> for CommandCode {
|
|||||||
0x100D => Ok(Self::SendObject),
|
0x100D => Ok(Self::SendObject),
|
||||||
0x1015 => Ok(Self::GetDevicePropValue),
|
0x1015 => Ok(Self::GetDevicePropValue),
|
||||||
0x1016 => Ok(Self::SetDevicePropValue),
|
0x1016 => Ok(Self::SetDevicePropValue),
|
||||||
v => bail!("Unknown command code '{v}'"),
|
v => bail!("Unknown command code '{v:x?}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,11 +112,43 @@ impl std::convert::TryFrom<u16> for ResponseCode {
|
|||||||
0x201E => Ok(Self::SessionAlreadyOpen),
|
0x201E => Ok(Self::SessionAlreadyOpen),
|
||||||
0x201F => Ok(Self::TransactionCancelled),
|
0x201F => Ok(Self::TransactionCancelled),
|
||||||
0x2020 => Ok(Self::SpecificationOfDestinationUnsupported),
|
0x2020 => Ok(Self::SpecificationOfDestinationUnsupported),
|
||||||
v => bail!("Unknown response code '{v}'"),
|
v => bail!("Unknown response code '{v:x?}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ContainerCode {
|
||||||
|
Command(CommandCode),
|
||||||
|
Response(ResponseCode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContainerCode> for u16 {
|
||||||
|
fn from(code: ContainerCode) -> Self {
|
||||||
|
match code {
|
||||||
|
ContainerCode::Command(cmd) => cmd as Self,
|
||||||
|
ContainerCode::Response(resp) => resp as Self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for ContainerCode {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
if let Ok(cmd) = CommandCode::try_from(value) {
|
||||||
|
return Ok(Self::Command(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(resp) = ResponseCode::try_from(value) {
|
||||||
|
return Ok(Self::Response(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown container code '{value:x?}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum PropCode {
|
pub enum PropCode {
|
||||||
|
@@ -2,3 +2,273 @@ pub mod enums;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod structs;
|
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<GlobalContext>,
|
||||||
|
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<Vec<u8>> {
|
||||||
|
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<Vec<u8>> {
|
||||||
|
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<u32>) {
|
||||||
|
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<u32>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut payload = Vec::with_capacity(params.len() * 4);
|
||||||
|
for p in params {
|
||||||
|
payload.write_u32::<LittleEndian>(*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<Vec<u8>> {
|
||||||
|
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<u32>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let container_info = ContainerInfo::new(kind, code, transaction_id, payload.len())?;
|
||||||
|
let mut buffer: Vec<u8> = 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<u32>,
|
||||||
|
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<u8> = 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<u8>)> {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
use super::{enums::ContainerType, read::Read};
|
use super::{
|
||||||
|
enums::{CommandCode, ContainerCode, ContainerType},
|
||||||
|
read::Read,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -50,28 +53,81 @@ impl TryFrom<&[u8]> for DeviceInfo {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ContainerInfo {
|
pub struct ContainerInfo {
|
||||||
|
pub total_len: u32,
|
||||||
pub kind: ContainerType,
|
pub kind: ContainerType,
|
||||||
pub payload_len: u32,
|
pub code: ContainerCode,
|
||||||
pub code: u16,
|
pub transaction_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerInfo {
|
impl ContainerInfo {
|
||||||
pub const SIZE: usize =
|
const BASE_SIZE: usize = size_of::<u32>() + size_of::<u16>() + size_of::<u16>();
|
||||||
size_of::<ContainerType>() + size_of::<u32>() + size_of::<u16>() + size_of::<u32>();
|
pub const SIZE: usize = Self::BASE_SIZE + size_of::<u32>();
|
||||||
}
|
|
||||||
|
|
||||||
impl ContainerInfo {
|
pub fn new(
|
||||||
pub fn parse<R: ReadBytesExt>(mut r: R) -> anyhow::Result<Self> {
|
kind: ContainerType,
|
||||||
let payload_len = r.read_u32::<LittleEndian>()? - Self::SIZE as u32;
|
code: CommandCode,
|
||||||
let kind = r.read_u16::<LittleEndian>()?;
|
transaction_id: Option<u32>,
|
||||||
let kind = ContainerType::try_from(kind)?;
|
payload_len: usize,
|
||||||
let code = r.read_u16::<LittleEndian>()?;
|
) -> anyhow::Result<Self> {
|
||||||
let _transaction_id = r.read_u32::<LittleEndian>()?;
|
let mut total_len = if transaction_id.is_some() {
|
||||||
|
Self::SIZE
|
||||||
|
} else {
|
||||||
|
Self::BASE_SIZE
|
||||||
|
};
|
||||||
|
total_len += payload_len;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
total_len: u32::try_from(total_len)?,
|
||||||
|
kind,
|
||||||
|
code: ContainerCode::Command(code),
|
||||||
|
transaction_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn len(&self) -> usize {
|
||||||
|
if self.transaction_id.is_some() {
|
||||||
|
Self::SIZE
|
||||||
|
} else {
|
||||||
|
Self::BASE_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn payload_len(&self) -> usize {
|
||||||
|
self.total_len as usize - self.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for ContainerInfo {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
let mut r = Cursor::new(bytes);
|
||||||
|
|
||||||
|
let total_len = r.read_u32::<LittleEndian>()?;
|
||||||
|
let kind = ContainerType::try_from(r.read_u16::<LittleEndian>()?)?;
|
||||||
|
let code = ContainerCode::try_from(r.read_u16::<LittleEndian>()?)?;
|
||||||
|
let transaction_id = Some(r.read_u32::<LittleEndian>()?);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
total_len,
|
||||||
kind,
|
kind,
|
||||||
payload_len,
|
|
||||||
code,
|
code,
|
||||||
|
transaction_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ContainerInfo> for Vec<u8> {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(val: ContainerInfo) -> Result<Self, Self::Error> {
|
||||||
|
let mut buf = Self::with_capacity(val.len());
|
||||||
|
buf.write_u32::<LittleEndian>(val.total_len)?;
|
||||||
|
buf.write_u16::<LittleEndian>(val.kind as u16)?;
|
||||||
|
buf.write_u16::<LittleEndian>(val.code.into())?;
|
||||||
|
if let Some(transaction_id) = val.transaction_id {
|
||||||
|
buf.write_u32::<LittleEndian>(transaction_id)?;
|
||||||
|
}
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user