lldpd_decode(struct lldpd *cfg, char *frame, int s,
    struct lldpd_hardware *hardware)
{
	int i;
	struct lldpd_chassis *chassis, *ochassis = NULL;
	struct lldpd_port *port, *oport = NULL, *aport;
	int guess = LLDPD_MODE_LLDP;

	log_debug("decode", "decode a received frame on %s",
	    hardware->h_ifname);

	if (s < sizeof(struct ether_header) + 4)
		/* Too short, just discard it */
		return;

	/* Decapsulate VLAN frames */
	struct ether_header eheader;
	memcpy(&eheader, frame, sizeof(struct ether_header));
	if (eheader.ether_type == htons(ETHERTYPE_VLAN)) {
		/* VLAN decapsulation means to shift 4 bytes left the frame from
		 * offset 2*ETHER_ADDR_LEN */
		memmove(frame + 2*ETHER_ADDR_LEN, frame + 2*ETHER_ADDR_LEN + 4, s - 2*ETHER_ADDR_LEN);
		s -= 4;
	}

	TAILQ_FOREACH(oport, &hardware->h_rports, p_entries) {
		if ((oport->p_lastframe != NULL) &&
		    (oport->p_lastframe->size == s) &&
		    (memcmp(oport->p_lastframe->frame, frame, s) == 0)) {
			/* Already received the same frame */
			log_debug("decode", "duplicate frame, no need to decode");
			oport->p_lastupdate = time(NULL);
			return;
		}
	}

	guess = lldpd_guess_type(cfg, frame, s);
	for (i=0; cfg->g_protocols[i].mode != 0; i++) {
		if (!cfg->g_protocols[i].enabled)
			continue;
		if (cfg->g_protocols[i].mode == guess) {
			log_debug("decode", "using decode function for %s protocol",
			    cfg->g_protocols[i].name);
			if (cfg->g_protocols[i].decode(cfg, frame,
				s, hardware, &chassis, &port) == -1) {
				log_debug("decode", "function for %s protocol did not decode this frame",
				    cfg->g_protocols[i].name);
				return;
			}
			chassis->c_protocol = port->p_protocol =
			    cfg->g_protocols[i].mode;
			break;
			}
	}
	if (cfg->g_protocols[i].mode == 0) {
		log_debug("decode", "unable to guess frame type on %s",
		    hardware->h_ifname);
		return;
	}
	TRACE(LLDPD_FRAME_DECODED(
		    hardware->h_ifname,
		    cfg->g_protocols[i].name,
		    chassis->c_name,
		    port->p_descr));

	/* Do we already have the same MSAP somewhere? */
	int count = 0;
	log_debug("decode", "search for the same MSAP");
	TAILQ_FOREACH(oport, &hardware->h_rports, p_entries) {
		if (port->p_protocol == oport->p_protocol) {
			count++;
			if ((port->p_id_subtype == oport->p_id_subtype) &&
			    (port->p_id_len == oport->p_id_len) &&
			    (memcmp(port->p_id, oport->p_id, port->p_id_len) == 0) &&
			    (chassis->c_id_subtype == oport->p_chassis->c_id_subtype) &&
			    (chassis->c_id_len == oport->p_chassis->c_id_len) &&
			    (memcmp(chassis->c_id, oport->p_chassis->c_id,
				chassis->c_id_len) == 0)) {
				ochassis = oport->p_chassis;
				log_debug("decode", "MSAP is already known");
				break;
			}
		}
	}
	/* Do we have room for a new MSAP? */
	if (!oport && cfg->g_config.c_max_neighbors) {
	    if (count == (cfg->g_config.c_max_neighbors - 1)) {
		log_debug("decode",
		    "max neighbors %d reached for port %s, "
		    "dropping any new ones silently",
		    cfg->g_config.c_max_neighbors,
		    hardware->h_ifname);
	    } else if (count > cfg->g_config.c_max_neighbors - 1) {
		log_debug("decode",
		    "too many neighbors for port %s, drop this new one",
		    hardware->h_ifname);
		lldpd_port_cleanup(port, 1);
		lldpd_chassis_cleanup(chassis, 1);
		free(port);
		return;
	    }
	}
	/* No, but do we already know the system? */
	if (!oport) {
		log_debug("decode", "MSAP is unknown, search for the chassis");
		TAILQ_FOREACH(ochassis, &cfg->g_chassis, c_entries) {
			if ((chassis->c_protocol == ochassis->c_protocol) &&
			    (chassis->c_id_subtype == ochassis->c_id_subtype) &&
			    (chassis->c_id_len == ochassis->c_id_len) &&
			    (memcmp(chassis->c_id, ochassis->c_id,
				chassis->c_id_len) == 0))
			break;
		}
	}

	if (oport) {
		/* The port is known, remove it before adding it back */
		TAILQ_REMOVE(&hardware->h_rports, oport, p_entries);
		lldpd_port_cleanup(oport, 1);
		free(oport);
	}
	if (ochassis) {
		lldpd_move_chassis(ochassis, chassis);
		chassis = ochassis;
	} else {
		/* Chassis not known, add it */
		log_debug("decode", "unknown chassis, add it to the list");
		chassis->c_index = ++cfg->g_lastrid;
		chassis->c_refcount = 0;
		TAILQ_INSERT_TAIL(&cfg->g_chassis, chassis, c_entries);
		i = 0; TAILQ_FOREACH(ochassis, &cfg->g_chassis, c_entries) i++;
		log_debug("decode", "%d different systems are known", i);
	}
	/* Add port */
	port->p_lastchange = port->p_lastupdate = time(NULL);
	if ((port->p_lastframe = (struct lldpd_frame *)malloc(s +
		    sizeof(struct lldpd_frame))) != NULL) {
		port->p_lastframe->size = s;
		memcpy(port->p_lastframe->frame, frame, s);
	}
	TAILQ_INSERT_TAIL(&hardware->h_rports, port, p_entries);
	port->p_chassis = chassis;
	port->p_chassis->c_refcount++;
	/* Several cases are possible :
	     1. chassis is new, its refcount was 0. It is now attached
	        to this port, its refcount is 1.
	     2. chassis already exists and was attached to another
	        port, we increase its refcount accordingly.
	     3. chassis already exists and was attached to the same
	        port, its refcount was decreased with
	        lldpd_port_cleanup() and is now increased again.

	   In all cases, if the port already existed, it has been
	   freed with lldpd_port_cleanup() and therefore, the refcount
	   of the chassis that was attached to it is decreased.
	*/
	/* coverity[use_after_free]
	   TAILQ_REMOVE does the right thing */
	i = 0; TAILQ_FOREACH(aport, &hardware->h_rports, p_entries)
		i++;
	log_debug("decode", "%d neighbors for %s", i,
	    hardware->h_ifname);

	if (!oport) hardware->h_insert_cnt++;

	/* Notify */
	log_debug("decode", "send notifications for changes on %s",
	    hardware->h_ifname);
	if (oport) {
		TRACE(LLDPD_NEIGHBOR_UPDATE(hardware->h_ifname,
			chassis->c_name,
			port->p_descr,
			i));
		levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_UPDATED, port);
#ifdef USE_SNMP
		agent_notify(hardware, NEIGHBOR_CHANGE_UPDATED, port);
#endif
	} else {
		TRACE(LLDPD_NEIGHBOR_NEW(hardware->h_ifname,
			chassis->c_name,
			port->p_descr,
			i));
		levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_ADDED, port);
#ifdef USE_SNMP
		agent_notify(hardware, NEIGHBOR_CHANGE_ADDED, port);
#endif
	}

#ifdef ENABLE_LLDPMED
	if (!oport && port->p_chassis->c_med_type) {
		/* New neighbor, fast start */
		if (hardware->h_cfg->g_config.c_enable_fast_start &&
		    !hardware->h_tx_fast) {
			log_debug("decode", "%s: entering fast start due to "
			    "new neighbor", hardware->h_ifname);
			hardware->h_tx_fast = hardware->h_cfg->g_config.c_tx_fast_init;
		}

		levent_schedule_pdu(hardware);
	}
#endif

	return;
}