/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2007
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-cast.c :: Midnight Code casting protocol library	*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <time.h>

#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-parse.h"
#include "midnightcode-ip.h"
#include "midnightcode-config.h"
#include "midnightcode-socket.h"
#include "midnightcode-random.h"
#include "midnightcode-cast.h"



char midnightcode_cast_socket_ipv4_address_s[32];
int midnightcode_cast_socket_port;
int midnightcode_cast_options_multicast;
int midnightcode_cast_options_unicast;
struct midnightcode_cast_head_t * midnightcode_cast_cache = NULL;
struct midnightcode_socket_element_t * midnightcode_cast_socket = NULL;
pthread_mutex_t midnightcode_cast_mutex_t = PTHREAD_MUTEX_INITIALIZER;
pthread_t midnightcode_cast_inbound_pthread_t;
pthread_t midnightcode_cast_outbound_pthread_t;



int
midnightcode_cast_read(
                     struct midnightcode_cast_head_t * cache,
                     char * address_s, int * timestamp,
                     char ** buffer, int * buffer_size) {
  struct midnightcode_cast_element_t * cache_entry;
  int ret_i;


  if(!cache)
    return -1;
  if(!buffer)
    return -1;
  if(buffer_size == NULL)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;


  ret_i = 0;
  *buffer = NULL;
  *buffer_size = 0;
  pthread_mutex_lock(&midnightcode_cast_mutex_t);
  midnightcode_list_for_each_r(cache_entry, cache) {
    if(cache_entry->retrieved)
      continue;
    cache_entry->retrieved++;
    *buffer = malloc(cache_entry->buffer_size);
    if(*buffer == NULL)
      continue;
    memcpy(*buffer, cache_entry->buffer, cache_entry->buffer_size);
    *buffer_size = cache_entry->buffer_size;
    *timestamp = cache_entry->timestamp;
    if(address_s != NULL) {
      memset(address_s, 0, sizeof(midnightcode_cast_socket_ipv4_address_s));
      strncpy(address_s, cache_entry->ipv4_address_s, 
        sizeof(midnightcode_cast_socket_ipv4_address_s));
    }
    ret_i++;
    break;
  }
  pthread_mutex_unlock(&midnightcode_cast_mutex_t);


  return ret_i;
}



int
midnightcode_cast_write(
                     struct midnightcode_cast_head_t * cache,
                     char * buffer, int buffer_size) {
  struct midnightcode_cast_element_t * cache_entry;
  int ret_i;


  if(!cache)
    return -1;
  if(buffer_size < 0)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  ret_i = 0;

  cache_entry = malloc(sizeof(struct midnightcode_cast_element_t));
  if(cache_entry == NULL)
    return -1;
  memset(cache_entry, 0, sizeof(struct midnightcode_cast_element_t));

  cache_entry->gateway = 0;
  strncpy(cache_entry->ipv4_address_s, midnightcode_cast_socket_ipv4_address_s,
    sizeof(midnightcode_cast_socket_ipv4_address_s));
  cache_entry->port = midnightcode_cast_socket_port;
  cache_entry->retrieved = 0;
  cache_entry->dispatched = 0;
  cache_entry->buffer_size = buffer_size;
  if(cache_entry->buffer_size > 0)
    cache_entry->buffer = buffer;

  pthread_mutex_lock(&midnightcode_cast_mutex_t);
  cache_entry->timestamp = (int)time(NULL);
  if(cache_entry->timestamp == -1)
    cache_entry->timestamp = 0;
  midnightcode_list_insert(cache, cache_entry);
  midnightcode_cast_cache_trim(cache);
  pthread_mutex_unlock(&midnightcode_cast_mutex_t);


  return ret_i;
}



int
midnightcode_cast_seed(
                     struct midnightcode_cast_head_t * cache,
                     char * address_s, int port,
                     char * buffer, int buffer_size) {
  struct midnightcode_cast_element_t * cache_entry;
  int ret_i;


  if(!cache)
    return -1;
  if(!address_s)
    return -1;
  if(port < 1 || port > 65535)
    return -1;
  if(port != MIDNIGHTCODE_CAST_PORT)
    return -1;
  if(buffer_size < 0)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  ret_i = 0;

  cache_entry = malloc(sizeof(struct midnightcode_cast_element_t));
  if(cache_entry == NULL)
    return -1;
  memset(cache_entry, 0, sizeof(struct midnightcode_cast_element_t));

  cache_entry->gateway = 0;
  memcpy(cache_entry->ipv4_address_s, address_s, 
    sizeof(midnightcode_cast_socket_ipv4_address_s));
  cache_entry->port = port;
  cache_entry->retrieved = 0;
  cache_entry->dispatched = 0;
  cache_entry->buffer_size = buffer_size;
  if(cache_entry->buffer_size > 0)
    cache_entry->buffer = buffer;

  pthread_mutex_lock(&midnightcode_cast_mutex_t);
  cache_entry->timestamp = (int)time(NULL);
  if(cache_entry->timestamp == -1)
    cache_entry->timestamp = 0;
  midnightcode_list_insert(cache, cache_entry);
  pthread_mutex_unlock(&midnightcode_cast_mutex_t);


  return ret_i;
}



int
midnightcode_cast_cache_empty(struct midnightcode_cast_head_t * cache) {
  struct midnightcode_cast_element_t * cache_entry;
  struct midnightcode_cast_element_t * hold_cache_entry;
  int ret_i;


  if(!cache)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  ret_i = 0;
  midnightcode_list_for_each_safe(cache_entry, hold_cache_entry, cache) {
    midnightcode_list_remove(cache, cache_entry);
    if(cache_entry != NULL) {
      if(cache_entry->buffer != NULL)
        free(cache_entry->buffer);
      free(cache_entry);
    }
    ret_i++;
    midnightcode_list_for_each_safe_loop(cache_entry, hold_cache_entry, cache);
  }


  return ret_i;
}



int
midnightcode_cast_cache_trim(struct midnightcode_cast_head_t * cache) {
  struct midnightcode_cast_element_t * cache_entry;
  int ret_i;


  if(!cache)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  ret_i = 0;
  while(midnightcode_list_count(cache) > MIDNIGHTCODE_CAST_CACHE_SIZE) {
    midnightcode_list_last(cache_entry, cache);
    if(cache_entry == NULL) {
      break;
    } else {
      midnightcode_list_remove(cache, cache_entry);
      if(cache_entry != NULL) {
        if(cache_entry->buffer != NULL)
          free(cache_entry->buffer);
        free(cache_entry);
      }
      ret_i++;
    }
  }


  return ret_i;
}



int
midnightcode_cast_cache_merge(
                     struct midnightcode_cast_head_t * cache,
                     struct midnightcode_cast_head_t * new_cache) {
  struct midnightcode_cast_element_t * cache_entry;
  struct midnightcode_cast_element_t * hold_cache_entry;
  struct midnightcode_cast_element_t * new_cache_entry;
  struct midnightcode_cast_element_t * hold_new_cache_entry;
  int inserted;
  int ret_i;


  if(!cache)
    return -1;
  if(!new_cache)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  ret_i = 0;

  midnightcode_list_for_each_safe(new_cache_entry,
    hold_new_cache_entry, new_cache) {
    inserted = 0;
    midnightcode_list_for_each_safe(cache_entry, hold_cache_entry, cache) {
      if(memcmp(cache_entry->ipv4_address_s, new_cache_entry->ipv4_address_s,
        32) == 0 && cache_entry->port == new_cache_entry->port &&
        (new_cache_entry->timestamp <= (cache_entry->timestamp +
          MIDNIGHTCODE_CAST_CACHE_TIME_VARIANCE) &&
          new_cache_entry->timestamp >= (cache_entry->timestamp -
          MIDNIGHTCODE_CAST_CACHE_TIME_VARIANCE)) &&
        cache_entry->buffer_size == new_cache_entry->buffer_size) {
        if(cache_entry->buffer_size <= 0) {
          inserted++;
          break;
        }
        if(memcmp(cache_entry->buffer, new_cache_entry->buffer,
          cache_entry->buffer_size) == 0) {
          inserted++;
          break;
        }
      }
      midnightcode_list_for_each_safe_loop(cache_entry, hold_cache_entry,
        cache);
    }
    if(inserted) {
      midnightcode_list_remove(new_cache, new_cache_entry);
      if(new_cache_entry != NULL) {
        if(new_cache_entry->buffer != NULL)
          free(new_cache_entry->buffer);
        free(new_cache_entry);
      }
    }
    midnightcode_list_for_each_safe_loop(new_cache_entry,
      hold_new_cache_entry, new_cache);
  }

  midnightcode_list_for_each_safe(new_cache_entry,
    hold_new_cache_entry, new_cache) {
    inserted = 0;
    midnightcode_list_for_each_safe(cache_entry, hold_cache_entry, cache) {
      if(new_cache_entry->timestamp > cache_entry->timestamp) {
        midnightcode_list_remove(new_cache, new_cache_entry);
        midnightcode_list_insert_before(cache,
          cache_entry, new_cache_entry);
        inserted++;
        ret_i++;
        break;
      }
      midnightcode_list_for_each_safe_loop(cache_entry, hold_cache_entry,
        cache);
    }
    if(!inserted) {
      midnightcode_list_remove(new_cache, new_cache_entry);
      midnightcode_list_append(cache, new_cache_entry);
      ret_i++;
    }
    midnightcode_list_for_each_safe_loop(new_cache_entry,
      hold_new_cache_entry, new_cache);
  }

  midnightcode_cast_cache_trim(cache);
  midnightcode_cast_cache_empty(new_cache);


  return ret_i;
}



int
midnightcode_cast_membrane(int direction, int type,
                     char ** buffer, int * buffer_size,
                     struct midnightcode_cast_element_t ** cache_entry_p,
                     struct midnightcode_cast_head_t * cache) {
  struct midnightcode_cast_element_t * cache_entry;
  struct midnightcode_cast_element_t cache_empty;
  char * buffer_ptr;
  char query_s[5];
  char entry_s[5];
  char gateway_s[9];
  char address_s[9];
  char port_s[6];
  char timestamp_s[12];
  char message_size_s[9];
  int query;
  int entry;
  int ret_i;


  if(direction != MIDNIGHTCODE_CAST_MEMBRANE_ENCODE &&
    direction != MIDNIGHTCODE_CAST_MEMBRANE_DECODE)
    return -1;
  if(type != MIDNIGHTCODE_CAST_TYPE_ACK &&
    type != MIDNIGHTCODE_CAST_TYPE_CONTROL &&
    type != MIDNIGHTCODE_CAST_TYPE_DATA)
    return -1;
  if(!cache)
    return -1;
  if(!buffer)
    return -1;
  if(!buffer_size)
    return -1;

  memset(query_s, 0, sizeof(query_s));
  memset(entry_s, 0, sizeof(entry_s));
  memset(gateway_s, 0, sizeof(gateway_s));
  memset(address_s, 0, sizeof(address_s));
  memset(port_s, 0, sizeof(port_s));
  memset(timestamp_s, 0, sizeof(timestamp_s));
  memset(message_size_s, 0, sizeof(message_size_s));

  ret_i = 0;

  /*

      +---+---+---+---+---+---+---+---+
      | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
      +---+---+---+---+---+---+---+---+
      |     Query     |     Entry     |
      +---+---+---+---+---+---+---+---+
      |       Forwarder (Unused)      |
      +---+---+---+---+---+---+---+---+
      |          IP Address           |
      +---+---+---+---+---+---+---+---+
      |        Port       |      Time
      +---+---+---+---+---+---+---+---+
        Stamp                         |
      +---+---+---+---+---+---+---+---+
      |          Message Size         |
      +---+---+---+---+---+---+---+---+
      | Message ... 
      +---+---+---+---+---+---+---+---+


  */

  switch(direction) {

    case MIDNIGHTCODE_CAST_MEMBRANE_ENCODE:
      if(type == MIDNIGHTCODE_CAST_TYPE_ACK) {
        query = MIDNIGHTCODE_CAST_QUERY_ACK;
        entry = 0;
        memset(&cache_empty, 0, sizeof(struct midnightcode_cast_element_t));
        cache_entry = &cache_empty;
        cache_entry->gateway = 0;
        strncpy(cache_entry->ipv4_address_s,
          midnightcode_cast_socket_ipv4_address_s,
          sizeof(midnightcode_cast_socket_ipv4_address_s));
        cache_entry->port = midnightcode_cast_socket_port;
        cache_entry->timestamp = (int)time(NULL);
        if(cache_entry->timestamp == -1)
          cache_entry->timestamp = 0;
        cache_entry->buffer_size = 0;
        cache_entry->buffer = NULL;
      } else {
        query = MIDNIGHTCODE_CAST_QUERY_SET;
        entry = 0;
        if(type == MIDNIGHTCODE_CAST_TYPE_CONTROL) {
          query |= MIDNIGHTCODE_CAST_QUERY_CONTROL;
          entry = midnightcode_list_count(cache);
          memset(&cache_empty, 0, sizeof(struct midnightcode_cast_element_t));
          cache_entry = &cache_empty;
          cache_entry->gateway = 0;
          strncpy(cache_entry->ipv4_address_s,
            midnightcode_cast_socket_ipv4_address_s,
            sizeof(midnightcode_cast_socket_ipv4_address_s));
          cache_entry->port = midnightcode_cast_socket_port;
          cache_entry->timestamp = (int)time(NULL);
          if(cache_entry->timestamp == -1)
            cache_entry->timestamp = 0;
          cache_entry->buffer_size = 0;
          cache_entry->buffer = NULL;
        } else {
          query |= MIDNIGHTCODE_CAST_QUERY_DATA;
          cache_entry = *cache_entry_p;
          entry = midnightcode_list_entry_index(cache_entry);
        }
      }
      if(cache_entry == NULL)
        return -1;
      if(cache_entry->buffer_size < 0)
        return -1;
      *buffer_size = (MIDNIGHTCODE_CAST_MESSAGE_MINSIZE +
        cache_entry->buffer_size);
      *buffer = malloc(*buffer_size);
      if(*buffer == NULL)
        return -1;
      memset(*buffer, 0, *buffer_size);
      buffer_ptr = *buffer;
      if(midnightcode_parse_vartostr(buffer_ptr, &(query),
        4, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 4;
      if(midnightcode_parse_vartostr(buffer_ptr, &(entry),
        4, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 8;
      if(midnightcode_parse_vartostr(buffer_ptr, &(cache_entry->gateway),
        8, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 16;
      if(midnightcode_ip_tohex_s((cache_entry->ipv4_address_s),
        buffer_ptr) != 1)
        ret_i = -1;
      buffer_ptr = *buffer + 24;
      if(midnightcode_parse_vartostr(buffer_ptr, &(cache_entry->port),
        5, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 29;
      if(midnightcode_parse_vartostr(buffer_ptr, &(cache_entry->timestamp),
        12, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 40;
      if(midnightcode_parse_vartostr(buffer_ptr, &(cache_entry->buffer_size),
        8, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      if(ret_i < 0) {
        free(*buffer);
        *buffer = NULL;
        return -1;
      }
      if(cache_entry->buffer_size > 0) {
        buffer_ptr = *buffer + MIDNIGHTCODE_CAST_MESSAGE_MINSIZE;
        memcpy(buffer_ptr, cache_entry->buffer, cache_entry->buffer_size);
      }
      break;

    case MIDNIGHTCODE_CAST_MEMBRANE_DECODE:
      if(cache_entry_p == NULL)
        return -1;
      if(buffer == NULL)
        return -1;
      if(*buffer_size < MIDNIGHTCODE_CAST_MESSAGE_MINSIZE)
        return -1;
      cache_entry = malloc(sizeof(struct midnightcode_cast_element_t));
      if(cache_entry == NULL)
        return -1;
      memset(cache_entry, 0, sizeof(struct midnightcode_cast_element_t));
      *cache_entry_p = cache_entry;
      buffer_ptr = *buffer;
      memcpy(query_s, buffer_ptr, 4);
      if(midnightcode_parse_strtovar(&query, query_s,
          0, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 4;
      memcpy(entry_s, buffer_ptr, 4);
      if(midnightcode_parse_strtovar(&entry, entry_s,
          0, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 8;
      memcpy(gateway_s, buffer_ptr, 8);
      if(midnightcode_parse_strtovar(&(cache_entry->gateway), gateway_s,
          0, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 16;
      memcpy(address_s, buffer_ptr, 8);
      if(midnightcode_ip_fromhex_s((cache_entry->ipv4_address_s),
        address_s) != 1)
        ret_i = -1;
      if(midnightcode_ip_isvalid_s(cache_entry->ipv4_address_s) != 1)
        ret_i = -1;
      buffer_ptr = *buffer + 24;
      memcpy(port_s, buffer_ptr, 5);
      if(midnightcode_parse_strtovar(&(cache_entry->port), port_s,
          0, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      if(cache_entry->port != MIDNIGHTCODE_CAST_PORT)
        ret_i = -1;
      buffer_ptr = *buffer + 29;
      memcpy(timestamp_s, buffer_ptr, 11);
      if(midnightcode_parse_strtovar(&(cache_entry->timestamp), timestamp_s,
          0, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      buffer_ptr = *buffer + 40;
      memcpy(message_size_s, buffer_ptr, 8);
      if(midnightcode_parse_strtovar(&(cache_entry->buffer_size),
          message_size_s, 0, 0, MIDNIGHTCODE_PARSE_TYPE_INT))
        ret_i = -1;
      if(cache_entry->buffer_size < 0 ||
        *buffer_size != (MIDNIGHTCODE_CAST_MESSAGE_MINSIZE +
        cache_entry->buffer_size))
        ret_i = -1;
      if(ret_i < 0) {
        free(cache_entry);
        *cache_entry_p = NULL;
        return -1;
      }
      if(query == MIDNIGHTCODE_CAST_QUERY_ACK)
        ret_i = MIDNIGHTCODE_CAST_TYPE_ACK;
      if(query == (MIDNIGHTCODE_CAST_QUERY_SET|MIDNIGHTCODE_CAST_QUERY_CONTROL))
        ret_i = entry;
      if(query == (MIDNIGHTCODE_CAST_QUERY_SET|MIDNIGHTCODE_CAST_QUERY_DATA))
        ret_i = MIDNIGHTCODE_CAST_TYPE_DATA;
      if(cache_entry->buffer_size > 0) {
        cache_entry->buffer = malloc(cache_entry->buffer_size);
        if(cache_entry->buffer == NULL) {
          free(cache_entry);
          *cache_entry_p = NULL;
          return -1;
        }
        buffer_ptr = *buffer + MIDNIGHTCODE_CAST_MESSAGE_MINSIZE;
        memcpy(cache_entry->buffer, buffer_ptr, cache_entry->buffer_size);
      }
      break;

    default:
      return -1;
      break;
  }


  return ret_i;
}



int
midnightcode_cast_send(
                     struct midnightcode_cast_head_t * cache,
                     struct midnightcode_socket_element_t * socket) {
  struct midnightcode_cast_element_t * cache_entry;
  char * buffer;
  int buffer_len;
  int block;
  int query;
  int ret_i;


  if(!cache)
    return -1;
  if(!socket)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  buffer = NULL;
  block = -1;
  ret_i = 0;
  while(socket && socket->state == MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL) {

    if(buffer != NULL)
      free(buffer);
    buffer = NULL;
    buffer_len = 0;
    cache_entry = NULL;

    if(block < 0) {
      query = MIDNIGHTCODE_CAST_TYPE_CONTROL;
    } else {
      query = MIDNIGHTCODE_CAST_TYPE_DATA;
      midnightcode_list_entry_for_index(cache_entry, cache, block);
      if(!cache_entry) {
        midnightcode_socket_close(socket);
        return -1;
      }
      cache_entry->dispatched++;
    }
    if(midnightcode_cast_membrane(MIDNIGHTCODE_CAST_MEMBRANE_ENCODE,
      query, &buffer, &buffer_len, &cache_entry, cache) < 0) {
      if(buffer != NULL)
        free(buffer);
      buffer = NULL;
      buffer_len = 0;
      midnightcode_socket_close(socket);
      return -1;
    }
    if(midnightcode_socket_write_block(socket, buffer, buffer_len, 
      MIDNIGHTCODE_CAST_SOCKET_HANG_MAX,
      MIDNIGHTCODE_SOCKET_IO_BLOCKING) <= 0) {
      if(buffer != NULL)
        free(buffer);
      buffer = NULL;
      buffer_len = 0;
      midnightcode_socket_close(socket);
      return -1;
    }

    if(block == -1 || block == (midnightcode_list_count(cache) - 1)) {
      if(buffer != NULL)
        free(buffer);
      buffer = NULL;
      buffer_len = 0;
      if(midnightcode_socket_read_block(socket, &buffer, &buffer_len, 1,
        MIDNIGHTCODE_CAST_SOCKET_HANG_MAX,
        MIDNIGHTCODE_SOCKET_IO_BLOCKING) <= 0) {
        midnightcode_socket_close(socket);
        return -1;
      }
      if(midnightcode_cast_membrane(MIDNIGHTCODE_CAST_MEMBRANE_DECODE,
        MIDNIGHTCODE_CAST_TYPE_ACK,
        &buffer, &buffer_len, &cache_entry, cache) !=
        MIDNIGHTCODE_CAST_TYPE_ACK) {
        if(buffer != NULL)
          free(buffer);
        buffer = NULL;
        buffer_len = 0;
        midnightcode_socket_close(socket);
        return -1;
      }
      if(block == (midnightcode_list_count(cache) - 1))
        break;
    }
    block++;
  }

  if(socket->state != MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL) {
    midnightcode_socket_close(socket);
    return -1;
  }


  return ret_i;
}



int
midnightcode_cast_receive(
                     struct midnightcode_cast_head_t * cache,
                     struct midnightcode_socket_element_t * socket) {
  struct midnightcode_cast_element_t * cache_entry;
  char * buffer;
  int buffer_len;
  int remote_cache_size;
  int remote_timestamp_delta;
  int block;
  int ret_i;


  if(!cache)
    return -1;
  if(!socket)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  buffer = NULL;
  block = -1;
  ret_i = 0;
  remote_cache_size = 0;
  remote_timestamp_delta = 0;

  while(socket->state == MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL) {

    if(buffer != NULL)
      free(buffer);
    buffer = NULL;
    buffer_len = 0;
    cache_entry = NULL;

    if(midnightcode_socket_read_block(socket, &buffer, &buffer_len, 1,
      MIDNIGHTCODE_CAST_SOCKET_HANG_MAX,
      MIDNIGHTCODE_SOCKET_IO_BLOCKING) <= 0) {
      midnightcode_socket_close(socket);
      return -1;
    }
    if((ret_i = midnightcode_cast_membrane(MIDNIGHTCODE_CAST_MEMBRANE_DECODE,
      MIDNIGHTCODE_CAST_TYPE_ACK,
      &buffer, &buffer_len, &cache_entry, cache)) < 0) {
      if(buffer != NULL)
        free(buffer);
      buffer = NULL;
      buffer_len = 0;
      midnightcode_socket_close(socket);
      return -1;
    }

    if(block == -1) {
      remote_cache_size = ret_i;
      if(remote_cache_size > MIDNIGHTCODE_CAST_CACHE_SIZE) {
        if(buffer != NULL)
          free(buffer);
        buffer = NULL;
        buffer_len = 0;
        midnightcode_socket_close(socket);
        return -1;
      }
      remote_timestamp_delta = (int)time(NULL);
      if(remote_timestamp_delta == -1) {
        remote_timestamp_delta = 0;
      } else {
        remote_timestamp_delta = remote_timestamp_delta -
          cache_entry->timestamp;
      }
    } else {
      if(ret_i == MIDNIGHTCODE_CAST_TYPE_DATA) {
        cache_entry->timestamp += remote_timestamp_delta;
        midnightcode_list_append(cache, cache_entry);
      }
    }
    if(block == -1 || block == (remote_cache_size - 1)) {
      if(buffer != NULL)
        free(buffer);
      buffer = NULL;
      buffer_len = 0;
      cache_entry = malloc(sizeof(struct midnightcode_cast_element_t));
      if(cache_entry == NULL) {
        midnightcode_socket_close(socket);
        return -1;
      }
      memset(cache_entry, 0, sizeof(struct midnightcode_cast_element_t));
      if(midnightcode_cast_membrane(MIDNIGHTCODE_CAST_MEMBRANE_ENCODE,
        MIDNIGHTCODE_CAST_TYPE_ACK,
        &buffer, &buffer_len, &cache_entry, cache) < 0) {
        if(buffer != NULL)
          free(buffer);
        buffer = NULL;
        buffer_len = 0;
        if(cache_entry != NULL)
          free(cache_entry);
        midnightcode_socket_close(socket);
        return -1;
      }
      free(cache_entry);

      if(midnightcode_socket_write_block(socket, buffer, buffer_len, 
        MIDNIGHTCODE_CAST_SOCKET_HANG_MAX,
        MIDNIGHTCODE_SOCKET_IO_BLOCKING) <= 0) {
        if(buffer != NULL)
          free(buffer);
        buffer = NULL;
        buffer_len = 0;
        midnightcode_socket_close(socket);
        return -1;
      }

      if(block == (remote_cache_size - 1))
        break;
    }
    block++;
  }

  if(socket->state != MIDNIGHTCODE_SOCKET_STATE_OPERATIONAL) {
    midnightcode_socket_close(socket);
    return -1;
  }


  return ret_i;
}



void *
midnightcode_cast_handler_inbound(void *arg) {
  struct midnightcode_socket_element_t * socket;
  struct midnightcode_cast_head_t * cache;
  struct midnightcode_cast_head_t * inbound_cache;
  void * tmp_void_p;

  midnightcode_list_init(cast_struct_head, midnightcode_cast_element_t);
  tmp_void_p = (void *)&cast_struct_head;
  inbound_cache = tmp_void_p;


  arg = NULL;
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

  while(1) {

    pthread_mutex_lock(&midnightcode_cast_mutex_t);

    if(!midnightcode_cast_cache) {
      pthread_mutex_unlock(&midnightcode_cast_mutex_t);
      pthread_exit(NULL);
    }

    cache = midnightcode_cast_cache;

    if(midnightcode_socket_getchild(&socket, midnightcode_cast_socket) > 0) {
      if(midnightcode_cast_receive(inbound_cache, socket) < 0) {
        midnightcode_cast_cache_empty(inbound_cache);
        pthread_mutex_unlock(&midnightcode_cast_mutex_t);
        pthread_testcancel();
        usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_INBOUND);
        continue;
      }
      if(midnightcode_cast_send(cache, socket) < 0) {
        pthread_mutex_unlock(&midnightcode_cast_mutex_t);
        pthread_testcancel();
        usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_INBOUND);
        continue;
      }
      midnightcode_socket_close(socket);

      /* TODO: consider checking merge error state */
      if(midnightcode_cast_cache_merge(cache, inbound_cache) < 0)
        midnightcode_cast_cache_empty(inbound_cache);
    }

    pthread_mutex_unlock(&midnightcode_cast_mutex_t);
    pthread_testcancel();
    usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_INBOUND);

  }

}



void *
midnightcode_cast_handler_outbound(void *arg) {
  struct midnightcode_socket_element_t * socket;
  struct midnightcode_cast_head_t * cache;
  struct midnightcode_cast_element_t * cache_entry;
  struct midnightcode_cast_head_t * inbound_cache;
  void * tmp_void_p;
  int random_attempt;
  int index_i;

  midnightcode_list_init(cast_struct_head, midnightcode_cast_element_t);
  tmp_void_p = (void *)&cast_struct_head;
  inbound_cache = tmp_void_p;


  arg = NULL;
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

  while(1) {

    pthread_mutex_lock(&midnightcode_cast_mutex_t);

    if(!midnightcode_cast_cache) {
      pthread_mutex_unlock(&midnightcode_cast_mutex_t);
      pthread_exit(NULL);
    }

    cache = midnightcode_cast_cache;
    if(midnightcode_list_count(cache) == 0) {
      pthread_mutex_unlock(&midnightcode_cast_mutex_t);
      pthread_testcancel();
      usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_OUTBOUND);
      continue;
    }

    cache_entry = NULL;
    for(random_attempt = midnightcode_list_count(cache); random_attempt > 0;
      random_attempt--) {

      index_i = midnightcode_random_randmax(midnightcode_list_count(cache)) - 1;

      cache_entry = NULL;
      midnightcode_list_entry_for_index(cache_entry, cache, index_i);
      if(!cache_entry)
        continue;
      if(memcmp(cache_entry->ipv4_address_s,
        midnightcode_cast_socket_ipv4_address_s,
        sizeof(midnightcode_cast_socket_ipv4_address_s)) == 0) {
        cache_entry = NULL;
        continue;
      }

      break;
    }
    if(!cache_entry) {
      pthread_mutex_unlock(&midnightcode_cast_mutex_t);
      pthread_testcancel();
      usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_OUTBOUND);
      continue;
    }

    /* TODO: establish a retry count and retry test */

    if(midnightcode_socket_open(&socket,
      MIDNIGHTCODE_SOCKET_CONTROL_MIDNIGHTCODE,
      MIDNIGHTCODE_SOCKET_DPD_ENABLE, 0,
      MIDNIGHTCODE_SOCKET_DIR_INITIATE,
      MIDNIGHTCODE_SOCKET_PROTOCOL_TCP,
      cache_entry->ipv4_address_s, cache_entry->port) >= 0) {

      if(midnightcode_cast_send(cache, socket) < 0) {
        pthread_mutex_unlock(&midnightcode_cast_mutex_t);
        pthread_testcancel();
        usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_OUTBOUND);
        continue;
      }
      if(midnightcode_cast_receive(inbound_cache, socket) < 0) {
        midnightcode_cast_cache_empty(inbound_cache);
        pthread_mutex_unlock(&midnightcode_cast_mutex_t);
        pthread_testcancel();
        usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_OUTBOUND);
        continue;
      }
      midnightcode_socket_close(socket);

      /* TODO: consider checking merge error state */
      if(midnightcode_cast_cache_merge(cache, inbound_cache) < 0)
        midnightcode_cast_cache_empty(inbound_cache);
    }

    pthread_mutex_unlock(&midnightcode_cast_mutex_t);
    pthread_testcancel();
    usleep(MIDNIGHTCODE_CAST_POLL_TIME_HANDLER_OUTBOUND);

  }

}



int
midnightcode_cast_initialise(struct midnightcode_cast_head_t * cache,
                     int options, char * address_s, int port) {
  int ret_i;


  if(!cache)
    return -1;
  if(!address_s)
    return -1;
  if((options & MIDNIGHTCODE_CAST_OPTION_UNICAST) !=
    MIDNIGHTCODE_CAST_OPTION_UNICAST)
    return -1;
  if(port < 1 || port > 65535)
    return -1;
  if(port != MIDNIGHTCODE_CAST_PORT)
    return -1;
  if(midnightcode_cast_cache != NULL)
    return -1;

  midnightcode_cast_cache = cache;
  memset(midnightcode_cast_socket_ipv4_address_s, 0,
    sizeof(midnightcode_cast_socket_ipv4_address_s));
  strncpy(midnightcode_cast_socket_ipv4_address_s, address_s, 
    sizeof(midnightcode_cast_socket_ipv4_address_s));
  midnightcode_cast_socket_port = port;
  midnightcode_cast_options_unicast = 0;
  if(options & MIDNIGHTCODE_CAST_OPTION_UNICAST)
    midnightcode_cast_options_unicast = 1;
  midnightcode_cast_options_multicast = 0;
  if(options & MIDNIGHTCODE_CAST_OPTION_MULTICAST)
    midnightcode_cast_options_multicast = 1;

  /* TODO: Consider Multicast implementation for inbound and outbound */

  ret_i = 0;
  if(midnightcode_socket_open(&midnightcode_cast_socket,
    MIDNIGHTCODE_SOCKET_CONTROL_MIDNIGHTCODE,
    MIDNIGHTCODE_SOCKET_DPD_ENABLE, 
    MIDNIGHTCODE_CAST_SOCKET_QUEUE,
    MIDNIGHTCODE_SOCKET_DIR_LISTEN,
    MIDNIGHTCODE_SOCKET_PROTOCOL_TCP,
    address_s, MIDNIGHTCODE_CAST_PORT) < 0) {
    midnightcode_cast_cache = NULL;
    memset(midnightcode_cast_socket_ipv4_address_s, 0,
      sizeof(midnightcode_cast_socket_ipv4_address_s));
    midnightcode_cast_socket_port = 0;
    midnightcode_cast_options_unicast = 0;
    midnightcode_cast_options_multicast = 0;
    return -2;
  }
  if(pthread_create(&midnightcode_cast_inbound_pthread_t, NULL,
    midnightcode_cast_handler_inbound, NULL)) {
    if(midnightcode_cast_shutdown(cache) < 0)
      midnightcode_cast_cache_empty(cache);
    return -1;
  }
  if(pthread_create(&midnightcode_cast_outbound_pthread_t, NULL,
    midnightcode_cast_handler_outbound, NULL)) {
    if(midnightcode_cast_shutdown(cache) < 0)
      midnightcode_cast_cache_empty(cache);
    return -1;
  }


  return ret_i;
}



int
midnightcode_cast_shutdown(struct midnightcode_cast_head_t * cache) {
  int ret_i;


  if(!cache)
    return -1;
  if(cache != midnightcode_cast_cache)
    return -1;
  if(midnightcode_cast_cache == NULL)
    return -1;

  ret_i = 0;

  midnightcode_socket_close(midnightcode_cast_socket);

  if(pthread_cancel(midnightcode_cast_inbound_pthread_t))
    ret_i = -1;
  if(pthread_cancel(midnightcode_cast_outbound_pthread_t))
    ret_i = -1;
  if(ret_i == 0)
    midnightcode_cast_cache_empty(cache);

  midnightcode_cast_cache = NULL;
  memset(midnightcode_cast_socket_ipv4_address_s, 0,
    sizeof(midnightcode_cast_socket_ipv4_address_s));
  midnightcode_cast_socket_port = 0;
  midnightcode_cast_options_unicast = 0;
  midnightcode_cast_options_multicast = 0;


  return ret_i;
}


