summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--camlibs/ptp2/n-param-test.c116
-rw-r--r--camlibs/ptp2/ptp.c70
2 files changed, 177 insertions, 9 deletions
diff --git a/camlibs/ptp2/n-param-test.c b/camlibs/ptp2/n-param-test.c
new file mode 100644
index 000000000..f6ed72829
--- /dev/null
+++ b/camlibs/ptp2/n-param-test.c
@@ -0,0 +1,116 @@
+/** \file camlibs/ptp2/n-param-test.c
+ * \brief Test the __VA_ARGS__ and va_args related hacks for ptp_init_container()
+ *
+ * The ptp_init_container() function and the PTPContainer type are a
+ * bit ugly (like assuming consecutive struct members are laid out
+ * like an array), so this makes sure that at least the macros calling
+ * ptp_init_container() actually do what they are intended to do when
+ * counting the arguments and passing them on.
+ *
+ * \author Copyright (C) 2021 Hans Ulrich Niedermann <hun@n-dimensional.de>
+ *
+ * \copyright GNU Lesser General Public License 2 or later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * Usage:
+ * $ clang -std=c99 -pedantic -Wall -Wextra -Weverything -o n-param-test-clang n-param-test.c && ./n-param-test-clang
+ * $ gcc -std=c99 -pedantic -Wall -Wextra -o n-param-test-gcc n-param-test.c && ./n-param-test-gcc
+ *
+ */
+
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+
+// #define TRY_CASES_OUTSIDE_VALID_RANGE
+
+
+#define N_PARAM_SEQ(DUMMY, \
+ Pa, Pb, Pc, Pd, Pe, \
+ P0, P1, P2, P3, P4, P5, \
+ N, ...) \
+ N
+
+
+#define N_PARAM(...) \
+ N_PARAM_SEQ(__VA_ARGS__, \
+ "a", "b", "c", "d", "e", \
+ 5, 4, 3, 2, 1, 0, NULL)
+
+
+#define FOO_INIT(PTR, ...) \
+ foo_func(PTR, N_PARAM(-666, __VA_ARGS__), __VA_ARGS__)
+
+
+static
+void foo_func(void *ptr, int n_param, ...)
+{
+ va_list args;
+
+ (void) ptr;
+
+ printf("%s: n_param=%d\n", "foo_func", n_param);
+ assert(n_param <= 5);
+
+ va_start(args, n_param);
+ int code = va_arg(args, int);
+ printf(" code = %d\n", code);
+
+ for (int i=0; i<n_param; ++i) {
+ int value = va_arg(args, int);
+ printf(" Param%d = param[%d] = %d\n", i+1, i, value);
+ }
+ va_end(args);
+}
+
+
+int main()
+{
+ /* Zero to five parameters after the code are the valid use
+ * cases. The CODE in these examples has the value (1000+n_param).
+ */
+
+ /* PTR CODE P1 P2 P3 P4 P5 */
+ FOO_INIT(NULL, 1000);
+ FOO_INIT(NULL, 1001, 201);
+ FOO_INIT(NULL, 1002, 201, 202);
+ FOO_INIT(NULL, 1003, 201, 202, 203);
+ FOO_INIT(NULL, 1004, 201, 202, 203, 204);
+ FOO_INIT(NULL, 1005, 201, 202, 203, 204, 205);
+
+ /* from here on, we want compile time errors or at least runtime
+ errors */
+#ifdef TRY_CASES_OUTSIDE_VALID_RANGE
+ FOO_INIT(NULL, 1006, 201, 202, 203, 204, 205, 206);
+ FOO_INIT(NULL, 1007, 201, 202, 203, 204, 205, 206, 207);
+ FOO_INIT(NULL, 1008, 201, 202, 203, 204, 205, 206, 207, 208);
+ FOO_INIT(NULL, 1009, 201, 202, 203, 204, 205, 206, 207, 208, 209);
+ FOO_INIT(NULL, 1010, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210);
+
+ FOO_INIT(NULL, 1011, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211);
+ FOO_INIT(NULL, 1012, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212);
+ FOO_INIT(NULL, 1013, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213);
+ FOO_INIT(NULL, 1014, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214);
+ FOO_INIT(NULL, 1015, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215);
+ FOO_INIT(NULL, 1016, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216);
+ FOO_INIT(NULL, 1017, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217);
+#endif
+
+ return 0;
+}
diff --git a/camlibs/ptp2/ptp.c b/camlibs/ptp2/ptp.c
index 47adce4bf..85f8470fc 100644
--- a/camlibs/ptp2/ptp.c
+++ b/camlibs/ptp2/ptp.c
@@ -30,14 +30,19 @@
# include <libxml/parser.h>
#endif
+#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
+#include <stdint.h>
+#include <limits.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
+#include "libgphoto2_port/compiletime-assert.h"
+
#ifdef ENABLE_NLS
# include <libintl.h>
# undef _
@@ -59,27 +64,74 @@
#define CHECK_PTP_RC(RESULT) do { uint16_t r = (RESULT); if (r != PTP_RC_OK) return r; } while(0)
-static inline void
-ptp_init_container(PTPContainer* ptp, uint16_t code, int n_param, ...)
+
+/* Initialize a PTPContainer struct. For the usage, see the PTP_CNT_INIT() macro below. */
+static inline
+void ptp_init_container(PTPContainer* ptp, int n_param, ...)
{
va_list args;
int i;
memset(ptp, 0, sizeof(*ptp));
- ptp->Code = code;
+ va_start(args, n_param);
+ /* verify the int value cannot have cut off the uint16_t value */
+ COMPILETIME_ASSERT(INT_MAX >= UINT16_MAX);
+ /* note that uint16_t can be promoted to an int, so va_arg
+ * needs to be called with the int type */
+ ptp->Code = (uint16_t)(va_arg(args, int));
ptp->Nparam = n_param;
- va_start(args, n_param);
- for (i=0; i<n_param; ++i)
+ /* Make certain the memory layout of PTPContainer matches what
+ * the for loop code below expects.
+ */
+ COMPILETIME_ASSERT((offsetof(PTPContainer, Param1) + sizeof(uint32_t))
+ == offsetof(PTPContainer, Param2));
+ COMPILETIME_ASSERT((offsetof(PTPContainer, Param2) + sizeof(uint32_t))
+ == offsetof(PTPContainer, Param3));
+ COMPILETIME_ASSERT((offsetof(PTPContainer, Param3) + sizeof(uint32_t))
+ == offsetof(PTPContainer, Param4));
+ COMPILETIME_ASSERT((offsetof(PTPContainer, Param4) + sizeof(uint32_t))
+ == offsetof(PTPContainer, Param5));
+
+ COMPILETIME_ASSERT((offsetof(PTPContainer, Param1) + 5*sizeof(uint32_t))
+ == offsetof(PTPContainer, Nparam));
+
+ /* Silently ignore parameters past Param5 */
+ for (i=0; (i<n_param) && (i<5); ++i) {
(&ptp->Param1)[i] = va_arg(args, uint32_t);
+ }
va_end(args);
}
-#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
-#define NARGS(...) NARGS_SEQ(-1, ##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
-#define PTP_CNT_INIT(PTP, CODE, ...) \
- ptp_init_container(&PTP, CODE, NARGS(__VA_ARGS__), ##__VA_ARGS__)
+#define N_PARAM_SEQ(DUMMY, \
+ Pa, Pb, Pc, Pd, Pe, \
+ P0, P1, P2, P3, P4, P5, \
+ N, ...) \
+ N
+
+
+#define N_PARAM(...) \
+ N_PARAM_SEQ(__VA_ARGS__, \
+ "a", "b", "c", "d", "e", \
+ 5U, 4U, 3U, 2U, 1U, 0U, NULL)
+
+
+/* Usage: PTP_CNT_INIT(PTP, CODE[, PARAM...])
+ *
+ * Together with ptp_init_container(), this basically is a shortcut to
+ * initializing a PTPContainer struct, but automatically counts the
+ * number of PTPContainer parameters (members Param1 through Param5)
+ * set.
+ *
+ * The valid number of parameters is 0 through 5.
+ */
+#define PTP_CNT_INIT(PTP, ...) \
+ do { \
+ const int n_param = N_PARAM(-666, __VA_ARGS__); \
+ ptp_init_container(&PTP, n_param, __VA_ARGS__); \
+ } while (0)
+
static uint16_t ptp_exit_recv_memory_handler (PTPDataHandler*,unsigned char**,unsigned long*);
static uint16_t ptp_init_recv_memory_handler(PTPDataHandler*);