[BACK]Return to ax.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / snmpd

File: [local] / src / usr.sbin / snmpd / ax.c (download)

Revision 1.6, Tue Feb 20 12:51:10 2024 UTC (3 months, 2 weeks ago) by martijn
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.5: +2 -3 lines

Remove some now unused variables. Somehow missed in previous commit.

/*	$OpenBSD: ax.c,v 1.6 2024/02/20 12:51:10 martijn Exp $ */
/*
 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/socket.h>

#include <arpa/inet.h>

#include <ctype.h>
#include <endian.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ax.h"

#define AX_PDU_HEADER 20

static int ax_pdu_need(struct ax *, size_t);
static int ax_pdu_header(struct ax *,
    enum ax_pdu_type, uint8_t, uint32_t, uint32_t, uint32_t,
    struct ax_ostring *);
static uint32_t ax_pdu_queue(struct ax *);
static int ax_pdu_add_uint16(struct ax *, uint16_t);
static int ax_pdu_add_uint32(struct ax *, uint32_t);
static int ax_pdu_add_uint64(struct ax *, uint64_t);
static int ax_pdu_add_oid(struct ax *, struct ax_oid *);
static int ax_pdu_add_str(struct ax *, struct ax_ostring *);
static int ax_pdu_add_varbindlist(struct ax *, struct ax_varbind *,
    size_t);
static int ax_pdu_add_searchrange(struct ax *, struct ax_searchrange *);
static uint16_t ax_pdutoh16(struct ax_pdu_header *, uint8_t *);
static uint32_t ax_pdutoh32(struct ax_pdu_header *, uint8_t *);
static uint64_t ax_pdutoh64(struct ax_pdu_header *, uint8_t *);
static ssize_t ax_pdutooid(struct ax_pdu_header *, struct ax_oid *,
    uint8_t *, size_t);
static ssize_t ax_pdutoostring(struct ax_pdu_header *,
    struct ax_ostring *, uint8_t *, size_t);
static ssize_t ax_pdutovarbind(struct ax_pdu_header *,
    struct ax_varbind *, uint8_t *, size_t);

struct ax *
ax_new(int fd)
{
	struct ax *ax;

	if (fd == -1) {
		errno = EINVAL;
		return NULL;
	}

	if ((ax = calloc(1, sizeof(*ax))) == NULL)
		return NULL;
	ax->ax_fd = fd;
	ax->ax_rbsize = 512;
	if ((ax->ax_rbuf = malloc(ax->ax_rbsize)) == NULL)
		goto fail;
	ax->ax_byteorder = AX_BYTE_ORDER_NATIVE;

	return ax;

fail:
	free(ax);
	return NULL;
}

void
ax_free(struct ax *ax)
{
	if (ax == NULL)
		return;
	close(ax->ax_fd);
	free(ax->ax_rbuf);
	free(ax->ax_wbuf);
	free(ax);
}

struct ax_pdu *
ax_recv(struct ax *ax)
{
	struct ax_pdu *pdu;
	struct ax_pdu_header header;
	struct ax_pdu_response *response;
	struct ax_varbind *varbind;
	struct ax_pdu_searchrangelist *srl = NULL;
	struct ax_pdu_varbindlist *vbl;
	struct ax_searchrange *sr;
	size_t rbsize, rawlen;
	ssize_t nread;
	uint8_t *u8;
	uint8_t *rbuf;

	/* Only read a single packet at a time to make sure libevent triggers */
	if (ax->ax_rblen < AX_PDU_HEADER) {
		if ((nread = read(ax->ax_fd, ax->ax_rbuf + ax->ax_rblen,
		    AX_PDU_HEADER - ax->ax_rblen)) == 0) {
			errno = ECONNRESET;
			return NULL;
		}
		if (nread == -1)
			return NULL;
		ax->ax_rblen += nread;
		if (ax->ax_rblen < AX_PDU_HEADER) {
			errno = EAGAIN;
			return NULL;
		}
	}
	u8 = ax->ax_rbuf;
	header.aph_version = *u8++;
	header.aph_type = *u8++;
	header.aph_flags = *u8++;
	u8++;
	header.aph_sessionid = ax_pdutoh32(&header, u8);
	u8 += 4;
	header.aph_transactionid = ax_pdutoh32(&header, u8);
	u8 += 4;
	header.aph_packetid = ax_pdutoh32(&header, u8);
	u8 += 4;
	header.aph_plength = ax_pdutoh32(&header, u8);

	if (header.aph_version != 1) {
		errno = EPROTO;
		return NULL;
	}
	if (ax->ax_rblen < AX_PDU_HEADER + header.aph_plength) {
		if (AX_PDU_HEADER + header.aph_plength > ax->ax_rbsize) {
			rbsize = (((AX_PDU_HEADER + header.aph_plength)
			    / 512) + 1) * 512;
			if ((rbuf = recallocarray(ax->ax_rbuf, ax->ax_rbsize,
			    rbsize, sizeof(*rbuf))) == NULL)
				return NULL;
			ax->ax_rbsize = rbsize;
			ax->ax_rbuf = rbuf;
		}
		nread = read(ax->ax_fd, ax->ax_rbuf + ax->ax_rblen,
		    header.aph_plength - (ax->ax_rblen - AX_PDU_HEADER));
		if (nread == 0)
			errno = ECONNRESET;
		if (nread <= 0)
			return NULL;
		ax->ax_rblen += nread;
		if (ax->ax_rblen < AX_PDU_HEADER + header.aph_plength) {
			errno = EAGAIN;
			return NULL;
		}
	}

	if ((pdu = calloc(1, sizeof(*pdu))) == NULL)
		return NULL;

	memcpy(&(pdu->ap_header), &header, sizeof(header));

#if defined(AX_DEBUG) && defined(AX_DEBUG_VERBOSE)
	{
		char chars[4];
		int print = 1;

		fprintf(stderr, "received packet:\n");
		for (i = 0; i < pdu->ap_header.aph_plength + AX_PDU_HEADER;
		    i++) {
			fprintf(stderr, "%02hhx ", ax->ax_rbuf[i]);
			chars[i % 4] = ax->ax_rbuf[i];
			if (!isprint(ax->ax_rbuf[i]))
				print = 0;
			if (i % 4 == 3) {
				if (print)
					fprintf(stderr, "%.4s", chars);
				fprintf(stderr, "\n");
				print = 1;
			}
		}
	}
#endif

	u8 = (ax->ax_rbuf) + AX_PDU_HEADER;
	rawlen = pdu->ap_header.aph_plength;
	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NON_DEFAULT_CONTEXT) {
		nread = ax_pdutoostring(&header, &(pdu->ap_context), u8,
		    rawlen);
		if (nread == -1)
			goto fail;
		rawlen -= nread;
		u8 += nread;
	}

	switch (pdu->ap_header.aph_type) {
	case AX_PDU_TYPE_OPEN:
		if (rawlen < 12) {
			errno = EPROTO;
			goto fail;
		}
		pdu->ap_payload.ap_open.ap_timeout = *u8;
		rawlen -= 4;
		u8 += 4;
		if ((nread = ax_pdutooid(&header,
		    &(pdu->ap_payload.ap_open.ap_oid), u8, rawlen)) == -1)
			goto fail;
		rawlen -= nread;
		u8 += nread;
		if ((nread = ax_pdutoostring(&header,
		    &(pdu->ap_payload.ap_open.ap_descr), u8, rawlen)) == -1)
			goto fail;
		if (rawlen - nread != 0) {
			errno = EPROTO;
			goto fail;
		}
		break;
	case AX_PDU_TYPE_CLOSE:
		if (rawlen != 4) {
			errno = EPROTO;
			goto fail;
		}
		if (u8[0] != AX_CLOSE_OTHER &&
		    u8[0] != AX_CLOSEN_PARSEERROR &&
		    u8[0] != AX_CLOSE_PROTOCOLERROR &&
		    u8[0] != AX_CLOSE_TIMEOUTS &&
		    u8[0] != AX_CLOSE_SHUTDOWN &&
		    u8[0] != AX_CLOSE_BYMANAGER) {
			errno = EPROTO;
			goto fail;
		}
		pdu->ap_payload.ap_close.ap_reason = u8[0];
		break;
	case AX_PDU_TYPE_REGISTER:
		if (rawlen < 8) {
			errno = EPROTO;
			goto fail;
		}
		pdu->ap_payload.ap_register.ap_timeout = *u8++;
		pdu->ap_payload.ap_register.ap_priority = *u8++;
		pdu->ap_payload.ap_register.ap_range_subid = *u8++;
		u8++;
		rawlen -= 4;
		if ((nread = ax_pdutooid(&header,
		    &(pdu->ap_payload.ap_register.ap_subtree),
		    u8, rawlen)) == -1)
			goto fail;
		rawlen -= nread;
		u8 += nread;
		if (pdu->ap_payload.ap_register.ap_range_subid) {
			if (rawlen != 4) {
				errno = EPROTO;
				goto fail;
			}
			pdu->ap_payload.ap_register.ap_upper_bound =
			    ax_pdutoh32(&header, u8);
			rawlen -= 4;
		}
		if (rawlen != 0) {
			errno = EPROTO;
			goto fail;
		}
		break;
	case AX_PDU_TYPE_UNREGISTER:
		if (rawlen < 8) {
			errno = EPROTO;
			goto fail;
		}
		u8++;
		pdu->ap_payload.ap_unregister.ap_priority = *u8++;
		pdu->ap_payload.ap_unregister.ap_range_subid = *u8++;
		u8++;
		rawlen -= 4;
		if ((nread = ax_pdutooid(&header,
		    &(pdu->ap_payload.ap_unregister.ap_subtree),
		    u8, rawlen)) == -1)
			goto fail;
		rawlen -= nread;
		u8 += nread;
		if (pdu->ap_payload.ap_unregister.ap_range_subid) {
			if (rawlen != 4) {
				errno = EPROTO;
				goto fail;
			}
			pdu->ap_payload.ap_unregister.ap_upper_bound =
			    ax_pdutoh32(&header, u8);
			rawlen -= 4;
		}
		if (rawlen != 0) {
			errno = EPROTO;
			goto fail;
		}
		break;
	case AX_PDU_TYPE_GETBULK:
		if (rawlen < 4) {
			errno = EPROTO;
			goto fail;
		}
		pdu->ap_payload.ap_getbulk.ap_nonrep =
		    ax_pdutoh16(&header, u8);
		u8 += 2;
		pdu->ap_payload.ap_getbulk.ap_maxrep =
		    ax_pdutoh16(&header, u8);
		u8 += 2;
		srl = &(pdu->ap_payload.ap_getbulk.ap_srl);
		rawlen -= 4;
		/* FALLTHROUGH */
	case AX_PDU_TYPE_GET:
	case AX_PDU_TYPE_GETNEXT:
		if (pdu->ap_header.aph_type != AX_PDU_TYPE_GETBULK)
			srl = &(pdu->ap_payload.ap_srl);
		while (rawlen > 0 ) {
			srl->ap_nsr++;
			sr = reallocarray(srl->ap_sr, srl->ap_nsr, sizeof(*sr));
			if (sr == NULL)
				goto fail;
			srl->ap_sr = sr;
			sr += (srl->ap_nsr - 1);
			if ((nread = ax_pdutooid(&header, &(sr->asr_start),
			    u8, rawlen)) == -1)
				goto fail;
			rawlen -= nread;
			u8 += nread;
			if ((nread = ax_pdutooid(&header, &(sr->asr_stop),
			    u8, rawlen)) == -1)
				goto fail;
			rawlen -= nread;
			u8 += nread;
		}
		break;
	case AX_PDU_TYPE_TESTSET:
	case AX_PDU_TYPE_INDEXALLOCATE:
	case AX_PDU_TYPE_INDEXDEALLOCATE:
	case AX_PDU_TYPE_NOTIFY:
		vbl = &(pdu->ap_payload.ap_vbl);
		while (rawlen > 0) {
			varbind = recallocarray(vbl->ap_varbind,
			    vbl->ap_nvarbind, vbl->ap_nvarbind + 1,
			    sizeof(*(vbl->ap_varbind)));
			if (varbind == NULL)
				goto fail;
			vbl->ap_varbind = varbind;
			nread = ax_pdutovarbind(&header,
			    &(vbl->ap_varbind[vbl->ap_nvarbind]), u8, rawlen);
			if (nread == -1)
				goto fail;
			vbl->ap_nvarbind++;
			u8 += nread;
			rawlen -= nread;
		}
		break;
	case AX_PDU_TYPE_COMMITSET:
	case AX_PDU_TYPE_UNDOSET:
	case AX_PDU_TYPE_CLEANUPSET:
	case AX_PDU_TYPE_PING:
		if (rawlen != 0) {
			errno = EPROTO;
			goto fail;
		}
		break;
	case AX_PDU_TYPE_ADDAGENTCAPS:
		nread = ax_pdutooid(&header,
		    &(pdu->ap_payload.ap_addagentcaps.ap_oid), u8, rawlen);
		if (nread == -1)
			goto fail;
		rawlen -= nread;
		u8 += nread;
		nread = ax_pdutoostring(&header,
		    &(pdu->ap_payload.ap_addagentcaps.ap_descr), u8, rawlen);
		if (nread == -1)
			goto fail;
		if (rawlen - nread != 0) {
			errno = EPROTO;
			goto fail;
		}
		break;
	case AX_PDU_TYPE_REMOVEAGENTCAPS:
		nread = ax_pdutooid(&header,
		    &(pdu->ap_payload.ap_removeagentcaps.ap_oid), u8, rawlen);
		if (nread == -1)
			goto fail;
		if (rawlen - nread != 0) {
			errno = EPROTO;
			goto fail;
		}
		break;
	case AX_PDU_TYPE_RESPONSE:
		if (rawlen < 8) {
			errno = EPROTO;
			goto fail;
		}
		response = &(pdu->ap_payload.ap_response);
		response->ap_uptime = ax_pdutoh32(&header, u8);
		u8 += 4;
		response->ap_error = ax_pdutoh16(&header, u8);
		u8 += 2;
		response->ap_index = ax_pdutoh16(&header, u8);
		u8 += 2;
		rawlen -= 8;
		while (rawlen > 0) {
			varbind = recallocarray(response->ap_varbindlist,
			    response->ap_nvarbind, response->ap_nvarbind + 1,
			    sizeof(*(response->ap_varbindlist)));
			if (varbind == NULL)
				goto fail;
			response->ap_varbindlist = varbind;
			nread = ax_pdutovarbind(&header,
			    &(response->ap_varbindlist[response->ap_nvarbind]),
			    u8, rawlen);
			if (nread == -1)
				goto fail;
			response->ap_nvarbind++;
			u8 += nread;
			rawlen -= nread;
		}
		break;
	default:
		errno = EPROTO;
		goto fail;
	}

	ax->ax_rblen = 0;

	return pdu;
fail:
	ax_pdu_free(pdu);
	return NULL;
}

static int
ax_pdu_need(struct ax *ax, size_t need)
{
	uint8_t *wbuf;
	size_t wbsize;

	if (ax->ax_wbtlen + need >= ax->ax_wbsize) {
		wbsize = (((ax->ax_wbtlen + need) / 512) + 1) * 512;
		wbuf = recallocarray(ax->ax_wbuf, ax->ax_wbsize, wbsize, 1);
		if (wbuf == NULL) {
			ax->ax_wbtlen = ax->ax_wblen;
			return -1;
		}
		ax->ax_wbsize = wbsize;
		ax->ax_wbuf = wbuf;
	}

	return 0;
}

ssize_t
ax_send(struct ax *ax)
{
	ssize_t nwrite;

	if (ax->ax_wblen != ax->ax_wbtlen) {
		errno = EALREADY;
		return -1;
	}

	if (ax->ax_wblen == 0)
		return 0;

#if defined(AX_DEBUG) && defined(AX_DEBUG_VERBOSE)
	{
		size_t i;
		char chars[4];
		int print = 1;

		fprintf(stderr, "sending packet:\n");
		for (i = 0; i < ax->ax_wblen; i++) {
			fprintf(stderr, "%02hhx ", ax->ax_wbuf[i]);
			chars[i % 4] = ax->ax_wbuf[i];
			if (!isprint(ax->ax_wbuf[i]))
				print = 0;
			if (i % 4 == 3) {
				if (print)
					fprintf(stderr, "%.4s", chars);
				fprintf(stderr, "\n");
				print = 1;
			}
		}
	}
#endif

	if ((nwrite = send(ax->ax_fd, ax->ax_wbuf, ax->ax_wblen,
	    MSG_NOSIGNAL | MSG_DONTWAIT)) == -1)
		return -1;

	memmove(ax->ax_wbuf, ax->ax_wbuf + nwrite, ax->ax_wblen - nwrite);
	ax->ax_wblen -= nwrite;
	ax->ax_wbtlen = ax->ax_wblen;

	return ax->ax_wblen;
}

uint32_t
ax_open(struct ax *ax, uint8_t timeout, struct ax_oid *oid,
    struct ax_ostring *descr)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_OPEN, 0, 0, 0, 0,
	    NULL) == -1)
		return 0;
	ax_pdu_need(ax, 4);
	ax->ax_wbuf[ax->ax_wbtlen++] = timeout;
	memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, 3);
	ax->ax_wbtlen += 3;
	if (ax_pdu_add_oid(ax, oid) == -1)
		return 0;
	if (ax_pdu_add_str(ax, descr) == -1)
		return 0;

	return ax_pdu_queue(ax);
}

uint32_t
ax_close(struct ax *ax, uint32_t sessionid,
    enum ax_close_reason reason)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_CLOSE, 0, sessionid, arc4random(), 0,
	    NULL) == -1)
		return 0;

	if (ax_pdu_need(ax, 4) == -1)
		return 0;
	ax->ax_wbuf[ax->ax_wbtlen++] = (uint8_t)reason;
	memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, 3);
	ax->ax_wbtlen += 3;

	return ax_pdu_queue(ax);
}

int
ax_get(struct ax *ax, uint32_t sessionid, uint32_t transactionid,
    uint32_t packetid, struct ax_ostring *context, struct ax_searchrange *srl,
    size_t nsr)
{
	size_t i;

	if (ax_pdu_header(ax, AX_PDU_TYPE_GET, 0, sessionid, transactionid,
	    packetid, context) == -1)
		return -1;

	for (i = 0; i < nsr; i++) {
		if (ax_pdu_add_searchrange(ax, &(srl[i])) == -1)
			return 0;
	}

	return ax_pdu_queue(ax);
}

int
ax_getnext(struct ax *ax, uint32_t sessionid, uint32_t transactionid,
    uint32_t packetid, struct ax_ostring *context, struct ax_searchrange *srl,
    size_t nsr)
{
	size_t i;

	if (ax_pdu_header(ax, AX_PDU_TYPE_GETNEXT, 0, sessionid, transactionid,
	    packetid, context) == -1)
		return -1;

	for (i = 0; i < nsr; i++) {
		if (ax_pdu_add_searchrange(ax, &(srl[i])) == -1)
			return 0;
	}

	return ax_pdu_queue(ax);
}

uint32_t
ax_indexallocate(struct ax *ax, uint8_t flags, uint32_t sessionid,
    struct ax_ostring *context, struct ax_varbind *vblist, size_t nvb)
{
	if (flags & ~(AX_PDU_FLAG_NEW_INDEX | AX_PDU_FLAG_ANY_INDEX)) {
		errno = EINVAL;
		return 0;
	}

	if (ax_pdu_header(ax, AX_PDU_TYPE_INDEXALLOCATE, flags,
	    sessionid, 0, 0, context) == -1)
		return 0;

	if (ax_pdu_add_varbindlist(ax, vblist, nvb) == -1)
		return 0;

	return ax_pdu_queue(ax);
}

uint32_t
ax_indexdeallocate(struct ax *ax, uint32_t sessionid,
    struct ax_ostring *context, struct ax_varbind *vblist, size_t nvb)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_INDEXDEALLOCATE, 0,
	    sessionid, 0, 0, context) == -1)
		return 0;

	if (ax_pdu_add_varbindlist(ax, vblist, nvb) == -1)
		return 0;

	return ax_pdu_queue(ax);
}

uint32_t
ax_addagentcaps(struct ax *ax, uint32_t sessionid,
    struct ax_ostring *context, struct ax_oid *id,
    struct ax_ostring *descr)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_ADDAGENTCAPS, 0,
	    sessionid, 0, 0, context) == -1)
		return 0;
	if (ax_pdu_add_oid(ax, id) == -1)
		return 0;
	if (ax_pdu_add_str(ax, descr) == -1)
		return 0;

	return ax_pdu_queue(ax);
}

uint32_t
ax_removeagentcaps(struct ax *ax, uint32_t sessionid,
    struct ax_ostring *context, struct ax_oid *id)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_REMOVEAGENTCAPS, 0,
	    sessionid, 0, 0, context) == -1)
		return 0;
	if (ax_pdu_add_oid(ax, id) == -1)
		return 0;

	return ax_pdu_queue(ax);

}

uint32_t
ax_register(struct ax *ax, uint8_t flags, uint32_t sessionid,
    struct ax_ostring *context, uint8_t timeout, uint8_t priority,
    uint8_t range_subid, struct ax_oid *subtree, uint32_t upperbound)
{
	if (flags & ~(AX_PDU_FLAG_INSTANCE_REGISTRATION)) {
		errno = EINVAL;
		return 0;
	}

	if (ax_pdu_header(ax, AX_PDU_TYPE_REGISTER, flags,
	    sessionid, 0, 0, context) == -1)
		return 0;

	if (ax_pdu_need(ax, 4) == -1)
		return 0;
	ax->ax_wbuf[ax->ax_wbtlen++] = timeout;
	ax->ax_wbuf[ax->ax_wbtlen++] = priority;
	ax->ax_wbuf[ax->ax_wbtlen++] = range_subid;
	ax->ax_wbuf[ax->ax_wbtlen++] = 0;
	if (ax_pdu_add_oid(ax, subtree) == -1)
		return 0;
	if (range_subid != 0) {
		if (ax_pdu_add_uint32(ax, upperbound) == -1)
			return 0;
	}

	return ax_pdu_queue(ax);
}

uint32_t
ax_unregister(struct ax *ax, uint32_t sessionid,
    struct ax_ostring *context, uint8_t priority, uint8_t range_subid,
    struct ax_oid *subtree, uint32_t upperbound)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_UNREGISTER, 0,
	    sessionid, 0, 0, context) == -1)
		return 0;

	if (ax_pdu_need(ax, 4) == -1)
		return 0;
	ax->ax_wbuf[ax->ax_wbtlen++] = 0;
	ax->ax_wbuf[ax->ax_wbtlen++] = priority;
	ax->ax_wbuf[ax->ax_wbtlen++] = range_subid;
	ax->ax_wbuf[ax->ax_wbtlen++] = 0;
	if (ax_pdu_add_oid(ax, subtree) == -1)
		return 0;
	if (range_subid != 0) {
		if (ax_pdu_add_uint32(ax, upperbound) == -1)
			return 0;
	}

	return ax_pdu_queue(ax);
}

int
ax_response(struct ax *ax, uint32_t sessionid, uint32_t transactionid,
    uint32_t packetid, uint32_t sysuptime, uint16_t error, uint16_t index,
    struct ax_varbind *vblist, size_t nvb)
{
	if (ax_pdu_header(ax, AX_PDU_TYPE_RESPONSE, 0, sessionid,
	    transactionid, packetid, NULL) == -1)
		return -1;

	if (ax_pdu_add_uint32(ax, sysuptime) == -1 ||
	    ax_pdu_add_uint16(ax, error) == -1 ||
	    ax_pdu_add_uint16(ax, index) == -1)
		return -1;

	if (ax_pdu_add_varbindlist(ax, vblist, nvb) == -1)
		return -1;
	if (ax_pdu_queue(ax) == 0)
		return -1;
	return 0;
}

void
ax_pdu_free(struct ax_pdu *pdu)
{
	size_t i;
	struct ax_pdu_response *response;
	struct ax_pdu_varbindlist *vblist;

	if (pdu->ap_header.aph_flags & AX_PDU_FLAG_NON_DEFAULT_CONTEXT)
		free(pdu->ap_context.aos_string);

	switch (pdu->ap_header.aph_type) {
	case AX_PDU_TYPE_OPEN:
		free(pdu->ap_payload.ap_open.ap_descr.aos_string);
		break;
	case AX_PDU_TYPE_GET:
	case AX_PDU_TYPE_GETNEXT:
	case AX_PDU_TYPE_GETBULK:
		free(pdu->ap_payload.ap_srl.ap_sr);
		break;
	case AX_PDU_TYPE_TESTSET:
	case AX_PDU_TYPE_INDEXALLOCATE:
	case AX_PDU_TYPE_INDEXDEALLOCATE:
		vblist = &(pdu->ap_payload.ap_vbl);
		for (i = 0; i < vblist->ap_nvarbind; i++)
			ax_varbind_free(&(vblist->ap_varbind[i]));
		free(vblist->ap_varbind);
		break;
	case AX_PDU_TYPE_RESPONSE:
		response = &(pdu->ap_payload.ap_response);
		for (i = 0; i < response->ap_nvarbind; i++)
			ax_varbind_free(&(response->ap_varbindlist[i]));
		free(response->ap_varbindlist);
		break;
	default:
		break;
	}
	free(pdu);
}

void
ax_varbind_free(struct ax_varbind *varbind)
{
	switch (varbind->avb_type) {
	case AX_DATA_TYPE_OCTETSTRING:
	case AX_DATA_TYPE_IPADDRESS:
	case AX_DATA_TYPE_OPAQUE:
		free(varbind->avb_data.avb_ostring.aos_string);
		break;
	default:
		break;
	}
}

const char *
ax_error2string(enum ax_pdu_error error)
{
	static char buffer[64];
	switch (error) {
	case AX_PDU_ERROR_NOERROR:
		return "No error";
	case AX_PDU_ERROR_GENERR:
		return "Generic error";
	case AX_PDU_ERROR_NOACCESS:
		return "No access";
	case AX_PDU_ERROR_WRONGTYPE:
		return "Wrong type";
	case AX_PDU_ERROR_WRONGLENGTH:
		return "Wrong length";
	case AX_PDU_ERROR_WRONGENCODING:
		return "Wrong encoding";
	case AX_PDU_ERROR_WRONGVALUE:
		return "Wrong value";
	case AX_PDU_ERROR_NOCREATION:
		return "No creation";
	case AX_PDU_ERROR_INCONSISTENTVALUE:
		return "Inconsistent value";
	case AX_PDU_ERROR_RESOURCEUNAVAILABLE:
		return "Resource unavailable";
	case AX_PDU_ERROR_COMMITFAILED:
		return "Commit failed";
	case AX_PDU_ERROR_UNDOFAILED:
		return "Undo failed";
	case AX_PDU_ERROR_NOTWRITABLE:
		return "Not writable";
	case AX_PDU_ERROR_INCONSISTENTNAME:
		return "Inconsistent name";
	case AX_PDU_ERROR_OPENFAILED:
		return "Open Failed";
	case AX_PDU_ERROR_NOTOPEN:
		return "Not open";
	case AX_PDU_ERROR_INDEXWRONGTYPE:
		return "Index wrong type";
	case AX_PDU_ERROR_INDEXALREADYALLOCATED:
		return "Index already allocated";
	case AX_PDU_ERROR_INDEXNONEAVAILABLE:
		return "Index none available";
	case AX_PDU_ERROR_INDEXNOTALLOCATED:
		return "Index not allocated";
	case AX_PDU_ERROR_UNSUPPORTEDCONETXT:
		return "Unsupported context";
	case AX_PDU_ERROR_DUPLICATEREGISTRATION:
		return "Duplicate registration";
	case AX_PDU_ERROR_UNKNOWNREGISTRATION:
		return "Unkown registration";
	case AX_PDU_ERROR_UNKNOWNAGENTCAPS:
		return "Unknown agent capabilities";
	case AX_PDU_ERROR_PARSEERROR:
		return "Parse error";
	case AX_PDU_ERROR_REQUESTDENIED:
		return "Request denied";
	case AX_PDU_ERROR_PROCESSINGERROR:
		return "Processing error";
	}
	snprintf(buffer, sizeof(buffer), "Unknown error: %d", error);
	return buffer;
}

const char *
ax_pdutype2string(enum ax_pdu_type type)
{
	static char buffer[64];
	switch(type) {
	case AX_PDU_TYPE_OPEN:
		return "agentx-Open-PDU";
	case AX_PDU_TYPE_CLOSE:
		return "agentx-Close-PDU";
	case AX_PDU_TYPE_REGISTER:
		return "agentx-Register-PDU";
	case AX_PDU_TYPE_UNREGISTER:
		return "agentx-Unregister-PDU";
	case AX_PDU_TYPE_GET:
		return "agentx-Get-PDU";
	case AX_PDU_TYPE_GETNEXT:
		return "agentx-GetNext-PDU";
	case AX_PDU_TYPE_GETBULK:
		return "agentx-GetBulk-PDU";
	case AX_PDU_TYPE_TESTSET:
		return "agentx-TestSet-PDU";
	case AX_PDU_TYPE_COMMITSET:
		return "agentx-CommitSet-PDU";
	case AX_PDU_TYPE_UNDOSET:
		return "agentx-UndoSet-PDU";
	case AX_PDU_TYPE_CLEANUPSET:
		return "agentx-CleanupSet-PDU";
	case AX_PDU_TYPE_NOTIFY:
		return "agentx-Notify-PDU";
	case AX_PDU_TYPE_PING:
		return "agentx-Ping-PDU";
	case AX_PDU_TYPE_INDEXALLOCATE:
		return "agentx-IndexAllocate-PDU";
	case AX_PDU_TYPE_INDEXDEALLOCATE:
		return "agentx-IndexDeallocate-PDU";
	case AX_PDU_TYPE_ADDAGENTCAPS:
		return "agentx-AddAgentCaps-PDU";
	case AX_PDU_TYPE_REMOVEAGENTCAPS:
		return "agentx-RemoveAgentCaps-PDU";
	case AX_PDU_TYPE_RESPONSE:
		return "agentx-Response-PDU";
	}
	snprintf(buffer, sizeof(buffer), "Unknown type: %d", type);
	return buffer;
}

const char *
ax_closereason2string(enum ax_close_reason reason)
{
	static char buffer[64];

	switch (reason) {
	case AX_CLOSE_OTHER:
		return "Undefined reason";
	case AX_CLOSEN_PARSEERROR:
		return "Too many AgentX parse errors from peer";
	case AX_CLOSE_PROTOCOLERROR:
		return "Too many AgentX protocol errors from peer";
	case AX_CLOSE_TIMEOUTS:
		return "Too many timeouts waiting for peer";
	case AX_CLOSE_SHUTDOWN:
		return "shutting down";
	case AX_CLOSE_BYMANAGER:
		return "Manager shuts down";
	}
	snprintf(buffer, sizeof(buffer), "Unknown reason: %d", reason);
	return buffer;
}

const char *
ax_oid2string(struct ax_oid *oid)
{
	return ax_oidrange2string(oid, 0, 0);
}

const char *
ax_oidrange2string(struct ax_oid *oid, uint8_t range_subid,
    uint32_t upperbound)
{
	static char buf[1024];
	char *p;
	size_t i, rest;
	int ret;

	rest = sizeof(buf);
	p = buf;
	for (i = 0; i < oid->aoi_idlen; i++) {
		if (range_subid != 0 && range_subid - 1 == (uint8_t)i)
			ret = snprintf(p, rest, ".[%u-%u]", oid->aoi_id[i],
			    upperbound);
		else
			ret = snprintf(p, rest, ".%u", oid->aoi_id[i]);
		if ((size_t) ret >= rest) {
			snprintf(buf, sizeof(buf), "Couldn't parse oid");
			return buf;
		}
		p += ret;
		rest -= (size_t) ret;
	}
	return buf;
}

const char *
ax_varbind2string(struct ax_varbind *vb)
{
	static char buf[1024];
	char tmpbuf[1024];
	size_t i, bufleft;
	int ishex = 0;
	char *p;
	int ret;

	switch (vb->avb_type) {
	case AX_DATA_TYPE_INTEGER:
		snprintf(buf, sizeof(buf), "%s: (int)%d",
		    ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_int32);
		break;
	case AX_DATA_TYPE_OCTETSTRING:
		for (i = 0;
		    i < vb->avb_data.avb_ostring.aos_slen && !ishex; i++) {
			if (!isprint(vb->avb_data.avb_ostring.aos_string[i]))
				ishex = 1;
		}
		if (ishex) {
			p = tmpbuf;
			bufleft = sizeof(tmpbuf);
			for (i = 0;
			    i < vb->avb_data.avb_ostring.aos_slen; i++) {
				ret = snprintf(p, bufleft, " %02hhX",
				    vb->avb_data.avb_ostring.aos_string[i]);
				if (ret >= (int) bufleft) {
					p = strrchr(p, ' ');
					strlcpy(p, "...", 4);
					break;
				}
				p += 3;
				bufleft -= 3;
			}
			ret = snprintf(buf, sizeof(buf), "%s: (hex-string)%s",
			    ax_oid2string(&(vb->avb_oid)), tmpbuf);
			if (ret >= (int) sizeof(buf)) {
				p  = strrchr(buf, ' ');
				strlcpy(p, "...", 4);
			}
		} else {
			ret = snprintf(buf, sizeof(buf), "%s: (string)",
			    ax_oid2string(&(vb->avb_oid)));
			if (ret >= (int) sizeof(buf)) {
				snprintf(buf, sizeof(buf), "<too large OID>: "
				    "(string)<too large string>");
				break;
			}
			p = buf + ret;
			bufleft = (int) sizeof(buf) - ret;
			if (snprintf(p, bufleft, "%.*s",
			    vb->avb_data.avb_ostring.aos_slen,
			    vb->avb_data.avb_ostring.aos_string) >=
			    (int) bufleft) {
				p = buf + sizeof(buf) - 4;
				strlcpy(p, "...", 4);
			}
		}
		break;
	case AX_DATA_TYPE_NULL:
		snprintf(buf, sizeof(buf), "%s: <null>",
		    ax_oid2string(&(vb->avb_oid)));
		break;
	case AX_DATA_TYPE_OID:
		strlcpy(tmpbuf,
		    ax_oid2string(&(vb->avb_data.avb_oid)), sizeof(tmpbuf));
		snprintf(buf, sizeof(buf), "%s: (oid)%s",
		    ax_oid2string(&(vb->avb_oid)), tmpbuf);
		break;
	case AX_DATA_TYPE_IPADDRESS:
		if (vb->avb_data.avb_ostring.aos_slen != 4) {
			snprintf(buf, sizeof(buf), "%s: (ipaddress)<invalid>",
			    ax_oid2string(&(vb->avb_oid)));
			break;
		}
		if (inet_ntop(PF_INET, vb->avb_data.avb_ostring.aos_string,
		    tmpbuf, sizeof(tmpbuf)) == NULL) {
			snprintf(buf, sizeof(buf), "%s: (ipaddress)"
			    "<unparseable>: %s",
			    ax_oid2string(&(vb->avb_oid)),
			    strerror(errno));
			break;
		}
		snprintf(buf, sizeof(buf), "%s: (ipaddress)%s",
		    ax_oid2string(&(vb->avb_oid)), tmpbuf);
		break;
	case AX_DATA_TYPE_COUNTER32:
		snprintf(buf, sizeof(buf), "%s: (counter32)%u",
		    ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint32);
		break;
	case AX_DATA_TYPE_GAUGE32:
		snprintf(buf, sizeof(buf), "%s: (gauge32)%u",
		    ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint32);
		break;
	case AX_DATA_TYPE_TIMETICKS:
		snprintf(buf, sizeof(buf), "%s: (timeticks)%u",
		    ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint32);
		break;
	case AX_DATA_TYPE_OPAQUE:
		p = tmpbuf;
		bufleft = sizeof(tmpbuf);
		for (i = 0;
		    i < vb->avb_data.avb_ostring.aos_slen; i++) {
			ret = snprintf(p, bufleft, " %02hhX",
			    vb->avb_data.avb_ostring.aos_string[i]);
			if (ret >= (int) bufleft) {
				p = strrchr(p, ' ');
				strlcpy(p, "...", 4);
				break;
			}
			p += 3;
			bufleft -= 3;
		}
		ret = snprintf(buf, sizeof(buf), "%s: (opaque)%s",
		    ax_oid2string(&(vb->avb_oid)), tmpbuf);
		if (ret >= (int) sizeof(buf)) {
			p  = strrchr(buf, ' ');
			strlcpy(p, "...", 4);
		}
		break;
	case AX_DATA_TYPE_COUNTER64:
		snprintf(buf, sizeof(buf), "%s: (counter64)%"PRIu64,
		    ax_oid2string(&(vb->avb_oid)), vb->avb_data.avb_uint64);
		break;
	case AX_DATA_TYPE_NOSUCHOBJECT:
		snprintf(buf, sizeof(buf), "%s: <noSuchObject>",
		    ax_oid2string(&(vb->avb_oid)));
		break;
	case AX_DATA_TYPE_NOSUCHINSTANCE:
		snprintf(buf, sizeof(buf), "%s: <noSuchInstance>",
		    ax_oid2string(&(vb->avb_oid)));
		break;
	case AX_DATA_TYPE_ENDOFMIBVIEW:
		snprintf(buf, sizeof(buf), "%s: <endOfMibView>",
		    ax_oid2string(&(vb->avb_oid)));
		break;
	}
	return buf;
}

int
ax_oid_cmp(struct ax_oid *o1, struct ax_oid *o2)
{
	size_t i, min;

	min = o1->aoi_idlen < o2->aoi_idlen ? o1->aoi_idlen : o2->aoi_idlen;
	for (i = 0; i < min; i++) {
		if (o1->aoi_id[i] < o2->aoi_id[i])
			return -1;
		if (o1->aoi_id[i] > o2->aoi_id[i])
			return 1;
	}
	/* o1 is parent of o2 */
	if (o1->aoi_idlen < o2->aoi_idlen)
		return -2;
	/* o1 is child of o2 */
	if (o1->aoi_idlen > o2->aoi_idlen)
		return 2;
	return 0;
}

int
ax_oid_add(struct ax_oid *oid, uint32_t value)
{
	if (oid->aoi_idlen == AX_OID_MAX_LEN)
		return -1;
	oid->aoi_id[oid->aoi_idlen++] = value;
	return 0;
}

static uint32_t
ax_pdu_queue(struct ax *ax)
{
	struct ax_pdu_header header;
	uint32_t packetid, plength;
	size_t wbtlen = ax->ax_wbtlen;

	header.aph_flags = ax->ax_byteorder == AX_BYTE_ORDER_BE ?
	    AX_PDU_FLAG_NETWORK_BYTE_ORDER : 0;
	packetid = ax_pdutoh32(&header, &(ax->ax_wbuf[ax->ax_wblen + 12]));
	plength = (ax->ax_wbtlen - ax->ax_wblen) - AX_PDU_HEADER;
	ax->ax_wbtlen = ax->ax_wblen + 16;
	(void)ax_pdu_add_uint32(ax, plength);

	ax->ax_wblen = ax->ax_wbtlen = wbtlen;

	return packetid;
}

static int
ax_pdu_header(struct ax *ax, enum ax_pdu_type type, uint8_t flags,
    uint32_t sessionid, uint32_t transactionid, uint32_t packetid,
    struct ax_ostring *context)
{
	if (ax->ax_wblen != ax->ax_wbtlen) {
		errno = EALREADY;
		return -1;
	}

	if (ax_pdu_need(ax, 4) == -1)
		return -1;
	ax->ax_wbuf[ax->ax_wbtlen++] = 1;
	ax->ax_wbuf[ax->ax_wbtlen++] = (uint8_t) type;
	if (context != NULL)
		flags |= AX_PDU_FLAG_NON_DEFAULT_CONTEXT;
	if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
		flags |= AX_PDU_FLAG_NETWORK_BYTE_ORDER;
	ax->ax_wbuf[ax->ax_wbtlen++] = flags;
	ax->ax_wbuf[ax->ax_wbtlen++] = 0;
	if (ax_pdu_add_uint32(ax, sessionid) == -1 ||
	    ax_pdu_add_uint32(ax, transactionid) == -1 ||
	    ax_pdu_add_uint32(ax, packetid) == -1 ||
	    ax_pdu_need(ax, 4) == -1)
		return -1;
	ax->ax_wbtlen += 4;
	if (context != NULL) {
		if (ax_pdu_add_str(ax, context) == -1)
			return -1;
	}

	return 0;
}

static int
ax_pdu_add_uint16(struct ax *ax, uint16_t value)
{
	if (ax_pdu_need(ax, sizeof(value)) == -1)
		return -1;

	if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
		value = htobe16(value);
	else
		value = htole16(value);
	memcpy(ax->ax_wbuf + ax->ax_wbtlen, &value, sizeof(value));
	ax->ax_wbtlen += sizeof(value);
	return 0;
}

static int
ax_pdu_add_uint32(struct ax *ax, uint32_t value)
{
	if (ax_pdu_need(ax, sizeof(value)) == -1)
		return -1;

	if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
		value = htobe32(value);
	else
		value = htole32(value);
	memcpy(ax->ax_wbuf + ax->ax_wbtlen, &value, sizeof(value));
	ax->ax_wbtlen += sizeof(value);
	return 0;
}

static int
ax_pdu_add_uint64(struct ax *ax, uint64_t value)
{
	if (ax_pdu_need(ax, sizeof(value)) == -1)
		return -1;

	if (ax->ax_byteorder == AX_BYTE_ORDER_BE)
		value = htobe64(value);
	else
		value = htole64(value);
	memcpy(ax->ax_wbuf + ax->ax_wbtlen, &value, sizeof(value));
	ax->ax_wbtlen += sizeof(value);
	return 0;
}


static int
ax_pdu_add_oid(struct ax *ax, struct ax_oid *oid)
{
	static struct ax_oid nulloid = {0};
	uint8_t prefix = 0, n_subid, i = 0;

	n_subid = oid->aoi_idlen;

	if (oid == NULL)
		oid = &nulloid;

	if (oid->aoi_idlen > 4 &&
	    oid->aoi_id[0] == 1 && oid->aoi_id[1] == 3 &&
	    oid->aoi_id[2] == 6 && oid->aoi_id[3] == 1 &&
	    oid->aoi_id[4] <= UINT8_MAX) {
		prefix = oid->aoi_id[4];
		i = 5;
	}

	if (ax_pdu_need(ax, 4) == -1)
		return -1;
	ax->ax_wbuf[ax->ax_wbtlen++] = n_subid - i;
	ax->ax_wbuf[ax->ax_wbtlen++] = prefix;
	ax->ax_wbuf[ax->ax_wbtlen++] = oid->aoi_include;
	ax->ax_wbuf[ax->ax_wbtlen++] = 0;

	for (; i < n_subid; i++) {
		if (ax_pdu_add_uint32(ax, oid->aoi_id[i]) == -1)
			return -1;
	}

	return 0;
}

static int
ax_pdu_add_str(struct ax *ax, struct ax_ostring *str)
{
	size_t length, zeroes;

	if (ax_pdu_add_uint32(ax, str->aos_slen) == -1)
		return -1;

	if ((zeroes = (4 - (str->aos_slen % 4))) == 4)
		zeroes = 0;
	length = str->aos_slen + zeroes;
	if (ax_pdu_need(ax, length) == -1)
		return -1;

	memcpy(&(ax->ax_wbuf[ax->ax_wbtlen]), str->aos_string, str->aos_slen);
	ax->ax_wbtlen += str->aos_slen;
	memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, zeroes);
	ax->ax_wbtlen += zeroes;
	return 0;
}

static int
ax_pdu_add_varbindlist(struct ax *ax,
    struct ax_varbind *vblist, size_t nvb)
{
	size_t i;
	uint16_t temp;

	for (i = 0; i < nvb; i++) {
		temp = (uint16_t) vblist[i].avb_type;
		if (ax_pdu_add_uint16(ax, temp) == -1 ||
		    ax_pdu_need(ax, 2))
			return -1;
		memset(&(ax->ax_wbuf[ax->ax_wbtlen]), 0, 2);
		ax->ax_wbtlen += 2;
		if (ax_pdu_add_oid(ax, &(vblist[i].avb_oid)) == -1)
			return -1;
		switch (vblist[i].avb_type) {
		case AX_DATA_TYPE_INTEGER:
			if (ax_pdu_add_uint32(ax,
			    vblist[i].avb_data.avb_int32) == -1)
				return -1;
			break;
		case AX_DATA_TYPE_COUNTER32:
		case AX_DATA_TYPE_GAUGE32:
		case AX_DATA_TYPE_TIMETICKS:
			if (ax_pdu_add_uint32(ax,
			    vblist[i].avb_data.avb_uint32) == -1)
				return -1;
			break;
		case AX_DATA_TYPE_COUNTER64:
			if (ax_pdu_add_uint64(ax,
			    vblist[i].avb_data.avb_uint64) == -1)
				return -1;
			break;
		case AX_DATA_TYPE_OCTETSTRING:
		case AX_DATA_TYPE_IPADDRESS:
		case AX_DATA_TYPE_OPAQUE:
			if (ax_pdu_add_str(ax,
			    &(vblist[i].avb_data.avb_ostring)) == -1)
				return -1;
			break;
		case AX_DATA_TYPE_OID:
			if (ax_pdu_add_oid(ax,
			    &(vblist[i].avb_data.avb_oid)) == -1)
				return -1;
			break;
		case AX_DATA_TYPE_NULL:
		case AX_DATA_TYPE_NOSUCHOBJECT:
		case AX_DATA_TYPE_NOSUCHINSTANCE:
		case AX_DATA_TYPE_ENDOFMIBVIEW:
			break;
		default:
			errno = EINVAL;
			return -1;
		}
	}
	return 0;
}

static int
ax_pdu_add_searchrange(struct ax *ax, struct ax_searchrange *sr)
{
	if (ax_pdu_add_oid(ax, &(sr->asr_start)) == -1 ||
	    ax_pdu_add_oid(ax, &(sr->asr_stop)) == -1)
		return -1;
	return 0;
}

static uint16_t
ax_pdutoh16(struct ax_pdu_header *header, uint8_t *buf)
{
	uint16_t value;

	memcpy(&value, buf, sizeof(value));
	if (header->aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
		return be16toh(value);
	return le16toh(value);
}

static uint32_t
ax_pdutoh32(struct ax_pdu_header *header, uint8_t *buf)
{
	uint32_t value;

	memcpy(&value, buf, sizeof(value));
	if (header->aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
		return be32toh(value);
	return le32toh(value);
}

static uint64_t
ax_pdutoh64(struct ax_pdu_header *header, uint8_t *buf)
{
	uint64_t value;

	memcpy(&value, buf, sizeof(value));
	if (header->aph_flags & AX_PDU_FLAG_NETWORK_BYTE_ORDER)
		return be64toh(value);
	return le64toh(value);
}

static ssize_t
ax_pdutooid(struct ax_pdu_header *header, struct ax_oid *oid,
    uint8_t *buf, size_t rawlen)
{
	size_t i = 0;
	ssize_t nread;

	if (rawlen < 4)
		goto fail;
	rawlen -= 4;
	nread = 4;
	oid->aoi_idlen = *buf++;
	if (rawlen < (oid->aoi_idlen * 4))
		goto fail;
	nread += oid->aoi_idlen * 4;
	if (*buf != 0) {
		oid->aoi_id[0] = 1;
		oid->aoi_id[1] = 3;
		oid->aoi_id[2] = 6;
		oid->aoi_id[3] = 1;
		oid->aoi_id[4] = *buf;
		oid->aoi_idlen += 5;
		i = 5;
	}
	buf++;
	oid->aoi_include = *buf;
	if (oid->aoi_idlen > AX_OID_MAX_LEN)
		goto fail;
	for (buf += 2; i < oid->aoi_idlen; i++, buf += 4)
		oid->aoi_id[i] = ax_pdutoh32(header, buf);

	return nread;

fail:
	errno = EPROTO;
	return -1;
}

static ssize_t
ax_pdutoostring(struct ax_pdu_header *header,
    struct ax_ostring *ostring, uint8_t *buf, size_t rawlen)
{
	ssize_t nread;

	if (rawlen < 4)
		goto fail;

	ostring->aos_slen = ax_pdutoh32(header, buf);
	rawlen -= 4;
	buf += 4;
	if (ostring->aos_slen > rawlen)
		goto fail;
	if ((ostring->aos_string = malloc(ostring->aos_slen + 1)) == NULL)
		return -1;
	memcpy(ostring->aos_string, buf, ostring->aos_slen);
	ostring->aos_string[ostring->aos_slen] = '\0';

	nread = 4 + ostring->aos_slen;
	if (ostring->aos_slen % 4 != 0)
		nread += 4 - (ostring->aos_slen % 4);

	return nread;

fail:
	errno = EPROTO;
	return -1;
}

static ssize_t
ax_pdutovarbind(struct ax_pdu_header *header,
    struct ax_varbind *varbind, uint8_t *buf, size_t rawlen)
{
	ssize_t nread, rread = 4;

	if (rawlen == 0)
		return 0;

	if (rawlen < 8)
		goto fail;
	varbind->avb_type = ax_pdutoh16(header, buf);

	buf += 4;
	rawlen -= 4;
	nread = ax_pdutooid(header, &(varbind->avb_oid), buf, rawlen);
	if (nread == -1)
		return -1;
	rread += nread;
	buf += nread;
	rawlen -= nread;

	switch(varbind->avb_type) {
	case AX_DATA_TYPE_INTEGER:
		if (rawlen < 4)
			goto fail;
		varbind->avb_data.avb_int32 = ax_pdutoh32(header, buf);
		return rread + 4;
	case AX_DATA_TYPE_COUNTER32:
	case AX_DATA_TYPE_GAUGE32:
	case AX_DATA_TYPE_TIMETICKS:
		if (rawlen < 4)
			goto fail;
		varbind->avb_data.avb_uint32 = ax_pdutoh32(header, buf);
		return rread + 4;
	case AX_DATA_TYPE_COUNTER64:
		if (rawlen < 8)
			goto fail;
		varbind->avb_data.avb_uint64 = ax_pdutoh64(header, buf);
		return rread + 8;
	case AX_DATA_TYPE_OCTETSTRING:
	case AX_DATA_TYPE_IPADDRESS:
	case AX_DATA_TYPE_OPAQUE:
		nread = ax_pdutoostring(header,
		    &(varbind->avb_data.avb_ostring), buf, rawlen);
		if (nread == -1)
			return -1;
		return nread + rread;
	case AX_DATA_TYPE_OID:
		nread = ax_pdutooid(header, &(varbind->avb_data.avb_oid),
		    buf, rawlen);
		if (nread == -1)
			return -1;
		return nread + rread;
	case AX_DATA_TYPE_NULL:
	case AX_DATA_TYPE_NOSUCHOBJECT:
	case AX_DATA_TYPE_NOSUCHINSTANCE:
	case AX_DATA_TYPE_ENDOFMIBVIEW:
		return rread;
	}

fail:
	errno = EPROTO;
	return -1;
}