/*
 * backend.c,v 1.1 1994/01/28 17:05:17 franktor Exp
 *
 * This file contains routines which handles the different linked
 * lists in the structures (see server.h) used by the server.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <malloc.h>
#include <sr-general.h>
#include <sr-api.h>
#include <high/eapi.h>
#include <high/sr_structcodec.h>
#include <high/server-structdesc.h> /* Description of eapi.h structs */
#include <sr-converter.h>

#include "server.h"

#ifndef __CEXTRACT__
#include "proto.h"
#endif

/*
 * The following two functions are meant as "glue" for the "glue" 8)
 * They handle the storing of the data which is used by the unseen
 * routines which handles the physical transfer of the packets.
 */

Boolean
SendDBPacket(EapiPacket *packet, Client *client, DatabaseServerConnection *connection)
{
  socketStat res;
  packet->ref = client->ref;
  LOG(facHigh, llevTrace, "Sending packet with reference %d & type %d.",
      packet->ref,packet->type);
  res = send_struct(connection->file_descriptor, (void *) packet,
                    &connection->selectOnWrite);
  return (res == sockStatOK) ? True : False;
}

/*
 * RecieveDBPacket isn't used right now.
 */

Boolean
ReceiveDBPacket(EapiPacket *packet)
{
  socketStat res;
  res = read_struct( packet->ref, (void **) &packet);
  LOG(facHigh, llevDebug, "read_struct returned %d with packet type %d.",
      res, packet->type);
  return (res == sockStatOK) ? True : False;
}

/*
 * set_relevant_bits:
 * This function loops through all connections to database-servers, and
 * sets all the bits in the (argument) given bitfield which corresponds
 * to active file-descriptors, if it is of interest to listen to
 * the during select().
 */

void
set_relevant_bits()
{
  Client *client;
  ClientDatabaseServer *cds;

  FD_ZERO(&eapi_read);
  FD_ZERO(&eapi_write);
  FD_ZERO(&eapi_exceptions);
  for(client = first_client; client; client = client->next)
    for(cds = client->databaseServers; cds; cds = cds->next)
      if(cds->connection->file_descriptor >= 0)
      {
        FD_SET(cds->connection->file_descriptor, &eapi_read);
        if (cds->connection->selectOnWrite == True)
          FD_SET(cds->connection->file_descriptor, &eapi_write);
        FD_SET(cds->connection->file_descriptor, &eapi_exceptions);
      }
}

void
add_database_server(DatabaseServer *dbs)
{
  dbs->next = first_database_server;
  first_database_server = dbs;
}

void
add_databases(DatabaseServer *dbs, char *name)
{
  DatabaseName *dbn = (DatabaseName *) malloc(sizeof(DatabaseName));
  char *symbolic_name;

  if ((symbolic_name = strchr(name,',')) != NULL)
  {
    *symbolic_name = '\0';
    LOG(facHigh, llevExceptions, "Aliases are no longer allowed: %s.",
        ++symbolic_name);
  }
  dbn->database = strdup(name);
  dbn->next = dbs->databases;
  dbs->databases = dbn;
}

void
add_supported_syntaxes(DatabaseServer *dbs, char *name)
{
  char *next_name;
  SupportedSyntaxes *syn;

  if (!name || !(*name))
  {
    LOG(facHigh, llevDebug, "add_supported_syntaxes %s not accepted.", name);
    return;
  }
  LOG(facHigh, llevDebug, "add_supported_syntaxes(%s,%s)", dbs->name, name);
  if ((next_name = strchr(name,',')) != NULL)
  {
    *next_name = '\0';
    next_name++;
  }
  syn = (SupportedSyntaxes *) malloc(sizeof(SupportedSyntaxes));
  syn->next = dbs->supported_syntaxes;
  dbs->supported_syntaxes = syn;
  syn->abstractSyntax = OID_str2Oid (name);
  syn->transferSyntax = next_name ? OID_str2Oid (next_name) : OID_NULLPTR;
}

/* DATABASE SERVER CONNECTION */

/*
 * close_servers:
 * Send close-requests to all database-servers which we're the last client
 * who listens too.
 * Free the ClientDatabaseServer structures which we don't send
 * close-requests too.
 */

void close_servers (Client *client)
{
  ClientDatabaseServer *server, *next;

  LOG(facHigh, llevTrace, "close_servers(%d)",client->ref);

  for (server = client->databaseServers; server; server = next)
  {
    next = server->next;
    if (server->connection->state != clientServerReady)
      LOG(facHigh, llevTrace, "Trying to close server which isn't ready yet.");
    if (server->connection->refcount ==  1)
    {
      /*
       * This is the only client using it, but I probably should
       * make sure that no new clients try to use this connection
       * while waiting for the close response.
       */
      EapiPacket *pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
      pkt->type = eapiCloseRequest;
      pkt->u.close_request = (CloseRequest *) malloc(sizeof(CloseRequest));
      pkt->ref = server->connection->file_descriptor;
      if (SendDBPacket(pkt, client, server->connection) == False)
        LOG(facHigh, llevExceptions, "close_servers: Failed to send CloseRequest to %d.", pkt->ref);
      else
        LOG(facHigh, llevTrace, "close_servers: Sent CloseRequest to %s (%d).",
            server->connection->database_server->name, pkt->ref);
      free_EapiPacket(pkt);

      server->connection->state = clientServerClosing;
    }
    else
    {
      /* Just free the structure, only last client is responsible to close */
      remove_DatabaseServerConnectionToClient(server, client, True);
    }
  }
}

/*
 * find_DatabaseServerConnection():
 * This function searches for an available connection to a database from
 * the global list.
 */

DatabaseServerConnection *
find_DatabaseServerConnection(DatabaseServer *dbs)
{
  DatabaseServerConnection *dbsc;

  for (dbsc = first_database_server_connection; dbsc; dbsc = dbsc->next)
    if (dbsc->database_server == dbs)
      return dbsc;
  return (DatabaseServerConnection *) NULL;
}

/*
 * fd2DatabaseServerConnection():
 * This function searches for which connection is using a certain file descriptor.
 * NULL is returned on failure.
 */

DatabaseServerConnection *
fd2DatabaseServerConnection(int fd)
{
  DatabaseServerConnection *dbsc;

  for (dbsc = first_database_server_connection; dbsc; dbsc = dbsc->next)
    if (dbsc->file_descriptor == fd)
      return dbsc;
  return (DatabaseServerConnection *) NULL;
}

/*
 * add_DatabaseServerConnection:
 * Creates a new connection structure, and links it with the global list.
 */

DatabaseServerConnection *
add_DatabaseServerConnection(DatabaseServer *dbs)
{
  DatabaseServerConnection *dbsc = (DatabaseServerConnection *) malloc(sizeof(DatabaseServerConnection));
  DatabaseServerConnection *last_dbsc;

  for (last_dbsc = first_database_server_connection;
       last_dbsc && last_dbsc->next;
       last_dbsc = last_dbsc->next);
  if (last_dbsc)
    last_dbsc->next = dbsc;
  else
    first_database_server_connection = dbsc;

  dbsc->next = NULL;
  dbsc->database_server = dbs;
  dbsc->state = clientServerOpening;	/* I assume an OpenRequest is about to be sent */
  dbsc->file_descriptor = (-1);
  dbsc->data = NULL;
  dbsc->refcount = 1;
  dbsc->selectOnWrite = False;
  dbsc->loopcount = 0;

  return dbsc;
}

/*
 * remove_DatabaseServerConnection:
 * This decreases the refcount of one DatabaseServerConnection.
 * If the refcount reaches 0, the structure is removed from the global list.
 * If Close is set to False, remove_DatabaseServerConnection() will not try
 * to close the connection (This is ideal when just removing the datastructure
 * after a failed attempt to open a connection).
 */

void
remove_DatabaseServerConnection(DatabaseServerConnection *dbsc, Boolean Close)
{
  DatabaseServerConnection *tmp, *prev = NULL;

  for (tmp = first_database_server_connection; tmp; prev = tmp, tmp = tmp->next)
    if (tmp == dbsc)
      break;
  if (tmp == NULL)
  {
    LOG(facHigh, llevExceptions,
        "Failed to remove DatabaseServerConnection: Didn't find it.");
    return;
  }
  LOG(facHigh, llevDebug, "remove_connection: Refcount is %d.", dbsc->refcount);
  if (--dbsc->refcount > 0)
  {
    LOG(facHigh, llevDebug, "Returning, still more open connections.");
    return;	/* There are still other clients using this connection */
  }
  if (Close == True)
  {
    if (closeConnection(dbsc->file_descriptor) != srPktStat_ok)
      LOG(facHigh, llevExceptions, "closeConnection() failed.");
  }
  if (prev == NULL) /* Was this the first connection? */
    first_database_server_connection = tmp->next;
  else
    prev->next = tmp->next;

  if (dbsc->data)	/* Is this my responsibility? */
    free(dbsc->data);
  free(dbsc);
}

/* DATABASE SERVER CONNECTION TO CLIENT */

/*
 * connectClientToDatabaseServer():
 * There are several steps in this function:
 * - See if there already is a connection to this database, and if it is
 *   is possible to multiplex several clients through that connection.
 *   If so, use that connection, and return.
 * - 
 * If there isn't already a connection from this client to that database
 * server, send a eapiOpenRequest for that database.
 * If there is already an open connection, return True, otherwise False.
 * (If this return False, the client will need to store the information
 * it meant to send to the server until a eapiOpenResponse is received)
 *
 * On failure, connectClientToDatabaseServer() returns NULL;
 */

ClientDatabaseServer *
connectClientToDatabaseServer(Client *client, DatabaseServer *server)
{
  ClientDatabaseServer *cds;
  DatabaseServerConnection *connection;
  EapiPacket *pkt;
  int fd;
  connectionParameters conn;

  for (cds = client->databaseServers; cds; cds = cds->next)
    if (cds->connection->database_server == server)
    {
      LOG(facHigh, llevTrace, "Found previous connection to given server.");
      return cds;
    }

/* First see if there is an available connection already: */
  if (server->handle_several_clients)
  {
    DatabaseServerConnection *dbsc;
    dbsc = find_DatabaseServerConnection(server);
    if (dbsc) {
      LOG(facHigh, llevTrace, "Found connection to server from other client.");
      dbsc->refcount++;
      LOG(facHigh, llevTrace, "Connection is now used by %d clients.",dbsc->refcount);
      return add_DatabaseServerConnectionToClient(dbsc,client);
    }
  }
  LOG(facHigh, llevTrace, "Didn't find previous connection to given server.");

/*
 * Create the DatabaseServerConnection and ClientDatabaseServer structures:
 */
  connection = add_DatabaseServerConnection(server);
  cds = add_DatabaseServerConnectionToClient(connection, client);

  LOG(facHigh, llevTrace, "Added new connection structure.");

/* Now we will have to send an open request, so set up the packet. */

  conn.method = server->connection.method;
  switch(server->connection.method)
  {
    case connect_tcpip:
      conn.u.ip.address = strdup(server->connection.u.ip.address);
      conn.u.ip.port = server->connection.u.ip.port;
      LOG(facHigh, llevTrace, "Con. Method: %s port %d.",
          conn.u.ip.address,conn.u.ip.port);
      break;
    case connect_pipe:
      conn.u.program = strdup(server->connection.u.program);
      break;
    case connect_none:
    case connect_tcpip_multi:
      break; /* Should be impossible, but compilers would complain */
  }

  switch(ConnectToDatabaseServer(&conn, &fd, desc_EapiPacket))
  {
    case sockStatOK:
      LOG(facHigh, llevDebug, "ConnectToSocket() returned OK");
      break;
    case sockStatFailed:
      LOG(facHigh, llevExceptions, "ConnectToSocket() returned Failed");
      remove_DatabaseServerConnectionToClient(cds, client, False);
      /* remove_DatabaseServerConnection(connection); (Is done automatically) */
      return (ClientDatabaseServer *) NULL;
      break;
    default:
      LOG(facHigh, llevExceptions, "ConnectToSocket() returned unknown");
      /* Do something appropriate in the future here */
      break;
  }

  pkt = (EapiPacket *) malloc(sizeof(EapiPacket));
  pkt->type = eapiOpenRequest;
  pkt->ref = fd;
  pkt->u.open_request = (OpenRequest *) malloc(sizeof(OpenRequest));
  pkt->u.open_request->filler = 42; /* To avoid problems with purify */
  connection->file_descriptor = fd;

  if (SendDBPacket(pkt, client, cds->connection) == False) {
    LOG(facHigh, llevExceptions, "Failed to send eapi OpenRequest to %d.", pkt->ref);
    /* Add diagnostic */
    return (ClientDatabaseServer *) NULL;
  }
  free_EapiPacket(pkt);

/*
 * Now we know the file descriptor.
 */

  FD_SET(fd, &eapi_write); /* We want select() to notice new packets */
  FD_SET(fd, &eapi_read);
  FD_SET(fd, &eapi_exceptions);


  return cds;
}

/*
 * FirstClientDatabaseServer:
 * Return the first ClientDatabaseServer which has a matching state.
 * If none are found, return NULL.
 */
  
ClientDatabaseServer *
FirstClientDatabaseServer(Client *client, ClientServerConnectionStatuses state)
{
  ClientDatabaseServer *server;

  for (server = client->databaseServers; server; server = server->next)
    if (server->connection->state == state)
      return server;
  return NULL;
}

/*
 * FirstSingleClientDatabaseServer:
 * Returns the first ClientDatabaseServer which has a matching state AND
 * which we're the only one who listens to.  If there are none, return NULL;
 */

ClientDatabaseServer *
FirstSingleClientDatabaseServer(Client *client, ClientServerConnectionStatuses state)
{
  ClientDatabaseServer *server;

  for (server = client->databaseServers; server; server = server->next)
    if (server->connection->state == state && server->connection->refcount == 1)
      return server;
  return NULL;
}

/*
 * FirstNonsingleClientDatabaseServer:
 * Returns the first ClientDatabaseServer which has a matching state AND
 * which we're NOT the only one who listens to.  If there are none, return NULL;
 */

ClientDatabaseServer *
FirstNonsingleClientDatabaseServer(Client *client, ClientServerConnectionStatuses state)
{
  ClientDatabaseServer *server;

  for (server = client->databaseServers; server; server = server->next)
    if (server->connection->state == state && server->connection->refcount > 1)
      return server;
  return NULL;
}

/*
 * Database2ClientDatabaseServer:
 * Find out which connection to use for accessing a specific database.
 */

ClientDatabaseServer *
Database2ClientDatabaseServer (Client *client, DatabaseName *database)
{
  ClientDatabaseServer *server;
  DatabaseName *tmpdatabase;

  for (server = client->databaseServers; server; server = server->next)
    for (tmpdatabase = server->connection->database_server->databases;
         tmpdatabase; tmpdatabase = tmpdatabase->next)
      if (!strcmp(database->database, tmpdatabase->database))
        return server;
  return (ClientDatabaseServer *) NULL;
}
/*
 * fd2ClientDatabaseServer:
 * Return the ClientDatabaseServer structure which is associated with fd.
 */

ClientDatabaseServer *
fd2ClientDatabaseServer(Client *client, int fd)
{
  ClientDatabaseServer *server;

  for (server = client->databaseServers; server; server = server->next)
    if (server->connection->file_descriptor == fd)
      return server;
  return (ClientDatabaseServer *) NULL;
}

/*
 * add_DatabaseServerConnectionToClient:
 * Allocates a new ClientDatabaseServer, links it to the client, and returns it.
 */

ClientDatabaseServer *
add_DatabaseServerConnectionToClient(DatabaseServerConnection *connection, Client *client)
{
  ClientDatabaseServer *cdbs, *prev = NULL;
  for (cdbs = client->databaseServers; cdbs; prev = cdbs, cdbs = cdbs->next)
    if (cdbs->connection == connection) {
      LOG(facHigh, llevExceptions, "Tried to add connection which was already present.");
      return cdbs;
    }
  cdbs = (ClientDatabaseServer *) malloc(sizeof(ClientDatabaseServer));
  cdbs->connection = connection;
  cdbs->pending_packet = (EapiPacket *) NULL;
  cdbs->waiting_for_packet = False;
  cdbs->next = (ClientDatabaseServer *) NULL;

  if (prev == NULL)
    client->databaseServers = cdbs;
  else
    prev->next = cdbs;

  return cdbs;
}

/*
 * remove_DatabaseServerConnectionToClient:
 * Removes a ClientDatabaseServer from the linked list in a client.
 * If Close is false, remove_DatabaseServerConnectionToClient() will not
 * attempt to close any connections.  Use this when just removing the
 * data-structure after a failed attempt to establish an connection.
 */

void
remove_DatabaseServerConnectionToClient(ClientDatabaseServer *cdbs, Client *client,
					Boolean Close)
{
  ClientDatabaseServer *tmp, *prev = (ClientDatabaseServer *) NULL;
  ClientResultSetList *resultset;

  for (tmp = client->databaseServers; tmp; prev = tmp, tmp = tmp->next)
    if (tmp == cdbs)
      break;
  if (tmp == NULL)
  {
    LOG(facHigh, llevAll, "Failed to remove DatabaseServerConnection from client.");
    return;
  }
  if(cdbs->connection)
    remove_DatabaseServerConnection(cdbs->connection, Close);
  if (prev == NULL) /* Was this the first DatabaseServerConnection? */
    client->databaseServers = tmp->next;
  else
    prev->next = tmp->next;

  for (resultset = client->resultsets; resultset; resultset = resultset->next)
    if (resultset->server == cdbs)
      resultset->server = NULL;  /* Remove stray pointers */

  free(cdbs);
}

Client *
add_client(int ref)
{
  Client *client = (Client *) malloc(sizeof(Client));
  Client *last_client;

  for (last_client = first_client;
       last_client && last_client->next;
       last_client = last_client->next);
  if (last_client)
    last_client->next = client;
  else
    first_client = client;

  client->state = clientWaiting;

  client->next = NULL;
  client->ref = ref;
  client->resultsets = (ClientResultSetList *) NULL;
  client->databaseServers = (ClientDatabaseServer *) NULL;

  client->protocolVersion = 0;
  client->preferredMessageSize = 0;
  client->maximumMessageSize = 0;
  client->user = NULL;
  client->password = NULL;
  client->account = NULL;
  client->implementationId = NULL;
  client->implementationName = NULL;
  client->implementationVersion = NULL;
  client->pending_request = NULL;
  client->pending_delete_requests = NULL;
  client->finished_delete_requests = NULL;
  client->eapi_incoming = NULL;
  client->numberOfRecordsFound = (-1);
  client->resultSetStartPoint = 0;
  client->numberOfRecords = 0;
  client->numberOfRecordsFound = 0;
  client->file_descriptor = (-1);
  client->resultSetsLeftToDelete = 0;
  client->working_delete_request = NULL;
  client->preferredRecordSyntax = OID_NULLPTR;
  client->current_server = (ClientDatabaseServer *) NULL;
  client->conv_from = (ConvertInfo *) NULL;
  client->conv_to = (ConvertInfo *) NULL;
   
  return client;
}

void
remove_client(Client *client)
{
  Client *tmp, *prev = NULL;

  LOG(facHigh, llevTrace, "remove_client(%d) [%d]", client->ref, exit_on_idle);
  for (tmp = first_client; tmp; prev = tmp, tmp = tmp->next)
    if (tmp == client)
      break;
  if (tmp == NULL)
  {
    LOG(facHigh, llevAll, "Failed to remove client %d.",client->ref);
    return;
  }
  if (prev == NULL) /* Was this the first client? */
    first_client = tmp->next;
  else
    prev->next = tmp->next;

  if (client->user)
    free (client->user);
  if (client->password)
    free (client->password);
  if (client->account)
    free (client->account);
  if (client->implementationId)
    free (client->implementationId);
  if (client->implementationName)
    free (client->implementationName);
  if (client->implementationVersion)
    free (client->implementationVersion);
  if (client->resultsets)
    free_resultsets(client->resultsets);
    
  free (client);
  if (exit_on_idle && first_client == NULL)
  {
    LOG(facHigh, llevNotice, "No clients, exiting.");
    exit(0);
  }
}

Client *find_client(int ref) {
  Client *client;
  /* Maybe improve the speed here in the future? */
  for(client = first_client; client; client = client->next)
    if(client->ref == ref)
      return client;
  return NULL;
}

Client *ref2client(int ref) {
  Client *client;

  for(client = first_client; client; client = client->next)
    if(client->ref == ref)
      return client;
  return NULL;
}

Client *fd2client(int fd) {
  Client *client;
  ClientDatabaseServer *server;

  for (client = first_client; client; client = client->next)
    for (server = client->databaseServers; server; server = server->next)
      if (server->connection->file_descriptor == fd)
        return client;
  return NULL;
}

ClientResultSetList *
find_ClientResultSet(Client *client, char *name)
{
  ClientResultSetList *resultset;

  if(name == (char *) NULL)
    return (ClientResultSetList *) NULL;

  for (resultset = client->resultsets; resultset; resultset = resultset->next)
    if(resultset->resultSetId != NULL && !strcmp(resultset->resultSetId, name))
    {
      LOG(facHigh, llevDebug, "Found resultset %s.",name);
      return resultset;
    }
  LOG(facHigh, llevDebug, "Didn't find resultset %s.",name);
  return (ClientResultSetList *) NULL;
}

ClientResultSetList *
add_ClientResultSet(Client *client, ClientDatabaseServer *server, char *name)
{
  ClientResultSetList *tmp, *last = NULL;

  LOG(facHigh, llevDebug, "Added resultset %s.",name);
  for (last = client->resultsets; last && last->next; last = last->next)
    if (!strcmp(name,last->resultSetId)) {
      LOG(facHigh, llevExceptions, "Tried to add excisting resultsetlist %s.",name);
      return (ClientResultSetList *) NULL;
    }
  tmp = (ClientResultSetList *) malloc(sizeof(ClientResultSetList));
  tmp->next = (ClientResultSetList *) NULL;
  tmp->resultSetId = strdup(name);
  tmp->server = server;
  tmp->numberOfRecords = 0;
  if (last)
    last->next = tmp;
  else
    client->resultsets = tmp;
  return tmp;
}

void
remove_ClientResultSet(Client *client, ClientResultSetList *resultset)
{
  ClientResultSetList *last = NULL, *tmp;
  for (tmp = client->resultsets; tmp; last = tmp, tmp = tmp->next)
    if (tmp == resultset)
      break;
  if (tmp == NULL)
  {
    LOG(facHigh, llevExceptions, "Tried to remove nonexisting resultset.");
    return;
  }
  LOG(facHigh, llevDebug, "Removed resultset %s.",tmp->resultSetId);
  if (last)
    last->next = resultset->next;
  else
    client->resultsets = resultset->next;
  if (tmp->resultSetId)
    free(tmp->resultSetId);
  free(tmp);
}

void free_resultsets(ClientResultSetList *resultset)
{
  LOG(facHigh, llevDebug, "Removed all resultsets.");
  if (resultset->next)
    free_resultsets(resultset->next);
  if (resultset->server)
    resultset->server->waiting_for_packet = False; /* A hack, I suppose... */
  if (resultset->resultSetId)
    free (resultset->resultSetId);
  free (resultset);
}

/*
 * Convert between Eapi Records and SR Records.
 * Also check for possible format conversions within the data of the record.
 */

octetString *
Record_CopyOctStr(Client *client, octetString *os)
{
  if (client->conv_from && client->conv_to)
  {
    octetString *ret = (octetString *) malloc(sizeof(octetString));
    LOG(facHigh, llevTrace, "Converting %s -> %s.",
	OID_Oid2name (client->conv_from->abstractSyntax),
	OID_Oid2name (client->conv_to->abstractSyntax));
    switch(Convert("MARC", client->conv_from->converter_name, client->conv_to->converter_name, os, &ret)) {
    case CV_NoConverterFound:
      LOG(facHigh, llevExceptions, "Found no matching converter.");
      ret->value = strdup("ERROR: No converter found.");
      ret->len = strlen(ret->value);
      return ret;
    case CV_ConverterNotInitialised:
      LOG(facHigh, llevExceptions, "Converter was not initialised.");
      ret->value = strdup("ERROR: Converter is not initialised.");
      ret->len = strlen(ret->value);
      return ret;
    case CV_ConversionError:
      LOG(facHigh, llevExceptions, "Conversion error.");
      ret->value = strdup("ERROR: Conversion error.");
      ret->len = strlen(ret->value);
      return ret;
    case CV_OK:
#if 0 /* Debug */
      {
        char *buf = (char *) malloc(ret->len + 1);
        (void) memcpy(buf, ret->value, ret->len);
        *(buf + ret->len) = '\0';
        LOG(facHigh, llevDebug, "Output after conversion:\n%s",buf);
        free(buf);
      }
#endif
      return ret;
    default:
      LOG(facHigh, llevExceptions, "Unknown converter error.");
      ret->value = strdup("ERROR: Unknown converter error.");
      ret->len = strlen(ret->value);
      return ret;
    }
  }
  return CopyOctStr(os);
}

octetString *
CopyOctStr(octetString *os)
{
  int nonullterm = 0;
  octetString *ret = (octetString *) malloc(sizeof(octetString));

  ret->len = os->len;
  if (os->len > 0 && *(os->value + os->len - 1))
  {
#if 0
    LOG(facHigh, llevTrace, "CopyOctStr: Non-nullterminated string.");
#endif
    nonullterm = 1;
  }
  ret->value = (char *) malloc(os->len + nonullterm);
  (void) memcpy(ret->value, os->value, os->len);
  if (nonullterm) /* Maybe 'len' should be increased by one */
    *( ret->value + ret->len ) = '\0';
  return ret;
}

DiagRec *
ConvertFromEapiDiagRec(EapiDiagRec *e_diag)
{
  DiagRec *diag = (DiagRec *) malloc(sizeof(DiagRec));
/*diag->diagnosticSetId = OID_str2Oid (e_diag->diagnosticSetId); */
  diag->diagnosticSetId = Oid_DIAGSET_BIB1;
  diag->condition = e_diag->condition;
  if (e_diag->addinfo)
    diag->addinfo = strdup(e_diag->addinfo);
  else
    diag->addinfo = strdup(""); /* Maybe SR is unable to encode NULL here? */
  return diag;
}

NamePlusRecord *
ConvertFromEapiNamePlusRecord(Client *client, EapiNamePlusRecord *e_rec)
{
  NamePlusRecord *rec = (NamePlusRecord *) malloc(sizeof(NamePlusRecord));
  rec->next = NULL;
  switch(e_rec->recKind) {
  case recEapiRecord:
    rec->nprKind = nprRecord;
    rec->u.databaseRecord = (EXTERN *) malloc(sizeof(EXTERN));
    rec->u.databaseRecord->externEncoding = extEncOctetString;
    rec->u.databaseRecord->octStr = Record_CopyOctStr(client, &e_rec->u.record.databaseRecord);
    rec->u.databaseRecord->directReference = OID_NULLPTR;
    rec->u.databaseRecord->indirectReference = (int *) NULL;
    rec->u.databaseRecord->dataValueDescriptor = (octetString *) NULL;
    if (e_rec->u.record.databaseName)
      rec->databaseName = strdup(e_rec->u.record.databaseName);
    else
      rec->databaseName = (char *) NULL;
    break;
  case recEapiSurrogateDiagRec:
    rec->nprKind = nprDiag;
    rec->u.surrogateDiagnostic =
      ConvertFromEapiDiagRec(e_rec->u.surrogateDiagnostic);
    rec->databaseName = (char *) NULL;
    break;
  }
  rec->next = NULL;
  return rec;
}

Records *
ConvertFromEapiRecords( Client *client, EapiRecords * e_rec)
{
  Records *rec = (Records *) malloc(sizeof(Records));
  switch(e_rec->recKind) {
  case recEapiNamePlusRecord:
    {
      NamePlusRecord *prev = NULL;
      EapiNamePlusRecord *tmp;

      rec->recKind = recNamePlusRecord;
      for (tmp = e_rec->u.databaseOrSurDiagnostics; tmp; tmp = tmp->next)
      {
        NamePlusRecord *npr = ConvertFromEapiNamePlusRecord(client, tmp);
        if (prev)
          prev->next = npr;
        else
          rec->u.databaseOrSurDiagnostics = npr;
        prev = npr;
      }
      break;
    }
  case recEapiNonSurrogateDiagRec:
    rec->recKind = recDiagRec;
    rec->u.nonSurrogateDiagnostic =
      ConvertFromEapiDiagRec(e_rec->u.nonSurrogateDiagnosticRecord);
    break;
  }
  return rec;
}

Query *
ConvertToEapiQuery (Query *q)
{
  octetString *os;
  Query *out;
  int not_nullterminated = 0;

  if (q == (Query *) NULL)
    return (Query *) NULL;

  out = (Query *) malloc(sizeof(Query));
  out->kind = q->kind;
  switch(q->kind)
  {
  case queryRpnQuery:
    out->q.rpnQuery = NULL;
    LOG(facHigh, llevExceptions, "RpnQuery not supported yet.");
    break;
  case queryPrivate:
    LOG(facHigh, llevExceptions, "Private query not supported yet.");
    break;
  case queryIso8777Query:
    if (*(q->q.iso8777Query->value + q->q.iso8777Query->len - 1))
      not_nullterminated = 1;
    os = (octetString *) malloc(sizeof(octetString));
    os->len = q->q.iso8777Query->len;
    os->value = (char *) malloc(os->len + not_nullterminated);
    (void) memcpy(os->value, q->q.iso8777Query->value, os->len);
    if (not_nullterminated) /* Add a trailing null-byte to help structcodec */
      * (os->value + os->len++) = '\0';
    out->q.iso8777Query = os;
  }
  return out;
}

/*
 * Check conversion for the specified client
 * Note: client->current_sserver and client->preferredRecordSyntax _must_ be
 * set when this function is called.
 */

void
CheckConversion(Client *client)
{
  SupportedSyntaxes *ss;
  ConvertInfo *ci_in, *ci_out;

  client->conv_from = (ConvertInfo *) NULL;
  client->conv_to = (ConvertInfo *) NULL;

  LOG(facHigh, llevTrace, "Check conversion: %s", OID_Oid2name (client->preferredRecordSyntax));

  /*
   * First some sanity check to avoid crashes.
   */
  if (client->current_server == NULL || client->current_server->connection == NULL || client->current_server->connection->database_server == NULL)
  {
    LOG(facHigh, llevExceptions, "Internal error in CheckConversion.");
    return;
  }
  /*
   * First check: does the database server support the requested
   * abstract syntax?  If so, we won't do anything.
   */
  for (ss = client->current_server->connection->database_server->supported_syntaxes; ss; ss = ss->next)
  {
    LOG(facHigh, llevTrace, "OID_cmp: %s %s", OID_Oid2name (client->preferredRecordSyntax), OID_Oid2name (ss->abstractSyntax));
    if (!OID_cmp (client->preferredRecordSyntax, ss->abstractSyntax))
      break;
  }
  if (ss != (SupportedSyntaxes *) NULL)
  {
    LOG(facHigh, llevTrace, "Check conversion: Found support.");
    return;
  }

  /*
   * No.  Then the second test:  does the highlevel server support the
   * preferred Record Syntax.  If not, we _can't_ do anything.
   * (In the future: generate a diagnostic about this!)
   */
  for (ci_out = first_convert_info; ci_out; ci_out = ci_out->next)
    if (!OID_cmp (ci_out->abstractSyntax, client->preferredRecordSyntax)) {
      LOG(facHigh, llevTrace, "Success OUT: Oid %s == %s",
	  OID_Oid2name (ci_out->abstractSyntax),
	  OID_Oid2name (client->preferredRecordSyntax));
      break;
    }

  if (ci_out == (ConvertInfo *) NULL)
  {
    LOG(facHigh, llevExceptions, "Don't understand the abstract syntax %s.",
	OID_Oid2name (client->preferredRecordSyntax));
    return;
  }
  /* No, then the third and last test:  does the highlevel server support
   * the default (the first in the config-file) record syntax which
   * the database server supplies.  If not, we still can't do anything.
   */
  if (!client->current_server->connection->database_server->supported_syntaxes)
  {
    LOG(facHigh, llevExceptions, "Server had no default abstract syntax: %s", client->current_server->connection->database_server->name);
    return;
  }
  for (ci_in = first_convert_info; ci_in; ci_in = ci_in->next) {
    Oid ssy = client->current_server->connection->database_server->supported_syntaxes->abstractSyntax;
    if (!OID_cmp (ci_in->abstractSyntax, ssy)) {
      LOG(facHigh, llevTrace, "Success IN: Oid %s == %s",
	  OID_Oid2name (ci_in->abstractSyntax), OID_Oid2name (ssy));
      break;
    }
  }
  if (ci_in == NULL)
  {
    LOG(facHigh, llevExceptions, "Don't understand the default format for server %s: %s.", client->current_server->connection->database_server->name, OID_Oid2name (client->current_server->connection->database_server->supported_syntaxes->abstractSyntax));
    return;
  }

  /*
   * Make ready for conversion:
   */
  LOG(facHigh, llevTrace, "Will convert between %s and %s.", OID_Oid2name (ci_in->abstractSyntax), OID_Oid2name (ci_out->abstractSyntax));
  client->conv_from = ci_in;
  client->conv_to = ci_out;
}

/*
 * Frees the EapiPacket structure and all which is allocated within it.
 */

void free_EapiPacket(EapiPacket *ep)
{
  free_struct((char *) ep, desc_EapiPacket);
}

