/*
 * 3binin.c
 * code to read type 3 binary packets
 * Public Domain from M. Kimes
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "3binary.h"

static CHUNK3 *globals = NULL;


size_t align_fread (char *data,int size,int count,FILE *handle) {

  /*
   * read a chunk, adjusting for alignment
   */

  size_t len = size * count;

  return fread(data,1,len + (len % sizeof(USHORT)),handle);
}


CHUNK3 * free_chunk3 (CHUNK3 *chunk) {

  /*
   * free memory containing a single chunk
   */

  if(chunk->data)
    free(chunk->data);
  free(chunk);
  return NULL;  /* for assignment */
}


CHUNK3 * free_chunk3_list (CHUNK3 *chunkhead) {

  /*
   * free a complete linked list of chunks
   */

  register CHUNK3 *info,*next;

  info = chunkhead;
  while(info) {
    next = info->next;
    free_chunk3(info);
    info = next;
  }
  return NULL;    /* for assignment */
}


CHUNK3 * copy_chunk3 (CHUNK3 *chunk) {

  /*
   * duplicate a 3binary chunk using allocated memory
   */

  register CHUNK3 *info = NULL;

  if(chunk) {
    info = malloc(sizeof(CHUNK3));
    if(info) {
      info->len = chunk->len;
      info->type = chunk->type;
      if(info->len) {
        info->data = malloc(chunk->len);
        if(!info->data) {
          info = free_chunk3(info);
          return info;
        }
        memcpy(info->data,chunk->data,info->len);
      }
      else
        info->data = NULL;
    }
  }
  return info;
}


int read_chunk3 (FILE *handle,CHUNK3 *chunk) {

  /*
   * read a type 3binary chunk into memory from disk
   * swapping is handled here for len and type
   * returns NOERR3 on success
   */

  memset(chunk,0,sizeof(CHUNK3));
  if(fread(&chunk->len,sizeof(USHORT),1,handle) != 1)
    return READERR3;
#ifdef MOTOROLA
  chunk->len = swap3(chunk->len);
#endif
  if(chunk->len < sizeof(USHORT))
    return BADCHUNK3;
  if(fread(&chunk->type,sizeof(USHORT),1,handle) != 1)
    return READERR3;
#ifdef MOTOROLA
  chunk->type = swap3(chunk->type);
#endif
  chunk->len -= sizeof(USHORT);  /* note we decrement chunk len by type size here */
  if(chunk->len) {
    chunk->data = malloc(chunk->len + (chunk->len % sizeof(USHORT)));
    if(!chunk->data)
      return NOMEM3;
    if(align_fread(chunk->data,1,chunk->len,handle) !=
       (size_t)chunk->len + (chunk->len % sizeof(USHORT))) {
      free(chunk->data);
      return (ferror(handle)) ? BADCHUNK3 : READERR3;
    }
#ifdef MOTOROLA
    switch(chunk->type) { /* swap bytes around on chunk data we understand */
      case MSG3:
      case PKT3:
      case GLOBAL3:
      case ID3:
      case REF3:
      case ATTRIB3:
        *(long *)chunk->data = swap3l(*(long *)chunk->data);
        break;
      case DATE3:
        (*(BINDATE3 *)chunk->data).year = swap3((*(BINDATE3 *)chunk->data).year);
        (*(BINDATE3 *)chunk->data).gmtoff = swap3((*(BINDATE3 *)chunk->data).gmtoff);
        break;
      case ENCRYPT3:
      case QUOTE3:
        *(USHORT *)chunk->data = swap3(*(USHORT *)chunk->data;
        *(USHORT *)chunk->data + (*(*SHORT *)chunk->data) + sizeof(USHORT) +
        (*(USHORT *)chunk->data % sizeof(USHORT)) =
          swap3(*(USHORT *)chunk->data + (*(*SHORT *)chunk->data) +
          sizeof(USHORT) + (*(USHORT *)chunk->data % sizeof(USHORT)));
        break;
    }
#endif
  }
  return NOERR3;
}


CHUNK3 * verify_chunk3_list_item (CHUNK3 *list,USHORT type) {

  /*
   * looks for a chunk of type type in a linked list of chunks
   * returns the chunk if it finds it
   * returns NULL otherwise
   * see also 3binary.h for macro verify_chunk3_list_next
   */

  register CHUNK3 *info = list;

  while(info) {
    if(info->type == type)
      break;
    info = info->next;
  }
  return info;
}


CHUNK3 * read_chunk3_list (FILE *handle,USHORT type,long totallen,int *error) {

  /*
   * read in a single type 3 "section" into a linked list of chunks
   * returns head of chunk list
   * error return in error
   * total length in totallen is verified; error if incorrect
   */

  register CHUNK3  *info = NULL,*last = NULL,*newchunk;
  long              chklen;
  CHUNK3            curr;         /* working chunk */
  CHUNK3           *msg3 = NULL;  /* head of list */

  *error = NOERR3;

  if(type == GLOBAL3 && totallen == 0L) { /* remove all global chunks */
    globals = free_chunk3_list(globals);
    return NULL;
  }

  curr.data = NULL;
  chklen = 0L;

  while(chklen < totallen && *error == NOERR3) {
    if(feof(handle)) {    /* premature end of packet */
      *error = SHORTPKT3;
      break;
    }

    if(curr.data) {       /* prepare working chunk */
      free(curr.data);
      curr.data = NULL;
    }

    if((*error = read_chunk3(handle,&curr)) != NOERR3)
      break;  /* some sort of reading error */

    if(curr.type == MSG3 || curr.type == GLOBAL3 ||
       curr.type == PKT3) { /* error! can't be included in here! */
      *error = BADLENGTH3;
      break;
    }

    /* track size read so we can check totallen */
    chklen += sizeof(USHORT) + sizeof(USHORT) + curr.len +
              (curr.len % sizeof(USHORT));

    if(type == GLOBAL3) {  /* handle GLOBAL chunks */
      if(curr.len) {
        /* put chunk into global list */
        newchunk = copy_chunk3(&curr);
        if(!newchunk) {
          *error = NOMEM3;
          break;
        }
        info = globals;
        last = NULL;
        while(info) {
          if(info->type == newchunk->type) { /* exists; replace this chunk */
            if(last)
              last->next = newchunk;
            newchunk->next = info->next;
            free_chunk3(info);
            break;
          }
          last = info;
          info = info->next;  /* step through linked list of global chunks */
        }
        if(!info) {           /* didn't exist; add this chunk */
          if(last)
            last->next = newchunk;
          else
            globals = newchunk;
          newchunk->next = NULL;
        }
      }
      else {  /* if 0-length chunk, free any existing instance of this type */
        info = globals;
        last = NULL;
        while(info) {
          if(info->type == curr.type) {
            if(last)
              last->next = info->next;
            if(info == globals)
              globals = info->next;
            free_chunk3(info);
            break;
          }
          last = info;
          info = info->next;
        }
      }
    }
    else {    /* handle "normal" chunks */
      info = copy_chunk3(&curr);
      if(!info) {
        *error = NOMEM3;
        break;
      }
      if(!msg3)             /* first chunk in list */
        msg3 = info;
      else
        last->next = info;  /* linking to previous chunk */
      info->next = NULL;    /* mark as end of list */
      last = info;          /* for linking to next chunk */
    }
  }

  return msg3;
}


long process_pkt3 (void *anon,char *filename,int *error) {

  /*
   * process a type 3 binary packet
   *
   * two external (user provided) functions are called from here:
   *
   * check_3bpkthdr(void *anon,char *filename,CHUNK3 *pkt3,CHUNK3 *globals,
   *                long hdrlen,int *error);
   *   return non-zero to abort processing of the packet
   *
   * import_3bmsg(void *anon,CHUNK3 *pkt3,CHUNK3 *msg3,CHUNK3 *globals,
   *              long msglen,int *error);
   *   return non-zero to abort further processing of the packet
   *
   */

  FILE    *handle;
  long     nummsgs = 0L,msglen,pktlen;
  CHUNK3  *pkt3 = NULL;
  CHUNK3  *msg3 = NULL;
  CHUNK3   chunk;
  int      ierror;

  *error = NOERR3;
  chunk.data = NULL;
  if(globals)  /* no globals at start of packet */
    globals = free_chunk3_list(globals);

  handle = fopen(filename,"rb");
  if(handle != NULL) {
    {
      USHORT test,t;

#ifdef MOTOROLA
      test = swap3(IS3PKT);
#else
      test = IS3PKT;
#endif

      if(fread(&t,sizeof(SHORT),1,handle) != 1 || t != test) /* is a pkt? */
        *error = NOTPKT3;
      if(*error == NOERR3) {
        *error = read_chunk3(handle,&chunk);
        if(*error == NOERR3) {
          if(chunk.type != PKT3)
            *error = NOTPKT3;
          else if(chunk.len != sizeof(long))
            *error = BADCHUNK3;
        }
      }
    }
    if(*error == NOERR3) {
      pktlen = *(long *)chunk.data;
      pkt3 = read_chunk3_list(handle,PKT3,pktlen,error);
      if(*error == NOERR3 && pkt3) {
        if(!check_3bpkthdr(anon,filename,pkt3,globals,pktlen,error)) {
          while(*error == NOERR3) {
            if(feof(handle)) {
              *error = SHORTPKT3;
              break;
            }
            if(chunk.data) {
              free(chunk.data);
              chunk.data = NULL;
            }
            *error = read_chunk3(handle,&chunk);
            if(*error != NOERR3)
              break;
            if(chunk.type == EOP3)
              break;
            else if(chunk.type != MSG3 && chunk.type != GLOBAL3 &&
                    chunk.type < MAXKNOWN) {
                *error = BADCHUNK3;
                break;
            }
            if(chunk.len != sizeof(long)) {
              *error = BADCHUNK3;
              break;
            }
            msglen = *(long *)chunk.data;

            if(chunk.type != MSG3 && chunk.type != GLOBAL3) {
              process_unknown_chunk3(handle,chunk.type,msglen,error);
              continue;
            }

            if(chunk.type == GLOBAL3) {
              read_chunk3_list(handle,GLOBAL3,msglen,error);
              continue;
            }

            msg3 = free_chunk3_list(msg3);
            msg3 = read_chunk3_list(handle,chunk.type,msglen,error);
            if(*error == NOERR3 && msg3) {
              ierror = NOERR3;
              if(import_3bmsg(anon,pkt3,msg3,globals,msglen,&ierror))
                break;
              if(ierror) {
                error3(ierror);
                continue;
              }
              else {
                nummsgs++;
                end_3bmsg (anon,pkt3,msg3,globals);
              }
            }
            else
              break;
          }
        }
      }
    }
  }
  else
    *error = OPENERR3;

  /* clean up */

  msg3 = free_chunk3_list(msg3);
  pkt3 = free_chunk3_list(pkt3);
  globals = free_chunk3_list(globals);
  if(chunk.data)
    free(chunk.data);
  if(handle != NULL)
    fclose(handle);

  return nummsgs;
}
