/* d4lib.c
 * Copyright (C) 2001 Jean-Jacques Sarton jj.sarton@t-online.de
 *
 * 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, see <https://www.gnu.org/licenses/>.
 */

/* this file will be a library, which will allow to use the EPSON
 * Stylus Scanner and also allow to send commands to the
 * Stylus Color 480/580.
 *
 * At this stage a lot of things are not implemented and the
 * first goal is to get the Stylus Scanner printing.
 * This may be reached if this file is compiled with the TEST
 * option set.
 *
 * I donīt own a Stylus Scanner and I am not able to test this
 * code, as desired. Printing on a Stylus Photo 1290 work fine
 * with this.
 *
 * The best way to get the Stylus Scanner working is to test
 * this and also correct my possibly errors.
 * Programming knowledge will be helpful for this.
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "d4lib.h"


#ifndef RDTIMEOUT
#define RDTIMEOUT     10000
#define WRTIMEOUT     10000
#define RDDATATIMEOUT 1000
#define MICROTIMEOUT  1
#endif

int d4WrTimeout     = WRTIMEOUT;
int d4RdTimeout     = RDTIMEOUT;
int d4RdDataTimeout = RDDATATIMEOUT;
int d4MicroTimeout  = 1;
int ppid        = 0;

int debugD4     = 1;

typedef void (*signalHandler_t)(int);
static signalHandler_t sig;
static int timeoutGot = 0;
static int _readData(int fd, unsigned char *buf, int len);

static int d4Errno = 0;

/* commands for the D4 protocol

Transaction    Cmd    Reply
-------------- ----   -----
Init           0x00   0x80
OpenChannel    0x01   0x81
CloseChannel   0x02   0x82
Credit         0x03   0x83
CreditRequest  0x04   0x84
Exit           0x08   0x88
GetSocketID    0x09   0x89
GetServiceName 0x0a   0x8a
Error          0x7f   -

*/

typedef struct cmdHeader_s
{
   unsigned char psid;
   unsigned char ssid;
   unsigned char lengthH;
   unsigned char lengthL;
   unsigned char credit;
   unsigned char control;
   unsigned char command;
} cmdHeader_t;

typedef struct replyHeader_s
{
   unsigned char psid;
   unsigned char ssid;
   unsigned char lengthH;
   unsigned char lengthL;
   unsigned char credit;
   unsigned char control;
   unsigned char command;
   unsigned char result;
} replyHeader_t;

typedef struct init_s
{
   cmdHeader_t head;
   unsigned char          revision;
} init_t;

typedef struct initReply_s
{
   replyHeader_t head;
   unsigned char            revision;
} initReply_t;

typedef struct error_s
{
   replyHeader_t head;
   unsigned char            epsid;
   unsigned char            essid;
   unsigned char            ecode;
} error_t;

/* results */
typedef struct errorMessage_s
{
   unsigned char    result;
   const char *message;
   int   errorClass;
}  errorMessage_t;

#define RECOVERABLE 0
#define FATAL       1

static errorMessage_t errorMessage[] =
{
   { 0x01, "Unable to begin conversation, try later."                ,0 },
   { 0x02, "Protocol revision not supported."                        ,1 },
   { 0x03, "Transaction channel can't be closed."                    ,0 },
   { 0x04, "No sufficient resources available now."                  ,0 },
   { 0x05, "Connection denied."                                      ,1 },
   { 0x06, "Channel already open."                                   ,0 },
   { 0x07, "Credit overflow, previous credit remain valid."          ,0 },
   { 0x08, "Channel is not open."                                    ,1 },
   { 0x09, "Service not available on specified socket."              ,1 },
   { 0x0a, "Service name to socket ID failed."                       ,1 },
   { 0x0b, "Init transaction failed."                                ,1 },
   { 0x0c, "Invalid packet size."                                    ,1 },
   { 0x0d, "Requested packed size is 0, no data can be transferred." ,0 },
   { 0x80, "Malformed packet, ignored."                              ,1 },
   { 0x81, "No credit for received packet, ignored"                  ,0 },
   { 0x82, "Reply doesn't match with outstanding command, ignored."  ,1 },
   { 0x83, "Packet size greater as negotiated size."                 ,1 },
   { 0x84, "Data received for a non opened channel."                 ,1 },
   { 0x85, "Reply with unknown result value received."               ,1 },
   { 0x86, "Piggybacked credit in data packet cause overflow."       ,1 },
   { 0x87, "Unknown 1284.4 Reply."                                   ,0 },
   { 0x00, NULL                                                      ,0 }
};

#define RESET_TIMER(ti,oti) { signal(SIGALRM, sig); \
                              memset((void*)&ti,0,sizeof(ti)); \
                              memset((void*)&oti,0,sizeof(oti)); \
                              setitimer( ITIMER_REAL ,&ti, &oti); \
                            }

#define SET_TIMER(ti,oti,val) { memset((void*)&ti,0,sizeof(ti)); \
                                memset((void*)&oti,0,sizeof(oti)); \
                                ti.it_value.tv_sec  = val/1000; \
                                ti.it_value.tv_usec = (val%1000)*1000; \
                                setitimer( ITIMER_REAL ,&ti, &oti); \
                                sig = signal(SIGALRM, sigAlarm); \
                              }

static int d4USleep(long usec)
{
  struct timespec t;
  t.tv_sec = 0;
  t.tv_nsec = 1000l * usec;
  return nanosleep(&t, NULL);
}

/*******************************************************************/
/* Function printHexValues                                         */
/*                                                                 */
/* Print hex code contained in the passed buffer                   */
/*                                                                 */
/*******************************************************************/

static void printHexValues(const char *dir, const unsigned char *buf, int len)
{
   int i, j;
   int printable_count = 0;
   int longest_printable_run = 0;
   int current_printable_run = 0;
   int print_strings = 0;
   int blocks = (len + 15) / 16;
#if 0
   len = len > 30 ? 30 : len;
#endif
   printf("+++%s\n",dir);
   for (i = 0; i < len; i++)
     {
       if (isprint(buf[i]))
	 {
	   if (!isspace(buf[i]))
	     printable_count++;
	   current_printable_run++;
	 }
       else
	 {
	   if (current_printable_run > longest_printable_run)
	     longest_printable_run = current_printable_run;
	 }
     }
   if (current_printable_run > longest_printable_run)
     longest_printable_run = current_printable_run;
   if (longest_printable_run >= 8 ||
       ((float) printable_count / (float) len > .75))
     print_strings = 1;
   if (print_strings)
     {
       int print_prefix = 1;
       for (i = 0; i < len; i++)
	 {
	   printf("%s%c", (print_prefix ? "+++" : ""),
		  isprint(buf[i])?buf[i]:'*');
	   print_prefix = 0;
	   if (buf[i] == ';' && i < len - 1)
	     {
	       printf("\n");
	       print_prefix = 1;
	     }
	 }
       printf("\n");
     }
   for (j = 0; j < blocks; j++)
     {
       int baseidx = j * 16;
       int count = len;
       if (count > baseidx + 16)
	 count =  baseidx + 16;
       printf("+++ %4d: ", baseidx);
       for ( i = baseidx; i < count;i++)
	 {
	   if (i % 4 == 0)
	     printf(" ");
	   printf(" %02x",buf[i]);
	 }
       if (print_strings)
	 {
	   printf("\n+++      ");
	   for ( i = baseidx; i < count;i++)
	     {
	       if (i % 4 == 0)
		 printf(" ");
	       printf("  %c",
		       isprint(buf[i]) && !isspace(buf[i]) ? buf[i] : ' ');
	     }
	 }
       printf("\n");
     }
}

int SafeWrite(int fd, const void *data, int len)
{
  int status;
  int retries=30;
  if (debugD4)
    printHexValues("SafeWrite: ", data, len);
  do
    {
      status = write(fd, data, len);
      if(status < len)
	d4USleep(d4WrTimeout);
      retries--;
    }
  while ((status < len) && (retries > 0));
  return(status);
}

/*******************************************************************/
/* Function sigAlarm(int code)                                     */
/*                                                                 */
/* do nothing, avoid printing of undesired messages and/or         */
/* other not desirable actions                                     */
/*                                                                 */
/*******************************************************************/

static void sigAlarm(int code)
{
   timeoutGot = -1;
}


/*******************************************************************/
/* Function printError()                                           */
/*    print an error message on stdout                             */
/*                                                                 */
/* Input:  unsigned char errorNb the error number                  */
/*                                                                 */
/* Return: fatal = 1 or recoverable = 0                            */
/*                                                                 */
/*******************************************************************/

static int printError(unsigned char errorNb)
{
   errorMessage_t *msg = errorMessage;
   if ( errorNb == 0 )
   {
      return 0;
   }
   while (  msg->result )
   {
      if ( msg->result == errorNb )
      {
	 if (debugD4)
	   printf("+++ %s\n", msg->message);
         return msg->errorClass;
      }
      msg++;
   }
   printf("Unknown IEEE 1284.4 error number %d\n",errorNb);
   return 0;
}


/*******************************************************************/
/* Function printCmdType()                                         */
/*        print on stdout the command name                         */
/* Input:  unsigned char *cmd   the data are to be put here        */
/*                                                                 */
/* Return: -                                                       */
/*                                                                 */
/*******************************************************************/

static void printCmdType(unsigned char *cmd)
{
  printf("+++");
   if (cmd[6] & 0x80)
     printf(">>>");
   if ( cmd[0] == 0 && cmd[1] == 0 )
   {
      switch(cmd[6] & 0x7f)
      {
         case    0: printf("--- Init           ---\n");break;
         case    1: printf("--- OpenChannel    ---\n");break;
         case    2: printf("--- CloseChannel   ---\n");break;
         case    3: printf("--- Credit         ---\n");break;
         case    4: printf("--- CreditRequest  ---\n");break;
         case    8: printf("--- Exit           ---\n");break;
         case    9: printf("--- GetSocketID    ---\n");break;
         case   10: printf("--- GetServiceName ---\n");break;
         case 0x45: printf("--- EnterD4Mode    ---\n");break;
         case 0x7f: printf("--- Error          ---\n");break;
         default:   printf("--- ?????????????? ---\n");break;
      }
   }
   else
   {
      printf("--- Send Data      ---\n");
   }
}


/*******************************************************************/
/* Function writeCmd()                                             */
/*        write a command                                          */
/* Input:  int   fd    file handle                                 */
/*         char *cmd   the data are to be put here                 */
/*         int   len   the number of bytes to read                 */
/*                                                                 */
/* Return: number of bytes write or -1                             */
/*                                                                 */
/*******************************************************************/

static int writeCmd(int fd, unsigned char *cmd, int len)
{
   int w;
   int i = 0;
   struct  itimerval ti, oti;

# if PTIME
   struct timeval beg, end;
   long dt;
# endif
   if ( debugD4 )
   {
      printCmdType(cmd);
# if PTIME
      gettimeofday(&beg, NULL);
# endif
      if ( cmd[0] == 0 && cmd[1] == 0 )
      {
         printHexValues("Send: ", cmd, len);
      }
      else
      {
         printHexValues("Send: ", cmd, 6);
      }
   }

   /* according to Glen Steward, this will solve problems  */
   /* for the cartridge exchange with the Stylus Color 580 */
   d4USleep(d4MicroTimeout);

   timeoutGot = 0;
   errno = 0;
   while ( i < len )
   {
      SET_TIMER(ti,oti,d4WrTimeout);
      w = SafeWrite(fd, cmd+i,len-i);
      RESET_TIMER(ti,oti);
      if ( w < 0 )
      {
         if ( debugD4 )
         {
	   perror("Write error");
         }
         i= -1;
         break;
      }
      else
         i += w;
   }

# if PTIME
   if ( debugD4 )
   {
      gettimeofday(&end, NULL);
      dt = (end.tv_sec  - beg.tv_sec) * 1000000;
      dt += end.tv_usec - beg.tv_usec;
      printf("+++Write time %5.3f s\n",(double)dt/1000000);
   }
# endif

   if ( timeoutGot )
      return -1;
   return i;
}

/*******************************************************************/
/* Function readAnswer()                                           */
/*        Read the datas returned by the printer                   */
/* Input:  int   fd    file handle                                 */
/*         char *buf   the data are to be put here                 */
/*         int   len   the number of bytes to read                 */
/*                                                                 */
/* Return: number of bytes read. -1 on error                       */
/*                                                                 */
/*******************************************************************/

int readAnswer(int fd, unsigned char *buf, int len, int allowExtra)
{
   int rd    = 0;
   int total = 0;
   struct timeval beg, end;
   struct itimerval ti, oti;
   long dt;
   int count = 0;
   int first_read = 1;
   int excess = 0;
   /* wait a little bit before reading an answer */
   d4USleep(d4RdTimeout);

   /* for error handling in case of timeout */
   timeoutGot = 0;

   /* set errno to 0 in order to get correct informations */
   /* in case of error                                    */
   errno = 0;

   gettimeofday(&beg, NULL);

   if (debugD4)
     printf("+++length: %i\n", len);
   while ( total < len )
   {
      SET_TIMER(ti,oti, d4RdTimeout);
      rd = read(fd, buf+total, len-total);
      if (debugD4)
	{
	  if (first_read)
	    {
	      printf("+++read: ");
	      first_read = 0;
	    }
	  if (rd < 0)
	    {
	      printf("%i %s\n", rd, errno != 0 ?strerror(errno) : "");
	      first_read = 1;
	    }
	  else
	    printf("%i ", rd);
	}
      RESET_TIMER(ti,oti);
      if ( rd <= 0 )
      {
         gettimeofday(&end, NULL);
         dt  = (end.tv_sec  - beg.tv_sec) * 1000;
         dt += (end.tv_usec - beg.tv_usec) / 1000;
         if ( dt > d4RdTimeout * 2 )
         {
            if ( debugD4 )
               printf("\n+++Timeout 1 at readAnswer() rcv %d bytes",total);
            timeoutGot = 1;
            break;
         }
         count++;
         if ( count >= 100 )
         {
             timeoutGot = 1;
             if ( rd == 0 )
                errno = -1; /* tell that there is an abnormal condition */
             break;
         }
         errno = 0;
      } else {
         total += rd;
         if ( total > 3 )
         {
            /* the bytes idx 2 and 3 contain the length */
            /* in case of errors this may differ from   */
            /* the expected length. Setting len to this */
            /* value will avoid waiting for timeout     */
	    int newlen = (buf[2] << 8) + buf[3];
	    if (len > newlen)
	      {
		if (debugD4)
		  printf("\n+++Changing len from %d to %d", len, newlen);
		len = newlen;
	      }
	    else if (len < newlen)
	      {
		excess = newlen - len;
		if (debugD4)
		  printf("\n+++Expected %d, getting %d, %sflushing %d",
			  len, newlen, allowExtra ? "not " : "", excess);
	      }
         }
      }
      d4USleep(d4RdTimeout);
   }
   if (debugD4)
     printf("\n");
   if (! allowExtra)
     {
       int retry_count = 0;
       while (excess > 0)
	 {
	   char wastebuf[256];
	   int bytes = excess > 256 ? 256 : excess;
	   int status = read(fd, wastebuf, bytes);
	   if (status < 0)
	     break;
	   else if (status == 0 && retry_count > 2)
	     break;
	   else if (status == 0)
	     retry_count++;
	   else
	     retry_count = 0;
	   if (status < bytes)
	     d4USleep(d4RdTimeout);
	   if (debugD4)
	     printHexValues("waste", (const unsigned char *) wastebuf, status);
	   excess -= status;
	 }
     }

   if ( debugD4 )
   {
      printCmdType(buf);
#  if PTIME
      gettimeofday(&end, NULL);
#  endif
      printf("+++total: %i\n", total);
      printHexValues("Recv: ",buf,total);
#  if PTIME
      dt = (end.tv_sec  - beg.tv_sec) * 1000000;
      dt += end.tv_usec - beg.tv_usec;
      printf("+++Read time %5.3f s\n",(double)dt/1000000);
#  endif
   }
   if ( timeoutGot )
   {
      if ( debugD4 )
         printf("+++Timeout 2 at readAnswer()\n");
      return -1;
   }
   return total;
}

static void _flushData(int fd)
{
   int rd    = 0;
   struct itimerval ti, oti;
   char buf[1024];
   int len = 1023;
   int count = 200;
   d4USleep(d4RdTimeout);

   /* for error handling in case of timeout */
   timeoutGot = 0;

   /* set errno to 0 in order to get correct informations */
   /* in case of error                                    */
   errno = 0;

   if (debugD4)
     printf("+++flush data: length: %i\n", len);
   do
     {
       d4USleep(d4RdTimeout);
       SET_TIMER(ti,oti, d4RdTimeout);
       rd = read(fd, buf, len);
       if (debugD4)
	 printf("+++flush: read: %i %s\n", rd,
		 rd < 0 && errno != 0 ?strerror(errno) : "");
       RESET_TIMER(ti,oti);
       count--;
     } while ( count > 0 && (rd > 0 || (rd < 0 && errno == EAGAIN)));
}

/*******************************************************************/
/* Function _readData()                                            */
/*        Read the datas returned by the printer                   */
/* Input:  int   fd    file handle                                 */
/*         char *buf   the data are to be put here                 */
/*         int   len   the number of bytes to read                 */
/*                                                                 */
/* Return: number of bytes read. -1 on error                       */
/*                                                                 */
/*******************************************************************/

static int _readData(int fd, unsigned char *buf, int len)
{
   int rd    = 0;
   int total = 0;
   int toGet = 0;
   unsigned char  header[6];
   struct timeval beg, end;
   long dt;
   struct itimerval ti, oti;

   /* set errno to 0 in order to get correct informations */
   /* in case of error                                    */
   errno = 0;

   /* read the first 6 bytes */
   gettimeofday(&beg, NULL);
   while ( total < 6 )
   {
      SET_TIMER(ti,oti, d4RdTimeout);
      rd = read(fd, header+total, 6-total);
      RESET_TIMER(ti,oti);
      if ( rd <= 0 )
      {
         gettimeofday(&end, NULL);
         dt  = (end.tv_sec  - beg.tv_sec) * 1000;
         dt += (end.tv_usec - beg.tv_usec) / 1000;
         if ( dt > d4RdTimeout*3 )
         {
            if ( debugD4 )
               printf("+++Timeout at _readData(), dt = %ld ms\n", dt);
            return -1;
            break;
         }
         continue;
      }
      else
      {
         total += rd;
      }
   }

   if ( debugD4 )
      printHexValues("Recv: ",header,total);

   if ( total == 6 )
   {
      toGet = (header[2] << 8) + header[3] - 6;
      if (debugD4)
	printf("+++toGet: %i\n", toGet);
      total = 0;
      gettimeofday(&beg, NULL);
      while ( total < toGet )
      {
         SET_TIMER(ti,oti, d4RdTimeout);
         rd = read(fd, buf+total, toGet-total);
         RESET_TIMER(ti,oti);
         if ( rd <= 0 )
         {
            gettimeofday(&end, NULL);
            dt  = (end.tv_sec  - beg.tv_sec) * 1000;
            dt += (end.tv_usec - beg.tv_usec) / 1000;
            if ( dt > d4RdTimeout*3 )
            {
               if ( debugD4 )
                  printf("+++Timeout at _readData(), dt = %ld ms\n",dt);
               return -1;
               break;
            }
            continue;
         }
         else
         {
            total += rd;
         }
      }
      if ( debugD4 )
         printHexValues("Recv: ",buf,total);
      return total;
   }

   return -1;
}

/*******************************************************************/
/* Function sendReceiveCmd()                                       */
/*        send a command and get the answer.                       */
/* Input:  int   fd    file handle                                 */
/*         char *buf   the data are to be put here                 */
/*         int   len   the number of bytes to read                 */
/*                                                                 */
/* Return: number of bytes read                                    */
/*                                                                 */
/*******************************************************************/

static int sendReceiveCmd(int fd, unsigned char *cmd, int len, unsigned char *answer, int expectedlen, int allowExtra)
{
   int rd;
   d4Errno = 0;
   if ( (rd = writeCmd(fd, cmd, len ) ) != len )
   {
      if ( rd < 0 ) return -1;
      return 0;
   }
   rd = readAnswer(fd, answer, expectedlen, allowExtra );
   if ( rd == 0 )
   {
      /* no answer from device */
      return 0;
   }
   else if ( rd < 0 )
   {
      /* interrupted write call */
      if ( debugD4 )
         printf("+++interrupt received\n");
      return -1;
   }
   else
   {
      /* check result */
      if ( answer[6] == 0x7f )
      {
         printError(answer[9]);
	 d4Errno = answer[9];
         return -1;
      }
      else if (  answer[7] != 0 )
      {
	 d4Errno = answer[7];
         if ( printError(answer[7]) )
         {
            return -1;
         }
         return 0;
      }
      else
      {
         return rd;
      }
   }
}

/*******************************************************************/
/* Function EnterIEEE()                                            */
/*        send a command and get the answer.                       */
/* Input:  int   fd    file handle                                 */
/*                                                                 */
/* Return: 0 on error 1 if all is OK                               */
/*                                                                 */
/*******************************************************************/

int EnterIEEE(int fd)
{
   unsigned char buf[200];
   unsigned char cmd[] =
   {
      0x00, 0x00, 0x00, 0x1b, 0x01, '@', 'E', 'J', 'L', ' ',
      '1', '2', '8', '4', '.', '4', 0x0a, '@', 'E', 'J',
      'L', 0x0a, '@', 'E', 'J', 'L', 0x0a
   };
   int rd;
   memset(buf, 0, sizeof(buf));
Loop:
   if ( writeCmd(fd, cmd, sizeof(cmd) ) != sizeof(cmd) )
   {
      return 0;
   }
   rd = readAnswer(fd, buf, 8, 0);
   if ( rd == 0 )
   {
      /* no answer from device */
      if (debugD4)
        printf("+++>>>No answer from printer\n");
      return 0;
   }
   else if ( rd < 0 )
   {
      if (debugD4)
        printf("+++>>>Interrupted write\n");
      /* interrupted write call */
      return 0;
   }
   else
   {
      int i;
      /* check result */
      for (i=0; i < rd; i++ )
        if ( buf[i] != 0 )
           break;
      if ( i == rd ) goto Loop;
      return 1;
   }
}

/*******************************************************************/
/* Function Init()                                                 */
/*        handle the init command                                  */
/* Input:  int   fd    file handle                                 */
/*                                                                 */
/* Return: 0 on error 1 if all is OK                               */
/*                                                                 */
/*******************************************************************/

int Init(int fd)
{
   unsigned char buf[20];
   init_t cmd;
   int rd;

   cmd.head.psid     = 0;
   cmd.head.ssid     = 0;
   cmd.head.lengthH  = 0;
   cmd.head.lengthL  = 8;
   cmd.head.credit   = 1;
   cmd.head.control  = 0;
   cmd.head.command  = 0;
   cmd.revision      = 0x10;

   rd = sendReceiveCmd(fd, (unsigned char*)&cmd, sizeof(cmd), buf, 9, 0 );
   return rd == 9 ? 1 : 0;
}

/*******************************************************************/
/* Function Exit()                                                 */
/*        handle the Exit command                                  */
/* Input:  int   fd    file handle                                 */
/*                                                                 */
/* Return: 0 on error 1 if all is OK                               */
/*                                                                 */
/*******************************************************************/

int Exit(int fd)
{
   int rd;
   unsigned char  buf[20];
   cmdHeader_t cmd;
   cmd.psid     = 0;
   cmd.ssid     = 0;
   cmd.lengthH  = 0;
   cmd.lengthL  = 7;
   cmd.credit   = 1;
   cmd.control  = 0;
   cmd.command  = 8;

   rd = sendReceiveCmd(fd, (unsigned char*)&cmd, sizeof(cmd), buf, 8, 0 );
   return rd > 0 ? 1 : rd;
}

/*******************************************************************/
/* Function GetSocketID()                                          */
/*        handle the GetSocketID command                           */
/* Input:  int   fd    file handle                                 */
/*         char *serviceName  name of wanted service               */
/*                                                                 */
/* Return: 0 on error else the socket ID                           */
/*                                                                 */
/*******************************************************************/

int GetSocketID(int fd, const char *serviceName)
{
   /* the service name may not be longer as 40 bytes */
   int len = sizeof(cmdHeader_t) + strlen(serviceName);
   char buf[100];
   unsigned char rBuf[100];
   int rd;
   cmdHeader_t *cmd = (cmdHeader_t*)buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = len & 0xff;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 0x09;
   strcpy(buf + sizeof(cmdHeader_t), serviceName);

   rd = sendReceiveCmd(fd, (unsigned char*)buf, len, rBuf, len + 2, 0);
   if ( rd > 0 )
   {
      return rBuf[8];
   }
   else
   {
      return 0;
   }
}

/*******************************************************************/
/* Function OpenChannel()                                          */
/*        handle the OpenChannel command                           */
/* Input:  int   fd    file handle                                 */
/* I/O:    int  *sndSz The size for sendig of datas                */
/*         int  *recSz The size for receiving of datas             */
/*                                                                 */
/* Return: -1 on error or 1 if all is OK                           */
/*                                                                 */
/*******************************************************************/

int OpenChannel(int fd, unsigned char sockId, int *sndSz, int *rcvSz)
{
   unsigned char  cmd[17];
   unsigned char  buf[20];
   int rd;
   int i;

   for(i = 0; i < 5; i++)	/* Retry count */
   {
      cmd[0]  = 0;       /* transaction sockets */
      cmd[1]  = 0;
      cmd[2]  = 0;       /* len */
      cmd[3]  = 17;      /* len */
      cmd[4]  = 1;       /* credit */
      cmd[5]  = 0;       /* control */
      cmd[6]  = 1;       /* command */
      cmd[7]  = sockId;  /* sockets # */
      cmd[8]  = sockId;  /* sockets # */
      cmd[9]  = *sndSz >> 8;   /* packet size in send dir */
      cmd[10] = *sndSz & 0xff;
      cmd[11] = *rcvSz >> 8;   /* packet size in recv dir */
      cmd[12] = *rcvSz & 0xff;
      cmd[13] = 0;    /* max outstanding Credit, must be 0 */
      cmd[14] = 0;
      cmd[15] = 0;    /* initial credit for us ? */
      cmd[16] = 0;

      rd = sendReceiveCmd(fd, cmd, 17, buf, 16, 0);
      if ( rd == -1 )
      {
	if (debugD4)
	  printf("+++OpenChannel %d fails, error %d\n", sockId, d4Errno);
	if (d4Errno == 6)	/* channel already open */
	  {
	    if ( debugD4 )
	      printf("+++Channel %d already open, closing\n", sockId);
	    CloseChannel(fd, sockId);
	    continue;
	  }
	else
	  return -1;
      }
      else if ( rd == 16 )
      {
         if ( buf[7] == 4 )
         {
            /* device canīt allocate resources now */
            continue;
         }
         else if ( buf[7] != 0 )
         {
	   if (debugD4)
	     printf("+++OpenChannel %d fails, hard error\n", sockId);
            /* hard error */
            return -1;
         }
         *sndSz = (buf[10]<<8) + buf[11];
         *rcvSz = (buf[12]<<8) + buf[13];
         break;
      }
      else
      {
	if (d4Errno == 6)	/* channel already open */
	  {
	    if ( debugD4 )
	      printf("+++Channel %d already open, closing\n", sockId);
	    CloseChannel(fd, sockId);
	    continue;
	  }
         /* at this stage we can only have an error */
	if (debugD4)
	  printf("+++OpenChannel %d fails, wrong count %d\n", sockId, rd);
         return -1;
      }
   }
   return 1;
}

/*******************************************************************/
/* Function CloseChannel()                                         */
/*        handle the CloseChannel command                          */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  he socket to close                      */
/*                                                                 */
/* Return: -1 on error or 1 if all is OK                           */
/*                                                                 */
/*******************************************************************/

int CloseChannel(int fd, unsigned char socketID)
{
   unsigned char buf[100];
   int           rd;
   cmdHeader_t *cmd = (cmdHeader_t *)buf;
   cmd->psid     =  0;
   cmd->ssid     =  0;
   cmd->lengthH  =  0;
   cmd->lengthL  = 10;
   cmd->credit   =  1;
   cmd->control  =  0;
   cmd->command  =  2;
   buf[sizeof(cmdHeader_t)+0] = socketID;
   buf[sizeof(cmdHeader_t)+1] = socketID;
   buf[sizeof(cmdHeader_t)+2] = 0;
   rd = sendReceiveCmd(fd, buf,10, buf, 10, 0);
   return rd == 10 ? 1 : rd;
}

/*******************************************************************/
/* Function CreditRequest()                                        */
/*        handle the CreditRequest command                         */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  he socket to close                      */
/*                                                                 */
/* Return: -1 on error else the credit got                         */
/*                                                                 */
/*******************************************************************/

int CreditRequest(int fd, unsigned char socketID)
{
   int           rd;
   unsigned char            buf[100];
   unsigned char            rBuf[100];
   cmdHeader_t *cmd = (cmdHeader_t *)buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = 13;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 4;
   buf[sizeof(cmdHeader_t)+0] = socketID;
   buf[sizeof(cmdHeader_t)+1] = socketID;
   buf[sizeof(cmdHeader_t)+2] = 0x00;
   buf[sizeof(cmdHeader_t)+3] = 0x80;
   buf[sizeof(cmdHeader_t)+4] = 0xff;
   buf[sizeof(cmdHeader_t)+5] = 0xff;
   rd = sendReceiveCmd(fd, buf, 13, rBuf, 12, 0);
   if ( rd == 12 )
   {
      /* this is the credit */
      return (rBuf[10]*256)+rBuf[11];
   }
   else
   {
      return rd > 0 ? 0 : rd; /* there was an error */
   }
}

/*******************************************************************/
/* Function Credit()                                               */
/*        give credit to the attached device                       */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  the socket to close                     */
/*                                                                 */
/* Return: -1 on error or 1 if all is OK                           */
/*                                                                 */
/*******************************************************************/

/* needed for sending of commands (channel 2) or scanning */
int Credit(int fd, unsigned char socketID, int credit)
{
   int rd;
   unsigned char buf[100];
   unsigned char rBuf[100];
   cmdHeader_t *cmd = (cmdHeader_t*)buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = 0x0b;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 0x03;

   buf[sizeof(cmdHeader_t)+0] = socketID;
   buf[sizeof(cmdHeader_t)+1] = socketID;
   buf[sizeof(cmdHeader_t)+2] = credit >> 8;
   buf[sizeof(cmdHeader_t)+3] = credit & 0xff;
   rd = sendReceiveCmd(fd, buf, 11, rBuf, 10, 0);
   if ( rd == 10 )
   {
      return 1;
   }
   else
   {
      return 0;
   }
}

/*******************************************************************/
/* Function askForCredit()                                         */
/*        Convenience function                                     */
/*        handle the CreditRequest command                         */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID                               */
/* IN/Out  int  *sndSize  for error handling                       */
/* IN/Out  int  *rcvSize  for error handling                       */
/*                                                                 */
/* Return: credit                                                  */
/*                                                                 */
/* Remark: CreditRequest() will be called in a loop as long as     */
/*         the returned credit is 0                                */
/*                                                                 */
/*******************************************************************/
#define MAX_CREDIT_REQUEST 2
int askForCredit(int fd, unsigned char socketID, int *sndSize, int *rcvSize)
{
   int credit = 0;
   int count  = 0;
   int retries = 10;

   while (credit == 0 && retries-- >= 0 )
   {
      while((credit=CreditRequest(fd,socketID)) == 0  && count < MAX_CREDIT_REQUEST && retries-- >= 0)
         d4USleep(d4RdTimeout);

      if ( credit == -1 )
      {
         if ( errno == ENODEV || count == MAX_CREDIT_REQUEST )
         {
            break;
         }
         credit = 0;
         /* init printer and reopen the printer channel */
         CloseChannel(fd, socketID);
	 socketID = GetSocketID(fd, "EPSON-CTRL");
         if ( Init(fd) )
         {
	   if (debugD4)
	     printf("+++askForCredit init succeeded, now try to open\n");
            OpenChannel(fd, socketID, sndSize, rcvSize);
         }
      }
      /* if the parent died, live this loop if credit not got */
      if ( credit == 0 && getppid() == ppid )
         return 0;
      count++;
   }
   return credit;
}

/*******************************************************************/
/* Function writeData()                                            */
/*        Convenience function                                     */
/*        write the data to the device                             */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  the deetination socket       */
/*         unsigned char   *buf       the datas to be send         */
/*         int   len       how many datas are to we send           */
/*         int   eoj       set out of band flag if eoj set         */
/*                                                                 */
/* Return: number of bytes written or -1;                          */
/*                                                                 */
/*******************************************************************/

int writeData(int fd, unsigned char socketID, const unsigned char *buf, int len, int eoj)
{
   unsigned char  cmd[6];
   int wr = 0;
   int ret = 0;
   struct  itimerval ti, oti;
   struct timeval beg;
   static unsigned char *buffer = NULL;
   static int bLen   = 0;
   if ( debugD4 )
   {
      printf("+++--- Send Data      ---\n");
      gettimeofday(&beg, NULL);
   }
   len += 6;
   if ( len > bLen )
   {
      if ( buffer == NULL )
         buffer = (unsigned char*)malloc(len);
      else
         buffer = (unsigned char*)realloc(buffer, len);
      if ( buffer == NULL )
         return -1;
      bLen = len;
   }
   cmd[0] = socketID;
   cmd[1] = socketID;
   cmd[2] = len >> 8;
   cmd[3] = len & 0xff;
   cmd[4] = 0;
   cmd[5] = eoj ? 1 : 0;

   memcpy(buffer, cmd, 6);
   memcpy(buffer + 6, buf, len - 6 );
   while( ret > -1 && wr != len )
   {
      SET_TIMER(ti,oti,d4WrTimeout);
      ret = SafeWrite(fd, buffer+wr, len-wr );
      RESET_TIMER(ti,oti);
      if ( ret == -1 )
      {
         perror("write: ");
      }
      else
      {
         wr += ret;
      }
   }

   if ( debugD4 )
   {
# if PTIME
      gettimeofday(&end, NULL);
      dt = (end.tv_sec  - beg.tv_sec) * 1000000;
      dt += end.tv_usec - beg.tv_usec;
# endif
      printf("+++Send: ");
      for ( ret = 0; (wr > 0) && (ret < ((wr > 20) ? 20 : wr)) ; ret++ )
         printf("%02x ", buffer[ret]);
      printf("\n+++      ");
      for ( ret = 0; (wr > 0) && (ret < ((wr > 20) ? 20 : wr)) ; ret++ )
         printf("%c  ", isprint(buffer[ret])&&!isspace(buffer[ret])?buffer[ret]:' ');
      printf("\n");
# if PTIME
       printf("+++Write time %5.3f s\n",(double)dt/1000000);
# endif
   }

   if (  wr > 6 )
      wr -= 6;
   else
      wr = -1;
   return wr;

}

/*******************************************************************/
/* Function readData()                                             */
/*        Convenience function                                     */
/*        give credit and read then the expected datas             */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  the destination socket       */
/*         unsigned char   *buf       the datas to be send         */
/*         int   len       howmany datas are to we send            */
/*                                                                 */
/* Return: number of bytes read or -1;                             */
/*                                                                 */
/*******************************************************************/

int readData(int fd, unsigned char socketID, unsigned char *buf, int len)
{
   int ret;
   /* give credit */
   if ( Credit(fd, socketID, 1) == 1 )
   {
      /* wait a little bit */
      d4USleep(d4RdDataTimeout);
      ret = _readData(fd, buf, len);
      return ret;
   }
   else
   {
      return -1;
   }
}

/*******************************************************************/
/* Function writeAndReadData()                                     */
/*        Convenience function                                     */
/*        give credit and read then the expected datas             */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  the destination socket       */
/*         unsigned char   *cmd       the datas to be send         */
/*         int   cmd_len       howmany datas are to we send        */
/*         int   eoj       set out of band flag if eoj set         */
/*         unsigned char   *buf       the datas to be send         */
/*         int   *sndSz    Send buffer size                        */
/*         int   *rcvSz    Receive buffer size                     */
/*         int   len       how many datas are to we send           */
/*         fptr  test      function to verify buffer contents      */
/*                                                                 */
/* Return: number of bytes read or -1;                             */
/*                                                                 */
/* This allows us to give credit before sending the command.       */
/* Sending the command and then giving credit sometimes causes     */
/* the actual data to be sent as a reply to the credit command.    */
/*                                                                 */
/*******************************************************************/

int writeAndReadData(int fd, unsigned char socketID,
		     const unsigned char *cmd, int cmd_len, int eoj,
		     unsigned char *buf, int len, int *sndSz, int *rcvSz,
		     int (*test)(const unsigned char *buf))
{
   int ret;
   int retry = 5;
   int credit = askForCredit(fd, socketID, sndSz, rcvSz);
   if (credit < 0)
     return -1;
   /* give credit */
   if ( Credit(fd, socketID, 1) == 1 )
     {
       if (writeData(fd, socketID, cmd, cmd_len, eoj) <= 0)
	 return -1;
       /* wait a little bit */
       do
	 {
	   d4USleep(d4RdDataTimeout);
	   ret = _readData(fd, buf, len);
	   if (ret < 0)
	     return ret;
	 } while (retry-- >= 0 && (!test || !(*test)(buf)));
       return ret;
     }
   else
     return -1;
}

/*******************************************************************/
/* Function readData()                                             */
/*        Convenience function                                     */
/*        give credit and read then the expected datas             */
/* Input:  int   fd    file handle                                 */
/*         unsigned char    socketID  the destination socket                  */
/*         unsigned char   *buf       the datas to be send                    */
/*         int   len       howmany datas are to we send            */
/*                                                                 */
/* Return: number of bytes written or -1;                          */
/*                                                                 */
/*******************************************************************/

void flushData(int fd, unsigned char socketID)
{
  if (debugD4)
    printf("+++flushData %d\n", socketID);
   /* give credit */
   if (socketID != (unsigned char) -1)
     {
       if ( Credit(fd, socketID, 1) == 1 )
	 {
	   /* wait a little bit */
	   d4USleep(d4RdDataTimeout);
	   _flushData(fd);
	 }
     }
   else
     _flushData(fd);
}

/*******************************************************************/
/* Function clearSndBuf()                                          */
/*        Convenience function                                     */
/*                                                                 */
/* Input:  int   fd    file handle                                 */
/*                                                                 */
/*                                                                 */
/*******************************************************************/

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
static inline void clearSndBuf(int fd)
{
   char             buf[256];
   struct itimerval ti, oti;

   SET_TIMER(ti,oti, d4RdTimeout);
   while ( read(fd, buf, sizeof(buf) ) > 0 )
      SET_TIMER(ti,oti, d4RdTimeout);
   RESET_TIMER(ti,oti);
}
#pragma GCC diagnostic pop

void setDebug(int debug)
{
  debugD4 = debug;
}

#if 0 /* implementation later ? */
int InitReply(int fd)
{
   unsigned char buf[20];
   initReply_t cmd;

   cmd.head.psid     = 0;
   cmd.head.ssid     = 0;
   cmd.head.lengthH  = 0;
   cmd.head.lengthL  = 9;
   cmd.head.credit   = 1;
   cmd.head.control  = 0;
   cmd.head.command  = 0x80;
   cmd.head.result   = 0;   /* put the correct value here, 0,1,2 or 0x0b */
   cmd.revision      = 0x10;

}

int ExitReply(int fd)
{
   replyHeader_t cmd;
   cmd.psid     = 0;
   cmd.ssid     = 0;
   cmd.lengthH  = 0;
   cmd.lengthL  = 7;
   cmd.credit   = 1;    /* always ignored */
   cmd.control  = 0;
   cmd.command  = 0x88;
   cmd.result   = 0x0;  /* put the correct value here always 0 */
}

int Error(int fd)
{
   char
   error_t cmd;
   cmd.head.psid     = 0;
   cmd.head.ssid     = 0;
   cmd.head.lengthH  = 0;
   cmd.head.lengthL  = 0x0a;
   cmd.head.credit   = 0;
   cmd.head.control  = 0;
   cmd.head.command  = 0x7f;
   cmd.epsid         = psid;
   cmd.essid         = ssid;
   cmd.ecode         = 0x80; /* + 1...7 */
}

int GetSocketIDReply(int fd, unsigned char socketID)
{
   /* the service name may not be longer as 40 bytes */
   int len = sizeof(replyHeader_t) + 1 + strlen(serviceName);
   unsigned char buf[100];
   replyHeader_t *cmd = buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = len & 0xff;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 0x89;
   cmd->result   = 0; /* put the correct vale here 0 or 0x0a */
   buf[sizeof(cmdHeader_t)] = socketID;
   strcpy(buf+1+sizeof(cmdHeader_t), serviceName);

}

int GetServiceName(inf fd, unsigned char socketID)
{
   int rd;
   unsigned char buf[100];
   cmdHeader_t *cmd = buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = 8;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 0x0a;
   buf[sizeof(cmdHeader_t)] = socketID;
}

/* as GetSocketIDReply but with command 0x8a instead 0f 0x89 */
int GetServiceNameReply(inf fd)
{
   /* the service name may not be longer as 40 bytes */
   int len = sizeof(replyHeader_t) + 1 + strlen(serviceName);
   unsigned char buf[100];
   replyHeader_t *cmd = buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = len & 0xff;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 0x8a;
   cmd->result   = 0; /* put the correct vale here 0 or 0x0a */
   buf[sizeof(replyHeader_t)] = socketID;
   strcpy(buf+1+sizeof(replyHeader_t), serviceName);

}

int CreditReply(int fd, unsigned char socketID, int credit)
{
   unsigned char buf[100];
   replyHeader_t *cmd = buf;
   cmd->psid     = 0;
   cmd->ssid     = 0;
   cmd->lengthH  = 0;
   cmd->lengthL  = 0x0b;
   cmd->credit   = 1;
   cmd->control  = 0;
   cmd->command  = 0x83;
   buf[sizeof(replyHeader_t)]   = socketID;
   buf[sizeof(replyHeader_t)+1] = socketID;
}

#endif
