blob: 5ca9e0b9653e70a8a2bf18b7e50a3a6658cc68e4 [file] [log] [blame]
/**
* @file option.c PCP options
*
* Copyright (C) 2010 - 2016 Creytiv.com
*/
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_sa.h>
#include <re_pcp.h>
#include "pcp.h"
static void destructor(void *arg)
{
struct pcp_option *opt = arg;
list_unlink(&opt->le);
switch (opt->code) {
case PCP_OPTION_DESCRIPTION:
mem_deref(opt->u.description);
break;
default:
break;
}
}
int pcp_option_encode(struct mbuf *mb, enum pcp_option_code code,
const void *v)
{
const struct sa *sa = v;
const struct pcp_option_filter *filt = v;
size_t start, len;
int err = 0;
if (!mb)
return EINVAL;
mb->pos += 4;
start = mb->pos;
switch (code) {
case PCP_OPTION_THIRD_PARTY:
if (!sa)
return EINVAL;
err |= pcp_ipaddr_encode(mb, sa);
break;
case PCP_OPTION_PREFER_FAILURE:
/* no payload */
break;
case PCP_OPTION_FILTER:
if (!filt)
return EINVAL;
err |= mbuf_write_u8(mb, 0x00);
err |= mbuf_write_u8(mb, filt->prefix_length);
err |= mbuf_write_u16(mb, htons(sa_port(&filt->remote_peer)));
err |= pcp_ipaddr_encode(mb, &filt->remote_peer);
break;
case PCP_OPTION_DESCRIPTION:
if (!v)
return EINVAL;
err |= mbuf_write_str(mb, v);
break;
default:
(void)re_fprintf(stderr,
"pcp: unsupported option %d\n", code);
return EINVAL;
}
/* header */
len = mb->pos - start;
mb->pos = start - 4;
err |= mbuf_write_u8(mb, code);
err |= mbuf_write_u8(mb, 0x00);
err |= mbuf_write_u16(mb, htons(len));
mb->pos += len;
/* padding */
while ((mb->pos - start) & 0x03)
err |= mbuf_write_u8(mb, 0x00);
return err;
}
int pcp_option_decode(struct pcp_option **optp, struct mbuf *mb)
{
struct pcp_option *opt;
size_t start, len;
uint16_t port;
int err = 0;
if (!optp || !mb)
return EINVAL;
if (mbuf_get_left(mb) < 4)
return EBADMSG;
opt = mem_zalloc(sizeof(*opt), destructor);
if (!opt)
return ENOMEM;
opt->code = mbuf_read_u8(mb);
(void)mbuf_read_u8(mb);
len = ntohs(mbuf_read_u16(mb));
if (mbuf_get_left(mb) < len)
goto badmsg;
start = mb->pos;
switch (opt->code) {
case PCP_OPTION_THIRD_PARTY:
if (len < 16)
goto badmsg;
err = pcp_ipaddr_decode(mb, &opt->u.third_party);
break;
case PCP_OPTION_PREFER_FAILURE:
/* no payload */
break;
case PCP_OPTION_FILTER:
if (len < 20)
goto badmsg;
(void)mbuf_read_u8(mb);
opt->u.filter.prefix_length = mbuf_read_u8(mb);
port = ntohs(mbuf_read_u16(mb));
err = pcp_ipaddr_decode(mb, &opt->u.filter.remote_peer);
sa_set_port(&opt->u.filter.remote_peer, port);
break;
case PCP_OPTION_DESCRIPTION:
err = mbuf_strdup(mb, &opt->u.description, len);
break;
default:
mb->pos += len;
(void)re_printf("pcp: ignore option code %d (len=%zu)\n",
opt->code, len);
break;
}
if (err)
goto error;
/* padding */
while (((mb->pos - start) & 0x03) && mbuf_get_left(mb))
++mb->pos;
*optp = opt;
return 0;
badmsg:
err = EBADMSG;
error:
mem_deref(opt);
return err;
}
static const char *pcp_option_name(enum pcp_option_code code)
{
switch (code) {
case PCP_OPTION_THIRD_PARTY: return "THIRD_PARTY";
case PCP_OPTION_PREFER_FAILURE: return "PREFER_FAILURE";
case PCP_OPTION_FILTER: return "FILTER";
case PCP_OPTION_DESCRIPTION: return "DESCRIPTION";
default: return "?";
}
}
int pcp_option_print(struct re_printf *pf, const struct pcp_option *opt)
{
int err;
if (!opt)
return 0;
err = re_hprintf(pf, " %-25s", pcp_option_name(opt->code));
switch (opt->code) {
case PCP_OPTION_THIRD_PARTY:
err |= re_hprintf(pf, "address=%j",
&opt->u.third_party);
break;
case PCP_OPTION_PREFER_FAILURE:
break;
case PCP_OPTION_FILTER:
err |= re_hprintf(pf, "prefix_length=%u, remote_peer=%J",
opt->u.filter.prefix_length,
&opt->u.filter.remote_peer);
break;
case PCP_OPTION_DESCRIPTION:
err |= re_hprintf(pf, "'%s'", opt->u.description);
break;
default:
err |= re_hprintf(pf, "???");
break;
}
err |= re_hprintf(pf, "\n");
return err;
}