diff --git a/src/camlib.h b/src/camlib.h index d21872f..50b720d 100644 --- a/src/camlib.h +++ b/src/camlib.h @@ -25,7 +25,7 @@ // Logging+panic mechanism, define it yourself or link in log.c void ptp_verbose_log(char *fmt, ...); -void ptp_panic(char *fmt, ...); +__attribute__ ((noreturn)) void ptp_panic(char *fmt, ...); // 4mb recommended default buffer size #define CAMLIB_DEFAULT_SIZE 1000000 diff --git a/src/cl_data.h b/src/cl_data.h index e563f9e..fa89e15 100644 --- a/src/cl_data.h +++ b/src/cl_data.h @@ -83,15 +83,30 @@ struct PtpObjectInfo { char keywords[64]; }; -struct PtpDevPropDesc { +struct PtpEnumerationForm { + uint16_t length; + char data[]; +}; + +struct PtpRangeForm { + int min; + int max; + int step; +}; + +struct PtpPropDesc { uint16_t code; uint16_t data_type; uint8_t read_only; // (get/set) - #define PTP_PROP_DESC_VAR_START 5 + uint32_t default_value32; + uint32_t current_value32; + void *current_value; + void *default_value; - int default_value; - int current_value; + uint8_t form_type; + struct PtpRangeForm range_form; + struct PtpEnumerationForm *enum_form; }; struct PtpObjPropDesc { @@ -202,7 +217,7 @@ int ptp_pack_object_info(struct PtpRuntime *r, struct PtpObjectInfo *oi, uint8_t int ptp_parse_prop_value(struct PtpRuntime *r); int ptp_parse_device_info(struct PtpRuntime *r, struct PtpDeviceInfo *di); int ptp_device_info_json(struct PtpDeviceInfo *di, char *buffer, int max); -int ptp_parse_prop_desc(struct PtpRuntime *r, struct PtpDevPropDesc *oi); +int ptp_parse_prop_desc(struct PtpRuntime *r, struct PtpPropDesc *oi); int ptp_parse_object_info(struct PtpRuntime *r, struct PtpObjectInfo *oi); int ptp_storage_info_json(struct PtpStorageInfo *so, char *buffer, int max); int ptp_object_info_json(struct PtpObjectInfo *so, char *buffer, int max); diff --git a/src/cl_ops.h b/src/cl_ops.h index ce24492..e85046f 100644 --- a/src/cl_ops.h +++ b/src/cl_ops.h @@ -29,7 +29,7 @@ int ptp_send_object_info(struct PtpRuntime *r, int storage_id, int handle, struc int ptp_get_prop_value(struct PtpRuntime *r, int code); int ptp_set_prop_value(struct PtpRuntime *r, int code, int value); int ptp_set_prop_value_data(struct PtpRuntime *r, int code, void *data, int length); -int ptp_get_prop_desc(struct PtpRuntime *r, int code, struct PtpDevPropDesc *pd); +int ptp_get_prop_desc(struct PtpRuntime *r, int code, struct PtpPropDesc *pd); /// @brief Gets a list of object handles in a storage device or folder. // @param id storage ID // @param format Can specify file format ID, or zero for all IDs diff --git a/src/data.c b/src/data.c index 9c3b35c..c8b065b 100644 --- a/src/data.c +++ b/src/data.c @@ -12,6 +12,7 @@ // Custom snprint with offset - for safer string building static int osnprintf(char *str, int cur, int size, const char *format, ...) { if (size - cur < 0) { + ptp_panic("osnprintf overflow %d/%d", cur, size); return 0; } @@ -25,6 +26,8 @@ static int osnprintf(char *str, int cur, int size, const char *format, ...) { } int ptp_get_data_size(void *d, int type) { + uint32_t length32; + uint8_t length8; switch (type) { case PTP_TC_INT8: case PTP_TC_UINT8: @@ -42,11 +45,14 @@ int ptp_get_data_size(void *d, int type) { case PTP_TC_UINT16ARRAY: case PTP_TC_UINT32ARRAY: case PTP_TC_UINT64ARRAY: - return ((uint32_t *)d)[0]; + ptp_read_u32(d, &length32); + return length32; case PTP_TC_STRING: - return ((uint8_t *)d)[0]; + ptp_read_u8(d, &length8); + return length8; } + ptp_panic("Invalid size read"); return 0; } @@ -84,14 +90,80 @@ int ptp_parse_prop_value(struct PtpRuntime *r) { return -1; } -int ptp_parse_prop_desc(struct PtpRuntime *r, struct PtpDevPropDesc *oi) { +static int parse_data_data_or_u32(uint8_t *d, int type, uint32_t *u32, void **data) { + int size; + uint32_t length32; + uint8_t length8; + int len_total; + switch (type) { + case PTP_TC_INT8: + case PTP_TC_UINT8: + case PTP_TC_INT16: + case PTP_TC_UINT16: + case PTP_TC_INT32: + case PTP_TC_UINT32: + return ptp_parse_data(d, type, u32); + case PTP_TC_INT64: + case PTP_TC_UINT64: + (*data) = malloc(8); + memcpy((*data), d, 8); + return 8; + case PTP_TC_UINT8ARRAY: + size = 1; + case PTP_TC_UINT16ARRAY: + size = 2; + case PTP_TC_UINT32ARRAY: + size = 4; + case PTP_TC_UINT64ARRAY: + size = 8; + ptp_read_u32(d, &length32); + (*data) = malloc(4 + length32 * size); + memcpy((*data), d, 4 + length32 * size); + return 4 + length32 * size; + case PTP_TC_STRING: + ptp_read_u8(d, &length8); + len_total = 1 + (length8 * 2); + (*data) = malloc(len_total); + memcpy((*data), d, len_total); + return len_total; + default: + ptp_panic("Unknown data type %d\n", type); + } +} + +int ptp_parse_prop_desc(struct PtpRuntime *r, struct PtpPropDesc *oi) { uint8_t *d = ptp_get_payload(r); - memcpy(oi, d, PTP_PROP_DESC_VAR_START); - d += PTP_PROP_DESC_VAR_START; - d += ptp_parse_data(d, oi->data_type, &oi->default_value); - d += ptp_parse_data(d, oi->data_type, &oi->current_value); - // TODO: Form flag + form (for properties like date/time) + d += ptp_read_u16(d, &oi->code); + d += ptp_read_u16(d, &oi->data_type); + d += ptp_read_u8(d, &oi->read_only); + + // TODO: Arrays will be ignored + d += parse_data_data_or_u32(d, oi->data_type, &oi->default_value32, &oi->default_value); + d += parse_data_data_or_u32(d, oi->data_type, &oi->current_value32, &oi->current_value); + + d += ptp_read_u8(d, &oi->form_type); + + if (oi->form_type == PTP_RangeForm) { + d += ptp_parse_data(d, oi->data_type, &oi->range_form.min); + d += ptp_parse_data(d, oi->data_type, &oi->range_form.max); + d += ptp_parse_data(d, oi->data_type, &oi->range_form.step); + } else if (oi->form_type == PTP_EnumerationForm) { + uint16_t num_values = 0; + d += ptp_read_u16(d, &num_values); + int length = 0; + for (uint32_t i = 0; i < num_values; i++) { + length += ptp_get_data_size(d + length, oi->data_type); + } + struct PtpEnumerationForm *form = (struct PtpEnumerationForm *)malloc(sizeof(struct PtpEnumerationForm) + length); + form->length = num_values; + memcpy(form->data, d, length); + oi->enum_form = form; + } else { + ptp_panic("Unknown form type %d\n", oi->form_type); + return -1; + } + return 0; } diff --git a/src/libusb.c b/src/libusb.c index aff3205..5d6d5f6 100644 --- a/src/libusb.c +++ b/src/libusb.c @@ -36,6 +36,8 @@ int ptp_comm_init(struct PtpRuntime *r) { ptp_verbose_log("Initializing libusb...\n"); libusb_init(&(backend->ctx)); + + //libusb_set_option(backend->ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG); } return 0; @@ -297,7 +299,7 @@ int ptp_cmd_write(struct PtpRuntime *r, void *to, int length) { const struct LibUSBBackend *backend = (struct LibUSBBackend *)r->comm_backend; if (backend == NULL || r->io_kill_switch) { - return -1; + return -11; } int transferred; @@ -306,6 +308,7 @@ int ptp_cmd_write(struct PtpRuntime *r, void *to, int length) { backend->endpoint_out, (unsigned char *)to, length, &transferred, PTP_TIMEOUT); if (rc) { + perror("libusb_bulk_transfer"); return -1; } diff --git a/src/log.c b/src/log.c index c2167c4..17aed5f 100644 --- a/src/log.c +++ b/src/log.c @@ -1,5 +1,4 @@ -// Optional implementation of a logging mechanism - you can replace this file -// with your own mechanism. +// Optional implementation of a logger - you can and should replace this file. #include #include @@ -7,20 +6,21 @@ #include void ptp_verbose_log(char *fmt, ...) { - #ifdef VERBOSE +#ifdef VERBOSE va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); - #endif +#endif } +__attribute__ ((noreturn)) void ptp_panic(char *fmt, ...) { + printf("PTP abort: "); va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); - printf("PTP triggered PANIC\n"); - exit(1); + abort(); } diff --git a/src/operations.c b/src/operations.c index 535d308..81dec51 100644 --- a/src/operations.c +++ b/src/operations.c @@ -247,7 +247,7 @@ int ptp_get_prop_value(struct PtpRuntime *r, int code) { return ptp_send(r, &cmd); } -int ptp_get_prop_desc(struct PtpRuntime *r, int code, struct PtpDevPropDesc *pd) { +int ptp_get_prop_desc(struct PtpRuntime *r, int code, struct PtpPropDesc *pd) { struct PtpCommand cmd; cmd.code = PTP_OC_GetDevicePropDesc; cmd.param_length = 1; diff --git a/src/pack.c b/src/pack.c index a1149fc..c4eacc2 100644 --- a/src/pack.c +++ b/src/pack.c @@ -1,9 +1,2 @@ #include - -int ptp_pack_fuji_object_info(struct PtpRuntime *r, struct PtpObjectInfo *oi, void *buffer, int max) { - void **ptr = (void **)(&buffer); - - memcpy(*ptr, oi, PTP_FUJI_OBJ_INFO_VAR_START); - (*ptr) += PTP_FUJI_OBJ_INFO_VAR_START; -} - +// ... diff --git a/src/ptp.h b/src/ptp.h index a8871be..8d10081 100644 --- a/src/ptp.h +++ b/src/ptp.h @@ -661,6 +661,9 @@ struct PtpIpInitPacket { #define PTP_TC_UINT64ARRAY 0x4008 #define PTP_TC_STRING 0xFFFF +#define PTP_RangeForm 0x1 +#define PTP_EnumerationForm 0x2 + // Used for socket initialization #define PTPIP_INIT_COMMAND_REQ 0x1 #define PTPIP_INIT_COMMAND_ACK 0x2 @@ -679,16 +682,18 @@ struct PtpIpInitPacket { #define PTPIP_PING 0xD #define PTPIP_PONG 0xE -// Standard interface Class ID for PTP. -// See https://en.wikipedia.org/wiki/USB#Device_classes -#define PTP_CLASS_ID 6 - #define USB_VENDOR_CANON 0x4A9 -// ISO number for PTP/IP, seems to be standard (?) +// ISO number for PTP/IP #define PTP_IP_PORT 15740 -// Vendor init/USB codes, not specifically PTP +// Standard interface Class ID for PTP. +// See https://en.wikipedia.org/wiki/USB#Device_classes +#define PTP_CLASS_ID 6 + +// bRequest codes +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 #define USB_REQ_RESET 0x66 #define USB_REQ_STATUS 0x67 #define USB_REQ_GET_STATUS 0x00 diff --git a/test/test.c b/test/test.c index e2a369e..1c0252b 100644 --- a/test/test.c +++ b/test/test.c @@ -63,7 +63,7 @@ int ptp_vcam_magic() { return 0; } -// Test case for EOS T6/1300D (most common DSLR) +// Test case for EOS T6/1300D vcam int test_eos_t6() { struct PtpRuntime r; @@ -138,6 +138,33 @@ int test_bind() { return 0; } +int test_props() { + struct PtpRuntime r; + + int rc = test_setup_usb(&r); + if (rc) return rc; + + struct PtpPropDesc pd; + rc = ptp_get_prop_desc(&r, PTP_PC_BatteryLevel, &pd); + if (rc) return rc; + assert(pd.current_value32 == 50); + assert(pd.default_value32 == 50); + + assert(pd.range_form.min == 0); + assert(pd.range_form.max == 100); + assert(pd.range_form.step == 1); + + rc = ptp_get_prop_desc(&r, PTP_PC_ImageSize, &pd); + if (rc) return rc; + + char buffer[128]; + ptp_read_string(pd.default_value, buffer, sizeof(buffer)); + assert(!strcmp(buffer, "640x480")); + + ptp_close(&r); + return 0; +} + int test_fs() { struct PtpRuntime r; @@ -151,12 +178,6 @@ int test_fs() { ptp_device_info_json(&di, buffer, sizeof(buffer)); printf("%s\n", buffer); - struct PtpDevPropDesc pd; - rc = ptp_get_prop_desc(&r, PTP_PC_BatteryLevel, &pd); - if (rc) return rc; - assert(pd.current_value == 50); - assert(pd.default_value == 50); - struct PtpArray *arr; rc = ptp_get_storage_ids(&r, &arr); if (rc) return rc; @@ -268,5 +289,9 @@ int main() { printf("Return code: %d\n", rc); if (rc) return rc; + rc = test_props(); + printf("Return code: %d\n", rc); + if (rc) return rc; + return 0; }