/* Copyright 2011 PADL Software Pty Ltd. All rights reserved.
   See the file COPYING for licensing information.  */

#if defined HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

#include <radsec/radsec.h>
#include <radius/client.h>

#define RS_ERR(err) ((err) < 0 ? -err : RSE_OK)

void
rs_avp_free (rs_avp **vps)
{
  nr_vp_free (vps);
}

size_t
rs_avp_length (rs_const_avp *vp)
{
  assert (vp != NULL);
  return vp->length;
}

rs_attr_type_t
rs_avp_typeof (rs_const_avp *vp)
{
  return vp ? vp->da->type : RS_TYPE_INVALID;
}

void
rs_avp_attrid (rs_const_avp *vp,
	       unsigned int *attr,
	       unsigned int *vendor)
{
  *attr = vp->da->attr;
  *vendor = vp->da->vendor;
}

const char *
rs_avp_name (rs_const_avp *vp)
{
  return vp ? vp->da->name : NULL;
}

void
rs_avp_append (rs_avp **head, rs_avp *tail)
{
  nr_vps_append (head, tail);
}

rs_avp *
rs_avp_find (rs_avp *vp, unsigned int attr, unsigned int vendor)
{
  if (vp == NULL)
    return NULL;

  return nr_vps_find (vp, attr, vendor);
}

rs_const_avp *
rs_avp_find_const (rs_const_avp *vp,
                   unsigned int attr, unsigned int vendor)
{
  if (vp == NULL)
    return NULL;

  return nr_vps_find ((rs_avp *)vp, attr, vendor);
}

rs_avp *
rs_avp_alloc (unsigned int attr, unsigned int vendor)
{
  const DICT_ATTR *da;
  VALUE_PAIR *vp;

  da = nr_dict_attr_byvalue (attr, vendor);
  if (da == NULL) {
    vp = nr_vp_alloc_raw (attr, vendor);
  } else {
    vp = nr_vp_alloc (da);
  }

  if (vp == NULL)
    return NULL;

  return vp;
}

rs_avp *
rs_avp_dup (rs_const_avp *vp)
{
  rs_avp *vp2;

  vp2 = nr_vp_alloc (vp->da);
  if (vp2 == NULL)
    return NULL;

  vp2->length = vp->length;
  vp2->tag = vp->tag;
  vp2->next = NULL;

#ifdef RS_TYPE_TLV
  if (rs_avp_is_tlv (vp)) {
    vp2->vp_tlv = malloc (vp->length);
    if (vp2->vp_tlv == NULL) {
      rs_avp_free (vp2);
      return NULL;
    }
    memcpy (vp2->vp_tlv, vp->vp_tlv, vp->length);
    return vp2;
  }
#endif

  memcpy (vp2->vp_strvalue, vp->vp_strvalue, vp->length);
  if (rs_avp_is_string (vp))
    vp2->vp_strvalue[vp->length] = '\0';

  return vp2;
}

rs_avp *
rs_avp_next (rs_avp *avp)
{
  return avp ? avp->next : NULL;
}

rs_const_avp *
rs_avp_next_const (rs_const_avp *avp)
{
  return avp ? avp->next : NULL;
}

int
rs_avp_delete (rs_avp **first,
               unsigned int attr, unsigned int vendor)
{
  int found = 0;
  rs_avp **p;

  for (p = first; *p != NULL; p++) {
    if ((*p)->da->attr == attr &&
        (*p)->da->vendor == vendor) {
      rs_avp *next = (*p)->next;

      (*p)->next = NULL;
      rs_avp_free (p);

      *p = next;
      found++;
    }
  }

  return found ? RSE_OK : RSE_ATTR_UNKNOWN;
}

const char *
rs_avp_string_value (rs_const_avp *vp)
{
  if (!rs_avp_is_string (vp))
    return NULL;

  return vp->vp_strvalue;
}

int
rs_avp_string_set (rs_avp *vp, const char *str)
{
  int err;

  if (vp == NULL)
    return RSE_INVAL;
  if (!rs_avp_is_string (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, str, strlen (str));
  return RS_ERR(err);
}

uint32_t
rs_avp_integer_value (rs_const_avp *vp)
{
  if (!rs_avp_is_integer (vp))
    return 0;
  return vp->vp_integer;
}

int
rs_avp_integer_set (rs_avp *vp, uint32_t val)
{
  int err;

  if (vp == NULL)
    return RSE_INVAL;
  if (!rs_avp_is_integer (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, &val, sizeof (val));
  return RS_ERR(err);
}

uint32_t
rs_avp_ipaddr_value (rs_const_avp *vp)
{
  if (!rs_avp_is_ipaddr (vp))
    return 0;
  return vp->vp_ipaddr;
}

int
rs_avp_ipaddr_set (rs_avp *vp, struct in_addr in)
{
  int err;

  if (vp == NULL)
    return RSE_INVAL;
  if (!rs_avp_is_ipaddr (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, &in, sizeof (in));
  return RS_ERR(err);
}

time_t
rs_avp_date_value (rs_const_avp *vp)
{
  if (!rs_avp_is_date (vp))
    return 0;
  return vp->vp_date;
}

int
rs_avp_date_set (rs_avp *vp, time_t date)
{
  uint32_t date32;
  int err;

  if (vp == NULL)
    return RSE_INVAL;
  if (!rs_avp_is_date (vp))
    return RSE_ATTR_INVALID;
  if (date > 0xFFFFFFFF)
    return RSE_ATTR_INVALID;

  date32 = (uint32_t)date;
  err = nr_vp_set_data (vp, &date32, sizeof (date32));

  return RS_ERR(err);
}

const unsigned char *
rs_avp_octets_value_const_ptr (rs_const_avp *vp)
{
  return rs_avp_octets_value_ptr ((rs_avp *)vp);
}

unsigned char *
rs_avp_octets_value_ptr (rs_avp *vp)
{
  if (vp == NULL)
    return NULL;

#ifdef RS_TYPE_TLV
  if (rs_avp_is_tlv (vp))
    return vp->vp_tlv;
#endif

  return vp->vp_octets;
}

int
rs_avp_octets_value_byref (rs_avp *vp,
			   unsigned char **p,
			   size_t *len)
{
  if (vp == NULL)
    return RSE_INVAL;

  *len = vp->length;
  *p = (unsigned char *)rs_avp_octets_value_ptr (vp);

  return RSE_OK;
}

int
rs_avp_octets_value (rs_const_avp *vp,
		     unsigned char *buf,
		     size_t *len)
{
  if (vp == NULL)
    return RSE_INVAL;

  if (vp->length > *len) {
    *len = vp->length;
    return RSE_ATTR_TOO_SMALL;
  }

  *len = vp->length;

#ifdef RS_TYPE_TLV
  if (rs_avp_is_tlv (vp))
    memcpy (buf, vp->vp_tlv, vp->length);
  else
#endif
    memcpy (buf, vp->vp_octets, vp->length);

  return RSE_OK;
}

int
rs_avp_fragmented_value (rs_const_avp *vps,
		         unsigned char *buf,
		         size_t *len)
{
  size_t total_len = 0;
  unsigned char *p;
  rs_const_avp *vp;

  if (vps == NULL)
    return RSE_INVAL;

  if (!rs_avp_is_octets (vps) &&
      !rs_avp_is_string (vps))
    return RSE_ATTR_INVALID;

  for (vp = vps;
       vp != NULL;
       vp = rs_avp_find_const (vp->next, vp->da->attr, vp->da->vendor))
    total_len += vp->length;

  if (*len < total_len) {
    *len = total_len;
    return RSE_ATTR_TOO_SMALL;
  }

  for (vp = vps, p = buf;
       vp != NULL;
       vp = rs_avp_find_const (vp->next, vp->da->attr, vp->da->vendor)) {
    memcpy (p, vp->vp_octets, vp->length);
    p += vp->length;
  }

  *len = total_len;

  return RSE_OK;
}

int
rs_avp_octets_set (rs_avp *vp,
		   const unsigned char *buf,
		   size_t len)
{
  int err;

  if (!rs_avp_is_octets (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, buf, len);

  return RS_ERR(err);
}

int
rs_avp_ifid_value (rs_const_avp *vp, uint8_t val[8])
{
  if (!rs_avp_is_ifid (vp))
    return RSE_ATTR_INVALID;

  memcpy (val, vp->vp_ifid, 8);

  return RSE_OK;
}

int
rs_avp_ifid_set (rs_avp *vp, const uint8_t val[8])
{
  int err;

  if (!rs_avp_is_ifid (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, val, 8);
  return RS_ERR(err);
}

uint8_t
rs_avp_byte_value (rs_const_avp *vp)
{
  if (!rs_avp_is_byte (vp))
    return 0;
  return vp->vp_integer;
}

int
rs_avp_byte_set (rs_avp *vp, uint8_t val)
{
  int err;

  if (!rs_avp_is_byte (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, &val, sizeof (val));
  return RS_ERR(err);
}

uint16_t
rs_avp_short_value (rs_const_avp *vp)
{
  if (!rs_avp_is_short (vp))
    return 0;
  return vp->vp_integer;
}

int
rs_avp_short_set (rs_avp *vp, uint16_t val)
{
  int err;

  if (!rs_avp_is_short (vp))
    return RSE_ATTR_INVALID;

  err = nr_vp_set_data (vp, &val, sizeof (val));
  return RS_ERR(err);
}

int
rs_attr_find (const char *name,
              unsigned int *attr,
              unsigned int *vendor)
{
  const DICT_ATTR *da;

  da = nr_dict_attr_byname (name);
  if (da == NULL)
    return RSE_ATTR_UNKNOWN;

  *attr = da->attr;
  *vendor = da->vendor;

  return RSE_OK;
}

size_t
rs_avp_display_value (rs_const_avp *vp,
                      char *buffer,
                      size_t buflen)
{
  return nr_vp_snprintf_value (buffer, buflen, vp);
}