1/* sane - Scanner Access Now Easy. 2 3 Copyright (C) 2019 Touboul Nathane 4 Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com> 5 6 This file is part of the SANE package. 7 8 SANE is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free 10 Software Foundation; either version 3 of the License, or (at your 11 option) any later version. 12 13 SANE is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with sane; see the file COPYING. 20 If not, see <https://www.gnu.org/licenses/>. 21 22 This file implements a SANE backend for eSCL scanners. */ 23 24#define DEBUG_DECLARE_ONLY 25#include "../include/sane/config.h" 26 27#include "escl.h" 28 29#include <assert.h> 30#include <stdio.h> 31#include <stdlib.h> 32#include <string.h> 33#include <arpa/inet.h> 34 35#include <avahi-client/lookup.h> 36#include <avahi-common/error.h> 37#include <avahi-common/simple-watch.h> 38 39#include "../include/sane/sanei.h" 40 41static AvahiSimplePoll *simple_poll = NULL; 42static int count_finish = 0; 43 44/** 45 * \fn static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED 46 * AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, 47 * AvahiResolverEvent event, const char *name, 48 * const char *type, const char *domain, const char *host_name, 49 * const AvahiAddress *address, uint16_t port, 50 * AvahiStringList *txt, AvahiLookupResultFlags flags, 51 * void *userdata) 52 * \brief Callback function that will check if the selected scanner follows the escl 53 * protocol or not. 54 */ 55static void 56resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, 57 AvahiProtocol protocol, 58 AvahiResolverEvent event, 59 const char *name, 60 const char __sane_unused__ *type, 61 const char __sane_unused__ *domain, 62 const char __sane_unused__ *host_name, 63 const AvahiAddress *address, 64 uint16_t port, 65 AvahiStringList *txt, 66 AvahiLookupResultFlags __sane_unused__ flags, 67 void __sane_unused__ *userdata) 68{ 69 char a[(AVAHI_ADDRESS_STR_MAX + 10)] = { 0 }; 70 char *t; 71 const char *is; 72 const char *uuid; 73 AvahiStringList *s; 74 assert(r); 75 switch (event) { 76 case AVAHI_RESOLVER_FAILURE: 77 break; 78 case AVAHI_RESOLVER_FOUND: 79 { 80 char *psz_addr = ((void*)0); 81 char b[128] = { 0 }; 82 avahi_address_snprint(b, (sizeof(b)/sizeof(b[0]))-1, address); 83#ifdef ENABLE_IPV6 84 if (protocol == AVAHI_PROTO_INET6 && strchr(b, ':')) 85 { 86 if ( asprintf( &psz_addr, "[%s]", b ) == -1 ) 87 break; 88 } 89 else 90#endif 91 { 92 if ( asprintf( &psz_addr, "%s", b ) == -1 ) 93 break; 94 } 95 t = avahi_string_list_to_string(txt); 96 if (strstr(t, "\"rs=eSCL\"") || strstr(t, "\"rs=/eSCL\"")) { 97 s = avahi_string_list_find(txt, "is"); 98 if (s && s->size > 3) 99 is = (const char*)s->text + 3; 100 else 101 is = (const char*)NULL; 102 s = avahi_string_list_find(txt, "uuid"); 103 if (s && s->size > 5) 104 uuid = (const char*)s->text + 5; 105 else 106 uuid = (const char*)NULL; 107 DBG (10, "resolve_callback [%s]\n", a); 108 if (strstr(psz_addr, "127.0.0.1") != NULL) { 109 escl_device_add(port, name, "localhost", is, uuid, (char*)type); 110 DBG (10,"resolve_callback fix redirect [localhost]\n"); 111 } 112 else 113 escl_device_add(port, name, psz_addr, is, uuid, (char*)type); 114 } 115 } 116 } 117} 118 119/** 120 * \fn static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, 121 * AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, 122 * const char *type, const char *domain, 123 * AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) 124 * \brief Callback function that will browse tanks to 'avahi' the scanners 125 * connected in network. 126 */ 127static void 128browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, 129 AvahiProtocol protocol, AvahiBrowserEvent event, 130 const char *name, const char *type, 131 const char *domain, 132 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, 133 void* userdata) 134{ 135 AvahiClient *c = userdata; 136 assert(b); 137 switch (event) { 138 case AVAHI_BROWSER_FAILURE: 139 avahi_simple_poll_quit(simple_poll); 140 return; 141 case AVAHI_BROWSER_NEW: 142 if (!(avahi_service_resolver_new(c, interface, protocol, name, 143 type, domain, 144 AVAHI_PROTO_UNSPEC, 0, 145 resolve_callback, c))) 146 break; 147 case AVAHI_BROWSER_REMOVE: 148 break; 149 case AVAHI_BROWSER_ALL_FOR_NOW: 150 case AVAHI_BROWSER_CACHE_EXHAUSTED: 151 if (event != AVAHI_BROWSER_CACHE_EXHAUSTED) 152 { 153 count_finish++; 154 if (count_finish == 2) 155 avahi_simple_poll_quit(simple_poll); 156 } 157 break; 158 } 159} 160 161/** 162 * \fn static void client_callback(AvahiClient *c, AvahiClientState state, 163 * AVAHI_GCC_UNUSED void *userdata) 164 * \brief Callback Function that quit if it doesn't find a connected scanner, 165 * possible thanks the "Hello Protocol". 166 * --> Waiting for a answer by the scanner to continue the avahi process. 167 */ 168static void 169client_callback(AvahiClient *c, AvahiClientState state, 170 AVAHI_GCC_UNUSED void *userdata) 171{ 172 assert(c); 173 if (state == AVAHI_CLIENT_FAILURE) 174 avahi_simple_poll_quit(simple_poll); 175} 176 177/** 178 * \fn ESCL_Device *escl_devices(SANE_Status *status) 179 * \brief Function that calls all the avahi functions and then, recovers the 180 * connected eSCL devices. 181 * This function is called in the 'sane_get_devices' function. 182 * 183 * \return NULL (the eSCL devices found) 184 */ 185ESCL_Device * 186escl_devices(SANE_Status *status) 187{ 188 AvahiClient *client = NULL; 189 AvahiServiceBrowser *sb = NULL; 190 int error; 191 192 count_finish = 0; 193 194 *status = SANE_STATUS_GOOD; 195 if (!(simple_poll = avahi_simple_poll_new())) { 196 DBG( 1, "Failed to create simple poll object.\n"); 197 *status = SANE_STATUS_INVAL; 198 goto fail; 199 } 200 client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, 201 client_callback, NULL, &error); 202 if (!client) { 203 DBG( 1, "Failed to create client: %s\n", avahi_strerror(error)); 204 *status = SANE_STATUS_INVAL; 205 goto fail; 206 } 207 if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, 208 AVAHI_PROTO_UNSPEC, "_uscan._tcp", 209 NULL, 0, browse_callback, client))) { 210 DBG( 1, "Failed to create service browser: %s\n", 211 avahi_strerror(avahi_client_errno(client))); 212 *status = SANE_STATUS_INVAL; 213 goto fail; 214 } 215 if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, 216 AVAHI_PROTO_UNSPEC, 217 "_uscans._tcp", NULL, 0, 218 browse_callback, client))) { 219 DBG( 1, "Failed to create service browser: %s\n", 220 avahi_strerror(avahi_client_errno(client))); 221 *status = SANE_STATUS_INVAL; 222 goto fail; 223 } 224 avahi_simple_poll_loop(simple_poll); 225fail: 226 if (sb) 227 avahi_service_browser_free(sb); 228 if (client) 229 avahi_client_free(client); 230 if (simple_poll) 231 avahi_simple_poll_free(simple_poll); 232 return (NULL); 233} 234