/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2006 Shteryana Shopova <syrinx@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Bridge MIB implementation for SNMPd.
 * Bridge ports.
 */

#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_mib.h>

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>

#include <bsnmp/snmpmod.h>
#include <bsnmp/snmp_mibII.h>

#define	SNMPTREE_TYPES
#include "bridge_tree.h"
#include "bridge_snmp.h"

TAILQ_HEAD(bridge_ports, bridge_port);

/*
 * Free the bridge base ports list.
 */
static void
bridge_ports_free(struct bridge_ports *headp)
{
	struct bridge_port *bp;

	while ((bp = TAILQ_FIRST(headp)) != NULL) {
		TAILQ_REMOVE(headp, bp, b_p);
		free(bp);
	}
}

/*
 * Free the bridge base ports from the base ports list,
 * members of a specified bridge interface only.
 */
static void
bridge_port_memif_free(struct bridge_ports *headp,
	struct bridge_if *bif)
{
	struct bridge_port *bp;

	while (bif->f_bp != NULL && bif->sysindex == bif->f_bp->sysindex) {
		bp = TAILQ_NEXT(bif->f_bp, b_p);
		TAILQ_REMOVE(headp, bif->f_bp, b_p);
		free(bif->f_bp);
		bif->f_bp = bp;
	}
}

/*
 * Insert a port entry in the base port TAILQ starting to search
 * for its place from the position of the first bridge port for the bridge
 * interface. Update the first bridge port if necessary.
 */
static void
bridge_port_insert_at(struct bridge_ports *headp,
	struct bridge_port *bp, struct bridge_port **f_bp)
{
	struct bridge_port *t1;

	assert(f_bp != NULL);

	for (t1 = *f_bp;
	    t1 != NULL && bp->sysindex == t1->sysindex;
	    t1 = TAILQ_NEXT(t1, b_p)) {
		if (bp->if_idx < t1->if_idx) {
			TAILQ_INSERT_BEFORE(t1, bp, b_p);
			if (*f_bp == t1)
				*f_bp = bp;
			return;
		}
	}

	/*
	 * Handle the case when our first port was actually the
	 * last element of the TAILQ.
	 */
	if (t1 == NULL)
		TAILQ_INSERT_TAIL(headp, bp, b_p);
	else
		TAILQ_INSERT_BEFORE(t1, bp, b_p);
}

/*
 * Find a port entry's position in the ports list according
 * to it's parent bridge interface name. Returns a NULL if
 * we should be at the TAILQ head, otherwise the entry after
 * which we should be inserted.
 */
static struct bridge_port *
bridge_port_find_pos(struct bridge_ports *headp, uint32_t b_idx)
{
	uint32_t t_idx;
	struct bridge_port *t1;

	if ((t1 = TAILQ_FIRST(headp)) == NULL ||
	    bridge_compare_sysidx(b_idx, t1->sysindex) < 0)
		return (NULL);

	t_idx = t1->sysindex;

	for (t1 = TAILQ_NEXT(t1, b_p); t1 != NULL; t1 = TAILQ_NEXT(t1, b_p)) {
		if (t1->sysindex != t_idx) {
			if (bridge_compare_sysidx(b_idx, t1->sysindex) < 0)
				return (TAILQ_PREV(t1, bridge_ports, b_p));
			else
				t_idx = t1->sysindex;
		}
	}

	if (t1 == NULL)
		t1 = TAILQ_LAST(headp, bridge_ports);

	return (t1);
}

/*
 * Insert a bridge member interface in the ports TAILQ.
 */
static void
bridge_port_memif_insert(struct bridge_ports *headp,
	struct bridge_port *bp, struct bridge_port **f_bp)
{
	struct bridge_port *temp;

	if (*f_bp != NULL)
		bridge_port_insert_at(headp, bp, f_bp);
	else {
		temp = bridge_port_find_pos(headp, bp->sysindex);

		if (temp == NULL)
			TAILQ_INSERT_HEAD(headp, bp, b_p);
		else
			TAILQ_INSERT_AFTER(headp, temp, bp, b_p);
		*f_bp = bp;
	}
}

/* The global ports list. */
static struct bridge_ports bridge_ports = TAILQ_HEAD_INITIALIZER(bridge_ports);
static time_t ports_list_age;

void
bridge_ports_update_listage(void)
{
	ports_list_age = time(NULL);
}

void
bridge_ports_fini(void)
{
	bridge_ports_free(&bridge_ports);
}

void
bridge_members_free(struct bridge_if *bif)
{
	bridge_port_memif_free(&bridge_ports, bif);
}

/*
 * Find the first port in the ports list.
 */
static struct bridge_port *
bridge_port_first(void)
{
	return (TAILQ_FIRST(&bridge_ports));
}

/*
 * Find the next port in the ports list.
 */
static struct bridge_port *
bridge_port_next(struct bridge_port *bp)
{
	return (TAILQ_NEXT(bp, b_p));
}

/*
 * Find the first member of the specified bridge interface.
 */
struct bridge_port *
bridge_port_bif_first(struct bridge_if *bif)
{
	return (bif->f_bp);
}

/*
 * Find the next member of the specified bridge interface.
 */
struct bridge_port *
bridge_port_bif_next(struct bridge_port *bp)
{
	struct bridge_port *bp_next;

	if ((bp_next = TAILQ_NEXT(bp, b_p)) == NULL ||
	    bp_next->sysindex != bp->sysindex)
		return (NULL);

	return (bp_next);
}

/*
 * Remove a bridge port from the ports list.
 */
void
bridge_port_remove(struct bridge_port *bp, struct bridge_if *bif)
{
	if (bif->f_bp == bp)
		bif->f_bp = bridge_port_bif_next(bp);

	TAILQ_REMOVE(&bridge_ports, bp, b_p);
	free(bp);
}

/*
 * Allocate memory for a new bridge port and insert it
 * in the base ports list. Return a pointer to the port's
 * structure in case we want to do anything else with it.
 */
struct bridge_port *
bridge_new_port(struct mibif *mif, struct bridge_if *bif)
{
	struct bridge_port *bp;

	if ((bp = (struct bridge_port *) malloc(sizeof(*bp))) == NULL) {
		syslog(LOG_ERR, "bridge new member: failed: %s",
			strerror(errno));
		return (NULL);
	}

	bzero(bp, sizeof(*bp));

	bp->sysindex = bif->sysindex;
	bp->if_idx = mif->index;
	bp->port_no = mif->sysindex;
	strlcpy(bp->p_name, mif->name, IFNAMSIZ);
	bp->circuit = oid_zeroDotZero;

	/*
	 * Initialize all rstpMib specific values to false/default.
	 * These will be set to their true values later if the bridge
	 * supports RSTP.
	 */
	bp->proto_migr = TruthValue_false;
	bp->admin_edge = TruthValue_false;
	bp->oper_edge = TruthValue_false;
	bp->oper_ptp = TruthValue_false;
	bp->admin_ptp = StpPortAdminPointToPointType_auto;

	bridge_port_memif_insert(&bridge_ports, bp, &(bif->f_bp));

	return (bp);
}

/*
 * Update our info from the corresponding mibII interface info.
 */
void
bridge_port_getinfo_mibif(struct mibif *m_if, struct bridge_port *bp)
{
	bp->max_info = m_if->mib.ifmd_data.ifi_mtu;
	bp->in_frames = m_if->mib.ifmd_data.ifi_ipackets;
	bp->out_frames = m_if->mib.ifmd_data.ifi_opackets;
	bp->in_drops = m_if->mib.ifmd_data.ifi_iqdrops;
}

/*
 * Find a port, whose SNMP's mibII ifIndex matches one of the ports,
 * members of the specified bridge interface.
 */
struct bridge_port *
bridge_port_find(int32_t if_idx, struct bridge_if *bif)
{
	struct bridge_port *bp;

	for (bp = bif->f_bp; bp != NULL; bp = TAILQ_NEXT(bp, b_p)) {
		if (bp->sysindex != bif->sysindex) {
			bp = NULL;
			break;
		}

		if (bp->if_idx == if_idx)
			break;
	}

	return (bp);
}

void
bridge_ports_dump(struct bridge_if *bif)
{
	struct bridge_port *bp;

	for (bp = bridge_port_bif_first(bif); bp != NULL;
	    bp = bridge_port_bif_next(bp)) {
		syslog(LOG_ERR, "memif - %s, index - %d",
		bp->p_name, bp->port_no);
	}
}

/*
 * RFC4188 specifics.
 */
int
op_dot1d_base_port(struct snmp_context *c __unused, struct snmp_value *val,
	uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_if *bif;
	struct bridge_port *bp;

	if ((bif = bridge_get_default()) == NULL)
		return (SNMP_ERR_NOSUCHNAME);

	if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
	    bridge_update_memif(bif) <= 0)
		return (SNMP_ERR_NOSUCHNAME);

	switch (op) {
		case SNMP_OP_GET:
		    if (val->var.len - sub != 1)
			return (SNMP_ERR_NOSUCHNAME);
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    goto get;

		case SNMP_OP_GETNEXT:
		    if (val->var.len - sub == 0) {
			if ((bp = bridge_port_bif_first(bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    } else {
			if ((bp = bridge_port_find(val->var.subs[sub],
			    bif)) == NULL ||
			    (bp = bridge_port_bif_next(bp)) == NULL)
				return (SNMP_ERR_NOSUCHNAME);
		    }
		    val->var.len = sub + 1;
		    val->var.subs[sub] = bp->port_no;
		    goto get;

		case SNMP_OP_SET:
		    return (SNMP_ERR_NOT_WRITEABLE);

		case SNMP_OP_ROLLBACK:
		case SNMP_OP_COMMIT:
		    break;
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
	    case LEAF_dot1dBasePort:
		val->v.integer = bp->port_no;
		return (SNMP_ERR_NOERROR);

	    case LEAF_dot1dBasePortIfIndex:
		val->v.integer = bp->if_idx;
		return (SNMP_ERR_NOERROR);

	    case LEAF_dot1dBasePortCircuit:
		val->v.oid = bp->circuit;
		return (SNMP_ERR_NOERROR);

	    case LEAF_dot1dBasePortDelayExceededDiscards:
		val->v.uint32 = bp->dly_ex_drops;
		return (SNMP_ERR_NOERROR);

	    case LEAF_dot1dBasePortMtuExceededDiscards:
		val->v.uint32 = bp->dly_mtu_drops;
		return (SNMP_ERR_NOERROR);
	}

	abort();
}

int
op_dot1d_stp_port(struct snmp_context *ctx, struct snmp_value *val,
	 uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_if *bif;
	struct bridge_port *bp;

	if ((bif = bridge_get_default()) == NULL)
		return (SNMP_ERR_NOSUCHNAME);

	if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
	    bridge_update_memif(bif) <= 0)
		return (SNMP_ERR_NOSUCHNAME);

	switch (op) {
		case SNMP_OP_GET:
		    if (val->var.len - sub != 1)
			return (SNMP_ERR_NOSUCHNAME);
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    goto get;

		case SNMP_OP_GETNEXT:
		    if (val->var.len - sub == 0) {
			if ((bp = bridge_port_bif_first(bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    } else {
			if ((bp = bridge_port_find(val->var.subs[sub],
			    bif)) == NULL ||
			    (bp = bridge_port_bif_next(bp)) == NULL)
				return (SNMP_ERR_NOSUCHNAME);
		    }
		    val->var.len = sub + 1;
		    val->var.subs[sub] = bp->port_no;
		    goto get;

		case SNMP_OP_SET:
		    if (val->var.len - sub != 1)
			return (SNMP_ERR_NOSUCHNAME);
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);

		    switch (val->var.subs[sub - 1]) {
			case LEAF_dot1dStpPortPriority:
			    if (val->v.integer < 0 || val->v.integer > 255)
				return (SNMP_ERR_WRONG_VALUE);

			    ctx->scratch->int1 = bp->priority;
			    if (bridge_port_set_priority(bif->bif_name, bp,
				val->v.integer) < 0)
				return (SNMP_ERR_GENERR);
			    return (SNMP_ERR_NOERROR);

			case LEAF_dot1dStpPortEnable:
			    if (val->v.integer != dot1dStpPortEnable_enabled &&
				val->v.integer != dot1dStpPortEnable_disabled)
				return (SNMP_ERR_WRONG_VALUE);

			    ctx->scratch->int1 = bp->enable;
			    if (bridge_port_set_stp_enable(bif->bif_name,
				bp, val->v.integer) < 0)
				return (SNMP_ERR_GENERR);
			    return (SNMP_ERR_NOERROR);

			case LEAF_dot1dStpPortPathCost:
			    if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
				val->v.integer > SNMP_PORT_MAX_PATHCOST)
				return (SNMP_ERR_WRONG_VALUE);

			    ctx->scratch->int1 = bp->path_cost;
			    if (bridge_port_set_path_cost(bif->bif_name, bp,
				val->v.integer) < 0)
				return (SNMP_ERR_GENERR);
			    return (SNMP_ERR_NOERROR);

			case LEAF_dot1dStpPort:
			case LEAF_dot1dStpPortState:
			case LEAF_dot1dStpPortDesignatedRoot:
			case LEAF_dot1dStpPortDesignatedCost:
			case LEAF_dot1dStpPortDesignatedBridge:
			case LEAF_dot1dStpPortDesignatedPort:
			case LEAF_dot1dStpPortForwardTransitions:
			    return (SNMP_ERR_NOT_WRITEABLE);
		    }
		    abort();

		case SNMP_OP_ROLLBACK:
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_GENERR);
		    switch (val->var.subs[sub - 1]) {
			case LEAF_dot1dStpPortPriority:
			    bridge_port_set_priority(bif->bif_name, bp,
				ctx->scratch->int1);
			    break;
			case LEAF_dot1dStpPortEnable:
			    bridge_port_set_stp_enable(bif->bif_name, bp,
				ctx->scratch->int1);
			    break;
			case LEAF_dot1dStpPortPathCost:
			    bridge_port_set_path_cost(bif->bif_name, bp,
				ctx->scratch->int1);
			    break;
		    }
		    return (SNMP_ERR_NOERROR);

		case SNMP_OP_COMMIT:
		    return (SNMP_ERR_NOERROR);
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
		case LEAF_dot1dStpPort:
			val->v.integer = bp->port_no;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortPriority:
			val->v.integer = bp->priority;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortState:
			val->v.integer = bp->state;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortEnable:
			val->v.integer = bp->enable;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortPathCost:
			val->v.integer = bp->path_cost;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortDesignatedRoot:
			return (string_get(val, bp->design_root,
			    SNMP_BRIDGE_ID_LEN));

		case LEAF_dot1dStpPortDesignatedCost:
			val->v.integer = bp->design_cost;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortDesignatedBridge:
			return (string_get(val, bp->design_bridge,
			    SNMP_BRIDGE_ID_LEN));

		case LEAF_dot1dStpPortDesignatedPort:
			return (string_get(val, bp->design_port, 2));

		case LEAF_dot1dStpPortForwardTransitions:
			val->v.uint32 = bp->fwd_trans;
			return (SNMP_ERR_NOERROR);
	}

	abort();
}

int
op_dot1d_stp_ext_port(struct snmp_context *ctx, struct snmp_value *val,
    uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_if *bif;
	struct bridge_port *bp;

	if ((bif = bridge_get_default()) == NULL)
		return (SNMP_ERR_NOSUCHNAME);

	if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
	    bridge_update_memif(bif) <= 0)
		return (SNMP_ERR_NOSUCHNAME);

	switch (op) {
		case SNMP_OP_GET:
		    if (val->var.len - sub != 1)
			return (SNMP_ERR_NOSUCHNAME);
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    goto get;

		case SNMP_OP_GETNEXT:
		    if (val->var.len - sub == 0) {
			if ((bp = bridge_port_bif_first(bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    } else {
			if ((bp = bridge_port_find(val->var.subs[sub],
			    bif)) == NULL ||
			    (bp = bridge_port_bif_next(bp)) == NULL)
				return (SNMP_ERR_NOSUCHNAME);
		    }
		    val->var.len = sub + 1;
		    val->var.subs[sub] = bp->port_no;
		    goto get;

		case SNMP_OP_SET:
		    if (val->var.len - sub != 1)
			return (SNMP_ERR_NOSUCHNAME);
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);

		    switch (val->var.subs[sub - 1]) {
			case LEAF_dot1dStpPortAdminEdgePort:
			    if (val->v.integer != TruthValue_true &&
				val->v.integer != TruthValue_false)
				return (SNMP_ERR_WRONG_VALUE);

			    ctx->scratch->int1 = bp->admin_edge;
			    if (bridge_port_set_admin_edge(bif->bif_name, bp,
				val->v.integer) < 0)
				return (SNMP_ERR_GENERR);
			    return (SNMP_ERR_NOERROR);

			case LEAF_dot1dStpPortAdminPointToPoint:
			    if (val->v.integer < 0 || val->v.integer >
				StpPortAdminPointToPointType_auto)
				return (SNMP_ERR_WRONG_VALUE);

			    ctx->scratch->int1 = bp->admin_ptp;
			    if (bridge_port_set_admin_ptp(bif->bif_name, bp,
				val->v.integer) < 0)
				return (SNMP_ERR_GENERR);
			    return (SNMP_ERR_NOERROR);

			case LEAF_dot1dStpPortAdminPathCost:
			    if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
				val->v.integer > SNMP_PORT_MAX_PATHCOST)
				return (SNMP_ERR_WRONG_VALUE);

			    ctx->scratch->int1 = bp->admin_path_cost;
			    if (bridge_port_set_path_cost(bif->bif_name, bp,
				val->v.integer) < 0)
				return (SNMP_ERR_GENERR);
			    return (SNMP_ERR_NOERROR);

			case LEAF_dot1dStpPortProtocolMigration:
			case LEAF_dot1dStpPortOperEdgePort:
			case LEAF_dot1dStpPortOperPointToPoint:
			    return (SNMP_ERR_NOT_WRITEABLE);
		    }
		    abort();

		case SNMP_OP_ROLLBACK:
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_GENERR);

		    switch (val->var.subs[sub - 1]) {
			case LEAF_dot1dStpPortAdminEdgePort:
			    bridge_port_set_admin_edge(bif->bif_name, bp,
				ctx->scratch->int1);
			    break;
			case LEAF_dot1dStpPortAdminPointToPoint:
			    bridge_port_set_admin_ptp(bif->bif_name, bp,
				ctx->scratch->int1);
			    break;
			case LEAF_dot1dStpPortAdminPathCost:
			    bridge_port_set_path_cost(bif->bif_name, bp,
				ctx->scratch->int1);
			    break;
		    }
		    return (SNMP_ERR_NOERROR);

		case SNMP_OP_COMMIT:
		    return (SNMP_ERR_NOERROR);
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
		case LEAF_dot1dStpPortProtocolMigration:
			val->v.integer = bp->proto_migr;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortAdminEdgePort:
			val->v.integer = bp->admin_edge;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortOperEdgePort:
			val->v.integer = bp->oper_edge;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortAdminPointToPoint:
			val->v.integer = bp->admin_ptp;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortOperPointToPoint:
			val->v.integer = bp->oper_ptp;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dStpPortAdminPathCost:
			val->v.integer = bp->admin_path_cost;
			return (SNMP_ERR_NOERROR);
	}

	abort();
}

int
op_dot1d_tp_port(struct snmp_context *c __unused, struct snmp_value *val,
    uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_if *bif;
	struct bridge_port *bp;

	if ((bif = bridge_get_default()) == NULL)
		return (SNMP_ERR_NOSUCHNAME);

	if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
	    bridge_update_memif(bif) <= 0)
		return (SNMP_ERR_NOSUCHNAME);

	switch (op) {
		case SNMP_OP_GET:
		    if (val->var.len - sub != 1)
			return (SNMP_ERR_NOSUCHNAME);
		    if ((bp = bridge_port_find(val->var.subs[sub],
			bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    goto get;

		case SNMP_OP_GETNEXT:
		    if (val->var.len - sub == 0) {
			if ((bp = bridge_port_bif_first(bif)) == NULL)
			    return (SNMP_ERR_NOSUCHNAME);
		    } else {
			if ((bp = bridge_port_find(val->var.subs[sub],
			    bif)) == NULL ||
			    (bp = bridge_port_bif_next(bp)) == NULL)
				return (SNMP_ERR_NOSUCHNAME);
		    }
		    val->var.len = sub + 1;
		    val->var.subs[sub] = bp->port_no;
		    goto get;

		case SNMP_OP_SET:
		    return (SNMP_ERR_NOT_WRITEABLE);

		case SNMP_OP_ROLLBACK:
		case SNMP_OP_COMMIT:
		    break;
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
		case LEAF_dot1dTpPort:
			val->v.integer = bp->port_no;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dTpPortMaxInfo:
			val->v.integer = bp->max_info;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dTpPortInFrames:
			val->v.uint32 = bp->in_frames;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dTpPortOutFrames:
			val->v.uint32 = bp->out_frames;
			return (SNMP_ERR_NOERROR);

		case LEAF_dot1dTpPortInDiscards:
			val->v.uint32 = bp->in_drops;
			return (SNMP_ERR_NOERROR);
	}

	abort();
}

/*
 * Private BEGEMOT-BRIDGE-MIB specifics.
 */

/*
 * Construct a bridge port entry index.
 */
static int
bridge_port_index_append(struct asn_oid *oid, uint sub,
	const struct bridge_port *bp)
{
	uint i;
	const char *b_name;

	if ((b_name = bridge_if_find_name(bp->sysindex)) == NULL)
		return (-1);

	oid->len = sub + strlen(b_name) + 1 + 1;
	oid->subs[sub] = strlen(b_name);

	for (i = 1; i <= strlen(b_name); i++)
		oid->subs[sub + i] = b_name[i - 1];

	oid->subs[sub + i] = bp->port_no;

	return (0);
}

/*
 * Get the port entry from an entry's index.
 */
static struct bridge_port *
bridge_port_index_get(const struct asn_oid *oid, uint sub, int8_t status)
{
	uint i;
	int32_t port_no;
	char bif_name[IFNAMSIZ];
	struct bridge_if *bif;
	struct bridge_port *bp;

	if (oid->len - sub != oid->subs[sub] + 2 ||
	    oid->subs[sub] >= IFNAMSIZ)
		return (NULL);

	for (i = 0; i < oid->subs[sub]; i++)
		bif_name[i] = oid->subs[sub + i + 1];
	bif_name[i] = '\0';

	port_no = oid->subs[sub + i + 1];

	if ((bif = bridge_if_find_ifname(bif_name)) == NULL)
		return (NULL);

	if ((bp = bridge_port_find(port_no, bif)) == NULL ||
	    (status == 0 && bp->status != RowStatus_active))
		return (NULL);

	return (bp);
}

/*
 * Get the next port entry from an entry's index.
 */
static struct bridge_port *
bridge_port_index_getnext(const struct asn_oid *oid, uint sub, int8_t status)
{
	uint i;
	int32_t port_no;
	char bif_name[IFNAMSIZ];
	struct bridge_if *bif;
	struct bridge_port *bp;

	if (oid->len - sub == 0)
		bp = bridge_port_first();
	else {
		if (oid->len - sub != oid->subs[sub] + 2 ||
		    oid->subs[sub] >= IFNAMSIZ)
			return (NULL);

		for (i = 0; i < oid->subs[sub]; i++)
			bif_name[i] = oid->subs[sub + i + 1];
		bif_name[i] = '\0';

		port_no = oid->subs[sub + i + 1];

		if ((bif = bridge_if_find_ifname(bif_name)) == NULL ||
		    (bp = bridge_port_find(port_no, bif)) == NULL)
			return (NULL);

		bp = bridge_port_next(bp);
	}

	if (status == 1)
		return (bp);

	while (bp != NULL) {
		if (bp->status == RowStatus_active)
			break;
		bp = bridge_port_next(bp);
	}

	return (bp);
}

/*
 * Read the bridge name and port index from a ASN OID structure.
 */
static int
bridge_port_index_decode(const struct asn_oid *oid, uint sub,
	char *b_name, int32_t *idx)
{
	uint i;

	if (oid->len - sub != oid->subs[sub] + 2 ||
	    oid->subs[sub] >= IFNAMSIZ)
		return (-1);

	for (i = 0; i < oid->subs[sub]; i++)
		b_name[i] = oid->subs[sub + i + 1];
	b_name[i] = '\0';

	*idx = oid->subs[sub + i + 1];
	return (0);
}

static int
bridge_port_set_status(struct snmp_context *ctx,
	struct snmp_value *val, uint sub)
{
	int32_t if_idx;
	char b_name[IFNAMSIZ];
	struct bridge_if *bif;
	struct bridge_port *bp;
	struct mibif *mif;

	if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
		return (SNMP_ERR_INCONS_VALUE);

	if ((bif = bridge_if_find_ifname(b_name)) == NULL ||
	    (mif = mib_find_if(if_idx)) == NULL)
		return (SNMP_ERR_INCONS_VALUE);

	bp = bridge_port_find(if_idx, bif);

	switch (val->v.integer) {
	    case RowStatus_active:
		if (bp == NULL)
		    return (SNMP_ERR_INCONS_VALUE);

		if (bp->span_enable == 0)
		    return (SNMP_ERR_INCONS_VALUE);

		ctx->scratch->int1 = bp->status;
		bp->status = RowStatus_active;
		break;

	    case RowStatus_notInService:
		if (bp == NULL || bp->span_enable == 0 ||
		    bp->status == RowStatus_active)
			return (SNMP_ERR_INCONS_VALUE);

		ctx->scratch->int1 = bp->status;
		bp->status = RowStatus_notInService;

	    case RowStatus_notReady:
		/* FALLTHROUGH */
	    case RowStatus_createAndGo:
		return (SNMP_ERR_INCONS_VALUE);

	    case RowStatus_createAndWait:
		if (bp != NULL)
		    return (SNMP_ERR_INCONS_VALUE);

		if ((bp = bridge_new_port(mif, bif)) == NULL)
			return (SNMP_ERR_GENERR);

		ctx->scratch->int1 = RowStatus_destroy;
		bp->status = RowStatus_notReady;
		break;

	    case RowStatus_destroy:
		if (bp == NULL)
		    return (SNMP_ERR_INCONS_VALUE);

		ctx->scratch->int1 = bp->status;
		bp->status = RowStatus_destroy;
		break;
	}

	return (SNMP_ERR_NOERROR);
}

static int
bridge_port_rollback_status(struct snmp_context *ctx,
	struct snmp_value *val, uint sub)
{
	int32_t if_idx;
	char b_name[IFNAMSIZ];
	struct bridge_if *bif;
	struct bridge_port *bp;

	if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
		return (SNMP_ERR_GENERR);

	if ((bif = bridge_if_find_ifname(b_name)) == NULL ||
	    (bp = bridge_port_find(if_idx, bif)) == NULL)
		return (SNMP_ERR_GENERR);

	if (ctx->scratch->int1 == RowStatus_destroy)
		bridge_port_remove(bp, bif);
	else
		bp->status = ctx->scratch->int1;

	return (SNMP_ERR_NOERROR);
}

static int
bridge_port_commit_status(struct snmp_value *val, uint sub)
{
	int32_t if_idx;
	char b_name[IFNAMSIZ];
	struct bridge_if *bif;
	struct bridge_port *bp;

	if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
		return (SNMP_ERR_GENERR);

	if ((bif = bridge_if_find_ifname(b_name)) == NULL ||
	    (bp = bridge_port_find(if_idx, bif)) == NULL)
		return (SNMP_ERR_GENERR);

	switch (bp->status) {
		case RowStatus_active:
			if (bridge_port_addm(bp, b_name) < 0)
				return (SNMP_ERR_COMMIT_FAILED);
			break;

		case RowStatus_destroy:
			if (bridge_port_delm(bp, b_name) < 0)
				return (SNMP_ERR_COMMIT_FAILED);
			bridge_port_remove(bp, bif);
			break;
	}

	return (SNMP_ERR_NOERROR);
}

static int
bridge_port_set_span_enable(struct snmp_context *ctx,
		struct snmp_value *val, uint sub)
{
	int32_t if_idx;
	char b_name[IFNAMSIZ];
	struct bridge_if *bif;
	struct bridge_port *bp;
	struct mibif *mif;

	if (val->v.integer != begemotBridgeBaseSpanEnabled_enabled &&
	    val->v.integer != begemotBridgeBaseSpanEnabled_disabled)
		return (SNMP_ERR_BADVALUE);

	if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
		return (SNMP_ERR_INCONS_VALUE);

	if ((bif = bridge_if_find_ifname(b_name)) == NULL)
		return (SNMP_ERR_INCONS_VALUE);

	if ((bp = bridge_port_find(if_idx, bif)) == NULL) {
		if ((mif = mib_find_if(if_idx)) == NULL)
			return (SNMP_ERR_INCONS_VALUE);

		if ((bp = bridge_new_port(mif, bif)) == NULL)
			return (SNMP_ERR_GENERR);

		ctx->scratch->int1 = RowStatus_destroy;
	} else if (bp->status == RowStatus_active) {
		return (SNMP_ERR_INCONS_VALUE);
	} else {
		ctx->scratch->int1 = bp->status;
	}

	bp->span_enable = val->v.integer;
	bp->status = RowStatus_notInService;

	return (SNMP_ERR_NOERROR);
}

int
op_begemot_base_port(struct snmp_context *ctx, struct snmp_value *val,
	uint sub, uint iidx __unused, enum snmp_op op)
{
	int8_t status, which;
	const char *bname;
	struct bridge_port *bp;

	if (time(NULL) - ports_list_age > bridge_get_data_maxage())
		bridge_update_all_ports();

	which = val->var.subs[sub - 1];
	status = 0;

	switch (op) {
	    case SNMP_OP_GET:
		if (which == LEAF_begemotBridgeBaseSpanEnabled ||
		    which == LEAF_begemotBridgeBasePortStatus)
			status = 1;
		if ((bp = bridge_port_index_get(&val->var, sub,
		    status)) == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_GETNEXT:
		if (which == LEAF_begemotBridgeBaseSpanEnabled ||
		    which == LEAF_begemotBridgeBasePortStatus)
			status = 1;
		if ((bp = bridge_port_index_getnext(&val->var, sub,
		    status)) == NULL ||
		    bridge_port_index_append(&val->var, sub, bp) < 0)
			return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_SET:
		switch (which) {
		    case LEAF_begemotBridgeBaseSpanEnabled:
			return (bridge_port_set_span_enable(ctx, val, sub));

		    case LEAF_begemotBridgeBasePortStatus:
			return (bridge_port_set_status(ctx, val, sub));

		    case LEAF_begemotBridgeBasePortPrivate:
			if ((bp = bridge_port_index_get(&val->var, sub,
			    status)) == NULL)
				return (SNMP_ERR_NOSUCHNAME);
			if ((bname = bridge_if_find_name(bp->sysindex)) == NULL)
				return (SNMP_ERR_GENERR);
			ctx->scratch->int1 = bp->priv_set;
			return (bridge_port_set_private(bname, bp,
			    val->v.integer));

		    case LEAF_begemotBridgeBasePort:
		    case LEAF_begemotBridgeBasePortIfIndex:
		    case LEAF_begemotBridgeBasePortDelayExceededDiscards:
		    case LEAF_begemotBridgeBasePortMtuExceededDiscards:
			return (SNMP_ERR_NOT_WRITEABLE);
		}
		abort();

	    case SNMP_OP_ROLLBACK:
		switch (which) {
		    case LEAF_begemotBridgeBaseSpanEnabled:
			/* FALLTHROUGH */
		    case LEAF_begemotBridgeBasePortStatus:
			return (bridge_port_rollback_status(ctx, val, sub));
		    case LEAF_begemotBridgeBasePortPrivate:
			if ((bp = bridge_port_index_get(&val->var, sub,
			    status)) == NULL)
				return (SNMP_ERR_GENERR);
			if ((bname = bridge_if_find_name(bp->sysindex)) == NULL)
				return (SNMP_ERR_GENERR);
			return (bridge_port_set_private(bname, bp,
			    ctx->scratch->int1));
		}
		return (SNMP_ERR_NOERROR);

	    case SNMP_OP_COMMIT:
		if (which == LEAF_begemotBridgeBasePortStatus)
			return (bridge_port_commit_status(val, sub));

		return (SNMP_ERR_NOERROR);
	}
	abort();

get:
	switch (which) {
	    case LEAF_begemotBridgeBasePort:
		val->v.integer = bp->port_no;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeBasePortIfIndex:
		val->v.integer = bp->if_idx;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeBaseSpanEnabled:
		val->v.integer = bp->span_enable;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeBasePortDelayExceededDiscards:
		val->v.uint32 = bp->dly_ex_drops;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeBasePortMtuExceededDiscards:
		val->v.uint32 = bp->dly_mtu_drops;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeBasePortStatus:
		val->v.integer = bp->status;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeBasePortPrivate:
		val->v.integer = bp->priv_set;
		return (SNMP_ERR_NOERROR);
	}

	abort();
}

int
op_begemot_stp_port(struct snmp_context *ctx, struct snmp_value *val,
	uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_port *bp;
	const char *b_name;

	if (time(NULL) - ports_list_age > bridge_get_data_maxage())
		bridge_update_all_ports();

	switch (op) {
	    case SNMP_OP_GET:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
		    return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_GETNEXT:
		if ((bp = bridge_port_index_getnext(&val->var, sub, 0)) ==
		    NULL || bridge_port_index_append(&val->var, sub, bp) < 0)
			return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_SET:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		if ((b_name = bridge_if_find_name(bp->sysindex)) == NULL)
			return (SNMP_ERR_GENERR);

		switch (val->var.subs[sub - 1]) {
		    case LEAF_begemotBridgeStpPortPriority:
			if (val->v.integer < 0 || val->v.integer > 255)
			    return (SNMP_ERR_WRONG_VALUE);

			ctx->scratch->int1 = bp->priority;
			if (bridge_port_set_priority(b_name, bp,
			    val->v.integer) < 0)
			    return (SNMP_ERR_GENERR);
			return (SNMP_ERR_NOERROR);

		    case LEAF_begemotBridgeStpPortEnable:
			if (val->v.integer !=
			    (int32_t)begemotBridgeStpPortEnable_enabled ||
			    val->v.integer !=
			    (int32_t)begemotBridgeStpPortEnable_disabled)
			    return (SNMP_ERR_WRONG_VALUE);

			ctx->scratch->int1 = bp->enable;
			if (bridge_port_set_stp_enable(b_name, bp,
			    val->v.integer) < 0)
			    return (SNMP_ERR_GENERR);
			return (SNMP_ERR_NOERROR);

		    case LEAF_begemotBridgeStpPortPathCost:
			if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
			    val->v.integer > SNMP_PORT_MAX_PATHCOST)
			    return (SNMP_ERR_WRONG_VALUE);

			ctx->scratch->int1 = bp->path_cost;
			if (bridge_port_set_path_cost(b_name, bp,
			    val->v.integer) < 0)
			    return (SNMP_ERR_GENERR);
			return (SNMP_ERR_NOERROR);

		    case LEAF_begemotBridgeStpPort:
		    case LEAF_begemotBridgeStpPortState:
		    case LEAF_begemotBridgeStpPortDesignatedRoot:
		    case LEAF_begemotBridgeStpPortDesignatedCost:
		    case LEAF_begemotBridgeStpPortDesignatedBridge:
		    case LEAF_begemotBridgeStpPortDesignatedPort:
		    case LEAF_begemotBridgeStpPortForwardTransitions:
			return (SNMP_ERR_NOT_WRITEABLE);
		}
		abort();

	    case SNMP_OP_ROLLBACK:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL ||
		    (b_name = bridge_if_find_name(bp->sysindex)) == NULL)
			return (SNMP_ERR_GENERR);

		switch (val->var.subs[sub - 1]) {
		    case LEAF_begemotBridgeStpPortPriority:
			bridge_port_set_priority(b_name, bp,
			    ctx->scratch->int1);
			break;
		    case LEAF_begemotBridgeStpPortEnable:
			bridge_port_set_stp_enable(b_name, bp,
			    ctx->scratch->int1);
			break;
		    case LEAF_begemotBridgeStpPortPathCost:
			bridge_port_set_path_cost(b_name, bp,
			    ctx->scratch->int1);
			break;
		}
		return (SNMP_ERR_NOERROR);

	    case SNMP_OP_COMMIT:
		return (SNMP_ERR_NOERROR);
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
	    case LEAF_begemotBridgeStpPort:
		val->v.integer = bp->port_no;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeStpPortPriority:
		val->v.integer = bp->priority;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeStpPortState:
		val->v.integer = bp->state;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeStpPortEnable:
		val->v.integer = bp->enable;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeStpPortPathCost:
		val->v.integer = bp->path_cost;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeStpPortDesignatedRoot:
		return (string_get(val, bp->design_root, SNMP_BRIDGE_ID_LEN));

	    case LEAF_begemotBridgeStpPortDesignatedCost:
		val->v.integer = bp->design_cost;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeStpPortDesignatedBridge:
		return (string_get(val, bp->design_bridge, SNMP_BRIDGE_ID_LEN));

	    case LEAF_begemotBridgeStpPortDesignatedPort:
		return (string_get(val, bp->design_port, 2));

	    case LEAF_begemotBridgeStpPortForwardTransitions:
		val->v.uint32 = bp->fwd_trans;
		return (SNMP_ERR_NOERROR);
	}

	abort();
}

int
op_begemot_stp_ext_port(struct snmp_context *ctx, struct snmp_value *val,
    uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_port *bp;
	const char *b_name;

	if (time(NULL) - ports_list_age > bridge_get_data_maxage())
		bridge_update_all_ports();

	switch (op) {
	    case SNMP_OP_GET:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
		    return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_GETNEXT:
		if ((bp = bridge_port_index_getnext(&val->var, sub, 0)) ==
		    NULL || bridge_port_index_append(&val->var, sub, bp) < 0)
			return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_SET:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
			return (SNMP_ERR_NOSUCHNAME);
		if ((b_name = bridge_if_find_name(bp->sysindex)) == NULL)
			return (SNMP_ERR_GENERR);

		switch (val->var.subs[sub - 1]) {
		    case LEAF_begemotBridgeStpPortAdminEdgePort:
			if (val->v.integer != TruthValue_true &&
			    val->v.integer != TruthValue_false)
			    return (SNMP_ERR_WRONG_VALUE);

			ctx->scratch->int1 = bp->admin_edge;
			if (bridge_port_set_admin_edge(b_name, bp,
			    val->v.integer) < 0)
			    return (SNMP_ERR_GENERR);
			return (SNMP_ERR_NOERROR);

		    case LEAF_begemotBridgeStpPortAdminPointToPoint:
			if (val->v.integer < 0 || val->v.integer >
			    StpPortAdminPointToPointType_auto)
			    return (SNMP_ERR_WRONG_VALUE);

			ctx->scratch->int1 = bp->admin_ptp;
			if (bridge_port_set_admin_ptp(b_name, bp,
			    val->v.integer) < 0)
			    return (SNMP_ERR_GENERR);
			return (SNMP_ERR_NOERROR);

		    case LEAF_begemotBridgeStpPortAdminPathCost:
			if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
			    val->v.integer > SNMP_PORT_MAX_PATHCOST)
			    return (SNMP_ERR_WRONG_VALUE);

			ctx->scratch->int1 = bp->admin_path_cost;
			if (bridge_port_set_path_cost(b_name, bp,
			    val->v.integer) < 0)
			    return (SNMP_ERR_GENERR);
			return (SNMP_ERR_NOERROR);

		    case LEAF_begemotBridgeStpPortProtocolMigration:
		    case LEAF_begemotBridgeStpPortOperEdgePort:
		    case LEAF_begemotBridgeStpPortOperPointToPoint:
			return (SNMP_ERR_NOT_WRITEABLE);
		}
		abort();

	    case SNMP_OP_ROLLBACK:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL ||
		    (b_name = bridge_if_find_name(bp->sysindex)) == NULL)
			return (SNMP_ERR_GENERR);

		switch (val->var.subs[sub - 1]) {
		    case LEAF_begemotBridgeStpPortAdminEdgePort:
			bridge_port_set_admin_edge(b_name, bp,
			    ctx->scratch->int1);
			break;
		    case LEAF_begemotBridgeStpPortAdminPointToPoint:
			bridge_port_set_admin_ptp(b_name, bp,
			    ctx->scratch->int1);
			break;
		    case LEAF_begemotBridgeStpPortAdminPathCost:
			bridge_port_set_path_cost(b_name, bp,
			    ctx->scratch->int1);
			break;
		}
		return (SNMP_ERR_NOERROR);

	    case SNMP_OP_COMMIT:
		return (SNMP_ERR_NOERROR);
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
		case LEAF_begemotBridgeStpPortProtocolMigration:
			val->v.integer = bp->proto_migr;
			return (SNMP_ERR_NOERROR);

		case LEAF_begemotBridgeStpPortAdminEdgePort:
			val->v.integer = bp->admin_edge;
			return (SNMP_ERR_NOERROR);

		case LEAF_begemotBridgeStpPortOperEdgePort:
			val->v.integer = bp->oper_edge;
			return (SNMP_ERR_NOERROR);

		case LEAF_begemotBridgeStpPortAdminPointToPoint:
			val->v.integer = bp->admin_ptp;
			return (SNMP_ERR_NOERROR);

		case LEAF_begemotBridgeStpPortOperPointToPoint:
			val->v.integer = bp->oper_ptp;
			return (SNMP_ERR_NOERROR);

		case LEAF_begemotBridgeStpPortAdminPathCost:
			val->v.integer = bp->admin_path_cost;
			return (SNMP_ERR_NOERROR);
	}

	abort();
}

int
op_begemot_tp_port(struct snmp_context *c __unused, struct snmp_value *val,
	uint sub, uint iidx __unused, enum snmp_op op)
{
	struct bridge_port *bp;

	if (time(NULL) - ports_list_age > bridge_get_data_maxage())
		bridge_update_all_ports();

	switch (op) {
	    case SNMP_OP_GET:
		if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
		    return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_GETNEXT:
		if ((bp = bridge_port_index_getnext(&val->var, sub, 0)) ==
		    NULL || bridge_port_index_append(&val->var, sub, bp) < 0)
		    return (SNMP_ERR_NOSUCHNAME);
		goto get;

	    case SNMP_OP_SET:
		return (SNMP_ERR_NOT_WRITEABLE);

	    case SNMP_OP_ROLLBACK:
	    case SNMP_OP_COMMIT:
		break;
	}
	abort();

get:
	switch (val->var.subs[sub - 1]) {
	    case LEAF_begemotBridgeTpPort:
		val->v.integer = bp->port_no;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeTpPortMaxInfo:
		val->v.integer = bp->max_info;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeTpPortInFrames:
		val->v.uint32 = bp->in_frames;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeTpPortOutFrames:
		val->v.uint32 = bp->out_frames;
		return (SNMP_ERR_NOERROR);

	    case LEAF_begemotBridgeTpPortInDiscards:
		val->v.uint32 = bp->in_drops;
		return (SNMP_ERR_NOERROR);
	}

	abort();
}
