diff options
Diffstat (limited to 'backend/dell1600n_net.c')
-rw-r--r-- | backend/dell1600n_net.c | 2065 |
1 files changed, 2065 insertions, 0 deletions
diff --git a/backend/dell1600n_net.c b/backend/dell1600n_net.c new file mode 100644 index 0000000..d19059b --- /dev/null +++ b/backend/dell1600n_net.c @@ -0,0 +1,2065 @@ +/* + sane - Scanner Access Now Easy. + Copyright (C) 2006 Jon Chambers <jon@jon.demon.co.uk> + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + + Dell 1600n network scan driver for SANE. + + To debug: + SANE_DEBUG_DELL1600N_NET=255 scanimage --verbose 2>scan.errs 1>scan.png +*/ + +/*********************************************************** + * INCLUDES + ***********************************************************/ + +#include "../include/sane/config.h" +#include "../include/sane/sane.h" +#include "../include/sane/sanei.h" + +#define BACKEND_NAME dell1600n_net +#include "../include/sane/sanei_backend.h" +#include "../include/sane/sanei_config.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <unistd.h> + +/* :NOTE: these are likely to be platform-specific! */ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <jpeglib.h> +#include <tiffio.h> + +/* OS/2... */ +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + + +/*********************************************************** + * DEFINITIONS + ***********************************************************/ + +/* Maximum number of scanners */ +#define MAX_SCANNERS 32 + +/* version number */ +#define DRIVER_VERSION SANE_VERSION_CODE( SANE_CURRENT_MAJOR, V_MINOR, 0 ) + +/* size of buffer for socket communication */ +#define SOCK_BUF_SIZE 2048 + +/* size of registation name */ +#define REG_NAME_SIZE 64 + +struct DeviceRecord +{ + SANE_Device m_device; + char * m_pName; /* storage of name */ + char * m_pModel; /* storage of model */ +}; + +/* a buffer struct to store "stuff" */ +struct ComBuf +{ + size_t m_capacity; /* current allocated size in bytes */ + size_t m_used; /* current used size in bytes */ + unsigned char *m_pBuf; /* storage (or NULL if none allocated) */ +}; + +/* state data for a single scanner connection */ +struct ScannerState +{ + int m_udpFd; /* file descriptor to UDP socket */ + int m_tcpFd; /* file descriptor to TCP socket */ + struct sockaddr_in m_sockAddr; /* printer address */ + struct ComBuf m_buf; /* buffer for network data */ + struct ComBuf m_imageData; /* storage for decoded image data */ + int m_numPages; /* number of complete pages (host byte order) */ + struct ComBuf m_pageInfo; /* "array" of numPages PageInfo structs */ + int m_bFinish; /* set non-0 to signal that we are finished */ + int m_bCancelled; /* set non-0 that bFinish state arose from cancelation */ + char m_regName[REG_NAME_SIZE]; /* name with which to register */ + unsigned short m_xres; /* x resolution (network byte order) */ + unsigned short m_yres; /* y resolution (network byte order) */ + unsigned int m_composition; /* composition (0x01=>TIFF/PDF,0x40=>JPEG) (network byte order) */ + unsigned char m_brightness; /* brightness */ + unsigned int m_compression; /* compression (0x08=>CCIT Group 4,0x20=>JPEG) (network byte order) */ + unsigned int m_fileType; /* file type (2=>TIFF,4=>PDF,8=>JPEG)(network byte order) */ + unsigned int m_pixelWidth; /* width in pixels (network byte order) */ + unsigned int m_pixelHeight; /* height in pixels (network byte order) */ + unsigned int m_bytesRead; /* bytes read by SANE (host byte order) */ + unsigned int m_currentPageBytes;/* number of bytes of current page read (host byte order) */ +}; + +/* state data for a single page + NOTE: all ints are in host byte order +*/ +struct PageInfo +{ + int m_width; /* pixel width */ + int m_height; /* pixel height */ + int m_totalSize; /* total page size (bytes) */ + int m_bytesRemaining; /* number of bytes not yet passed to SANE client */ +}; + +/* struct for in-memory jpeg decompression */ +struct JpegDataDecompState +{ + struct jpeg_decompress_struct m_cinfo; /* base struct */ + unsigned char *m_pData; /* data pointer */ + unsigned int m_bytesRemaining; /* amount of unprocessed data */ +}; + +/* initial ComBuf allocation */ +#define INITIAL_COM_BUF_SIZE 1024 + +/*********************************************************** + * FUNCTION PROTOTYPES + ***********************************************************/ + +/* print hex buffer to stdout */ +static void HexDump (int debugLevel, const unsigned char *buf, + size_t bufSize); + +/* clears gKnownDevices array */ +static void ClearKnownDevices (void); + +/* initialise a ComBuf struct */ +static int InitComBuf (struct ComBuf *pBuf); + +/* free a ComBuf struct */ +static void FreeComBuf (struct ComBuf *pBuf); + +/* add data to a ComBuf struct */ +static int AppendToComBuf (struct ComBuf *pBuf, const unsigned char *pData, + size_t datSize); + +/* remove data from the front of a ComBuf struct */ +static int PopFromComBuf (struct ComBuf *pBuf, size_t datSize); + +/* initialise a packet */ +static int InitPacket (struct ComBuf *pBuf, char type); + +/* append message to a packet */ +static int AppendMessageToPacket (struct ComBuf *pBuf, + char messageType, + char *messageName, + char valueType, + void *pValue, size_t valueLen); + +/* write length data to packet header */ +static void FinalisePacket (struct ComBuf *pBuf); + +/* \return 1 if message is complete, 0 otherwise */ +static int MessageIsComplete (unsigned char *pData, size_t size); + +/* process a registration broadcast response + \return DeviceRecord pointer on success (caller frees), NULL on failure +*/ +static struct DeviceRecord *ProcessFindResponse (unsigned char *pData, size_t size); + +/* frees a scanner state struct stored in gOpenScanners */ +static void FreeScannerState (int iHandle); + +/* \return 1 if iHandle is a valid member of gOpenScanners, 0 otherwise */ +static int ValidScannerNumber (int iHandle); + +/* process UDP responses, \return 0 in success, >0 otherwise */ +static int ProcessUdpResponse (unsigned char *pData, size_t size, + struct ScannerState *pState); + +/* process TCP responses, \return 0 in success, >0 otherwise */ +static int ProcessTcpResponse (struct ScannerState *pState, + struct ComBuf *pTcpBufBuf); + +/* Process the data from a single scanned page, \return 0 in success, >0 otherwise */ +static int ProcessPageData (struct ScannerState *pState); + +/* Libjpeg decompression interface */ +static void JpegDecompInitSource (j_decompress_ptr cinfo); +static boolean JpegDecompFillInputBuffer (j_decompress_ptr cinfo); +static void JpegDecompSkipInputData (j_decompress_ptr cinfo, long numBytes); +static void JpegDecompTermSource (j_decompress_ptr cinfo); + +/*********************************************************** + * GLOBALS + ***********************************************************/ + +/* Results of last call to sane_get_devices */ +static struct DeviceRecord *gKnownDevices[MAX_SCANNERS]; + +/* Array of open scanner device states. + :NOTE: (int)SANE_Handle is an offset into this array */ +static struct ScannerState *gOpenScanners[MAX_SCANNERS]; + +/* scanner port */ +static unsigned short gScannerPort = 1124; + +/* ms to wait for registration replies */ +static unsigned short gRegReplyWaitMs = 300; + +/*********************************************************** + * FUNCTION IMPLEMENTATIONS + ***********************************************************/ + +SANE_Status +sane_init (SANE_Int * version_code, + SANE_Auth_Callback __sane_unused__ authorize) +{ + + /* init globals */ + memset (gKnownDevices, 0, sizeof (gKnownDevices)); + memset (gOpenScanners, 0, sizeof (gOpenScanners)); + + /* report version */ + *version_code = DRIVER_VERSION; + + /* init debug */ + DBG_INIT (); + + return SANE_STATUS_GOOD; + +} /* sane_init */ + +/***********************************************************/ + +void +sane_exit (void) +{ + + int iHandle; + + /* clean up */ + ClearKnownDevices (); + + for (iHandle = 0; iHandle < MAX_SCANNERS; ++iHandle) + { + if (gOpenScanners[iHandle]) + FreeScannerState (iHandle); + } + +} /* sane_exit */ + +/***********************************************************/ + +SANE_Status +sane_get_devices (const SANE_Device *** device_list, + SANE_Bool __sane_unused__ local_only) +{ + + int ret; + unsigned char sockBuf[SOCK_BUF_SIZE]; + int sock, optYes; + struct DeviceRecord *pDevice; + struct ComBuf queryPacket; + struct sockaddr_in remoteAddr; + unsigned char ucVal; + fd_set readFds; + struct timeval selTimeVal; + int nread, iNextDevice; + FILE *fConfig; + char configBuf[ 256 ]; + const char *pVal; + int valLen; + + /* init variables */ + ret = SANE_STATUS_GOOD; + sock = 0; + pDevice = NULL; + optYes = 1; + InitComBuf (&queryPacket); + + /* clear previous results */ + ClearKnownDevices (); + iNextDevice = 0; + + /* look for a config file */ + fConfig = sanei_config_open( "dell1600n_net.conf" ); + if ( fConfig ) + { + while ( ! feof( fConfig ) ) + { + if ( ! sanei_config_read ( configBuf, sizeof( configBuf ), fConfig ) ) break; + + /* skip whitespace */ + pVal = sanei_config_skip_whitespace ( configBuf ); + + /* skip comments */ + if ( *pVal == '#' ) continue; + + /* process named_scanner */ + valLen = strlen( "named_scanner:" ); + if ( ! strncmp( pVal, "extra_scanner:", valLen ) ){ + + pVal = sanei_config_skip_whitespace ( pVal + valLen ); + + pDevice = malloc (sizeof (struct DeviceRecord)); + if (!pDevice) + { + DBG (1, "sane_get_devices: memory allocation failure\n"); + break; + } + + pDevice->m_pName = strdup (pVal); + pDevice->m_device.vendor = "Dell"; + pDevice->m_pModel = strdup( "1600n" ); + pDevice->m_device.type = "multi-function peripheral"; + + pDevice->m_device.name = pDevice->m_pName; + pDevice->m_device.model = pDevice->m_pModel; + + /* add to list */ + gKnownDevices[iNextDevice++] = pDevice; + + continue; + } /* if */ + + } /* while */ + + /* Close the file */ + fclose( fConfig ); + } + + /* open UDP socket */ + sock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == -1) + { + DBG (1, "Error creating socket\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + setsockopt (sock, SOL_SOCKET, SO_BROADCAST, &optYes, sizeof (optYes)); + + /* prepare select mask */ + FD_ZERO (&readFds); + FD_SET (sock, &readFds); + selTimeVal.tv_sec = 0; + selTimeVal.tv_usec = gRegReplyWaitMs * 1000; + + /* init a packet */ + InitPacket (&queryPacket, 0x01); + + /* add query */ + ucVal = 0; + AppendMessageToPacket (&queryPacket, 0x25, "std-scan-discovery-all", + 0x02, &ucVal, sizeof (ucVal)); + + FinalisePacket (&queryPacket); + + DBG (10, "Sending:\n"); + HexDump (10, queryPacket.m_pBuf, queryPacket.m_used); + + + remoteAddr.sin_family = AF_INET; + remoteAddr.sin_port = htons (gScannerPort); + remoteAddr.sin_addr.s_addr = 0xFFFFFFFF; /* broadcast */ + + if (sendto (sock, queryPacket.m_pBuf, queryPacket.m_used, 0, + &remoteAddr, sizeof (remoteAddr)) == -1) + { + DBG (1, "Error sending broadcast packet\n"); + ret = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* process replies */ + while (select (sock + 1, &readFds, NULL, NULL, &selTimeVal)) + { + + /* break if we've got no more storage space in array */ + if (iNextDevice >= MAX_SCANNERS) + { + DBG (1, "sane_get_devices: more than %d devices, ignoring\n", + MAX_SCANNERS); + break; + } + + nread = read (sock, sockBuf, sizeof (sockBuf)); + DBG (5, "Got a broadcast response, (%d bytes)\n", nread); + + if (nread <= 0) + break; + + HexDump (10, sockBuf, nread); + + /* process response (skipping bad ones) */ + if (!(pDevice = ProcessFindResponse (sockBuf, nread))) continue; + + /* add to list */ + gKnownDevices[iNextDevice++] = pDevice; + + } /* while */ + + /* report our finds */ + *device_list = (const SANE_Device **) gKnownDevices; + +cleanup: + + if (sock) + close (sock); + FreeComBuf (&queryPacket); + + return ret; + +} /* sane_get_devices */ + +/***********************************************************/ + +SANE_Status +sane_open (SANE_String_Const devicename, SANE_Handle * handle) +{ + + int iHandle = -1, i; + SANE_Status status = SANE_STATUS_GOOD; + struct hostent *pHostent; + char *pDot; + + DBG( 5, "sane_open: %s\n", devicename ); + + /* find the next available scanner pointer in gOpenScanners */ + for (i = 0; i < MAX_SCANNERS; ++i) + { + + if (gOpenScanners[i]) continue; + + iHandle = i; + break; + + } /* for */ + if (iHandle == -1) + { + DBG (1, "sane_open: no space left in gOpenScanners array\n"); + status = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* allocate some space */ + if (!(gOpenScanners[iHandle] = malloc (sizeof (struct ScannerState)))) + { + status = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* init data */ + memset (gOpenScanners[iHandle], 0, sizeof (struct ScannerState)); + InitComBuf (&gOpenScanners[iHandle]->m_buf); + InitComBuf (&gOpenScanners[iHandle]->m_imageData); + InitComBuf (&gOpenScanners[iHandle]->m_pageInfo); + gOpenScanners[iHandle]->m_xres = ntohs (200); + gOpenScanners[iHandle]->m_yres = ntohs (200); + gOpenScanners[iHandle]->m_composition = ntohl (0x01); + gOpenScanners[iHandle]->m_brightness = 0x80; + gOpenScanners[iHandle]->m_compression = ntohl (0x08); + gOpenScanners[iHandle]->m_fileType = ntohl (0x02); + + + /* look up scanner name */ + pHostent = gethostbyname (devicename); + if ((!pHostent) || (!pHostent->h_addr_list)) + { + DBG (1, "sane_open: error looking up scanner name %s\n", devicename); + status = SANE_STATUS_INVAL; + goto cleanup; + } + + /* open a UDP socket */ + if (!(gOpenScanners[iHandle]->m_udpFd = + socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP))) + { + DBG (1, "sane_open: error opening socket\n"); + status = SANE_STATUS_IO_ERROR; + goto cleanup; + } + + /* connect to the scanner */ + memset (&gOpenScanners[iHandle]->m_sockAddr, 0, + sizeof (gOpenScanners[iHandle]->m_sockAddr)); + gOpenScanners[iHandle]->m_sockAddr.sin_family = AF_INET; + gOpenScanners[iHandle]->m_sockAddr.sin_port = htons (gScannerPort); + memcpy (&gOpenScanners[iHandle]->m_sockAddr.sin_addr, + pHostent->h_addr_list[0], pHostent->h_length); + if (connect (gOpenScanners[iHandle]->m_udpFd, + (struct sockaddr *) &gOpenScanners[iHandle]->m_sockAddr, + sizeof (gOpenScanners[iHandle]->m_sockAddr))) + { + DBG (1, "sane_open: error connecting to %s:%d\n", devicename, + gScannerPort); + status = SANE_STATUS_IO_ERROR; + goto cleanup; + } + + /* set fallback registration name */ + sprintf (gOpenScanners[iHandle]->m_regName, "Sane"); + + /* try to fill in hostname */ + gethostname (gOpenScanners[iHandle]->m_regName, REG_NAME_SIZE); + + /* just in case... */ + gOpenScanners[iHandle]->m_regName[REG_NAME_SIZE - 1] = 0; + + /* chop off any domain (if any) */ + if ((pDot = strchr (gOpenScanners[iHandle]->m_regName, '.'))) + *pDot = 0; + + DBG (5, "sane_open: connected to %s:%d as %s\n", devicename, gScannerPort, + gOpenScanners[iHandle]->m_regName); + + + /* set the handle */ + *handle = (SANE_Handle) (unsigned long)iHandle; + + return status; + +cleanup: + + if (iHandle != -1) + FreeScannerState (iHandle); + + return status; + +} /* sane_open */ + +/***********************************************************/ + +void +sane_close (SANE_Handle handle) +{ + + DBG( 5, "sane_close: %lx\n", (unsigned long)handle ); + + FreeScannerState ((unsigned long) handle); + +} /* sane_close */ + +/***********************************************************/ + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle __sane_unused__ handle, + SANE_Int option) +{ + + static SANE_Option_Descriptor numOptions = { + "num_options", + "Number of options", + "Number of options", + SANE_TYPE_INT, + SANE_UNIT_NONE, + 1, + 0, + 0, + {0} + }; + + if (option == 0) + return &numOptions; + else + return NULL; + +} /* sane_get_option_descriptor */ + +/***********************************************************/ + +SANE_Status +sane_control_option (SANE_Handle __sane_unused__ handle, SANE_Int option, + SANE_Action action, void *value, + SANE_Int __sane_unused__ * info) +{ + + static int numOptions = 1; + + if (action == SANE_ACTION_GET_VALUE && option == 0) + *(int *) value = numOptions; + + return SANE_STATUS_GOOD; + +} /* sane_control_option */ + +/***********************************************************/ + +SANE_Status +sane_get_parameters (SANE_Handle handle, SANE_Parameters * params) +{ + int iHandle = (int) (unsigned long)handle; + unsigned int width, height, imageSize; + struct PageInfo pageInfo; + + if (!gOpenScanners[iHandle]) + return SANE_STATUS_INVAL; + + /* fetch page info */ + memcpy( & pageInfo, gOpenScanners[iHandle]->m_pageInfo.m_pBuf, sizeof( pageInfo ) ); + + width = pageInfo.m_width; + height = pageInfo.m_height; + imageSize = width * height * 3; + + DBG( 5, "sane_get_parameters: bytes remaining on this page: %d, num pages: %d, size: %dx%d\n", + pageInfo.m_bytesRemaining, + gOpenScanners[iHandle]->m_numPages, + width, + height ); + + DBG (5, + "sane_get_parameters: handle %x: bytes outstanding: %lu, image size: %d\n", + iHandle, (unsigned long)gOpenScanners[iHandle]->m_imageData.m_used, imageSize); + + /* check for enough data */ + /* + if (gOpenScanners[iHandle]->m_imageData.m_used < imageSize) + { + DBG (1, "sane_get_parameters: handle %d: not enough data: %d < %d\n", + iHandle, gOpenScanners[iHandle]->m_imageData.m_used, imageSize); + return SANE_STATUS_INVAL; + } + */ + + + params->format = SANE_FRAME_RGB; + params->last_frame = SANE_TRUE; + params->lines = height; + params->depth = 8; + params->pixels_per_line = width; + params->bytes_per_line = width * 3; + + return SANE_STATUS_GOOD; + +} /* sane_get_parameters */ + +/***********************************************************/ + +SANE_Status +sane_start (SANE_Handle handle) +{ + + SANE_Status status = SANE_STATUS_GOOD; + struct ComBuf buf; + unsigned char sockBuf[SOCK_BUF_SIZE]; + int iHandle, nread; + int errorCheck = 0; + struct sockaddr_in myAddr; + socklen_t addrSize; + fd_set readFds; + struct timeval selTimeVal; + + iHandle = (int) (unsigned long)handle; + + DBG( 5, "sane_start: %x\n", iHandle ); + + /* fetch and check scanner index */ + if (!ValidScannerNumber (iHandle)) + return SANE_STATUS_INVAL; + + /* check if we still have oustanding pages of data on this handle */ + if (gOpenScanners[iHandle]->m_imageData.m_used){ + + /* remove empty page */ + PopFromComBuf ( & gOpenScanners[iHandle]->m_pageInfo, sizeof( struct PageInfo ) ); + return SANE_STATUS_GOOD; + + } + + /* determine local IP address */ + addrSize = sizeof (myAddr); + if (getsockname (gOpenScanners[iHandle]->m_udpFd, &myAddr, &addrSize)) + { + DBG (1, "sane_start: Error getting own IP address\n"); + return SANE_STATUS_IO_ERROR; + } + + /* init a buffer for our registration message */ + errorCheck |= InitComBuf (&buf); + + /* build packet */ + errorCheck |= InitPacket (&buf, 1); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-subscribe-user-name", 0x0b, + gOpenScanners[iHandle]->m_regName, + strlen (gOpenScanners[iHandle]->m_regName)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-subscribe-ip-address", 0x0a, + &myAddr.sin_addr, 4); + FinalisePacket (&buf); + + /* check nothing went wrong along the way */ + if (errorCheck) + { + status = SANE_STATUS_NO_MEM; + goto cleanup; + } + + /* send the packet */ + send (gOpenScanners[iHandle]->m_udpFd, buf.m_pBuf, buf.m_used, 0); + + + /* loop until done */ + gOpenScanners[iHandle]->m_bFinish = 0; + while (!gOpenScanners[iHandle]->m_bFinish) + { + + /* prepare select mask */ + FD_ZERO (&readFds); + FD_SET (gOpenScanners[iHandle]->m_udpFd, &readFds); + selTimeVal.tv_sec = 1; + selTimeVal.tv_usec = 0; + + + + DBG (5, "sane_start: waiting for scan signal\n"); + + /* wait again if nothing received */ + if (!select (gOpenScanners[iHandle]->m_udpFd + 1, + &readFds, NULL, NULL, &selTimeVal)) + continue; + + /* read from socket */ + nread = + read (gOpenScanners[iHandle]->m_udpFd, sockBuf, sizeof (sockBuf)); + + if (nread <= 0) + { + DBG (1, "sane_start: read returned %d\n", nread); + break; + } + + /* process the response */ + if (ProcessUdpResponse (sockBuf, nread, gOpenScanners[iHandle])) + { + status = SANE_STATUS_IO_ERROR; + goto cleanup; + } + + } /* while */ + + /* check whether we were cancelled */ + if ( gOpenScanners[iHandle]->m_bCancelled ) status = SANE_STATUS_CANCELLED; + +cleanup: + + FreeComBuf (&buf); + + return status; + +} /* sane_start */ + +/***********************************************************/ + +SANE_Status +sane_read (SANE_Handle handle, SANE_Byte * data, + SANE_Int max_length, SANE_Int * length) +{ + + int iHandle = (int) (unsigned long)handle; + int dataSize; + struct PageInfo pageInfo; + + DBG( 5, "sane_read: %x (max_length=%d)\n", iHandle, max_length ); + + *length = 0; + + if (!gOpenScanners[iHandle]) + return SANE_STATUS_INVAL; + + /* check for end of data (no further pages) */ + if ( ( ! gOpenScanners[iHandle]->m_imageData.m_used ) + || ( ! gOpenScanners[iHandle]->m_numPages ) ) + { + /* remove empty page if there are no more cached pages */ + PopFromComBuf ( & gOpenScanners[iHandle]->m_pageInfo, sizeof( struct PageInfo ) ); + + return SANE_STATUS_EOF; + } + + /* fetch page info */ + memcpy( & pageInfo, gOpenScanners[iHandle]->m_pageInfo.m_pBuf, sizeof( pageInfo ) ); + + /* check for end of page data (we still have further cached pages) */ + if ( pageInfo.m_bytesRemaining < 1 ) return SANE_STATUS_EOF; + + /* send the remainder of the current image */ + dataSize = pageInfo.m_bytesRemaining; + + /* unless there's not enough room in the output buffer */ + if (dataSize > max_length) + dataSize = max_length; + + /* update the data sent counters */ + gOpenScanners[iHandle]->m_bytesRead += dataSize; + pageInfo.m_bytesRemaining -= dataSize; + + /* update counter */ + memcpy( gOpenScanners[iHandle]->m_pageInfo.m_pBuf, & pageInfo, sizeof( pageInfo ) ); + + /* check for end of page */ + if ( pageInfo.m_bytesRemaining < 1 ){ + + /* yes, so remove page info */ + gOpenScanners[iHandle]->m_numPages--; + + } /* if */ + + DBG (5, + "sane_read: sending %d bytes, image total %d, %d page bytes remaining, %lu total remaining, image: %dx%d\n", + dataSize, gOpenScanners[iHandle]->m_bytesRead, pageInfo.m_bytesRemaining , + (unsigned long)(gOpenScanners[iHandle]->m_imageData.m_used - dataSize), + pageInfo.m_width, + pageInfo.m_height); + + /* copy the data */ + memcpy (data, gOpenScanners[iHandle]->m_imageData.m_pBuf, dataSize); + if (PopFromComBuf (&gOpenScanners[iHandle]->m_imageData, dataSize)) + return SANE_STATUS_NO_MEM; + + *length = dataSize; + + return SANE_STATUS_GOOD; + +} /* sane_read */ + +/***********************************************************/ + +void +sane_cancel (SANE_Handle handle) +{ + int iHandle = (int) (unsigned long)handle; + + DBG( 5, "sane_cancel: %x\n", iHandle ); + + /* signal that bad things are afoot */ + gOpenScanners[iHandle]->m_bFinish = 1; + gOpenScanners[iHandle]->m_bCancelled = 1; + +} /* sane_cancel */ + +/***********************************************************/ + +SANE_Status +sane_set_io_mode (SANE_Handle __sane_unused__ handle, + SANE_Bool __sane_unused__ non_blocking) +{ + + return SANE_STATUS_UNSUPPORTED; + +} /* sane_set_io_mode */ + +/***********************************************************/ + +SANE_Status +sane_get_select_fd (SANE_Handle __sane_unused__ handle, + SANE_Int __sane_unused__ * fd) +{ + + return SANE_STATUS_UNSUPPORTED; + +} /* sane_get_select_fd */ + +/***********************************************************/ + +/* Clears the contents of gKnownDevices and zeros it */ +void +ClearKnownDevices () +{ + + int i; + + for (i = 0; i < MAX_SCANNERS; ++i) + { + + if (gKnownDevices[i]) + { + if (gKnownDevices[i]->m_pName) free ( gKnownDevices[i]->m_pName ); + if (gKnownDevices[i]->m_pModel) free ( gKnownDevices[i]->m_pModel ); + free ( gKnownDevices[i] ); + } + gKnownDevices[i] = NULL; + + } + +} /* ClearKnownDevices */ + +/***********************************************************/ + +/* print hex buffer to debug output */ +void +HexDump (int debugLevel, const unsigned char *buf, size_t bufSize) +{ + + unsigned int i, j; + + char itemBuf[16] = { 0 }, lineBuf[256] = { 0 }; + + if (DBG_LEVEL < debugLevel) + return; + + for (i = 0; i < bufSize; ++i) + { + + if (!(i % 16)) + sprintf (lineBuf, "%p: ", (buf + i)); + + sprintf (itemBuf, "%02x ", (const unsigned int) buf[i]); + + strncat (lineBuf, itemBuf, sizeof (lineBuf)); + + if ((i + 1) % 16) + continue; + + /* print string equivalent */ + for (j = i - 15; j <= i; ++j) + { + + if ((buf[j] >= 0x20) && (!(buf[j] & 0x80))) + { + sprintf (itemBuf, "%c", buf[j]); + } + else + { + sprintf (itemBuf, "."); + } + strncat (lineBuf, itemBuf, sizeof (lineBuf)); + + } /* for j */ + + DBG (debugLevel, "%s\n", lineBuf); + lineBuf[0] = 0; + + } /* for i */ + + if (i % 16) + { + + for (j = (i % 16); j < 16; ++j) + { + strncat (lineBuf, " ", sizeof (lineBuf)); + } + for (j = 1 + i - ((i + 1) % 16); j < i; ++j) + { + if ((buf[j] >= 0x20) && (!(buf[j] & 0x80))) + { + sprintf (itemBuf, "%c", buf[j]); + } + else + { + strcpy (itemBuf, "."); + } + strncat (lineBuf, itemBuf, sizeof (lineBuf)); + } + DBG (debugLevel, "%s\n", lineBuf); + } +} /* HexDump */ + +/***********************************************************/ + +/* initialise a ComBuf struct + \return 0 on success, >0 on failure +*/ +int +InitComBuf (struct ComBuf *pBuf) +{ + + memset (pBuf, 0, sizeof (struct ComBuf)); + + pBuf->m_pBuf = malloc (INITIAL_COM_BUF_SIZE); + if (!pBuf->m_pBuf) + return 1; + + pBuf->m_capacity = INITIAL_COM_BUF_SIZE; + pBuf->m_used = 0; + + return 0; + +} /* InitComBuf */ + +/***********************************************************/ + +/* free a ComBuf struct */ +void +FreeComBuf (struct ComBuf *pBuf) +{ + + if (pBuf->m_pBuf) + free (pBuf->m_pBuf); + memset (pBuf, 0, sizeof (struct ComBuf)); + +} /* FreeComBuf */ + +/***********************************************************/ + +/* add data to a ComBuf struct + \return 0 on success, >0 on failure + \note If pData is NULL then buffer size will be increased but no copying will take place + \note In case of failure pBuf will be released using FreeComBuf +*/ +int +AppendToComBuf (struct ComBuf *pBuf, const unsigned char *pData, + size_t datSize) +{ + + size_t newSize; + + /* check we have enough space */ + if (pBuf->m_used + datSize > pBuf->m_capacity) + { + /* nope - allocate some more */ + newSize = pBuf->m_used + datSize + INITIAL_COM_BUF_SIZE; + pBuf->m_pBuf = realloc (pBuf->m_pBuf, newSize); + if (!pBuf->m_pBuf) + { + DBG (1, "AppendToComBuf: memory allocation error"); + FreeComBuf (pBuf); + return (1); + } + pBuf->m_capacity = newSize; + } /* if */ + + /* add data */ + if (pData) + memcpy (pBuf->m_pBuf + pBuf->m_used, pData, datSize); + pBuf->m_used += datSize; + + return 0; + +} /* AppendToComBuf */ + +/***********************************************************/ + +/* append message to a packet + \return 0 if ok, 1 if bad */ +int +AppendMessageToPacket (struct ComBuf *pBuf, /* packet to which to append */ + char messageType, /* type of message */ + char *messageName, /* name of message */ + char valueType, /* type of value */ + void *pValue, /* pointer to value */ + size_t valueLen /* length of value (bytes) */ + ) +{ + + unsigned short slen; + + /* message type */ + AppendToComBuf (pBuf, (void *) &messageType, 1); + + /* message length */ + slen = htons (strlen (messageName)); + AppendToComBuf (pBuf, (void *) &slen, 2); + + /* and name */ + AppendToComBuf (pBuf, (void *) messageName, strlen (messageName)); + + /* and value type */ + AppendToComBuf (pBuf, (void *) &valueType, 1); + + /* value length */ + slen = htons (valueLen); + AppendToComBuf (pBuf, (void *) &slen, 2); + + /* and value */ + return (AppendToComBuf (pBuf, (void *) pValue, valueLen)); + +} /* AppendMessageToPacket */ + +/***********************************************************/ + +/* Initialise a packet + \param pBuf : An initialise ComBuf + \param type : either 0x01 ("normal" ) or 0x02 ("reply" ) + \return 0 on success, >0 otherwise +*/ +int +InitPacket (struct ComBuf *pBuf, char type) +{ + + char header[8] = { 2, 0, 0, 2, 0, 0, 0, 0 }; + + header[2] = type; + + /* reset size */ + pBuf->m_used = 0; + + /* add header */ + return (AppendToComBuf (pBuf, (void *) &header, 8)); + +} /* InitPacket */ + +/***********************************************************/ + +/* write length data to packet header +*/ +void +FinalisePacket (struct ComBuf *pBuf) +{ + + /* sanity check */ + if (pBuf->m_used < 8) + return; + + /* set the size */ + *((unsigned short *) (pBuf->m_pBuf + 6)) = htons (pBuf->m_used - 8); + + DBG (20, "FinalisePacket: outgoing packet:\n"); + HexDump (20, pBuf->m_pBuf, pBuf->m_used); + +} /* FinalisePacket */ + +/***********************************************************/ + +/* \return 1 if message is complete, 0 otherwise */ +int +MessageIsComplete (unsigned char *pData, size_t size) +{ + unsigned short dataSize; + + /* sanity check */ + if (size < 8) + return 0; + + /* :NOTE: we can't just cast to a short as data may not be aligned */ + dataSize = (((unsigned short) pData[6]) << 8) | pData[7]; + + DBG (20, "MessageIsComplete: data size = %d\n", dataSize); + + if (size >= (size_t) (dataSize + 8)) + return 1; + else + return 0; + +} /* MessageIsComplete */ + +/***********************************************************/ + +/* process a registration broadcast response + \return struct DeviceRecord pointer on success (caller frees), NULL on failure +*/ +struct DeviceRecord * +ProcessFindResponse (unsigned char *pData, size_t size) +{ + + struct DeviceRecord *pDevice = NULL; + unsigned short messageSize, nameSize, valueSize; + unsigned char *pItem, *pEnd, *pValue; + char printerName[256] = { 0 }; + char printerModel[256] = "1600n"; + char *pModel, *pName; + + + DBG (10, "ProcessFindResponse: processing %lu bytes, pData=%p\n", + (unsigned long)size, pData); + + /* check we have a complete packet */ + if (!MessageIsComplete (pData, size)) + { + DBG (1, "ProcessFindResponse: Ignoring incomplete packet\n"); + return NULL; + } + + /* extract data size */ + messageSize = (((unsigned short) (pData[6])) << 8) | pData[7]; + + /* loop through items in message */ + pItem = pData + 8; + pEnd = pItem + messageSize; + while (pItem < pEnd) + { + + pItem++; + nameSize = (((unsigned short) pItem[0]) << 8) | pItem[1]; + pItem += 2; + pName = (char *) pItem; + + pItem += nameSize; + + pItem++; + valueSize = (((unsigned short) pItem[0]) << 8) | pItem[1]; + pItem += 2; + + pValue = pItem; + + pItem += valueSize; + + /* process the item */ + if (!strncmp ("std-scan-discovery-ip", pName, nameSize)) + { + + snprintf (printerName, sizeof (printerName), "%d.%d.%d.%d", + (int) pValue[0], + (int) pValue[1], (int) pValue[2], (int) pValue[3]); + DBG (2, "%s\n", printerName); + + } + else if (!strncmp ("std-scan-discovery-model-name", pName, nameSize)) + { + + memset (printerModel, 0, sizeof (printerModel)); + if (valueSize > (sizeof (printerModel) - 1)) + valueSize = sizeof (printerModel) - 1; + memcpy (printerModel, pValue, valueSize); + DBG (2, "std-scan-discovery-model-name: %s\n", printerModel); + + } + + } /* while pItem */ + + /* just in case nothing sensible was found */ + if ( ! strlen( printerName ) ) return NULL; + + pDevice = malloc (sizeof (struct DeviceRecord)); + if (!pDevice) + { + DBG (1, "ProcessFindResponse: memory allocation failure\n"); + return NULL; + } + + /* knock off "Dell " from start of model name */ + pModel = printerModel; + if ( ! strncmp( pModel, "Dell ", 5 ) ) + pModel += 5; + + pDevice->m_pName = strdup( printerName ); + pDevice->m_device.vendor = "Dell"; + pDevice->m_pModel = strdup (pModel); + pDevice->m_device.type = "multi-function peripheral"; + + pDevice->m_device.name = pDevice->m_pName; + pDevice->m_device.model = pDevice->m_pModel; + + return pDevice; + +} /* ProcessFindResponse */ + +/***********************************************************/ + +/* frees a scanner state struct stored in gOpenScanners */ +void +FreeScannerState (int iHandle) +{ + + /* check range etc */ + if (!ValidScannerNumber (iHandle)) + return; + + /* close UDP handle */ + if (gOpenScanners[iHandle]->m_udpFd) + close (gOpenScanners[iHandle]->m_udpFd); + + /* free m_buf */ + FreeComBuf (&gOpenScanners[iHandle]->m_buf); + + /* free m_imageData */ + FreeComBuf (&gOpenScanners[iHandle]->m_imageData); + + /* free the struct */ + free (gOpenScanners[iHandle]); + + /* set pointer to NULL */ + gOpenScanners[iHandle] = NULL; + +} /* FreeScannerState */ + +/***********************************************************/ + +/* \return 1 if iHandle is a valid member of gOpenScanners, 0 otherwise */ +int +ValidScannerNumber (int iHandle) +{ + /* check range */ + if ((iHandle < 0) || (iHandle >= MAX_SCANNERS)) + { + DBG (1, "ValidScannerNumber: invalid scanner index %d", iHandle); + return 0; + } + + /* check non-NULL pointer */ + if (!gOpenScanners[iHandle]) + { + DBG (1, "ValidScannerNumber: NULL scanner struct %d", iHandle); + return 0; + } + + /* OK */ + return 1; + +} /* ValidScannerNumber */ + +/***********************************************************/ + +/* process UDP responses + \return 0 in success, >0 otherwise */ +static int +ProcessUdpResponse (unsigned char *pData, size_t size, + struct ScannerState *pState) +{ + + unsigned short messageSize, nameSize, valueSize; + unsigned char *pItem, *pEnd, *pValue; + char sockBuf[SOCK_BUF_SIZE], *pName; + struct ComBuf tcpBuf; + int nread; + unsigned int numUsed; + + HexDump (15, pData, size); + + DBG (10, "ProcessUdpResponse: processing %lu bytes, pData=%p\n", + (unsigned long)size, pData); + + /* check we have a complete packet */ + if (!MessageIsComplete (pData, size)) + { + DBG (1, "ProcessUdpResponse: Ignoring incomplete packet\n"); + return 1; + } + + /* init a com buf for use in tcp communication */ + InitComBuf (&tcpBuf); + + /* extract data size */ + messageSize = (((unsigned short) (pData[6])) << 8) | pData[7]; + + /* loop through items in message */ + pItem = pData + 8; + pEnd = pItem + messageSize; + while (pItem < pEnd) + { + + pItem++; + nameSize = (((unsigned short) pItem[0]) << 8) | pItem[1]; + pItem += 2; + pName = (char *) pItem; + + pItem += nameSize; + + pItem++; + valueSize = (((unsigned short) pItem[0]) << 8) | pItem[1]; + pItem += 2; + + pValue = pItem; + + pItem += valueSize; + + /* process the item */ + if (!strncmp ("std-scan-request-tcp-connection", pName, nameSize)) + { + + /* open TCP socket to scanner */ + if (!(pState->m_tcpFd = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP))) + { + DBG (1, "ProcessUdpResponse: error opening TCP socket\n"); + return 2; + } + if (connect (pState->m_tcpFd, + (struct sockaddr *) &pState->m_sockAddr, + sizeof (pState->m_sockAddr))) + { + DBG (1, + "ProcessUdpResponse: error connecting to scanner TCP port\n"); + goto cleanup; + } + + DBG (1, "ProcessUdpResponse: opened TCP connection to scanner\n"); + + /* clear read buf */ + tcpBuf.m_used = 0; + + /* TCP read loop */ + while (1) + { + + nread = read (pState->m_tcpFd, sockBuf, sizeof (sockBuf)); + + if (nread <= 0) + { + DBG (1, "ProcessUdpResponse: TCP read returned %d\n", + nread); + break; + } + + /* append message to buffer */ + if (AppendToComBuf (&tcpBuf, (unsigned char *) sockBuf, nread)) + goto cleanup; + + /* process all available responses */ + while (tcpBuf.m_used) + { + + /* note the buffer size before the call */ + numUsed = tcpBuf.m_used; + + /* process the response */ + if (ProcessTcpResponse (pState, &tcpBuf)) + goto cleanup; + + /* if the buffer size has not changed then assume no more processing is possible */ + if (numUsed == tcpBuf.m_used) + break; + + } /* while */ + + } /* while */ + + close (pState->m_tcpFd); + DBG (1, "ProcessUdpResponse: closed TCP connection to scanner\n"); + + /* signal end of session */ + pState->m_bFinish = 1; + + } /* if */ + + } /* while pItem */ + + return 0; + +cleanup: + + FreeComBuf (&tcpBuf); + close (pState->m_tcpFd); + return 3; + +} /* ProcessUdpResponse */ + +/***********************************************************/ + +/* process TCP responses, \return 0 in success, >0 otherwise */ +int +ProcessTcpResponse (struct ScannerState *pState, struct ComBuf *pTcpBuf) +{ + + struct ComBuf buf; + unsigned short messageSize = 0, nameSize, valueSize, dataChunkSize; + unsigned char *pItem, *pEnd, *pValue; + unsigned char *pData = pTcpBuf->m_pBuf; + char *pName; + unsigned int uiVal; + int errorCheck = 0; + int bProcessImage = 0; + + DBG (10, "ProcessTcpResponse: processing %lu bytes, pData=%p\n", + (unsigned long)pTcpBuf->m_used, pData); + HexDump (15, pData, pTcpBuf->m_used); + + /* if message not complete then wait for more to arrive */ + if (!MessageIsComplete (pData, pTcpBuf->m_used)) + { + DBG (10, "ProcessTcpResponse: incomplete message, returning\n"); + return 0; + } + + /* init a buffer for our outbound messages */ + if (InitComBuf (&buf)) + { + errorCheck |= 1; + goto cleanup; + } + + /* extract data size */ + messageSize = (((unsigned short) (pData[6])) << 8) | pData[7]; + + /* loop through items in message */ + pItem = pData + 8; + pEnd = pItem + messageSize; + while (pItem < pEnd) + { + + pItem++; + nameSize = (((unsigned short) pItem[0]) << 8) | pItem[1]; + pItem += 2; + pName = (char *) pItem; + + pItem += nameSize; + + pItem++; + valueSize = (((unsigned short) pItem[0]) << 8) | pItem[1]; + pItem += 2; + + pValue = pItem; + + pItem += valueSize; + + /* process the item */ + if (!strncmp ("std-scan-session-open", pName, nameSize)) + { + + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-session-open-response", 0x05, + &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + + } + else if (!strncmp ("std-scan-getclientpref", pName, nameSize)) + { + + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-getclientpref-x1", + 0x05, &uiVal, sizeof (uiVal)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-getclientpref-x2", + 0x05, &uiVal, sizeof (uiVal)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-getclientpref-y1", + 0x05, &uiVal, sizeof (uiVal)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-getclientpref-y2", + 0x05, &uiVal, sizeof (uiVal)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-xresolution", 0x04, + &pState->m_xres, sizeof (pState->m_xres)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-yresolution", 0x04, + &pState->m_yres, sizeof (pState->m_yres)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-image-composition", + 0x06, &pState->m_composition, + sizeof (pState->m_composition)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-brightness", 0x02, + &pState->m_brightness, + sizeof (pState->m_brightness)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-image-compression", + 0x06, &pState->m_compression, + sizeof (pState->m_compression)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-file-type", 0x06, + &pState->m_fileType, + sizeof (pState->m_fileType)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-paper-size-detect", + 0x06, &uiVal, sizeof (uiVal)); + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-getclientpref-paper-scanner-type", + 0x06, &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + + } + else if (!strncmp ("std-scan-document-start", pName, nameSize)) + { + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-document-start-response", 0x05, + &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + } + else if (!strncmp ("std-scan-document-file-type", pName, nameSize)) + { + memcpy (&pState->m_fileType, pValue, sizeof (pState->m_fileType)); + DBG (5, "File type: %x\n", ntohl (pState->m_fileType)); + } + else + if (!strncmp ("std-scan-document-image-compression", pName, nameSize)) + { + memcpy (&pState->m_compression, pValue, + sizeof (pState->m_compression)); + DBG (5, "Compression: %x\n", ntohl (pState->m_compression)); + + } + else if (!strncmp ("std-scan-document-xresolution", pName, nameSize)) + { + memcpy (&pState->m_xres, pValue, sizeof (pState->m_xres)); + DBG (5, "X resolution: %d\n", ntohs (pState->m_xres)); + } + else if (!strncmp ("std-scan-document-yresolution", pName, nameSize)) + { + memcpy (&pState->m_yres, pValue, sizeof (pState->m_yres)); + DBG (5, "Y resolution: %d\n", ntohs (pState->m_yres)); + } + else if (!strncmp ("std-scan-page-widthpixel", pName, nameSize)) + { + if (1 || !pState->m_pixelWidth) + { + memcpy (&pState->m_pixelWidth, pValue, + sizeof (pState->m_pixelWidth)); + DBG (5, "Width: %d\n", ntohl (pState->m_pixelWidth)); + } + else + { + DBG (5, "Ignoring width (already have a value)\n"); + } + } + else if (!strncmp ("std-scan-page-heightpixel", pName, nameSize)) + { + if (1 || !pState->m_pixelHeight) + { + memcpy (&pState->m_pixelHeight, pValue, + sizeof (pState->m_pixelHeight)); + DBG (5, "Height: %d\n", ntohl (pState->m_pixelHeight)); + } + else + { + DBG (5, "Ignoring height (already have a value)\n"); + } + } + else if (!strncmp ("std-scan-page-start", pName, nameSize)) + { + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-page-start-response", + 0x05, &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + + /* reset the data buffer ready to store a new page */ + pState->m_buf.m_used = 0; + + /* init current page size */ + pState->m_currentPageBytes = 0; + + pState->m_pixelWidth = 0; + pState->m_pixelHeight = 0; + } + else if (!strncmp ("std-scan-page-end", pName, nameSize)) + { + bProcessImage = 1; + + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, "std-scan-page-end-response", + 0x05, &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + } + else if (!strncmp ("std-scan-document-end", pName, nameSize)) + { + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-document-end-response", 0x05, + &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + + /* reset the data buffer ready to store a new page */ + pState->m_buf.m_used = 0; + } + else if (!strncmp ("std-scan-session-end", pName, nameSize)) + { + errorCheck |= InitPacket (&buf, 0x02); + uiVal = 0; + errorCheck |= + AppendMessageToPacket (&buf, 0x22, + "std-scan-session-end-response", 0x05, + &uiVal, sizeof (uiVal)); + FinalisePacket (&buf); + send (pState->m_tcpFd, buf.m_pBuf, buf.m_used, 0); + + /* initialise a shutodwn of the socket */ + shutdown (pState->m_tcpFd, SHUT_RDWR); + } + else if (!strncmp ("std-scan-scandata-error", pName, nameSize)) + { + /* determine the size of data in this chunk */ + dataChunkSize = (pItem[6] << 8) + pItem[7]; + + pItem += 8; + + DBG (10, "Reading %d bytes of scan data\n", dataChunkSize); + + /* append message to buffer */ + errorCheck |= AppendToComBuf (&pState->m_buf, pItem, dataChunkSize); + + pItem += dataChunkSize; + + DBG (10, "Accumulated %lu bytes of scan data so far\n", + (unsigned long)pState->m_buf.m_used); + } /* if */ + } /* while */ + + /* process page data if required */ + if ( bProcessImage ) errorCheck |= ProcessPageData (pState); + +cleanup: + + /* remove processed data (including 8 byte header) from start of tcp buffer */ + PopFromComBuf (pTcpBuf, messageSize + 8); + + /* free com buf */ + FreeComBuf (&buf); + + return errorCheck; + +} /* ProcessTcpResponse */ + +/***********************************************************/ + +/* remove data from the front of a ComBuf struct + \return 0 if sucessful, >0 otherwise +*/ +int +PopFromComBuf (struct ComBuf *pBuf, size_t datSize) +{ + + /* check if we're trying to remove more data than is present */ + if (datSize > pBuf->m_used) + { + pBuf->m_used = 0; + return 1; + } + + /* check easy cases */ + if ((!datSize) || (datSize == pBuf->m_used)) + { + pBuf->m_used -= datSize; + return 0; + } + + /* move remaining memory contents to start */ + memmove (pBuf->m_pBuf, pBuf->m_pBuf + datSize, pBuf->m_used - datSize); + + pBuf->m_used -= datSize; + return 0; + +} /* PopFromComBuf */ + +/***********************************************************/ + +/* Process the data from a single scanned page, \return 0 in success, >0 otherwise */ +int +ProcessPageData (struct ScannerState *pState) +{ + + FILE *fTmp; + int fdTmp; + struct jpeg_source_mgr jpegSrcMgr; + struct JpegDataDecompState jpegCinfo; + struct jpeg_error_mgr jpegErr; + int numPixels, iPixel, width, height, scanLineSize, imageBytes; + int ret = 0; + struct PageInfo pageInfo; + + JSAMPLE *pJpegLine = NULL; + uint32 *pTiffRgba = NULL; + unsigned char *pOut; + char tiffErrBuf[1024]; + + TIFF *pTiff = NULL; + + /* If there's no data then there's nothing to write */ + if (!pState->m_buf.m_used) + return 0; + + DBG (1, "ProcessPageData: Got compression %x\n", + ntohl (pState->m_compression)); + + switch (ntohl (pState->m_compression)) + { + + case 0x20: + /* decode as JPEG if appropriate */ + { + + jpegSrcMgr.resync_to_restart = jpeg_resync_to_restart; + jpegSrcMgr.init_source = JpegDecompInitSource; + jpegSrcMgr.fill_input_buffer = JpegDecompFillInputBuffer; + jpegSrcMgr.skip_input_data = JpegDecompSkipInputData; + jpegSrcMgr.term_source = JpegDecompTermSource; + + jpegCinfo.m_cinfo.err = jpeg_std_error (&jpegErr); + jpeg_create_decompress (&jpegCinfo.m_cinfo); + jpegCinfo.m_cinfo.src = &jpegSrcMgr; + jpegCinfo.m_bytesRemaining = pState->m_buf.m_used; + jpegCinfo.m_pData = pState->m_buf.m_pBuf; + + jpeg_read_header (&jpegCinfo.m_cinfo, TRUE); + jpeg_start_decompress (&jpegCinfo.m_cinfo); + + /* allocate space for a single scanline */ + scanLineSize = jpegCinfo.m_cinfo.output_width + * jpegCinfo.m_cinfo.output_components; + DBG (1, "ProcessPageData: image dimensions: %d x %d, line size: %d\n", + jpegCinfo.m_cinfo.output_width, + jpegCinfo.m_cinfo.output_height, scanLineSize); + + pJpegLine = calloc (scanLineSize, sizeof (JSAMPLE)); + if (!pJpegLine) + { + DBG (1, "ProcessPageData: memory allocation error\n"); + ret = 1; + goto JPEG_CLEANUP; + } /* if */ + + /* note dimensions - may be different from those previously reported */ + pState->m_pixelWidth = htonl (jpegCinfo.m_cinfo.output_width); + pState->m_pixelHeight = htonl (jpegCinfo.m_cinfo.output_height); + + /* decode scanlines */ + while (jpegCinfo.m_cinfo.output_scanline + < jpegCinfo.m_cinfo.output_height) + { + DBG (20, "Reading scanline %d of %d\n", + jpegCinfo.m_cinfo.output_scanline, + jpegCinfo.m_cinfo.output_height); + + /* read scanline */ + jpeg_read_scanlines (&jpegCinfo.m_cinfo, &pJpegLine, 1); + + /* append to output buffer */ + ret |= AppendToComBuf (&pState->m_imageData, + pJpegLine, scanLineSize); + + } /* while */ + + /* update info for this page */ + pageInfo.m_width = jpegCinfo.m_cinfo.output_width; + pageInfo.m_height = jpegCinfo.m_cinfo.output_height; + pageInfo.m_totalSize = pageInfo.m_width * pageInfo.m_height * 3; + pageInfo.m_bytesRemaining = pageInfo.m_totalSize; + + DBG( 1, "Process page data: page %d: JPEG image: %d x %d, %d bytes\n", + pState->m_numPages, pageInfo.m_width, pageInfo.m_height, pageInfo.m_totalSize ); + + ret |= AppendToComBuf( & pState->m_pageInfo, (unsigned char*)& pageInfo, sizeof( pageInfo ) ); + ++( pState->m_numPages ); + + JPEG_CLEANUP: + jpeg_finish_decompress (&jpegCinfo.m_cinfo); + jpeg_destroy_decompress (&jpegCinfo.m_cinfo); + + if (pJpegLine) + free (pJpegLine); + + return ret; + } /* case JPEG */ + + case 0x08: + /* CCITT Group 4 Fax data */ + { + /* get a temp file + :TODO: 2006-04-18: Use TIFFClientOpen and do everything in RAM + */ + fTmp = tmpfile (); + fdTmp = fileno (fTmp); + + pTiff = TIFFFdOpen (fdTmp, "tempfile", "w"); + if (!pTiff) + { + DBG (1, "ProcessPageData: Error opening temp TIFF file"); + ret = SANE_STATUS_IO_ERROR; + goto TIFF_CLEANUP; + } + + /* create a TIFF file */ + width = ntohl (pState->m_pixelWidth); + height = ntohl (pState->m_pixelHeight); + TIFFSetField (pTiff, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField (pTiff, TIFFTAG_IMAGELENGTH, height); + TIFFSetField (pTiff, TIFFTAG_BITSPERSAMPLE, 1); + TIFFSetField (pTiff, TIFFTAG_PHOTOMETRIC, 0); /* 0 is white */ + TIFFSetField (pTiff, TIFFTAG_COMPRESSION, 4); /* CCITT Group 4 */ + TIFFSetField (pTiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + + TIFFWriteRawStrip (pTiff, 0, pState->m_buf.m_pBuf, + pState->m_buf.m_used); + + if (0 > TIFFRGBAImageOK (pTiff, tiffErrBuf)) + { + DBG (1, "ProcessPageData: %s\n", tiffErrBuf); + ret = SANE_STATUS_IO_ERROR; + goto TIFF_CLEANUP; + } + + /* allocate space for RGBA representation of image */ + numPixels = height * width; + DBG (20, "ProcessPageData: num TIFF RGBA pixels: %d\n", numPixels); + if (!(pTiffRgba = calloc (numPixels, sizeof (u_long)))) + { + ret = SANE_STATUS_NO_MEM; + goto TIFF_CLEANUP; + } + + /* make space in image buffer to store the results */ + imageBytes = width * height * 3; + ret |= AppendToComBuf (&pState->m_imageData, NULL, imageBytes); + if (ret) + goto TIFF_CLEANUP; + + /* get a pointer to the start of the output data */ + pOut = pState->m_imageData.m_pBuf + + pState->m_imageData.m_used - imageBytes; + + /* read RGBA image */ + DBG (20, "ProcessPageData: setting up read buffer\n"); + TIFFReadBufferSetup (pTiff, NULL, width * height * sizeof (u_long)); + DBG (20, "ProcessPageData: reading RGBA data\n"); + TIFFReadRGBAImageOriented (pTiff, width, height, pTiffRgba, + ORIENTATION_TOPLEFT, 0); + + /* loop over pixels */ + for (iPixel = 0; iPixel < numPixels; ++iPixel) + { + + *(pOut++) = TIFFGetR (pTiffRgba[iPixel]); + *(pOut++) = TIFFGetG (pTiffRgba[iPixel]); + *(pOut++) = TIFFGetB (pTiffRgba[iPixel]); + + } /* for iRow */ + + + + /* update info for this page */ + pageInfo.m_width = width; + pageInfo.m_height = height; + pageInfo.m_totalSize = pageInfo.m_width * pageInfo.m_height * 3; + pageInfo.m_bytesRemaining = pageInfo.m_totalSize; + + DBG( 1, "Process page data: page %d: TIFF image: %d x %d, %d bytes\n", + pState->m_numPages, width, height, pageInfo.m_totalSize ); + + ret |= AppendToComBuf( & pState->m_pageInfo, (unsigned char*)& pageInfo, sizeof( pageInfo ) ); + ++( pState->m_numPages ); + + TIFF_CLEANUP: + if (pTiff) + TIFFClose (pTiff); + if (fTmp) + fclose (fTmp); + if (pTiffRgba) + free (pTiffRgba); + return ret; + + } /* case CCITT */ + default: + /* this is not expected or very useful */ + { + DBG (1, "ProcessPageData: Unexpected compression flag %d\n", ntohl (pState->m_compression)); + ret = SANE_STATUS_IO_ERROR; + } + } /* switch */ + + return ret; +} /* ProcessPageData */ + +/***********************************************************/ + +void +JpegDecompInitSource (j_decompress_ptr cinfo) +/* Libjpeg decompression interface */ +{ + cinfo->src->bytes_in_buffer = 0; + +} /* JpegDecompInitSource */ + +/***********************************************************/ + +boolean +JpegDecompFillInputBuffer (j_decompress_ptr cinfo) +/* Libjpeg decompression interface */ +{ + struct JpegDataDecompState *pState = (struct JpegDataDecompState *) cinfo; + static const unsigned char eoiByte[] = { + 0xFF, JPEG_EOI + }; + + DBG (10, "JpegDecompFillInputBuffer: bytes remaining: %d\n", + pState->m_bytesRemaining); + + if (!pState->m_bytesRemaining) + { + + /* no input data available so return dummy data */ + cinfo->src->bytes_in_buffer = 2; + cinfo->src->next_input_byte = (const JOCTET *) eoiByte; + + } + else + { + + /* point to data */ + cinfo->src->bytes_in_buffer = pState->m_bytesRemaining; + cinfo->src->next_input_byte = (const JOCTET *) pState->m_pData; + + /* note that data is now gone */ + pState->m_bytesRemaining = 0; + + } /* if */ + + return TRUE; + +} /* JpegDecompFillInputBuffer */ + +/***********************************************************/ + +void +JpegDecompSkipInputData (j_decompress_ptr cinfo, long numBytes) +/* Libjpeg decompression interface */ +{ + DBG (10, "JpegDecompSkipInputData: skipping %ld bytes\n", numBytes); + + cinfo->src->bytes_in_buffer -= numBytes; + cinfo->src->next_input_byte += numBytes; + +} /* JpegDecompSkipInputData */ + +/***********************************************************/ + +void +JpegDecompTermSource (j_decompress_ptr __sane_unused__ cinfo) +/* Libjpeg decompression interface */ +{ + /* nothing to do */ + +} /* JpegDecompTermSource */ + +/***********************************************************/ |