/*
  chronyd/chronyc - Programs for keeping computer clocks accurate.

 **********************************************************************
 * Copyright (C) Richard P. Curnow  1997-2002
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 **********************************************************************

  =======================================================================

  Routines to compute the expected length of a command or reply packet.
  These operate on the RAW NETWORK packets, from the point of view of
  integer endianness within the structures.

  */
#include "config.h"

#include "sysincl.h"

#include "util.h"
#include "pktlength.h"

/* ================================================== */

static int
command_unpadded_length(CMD_Request *r)
{
  int type;
  type = ntohs(r->command);
  if (type < 0 || type >= N_REQUEST_TYPES) {
    return 0;
  } else {
    switch (type) {
      case REQ_NULL:
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_ONLINE:
        return offsetof(CMD_Request, data.online.EOR);
      case REQ_OFFLINE:
        return offsetof(CMD_Request, data.offline.EOR);
      case REQ_BURST:
        return offsetof(CMD_Request, data.burst.EOR);
      case REQ_MODIFY_MINPOLL:
        return offsetof(CMD_Request, data.modify_minpoll.EOR);
      case REQ_MODIFY_MAXPOLL:
        return offsetof(CMD_Request, data.modify_maxpoll.EOR);
      case REQ_DUMP:
        return offsetof(CMD_Request, data.dump.EOR);
      case REQ_MODIFY_MAXDELAY:
        return offsetof(CMD_Request, data.modify_maxdelay.EOR);
      case REQ_MODIFY_MAXDELAYRATIO:
        return offsetof(CMD_Request, data.modify_maxdelayratio.EOR);
      case REQ_MODIFY_MAXDELAYDEVRATIO:
        return offsetof(CMD_Request, data.modify_maxdelaydevratio.EOR);
      case REQ_MODIFY_MAXUPDATESKEW:
        return offsetof(CMD_Request, data.modify_maxupdateskew.EOR);
      case REQ_MODIFY_MAKESTEP:
        return offsetof(CMD_Request, data.modify_makestep.EOR);
      case REQ_LOGON :
        return offsetof(CMD_Request, data.logon.EOR);
      case REQ_SETTIME :
        return offsetof(CMD_Request, data.settime.EOR);
      case REQ_LOCAL :
        return offsetof(CMD_Request, data.local.EOR);
      case REQ_MANUAL :
        return offsetof(CMD_Request, data.manual.EOR);
      case REQ_N_SOURCES :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_SOURCE_DATA :
        return offsetof(CMD_Request, data.source_data.EOR);
      case REQ_REKEY :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_ALLOW :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_ALLOWALL :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_DENY :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_DENYALL :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_CMDALLOW :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_CMDALLOWALL :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_CMDDENY :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_CMDDENYALL :
        return offsetof(CMD_Request, data.allow_deny.EOR);
      case REQ_ACCHECK :
        return offsetof(CMD_Request, data.ac_check.EOR);
      case REQ_CMDACCHECK :
        return offsetof(CMD_Request, data.ac_check.EOR);
      case REQ_ADD_SERVER :
        return offsetof(CMD_Request, data.ntp_source.EOR);
      case REQ_ADD_PEER :
        return offsetof(CMD_Request, data.ntp_source.EOR);
      case REQ_DEL_SOURCE :
        return offsetof(CMD_Request, data.del_source.EOR);
      case REQ_WRITERTC :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_DFREQ :
        return offsetof(CMD_Request, data.dfreq.EOR);
      case REQ_DOFFSET :
        return offsetof(CMD_Request, data.doffset.EOR);
      case REQ_TRACKING :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_SOURCESTATS :
        return offsetof(CMD_Request, data.sourcestats.EOR);
      case REQ_RTCREPORT :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_TRIMRTC :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_CYCLELOGS :
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_SUBNETS_ACCESSED :
      case REQ_CLIENT_ACCESSES:
        /* No longer supported */
        return 0;
      case REQ_CLIENT_ACCESSES_BY_INDEX:
        return offsetof(CMD_Request, data.client_accesses_by_index.EOR);
      case REQ_MANUAL_LIST:
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_MANUAL_DELETE:
        return offsetof(CMD_Request, data.manual_delete.EOR);
      case REQ_MAKESTEP:
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_ACTIVITY:
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_RESELECT:
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_RESELECTDISTANCE:
        return offsetof(CMD_Request, data.reselect_distance.EOR);
      case REQ_MODIFY_MINSTRATUM:
        return offsetof(CMD_Request, data.modify_minstratum.EOR);
      case REQ_MODIFY_POLLTARGET:
        return offsetof(CMD_Request, data.modify_polltarget.EOR);
      case REQ_SMOOTHING:
        return offsetof(CMD_Request, data.null.EOR);
      case REQ_SMOOTHTIME:
        return offsetof(CMD_Request, data.smoothtime.EOR);
      default:
        /* If we fall through the switch, it most likely means we've forgotten to implement a new case */
        assert(0);
    }
  }

  /* Catch-all case */
  return 0;

}


/* ================================================== */

int
PKL_CommandLength(CMD_Request *r)
{
  int command_length;

  command_length = command_unpadded_length(r);
  if (!command_length)
    return 0;

  return command_length + PKL_CommandPaddingLength(r);
}

/* ================================================== */

#define PADDING_LENGTH_(request_length, reply_length) \
  ((request_length) < (reply_length) ? (reply_length) - (request_length) : 0)

#define PADDING_LENGTH(request_data, reply_data) \
  PADDING_LENGTH_(offsetof(CMD_Request, request_data), offsetof(CMD_Reply, reply_data))

int
PKL_CommandPaddingLength(CMD_Request *r)
{
  int type;

  if (r->version < PROTO_VERSION_PADDING)
    return 0;

  type = ntohs(r->command);

  if (type < 0 || type >= N_REQUEST_TYPES)
    return 0;

  switch (type) {
    case REQ_NULL:
      return PADDING_LENGTH(data, data.null.EOR);
    case REQ_ONLINE:
      return PADDING_LENGTH(data.online.EOR, data.null.EOR);
    case REQ_OFFLINE:
      return PADDING_LENGTH(data.offline.EOR, data.null.EOR);
    case REQ_BURST:
      return PADDING_LENGTH(data.burst.EOR, data.null.EOR);
    case REQ_MODIFY_MINPOLL:
      return PADDING_LENGTH(data.modify_minpoll.EOR, data.null.EOR);
    case REQ_MODIFY_MAXPOLL:
      return PADDING_LENGTH(data.modify_maxpoll.EOR, data.null.EOR);
    case REQ_DUMP:
      return PADDING_LENGTH(data.dump.EOR, data.null.EOR);
    case REQ_MODIFY_MAXDELAY:
      return PADDING_LENGTH(data.modify_maxdelay.EOR, data.null.EOR);
    case REQ_MODIFY_MAXDELAYRATIO:
      return PADDING_LENGTH(data.modify_maxdelayratio.EOR, data.null.EOR);
    case REQ_MODIFY_MAXDELAYDEVRATIO:
      return PADDING_LENGTH(data.modify_maxdelaydevratio.EOR, data.null.EOR);
    case REQ_MODIFY_MAXUPDATESKEW:
      return PADDING_LENGTH(data.modify_maxupdateskew.EOR, data.null.EOR);
    case REQ_MODIFY_MAKESTEP:
      return PADDING_LENGTH(data.modify_makestep.EOR, data.null.EOR);
    case REQ_LOGON:
      return PADDING_LENGTH(data.logon.EOR, data.null.EOR);
    case REQ_SETTIME:
      return PADDING_LENGTH(data.settime.EOR, data.manual_timestamp.EOR);
    case REQ_LOCAL:
      return PADDING_LENGTH(data.local.EOR, data.null.EOR);
    case REQ_MANUAL:
      return PADDING_LENGTH(data.manual.EOR, data.null.EOR);
    case REQ_N_SOURCES:
      return PADDING_LENGTH(data.null.EOR, data.n_sources.EOR);
    case REQ_SOURCE_DATA:
      return PADDING_LENGTH(data.source_data.EOR, data.source_data.EOR);
    case REQ_REKEY:
      return PADDING_LENGTH(data.null.EOR, data.null.EOR);
    case REQ_ALLOW:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_ALLOWALL:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_DENY:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_DENYALL:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_CMDALLOW:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_CMDALLOWALL:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_CMDDENY:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_CMDDENYALL:
      return PADDING_LENGTH(data.allow_deny.EOR, data.null.EOR);
    case REQ_ACCHECK:
      return PADDING_LENGTH(data.ac_check.EOR, data.null.EOR);
    case REQ_CMDACCHECK:
      return PADDING_LENGTH(data.ac_check.EOR, data.null.EOR);
    case REQ_ADD_SERVER:
      return PADDING_LENGTH(data.ntp_source.EOR, data.null.EOR);
    case REQ_ADD_PEER:
      return PADDING_LENGTH(data.ntp_source.EOR, data.null.EOR);
    case REQ_DEL_SOURCE:
      return PADDING_LENGTH(data.del_source.EOR, data.null.EOR);
    case REQ_WRITERTC:
      return PADDING_LENGTH(data.null.EOR, data.null.EOR);
    case REQ_DFREQ:
      return PADDING_LENGTH(data.dfreq.EOR, data.null.EOR);
    case REQ_DOFFSET:
      return PADDING_LENGTH(data.doffset.EOR, data.null.EOR);
    case REQ_TRACKING:
      return PADDING_LENGTH(data.null.EOR, data.tracking.EOR);
    case REQ_SOURCESTATS:
      return PADDING_LENGTH(data.sourcestats.EOR, data.sourcestats.EOR);
    case REQ_RTCREPORT:
      return PADDING_LENGTH(data.null.EOR, data.rtc.EOR);
    case REQ_TRIMRTC:
      return PADDING_LENGTH(data.null.EOR, data.null.EOR);
    case REQ_CYCLELOGS:
      return PADDING_LENGTH(data.null.EOR, data.null.EOR);
    case REQ_SUBNETS_ACCESSED:
    case REQ_CLIENT_ACCESSES:
      /* No longer supported */
      return 0;
    case REQ_CLIENT_ACCESSES_BY_INDEX:
      return PADDING_LENGTH(data.client_accesses_by_index.EOR, data.client_accesses_by_index.EOR);
    case REQ_MANUAL_LIST:
      return PADDING_LENGTH(data.null.EOR, data.manual_list.EOR);
    case REQ_MANUAL_DELETE:
      return PADDING_LENGTH(data.manual_delete.EOR, data.null.EOR);
    case REQ_MAKESTEP:
      return PADDING_LENGTH(data.null.EOR, data.null.EOR);
    case REQ_ACTIVITY:
      return PADDING_LENGTH(data.null.EOR, data.activity.EOR);
    case REQ_RESELECT:
      return PADDING_LENGTH(data.null.EOR, data.null.EOR);
    case REQ_RESELECTDISTANCE:
      return PADDING_LENGTH(data.reselect_distance.EOR, data.null.EOR);
    case REQ_MODIFY_MINSTRATUM:
      return PADDING_LENGTH(data.modify_minstratum.EOR, data.null.EOR);
    case REQ_MODIFY_POLLTARGET:
      return PADDING_LENGTH(data.modify_polltarget.EOR, data.null.EOR);
    case REQ_SMOOTHING:
      return PADDING_LENGTH(data.null.EOR, data.smoothing.EOR);
    case REQ_SMOOTHTIME:
      return PADDING_LENGTH(data.smoothtime.EOR, data.null.EOR);
    default:
      /* If we fall through the switch, it most likely means we've forgotten to implement a new case */
      assert(0);
      return 0;
  }
}

/* ================================================== */

int
PKL_ReplyLength(CMD_Reply *r)
{
  int type;
  type = ntohs(r->reply);
  /* Note that reply type codes start from 1, not 0 */
  if (type < 1 || type >= N_REPLY_TYPES) {
    return 0;
  } else {
    switch (type) {
      case RPY_NULL:
        return offsetof(CMD_Reply, data.null.EOR);
      case RPY_N_SOURCES:
        return offsetof(CMD_Reply, data.n_sources.EOR);
      case RPY_SOURCE_DATA:
        return offsetof(CMD_Reply, data.source_data.EOR);
      case RPY_MANUAL_TIMESTAMP:
        return offsetof(CMD_Reply, data.manual_timestamp.EOR);
      case RPY_TRACKING:
        return offsetof(CMD_Reply, data.tracking.EOR);
      case RPY_SOURCESTATS:
        return offsetof(CMD_Reply, data.sourcestats.EOR);
      case RPY_RTC:
        return offsetof(CMD_Reply, data.rtc.EOR);
      case RPY_SUBNETS_ACCESSED :
      case RPY_CLIENT_ACCESSES:
        /* No longer supported */
        return 0;
      case RPY_CLIENT_ACCESSES_BY_INDEX:
        {
          unsigned long nc = ntohl(r->data.client_accesses_by_index.n_clients);
          if (r->status == htons(STT_SUCCESS)) {
            if (nc > MAX_CLIENT_ACCESSES)
              return 0;
            return (offsetof(CMD_Reply, data.client_accesses_by_index.clients) +
                    nc * sizeof(RPY_ClientAccesses_Client));
          } else {
            return offsetof(CMD_Reply, data);
          }
        }
      case RPY_MANUAL_LIST:
        {
          unsigned long ns = ntohl(r->data.manual_list.n_samples);
          if (ns > MAX_MANUAL_LIST_SAMPLES)
            return 0;
          if (r->status == htons(STT_SUCCESS)) {
            return (offsetof(CMD_Reply, data.manual_list.samples) +
                    ns * sizeof(RPY_ManualListSample));
          } else {
            return offsetof(CMD_Reply, data);
          }
        }
      case RPY_ACTIVITY:
        return offsetof(CMD_Reply, data.activity.EOR);
      case RPY_SMOOTHING:
        return offsetof(CMD_Reply, data.smoothing.EOR);
      default:
        assert(0);
    }
  }

  return 0;
}

/* ================================================== */

