/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
 * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
 *
 * Permission to use, copy, modify, and/or 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 <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <arpa/inet.h>

#include "../lldpctl.h"
#include "../../log.h"
#include "../atom.h"
#include "../helpers.h"

static struct atom_map lldpd_protocol_map = {
	.key = lldpctl_k_port_protocol,
	.map = {
		{ LLDPD_MODE_LLDP,	"LLDP" },
		{ LLDPD_MODE_CDPV1,	"CDPv1"},
		{ LLDPD_MODE_CDPV2,	"CDPv2"},
		{ LLDPD_MODE_EDP,	"EDP" },
		{ LLDPD_MODE_FDP,	"FDP"},
		{ LLDPD_MODE_SONMP,	"SONMP"},
		{ 0, NULL },
	}
};

ATOM_MAP_REGISTER(lldpd_protocol_map, 3);

static lldpctl_map_t port_id_subtype_map[] = {
	{ LLDP_PORTID_SUBTYPE_IFNAME,   "ifname"},
	{ LLDP_PORTID_SUBTYPE_IFALIAS,  "ifalias" },
	{ LLDP_PORTID_SUBTYPE_LOCAL,    "local" },
	{ LLDP_PORTID_SUBTYPE_LLADDR,   "mac" },
	{ LLDP_PORTID_SUBTYPE_ADDR,     "ip" },
	{ LLDP_PORTID_SUBTYPE_PORT,     "unhandled" },
	{ LLDP_PORTID_SUBTYPE_AGENTCID, "unhandled" },
	{ 0, NULL},
};

static struct atom_map port_status_map = {
	.key = lldpctl_k_port_status,
	.map = {
		{ LLDPD_RXTX_TXONLY,   "TX only" },
		{ LLDPD_RXTX_RXONLY,   "RX only" },
		{ LLDPD_RXTX_DISABLED, "disabled" },
		{ LLDPD_RXTX_BOTH,     "RX and TX" },
		{ 0, NULL },
	}
};

ATOM_MAP_REGISTER(port_status_map, 3);

#ifdef ENABLE_DOT3
static lldpctl_map_t operational_mau_type_values[] = {
	{ LLDP_DOT3_MAU_AUI,		"AUI - No internal MAU, view from AUI" },
	{ LLDP_DOT3_MAU_10BASE5,	"10Base5 - Thick coax MAU" },
	{ LLDP_DOT3_MAU_FOIRL,		"Foirl - FOIRL MAU" },
	{ LLDP_DOT3_MAU_10BASE2,	"10Base2 - Thin coax MAU" },
	{ LLDP_DOT3_MAU_10BASET,	"10BaseT - UTP MAU" },
	{ LLDP_DOT3_MAU_10BASEFP,	"10BaseFP - Passive fiber MAU" },
	{ LLDP_DOT3_MAU_10BASEFB,	"10BaseFB - Sync fiber MAU" },
	{ LLDP_DOT3_MAU_10BASEFL,	"10BaseFL - Async fiber MAU" },
	{ LLDP_DOT3_MAU_10BROAD36,	"10Broad36 - Broadband DTE MAU" },
	{ LLDP_DOT3_MAU_10BASETHD,	"10BaseTHD - UTP MAU, half duplex mode" },
	{ LLDP_DOT3_MAU_10BASETFD,	"10BaseTFD - UTP MAU, full duplex mode" },
	{ LLDP_DOT3_MAU_10BASEFLHD,	"10BaseFLHD - Async fiber MAU, half duplex mode" },
	{ LLDP_DOT3_MAU_10BASEFLFD,	"10BaseFLDF - Async fiber MAU, full duplex mode" },
	{ LLDP_DOT3_MAU_100BASET4,	"100BaseT4 - 4 pair category 3 UTP" },
	{ LLDP_DOT3_MAU_100BASETXHD,	"100BaseTXHD - 2 pair category 5 UTP, half duplex mode" },
	{ LLDP_DOT3_MAU_100BASETXFD,	"100BaseTXFD - 2 pair category 5 UTP, full duplex mode" },
	{ LLDP_DOT3_MAU_100BASEFXHD,	"100BaseFXHD - X fiber over PMT, half duplex mode" },
	{ LLDP_DOT3_MAU_100BASEFXFD,	"100BaseFXFD - X fiber over PMT, full duplex mode" },
	{ LLDP_DOT3_MAU_100BASET2HD,	"100BaseT2HD - 2 pair category 3 UTP, half duplex mode" },
	{ LLDP_DOT3_MAU_100BASET2FD,	"100BaseT2FD - 2 pair category 3 UTP, full duplex mode" },
	{ LLDP_DOT3_MAU_1000BASEXHD,	"1000BaseXHD - PCS/PMA, unknown PMD, half duplex mode" },
	{ LLDP_DOT3_MAU_1000BASEXFD,	"1000BaseXFD - PCS/PMA, unknown PMD, full duplex mode" },
	{ LLDP_DOT3_MAU_1000BASELXHD,	"1000BaseLXHD - Fiber over long-wavelength laser, half duplex mode" },
	{ LLDP_DOT3_MAU_1000BASELXFD,	"1000BaseLXFD - Fiber over long-wavelength laser, full duplex mode" },
	{ LLDP_DOT3_MAU_1000BASESXHD,	"1000BaseSXHD - Fiber over short-wavelength laser, half duplex mode" },
	{ LLDP_DOT3_MAU_1000BASESXFD,	"1000BaseSXFD - Fiber over short-wavelength laser, full duplex mode" },
	{ LLDP_DOT3_MAU_1000BASECXHD,	"1000BaseCXHD - Copper over 150-Ohm balanced cable, half duplex mode" },
	{ LLDP_DOT3_MAU_1000BASECXFD,	"1000BaseCXFD - Copper over 150-Ohm balanced cable, full duplex mode" },
	{ LLDP_DOT3_MAU_1000BASETHD,	"1000BaseTHD - Four-pair Category 5 UTP, half duplex mode" },
	{ LLDP_DOT3_MAU_1000BASETFD,	"1000BaseTFD - Four-pair Category 5 UTP, full duplex mode" },
	{ LLDP_DOT3_MAU_10GIGBASEX,	"10GigBaseX - X PCS/PMA, unknown PMD." },
	{ LLDP_DOT3_MAU_10GIGBASELX4,	"10GigBaseLX4 - X fiber over WWDM optics" },
	{ LLDP_DOT3_MAU_10GIGBASER,	"10GigBaseR - R PCS/PMA, unknown PMD." },
	{ LLDP_DOT3_MAU_10GIGBASEER,	"10GigBaseER - R fiber over 1550 nm optics" },
	{ LLDP_DOT3_MAU_10GIGBASELR,	"10GigBaseLR - R fiber over 1310 nm optics" },
	{ LLDP_DOT3_MAU_10GIGBASESR,	"10GigBaseSR - R fiber over 850 nm optics" },
	{ LLDP_DOT3_MAU_10GIGBASEW,	"10GigBaseW - W PCS/PMA, unknown PMD." },
	{ LLDP_DOT3_MAU_10GIGBASEEW,	"10GigBaseEW - W fiber over 1550 nm optics" },
	{ LLDP_DOT3_MAU_10GIGBASELW,	"10GigBaseLW - W fiber over 1310 nm optics" },
	{ LLDP_DOT3_MAU_10GIGBASESW,	"10GigBaseSW - W fiber over 850 nm optics" },
	{ LLDP_DOT3_MAU_10GIGBASECX4,	"10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable" },
	{ LLDP_DOT3_MAU_2BASETL,	"2BaseTL - Voice grade UTP copper, up to 2700m, optional PAF" },
	{ LLDP_DOT3_MAU_10PASSTS,	"10PassTS - Voice grade UTP copper, up to 750m, optional PAF" },
	{ LLDP_DOT3_MAU_100BASEBX10D,	"100BaseBX10D - One single-mode fiber OLT, long wavelength, 10km" },
	{ LLDP_DOT3_MAU_100BASEBX10U,	"100BaseBX10U - One single-mode fiber ONU, long wavelength, 10km" },
	{ LLDP_DOT3_MAU_100BASELX10,	"100BaseLX10 - Two single-mode fibers, long wavelength, 10km" },
	{ LLDP_DOT3_MAU_1000BASEBX10D,	"1000BaseBX10D - One single-mode fiber OLT, long wavelength, 10km" },
	{ LLDP_DOT3_MAU_1000BASEBX10U,	"1000BaseBX10U - One single-mode fiber ONU, long wavelength, 10km" },
	{ LLDP_DOT3_MAU_1000BASELX10,	"1000BaseLX10 - Two sigle-mode fiber, long wavelength, 10km" },
	{ LLDP_DOT3_MAU_1000BASEPX10D,	"1000BasePX10D - One single-mode fiber EPON OLT, 10km" },
	{ LLDP_DOT3_MAU_1000BASEPX10U,	"1000BasePX10U - One single-mode fiber EPON ONU, 10km" },
	{ LLDP_DOT3_MAU_1000BASEPX20D,	"1000BasePX20D - One single-mode fiber EPON OLT, 20km" },
	{ LLDP_DOT3_MAU_1000BASEPX20U,	"1000BasePX20U - One single-mode fiber EPON ONU, 20km" },
	{ LLDP_DOT3_MAU_10GBASET,	"10GbaseT - Four-pair Category 6A or better, full duplex mode only" },
	{ LLDP_DOT3_MAU_10GBASELRM,	"10GbaseLRM - R multimode fiber over 1310 nm optics" },
	{ LLDP_DOT3_MAU_1000BASEKX,	"1000baseKX - X backplane, full duplex mode only" },
	{ LLDP_DOT3_MAU_10GBASEKX4,	"10GbaseKX4 - 4 lane X backplane, full duplex mode only" },
	{ LLDP_DOT3_MAU_10GBASEKR,	"10GbaseKR - R backplane, full duplex mode only" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXD1,	"10G1GbasePRXD1 - One single-mode fiber asymmetric-rate EPON OLT, low power budget (PRX10)" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXD2, "10G1GbasePRXD2 - One single-mode fiber asymmetric-rate EPON OLT, medium power budget (PRX20)" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXD3, "10G1GbasePRXD3 - One single-mode fiber asymmetric-rate EPON OLT, high power budget (PRX30)" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXU1, "10G1GbasePRXU1 - One single-mode fiber asymmetric-rate EPON ONU, low power budget (PRX10)" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXU2, "10G1GbasePRXU2 - One single-mode fiber asymmetric-rate EPON ONU, medium power budget (PRX20)" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXU3, "10G1GbasePRXU3 - One single-mode fiber asymmetric-rate EPON ONU, high power budget (PRX30)" },
	{ LLDP_DOT3_MAU_10GBASEPRD1,	"10GbasePRD1 - One single-mode fiber symmetric-rate EPON OLT, low power budget (PR10)" },
	{ LLDP_DOT3_MAU_10GBASEPRD2,	"10GbasePRD2 - One single-mode fiber symmetric-rate EPON OLT, medium power budget (PR20)" },
	{ LLDP_DOT3_MAU_10GBASEPRD3,	"10GbasePRD3 - One single-mode fiber symmetric-rate EPON OLT, high power budget (PR30)" },
	{ LLDP_DOT3_MAU_10GBASEPRU1,	"10GbasePRU1 - One single-mode fiber symmetric-rate EPON ONU, low and medium power budget" },
	{ LLDP_DOT3_MAU_10GBASEPRU3,	"10GbasePRU3 - One single-mode fiber symmetric-rate EPON ONU, high power budget (PR30)" },
	{ LLDP_DOT3_MAU_40GBASEKR4,	"40GbaseKR4 - 40GBASE-R PCS/PMA over an electrical backplane" },
	{ LLDP_DOT3_MAU_40GBASECR4,	"40GbaseCR4 - 40GBASE-R PCS/PMA over 4 lane shielded copper balanced cable" },
	{ LLDP_DOT3_MAU_40GBASESR4,	"40GbaseSR4 - 40GBASE-R PCS/PMA over 4 lane multimode fiber" },
	{ LLDP_DOT3_MAU_40GBASEFR,	"40GbaseFR - 40GBASE-R PCS/PMA over single mode fiber" },
	{ LLDP_DOT3_MAU_40GBASELR4,	"40GbaseLR4 - 40GBASE-R PCS/PMA over 4 WDM lane single mode fiber" },
	{ LLDP_DOT3_MAU_100GBASECR10,   "100GbaseCR10 - 100GBASE-R PCS/PMA over 10 lane shielded copper balanced cable" },
	{ LLDP_DOT3_MAU_100GBASESR10,   "100GbaseSR10 - 100GBASE-R PCS/PMA over 10 lane multimode fiber" },
	{ LLDP_DOT3_MAU_100GBASELR4,	"100GbaseLR4 - 100GBASE-R PCS/PMA over 4 WDM lane single mode fiber, long reach" },
	{ LLDP_DOT3_MAU_100GBASEER4,	"100GbaseER4 - 100GBASE-R PCS/PMA over 4 WDM lane single mode fiber PMD, extended reach" },
	{ LLDP_DOT3_MAU_1000BASET1,	"1000baseT1 - 1000BASE-T1 single balanced twisted-pair copper cabling PHY" },
	{ LLDP_DOT3_MAU_1000BASEPX30D,	"1000basePX30D - One single-mode fiber EPON OLT, 20km, 1:32 split ratio" },
	{ LLDP_DOT3_MAU_1000BASEPX30U,	"1000basePX30U - One single-mode fiber EPON ONU, 20km, 1:32 split ratio" },
	{ LLDP_DOT3_MAU_1000BASEPX40D,	"1000basePX40D - One single-mode fiber EPON OLT, 20km, 1:64 split ratio" },
	{ LLDP_DOT3_MAU_1000BASEPX40U,	"1000basePX40U - One single-mode fiber EPON ONU, 20km, 1:64 split ratio" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXD4,	"10G1GbasePRXD4 - One single-mode fiber asymmetric-rate EPON OLT, supporting  extended power budget (PRX40)" },
	{ LLDP_DOT3_MAU_10G1GBASEPRXU4,	"10G1GbasePRXU4 - One single-mode fiber asymmetric-rate EPON ONU, supporting  extended power budget (PRX40)" },
	{ LLDP_DOT3_MAU_10GBASEPRD4,	"10GbasePRD4 - One single-mode fiber symmetric-rate EPON OLT, supporting  extended power budget (PR40)" },
	{ LLDP_DOT3_MAU_10GBASEPRU4,	"10GbasePRU4 - One single-mode fiber symmetric-rate EPON ONU, supporting  extended power budget (PR40)" },
	{ LLDP_DOT3_MAU_25GBASECR,	"25GbaseCR - 25GBASE-R PCS/PMA over shielded balanced copper cable" },
	{ LLDP_DOT3_MAU_25GBASECRS,	"25GbaseCRS - 25GBASE-R PCS/PMA over shielded balanced copper cable  without RS-FEC" },
	{ LLDP_DOT3_MAU_25GBASEKR,	"25GbaseKR - 25GBASE-R PCS/PMA over an electrical backplane" },
	{ LLDP_DOT3_MAU_25GBASEKRS,	"25GbaseKRS - 25GBASE-R PCS/PMA over an electrical backplane without RS-FEC" },
	{ LLDP_DOT3_MAU_25GBASER,	"25GbaseR - 25GBASE-R PCS/PMA over undefined PMD" },
	{ LLDP_DOT3_MAU_25GBASESR,	"25GbaseSR - 25GBASE-R PCS/PMA over multimode fiber" },
	{ LLDP_DOT3_MAU_25GBASET,	"25GbaseT - Four-pair twisted-pair balanced copper cabling" },
	{ LLDP_DOT3_MAU_40GBASEER4,	"40GbaseER4 - 40GBASE-R PCS/PMA over 4 WDM lane single mode fiber" },
	{ LLDP_DOT3_MAU_40GBASER,	"40GbaseR - 40GBASE-R PCS as over undefined PMA/PMD" },
	{ LLDP_DOT3_MAU_40GBASET,	"40GbaseT - Four-pair twisted-pair balanced copper cabling" },
	{ LLDP_DOT3_MAU_100GBASECR4,	"100GbaseCR4 - 100GBASE-R PCS/PMA over 4 lane shielded copper balanced cable" },
	{ LLDP_DOT3_MAU_100GBASEKR4,	"100GbaseKR4 - 100GBASE-R PCS/PMA over an electrical backplane" },
	{ LLDP_DOT3_MAU_100GBASEKP4,	"100GbaseKP4 - 100GBASE-P PCS/PMA over an electrical backplane PMD" },
	{ LLDP_DOT3_MAU_100GBASER,	"100GbaseR - 100GBASE-R Multi-lane PCS over undefined PMA/PMD" },
	{ LLDP_DOT3_MAU_100GBASESR4,	"100GbaseSR4 - 100GBASE-R PCS/PMA over 4 lane multimode fiber" },
	{ LLDP_DOT3_MAU_2P5GIGT,	"2p5GigT - 2.5GBASE-T Four-pair twisted-pair balanced copper cabling PHY" },
	{ LLDP_DOT3_MAU_5GIGT,		"5GigT - 5GBASE-T Four-pair twisted-pair balanced copper cabling PHY" },
	{ LLDP_DOT3_MAU_100BASET1,	"100baseT1 - 100BASE-T1 Single balanced twisted-pair copper cabling PHY" },
	{ LLDP_DOT3_MAU_1000BASERHA,	"1000baseRHA - 1000BASE-RHA Plastic optical fiber PHY" },
	{ LLDP_DOT3_MAU_1000BASERHB,	"1000baseRHB - 1000BASE-RHB Plastic optical fiber PHY" },
	{ LLDP_DOT3_MAU_1000BASERHC,	"1000baseRHC - 1000BASE-RHC Plastic optical fiber PHY" },
	{ LLDP_DOT3_MAU_2P5GBASEKX,	"2p5GbaseKX - 2.5GBASE-X PMD over an electrical backplane" },
	{ LLDP_DOT3_MAU_2P5GBASEX,	"2p5GbaseX - 2.5GBASE-X PCS/PMA over undefined PMD" },
	{ LLDP_DOT3_MAU_5GBASEKR,	"5GbaseKR - 5GBASE-KR PMD over an electrical backplane" },
	{ LLDP_DOT3_MAU_5GBASER,	"5GbaseR - 5GBASE-R PCS/PMA over undefined PMD" },
	{ LLDP_DOT3_MAU_10GPASSXR,	"10GpassXR - Coax cable distribution network PHY continuous downstream/burst mode upstream PHY" },
	{ LLDP_DOT3_MAU_25GBASELR,	"25GbaseLR - 25GBASE-R PCS/PMA over single-mode fiber PMD, with long reach" },
	{ LLDP_DOT3_MAU_25GBASEER,	"25GbaseER - 25GBASE-R PCS/PMA over single-mode fiber PMD, with extended reach" },
	{ LLDP_DOT3_MAU_50GBASER,	"50GbaseR - 50GBASE-R Multi-lane PCS over undefined PMA/PMD" },
	{ LLDP_DOT3_MAU_50GBASECR,	"50GbaseCR - 50GBASE-R PCS/PMA over shielded copper balanced cable PMD" },
	{ LLDP_DOT3_MAU_50GBASEKR,	"50GbaseKR - 50GBASE-R PCS/PMA over an electrical backplane PMD" },
	{ LLDP_DOT3_MAU_50GBASESR,	"50GbaseSR - 50GBASE-R PCS/PMA over multimode fiber PMD" },
	{ LLDP_DOT3_MAU_50GBASEFR,	"50GbaseFR - 50GBASE-R PCS/PMA over single mode fiber PMD with reach up to at least 2 km" },
	{ LLDP_DOT3_MAU_50GBASELR,	"50GbaseLR - 50GBASE-R PCS/PMA over single mode fiber PMD with reach up to at least 10 km" },
	{ LLDP_DOT3_MAU_50GBASEER,	"50GbaseER - 50GBASE-R PCS/PMA over single-mode fiber PMD with reach up to at least 40 km" },
	{ LLDP_DOT3_MAU_100GBASECR2,	"100GbaseCR2 - 100GBASE-R PCS/PMA over 2 lane shielded copper balanced cable PMD" },
	{ LLDP_DOT3_MAU_100GBASEKR2,	"100GbaseKR2 - 100GBASE-R PCS/PMA over an electrical backplane PMD" },
	{ LLDP_DOT3_MAU_100GBASESR2,	"100GbaseSR2 - 100GBASE-R PCS/PMA over 2 lane multimode fiber PMD" },
	{ LLDP_DOT3_MAU_100GBASEDR,	"100GbaseDR - 100GBASE-R PCS/PMA over single mode fiber PMD" },
	{ LLDP_DOT3_MAU_200GBASER,	"200GbaseR - 200GBASE-R Multi-lane PCS over undefined PMA/PMD" },
	{ LLDP_DOT3_MAU_200GBASEDR4,	"200GbaseDR4 - 200GBASE-R PCS/PMA over 4-lane single-mode fiber PMD" },
	{ LLDP_DOT3_MAU_200GBASEFR4,	"200GbaseFR4 - 200GBASE-R PCS/PMA over 4 WDM lane single-mode fiber PMD with reach up to at least 2 km" },
	{ LLDP_DOT3_MAU_200GBASELR4,	"200GbaseLR4 - 200GBASE-R PCS/PMA over 4 WDM lane single-mode fiber PMD with reach up to at least 10 km" },
	{ LLDP_DOT3_MAU_200GBASECR4,	"200GbaseCR4 - 200GBASE-R PCS/PMA over 4 lane shielded copper balanced cable PMD" },
	{ LLDP_DOT3_MAU_200GBASEKR4,	"200GbaseKR4 - 200GBASE-R PCS/PMA over an electrical backplane PMD" },
	{ LLDP_DOT3_MAU_200GBASESR4,	"200GbaseSR4 - 200GBASE-R PCS/PMA over 4 lane multimode fiber PMD" },
	{ LLDP_DOT3_MAU_200GBASEER4,	"200GbaseER4 - 200GBASE-R PCS/PMA over 4 WDM lane single-mode fiber PMD with reach up to at least 40 km" },
	{ LLDP_DOT3_MAU_400GBASER,	"400GbaseR - 400GBASE-R Multi-lane PCS over undefined PMA/PMD" },
	{ LLDP_DOT3_MAU_400GBASESR16,	"400GbaseSR16 - 400GBASE-R PCS/PMA over 16-lane multimode fiber PMD" },
	{ LLDP_DOT3_MAU_400GBASEDR4,	"400GbaseDR4 - 400GBASE-R PCS/PMA over 4-lane single-mode fiber PMD" },
	{ LLDP_DOT3_MAU_400GBASEFR8,	"400GbaseFR8 - 400GBASE-R PCS/PMA over 8 WDM lane single-mode fiber PMD with reach up to at least 2 km" },
	{ LLDP_DOT3_MAU_400GBASELR8,	"400GbaseLR8 - 400GBASE-R PCS/PMA over 8 WDM lane single-mode fiber PMD with reach up to at least 10 km" },
	{ LLDP_DOT3_MAU_400GBASEER8,	"400GbaseER8 - 400GBASE-R PCS/PMA over 8 WDM lane single-mode fiber PMD with reach up to at least 40 km" },
	{ LLDP_DOT3_MAU_10BASET1L,	"10baseT1L - 10BASE-T1L Single balanced pair PHY" },
	{ LLDP_DOT3_MAU_10BASET1SHD,	"10baseT1SHD - 10BASE-T1S Single balanced pair PHY, half duplex mode" },
	{ LLDP_DOT3_MAU_10BASET1SMD,	"10baseT1SMD - 10BASE-T1S Single balanced pair PHY, multidrop mode" },
	{ LLDP_DOT3_MAU_10BASET1SFD,	"10baseT1SFD - 10BASE-T1S Single balanced pair PHY, full duplex mode" },
	{ 0, NULL }
};
#endif

static lldpctl_atom_iter_t*
_lldpctl_atom_iter_ports_list(lldpctl_atom_t *atom)
{
	struct _lldpctl_atom_any_list_t *plist =
	    (struct _lldpctl_atom_any_list_t *)atom;
	return (lldpctl_atom_iter_t*)TAILQ_FIRST(&plist->parent->hardware->h_rports);
}

static lldpctl_atom_iter_t*
_lldpctl_atom_next_ports_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
{
	struct lldpd_port *port = (struct lldpd_port *)iter;
	return (lldpctl_atom_iter_t*)TAILQ_NEXT(port, p_entries);
}

static lldpctl_atom_t*
_lldpctl_atom_value_ports_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
{
	struct lldpd_port *port = (struct lldpd_port *)iter;
	return _lldpctl_new_atom(atom->conn, atom_port, 0, NULL, port,
	    ((struct _lldpctl_atom_any_list_t *)atom)->parent);
}

static int
_lldpctl_atom_new_port(lldpctl_atom_t *atom, va_list ap)
{
	struct _lldpctl_atom_port_t *port =
	    (struct _lldpctl_atom_port_t *)atom;
	port->local = va_arg(ap, int);
	port->hardware = va_arg(ap, struct lldpd_hardware*);
	port->port = va_arg(ap, struct lldpd_port*);
	port->parent = va_arg(ap, struct _lldpctl_atom_port_t*);
	if (port->parent)
		lldpctl_atom_inc_ref((lldpctl_atom_t*)port->parent);

	if (port->port) {
		/* Internal atom. We are the parent, but our reference count is
		 * not incremented. */
		port->chassis = _lldpctl_new_atom(atom->conn, atom_chassis,
			    port->port->p_chassis, port, 1);
	}
	return 1;
}

TAILQ_HEAD(chassis_list, lldpd_chassis);

static void
add_chassis(struct chassis_list *chassis_list,
	struct lldpd_chassis *chassis)
{
	struct lldpd_chassis *one_chassis;
	TAILQ_FOREACH(one_chassis, chassis_list, c_entries) {
		if (one_chassis == chassis) return;
	}
	TAILQ_INSERT_TAIL(chassis_list,
	    chassis, c_entries);
}

static void
_lldpctl_atom_free_port(lldpctl_atom_t *atom)
{
	struct _lldpctl_atom_port_t *port =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_hardware *hardware = port->hardware;
	struct lldpd_chassis  *one_chassis, *one_chassis_next;
	struct lldpd_port     *one_port;

	/* Free internal chassis atom. Should be freed immediately since we
	 * should have the only reference. */
	lldpctl_atom_dec_ref((lldpctl_atom_t*)port->chassis);

	/* We need to free the whole struct lldpd_hardware: local port, local
	 * chassis and remote ports... The same chassis may be present several
	 * times. We build a list of chassis (we don't use reference count). */
	struct chassis_list chassis_list;
	TAILQ_INIT(&chassis_list);

	if (port->parent) lldpctl_atom_dec_ref((lldpctl_atom_t*)port->parent);
	else if (!hardware && port->port) {
		/* No parent, no hardware, we assume a single neighbor: one
		 * port, one chassis. */
		if (port->port->p_chassis) {
			lldpd_chassis_cleanup(port->port->p_chassis, 1);
			port->port->p_chassis = NULL;
		}
		lldpd_port_cleanup(port->port, 1);
		free(port->port);
	}
	if (!hardware) return;

	add_chassis(&chassis_list, port->port->p_chassis);
	TAILQ_FOREACH(one_port, &hardware->h_rports, p_entries)
		add_chassis(&chassis_list, one_port->p_chassis);

	/* Free hardware port */
	lldpd_remote_cleanup(hardware, NULL, 1);
	lldpd_port_cleanup(port->port, 1);
	free(port->hardware);

	/* Free list of chassis */
	for (one_chassis = TAILQ_FIRST(&chassis_list);
	     one_chassis != NULL;
	     one_chassis = one_chassis_next) {
		one_chassis_next = TAILQ_NEXT(one_chassis, c_entries);
		lldpd_chassis_cleanup(one_chassis, 1);
	}
}

static lldpctl_atom_t*
_lldpctl_atom_get_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_port     *port     = p->port;
	struct lldpd_hardware *hardware = p->hardware;

	/* Local port only */
	if (hardware != NULL) {
		switch (key) {
		case lldpctl_k_port_neighbors:
			return _lldpctl_new_atom(atom->conn, atom_ports_list, p);
		default: break;
		}
	}

	/* Local and remote port */
	switch (key) {
	case lldpctl_k_port_chassis:
		if (port->p_chassis) {
			return _lldpctl_new_atom(atom->conn, atom_chassis,
			    port->p_chassis, p, 0);
		}
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
#ifdef ENABLE_DOT3
	case lldpctl_k_port_dot3_power:
		return _lldpctl_new_atom(atom->conn, atom_dot3_power,
		    p);
#endif
#ifdef ENABLE_DOT1
	case lldpctl_k_port_vlans:
		return _lldpctl_new_atom(atom->conn, atom_vlans_list,
		    p);
	case lldpctl_k_port_ppvids:
		return _lldpctl_new_atom(atom->conn, atom_ppvids_list,
		    p);
	case lldpctl_k_port_pis:
		return _lldpctl_new_atom(atom->conn, atom_pis_list,
		    p);
#endif
#ifdef ENABLE_LLDPMED
	case lldpctl_k_port_med_policies:
		return _lldpctl_new_atom(atom->conn, atom_med_policies_list,
		    p);
	case lldpctl_k_port_med_locations:
		return _lldpctl_new_atom(atom->conn, atom_med_locations_list,
		    p);
	case lldpctl_k_port_med_power:
		return _lldpctl_new_atom(atom->conn, atom_med_power, p);
#endif
#ifdef ENABLE_CUSTOM
	case lldpctl_k_custom_tlvs:
		return _lldpctl_new_atom(atom->conn, atom_custom_list, p);
#endif
	default:
		/* Compatibility: query the associated chassis too */
		if (port->p_chassis)
			return lldpctl_atom_get(p->chassis, key);
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}
}

static lldpctl_atom_t*
_lldpctl_atom_set_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key, lldpctl_atom_t *value)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_hardware *hardware = p->hardware;
	struct lldpd_port_set set = {};
	int rc;
	char *canary = NULL;

#ifdef ENABLE_DOT3
	struct _lldpctl_atom_dot3_power_t *dpow;
#endif
#ifdef ENABLE_LLDPMED
	struct _lldpctl_atom_med_power_t *mpow;
	struct _lldpctl_atom_med_policy_t *mpol;
	struct _lldpctl_atom_med_location_t *mloc;
#endif
#ifdef ENABLE_CUSTOM
	struct _lldpctl_atom_custom_t    *custom;
#endif

	/* Local and default port only */
	if (!p->local) {
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	set.vlan_tx_enabled = -1;

	switch (key) {
	case lldpctl_k_port_id:
		set.local_id = p->port->p_id;
		break;
	case lldpctl_k_port_descr:
		set.local_descr = p->port->p_descr;
		break;
	case lldpctl_k_port_status:
		set.rxtx = LLDPD_RXTX_FROM_PORT(p->port);
		break;
	case lldpctl_k_port_vlan_tx:
		set.vlan_tx_tag = p->port->p_vlan_tx_tag;
		set.vlan_tx_enabled = p->port->p_vlan_tx_enabled;
		break;
#ifdef ENABLE_DOT3
	case lldpctl_k_port_dot3_power:
		if (value->type != atom_dot3_power) {
			SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
			return NULL;
		}

		dpow = (struct _lldpctl_atom_dot3_power_t *)value;
		set.dot3_power = &dpow->parent->port->p_power;
		break;
#endif
#ifdef ENABLE_LLDPMED
	case lldpctl_k_port_med_power:
		if (value->type != atom_med_power) {
			SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
			return NULL;
		}

		mpow = (struct _lldpctl_atom_med_power_t *)value;
		set.med_power = &mpow->parent->port->p_med_power;
		break;
	case lldpctl_k_port_med_policies:
		if (value->type != atom_med_policy) {
			SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
			return NULL;
		}
		mpol = (struct _lldpctl_atom_med_policy_t *)value;
		set.med_policy = mpol->policy;
		break;
	case lldpctl_k_port_med_locations:
		if (value->type != atom_med_location) {
			SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
			return NULL;
		}
		mloc = (struct _lldpctl_atom_med_location_t *)value;
		set.med_location = mloc->location;
		break;
#endif
#ifdef ENABLE_CUSTOM
	case lldpctl_k_custom_tlvs_clear:
		set.custom_list_clear = 1;
		break;
	case lldpctl_k_custom_tlv:
		if (value->type != atom_custom) {
			SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
			return NULL;
		}
		custom = (struct _lldpctl_atom_custom_t *)value;
		set.custom = custom->tlv;
		set.custom_tlv_op = custom->op;
		break;
#endif
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	set.ifname = hardware ? hardware->h_ifname : "";

	if (asprintf(&canary, "%d%p%s", key, value, set.ifname) == -1) {
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
		return NULL;
	}
	rc = _lldpctl_do_something(atom->conn,
	    CONN_STATE_SET_PORT_SEND, CONN_STATE_SET_PORT_RECV,
	    canary,
	    SET_PORT, &set, &MARSHAL_INFO(lldpd_port_set),
	    NULL, NULL);
	free(canary);
	if (rc == 0) return atom;
	return NULL;
}

static const char*
_lldpctl_atom_get_str_port(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_port     *port     = p->port;
	struct lldpd_hardware *hardware = p->hardware;
	char *ipaddress = NULL; size_t len;

	/* Local port only */
	switch (key) {
	case lldpctl_k_port_name:
		if (hardware != NULL) return hardware->h_ifname;
		break;
	case lldpctl_k_port_status:
		if (p->local) return map_lookup(port_status_map.map,
		    LLDPD_RXTX_FROM_PORT(port));
		break;
	default: break;
	}

	if (!port)
		return NULL;

	/* Local and remote port */
	switch (key) {
	case lldpctl_k_port_protocol:
		return map_lookup(lldpd_protocol_map.map, port->p_protocol);
	case lldpctl_k_port_id_subtype:
		return map_lookup(port_id_subtype_map, port->p_id_subtype);
	case lldpctl_k_port_id:
		switch (port->p_id_subtype) {
		case LLDP_PORTID_SUBTYPE_IFNAME:
		case LLDP_PORTID_SUBTYPE_IFALIAS:
		case LLDP_PORTID_SUBTYPE_LOCAL:
			return port->p_id;
		case LLDP_PORTID_SUBTYPE_LLADDR:
			return _lldpctl_dump_in_atom(atom,
			    (uint8_t*)port->p_id, port->p_id_len,
			    ':', 0);
		case LLDP_PORTID_SUBTYPE_ADDR:
			switch (port->p_id[0]) {
			case LLDP_MGMT_ADDR_IP4: len = INET_ADDRSTRLEN + 1; break;
			case LLDP_MGMT_ADDR_IP6: len = INET6_ADDRSTRLEN + 1; break;
			default: len = 0;
			}
			if (len > 0) {
				ipaddress = _lldpctl_alloc_in_atom(atom, len);
				if (!ipaddress) return NULL;
				if (inet_ntop((port->p_id[0] == LLDP_MGMT_ADDR_IP4)?
					AF_INET:AF_INET6,
					&port->p_id[1], ipaddress, len) == NULL)
					break;
				return ipaddress;
			}
			break;
		}
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	case lldpctl_k_port_descr:
		return port->p_descr;

#ifdef ENABLE_DOT3
	case lldpctl_k_port_dot3_mautype:
		return map_lookup(operational_mau_type_values,
		    port->p_macphy.mau_type);
#endif

	default:
		/* Compatibility: query the associated chassis too */
		return lldpctl_atom_get_str(p->chassis, key);
	}
}

static lldpctl_atom_t*
_lldpctl_atom_set_int_port(lldpctl_atom_t *atom, lldpctl_key_t key,
    long int value)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_port     *port     = p->port;

	if (p->local) {
		switch (key) {
		case lldpctl_k_port_status:
			port->p_disable_rx = !LLDPD_RXTX_RXENABLED(value);
			port->p_disable_tx = !LLDPD_RXTX_TXENABLED(value);
			break;
		case lldpctl_k_port_vlan_tx:
			if (value > -1) {
				port->p_vlan_tx_tag = value;
				port->p_vlan_tx_enabled = 1;
			} else
				port->p_vlan_tx_enabled = 0;
			break;
		default:
			SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
			return NULL;
		}
	} else {
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	return _lldpctl_atom_set_atom_port(atom, key, NULL);
}

static lldpctl_atom_t*
_lldpctl_atom_set_str_port(lldpctl_atom_t *atom, lldpctl_key_t key,
    const char *value)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_port     *port     = p->port;

	if (!value || !strlen(value))
		return NULL;

	if (p->local) {
		switch (key) {
		case lldpctl_k_port_status:
			return _lldpctl_atom_set_int_port(atom, key,
			    map_reverse_lookup(port_status_map.map, value));
		default: break;
		}
	}

	switch (key) {
	case lldpctl_k_port_id:
		free(port->p_id);
		port->p_id = strdup(value);
		port->p_id_len = strlen(value);
		break;
	case lldpctl_k_port_descr:
		free(port->p_descr);
		port->p_descr = strdup(value);
		break;
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	return _lldpctl_atom_set_atom_port(atom, key, NULL);
}

static long int
_lldpctl_atom_get_int_port(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_port     *port     = p->port;
	struct lldpd_hardware *hardware = p->hardware;

	/* Local port only */
	if (hardware != NULL) {
		switch (key) {
		case lldpctl_k_port_index:
			return hardware->h_ifindex;
		case lldpctl_k_tx_cnt:
			return hardware->h_tx_cnt;
		case lldpctl_k_rx_cnt:
			return hardware->h_rx_cnt;
		case lldpctl_k_rx_discarded_cnt:
			return hardware->h_rx_discarded_cnt;
		case lldpctl_k_rx_unrecognized_cnt:
			return hardware->h_rx_unrecognized_cnt;
		case lldpctl_k_ageout_cnt:
			return hardware->h_ageout_cnt;
		case lldpctl_k_insert_cnt:
			return hardware->h_insert_cnt;
		case lldpctl_k_delete_cnt:
			return hardware->h_delete_cnt;
		default: break;
		}
	}
	if (p->local) {
		switch (key) {
		case lldpctl_k_port_status:
			return LLDPD_RXTX_FROM_PORT(port);
		default: break;
		}
	}
	if (!port)
		return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);

	/* Local and remote port */
	switch (key) {
	case lldpctl_k_port_protocol:
		return port->p_protocol;
	case lldpctl_k_port_age:
		return port->p_lastchange;
	case lldpctl_k_port_ttl:
		return port->p_ttl;
	case lldpctl_k_port_id_subtype:
		return port->p_id_subtype;
	case lldpctl_k_port_hidden:
		return port->p_hidden_in;
	case lldpctl_k_port_vlan_tx:
		return port->p_vlan_tx_enabled ? port->p_vlan_tx_tag : -1;
#ifdef ENABLE_DOT3
	case lldpctl_k_port_dot3_mfs:
		if (port->p_mfs > 0)
			return port->p_mfs;
		break;
	case lldpctl_k_port_dot3_aggregid:
		if (port->p_aggregid > 0)
			return port->p_aggregid;
		break;
	case lldpctl_k_port_dot3_autoneg_support:
		return port->p_macphy.autoneg_support;
	case lldpctl_k_port_dot3_autoneg_enabled:
		return port->p_macphy.autoneg_enabled;
	case lldpctl_k_port_dot3_autoneg_advertised:
		return port->p_macphy.autoneg_advertised;
	case lldpctl_k_port_dot3_mautype:
		return port->p_macphy.mau_type;
#endif
#ifdef ENABLE_DOT1
	case lldpctl_k_port_vlan_pvid:
		return port->p_pvid;
#endif
	default:
		/* Compatibility: query the associated chassis too */
		return lldpctl_atom_get_int(p->chassis, key);
	}
	return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
}

static const uint8_t*
_lldpctl_atom_get_buf_port(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *n)
{
	struct _lldpctl_atom_port_t *p =
	    (struct _lldpctl_atom_port_t *)atom;
	struct lldpd_port     *port     = p->port;

	switch (key) {
	case lldpctl_k_port_id:
		*n = port->p_id_len;
		return (uint8_t*)port->p_id;
	default:
		/* Compatibility: query the associated chassis too */
		return lldpctl_atom_get_buffer(p->chassis, key, n);
	}
}

static struct atom_builder ports_list =
	{ atom_ports_list, sizeof(struct _lldpctl_atom_any_list_t),
	  .init = _lldpctl_atom_new_any_list,
	  .free = _lldpctl_atom_free_any_list,
	  .iter = _lldpctl_atom_iter_ports_list,
	  .next = _lldpctl_atom_next_ports_list,
	  .value = _lldpctl_atom_value_ports_list };

static struct atom_builder port =
	{ atom_port, sizeof(struct _lldpctl_atom_port_t),
	  .init = _lldpctl_atom_new_port,
	  .free = _lldpctl_atom_free_port,
	  .get  = _lldpctl_atom_get_atom_port,
	  .set  = _lldpctl_atom_set_atom_port,
	  .get_str = _lldpctl_atom_get_str_port,
	  .set_str = _lldpctl_atom_set_str_port,
	  .get_int = _lldpctl_atom_get_int_port,
	  .set_int = _lldpctl_atom_set_int_port,
	  .get_buffer = _lldpctl_atom_get_buf_port };

ATOM_BUILDER_REGISTER(ports_list, 4);
ATOM_BUILDER_REGISTER(port,       5);

