mirror of
https://github.com/openharmony/third_party_sane-airscan.git
synced 2026-06-30 21:17:55 -04:00
07ebd85003
This variable, if set, overrides all devices, manually configured
in the log files and disables auto discovery.
Examples:
SANE_AIRSCAN_DEVICE="escl:Kyocera eSCL:http://192.168.1.102:9095/eSCL"
SANE_AIRSCAN_DEVICE="wsd:Kyocera WSD:http://192.168.1.102:5358/WSDScanner"
Formal syntax:
"PROTO:DEVICE NAME:URL"
Where:
- PROTO` is either `escl` or `wsd`.
- DEVICE NAME will appear in the list of devices.
- URL is the device URL, using `http:` or `https:` schemes.
The primary purpose of this variable is the automated testing
of the `sane-airscan` backend.
3869 lines
98 KiB
C
3869 lines
98 KiB
C
/* AirScan (a.k.a. eSCL) backend for SANE
|
|
*
|
|
* Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
|
|
* See LICENSE for license terms and conditions
|
|
*/
|
|
|
|
#ifndef airscan_h
|
|
#define airscan_h
|
|
|
|
#include <avahi-common/address.h>
|
|
#include <avahi-common/strlst.h>
|
|
#include <avahi-common/watch.h>
|
|
|
|
#include <sane/sane.h>
|
|
#include <sane/saneopts.h>
|
|
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/******************** Static configuration ********************/
|
|
/* Configuration path in environment
|
|
*/
|
|
#define CONFIG_PATH_ENV "SANE_CONFIG_DIR"
|
|
|
|
/* Standard SANE configuration directory
|
|
*/
|
|
#ifndef CONFIG_SANE_CONFIG_DIR
|
|
# define CONFIG_SANE_CONFIG_DIR "/etc/sane.d/"
|
|
#endif
|
|
|
|
/* Sane-airscan configuration file and subdirectory names
|
|
*/
|
|
#define CONFIG_AIRSCAN_CONF "airscan.conf"
|
|
#define CONFIG_AIRSCAN_D "airscan.d"
|
|
|
|
/* Environment variables:
|
|
*
|
|
* CONFIG_ENV_AIRSCAN_DEBUG - if set to "true" or non-zero number,
|
|
* enables writing debug messages to stderr
|
|
*
|
|
* SANE_AIRSCAN_DEVICE - allows to forcibly set the target device.
|
|
* See sane-airscan(5) for detains.
|
|
*/
|
|
#define CONFIG_ENV_AIRSCAN_DEBUG "SANE_DEBUG_AIRSCAN"
|
|
#define CONFIG_ENV_AIRSCAN_DEVICE "SANE_AIRSCAN_DEVICE"
|
|
|
|
/* Default resolution, DPI
|
|
*/
|
|
#define CONFIG_DEFAULT_RESOLUTION 300
|
|
|
|
/* Minimal interval between subsequent sane_start()
|
|
* attempts, if previous sane_start was failed
|
|
*/
|
|
#define CONFIG_START_RETRY_INTERVAL 2500
|
|
|
|
/* Default directory for AF_UNIX sockets
|
|
*/
|
|
#define CONFIG_DEFAULT_SOCKET_DIR "/var/run"
|
|
|
|
/******************** Forward declarations ********************/
|
|
/* log_ctx represents logging context
|
|
*/
|
|
typedef struct log_ctx log_ctx;
|
|
|
|
/* Type http_uri represents HTTP URI
|
|
*/
|
|
typedef struct http_uri http_uri;
|
|
|
|
/******************** Utility macros ********************/
|
|
/* Obtain pointer to outer structure from pointer to
|
|
* its known member
|
|
*/
|
|
#define OUTER_STRUCT(member_p,struct_t,field) \
|
|
((struct_t*)((char*)(member_p) - ((ptrdiff_t) &(((struct_t*) 0)->field))))
|
|
|
|
/******************** Circular Linked Lists ********************/
|
|
/* ll_node represents a linked data node.
|
|
* Data nodes are embedded into the corresponding data structures:
|
|
* struct data {
|
|
* ll_node chain; // Linked list chain
|
|
* ...
|
|
* };
|
|
*
|
|
* Use OUTER_STRUCT() macro to obtain pointer to containing
|
|
* structure from the pointer to the list node
|
|
*/
|
|
typedef struct ll_node ll_node;
|
|
struct ll_node {
|
|
ll_node *ll_prev, *ll_next;
|
|
};
|
|
|
|
/* ll_head represents a linked list head node
|
|
* ll_head must be initialized before use with ll_init() function
|
|
*/
|
|
typedef struct {
|
|
ll_node node;
|
|
} ll_head;
|
|
|
|
/* Initialize list head
|
|
*/
|
|
static inline void
|
|
ll_init (ll_head *head)
|
|
{
|
|
head->node.ll_next = head->node.ll_prev = &head->node;
|
|
}
|
|
|
|
/* Check if list is empty
|
|
*/
|
|
static inline bool
|
|
ll_empty (const ll_head *head)
|
|
{
|
|
return head->node.ll_next == &head->node;
|
|
}
|
|
|
|
/* Push node to the end of the list, represented
|
|
* by its head node
|
|
*/
|
|
static inline void
|
|
ll_push_end (ll_head *head, ll_node *node)
|
|
{
|
|
node->ll_prev = head->node.ll_prev;
|
|
node->ll_next = &head->node;
|
|
head->node.ll_prev->ll_next = node;
|
|
head->node.ll_prev = node;
|
|
}
|
|
|
|
/* Push node to the beginning of the list, represented
|
|
* by its head node
|
|
*/
|
|
static inline void
|
|
ll_push_beg (ll_head *head, ll_node *node)
|
|
{
|
|
node->ll_next = head->node.ll_next;
|
|
node->ll_prev = &head->node;
|
|
head->node.ll_next->ll_prev = node;
|
|
head->node.ll_next = node;
|
|
}
|
|
|
|
/* Delete node from the list
|
|
*/
|
|
static inline void
|
|
ll_del (ll_node *node)
|
|
{
|
|
ll_node *p = node->ll_prev, *n = node->ll_next;
|
|
|
|
p->ll_next = n;
|
|
n->ll_prev = p;
|
|
|
|
/* Make double-delete safe */
|
|
node->ll_next = node->ll_prev = node;
|
|
}
|
|
|
|
/* Pop node from the beginning of the list.
|
|
* Returns NULL if list is empty
|
|
*/
|
|
static inline ll_node*
|
|
ll_pop_beg (ll_head *head)
|
|
{
|
|
ll_node *node, *next;
|
|
|
|
node = head->node.ll_next;
|
|
if (node == &head->node) {
|
|
return NULL; /* List is empty if it is looped to itself */
|
|
}
|
|
|
|
next = node->ll_next;
|
|
next->ll_prev = &head->node;
|
|
head->node.ll_next = next;
|
|
|
|
/* Make double-delete safe */
|
|
node->ll_next = node->ll_prev = node;
|
|
|
|
return node;
|
|
}
|
|
|
|
/* Pop node from the end of the list.
|
|
* Returns NULL if list is empty
|
|
*/
|
|
static inline ll_node*
|
|
ll_pop_end (ll_head *head)
|
|
{
|
|
ll_node *node, *prev;
|
|
|
|
node = head->node.ll_prev;
|
|
if (node == &head->node) {
|
|
return NULL; /* List is empty if it is looped to itself */
|
|
}
|
|
|
|
prev = node->ll_prev;
|
|
prev->ll_next = &head->node;
|
|
head->node.ll_prev = prev;
|
|
|
|
/* Make double-delete safe */
|
|
node->ll_next = node->ll_prev = node;
|
|
|
|
return node;
|
|
}
|
|
|
|
/* Get next (from the beginning to the end) node of
|
|
* the list. Returns NULL, if end of list is reached
|
|
*/
|
|
static inline ll_node*
|
|
ll_next (const ll_head *head, const ll_node *node)
|
|
{
|
|
ll_node *next = node->ll_next;
|
|
return next == &head->node ? NULL : next;
|
|
}
|
|
|
|
/* Get previous (from the beginning to the end) node of
|
|
* the list. Returns NULL, if end of list is reached
|
|
*/
|
|
static inline ll_node*
|
|
ll_prev (const ll_head *head, const ll_node *node)
|
|
{
|
|
ll_node *prev = node->ll_prev;
|
|
return prev == &head->node ? NULL : prev;
|
|
}
|
|
|
|
/* Get first node of the list.
|
|
* Returns NULL if list is empty
|
|
*/
|
|
static inline ll_node*
|
|
ll_first (const ll_head *head)
|
|
{
|
|
return ll_next(head, &head->node);
|
|
}
|
|
|
|
/* Get last node of the list.
|
|
* Returns NULL if list is empty
|
|
*/
|
|
static inline ll_node*
|
|
ll_last (const ll_head *head)
|
|
{
|
|
return ll_prev(head, &head->node);
|
|
}
|
|
|
|
/* Concatenate lists:
|
|
* list1 += list2
|
|
* list2 = empty
|
|
*/
|
|
static inline void
|
|
ll_cat (ll_head *list1, ll_head *list2)
|
|
{
|
|
if (ll_empty(list2)) {
|
|
return;
|
|
}
|
|
|
|
list2->node.ll_prev->ll_next = &list1->node;
|
|
list2->node.ll_next->ll_prev = list1->node.ll_prev;
|
|
list1->node.ll_prev->ll_next = list2->node.ll_next;
|
|
list1->node.ll_prev = list2->node.ll_prev;
|
|
|
|
ll_init(list2);
|
|
}
|
|
|
|
/* Helper macro for list iteration.
|
|
* Usage:
|
|
* for (LL_FOR_EACH(node, list)) {
|
|
* // do something with the node
|
|
* }
|
|
*/
|
|
#define LL_FOR_EACH(node,list) \
|
|
node = ll_first(list); node != NULL; node = ll_next(list, node)
|
|
|
|
/******************** Memory allocation ********************/
|
|
/* Allocate `len' elements of type T
|
|
*/
|
|
#define mem_new(T,len) ((T*) __mem_alloc(len, 0, sizeof(T), true))
|
|
|
|
/* Resize memory. The returned memory block has length of `len' and
|
|
* capacity at least of `len' + `extra'
|
|
*
|
|
* If p is NULL, new memory block will be allocated. Otherwise,
|
|
* existent memory block will be resized, new pointer is returned,
|
|
* while old becomes invalid (similar to how realloc() works).
|
|
*
|
|
* This function never returns NULL, it panics in a case of
|
|
* memory allocation error.
|
|
*/
|
|
#define mem_resize(p,len,extra) \
|
|
((__typeof__(p)) __mem_resize(p,len,extra,sizeof(*p),true))
|
|
|
|
/* Try to resize memory. It works like mem_resize() but may
|
|
* return NULL if memory allocation failed.
|
|
*/
|
|
#define mem_try_resize(p,len,extra) __mem_resize(p,len,extra,sizeof(*p),false)
|
|
|
|
/* Truncate the memory block length, preserving its capacity
|
|
*/
|
|
void
|
|
mem_trunc (void *p);
|
|
|
|
/* Shrink the memory block length, preserving its capacity
|
|
*/
|
|
#define mem_shrink(p,len) __mem_shrink(p,len, sizeof(*p))
|
|
|
|
/* Free memory block, obtained from mem_new() or mem_resize()
|
|
* `p' can be NULL
|
|
*/
|
|
void
|
|
mem_free (void *p);
|
|
|
|
/* Get memory block length/capacity, in bytes
|
|
* For NULL pointer return 0
|
|
*/
|
|
size_t mem_len_bytes (const void *p);
|
|
size_t mem_cap_bytes (const void *p);
|
|
|
|
/* Get memory block length/capacity, in elements
|
|
* For NULL pointer return 0
|
|
*/
|
|
#define mem_len(v) (mem_len_bytes(v) / sizeof(*v))
|
|
#define mem_cap(v) (mem_cap_bytes(v) / sizeof(*v))
|
|
|
|
/* Helper functions for memory allocation, don't use directly
|
|
*/
|
|
void* __attribute__ ((__warn_unused_result__))
|
|
__mem_alloc (size_t len, size_t extra, size_t elsize, bool must);
|
|
|
|
void* __attribute__ ((__warn_unused_result__))
|
|
__mem_resize (void *p, size_t len, size_t cap, size_t elsize, bool must);
|
|
|
|
void
|
|
__mem_shrink (void *p, size_t len, size_t elsize);
|
|
|
|
/******************** Strings ********************/
|
|
/* Create new string
|
|
*/
|
|
static inline char*
|
|
str_new (void) {
|
|
char *s = mem_resize((char*) NULL, 0, 1);
|
|
*s = '\0';
|
|
return s;
|
|
}
|
|
|
|
/* Create new string as a copy of existent string
|
|
*/
|
|
static inline char*
|
|
str_dup (const char *s1)
|
|
{
|
|
size_t len = strlen(s1);
|
|
char *s = mem_resize((char*) NULL, len, 1);
|
|
memcpy(s, s1, len + 1);
|
|
return s;
|
|
}
|
|
|
|
/* Get string length in bytes, not including terminating '\0'
|
|
*/
|
|
static inline size_t
|
|
str_len (const char *s)
|
|
{
|
|
return mem_len(s);
|
|
}
|
|
|
|
/* Create new string as a lowercase copy of existent string
|
|
*/
|
|
char*
|
|
str_dup_tolower (const char *s1);
|
|
|
|
/* Create new string and print to it
|
|
*/
|
|
char*
|
|
str_printf (const char *format, ...);
|
|
|
|
/* Create new string and print to it, va_list version
|
|
*/
|
|
char*
|
|
str_vprintf (const char *format, va_list ap);
|
|
|
|
/* Truncate the string
|
|
*/
|
|
static inline void
|
|
str_trunc (char *s)
|
|
{
|
|
mem_trunc(s);
|
|
*s = '\0';
|
|
}
|
|
|
|
/* Resize the string
|
|
*
|
|
* s1 must be previously created by some of str_XXX functions,
|
|
* s1 will be consumed and the new pointer will be returned
|
|
*/
|
|
static inline char*
|
|
str_resize (char *s, size_t len)
|
|
{
|
|
s = mem_resize(s, len, 1);
|
|
s[len] = '\0';
|
|
return s;
|
|
}
|
|
|
|
/* Append memory to string:
|
|
* s1 += s2[:l2]
|
|
*
|
|
* s1 must be previously created by some of str_XXX functions,
|
|
* s1 will be consumed and the new pointer will be returned
|
|
*/
|
|
static inline char*
|
|
str_append_mem (char *s1, const char *s2, size_t l2)
|
|
{
|
|
size_t l1 = str_len(s1);
|
|
|
|
s1 = mem_resize(s1, l1 + l2, 1);
|
|
memcpy(s1 + l1, s2, l2);
|
|
s1[l1+l2] = '\0';
|
|
|
|
return s1;
|
|
}
|
|
|
|
/* Append string to string:
|
|
* s1 += s2
|
|
*
|
|
* s1 must be previously created by some of str_XXX functions,
|
|
* s1 will be consumed and the new pointer will be returned
|
|
*/
|
|
static inline char*
|
|
str_append (char *s1, const char *s2)
|
|
{
|
|
return str_append_mem(s1, s2, strlen(s2));
|
|
}
|
|
|
|
/* Append character to string:
|
|
* s1 += c
|
|
*
|
|
* `s' must be previously created by some of str_XXX functions,
|
|
* `s' will be consumed and the new pointer will be returned
|
|
*/
|
|
static inline char*
|
|
str_append_c (char *s, char c)
|
|
{
|
|
return str_append_mem(s, &c, 1);
|
|
}
|
|
|
|
/* Append formatted string to string
|
|
*
|
|
* `s' must be previously created by some of str_XXX functions,
|
|
* `s' will be consumed and the new pointer will be returned
|
|
*/
|
|
char*
|
|
str_append_printf (char *s, const char *format, ...);
|
|
|
|
/* Append formatted string to string -- va_list version
|
|
*/
|
|
char*
|
|
str_append_vprintf (char *s, const char *format, va_list ap);
|
|
|
|
/* Assign value to string
|
|
*
|
|
* `s1' must be previously created by some of str_XXX functions,
|
|
* `s1' will be consumed and the new pointer will be returned
|
|
*/
|
|
static inline char*
|
|
str_assign (char *s1, const char *s2)
|
|
{
|
|
mem_trunc(s1);
|
|
return str_append(s1, s2);
|
|
}
|
|
|
|
/* Concatenate several strings. Last pointer must be NULL.
|
|
* The returned pointer must be eventually freed by mem_free
|
|
*/
|
|
char*
|
|
str_concat (const char *s, ...);
|
|
|
|
/* Make sure that string is terminated with the `c' character:
|
|
* if string is not empty and the last character is not `c`,
|
|
* append `c' to the string
|
|
*
|
|
* `s' must be previously created by some of str_XXX functions,
|
|
* `s' will be consumed and the new pointer will be returned
|
|
*/
|
|
static inline char*
|
|
str_terminate (char *s, char c)
|
|
{
|
|
if (s[0] != '\0' && s[str_len(s) - 1] != c) {
|
|
s = str_append_c(s, c);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Check if string has a specified prefix
|
|
*/
|
|
bool
|
|
str_has_prefix (const char *s, const char *prefix);
|
|
|
|
/* Check if string has a specified suffix
|
|
*/
|
|
bool
|
|
str_has_suffix (const char *s, const char *suffix);
|
|
|
|
/* Remove leading and trailing white space.
|
|
* This function modifies string in place, and returns pointer
|
|
* to original string, for convenience
|
|
*/
|
|
char*
|
|
str_trim (char *s);
|
|
|
|
/******************** NULL-terminated pointer arrays ********************/
|
|
/* Create NULL-terminated array of pointers of type *T
|
|
*/
|
|
#define ptr_array_new(T) mem_resize((T*) NULL, 0, 1)
|
|
|
|
/* Append pointer to the NULL-terminated array of pointers.
|
|
* Returns new, potentially reallocated array
|
|
*/
|
|
#define ptr_array_append(a,p) \
|
|
((__typeof__(a)) __ptr_array_append((void**)a, p))
|
|
|
|
/* Truncate NULL-terminated array of pointers
|
|
*/
|
|
#define ptr_array_trunc(a) \
|
|
do { \
|
|
mem_trunc(a); \
|
|
a[0] = NULL; \
|
|
} while(0)
|
|
|
|
/* Find pointer within array of pointers.
|
|
* Return non-negative index if pointer was found, -1 otherwise
|
|
*/
|
|
#define ptr_array_find(a,p) __ptr_array_find((void**) a, p)
|
|
|
|
/* Delete element at given index.
|
|
* Returns value of deleted pointer or NULL, if index is out of range
|
|
*/
|
|
#define ptr_array_del(a,i) \
|
|
((__typeof__(*a)) __ptr_array_del((void**) a, i))
|
|
|
|
/* Helper function for ptr_array_append, don't use directly
|
|
*/
|
|
static inline void**
|
|
__ptr_array_append (void **a, void *p)
|
|
{
|
|
size_t len = mem_len(a) + 1;
|
|
a = mem_resize(a, len, 1);
|
|
a[len - 1] = p;
|
|
a[len] = NULL;
|
|
return a;
|
|
}
|
|
|
|
/* Helper function for ptr_array_find, don't use directly
|
|
*/
|
|
static inline int
|
|
__ptr_array_find (void **a, void *p)
|
|
{
|
|
size_t len = mem_len(a), i;
|
|
|
|
for (i = 0; i < len; i ++) {
|
|
if (a[i] == p) {
|
|
return (int) i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Helper function for ptr_array_del, don't use directly
|
|
*/
|
|
static inline void*
|
|
__ptr_array_del (void **a, int i)
|
|
{
|
|
size_t len = mem_len(a);
|
|
void *p;
|
|
|
|
if (i < 0 || i >= (int) len) {
|
|
return NULL;
|
|
}
|
|
|
|
len --;
|
|
p = a[i];
|
|
memmove(&a[i], &a[i + 1], sizeof(void*) * (len - i));
|
|
mem_shrink(a, len);
|
|
a[len] = NULL;
|
|
|
|
return p;
|
|
}
|
|
|
|
/******************** Safe ctype macros ********************/
|
|
#define safe_isspace(c) isspace((unsigned char) c)
|
|
#define safe_isxdigit(c) isxdigit((unsigned char) c)
|
|
#define safe_iscntrl(c) iscntrl((unsigned char) c)
|
|
#define safe_isprint(c) isprint((unsigned char) c)
|
|
#define safe_toupper(c) toupper((unsigned char) c)
|
|
#define safe_tolower(c) tolower((unsigned char) c)
|
|
|
|
/******************** OS Facilities ********************/
|
|
/* The following macros, if defined, indicate that OS
|
|
* has a particular features:
|
|
*
|
|
* OS_HAVE_EVENTFD - Linux-like eventfd (2)
|
|
* OS_HAVE_RTNETLINK - Linux-like rtnetlink (7)
|
|
* OS_HAVE_AF_ROUTE - BSD-like AF_ROUTE
|
|
* OS_HAVE_LINUX_PROCFS - Linux-style procfs
|
|
* OS_HAVE_IP_MREQN - OS defines struct ip_mreqn
|
|
* OS_HAVE_ENDIAN_H - #include <endian.h> works
|
|
* OS_HAVE_SYS_ENDIAN_H - #include <sys/endian.h> works
|
|
*/
|
|
#ifdef __linux__
|
|
# define OS_HAVE_EVENTFD 1
|
|
# define OS_HAVE_RTNETLINK 1
|
|
# define OS_HAVE_LINUX_PROCFS 1
|
|
# define OS_HAVE_IP_MREQN 1
|
|
# define OS_HAVE_ENDIAN_H 1
|
|
#endif
|
|
|
|
#ifdef BSD
|
|
# define OS_HAVE_AF_ROUTE 1
|
|
# ifdef __FreeBSD__
|
|
# define OS_HAVE_SYS_ENDIAN_H 1
|
|
# else
|
|
# define OS_HAVE_ENDIAN_H 1
|
|
# endif
|
|
#endif
|
|
|
|
/* Get user's home directory. There is no need to
|
|
* free the returned string
|
|
*
|
|
* May return NULL in a case of error
|
|
*/
|
|
const char *
|
|
os_homedir (void);
|
|
|
|
/* Get base name of the calling program.
|
|
* There is no need to free the returned string
|
|
*
|
|
* May return NULL in a case of error
|
|
*/
|
|
const char*
|
|
os_progname (void);
|
|
|
|
/* Make directory with parents
|
|
*/
|
|
int
|
|
os_mkdir (const char *path, mode_t mode);
|
|
|
|
/******************** Error handling ********************/
|
|
/* Type error represents an error. Its value either NULL,
|
|
* which indicates "no error" condition, or some opaque
|
|
* non-null pointer, which can be converted to string
|
|
* with textual description of the error, using the ESTRING()
|
|
* function
|
|
*
|
|
* Caller should not attempt to free the memory, referred
|
|
* by error or string, obtained from an error using the
|
|
* ESTRING() function
|
|
*/
|
|
typedef struct error_s *error;
|
|
|
|
/* Standard errors
|
|
*/
|
|
extern error ERROR_ENOMEM;
|
|
|
|
/* Construct error from a string
|
|
*/
|
|
static inline error
|
|
ERROR (const char *s)
|
|
{
|
|
return (error) s;
|
|
}
|
|
|
|
/* Obtain textual representation of the error
|
|
*/
|
|
static inline const char*
|
|
ESTRING (error err)
|
|
{
|
|
return (const char*) err;
|
|
}
|
|
|
|
/******************** Various identifiers ********************/
|
|
/* ID_PROTO represents protocol identifier
|
|
*/
|
|
typedef enum {
|
|
ID_PROTO_UNKNOWN = -1,
|
|
ID_PROTO_ESCL,
|
|
ID_PROTO_WSD,
|
|
|
|
NUM_ID_PROTO
|
|
} ID_PROTO;
|
|
|
|
/* id_proto_name returns protocol name
|
|
* For unknown ID returns NULL
|
|
*/
|
|
const char*
|
|
id_proto_name (ID_PROTO proto);
|
|
|
|
/* id_proto_by_name returns protocol identifier by name
|
|
* For unknown name returns ID_PROTO_UNKNOWN
|
|
*/
|
|
ID_PROTO
|
|
id_proto_by_name (const char* name);
|
|
|
|
/* ID_SOURCE represents scanning source
|
|
*/
|
|
typedef enum {
|
|
ID_SOURCE_UNKNOWN = -1,
|
|
ID_SOURCE_PLATEN,
|
|
ID_SOURCE_ADF_SIMPLEX,
|
|
ID_SOURCE_ADF_DUPLEX,
|
|
|
|
NUM_ID_SOURCE
|
|
} ID_SOURCE;
|
|
|
|
/* id_source_sane_name returns SANE name for the source
|
|
* For unknown ID returns NULL
|
|
*/
|
|
const char*
|
|
id_source_sane_name (ID_SOURCE id);
|
|
|
|
/* id_source_by_sane_name returns ID_SOURCE by its SANE name
|
|
* For unknown name returns ID_SOURCE_UNKNOWN
|
|
*/
|
|
ID_SOURCE
|
|
id_source_by_sane_name (const char *name);
|
|
|
|
/* ID_JUSTIFICATION represents hardware-defined ADF justification
|
|
* This value exposed to the SANE API as a couple of read-only
|
|
* options, separate for width and height justification.
|
|
* Not all scanners provide this information
|
|
*/
|
|
typedef enum {
|
|
ID_JUSTIFICATION_UNKNOWN = -1,
|
|
ID_JUSTIFICATION_LEFT,
|
|
ID_JUSTIFICATION_CENTER,
|
|
ID_JUSTIFICATION_RIGHT,
|
|
ID_JUSTIFICATION_TOP,
|
|
ID_JUSTIFICATION_BOTTOM,
|
|
|
|
NUM_ID_JUSTIFICATION
|
|
} ID_JUSTIFICATION;
|
|
|
|
/* id_justification_sane_name returns SANE name for the width justification
|
|
* For unknown ID returns NULL
|
|
*/
|
|
const char*
|
|
id_justification_sane_name (ID_JUSTIFICATION id);
|
|
|
|
/* ID_COLORMODE represents color mode
|
|
*/
|
|
typedef enum {
|
|
ID_COLORMODE_UNKNOWN = -1,
|
|
ID_COLORMODE_COLOR,
|
|
ID_COLORMODE_GRAYSCALE,
|
|
ID_COLORMODE_BW1,
|
|
|
|
NUM_ID_COLORMODE
|
|
} ID_COLORMODE;
|
|
|
|
/* id_colormode_sane_name returns SANE name for the color mode
|
|
* For unknown ID returns NULL
|
|
*/
|
|
const char*
|
|
id_colormode_sane_name (ID_COLORMODE id);
|
|
|
|
/* id_colormode_by_sane_name returns ID_COLORMODE by its SANE name
|
|
* For unknown name returns ID_COLORMODE_UNKNOWN
|
|
*/
|
|
ID_COLORMODE
|
|
id_colormode_by_sane_name (const char *name);
|
|
|
|
/* ID_FORMAT represents image format
|
|
*/
|
|
typedef enum {
|
|
ID_FORMAT_UNKNOWN = -1,
|
|
ID_FORMAT_JPEG,
|
|
ID_FORMAT_TIFF,
|
|
ID_FORMAT_PNG,
|
|
ID_FORMAT_PDF,
|
|
ID_FORMAT_BMP,
|
|
|
|
NUM_ID_FORMAT
|
|
} ID_FORMAT;
|
|
|
|
/* id_format_mime_name returns MIME name for the image format
|
|
*/
|
|
const char*
|
|
id_format_mime_name (ID_FORMAT id);
|
|
|
|
/* id_format_by_mime_name returns ID_FORMAT by its MIME name
|
|
* For unknown name returns ID_FORMAT_UNKNOWN
|
|
*/
|
|
ID_FORMAT
|
|
id_format_by_mime_name (const char *name);
|
|
|
|
/* if_format_short_name returns short name for ID_FORMAT
|
|
*/
|
|
const char*
|
|
id_format_short_name (ID_FORMAT id);
|
|
|
|
/* ID_SCANINTENT represents scan intent
|
|
*
|
|
* Intent hints scanner on a purpose of requested scan, which may
|
|
* imply carious parameters tweaks depending on that purpose.
|
|
*
|
|
* Intent maps to the eSCL Intent (see Mopria eSCL Technical Specification, 5)
|
|
* and WSD ContentType. The semantics of these two parameters looks very
|
|
* similar.
|
|
*
|
|
* Please note, eSCL defines also the ContentType parameter, but after
|
|
* some thinking and discussion we came to conclusion that Intent better
|
|
* maps our need.
|
|
*
|
|
* Dee discussion at: https://github.com/alexpevzner/sane-airscan/pull/351
|
|
*/
|
|
typedef enum {
|
|
ID_SCANINTENT_UNKNOWN = -1,
|
|
ID_SCANINTENT_UNSET, /* Intent is not set */
|
|
ID_SCANINTENT_AUTO, /* WSD: Auto */
|
|
ID_SCANINTENT_DOCUMENT, /* eSCL: Docoment, WSD: Text */
|
|
ID_SCANINTENT_TEXTANDGRAPHIC, /* eSCL: TextAndGraphic, WSD: Mixed */
|
|
ID_SCANINTENT_PHOTO, /* eSCL: Photo, WSD: Photo */
|
|
ID_SCANINTENT_PREVIEW, /* eSCL: Preview */
|
|
ID_SCANINTENT_OBJECT, /* eSCL: Objects (3d scan) */
|
|
ID_SCANINTENT_BUSINESSCARD, /* eSCL: BusinessCard */
|
|
ID_SCANINTENT_HALFTONE, /* WSD: Halftone */
|
|
|
|
NUM_ID_SCANINTENT
|
|
} ID_SCANINTENT;
|
|
|
|
/* id_scanintent_sane_name returns SANE name for the scan intents
|
|
* For unknown ID returns NULL
|
|
*/
|
|
const char*
|
|
id_scanintent_sane_name (ID_SCANINTENT id);
|
|
|
|
/* id_scanintent_by_sane_name returns ID_SCANINTENT by its SANE name
|
|
* For unknown name returns ID_SCANINTENT_UNKNOWN
|
|
*/
|
|
ID_SCANINTENT
|
|
id_scanintent_by_sane_name (const char *name);
|
|
|
|
/******************** Device ID ********************/
|
|
/* Allocate unique device ID
|
|
*/
|
|
unsigned int
|
|
devid_alloc (void);
|
|
|
|
/* Free device ID
|
|
*/
|
|
void
|
|
devid_free (unsigned int id);
|
|
|
|
/* Restart device ID allocation counter.
|
|
* Note, it doesn't free already allocated IDs, only restarts
|
|
* counter from the beginning.
|
|
*/
|
|
void
|
|
devid_restart (void);
|
|
|
|
/* Initialize device ID allocator
|
|
*/
|
|
void
|
|
devid_init (void);
|
|
|
|
/******************** Random bytes ********************/
|
|
/* Get N random bytes
|
|
*/
|
|
void
|
|
rand_bytes (void *buf, size_t n);
|
|
|
|
/* Initialize random bytes generator
|
|
*/
|
|
SANE_Status
|
|
rand_init (void);
|
|
|
|
/* Cleanup random bytes generator
|
|
*/
|
|
void
|
|
rand_cleanup (void);
|
|
|
|
/******************** UUID utilities ********************/
|
|
/* Type uuid represents a random UUID string.
|
|
*
|
|
* It is wrapped into struct, so it can be returned
|
|
* by value, without need to mess with memory allocation
|
|
*/
|
|
typedef struct {
|
|
char text[sizeof("urn:uuid:ede05377-460e-4b4a-a5c0-423f9e02e8fa")];
|
|
} uuid;
|
|
|
|
/* Check if uuid is valid
|
|
*/
|
|
static inline bool
|
|
uuid_valid (uuid u)
|
|
{
|
|
return u.text[0] != '\0';
|
|
}
|
|
|
|
/* Generate random UUID. Generated UUID has a following form:
|
|
* urn:uuid:ede05377-460e-4b4a-a5c0-423f9e02e8fa
|
|
*/
|
|
uuid
|
|
uuid_rand (void);
|
|
|
|
/* Parse UUID. This function ignores all "decorations", like
|
|
* urn:uuid: prefix and so on, and takes only hexadecimal digits
|
|
* into considerations
|
|
*
|
|
* Check the returned uuid with uuid_valid() for possible parse errors
|
|
*/
|
|
uuid
|
|
uuid_parse (const char *in);
|
|
|
|
/* Generate uuid by cryptographically cacheing input string
|
|
*/
|
|
uuid
|
|
uuid_hash (const char *s);
|
|
|
|
/* Compare two uuids
|
|
*/
|
|
static inline bool
|
|
uuid_equal (uuid u1, uuid u2)
|
|
{
|
|
return !strcmp(u1.text, u2.text);
|
|
}
|
|
|
|
/******************** Generic .INI file parser ********************/
|
|
/* Types of .INI file records
|
|
*/
|
|
typedef enum {
|
|
INIFILE_SECTION, /* The [section name] string */
|
|
INIFILE_VARIABLE, /* The variable = value string */
|
|
INIFILE_COMMAND, /* command param1 param2 ... */
|
|
INIFILE_SYNTAX /* The syntax error */
|
|
} INIFILE_RECORD;
|
|
|
|
/* .INI file record
|
|
*/
|
|
typedef struct {
|
|
INIFILE_RECORD type; /* Record type */
|
|
const char *section; /* Section name */
|
|
const char *variable; /* Variable name */
|
|
const char *value; /* Variable value */
|
|
const char **tokv; /* Value split to tokens */
|
|
unsigned int tokc; /* Count of strings in tokv */
|
|
const char *file; /* File name */
|
|
unsigned int line; /* File line */
|
|
} inifile_record;
|
|
|
|
/* .INI file (opaque)
|
|
*/
|
|
typedef struct {
|
|
const char *file; /* File name */
|
|
unsigned int line; /* File handle */
|
|
FILE *fp; /* File pointer */
|
|
|
|
bool tk_open; /* Token is currently open */
|
|
char *tk_buffer; /* Parser buffer, tokenized */
|
|
unsigned int *tk_offsets; /* Tokens offsets */
|
|
unsigned int tk_count; /* Tokens count */
|
|
|
|
char *buffer; /* Parser buffer */
|
|
char *section; /* Section name string */
|
|
char *variable; /* Variable name string */
|
|
char *value; /* Value string */
|
|
inifile_record record; /* Record buffer */
|
|
} inifile;
|
|
|
|
/* Open the .INI file
|
|
*/
|
|
inifile*
|
|
inifile_open (const char *name);
|
|
|
|
/* Close the .INI file
|
|
*/
|
|
void
|
|
inifile_close (inifile *file);
|
|
|
|
/* Read next record
|
|
*/
|
|
const inifile_record*
|
|
inifile_read (inifile *file);
|
|
|
|
/* Match name of section of variable
|
|
* - match is case-insensitive
|
|
* - difference in amount of free space is ignored
|
|
* - leading and trailing space is ignored
|
|
*/
|
|
bool
|
|
inifile_match_name (const char *n1, const char *n2);
|
|
|
|
/******************** Utility functions for IP addresses ********************/
|
|
/* Address string, wrapped into structure so can
|
|
* be passed by value
|
|
*/
|
|
typedef struct {
|
|
/* Holds sun_path from sockaddr_un plus a null byte. */
|
|
char text[109];
|
|
} ip_straddr;
|
|
|
|
/* Format ip_straddr from IP address (struct in_addr or struct in6_addr)
|
|
* af must be AF_INET or AF_INET6
|
|
*/
|
|
ip_straddr
|
|
ip_straddr_from_ip (int af, const void *addr);
|
|
|
|
/* Format ip_straddr from struct sockaddr.
|
|
* AF_INET, AF_INET6, and AF_UNIX are supported
|
|
*
|
|
* If `withzone' is true, zone suffix will be appended, when appropriate
|
|
*/
|
|
ip_straddr
|
|
ip_straddr_from_sockaddr(const struct sockaddr *addr, bool withzone);
|
|
|
|
/* Format ip_straddr from struct sockaddr.
|
|
* AF_INET, AF_INET6, and AF_UNIX are supported
|
|
*
|
|
* Port will not be appended, if it matches provided default port
|
|
*
|
|
* If `withzone' is true, zone suffix will be appended, when appropriate
|
|
*
|
|
* If `withlocalhost` is true and address is 127.0.0.1 or ::1,
|
|
* "localhost" will be used instead of the IP address literal
|
|
*/
|
|
ip_straddr
|
|
ip_straddr_from_sockaddr_dport (const struct sockaddr *addr,
|
|
int dport, bool withzone, bool withlocalhost);
|
|
|
|
/* Check if address is link-local
|
|
* af must be AF_INET or AF_INET6
|
|
*/
|
|
bool
|
|
ip_is_linklocal (int af, const void *addr);
|
|
|
|
/* Check if sockaddr is link-local
|
|
*/
|
|
bool
|
|
ip_sockaddr_is_linklocal (const struct sockaddr *addr);
|
|
|
|
/* Check if address is loopback
|
|
* af must be AF_INET or AF_INET6
|
|
*/
|
|
bool
|
|
ip_is_loopback (int af, const void *addr);
|
|
|
|
/* ip_addr represents IPv4 or IPv6 address
|
|
*/
|
|
typedef struct {
|
|
int af; /* AF_INET or AF_INET6 */
|
|
int ifindex; /* For IPv6 link-local addresses */
|
|
union {
|
|
struct in_addr v4; /* IPv4 address */
|
|
struct in6_addr v6; /* IPv4 address */
|
|
} ip;
|
|
} ip_addr;
|
|
|
|
/* Make ip_addr
|
|
*/
|
|
static inline ip_addr
|
|
ip_addr_make (int ifindex, int af, const void *addr)
|
|
{
|
|
ip_addr ip_addr;
|
|
|
|
memset(&ip_addr, 0, sizeof(ip_addr));
|
|
ip_addr.af = af;
|
|
|
|
switch (ip_addr.af) {
|
|
case AF_INET:
|
|
memcpy(&ip_addr.ip.v4, addr, 4);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
memcpy(&ip_addr.ip, addr, 16);
|
|
if (ip_is_linklocal(AF_INET6, &ip_addr.ip.v6)) {
|
|
ip_addr.ifindex = ifindex;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ip_addr;
|
|
}
|
|
|
|
/* Extract ip_addr from sockaddr
|
|
*/
|
|
static inline ip_addr
|
|
ip_addr_from_sockaddr (const struct sockaddr *sockaddr)
|
|
{
|
|
ip_addr addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.af = sockaddr->sa_family;
|
|
|
|
switch (addr.af) {
|
|
case AF_INET:
|
|
addr.ip.v4 = ((struct sockaddr_in*) sockaddr)->sin_addr;
|
|
break;
|
|
|
|
case AF_INET6:
|
|
addr.ip.v6 = ((struct sockaddr_in6*) sockaddr)->sin6_addr;
|
|
if (ip_is_linklocal(AF_INET6, &addr.ip.v6)) {
|
|
addr.ifindex = ((struct sockaddr_in6*) sockaddr)->sin6_scope_id;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
/* Format ip_addr into ip_straddr
|
|
*/
|
|
ip_straddr
|
|
ip_addr_to_straddr (ip_addr addr, bool withzone);
|
|
|
|
/* Check if two addresses are equal
|
|
*/
|
|
static inline bool
|
|
ip_addr_equal (ip_addr a1, ip_addr a2)
|
|
{
|
|
if (a1.af != a2.af) {
|
|
return false;
|
|
}
|
|
|
|
switch (a1.af) {
|
|
case AF_INET:
|
|
return a1.ip.v4.s_addr == a2.ip.v4.s_addr;
|
|
case AF_INET6:
|
|
return a1.ifindex == a2.ifindex &&
|
|
!memcmp(a1.ip.v6.s6_addr, a2.ip.v6.s6_addr, 16);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* ip_network represents IPv4 or IPv6 network (i.e., address with mask)
|
|
*/
|
|
typedef struct {
|
|
ip_addr addr; /* Network address */
|
|
int mask; /* Network mask */
|
|
} ip_network;
|
|
|
|
/* Format ip_network into ip_straddr
|
|
*/
|
|
ip_straddr
|
|
ip_network_to_straddr (ip_network net);
|
|
|
|
/* Check if ip_network contains ip_addr
|
|
*/
|
|
bool
|
|
ip_network_contains (ip_network net, ip_addr addr);
|
|
|
|
/* ip_addr_set represents a set of IP addresses
|
|
*/
|
|
typedef struct ip_addrset ip_addrset;
|
|
|
|
/* Create new ip_addrset
|
|
*/
|
|
ip_addrset*
|
|
ip_addrset_new (void);
|
|
|
|
/* Free ip_addrset
|
|
*/
|
|
void
|
|
ip_addrset_free (ip_addrset *addrset);
|
|
|
|
/* Check if address is in set
|
|
*/
|
|
bool
|
|
ip_addrset_lookup (const ip_addrset *addrset, ip_addr addr);
|
|
|
|
/* Add address to the set. Returns true, if address was
|
|
* actually added, false if it was already in the set
|
|
*/
|
|
bool
|
|
ip_addrset_add (ip_addrset *addrset, ip_addr addr);
|
|
|
|
/* Add address to the set without checking for duplicates
|
|
*/
|
|
void
|
|
ip_addrset_add_unsafe (ip_addrset *addrset, ip_addr addr);
|
|
|
|
/* Del address from the set.
|
|
*/
|
|
void
|
|
ip_addrset_del (ip_addrset *addrset, ip_addr addr);
|
|
|
|
/* Delete all addresses from the set
|
|
*/
|
|
void
|
|
ip_addrset_purge (ip_addrset *addrset);
|
|
|
|
/* Merge two sets:
|
|
* addrset += addrset2
|
|
*/
|
|
void
|
|
ip_addrset_merge (ip_addrset *addrset, const ip_addrset *addrset2);
|
|
|
|
/* Get access to array of addresses in the set
|
|
*/
|
|
const ip_addr*
|
|
ip_addrset_addresses (const ip_addrset *addrset, size_t *count);
|
|
|
|
/* Check if two address sets are intersecting
|
|
*/
|
|
bool
|
|
ip_addrset_is_intersect (const ip_addrset *set, const ip_addrset *set2);
|
|
|
|
/* Check if some of addresses in the address set is on the
|
|
* given network
|
|
*/
|
|
bool
|
|
ip_addrset_on_network (const ip_addrset *set, ip_network net);
|
|
|
|
/* Check if address set has some addresses of the specified
|
|
* address family
|
|
*/
|
|
bool
|
|
ip_addrset_has_af (const ip_addrset *set, int af);
|
|
|
|
/* Create user-friendly string out of set of addresses, containing
|
|
* in the ip_addrset:
|
|
* * addresses are sorted, IP4 addresses goes first
|
|
* * link-local addresses are skipped, if there are non-link-local ones
|
|
*
|
|
* Caller must use mem_free to release the returned string when
|
|
* it is not needed anymore
|
|
*/
|
|
char*
|
|
ip_addrset_friendly_str (const ip_addrset *set, char *s);
|
|
|
|
/******************** Network interfaces addresses ********************/
|
|
/* Network interface name, wrapped into structure, so
|
|
* it can be passed by value
|
|
*/
|
|
typedef struct {
|
|
char text[32];
|
|
} netif_name;
|
|
|
|
/* Network interface address
|
|
*/
|
|
typedef struct netif_addr netif_addr;
|
|
struct netif_addr {
|
|
netif_addr *next; /* Next address in the list */
|
|
int ifindex; /* Interface index */
|
|
netif_name ifname; /* Interface name, for logging */
|
|
bool ipv6; /* This is an IPv6 address */
|
|
void *data; /* Placeholder for user data */
|
|
char straddr[64]; /* Address string */
|
|
union {
|
|
struct in_addr v4; /* IPv4 address */
|
|
struct in6_addr v6; /* IPv6 address */
|
|
} ip;
|
|
};
|
|
|
|
/* NETIF_DISTANCE represents a distance to the target address
|
|
*/
|
|
typedef enum {
|
|
NETIF_DISTANCE_LOOPBACK, /* Target address is host's local address */
|
|
NETIF_DISTANCE_DIRECT, /* Target is on a local network */
|
|
NETIF_DISTANCE_ROUTED /* Target is behind a router */
|
|
} NETIF_DISTANCE;
|
|
|
|
/* Get distance to the target address
|
|
*/
|
|
NETIF_DISTANCE
|
|
netif_distance_get (const struct sockaddr *addr);
|
|
|
|
/* Check that interface has non-link-local address
|
|
* of particular address family
|
|
*/
|
|
bool
|
|
netif_has_non_link_local_addr (int af, int ifindex);
|
|
|
|
/* Compare addresses by distance. Returns:
|
|
* <0, if addr1 is closer that addr2
|
|
* >0, if addr2 is farther that addr2
|
|
* 0 if distance is equal
|
|
*/
|
|
static inline int
|
|
netif_distance_cmp (const struct sockaddr *addr1, const struct sockaddr *addr2)
|
|
{
|
|
int d1 = (int) netif_distance_get(addr1);
|
|
int d2 = (int) netif_distance_get(addr2);
|
|
|
|
return d1 - d2;
|
|
}
|
|
|
|
/* Get list of network interfaces addresses
|
|
* The returned list is sorted
|
|
*/
|
|
netif_addr*
|
|
netif_addr_list_get (void);
|
|
|
|
/* Free list of network interfaces addresses
|
|
*/
|
|
void
|
|
netif_addr_list_free (netif_addr *list);
|
|
|
|
/* netif_diff represents a difference between two
|
|
* lists of network interface addresses
|
|
*/
|
|
typedef struct {
|
|
netif_addr *added, *removed; /* What was added/removed */
|
|
netif_addr *preserved;
|
|
} netif_diff;
|
|
|
|
/* Compute a difference between two lists of addresses.
|
|
*
|
|
* It works by tossing nodes between 3 output lists:
|
|
* * if node is present in list2 only, it is moved
|
|
* to netif_diff.added
|
|
* * if node is present in list1 only, it is moved
|
|
* to netif_diff.removed
|
|
* * if node is present in both lists, node from
|
|
* list1 is moved to preserved, and node from
|
|
* list2 is released
|
|
*
|
|
* It assumes, both lists are sorted, as returned
|
|
* by netif_addr_get(). Returned lists are also sorted
|
|
*/
|
|
netif_diff
|
|
netif_diff_compute (netif_addr *list1, netif_addr *list2);
|
|
|
|
/* Merge two lists of addresses
|
|
*
|
|
* Input lists are consumed and new list is created.
|
|
*
|
|
* Input lists are assumed to be sorted, and output
|
|
* list will be sorted as well
|
|
*/
|
|
netif_addr*
|
|
netif_addr_list_merge (netif_addr *list1, netif_addr *list2);
|
|
|
|
/* Network interfaces addresses change notifier
|
|
*/
|
|
typedef struct netif_notifier netif_notifier;
|
|
|
|
/* Create netif_notifier
|
|
*/
|
|
netif_notifier*
|
|
netif_notifier_create (void (*callback) (void*), void *data);
|
|
|
|
/* Destroy netif_notifier
|
|
*/
|
|
void
|
|
netif_notifier_free (netif_notifier *notifier);
|
|
|
|
/* Initialize network interfaces monitoring
|
|
*/
|
|
SANE_Status
|
|
netif_init (void);
|
|
|
|
/* Cleanup network interfaces monitoring
|
|
*/
|
|
void
|
|
netif_cleanup (void);
|
|
|
|
/******************** Configuration file loader ********************/
|
|
/* Device URI for manually disabled device
|
|
*/
|
|
#define CONF_DEVICE_DISABLE "disable"
|
|
|
|
/* Device configuration, for manually added devices
|
|
*/
|
|
typedef struct conf_device conf_device;
|
|
struct conf_device {
|
|
unsigned int devid; /* Device ident */
|
|
const char *name; /* Device name */
|
|
ID_PROTO proto; /* Protocol to use */
|
|
http_uri *uri; /* Device URI, parsed; NULL if device disabled */
|
|
conf_device *next; /* Next device in the list */
|
|
};
|
|
|
|
/* WSDD_MODE represents WS-Discovery mode
|
|
*/
|
|
typedef enum {
|
|
WSDD_FAST, /* Use hints from DNS-SD to speed up WSDD */
|
|
WSDD_FULL, /* Full discovery, slow and fair */
|
|
WSDD_OFF /* Disable WSDD */
|
|
} WSDD_MODE;
|
|
|
|
/* Device blacklist entry
|
|
*/
|
|
typedef struct conf_blacklist conf_blacklist;
|
|
struct conf_blacklist {
|
|
const char *model; /* If not NULL, match by model */
|
|
const char *name; /* If not NULL, match by network name */
|
|
ip_network net; /* if net.addr.af != AF_UNSPEC, match by net */
|
|
conf_blacklist *next; /* Next entry in the list */
|
|
};
|
|
|
|
/* Backend configuration
|
|
*/
|
|
typedef struct {
|
|
bool dbg_enabled; /* Debugging enabled */
|
|
const char *dbg_trace; /* Trace directory */
|
|
bool dbg_hexdump; /* Hexdump all traffic to the trace */
|
|
conf_device *devices; /* Manually configured devices */
|
|
bool discovery; /* Scanners discovery enabled */
|
|
bool model_is_netname; /* Use network name instead of model */
|
|
bool proto_auto; /* Auto protocol selection */
|
|
WSDD_MODE wsdd_mode; /* WS-Discovery mode */
|
|
const char *socket_dir; /* Directory for AF_UNIX sockets */
|
|
conf_blacklist *blacklist; /* Devices blacklisted for discovery */
|
|
bool pretend_local; /* Pretend devices are local */
|
|
} conf_data;
|
|
|
|
#define CONF_INIT { \
|
|
.dbg_enabled = false, \
|
|
.dbg_trace = NULL, \
|
|
.dbg_hexdump = false, \
|
|
.devices = NULL, \
|
|
.discovery = true, \
|
|
.model_is_netname = true, \
|
|
.proto_auto = true, \
|
|
.wsdd_mode = WSDD_FAST, \
|
|
.socket_dir = NULL, \
|
|
.pretend_local = false \
|
|
}
|
|
|
|
extern conf_data conf;
|
|
|
|
/* Load configuration. It updates content of a global conf variable
|
|
*/
|
|
void
|
|
conf_load (void);
|
|
|
|
/* Free resources, allocated by conf_load, and reset configuration
|
|
* data into initial state
|
|
*/
|
|
void
|
|
conf_unload (void);
|
|
|
|
/******************** Pollable events ********************/
|
|
/* The pollable event
|
|
*
|
|
* Pollable events allow to wait until some event happens
|
|
* and can be used in combination with select()/poll()
|
|
* system calls
|
|
*/
|
|
typedef struct pollable pollable;
|
|
|
|
/* Create new pollable event
|
|
*/
|
|
pollable*
|
|
pollable_new (void);
|
|
|
|
/* Free pollable event
|
|
*/
|
|
void
|
|
pollable_free (pollable *p);
|
|
|
|
/* Get file descriptor for poll()/select().
|
|
*
|
|
* When pollable event becomes "ready", this file descriptor
|
|
* becomes readable from the select/poll point of view
|
|
*/
|
|
int
|
|
pollable_get_fd (pollable *p);
|
|
|
|
/* Make pollable event "ready"
|
|
*/
|
|
void
|
|
pollable_signal (pollable *p);
|
|
|
|
/* Make pollable event "not ready"
|
|
*/
|
|
void
|
|
pollable_reset (pollable *p);
|
|
|
|
/* Wait until pollable event is ready
|
|
*/
|
|
void
|
|
pollable_wait (pollable *p);
|
|
|
|
/******************** Time stamps ********************/
|
|
/* timestamp represents a monotonic time, in milliseconds
|
|
*/
|
|
typedef int64_t timestamp;
|
|
|
|
/* timestamp_now() returns a current time as timestamp
|
|
*/
|
|
static inline timestamp
|
|
timestamp_now (void)
|
|
{
|
|
struct timespec t;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
return (timestamp) t.tv_sec * 1000 + (timestamp) t.tv_nsec / 1000000;
|
|
}
|
|
|
|
/******************** Event loop ********************/
|
|
/* Initialize event loop
|
|
*/
|
|
SANE_Status
|
|
eloop_init (void);
|
|
|
|
/* Cleanup event loop
|
|
*/
|
|
void
|
|
eloop_cleanup (void);
|
|
|
|
/* Add start/stop callback. This callback is called
|
|
* on a event loop thread context, once when event
|
|
* loop is started, and second time when it is stopped
|
|
*
|
|
* Start callbacks are called in the same order as
|
|
* they were added. Stop callbacks are called in a
|
|
* reverse order
|
|
*/
|
|
void
|
|
eloop_add_start_stop_callback (void (*callback) (bool start));
|
|
|
|
/* Start event loop thread.
|
|
*/
|
|
void
|
|
eloop_thread_start (void);
|
|
|
|
/* Stop event loop thread and wait until its termination
|
|
*/
|
|
void
|
|
eloop_thread_stop (void);
|
|
|
|
/* Acquire event loop mutex
|
|
*/
|
|
void
|
|
eloop_mutex_lock (void);
|
|
|
|
/* Release event loop mutex
|
|
*/
|
|
void
|
|
eloop_mutex_unlock (void);
|
|
|
|
/* Wait on conditional variable under the event loop mutex
|
|
*/
|
|
void
|
|
eloop_cond_wait (pthread_cond_t *cond);
|
|
|
|
/* Get AvahiPoll that runs in event loop thread
|
|
*/
|
|
const AvahiPoll*
|
|
eloop_poll_get (void);
|
|
|
|
/* ELOOP_CALL_BADID is the invalid callid which will never be returned by
|
|
* the eloop_call().
|
|
*
|
|
* It is safe to use ELOOP_CALL_BADID as parameter to eloop_call_cancel().
|
|
* Calling eloop_call_cancel(ELOOP_CALL_BADID) is guaranteed to do nothing.
|
|
*/
|
|
#define ELOOP_CALL_BADID (~(uint64_t) 0)
|
|
|
|
/* Call function on a context of event loop thread
|
|
* The returned value can be supplied as a `callid'
|
|
* parameter for the eloop_call_cancel() function
|
|
*/
|
|
uint64_t
|
|
eloop_call (void (*func)(void*), void *data);
|
|
|
|
/* Cancel pending eloop_call
|
|
*
|
|
* This is safe to cancel already finished call (at this
|
|
* case nothing will happen)
|
|
*/
|
|
void
|
|
eloop_call_cancel (uint64_t callid);
|
|
|
|
/* Event notifier. Calls user-defined function on a context
|
|
* of event loop thread, when event is triggered. This is
|
|
* safe to trigger the event from a context of any thread
|
|
* or even from a signal handler
|
|
*/
|
|
typedef struct eloop_event eloop_event;
|
|
|
|
/* Create new event notifier. May return NULL
|
|
*/
|
|
eloop_event*
|
|
eloop_event_new (void (*callback)(void *), void *data);
|
|
|
|
/* Destroy event notifier
|
|
*/
|
|
void
|
|
eloop_event_free (eloop_event *event);
|
|
|
|
/* Trigger an event
|
|
*/
|
|
void
|
|
eloop_event_trigger (eloop_event *event);
|
|
|
|
/* Timer. Calls user-defined function after a specified
|
|
* interval
|
|
*/
|
|
typedef struct eloop_timer eloop_timer;
|
|
|
|
/* Create new timer. Timeout is in milliseconds
|
|
*/
|
|
eloop_timer*
|
|
eloop_timer_new (int timeout, void (*callback)(void *), void *data);
|
|
|
|
/* Cancel a timer
|
|
*
|
|
* Caller SHOULD NOT cancel expired timer (timer with called
|
|
* callback) -- this is done automatically
|
|
*/
|
|
void
|
|
eloop_timer_cancel (eloop_timer *timer);
|
|
|
|
/* eloop_fdpoll notifies user when file becomes
|
|
* readable, writable or both, depending on its
|
|
* event mask
|
|
*/
|
|
typedef struct eloop_fdpoll eloop_fdpoll;
|
|
|
|
/* Mask of file events user interested in
|
|
*/
|
|
typedef enum {
|
|
ELOOP_FDPOLL_READ = (1 << 0),
|
|
ELOOP_FDPOLL_WRITE = (1 << 1),
|
|
ELOOP_FDPOLL_BOTH = ELOOP_FDPOLL_READ | ELOOP_FDPOLL_WRITE
|
|
} ELOOP_FDPOLL_MASK;
|
|
|
|
/* Convert ELOOP_FDPOLL_MASK to string. Used for logging.
|
|
*/
|
|
const char*
|
|
eloop_fdpoll_mask_str (ELOOP_FDPOLL_MASK mask);
|
|
|
|
/* Create eloop_fdpoll
|
|
*
|
|
* Callback will be called, when file will be ready for read/write/both,
|
|
* depending on mask
|
|
*
|
|
* Initial mask value is 0, and it can be changed, using
|
|
* eloop_fdpoll_set_mask() function
|
|
*/
|
|
eloop_fdpoll*
|
|
eloop_fdpoll_new (int fd,
|
|
void (*callback) (int, void*, ELOOP_FDPOLL_MASK), void *data);
|
|
|
|
/* Destroy eloop_fdpoll
|
|
*/
|
|
void
|
|
eloop_fdpoll_free (eloop_fdpoll *fdpoll);
|
|
|
|
/* Set eloop_fdpoll event mask. It returns a previous value of event mask
|
|
*/
|
|
ELOOP_FDPOLL_MASK
|
|
eloop_fdpoll_set_mask (eloop_fdpoll *fdpoll, ELOOP_FDPOLL_MASK mask);
|
|
|
|
/* Format error string, as printf() does and save result
|
|
* in the memory, owned by the event loop
|
|
*
|
|
* Caller should not free returned string. This is safe
|
|
* to use the returned string as an argument to the
|
|
* subsequent eloop_eprintf() call.
|
|
*
|
|
* The returned string remains valid until next call
|
|
* to eloop_eprintf(), which makes it usable to
|
|
* report errors up by the stack. However, it should
|
|
* not be assumed, that the string will remain valid
|
|
* on a next eloop roll, so don't save this string
|
|
* anywhere, if you need to do so, create a copy!
|
|
*/
|
|
error
|
|
eloop_eprintf(const char *fmt, ...);
|
|
|
|
/******************** HTTP Client ********************/
|
|
/* Create new URI, by parsing URI string
|
|
*/
|
|
http_uri*
|
|
http_uri_new (const char *str, bool strip_fragment);
|
|
|
|
/* Clone an URI
|
|
*/
|
|
http_uri*
|
|
http_uri_clone (const http_uri *old);
|
|
|
|
/* Create URI, relative to base URI. If `path_only' is
|
|
* true, scheme, host and port are taken from the
|
|
* base URI
|
|
*/
|
|
http_uri*
|
|
http_uri_new_relative (const http_uri *base, const char *path,
|
|
bool strip_fragment, bool path_only);
|
|
|
|
/* Free the URI
|
|
*/
|
|
void
|
|
http_uri_free (http_uri *uri);
|
|
|
|
/* Get URI string
|
|
*/
|
|
const char*
|
|
http_uri_str (http_uri *uri);
|
|
|
|
/* Get URI's host address. If Host address is not literal, returns NULL
|
|
*/
|
|
const struct sockaddr*
|
|
http_uri_addr (const http_uri *uri);
|
|
|
|
/* Get URI's address family. May return AF_UNSPEC,
|
|
* if host address is not literal
|
|
*/
|
|
static inline int
|
|
http_uri_af (const http_uri *uri)
|
|
{
|
|
const struct sockaddr *addr = http_uri_addr(uri);
|
|
return addr ? addr->sa_family : AF_UNSPEC;
|
|
}
|
|
|
|
/* Tell if URI host is literal IP address
|
|
*/
|
|
static inline bool
|
|
http_uri_is_literal (const http_uri *uri)
|
|
{
|
|
return http_uri_addr(uri) != NULL;
|
|
}
|
|
|
|
/* Tell if URI IP address is loopback
|
|
*/
|
|
static inline bool
|
|
http_uri_is_loopback (const http_uri *uri)
|
|
{
|
|
const struct sockaddr *addr = http_uri_addr(uri);
|
|
const void *ip = NULL;
|
|
|
|
if (addr == NULL) {
|
|
return false;
|
|
}
|
|
|
|
switch (addr->sa_family) {
|
|
case AF_INET:
|
|
ip = &(((struct sockaddr_in*) addr)->sin_addr);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
ip = &(((struct sockaddr_in6*) addr)->sin6_addr);
|
|
break;
|
|
}
|
|
|
|
if (ip != NULL) {
|
|
return ip_is_loopback(addr->sa_family, ip);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Get URI path
|
|
*
|
|
* Note, if URL has empty path (i.e., "http://1.2.3.4"), the
|
|
* empty string will be returned
|
|
*/
|
|
const char*
|
|
http_uri_get_path (const http_uri *uri);
|
|
|
|
/* Set URI path
|
|
*/
|
|
void
|
|
http_uri_set_path (http_uri *uri, const char *path);
|
|
|
|
/* Get URI host. It returns only host name, port number is
|
|
* not included.
|
|
*
|
|
* IPv6 literal addresses are returned in square brackets
|
|
* (i.e., [fe80::217:c8ff:fe7b:6a91%4])
|
|
*
|
|
* Note, the subsequent modifications of URI, such as http_uri_fix_host(),
|
|
* http_uri_fix_ipv6_zone() etc, may make the returned string invalid,
|
|
* so if you need to keep it for a long time, better make a copy
|
|
*/
|
|
const char*
|
|
http_uri_get_host (const http_uri *uri);
|
|
|
|
/* http_uri_host_is checks if URI's host name is equal to the
|
|
* specified string.
|
|
*
|
|
* It does its best to compare domain names correctly, taking
|
|
* in account only significant difference (for example, the difference
|
|
* in upper/lower case * in domain names is not significant).
|
|
*/
|
|
bool
|
|
http_uri_host_is (const http_uri *uri, const char *host);
|
|
|
|
/* http_uri_host_is_literal returns true if URI uses literal
|
|
* IP address
|
|
*/
|
|
bool
|
|
http_uri_host_is_literal (const http_uri *uri);
|
|
|
|
/* Set URI host into the literal IP address.
|
|
*/
|
|
void
|
|
http_uri_set_host_addr (http_uri *uri, ip_addr addr);
|
|
|
|
/* Fix URI host: if `match` is NULL or uri's host matches `match`,
|
|
* replace uri's host and port with values taken from the base_uri
|
|
*/
|
|
void
|
|
http_uri_fix_host (http_uri *uri, const http_uri *base_uri, const char *match);
|
|
|
|
/* Fix IPv6 address zone suffix
|
|
*/
|
|
void
|
|
http_uri_fix_ipv6_zone (http_uri *uri, int ifindex);
|
|
|
|
/* Strip zone suffix from literal IPv6 host address
|
|
*
|
|
* If address is not IPv6 or doesn't have zone suffix, it is
|
|
* not changed
|
|
*/
|
|
void
|
|
http_uri_strip_zone_suffux (http_uri *uri);
|
|
|
|
/* Make sure URI's path ends with the slash character
|
|
*/
|
|
void
|
|
http_uri_fix_end_slash (http_uri *uri);
|
|
|
|
/* Check if 2 URIs are equal
|
|
*/
|
|
bool
|
|
http_uri_equal (const http_uri *uri1, const http_uri *uri2);
|
|
|
|
/* HTTP data
|
|
*/
|
|
typedef struct {
|
|
const char *content_type; /* Normalized: low-case with stripped directives */
|
|
const void *bytes; /* Data bytes */
|
|
size_t size; /* Data size */
|
|
} http_data;
|
|
|
|
/* Ref http_data
|
|
*/
|
|
http_data*
|
|
http_data_ref (http_data *data);
|
|
|
|
/* Unref http_data
|
|
*/
|
|
void
|
|
http_data_unref (http_data *data);
|
|
|
|
/* http_data_queue represents a queue of http_data items
|
|
*/
|
|
typedef struct http_data_queue http_data_queue;
|
|
|
|
/* Create new http_data_queue
|
|
*/
|
|
http_data_queue*
|
|
http_data_queue_new (void);
|
|
|
|
/* Destroy http_data_queue
|
|
*/
|
|
void
|
|
http_data_queue_free (http_data_queue *queue);
|
|
|
|
/* Push item into the http_data_queue.
|
|
*/
|
|
void
|
|
http_data_queue_push (http_data_queue *queue, http_data *data);
|
|
|
|
/* Pull an item from the http_data_queue. Returns NULL if queue is empty
|
|
*/
|
|
http_data*
|
|
http_data_queue_pull (http_data_queue *queue);
|
|
|
|
/* Get queue length
|
|
*/
|
|
int
|
|
http_data_queue_len (const http_data_queue *queue);
|
|
|
|
/* Check if queue is empty
|
|
*/
|
|
static inline bool
|
|
http_data_queue_empty (const http_data_queue *queue)
|
|
{
|
|
return http_data_queue_len(queue) == 0;
|
|
}
|
|
|
|
/* Purge the queue
|
|
*/
|
|
void
|
|
http_data_queue_purge (http_data_queue *queue);
|
|
|
|
/* Type http_client represents HTTP client instance
|
|
*/
|
|
typedef struct http_client http_client;
|
|
|
|
/* Create new http_client
|
|
*/
|
|
http_client*
|
|
http_client_new (log_ctx *log, void *ptr);
|
|
|
|
/* Destroy http_client
|
|
*/
|
|
void
|
|
http_client_free (http_client *client);
|
|
|
|
/* Set on-error callback. If this callback is not NULL,
|
|
* in a case of transport error it will be called instead
|
|
* of the http_query callback
|
|
*/
|
|
void
|
|
http_client_onerror (http_client *client,
|
|
void (*callback)(void *ptr, error err));
|
|
|
|
/* Cancel all pending queries, if any
|
|
*/
|
|
void
|
|
http_client_cancel (http_client *client);
|
|
|
|
/* Set timeout of all pending queries, if any. Timeout is in milliseconds
|
|
*/
|
|
void
|
|
http_client_timeout (http_client *client, int timeout);
|
|
|
|
/* Check if client has pending queries
|
|
*/
|
|
bool
|
|
http_client_has_pending (const http_client *client);
|
|
|
|
/* Type http_query represents HTTP query (both request and response)
|
|
*/
|
|
typedef struct http_query http_query;
|
|
|
|
/* Create new http_query
|
|
*
|
|
* Newly created http_query takes ownership on uri and body (if not NULL).
|
|
* The method and content_type assumed to be constant strings.
|
|
*/
|
|
http_query*
|
|
http_query_new (http_client *client, http_uri *uri, const char *method,
|
|
char *body, const char *content_type);
|
|
|
|
/* Create new http_query
|
|
*
|
|
* Newly created http_query takes ownership on uri and body (if not NULL).
|
|
* The method and content_type assumed to be constant strings.
|
|
*/
|
|
http_query*
|
|
http_query_new_len (http_client *client, http_uri *uri, const char *method,
|
|
void *body, size_t body_len, const char *content_type);
|
|
|
|
/* Create new http_query, relative to base URI
|
|
*
|
|
* Newly created http_query takes ownership on body (if not NULL).
|
|
* The method and content_type assumed to be constant strings.
|
|
*/
|
|
http_query*
|
|
http_query_new_relative(http_client *client,
|
|
const http_uri *base_uri, const char *path,
|
|
const char *method, char *body, const char *content_type);
|
|
|
|
/* Set query timeout, in milliseconds. Negative timeout means 'infinite'
|
|
*
|
|
* This function may be called multiple times (each subsequent call overrides
|
|
* a previous one)
|
|
*/
|
|
void
|
|
http_query_timeout (http_query *q, int timeout);
|
|
|
|
/* Set 'no_need_response_body' flag
|
|
*
|
|
* This flag notifies, that http_query issued is only interested
|
|
* in the HTTP response headers, not body
|
|
*
|
|
* If this flag is set, after successful reception of response
|
|
* HTTP header, errors in fetching response body is ignored
|
|
*/
|
|
void
|
|
http_query_no_need_response_body (http_query *q);
|
|
|
|
/* Set forcing port to be added to the Host header for this query.
|
|
*
|
|
* This function may be called multiple times (each subsequent call overrides
|
|
* a previous one).
|
|
*/
|
|
void
|
|
http_query_force_port(http_query *q, bool force_port);
|
|
|
|
/* For this particular query override on-error callback, previously
|
|
* set by http_client_onerror()
|
|
*
|
|
* If canllback is NULL, the completion callback, specified on a
|
|
* http_query_submit() call, will be used even in a case of
|
|
* transport error.
|
|
*/
|
|
void
|
|
http_query_onerror (http_query *q, void (*onerror)(void *ptr, error err));
|
|
|
|
/* Set on-redirect callback. It is called in a case of HTTP
|
|
* redirect and may modify the supplied URI
|
|
*/
|
|
void
|
|
http_query_onredir (http_query *q,
|
|
void (*onredir)(void *ptr, http_uri *uri, const http_uri *orig_uri));
|
|
|
|
/* Set callback that will be called, when response headers reception
|
|
* is completed
|
|
*/
|
|
void
|
|
http_query_onrxhdr (http_query *q, void (*onrxhdr)(void *ptr, http_query *q));
|
|
|
|
/* Submit the query.
|
|
*
|
|
* When query is finished, callback will be called. After return from
|
|
* callback, memory, owned by http_query will be invalidated
|
|
*/
|
|
void
|
|
http_query_submit (http_query *q, void (*callback)(void *ptr, http_query *q));
|
|
|
|
/* Get http_query timestamp. Timestamp is set when query is
|
|
* submitted. And this function should not be called before
|
|
* http_query_submit()
|
|
*/
|
|
timestamp
|
|
http_query_timestamp (const http_query *q);
|
|
|
|
/* Set uintptr_t parameter, associated with query.
|
|
* Completion callback may later use http_query_get_uintptr()
|
|
* to fetch this value
|
|
*/
|
|
void
|
|
http_query_set_uintptr (http_query *q, uintptr_t u);
|
|
|
|
/* Get uintptr_t parameter, previously set by http_query_set_uintptr()
|
|
*/
|
|
uintptr_t
|
|
http_query_get_uintptr (http_query *q);
|
|
|
|
/* Get query error, if any
|
|
*
|
|
* Both transport errors and erroneous HTTP response codes
|
|
* considered as errors here
|
|
*/
|
|
error
|
|
http_query_error (const http_query *q);
|
|
|
|
/* Get query transport error, if any
|
|
*
|
|
* Only transport errors considered errors here
|
|
*/
|
|
error
|
|
http_query_transport_error (const http_query *q);
|
|
|
|
/* Get HTTP status code. Code not available, if query finished
|
|
* with error
|
|
*/
|
|
int
|
|
http_query_status (const http_query *q);
|
|
|
|
/* Get HTTP status string
|
|
*/
|
|
const char*
|
|
http_query_status_string (const http_query *q);
|
|
|
|
/* Get query URI
|
|
*
|
|
* It works as http_query_orig_uri() before query is submitted
|
|
* or after it is completed, and as http_query_real_uri() in
|
|
* between
|
|
*
|
|
* This function is deprecated, use http_query_orig_uri()
|
|
* or http_query_real_uri() instead
|
|
*/
|
|
http_uri*
|
|
http_query_uri (const http_query *q);
|
|
|
|
/* Get original URI (the same as used when http_query was created)
|
|
*/
|
|
http_uri*
|
|
http_query_orig_uri (const http_query *q);
|
|
|
|
/* Get real URI, that can differ from the requested URI
|
|
* in a case of HTTP redirection
|
|
*/
|
|
http_uri*
|
|
http_query_real_uri (const http_query *q);
|
|
|
|
/* Get query method
|
|
*/
|
|
const char*
|
|
http_query_method (const http_query *q);
|
|
|
|
/* Set request header
|
|
*/
|
|
void
|
|
http_query_set_request_header (http_query *q, const char *name,
|
|
const char *value);
|
|
|
|
/* Get request header
|
|
*/
|
|
const char*
|
|
http_query_get_request_header (const http_query *q, const char *name);
|
|
|
|
/* Get response header
|
|
*/
|
|
const char*
|
|
http_query_get_response_header (const http_query *q, const char *name);
|
|
|
|
/* Get request data
|
|
*
|
|
* You need to http_data_ref(), if you want data to remain valid
|
|
* after query end of life
|
|
*/
|
|
http_data*
|
|
http_query_get_request_data (const http_query *q);
|
|
|
|
/* Get request data
|
|
*
|
|
* You need to http_data_ref(), if you want data to remain valid
|
|
* after query end of life
|
|
*/
|
|
http_data*
|
|
http_query_get_response_data (const http_query *q);
|
|
|
|
/* Get count of parts of multipart response
|
|
*/
|
|
int
|
|
http_query_get_mp_response_count (const http_query *q);
|
|
|
|
/* Get data of Nth part of multipart response
|
|
*
|
|
* You need to http_data_ref(), if you want data to remain valid
|
|
* after query end of life
|
|
*/
|
|
http_data*
|
|
http_query_get_mp_response_data (const http_query *q, int n);
|
|
|
|
/* Call callback for each request header
|
|
*/
|
|
void
|
|
http_query_foreach_request_header (const http_query *q,
|
|
void (*callback)(const char *name, const char *value, void *ptr),
|
|
void *ptr);
|
|
|
|
/* Call callback for each response header
|
|
*/
|
|
void
|
|
http_query_foreach_response_header (const http_query *q,
|
|
void (*callback)(const char *name, const char *value, void *ptr),
|
|
void *ptr);
|
|
|
|
/* Decode response part of the query.
|
|
* This function is intended for testing purposes, not for regular use
|
|
*/
|
|
error
|
|
http_query_test_decode_response (http_query *q, const void *data, size_t size);
|
|
|
|
/* HTTP schemes
|
|
*/
|
|
typedef enum {
|
|
HTTP_SCHEME_UNSET = -1,
|
|
HTTP_SCHEME_HTTP,
|
|
HTTP_SCHEME_HTTPS,
|
|
HTTP_SCHEME_UNIX
|
|
} HTTP_SCHEME;
|
|
|
|
/* Some HTTP status codes
|
|
*/
|
|
#ifndef NO_HTTP_STATUS
|
|
enum {
|
|
HTTP_STATUS_OK = 200,
|
|
HTTP_STATUS_CREATED = 201,
|
|
HTTP_STATUS_NOT_FOUND = 404,
|
|
HTTP_STATUS_GONE = 410,
|
|
HTTP_STATUS_SERVICE_UNAVAILABLE = 503
|
|
};
|
|
#endif
|
|
|
|
/* Initialize HTTP client
|
|
*/
|
|
SANE_Status
|
|
http_init (void);
|
|
|
|
/* Initialize HTTP client
|
|
*/
|
|
void
|
|
http_cleanup (void);
|
|
|
|
/******************** Protocol trace ********************/
|
|
/* Type trace represents an opaque handle of trace
|
|
* file
|
|
*/
|
|
typedef struct trace trace;
|
|
|
|
/* Initialize protocol trace. Called at backend initialization
|
|
*/
|
|
SANE_Status
|
|
trace_init (void);
|
|
|
|
/* Cleanup protocol trace. Called at backend unload
|
|
*/
|
|
void
|
|
trace_cleanup (void);
|
|
|
|
/* Open protocol trace
|
|
*/
|
|
trace*
|
|
trace_open (const char *device_name);
|
|
|
|
/* Ref the trace
|
|
*/
|
|
trace*
|
|
trace_ref (trace *t);
|
|
|
|
/* Unref the trace. When trace is not longer in use, it will be closed
|
|
*/
|
|
void
|
|
trace_unref (trace *t);
|
|
|
|
/* This hook is called on every http_query completion
|
|
*/
|
|
void
|
|
trace_http_query_hook (trace *t, http_query *q);
|
|
|
|
/* Printf to the trace log
|
|
*/
|
|
void
|
|
trace_printf (trace *t, const char *fmt, ...);
|
|
|
|
/* Note an error in trace log
|
|
*/
|
|
void
|
|
trace_error (trace *t, error err);
|
|
|
|
/* Dump message body
|
|
*/
|
|
void
|
|
trace_dump_body (trace *t, http_data *data);
|
|
|
|
/* Dump binary data (as hex dump)
|
|
* Each line is prefixed with the `prefix` character
|
|
*/
|
|
void
|
|
trace_hexdump (trace *t, char prefix, const void *data, size_t size);
|
|
|
|
/******************** SANE_Word/SANE_String arrays ********************/
|
|
/* Create array of SANE_Word
|
|
*/
|
|
static inline SANE_Word*
|
|
sane_word_array_new (void)
|
|
{
|
|
return mem_new(SANE_Word,1);
|
|
}
|
|
|
|
/* Free array of SANE_Word
|
|
*/
|
|
static inline void
|
|
sane_word_array_free (SANE_Word *a)
|
|
{
|
|
mem_free(a);
|
|
}
|
|
|
|
/* Reset array of SANE_Word
|
|
*/
|
|
static inline void
|
|
sane_word_array_reset (SANE_Word **a)
|
|
{
|
|
(*a)[0] = 0;
|
|
}
|
|
|
|
/* Get length of the SANE_Word array
|
|
*/
|
|
static inline size_t
|
|
sane_word_array_len (const SANE_Word *a)
|
|
{
|
|
return (size_t) a[0];
|
|
}
|
|
|
|
/* Append word to array. Returns new array (old becomes invalid)
|
|
*/
|
|
static inline SANE_Word*
|
|
sane_word_array_append (SANE_Word *a, SANE_Word w)
|
|
{
|
|
size_t len = sane_word_array_len(a) + 1;
|
|
a = mem_resize(a, len + 1, 0);
|
|
a[0] = len;
|
|
a[len] = w;
|
|
return a;
|
|
}
|
|
|
|
/* Drop array elements that outside of specified boundary
|
|
*/
|
|
void
|
|
sane_word_array_bound (SANE_Word *a, SANE_Word min, SANE_Word max);
|
|
|
|
/* Sort array of SANE_Word in increasing order
|
|
*/
|
|
void
|
|
sane_word_array_sort (SANE_Word *a);
|
|
|
|
/* Intersect two sorted arrays.
|
|
*/
|
|
SANE_Word*
|
|
sane_word_array_intersect_sorted ( const SANE_Word *a1, const SANE_Word *a2);
|
|
|
|
/* Create array of SANE_String
|
|
*/
|
|
static inline SANE_String*
|
|
sane_string_array_new (void)
|
|
{
|
|
return ptr_array_new(SANE_String);
|
|
}
|
|
|
|
/* Free array of SANE_String
|
|
*/
|
|
static inline void
|
|
sane_string_array_free (SANE_String *a)
|
|
{
|
|
mem_free(a);
|
|
}
|
|
|
|
/* Reset array of SANE_String
|
|
*/
|
|
static inline void
|
|
sane_string_array_reset (SANE_String *a)
|
|
{
|
|
ptr_array_trunc(a);
|
|
}
|
|
|
|
/* Get length of the SANE_String array
|
|
*/
|
|
static inline size_t
|
|
sane_string_array_len (const SANE_String *a)
|
|
{
|
|
return mem_len(a);
|
|
}
|
|
|
|
/* Append string to array Returns new array (old becomes invalid)
|
|
*/
|
|
static inline SANE_String*
|
|
sane_string_array_append(SANE_String *a, SANE_String s)
|
|
{
|
|
return ptr_array_append(a, s);
|
|
}
|
|
|
|
/* Compute max string length in array of strings
|
|
*/
|
|
size_t
|
|
sane_string_array_max_strlen(const SANE_String *a);
|
|
|
|
/* Create array of SANE_Device
|
|
*/
|
|
static inline const SANE_Device**
|
|
sane_device_array_new (void)
|
|
{
|
|
return ptr_array_new(const SANE_Device*);
|
|
}
|
|
|
|
/* Free array of SANE_Device
|
|
*/
|
|
static inline void
|
|
sane_device_array_free (const SANE_Device **a)
|
|
{
|
|
mem_free(a);
|
|
}
|
|
|
|
/* Get length of the SANE_Device array
|
|
*/
|
|
static inline size_t
|
|
sane_device_array_len (const SANE_Device * const *a)
|
|
{
|
|
return mem_len(a);
|
|
}
|
|
|
|
/* Append device to array. Returns new array (old becomes invalid)
|
|
*/
|
|
static inline const SANE_Device**
|
|
sane_device_array_append(const SANE_Device **a, SANE_Device *d)
|
|
{
|
|
return ptr_array_append(a, d);
|
|
}
|
|
|
|
/******************** XML utilities ********************/
|
|
/* xml_ns defines XML namespace.
|
|
*
|
|
* For XML writer namespaces are simply added to the root
|
|
* node attributes
|
|
*
|
|
* XML reader performs prefix substitutions
|
|
*
|
|
* If namespace substitution is enabled, for each note, if its
|
|
* namespace matches the pattern, will be reported with name prefix
|
|
* defined by substitution rule, regardless of prefix actually used
|
|
* in the document
|
|
*
|
|
* Example:
|
|
* <namespace:nodes xmlns:namespace="http://www.example.com/namespace">
|
|
* <namespace:node1/>
|
|
* <namespace:node2/>
|
|
* <namespace:node3/>
|
|
* </namespace:nodes>
|
|
*
|
|
* rule: {"ns", "http://www.example.com/namespace"}
|
|
*
|
|
* With this rule set, all nodes will be reported as if they
|
|
* had the "ns" prefix, though actually their prefix in document
|
|
* is different
|
|
*
|
|
* XML reader interprets namespace uri as a glob-style pattern,
|
|
* as used by fnmatch (3) function with flags = 0
|
|
*/
|
|
typedef struct {
|
|
const char *prefix; /* Short prefix */
|
|
const char *uri; /* The namespace uri (glob pattern for reader) */
|
|
} xml_ns;
|
|
|
|
/* xml_attr represents an XML attribute.
|
|
*
|
|
* Attributes are supported by XML writer. Array of attributes
|
|
* is terminated by the {NULL, NULL} attribute
|
|
*/
|
|
typedef struct {
|
|
const char *name; /* Attribute name */
|
|
const char *value; /* Attribute value */
|
|
} xml_attr;
|
|
|
|
/* XML reader
|
|
*/
|
|
typedef struct xml_rd xml_rd;
|
|
|
|
/* Parse XML text and initialize reader to iterate
|
|
* starting from the root node
|
|
*
|
|
* The 'ns' argument, if not NULL, points to array of substitution
|
|
* rules. Last element must have NULL prefix and url
|
|
*
|
|
* Array of rules considered to be statically allocated
|
|
* (at least, it can remain valid during reader life time)
|
|
*
|
|
* On success, saves newly constructed reader into
|
|
* the xml parameter.
|
|
*/
|
|
error
|
|
xml_rd_begin (xml_rd **xml, const char *xml_text, size_t xml_len,
|
|
const xml_ns *ns);
|
|
|
|
/* Finish reading, free allocated resources
|
|
*/
|
|
void
|
|
xml_rd_finish (xml_rd **xml);
|
|
|
|
/* Get current node depth in the tree. Root depth is 0
|
|
*/
|
|
unsigned int
|
|
xml_rd_depth (xml_rd *xml);
|
|
|
|
/* Check for end-of-document condition
|
|
*/
|
|
bool
|
|
xml_rd_end (xml_rd *xml);
|
|
|
|
/* Shift to the next node
|
|
*/
|
|
void
|
|
xml_rd_next (xml_rd *xml);
|
|
|
|
/* Shift to the next node, visiting the nested nodes on the way
|
|
*
|
|
* If depth > 0, it will not return from nested nodes
|
|
* upper the specified depth
|
|
*/
|
|
void
|
|
xml_rd_deep_next (xml_rd *xml, unsigned int depth);
|
|
|
|
/* Enter the current node - iterate its children
|
|
*/
|
|
void
|
|
xml_rd_enter (xml_rd *xml);
|
|
|
|
/* Leave the current node - return to its parent
|
|
*/
|
|
void
|
|
xml_rd_leave (xml_rd *xml);
|
|
|
|
/* Get name of the current node.
|
|
*
|
|
* The returned string remains valid, until reader is cleaned up
|
|
* or current node is changed (by set/next/enter/leave operations).
|
|
* You don't need to free this string explicitly
|
|
*/
|
|
const char*
|
|
xml_rd_node_name (xml_rd *xml);
|
|
|
|
/* Get full path to the current node, '/'-separated
|
|
*/
|
|
const char*
|
|
xml_rd_node_path (xml_rd *xml);
|
|
|
|
/* Match name of the current node against the pattern
|
|
*/
|
|
bool
|
|
xml_rd_node_name_match (xml_rd *xml, const char *pattern);
|
|
|
|
/* Get value of the current node as text
|
|
*
|
|
* The returned string remains valid, until reader is cleaned up
|
|
* or current node is changed (by set/next/enter/leave operations).
|
|
* You don't need to free this string explicitly
|
|
*/
|
|
const char*
|
|
xml_rd_node_value (xml_rd *xml);
|
|
|
|
/* Get value of the current node as unsigned integer
|
|
*/
|
|
error
|
|
xml_rd_node_value_uint (xml_rd *xml, SANE_Word *val);
|
|
|
|
/* XML writer
|
|
*/
|
|
typedef struct xml_wr xml_wr;
|
|
|
|
/* Begin writing XML document. Root node will be created automatically
|
|
*
|
|
* The ns parameter must be terminated by {NULL, NULL} structure
|
|
*/
|
|
xml_wr*
|
|
xml_wr_begin (const char *root, const xml_ns *ns);
|
|
|
|
/* Finish writing, generate document string.
|
|
* Caller must g_free() this string after use
|
|
*/
|
|
char*
|
|
xml_wr_finish (xml_wr *xml);
|
|
|
|
/* Like xml_wr_finish, but returns compact representation
|
|
* of XML (without indentation and new lines)
|
|
*/
|
|
char*
|
|
xml_wr_finish_compact (xml_wr *xml);
|
|
|
|
/* Add node with textual value
|
|
*/
|
|
void
|
|
xml_wr_add_text (xml_wr *xml, const char *name, const char *value);
|
|
|
|
/* Add text node with attributes
|
|
*/
|
|
void
|
|
xml_wr_add_text_attr (xml_wr *xml, const char *name, const char *value,
|
|
const xml_attr *attrs);
|
|
|
|
/* Add node with unsigned integer value
|
|
*/
|
|
void
|
|
xml_wr_add_uint (xml_wr *xml, const char *name, unsigned int value);
|
|
|
|
/* Add node with unsigned integer value and attributes
|
|
*/
|
|
void
|
|
xml_wr_add_uint_attr (xml_wr *xml, const char *name, unsigned int value,
|
|
const xml_attr *attrs);
|
|
|
|
/* Add node with boolean value
|
|
*/
|
|
void
|
|
xml_wr_add_bool (xml_wr *xml, const char *name, bool value);
|
|
|
|
/* Add node with boolean value and attributes
|
|
*/
|
|
void
|
|
xml_wr_add_bool_attr (xml_wr *xml, const char *name, bool value,
|
|
const xml_attr *attrs);
|
|
|
|
/* Create node with children and enter newly added node
|
|
*/
|
|
void
|
|
xml_wr_enter (xml_wr *xml, const char *name);
|
|
|
|
/* xml_wr_enter with attributes
|
|
*/
|
|
void
|
|
xml_wr_enter_attr (xml_wr *xml, const char *name, const xml_attr *attrs);
|
|
|
|
/* Leave the current node
|
|
*/
|
|
void
|
|
xml_wr_leave (xml_wr *xml);
|
|
|
|
/* Format XML to file. It either succeeds, writes a formatted XML
|
|
* and returns true, or fails, writes nothing to file and returns false
|
|
*/
|
|
bool
|
|
xml_format (FILE *fp, const char *xml_text, size_t xml_len);
|
|
|
|
/******************** Sane Options********************/
|
|
/* Options numbers, for internal use
|
|
*/
|
|
enum {
|
|
OPT_NUM_OPTIONS, /* Total number of options */
|
|
|
|
/* Standard options group */
|
|
OPT_GROUP_STANDARD,
|
|
OPT_SCAN_RESOLUTION,
|
|
OPT_SCAN_COLORMODE, /* I.e. color/grayscale etc */
|
|
OPT_SCAN_INTENT, /* Document/Photo etc */
|
|
OPT_SCAN_SOURCE, /* Platem/ADF/ADF Duplex */
|
|
|
|
/* Geometry options group */
|
|
OPT_GROUP_GEOMETRY,
|
|
OPT_SCAN_TL_X,
|
|
OPT_SCAN_TL_Y,
|
|
OPT_SCAN_BR_X,
|
|
OPT_SCAN_BR_Y,
|
|
|
|
/* Image enhancement group */
|
|
OPT_GROUP_ENHANCEMENT,
|
|
OPT_BRIGHTNESS,
|
|
OPT_CONTRAST,
|
|
OPT_SHADOW,
|
|
OPT_HIGHLIGHT,
|
|
OPT_GAMMA,
|
|
OPT_NEGATIVE,
|
|
|
|
/* Read-only options for ADF justification */
|
|
OPT_JUSTIFICATION_X,
|
|
OPT_JUSTIFICATION_Y,
|
|
|
|
/* Total count of options, computed by compiler */
|
|
NUM_OPTIONS
|
|
};
|
|
|
|
/* String constants for certain SANE options values
|
|
* (missed from sane/sameopt.h)
|
|
*/
|
|
#define OPTVAL_SOURCE_PLATEN "Flatbed"
|
|
#define OPTVAL_SOURCE_ADF_SIMPLEX "ADF"
|
|
#define OPTVAL_SOURCE_ADF_DUPLEX "ADF Duplex"
|
|
#define OPTVAL_JUSTIFICATION_LEFT "left"
|
|
#define OPTVAL_JUSTIFICATION_CENTER "center"
|
|
#define OPTVAL_JUSTIFICATION_RIGHT "right"
|
|
#define OPTVAL_JUSTIFICATION_TOP "top"
|
|
#define OPTVAL_JUSTIFICATION_BOTTOM "bottom"
|
|
|
|
/* Define options not included in saneopts.h */
|
|
#define SANE_NAME_ADF_JUSTIFICATION_X "adf-justification-x"
|
|
#define SANE_TITLE_ADF_JUSTIFICATION_X SANE_I18N("ADF Width Justification")
|
|
#define SANE_DESC_ADF_JUSTIFICATION_X \
|
|
SANE_I18N("ADF width justification (left/right/center)")
|
|
|
|
#define SANE_NAME_ADF_JUSTIFICATION_Y "adf-justification-y"
|
|
#define SANE_TITLE_ADF_JUSTIFICATION_Y SANE_I18N("ADF Height Justification")
|
|
#define SANE_DESC_ADF_JUSTIFICATION_Y \
|
|
SANE_I18N("ADF height justification (top/bottom/center)")
|
|
|
|
/* Check if option belongs to image enhancement group
|
|
*/
|
|
static inline bool
|
|
opt_is_enhancement (int opt)
|
|
{
|
|
return OPT_BRIGHTNESS <= opt && opt <= OPT_NEGATIVE;
|
|
}
|
|
|
|
/******************** Device Capabilities ********************/
|
|
/* Source flags
|
|
*/
|
|
enum {
|
|
/* Supported Intents */
|
|
DEVCAPS_SOURCE_INTENT_DOCUMENT = (1 << 3),
|
|
DEVCAPS_SOURCE_INTENT_TXT_AND_GRAPH = (1 << 4),
|
|
DEVCAPS_SOURCE_INTENT_PHOTO = (1 << 5),
|
|
DEVCAPS_SOURCE_INTENT_PREVIEW = (1 << 6),
|
|
|
|
DEVCAPS_SOURCE_INTENT_ALL =
|
|
DEVCAPS_SOURCE_INTENT_DOCUMENT |
|
|
DEVCAPS_SOURCE_INTENT_TXT_AND_GRAPH |
|
|
DEVCAPS_SOURCE_INTENT_PHOTO |
|
|
DEVCAPS_SOURCE_INTENT_PREVIEW,
|
|
|
|
/* How resolutions are defined */
|
|
DEVCAPS_SOURCE_RES_DISCRETE = (1 << 7), /* Discrete resolutions */
|
|
DEVCAPS_SOURCE_RES_RANGE = (1 << 8), /* Range of resolutions */
|
|
|
|
DEVCAPS_SOURCE_RES_ALL =
|
|
DEVCAPS_SOURCE_RES_DISCRETE |
|
|
DEVCAPS_SOURCE_RES_RANGE,
|
|
|
|
/* Miscellaneous flags */
|
|
DEVCAPS_SOURCE_HAS_SIZE = (1 << 12), /* max_width, max_height and
|
|
derivatives are valid */
|
|
|
|
/* Protocol dialects */
|
|
DEVCAPS_SOURCE_PWG_DOCFMT = (1 << 13), /* pwg:DocumentFormat */
|
|
DEVCAPS_SOURCE_SCAN_DOCFMT_EXT = (1 << 14), /* scan:DocumentFormatExt */
|
|
};
|
|
|
|
/* Supported image formats
|
|
*/
|
|
#define DEVCAPS_FORMATS_SUPPORTED \
|
|
((1 << ID_FORMAT_JPEG) | \
|
|
(1 << ID_FORMAT_PNG) | \
|
|
(1 << ID_FORMAT_TIFF) | \
|
|
(1 << ID_FORMAT_BMP))
|
|
|
|
/* Supported color modes
|
|
*
|
|
* Note, currently the only image format we support is JPEG
|
|
* With JPEG, ID_COLORMODE_BW1 cannot be supported
|
|
*/
|
|
#define DEVCAPS_COLORMODES_SUPPORTED \
|
|
((1 << ID_COLORMODE_COLOR) | \
|
|
(1 << ID_COLORMODE_GRAYSCALE))
|
|
|
|
/* Source Capabilities (each device may contain multiple sources)
|
|
*/
|
|
typedef struct {
|
|
unsigned int flags; /* Source flags */
|
|
unsigned int colormodes; /* Set of 1 << ID_COLORMODE */
|
|
unsigned int formats; /* Set of 1 << ID_FORMAT */
|
|
unsigned int scanintents; /* Set of 1 << ID_SCANINTENT */
|
|
SANE_Word min_wid_px, max_wid_px; /* Min/max width, in pixels */
|
|
SANE_Word min_hei_px, max_hei_px; /* Min/max height, in pixels */
|
|
SANE_Word *resolutions; /* Discrete resolutions, in DPI */
|
|
SANE_Range res_range; /* Resolutions range, in DPI */
|
|
SANE_Range win_x_range_mm; /* Window x range, in mm */
|
|
SANE_Range win_y_range_mm; /* Window y range, in mm */
|
|
} devcaps_source;
|
|
|
|
/* Allocate devcaps_source
|
|
*/
|
|
devcaps_source*
|
|
devcaps_source_new (void);
|
|
|
|
/* Free devcaps_source
|
|
*/
|
|
void
|
|
devcaps_source_free (devcaps_source *src);
|
|
|
|
/* Clone a source
|
|
*/
|
|
devcaps_source*
|
|
devcaps_source_clone (const devcaps_source *src);
|
|
|
|
/* Merge two sources, resulting the source that contains
|
|
* only capabilities, supported by two input sources
|
|
*
|
|
* Returns NULL, if sources cannot be merged
|
|
*/
|
|
devcaps_source*
|
|
devcaps_source_merge (const devcaps_source *s1, const devcaps_source *s2);
|
|
|
|
/* Device Capabilities
|
|
*/
|
|
typedef struct {
|
|
/* Fundamental values */
|
|
const char *protocol; /* Protocol name */
|
|
SANE_Word units; /* Size units, pixels per inch */
|
|
|
|
/* Image compression */
|
|
bool compression_ok; /* Compression params are supported */
|
|
SANE_Range compression_range; /* Compression range */
|
|
SANE_Word compression_norm; /* Normal compression */
|
|
|
|
/* Sources */
|
|
devcaps_source *src[NUM_ID_SOURCE]; /* Missed sources are NULL */
|
|
|
|
/* ADF Justification */
|
|
ID_JUSTIFICATION justification_x; /* Width justification*/
|
|
ID_JUSTIFICATION justification_y; /* Height justification*/
|
|
|
|
} devcaps;
|
|
|
|
/* Initialize Device Capabilities
|
|
*/
|
|
void
|
|
devcaps_init (devcaps *caps);
|
|
|
|
/* Cleanup Device Capabilities
|
|
*/
|
|
void
|
|
devcaps_cleanup (devcaps *caps);
|
|
|
|
/* Reset Device Capabilities into initial state
|
|
*/
|
|
void
|
|
devcaps_reset (devcaps *caps);
|
|
|
|
/* Dump device capabilities, for debugging
|
|
*
|
|
* The 3rd parameter, 'trace' configures the debug level
|
|
* (log_debug vs log_trace) of the generated output
|
|
*/
|
|
void
|
|
devcaps_dump (log_ctx *log, devcaps *caps, bool trace);
|
|
|
|
/******************** Device options ********************/
|
|
/* Scan options
|
|
*/
|
|
typedef struct {
|
|
devcaps caps; /* Device capabilities */
|
|
SANE_Option_Descriptor desc[NUM_OPTIONS]; /* Option descriptors */
|
|
ID_SOURCE src; /* Current source */
|
|
ID_COLORMODE colormode_emul; /* Current "emulated" color mode*/
|
|
ID_COLORMODE colormode_real; /* Current real color mode*/
|
|
ID_SCANINTENT scanintent; /* Current scan intent */
|
|
SANE_Word resolution; /* Current resolution */
|
|
SANE_Fixed tl_x, tl_y; /* Top-left x/y */
|
|
SANE_Fixed br_x, br_y; /* Bottom-right x/y */
|
|
SANE_Parameters params; /* Scan parameters */
|
|
SANE_String *sane_sources; /* Sources, in SANE format */
|
|
SANE_String *sane_colormodes; /* Color modes in SANE format */
|
|
SANE_String *sane_scanintents; /* Scan intents in SANE format */
|
|
SANE_Fixed brightness; /* -100.0 ... +100.0 */
|
|
SANE_Fixed contrast; /* -100.0 ... +100.0 */
|
|
SANE_Fixed shadow; /* 0.0 ... +100.0 */
|
|
SANE_Fixed highlight; /* 0.0 ... +100.0 */
|
|
SANE_Fixed gamma; /* Small positive value */
|
|
bool negative; /* Flip black and white */
|
|
|
|
} devopt;
|
|
|
|
/* Initialize device options
|
|
*/
|
|
void
|
|
devopt_init (devopt *opt);
|
|
|
|
/* Cleanup device options
|
|
*/
|
|
void
|
|
devopt_cleanup (devopt *opt);
|
|
|
|
/* Set default option values. Before call to this function,
|
|
* devopt.caps needs to be properly filled.
|
|
*/
|
|
void
|
|
devopt_set_defaults (devopt *opt);
|
|
|
|
/* Set device option
|
|
*/
|
|
SANE_Status
|
|
devopt_set_option (devopt *opt, SANE_Int option, void *value, SANE_Word *info);
|
|
|
|
/* Get device option
|
|
*/
|
|
SANE_Status
|
|
devopt_get_option (devopt *opt, SANE_Int option, void *value);
|
|
|
|
/******************** ZeroConf (device discovery) ********************/
|
|
/* Due to the way how device discovery is implemented, resolving
|
|
* of device IP addresses are independent between IPv4/IPv6 protocols
|
|
* and between different network interfaces
|
|
*
|
|
* It means that some of device addresses may be already discovered,
|
|
* while others still pending
|
|
*
|
|
* From another hand, some of addresses that we hope to discover may
|
|
* be not available at all. For example, device may have IPv4 address
|
|
* but IPv6 address may be missed.
|
|
*
|
|
* So once we have at least one address discovered, we limit discovery
|
|
* of another addresses by this constant.
|
|
*
|
|
* This parameter is common for both MDNS and WSDD worlds
|
|
*
|
|
* The timeout is in milliseconds
|
|
*/
|
|
#define ZEROCONF_PUBLISH_DELAY 1000
|
|
|
|
/* Common logging context for device discovery
|
|
*/
|
|
extern log_ctx *zeroconf_log;
|
|
|
|
/* zeroconf_device represents a single device
|
|
*/
|
|
typedef struct zeroconf_device zeroconf_device;
|
|
|
|
/* zeroconf_endpoint represents a device endpoint
|
|
*/
|
|
typedef struct zeroconf_endpoint zeroconf_endpoint;
|
|
struct zeroconf_endpoint {
|
|
ID_PROTO proto; /* The protocol */
|
|
http_uri *uri; /* I.e, "http://192.168.1.1:8080/eSCL/" */
|
|
zeroconf_endpoint *next; /* Next endpoint in the list */
|
|
};
|
|
|
|
/* ZEROCONF_METHOD represents a method how device was discovered
|
|
* The same device may be discovered using multiple methods
|
|
*/
|
|
typedef enum {
|
|
/* The following findings serve as indirect signs of
|
|
* scanner presence in the network
|
|
*/
|
|
ZEROCONF_MDNS_HINT, /* Hint finding from MDNS world */
|
|
|
|
/* The following findings are expected to bring actual
|
|
* scanner endpoints
|
|
*/
|
|
ZEROCONF_USCAN_TCP, /* _uscan._tcp */
|
|
ZEROCONF_USCANS_TCP, /* _uscans._tcp */
|
|
ZEROCONF_WSD, /* WS-Discovery */
|
|
|
|
NUM_ZEROCONF_METHOD
|
|
} ZEROCONF_METHOD;
|
|
|
|
/* zeroconf_finding represents a single device discovery finding.
|
|
* Multiple findings can point to the same device, and even
|
|
* endpoints may duplicate between findings (say, if the same
|
|
* device found using multiple network interfaces or using various
|
|
* discovery methods)
|
|
*
|
|
* zeroconf_finding are bound to method and interface index
|
|
*/
|
|
typedef struct {
|
|
ZEROCONF_METHOD method; /* Discovery method */
|
|
const char *name; /* Network-unique name, NULL for WSD */
|
|
const char *model; /* Model name, may be NULL for
|
|
WSDD non-scanner devices */
|
|
uuid uuid; /* Device UUID */
|
|
ip_addrset *addrs; /* Device addresses */
|
|
int ifindex; /* Network interface index */
|
|
zeroconf_endpoint *endpoints; /* List of endpoints */
|
|
|
|
/* The following fields are reserved for zeroconf core
|
|
* and should not be used by discovery providers
|
|
*/
|
|
zeroconf_device *device; /* Device the finding points to */
|
|
ll_node list_node; /* Node in device's list of findings */
|
|
} zeroconf_finding;
|
|
|
|
/* Compare two pointers to pointers to zeroconf_finding (zeroconf_finding**)
|
|
* by index+name, for qsort
|
|
*/
|
|
int
|
|
zeroconf_finding_qsort_by_index_name (const void *p1, const void *p2);
|
|
|
|
/* Publish the zeroconf_finding.
|
|
*
|
|
* Memory, referred by the finding, remains owned by
|
|
* caller, and caller is responsible to keep this
|
|
* memory valid until zeroconf_finding_withdraw()
|
|
* is called
|
|
*
|
|
* The 'endpoinds' field may be NULL. This mechanism is
|
|
* used by WS-Discovery to notify zeroconf that scanning
|
|
* for particular UUID has been finished, though without
|
|
* success.
|
|
*/
|
|
void
|
|
zeroconf_finding_publish (zeroconf_finding *finding);
|
|
|
|
/* Withdraw the finding
|
|
*/
|
|
void
|
|
zeroconf_finding_withdraw (zeroconf_finding *finding);
|
|
|
|
/* Notify zeroconf subsystem that initial scan
|
|
* for the method is done
|
|
*/
|
|
void
|
|
zeroconf_finding_done (ZEROCONF_METHOD method);
|
|
|
|
/* zeroconf_devinfo represents a device information
|
|
*/
|
|
typedef struct {
|
|
const char *ident; /* Unique ident */
|
|
const char *name; /* Human-friendly name */
|
|
const char *model; /* Model name, for quirks. "" if unknown */
|
|
zeroconf_endpoint *endpoints; /* Device endpoints */
|
|
} zeroconf_devinfo;
|
|
|
|
/* Initialize ZeroConf
|
|
*/
|
|
SANE_Status
|
|
zeroconf_init (void);
|
|
|
|
/* Cleanup ZeroConf
|
|
*/
|
|
void
|
|
zeroconf_cleanup (void);
|
|
|
|
/* Get list of devices, in SANE format
|
|
*/
|
|
const SANE_Device**
|
|
zeroconf_device_list_get (void);
|
|
|
|
/* Free list of devices, returned by zeroconf_device_list_get()
|
|
*/
|
|
void
|
|
zeroconf_device_list_free (const SANE_Device **dev_list);
|
|
|
|
/* Lookup device by ident (ident is reported as SANE_Device::name)
|
|
* by zeroconf_device_list_get())
|
|
*
|
|
* Caller becomes owner of resources (name and list of endpoints),
|
|
* referred by the returned zeroconf_devinfo
|
|
*
|
|
* Caller must free these resources, using zeroconf_devinfo_free()
|
|
*/
|
|
zeroconf_devinfo*
|
|
zeroconf_devinfo_lookup (const char *ident);
|
|
|
|
/*
|
|
* The format "protocol:name:url" is accepted to directly specify a device
|
|
* without listing it in the config or finding it with autodiscovery. Try
|
|
* to parse an identifier as that format. On success, returns a newly allocated
|
|
* zeroconf_devinfo that the caller must free with zeroconf_devinfo_free(). On
|
|
* failure, returns NULL.
|
|
*/
|
|
zeroconf_devinfo*
|
|
zeroconf_parse_devinfo_from_ident(const char *ident);
|
|
|
|
/* Free zeroconf_devinfo, returned by zeroconf_devinfo_lookup()
|
|
*/
|
|
void
|
|
zeroconf_devinfo_free (zeroconf_devinfo *devinfo);
|
|
|
|
/* Create new zeroconf_endpoint. Newly created endpoint
|
|
* takes ownership of uri string
|
|
*/
|
|
zeroconf_endpoint*
|
|
zeroconf_endpoint_new (ID_PROTO proto, http_uri *uri);
|
|
|
|
/* Free single zeroconf_endpoint
|
|
*/
|
|
void
|
|
zeroconf_endpoint_free_single (zeroconf_endpoint *endpoint);
|
|
|
|
/* Create a copy of zeroconf_endpoint list
|
|
*/
|
|
zeroconf_endpoint*
|
|
zeroconf_endpoint_list_copy (const zeroconf_endpoint *list);
|
|
|
|
/* Free zeroconf_endpoint list
|
|
*/
|
|
void
|
|
zeroconf_endpoint_list_free (zeroconf_endpoint *list);
|
|
|
|
/* Sort list of endpoints
|
|
*/
|
|
zeroconf_endpoint*
|
|
zeroconf_endpoint_list_sort (zeroconf_endpoint *list);
|
|
|
|
/* Sort list of endpoints and remove duplicates
|
|
*/
|
|
zeroconf_endpoint*
|
|
zeroconf_endpoint_list_sort_dedup (zeroconf_endpoint *list);
|
|
|
|
/* Check if list of endpoints already contains the given
|
|
* endpoint (i.e., endpoint with the same URI and protocol)
|
|
*/
|
|
bool
|
|
zeroconf_endpoint_list_contains (const zeroconf_endpoint *list,
|
|
const zeroconf_endpoint *endpoint);
|
|
|
|
/* Check if endpoints list contains a non-link-local address
|
|
* of the specified address family
|
|
*/
|
|
bool
|
|
zeroconf_endpoint_list_has_non_link_local_addr (int af,
|
|
const zeroconf_endpoint *list);
|
|
|
|
/******************** MDNS Discovery ********************/
|
|
/* Called by zeroconf to notify MDNS about initial scan timer expiration
|
|
*/
|
|
void
|
|
mdns_initscan_timer_expired (void);
|
|
|
|
/* Initialize MDNS
|
|
*/
|
|
SANE_Status
|
|
mdns_init (void);
|
|
|
|
/* Cleanup MDNS
|
|
*/
|
|
void
|
|
mdns_cleanup (void);
|
|
|
|
/* mdns_resolver asynchronously resolves IP addresses using MDNS
|
|
*/
|
|
typedef struct mdns_resolver mdns_resolver;
|
|
|
|
/* mdns_query represents a single mdns_resolver query
|
|
*/
|
|
typedef struct mdns_query mdns_query;
|
|
|
|
/* mdns_resolver_new creates a new MDNS resolver
|
|
*/
|
|
mdns_resolver*
|
|
mdns_resolver_new (int ifindex);
|
|
|
|
/* mdns_resolver_free frees the mdns_resolver previously created
|
|
* by mdns_resolver_new()
|
|
*/
|
|
void
|
|
mdns_resolver_free (mdns_resolver *resolver);
|
|
|
|
/* mdns_resolver_cancel cancels all pending queries
|
|
*/
|
|
void
|
|
mdns_resolver_cancel (mdns_resolver *resolver);
|
|
|
|
/* mdns_resolver_has_pending checks if resolver has pending queries
|
|
*/
|
|
bool
|
|
mdns_resolver_has_pending (mdns_resolver *resolver);
|
|
|
|
/* mdns_query_submit submits a new MDNS query for the specified domain
|
|
* name. When resolving is done, successfully or not, callback will be
|
|
* called
|
|
*
|
|
* The ptr parameter is passed to the callback without any interpretation
|
|
* as a user-defined argument
|
|
*
|
|
* Answer is a set of discovered IP addresses. It is owned by resolver,
|
|
* callback should not free it and should not assume that it is still
|
|
* valid after return from callback
|
|
*/
|
|
mdns_query*
|
|
mdns_query_submit (mdns_resolver *resolver,
|
|
const char *name,
|
|
void (*callback)(const mdns_query *query),
|
|
void *ptr);
|
|
|
|
/* mdns_query_cancel cancels the pending query. mdns_query memory will
|
|
* be released and callback will not be called
|
|
*
|
|
* Note, mdns_query pointer is valid when obtained from mdns_query_sumbit
|
|
* and until canceled or return from callback.
|
|
*/
|
|
void
|
|
mdns_query_cancel (mdns_query *query);
|
|
|
|
/* mdns_query_get_name returns domain name, as it was specified
|
|
* when query was submitted
|
|
*/
|
|
const char*
|
|
mdns_query_get_name (const mdns_query *query);
|
|
|
|
/* mdns_query_get_answer returns resolved addresses
|
|
*/
|
|
const ip_addrset*
|
|
mdns_query_get_answer (const mdns_query *query);
|
|
|
|
/* mdns_query_set_ptr gets the user-defined ptr, associated
|
|
* with query when it was submitted
|
|
*/
|
|
void*
|
|
mdns_query_get_ptr (const mdns_query *query);
|
|
|
|
/* mdns_device_count_by_model returns count of distinct devices
|
|
* with model names matching the specified parent.
|
|
*
|
|
* Several instances of the same device (i.e. printer vs scanner) are
|
|
* counted only once per network interface.
|
|
*
|
|
* WSDD uses this function to decide when to use extended discovery
|
|
* time (some devices are known to be hard for WD-Discovery)
|
|
*
|
|
* Pattern is the glob-style expression, applied to the model name
|
|
* of discovered devices.
|
|
*/
|
|
unsigned int
|
|
mdns_device_count_by_model (int ifindex, const char *pattern);
|
|
|
|
/******************** WS-Discovery ********************/
|
|
/* Called by zeroconf to notify wsdd about initial scan timer expiration
|
|
*/
|
|
void
|
|
wsdd_initscan_timer_expired (void);
|
|
|
|
/* Send WD-Discovery directed probe
|
|
*/
|
|
void
|
|
wsdd_send_directed_probe (int ifindex, int af, const void *addr);
|
|
|
|
/* Initialize WS-Discovery
|
|
*/
|
|
SANE_Status
|
|
wsdd_init (void);
|
|
|
|
/* Cleanup WS-Discovery
|
|
*/
|
|
void
|
|
wsdd_cleanup (void);
|
|
|
|
/******************** Device Management ********************/
|
|
/* Type device represents a scanner device
|
|
*/
|
|
typedef struct device device;
|
|
|
|
/* Open a device
|
|
*/
|
|
device*
|
|
device_open (const char *name, SANE_Status *status);
|
|
|
|
/* Close the device
|
|
* If log_msg is not NULL, it is written to the device log as late as possible
|
|
*/
|
|
void
|
|
device_close (device *dev, const char *log_msg);
|
|
|
|
/* Get device's logging context
|
|
*/
|
|
log_ctx*
|
|
device_log_ctx (device *dev);
|
|
|
|
/* Get option descriptor
|
|
*/
|
|
const SANE_Option_Descriptor*
|
|
device_get_option_descriptor (device *dev, SANE_Int option);
|
|
|
|
/* Get device option
|
|
*/
|
|
SANE_Status
|
|
device_get_option (device *dev, SANE_Int option, void *value);
|
|
|
|
/* Set device option
|
|
*/
|
|
SANE_Status
|
|
device_set_option (device *dev, SANE_Int option, void *value, SANE_Word *info);
|
|
|
|
/* Get current scan parameters
|
|
*/
|
|
SANE_Status
|
|
device_get_parameters (device *dev, SANE_Parameters *params);
|
|
|
|
SANE_Status
|
|
device_start (device *dev);
|
|
|
|
/* Cancel scanning operation
|
|
*/
|
|
void
|
|
device_cancel (device *dev);
|
|
|
|
/* Set I/O mode
|
|
*/
|
|
SANE_Status
|
|
device_set_io_mode (device *dev, SANE_Bool non_blocking);
|
|
|
|
/* Get select file descriptor
|
|
*/
|
|
SANE_Status
|
|
device_get_select_fd (device *dev, SANE_Int *fd);
|
|
|
|
/* Read scanned image
|
|
*/
|
|
SANE_Status
|
|
device_read (device *dev, SANE_Byte *data, SANE_Int max_len, SANE_Int *len);
|
|
|
|
/* Initialize device management
|
|
*/
|
|
SANE_Status
|
|
device_management_init (void);
|
|
|
|
/* Cleanup device management
|
|
*/
|
|
void
|
|
device_management_cleanup (void);
|
|
|
|
/******************** Image filters ********************/
|
|
/* Type filter represents image filter
|
|
*/
|
|
typedef struct filter filter;
|
|
struct filter {
|
|
filter *next; /* Next filter in a chain */
|
|
void (*dump) (filter *f, /* Dump filter to the log */
|
|
log_ctx *log);
|
|
void (*free) (filter *f); /* Free the filter */
|
|
void (*apply) (filter *f, /* Apply filter to the line of image */
|
|
uint8_t *line, size_t size);
|
|
};
|
|
|
|
/* Free chain of filters
|
|
*/
|
|
void
|
|
filter_chain_free (filter *chain);
|
|
|
|
/* Push translation table based filter, that handles the
|
|
* following options:
|
|
* - brightness
|
|
* - contrast
|
|
* - negative
|
|
*
|
|
* Returns updated chain
|
|
*/
|
|
filter*
|
|
filter_chain_push_xlat (filter *old_chain, const devopt *opt);
|
|
|
|
/* Dump filter chain to the log
|
|
*/
|
|
void
|
|
filter_chain_dump (filter *chain, log_ctx *log);
|
|
|
|
/* Apply filter chain to the image line
|
|
*/
|
|
void
|
|
filter_chain_apply (filter *chain, uint8_t *line, size_t size);
|
|
|
|
/******************** Scan Protocol handling ********************/
|
|
/* PROTO_OP represents operation
|
|
*/
|
|
typedef enum {
|
|
PROTO_OP_NONE, /* No operation */
|
|
PROTO_OP_PRECHECK,/* Pre-scan check */
|
|
PROTO_OP_SCAN, /* New scan */
|
|
PROTO_OP_LOAD, /* Load image */
|
|
PROTO_OP_CHECK, /* Check device status */
|
|
PROTO_OP_CLEANUP, /* Cleanup after scan */
|
|
PROTO_OP_FINISH /* Finish scanning */
|
|
} PROTO_OP;
|
|
|
|
/* Get PROTO_OP name, for logging
|
|
*/
|
|
const char*
|
|
proto_op_name (PROTO_OP op);
|
|
|
|
/* proto_scan_params represents scan parameters
|
|
*/
|
|
typedef struct {
|
|
int x_off, y_off; /* Scan area X/Y offset */
|
|
int wid, hei; /* Scan area width and height */
|
|
int x_res, y_res; /* X/Y resolution */
|
|
ID_SOURCE src; /* Desired source */
|
|
ID_COLORMODE colormode; /* Desired color mode */
|
|
ID_SCANINTENT scanintent; /* Desired scan intent */
|
|
ID_FORMAT format; /* Desired image format */
|
|
} proto_scan_params;
|
|
|
|
/* proto_ctx represents request context
|
|
*/
|
|
typedef struct {
|
|
/* Common context */
|
|
log_ctx *log; /* Logging context */
|
|
struct proto_handler *proto; /* Link to proto_handler */
|
|
const zeroconf_devinfo *devinfo; /* Device info, from zeroconf */
|
|
const devcaps *devcaps; /* Device capabilities */
|
|
PROTO_OP op; /* Current operation */
|
|
http_client *http; /* HTTP client for sending requests */
|
|
http_uri *base_uri; /* HTTP base URI for protocol */
|
|
http_uri *base_uri_nozone;/* base_uri without IPv6 zone */
|
|
proto_scan_params params; /* Scan parameters */
|
|
const char *location; /* Image location */
|
|
unsigned int images_received; /* Total count of received images */
|
|
|
|
/* Extra context for xxx_decode callbacks */
|
|
const http_query *query; /* Passed to xxx_decode callbacks */
|
|
|
|
/* Extra context for status_decode callback */
|
|
PROTO_OP failed_op; /* Failed operation */
|
|
int failed_http_status; /* Its HTTP status */
|
|
int failed_attempt; /* Retry count, 0-based */
|
|
|
|
/* Extra context for image decoding */
|
|
ID_FORMAT format_detected; /* Actual image format */
|
|
} proto_ctx;
|
|
|
|
/* proto_result represents decoded query results
|
|
*/
|
|
typedef struct {
|
|
PROTO_OP next; /* Next operation */
|
|
int delay; /* In milliseconds */
|
|
SANE_Status status; /* Job status */
|
|
error err; /* Error string, may be NULL */
|
|
union {
|
|
const char *location; /* Image location, protocol-specific */
|
|
http_data *image; /* Image buffer */
|
|
} data;
|
|
} proto_result;
|
|
|
|
/* proto represents scan protocol implementation
|
|
*/
|
|
typedef struct proto_handler proto_handler;
|
|
struct proto_handler {
|
|
const char *name; /* Protocol name (i.e., "eSCL", "WSD", "IPP") */
|
|
|
|
/* Free protocol handler
|
|
*/
|
|
void (*free) (proto_handler *proto);
|
|
|
|
/* Query and decode device capabilities
|
|
*/
|
|
http_query* (*devcaps_query) (const proto_ctx *ctx);
|
|
error (*devcaps_decode) (const proto_ctx *ctx, devcaps *caps);
|
|
|
|
/* Create pre-scan check query and decode result
|
|
* These callback are optional, set to NULL, if
|
|
* they are not implemented by the protocol
|
|
* handler
|
|
*/
|
|
http_query* (*precheck_query) (const proto_ctx *ctx);
|
|
proto_result (*precheck_decode) (const proto_ctx *ctx);
|
|
|
|
/* Initiate scanning and decode result.
|
|
* On success, scan_decode must set ctx->data.location
|
|
*/
|
|
http_query* (*scan_query) (const proto_ctx *ctx);
|
|
proto_result (*scan_decode) (const proto_ctx *ctx);
|
|
|
|
/* Initiate image downloading and decode result.
|
|
* On success, load_decode must set ctx->data.image
|
|
*/
|
|
http_query* (*load_query) (const proto_ctx *ctx);
|
|
proto_result (*load_decode) (const proto_ctx *ctx);
|
|
|
|
/* Request device status and decode result
|
|
*/
|
|
http_query* (*status_query) (const proto_ctx *ctx);
|
|
proto_result (*status_decode) (const proto_ctx *ctx);
|
|
|
|
/* Cleanup after scan
|
|
*/
|
|
http_query* (*cleanup_query) (const proto_ctx *ctx);
|
|
|
|
/* Cancel scan in progress
|
|
*/
|
|
http_query* (*cancel_query) (const proto_ctx *ctx);
|
|
|
|
/* Test interfaces. Not for regular use!
|
|
*/
|
|
error (*test_decode_devcaps) (proto_handler *proto,
|
|
const void *xml_text, size_t xms_size,
|
|
devcaps *caps);
|
|
};
|
|
|
|
/* proto_handler_escl_new creates new eSCL protocol handler
|
|
*/
|
|
proto_handler*
|
|
proto_handler_escl_new (void);
|
|
|
|
/* proto_handler_wsd_new creates new WSD protocol handler
|
|
*/
|
|
proto_handler*
|
|
proto_handler_wsd_new (void);
|
|
|
|
/* proto_handler_new creates new protocol handler by protocol ID
|
|
*/
|
|
static inline proto_handler*
|
|
proto_handler_new (ID_PROTO proto)
|
|
{
|
|
switch (proto) {
|
|
case ID_PROTO_ESCL:
|
|
return proto_handler_escl_new();
|
|
case ID_PROTO_WSD:
|
|
return proto_handler_wsd_new();
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* proto_handler_free destroys protocol handler, previously
|
|
* created by proto_handler_new/proto_handler_escl_new/
|
|
* proto_handler_wsd_new functions
|
|
*/
|
|
static inline void
|
|
proto_handler_free (proto_handler *proto)
|
|
{
|
|
proto->free(proto);
|
|
}
|
|
|
|
/******************** Image decoding ********************/
|
|
/* The window withing the image
|
|
*
|
|
* Note, all sizes and coordinates are in pixels
|
|
*/
|
|
typedef struct {
|
|
int x_off, y_off; /* Top-left corner offset */
|
|
int wid, hei; /* Image width and height */
|
|
} image_window;
|
|
|
|
/* Image decoder, with virtual methods
|
|
*/
|
|
typedef struct image_decoder image_decoder;
|
|
struct image_decoder {
|
|
const char *content_type;
|
|
void (*free) (image_decoder *decoder);
|
|
error (*begin) (image_decoder *decoder, const void *data, size_t size);
|
|
void (*reset) (image_decoder *decoder);
|
|
int (*get_bytes_per_pixel) (image_decoder *decoder);
|
|
void (*get_params) (image_decoder *decoder, SANE_Parameters *params);
|
|
error (*set_window) (image_decoder *decoder, image_window *win);
|
|
error (*read_line) (image_decoder *decoder, void *buffer);
|
|
};
|
|
|
|
/* Detect image format by image data
|
|
*/
|
|
ID_FORMAT
|
|
image_format_detect (const void *data, size_t size);
|
|
|
|
/* Create JPEG image decoder
|
|
*/
|
|
image_decoder*
|
|
image_decoder_jpeg_new (void);
|
|
|
|
/* Create PNG image decoder
|
|
*/
|
|
image_decoder*
|
|
image_decoder_png_new (void);
|
|
|
|
/* Create TIFF image decoder
|
|
*/
|
|
image_decoder*
|
|
image_decoder_tiff_new (void);
|
|
|
|
/* Create BMP image decoder
|
|
*/
|
|
image_decoder*
|
|
image_decoder_bmp_new (void);
|
|
|
|
/* Free image decoder
|
|
*/
|
|
static inline void
|
|
image_decoder_free (image_decoder *decoder)
|
|
{
|
|
decoder->free(decoder);
|
|
}
|
|
|
|
/* Get content type
|
|
*/
|
|
static inline const char*
|
|
image_content_type (image_decoder *decoder)
|
|
{
|
|
return decoder->content_type;
|
|
}
|
|
|
|
/* Begin image decoding. Decoder may assume that provided data
|
|
* buffer remains valid during a whole decoding cycle
|
|
*/
|
|
static inline error
|
|
image_decoder_begin (image_decoder *decoder, const void *data, size_t size)
|
|
{
|
|
return decoder->begin(decoder, data, size);
|
|
}
|
|
|
|
/* Reset image decoder after use. After reset, decoding of the
|
|
* another image can be started
|
|
*/
|
|
static inline void
|
|
image_decoder_reset (image_decoder *decoder)
|
|
{
|
|
decoder->reset(decoder);
|
|
}
|
|
|
|
/* Get bytes count per pixel
|
|
*/
|
|
static inline int
|
|
image_decoder_get_bytes_per_pixel (image_decoder *decoder)
|
|
{
|
|
return decoder->get_bytes_per_pixel(decoder);
|
|
}
|
|
|
|
/* Get image parameters. Can be called at any time between
|
|
* image_decoder_begin() and image_decoder_reset()
|
|
*
|
|
* Decoder must return an actual image parameters, regardless
|
|
* of clipping window set by image_decoder_set_window()
|
|
*/
|
|
static inline void
|
|
image_decoder_get_params (image_decoder *decoder, SANE_Parameters *params)
|
|
{
|
|
decoder->get_params(decoder, params);
|
|
}
|
|
|
|
/* Set window within the image. Only part of image that fits the
|
|
* window needs to be decoded. Decoder may assume that window is
|
|
* always within the actual image boundaries
|
|
*
|
|
* Note, if decoder cannot handle exact window boundaries, it
|
|
* it must update window to keep actual values
|
|
*
|
|
* In particular, if decoder doesn't implement image clipping
|
|
* at all, it is safe that decoder will simply set window boundaries
|
|
* to contain an entire image
|
|
*/
|
|
static inline error
|
|
image_decoder_set_window (image_decoder *decoder, image_window *win)
|
|
{
|
|
return decoder->set_window(decoder, win);
|
|
}
|
|
|
|
/* Read next line of image. Decoder may safely assume the provided
|
|
* buffer is big enough to keep the entire line
|
|
*/
|
|
static inline error
|
|
image_decoder_read_line (image_decoder *decoder, void *buffer)
|
|
{
|
|
return decoder->read_line(decoder, buffer);
|
|
}
|
|
|
|
/* image_decoder_create_all creates all decoders
|
|
* and fills array of decoders, indexed by ID_FORMAT
|
|
*
|
|
* Note, it is not guaranteed, that for all ID_FORMAT
|
|
* decoder will be created. Missed entries will be set
|
|
* to NULL. Be aware when using the filled array!
|
|
*/
|
|
static inline void
|
|
image_decoder_create_all (image_decoder *decoders[NUM_ID_FORMAT])
|
|
{
|
|
int i;
|
|
|
|
/* Fill entire array with NULLs
|
|
*/
|
|
for (i = 0; i < NUM_ID_FORMAT; i ++) {
|
|
decoders[i] = NULL;
|
|
}
|
|
|
|
/* Create known decoders
|
|
*/
|
|
decoders[ID_FORMAT_BMP] = image_decoder_bmp_new();
|
|
decoders[ID_FORMAT_JPEG] = image_decoder_jpeg_new();
|
|
decoders[ID_FORMAT_PNG] = image_decoder_png_new();
|
|
decoders[ID_FORMAT_TIFF] = image_decoder_tiff_new();
|
|
}
|
|
|
|
/* image_decoder_free_all destroys all decoders, previously
|
|
* created by image_decoder_create_all
|
|
*/
|
|
static inline void
|
|
image_decoder_free_all (image_decoder *decoders[NUM_ID_FORMAT])
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_ID_FORMAT; i ++) {
|
|
image_decoder *decoder = decoders[i];
|
|
if (decoder != NULL) {
|
|
image_decoder_free(decoder);
|
|
decoders[i] = NULL; /* For sanity */
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************** Mathematical Functions ********************/
|
|
/* Find greatest common divisor of two positive integers
|
|
*/
|
|
SANE_Word
|
|
math_gcd (SANE_Word x, SANE_Word y);
|
|
|
|
/* Find least common multiple of two positive integers
|
|
*/
|
|
SANE_Word
|
|
math_lcm (SANE_Word x, SANE_Word y);
|
|
|
|
/* Find min of two words
|
|
*/
|
|
static inline SANE_Word
|
|
math_min (SANE_Word a, SANE_Word b)
|
|
{
|
|
return a < b ? a : b;
|
|
}
|
|
|
|
/* Find max of two words
|
|
*/
|
|
static inline SANE_Word
|
|
math_max (SANE_Word a, SANE_Word b)
|
|
{
|
|
return a > b ? a : b;
|
|
}
|
|
|
|
/* Bound integer within range
|
|
*/
|
|
static inline SANE_Word
|
|
math_bound (SANE_Word x, SANE_Word min, SANE_Word max)
|
|
{
|
|
if (x < min) {
|
|
return min;
|
|
} else if (x > max) {
|
|
return max;
|
|
} else {
|
|
return x;
|
|
}
|
|
}
|
|
|
|
/* Bound double within range
|
|
*/
|
|
static inline double
|
|
math_bound_double (double x, double min, double max)
|
|
{
|
|
if (x < min) {
|
|
return min;
|
|
} else if (x > max) {
|
|
return max;
|
|
} else {
|
|
return x;
|
|
}
|
|
}
|
|
|
|
/* Compute x * mul / div, taking in account rounding
|
|
* and integer overflow
|
|
*/
|
|
static inline SANE_Word
|
|
math_muldiv (SANE_Word x, SANE_Word mul, SANE_Word div)
|
|
{
|
|
int64_t tmp;
|
|
|
|
tmp = (int64_t) x * (int64_t) mul;
|
|
tmp += div / 2;
|
|
tmp /= div;
|
|
|
|
return (SANE_Word) tmp;
|
|
}
|
|
|
|
/* Merge two ranges, if possible
|
|
*/
|
|
bool
|
|
math_range_merge (SANE_Range *out, const SANE_Range *r1, const SANE_Range *r2);
|
|
|
|
/* Choose nearest integer in range
|
|
*/
|
|
SANE_Word
|
|
math_range_fit (const SANE_Range *r, SANE_Word i);
|
|
|
|
/* Convert pixels to millimeters, using given resolution
|
|
*/
|
|
static inline SANE_Fixed
|
|
math_px2mm_res (SANE_Word px, SANE_Word res)
|
|
{
|
|
return SANE_FIX((double) px * 25.4 / res);
|
|
}
|
|
|
|
/* Convert millimeters to pixels, using given resolution
|
|
*/
|
|
static inline SANE_Word
|
|
math_mm2px_res (SANE_Fixed mm, SANE_Word res)
|
|
{
|
|
return (SANE_Word) roundl(SANE_UNFIX(mm) * res / 25.4);
|
|
}
|
|
|
|
/* Format millimeters, for printing
|
|
*/
|
|
char*
|
|
math_fmt_mm (SANE_Word mm, char buf[]);
|
|
|
|
/* Genrate random 32-bit integer
|
|
*/
|
|
uint32_t
|
|
math_rand_u32 (void);
|
|
|
|
/* Generate random integer in range [0...max], inclusively
|
|
*/
|
|
uint32_t
|
|
math_rand_max (uint32_t max);
|
|
|
|
/* Generate random integer in range [min...max], inclusively
|
|
*/
|
|
uint32_t
|
|
math_rand_range (uint32_t min, uint32_t max);
|
|
|
|
/* Count nonzero bits in 32-bit integer
|
|
*/
|
|
static inline unsigned int
|
|
math_popcount (unsigned int n)
|
|
{
|
|
unsigned int count = (n & 0x55555555) + ((n >> 1) & 0x55555555);
|
|
count = (count & 0x33333333) + ((count >> 2) & 0x33333333);
|
|
count = (count & 0x0F0F0F0F) + ((count >> 4) & 0x0F0F0F0F);
|
|
count = (count & 0x00FF00FF) + ((count >> 8) & 0x00FF00FF);
|
|
return (count & 0x0000FFFF) + ((count >> 16) & 0x0000FFFF);
|
|
}
|
|
|
|
/******************** Logging ********************/
|
|
/* Initialize logging
|
|
*
|
|
* No log messages should be generated before this call
|
|
*/
|
|
void
|
|
log_init (void);
|
|
|
|
/* Cleanup logging
|
|
*
|
|
* No log messages should be generated after this call
|
|
*/
|
|
void
|
|
log_cleanup (void);
|
|
|
|
/* Notify logger that configuration is loaded and
|
|
* logger can configure itself
|
|
*
|
|
* This is safe to generate log messages before log_configure()
|
|
* is called. These messages will be buffered, and after
|
|
* logger is configured, either written or abandoned, depending
|
|
* on configuration
|
|
*/
|
|
void
|
|
log_configure (void);
|
|
|
|
/* log_ctx_new creates new logging context
|
|
* If parent != NULL, new logging context will have its own prefix,
|
|
* but trace file will be inherited from parent
|
|
*/
|
|
log_ctx*
|
|
log_ctx_new (const char *name, log_ctx *parent);
|
|
|
|
/* log_ctx_free destroys logging context
|
|
*/
|
|
void
|
|
log_ctx_free (log_ctx *log);
|
|
|
|
/* Get protocol trace associated with logging context
|
|
*/
|
|
trace*
|
|
log_ctx_trace (log_ctx *log);
|
|
|
|
/* Write a debug message.
|
|
*/
|
|
void
|
|
log_debug (log_ctx *log, const char *fmt, ...);
|
|
|
|
/* Write a protocol trace message
|
|
*/
|
|
void
|
|
log_trace (log_ctx *log, const char *fmt, ...);
|
|
|
|
/* Write a block of data into protocol trace
|
|
*/
|
|
void
|
|
log_trace_data (log_ctx *log, const char *content_type,
|
|
const void *bytes, size_t size);
|
|
|
|
/* Write an error message and terminate a program.
|
|
*/
|
|
void
|
|
log_panic (log_ctx *log, const char *fmt, ...);
|
|
|
|
/* Panic if assertion fails
|
|
*/
|
|
#define log_assert(log,expr) \
|
|
do { \
|
|
if (!(expr)) { \
|
|
log_panic(log,"file %s: line %d (%s): assertion failed: (%s)",\
|
|
__FILE__, __LINE__, __PRETTY_FUNCTION__, #expr); \
|
|
__builtin_unreachable(); \
|
|
} \
|
|
} while (0)
|
|
|
|
/* Panic if this code is reached
|
|
*/
|
|
#define log_internal_error(log) \
|
|
do { \
|
|
log_panic(log,"file %s: line %d (%s): internal error", \
|
|
__FILE__, __LINE__, __PRETTY_FUNCTION__); \
|
|
__builtin_unreachable(); \
|
|
} while (0)
|
|
|
|
/******************** Initialization/Cleanup ********************/
|
|
/* AIRSCAN_INIT_FLAGS represents airscan_init() flags
|
|
*
|
|
* These flags are mostly used for testing
|
|
*/
|
|
typedef enum {
|
|
AIRSCAN_INIT_NO_CONF = (1 << 0), // Don't load configuration
|
|
AIRSCAN_INIT_NO_THREAD = (1 << 1) // Don't start worker thread
|
|
} AIRSCAN_INIT_FLAGS;
|
|
|
|
/* Initialize airscan.
|
|
* If log_msg is not NULL, it is written to the log early
|
|
*/
|
|
SANE_Status
|
|
airscan_init (AIRSCAN_INIT_FLAGS flags, const char *log_msg);
|
|
|
|
/* Cleanup airscan
|
|
* If log_msg is not NULL, it is written to the log as late as possible
|
|
*/
|
|
void
|
|
airscan_cleanup (const char *log_msg);
|
|
|
|
/* Get init flags from the airscan_init call
|
|
*/
|
|
AIRSCAN_INIT_FLAGS
|
|
airscan_get_init_flags (void);
|
|
|
|
#ifdef __cplusplus
|
|
};
|
|
#endif
|
|
|
|
#endif
|
|
/* vim:ts=8:sw=4:et
|
|
*/
|