/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Data-Link Provider Interface (Version 2) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdlpi_impl.h" static int i_dlpi_open(const char *, int *, uint_t); static int i_dlpi_style1_open(dlpi_impl_t *); static int i_dlpi_style2_open(dlpi_impl_t *); static int i_dlpi_checkstyle(dlpi_impl_t *, t_uscalar_t); static int i_dlpi_remove_ppa(char *); static int i_dlpi_attach(dlpi_impl_t *); static void i_dlpi_passive(dlpi_impl_t *); static int i_dlpi_strputmsg(int, const dlpi_msg_t *, const void *, size_t, int); static int i_dlpi_strgetmsg(int, int, dlpi_msg_t *, t_uscalar_t, t_uscalar_t, size_t, void *, size_t *, size_t *); static int i_dlpi_msg_common(dlpi_impl_t *, const dlpi_msg_t *, dlpi_msg_t *, size_t, int); static size_t i_dlpi_getprimsize(t_uscalar_t); static int i_dlpi_multi(dlpi_handle_t, t_uscalar_t, const uint8_t *, size_t); static int i_dlpi_promisc(dlpi_handle_t, t_uscalar_t, uint_t); static uint_t i_dlpi_buildsap(uint8_t *, uint_t); static void i_dlpi_writesap(void *, uint_t, uint_t); int dlpi_open(const char *linkname, dlpi_handle_t *dhp, uint_t flags) { int retval; int cnt; ifspec_t ifsp; dlpi_impl_t *dip; /* * Validate linkname, fail if logical unit number (lun) is specified, * otherwise decompose the contents into ifsp. */ if (linkname == NULL || (strchr(linkname, ':') != NULL) || !ifparse_ifspec(linkname, &ifsp)) return (DLPI_ELINKNAMEINVAL); /* Allocate a new dlpi_impl_t. */ if ((dip = calloc(1, sizeof (dlpi_impl_t))) == NULL) return (DL_SYSERR); /* Fill in known/default libdlpi handle values. */ dip->dli_timeout = DLPI_DEF_TIMEOUT; dip->dli_ppa = ifsp.ifsp_ppa; dip->dli_mod_cnt = ifsp.ifsp_modcnt; dip->dli_oflags = flags; for (cnt = 0; cnt != dip->dli_mod_cnt; cnt++) { (void) strlcpy(dip->dli_modlist[cnt], ifsp.ifsp_mods[cnt], DLPI_LINKNAME_MAX); } /* Copy linkname provided to the function. */ if (strlcpy(dip->dli_linkname, linkname, sizeof (dip->dli_linkname)) >= sizeof (dip->dli_linkname)) { free(dip); return (DLPI_ELINKNAMEINVAL); } /* Copy provider name. */ (void) strlcpy(dip->dli_provider, ifsp.ifsp_devnm, sizeof (dip->dli_provider)); /* * Special case: DLPI_SERIAL flag is set to indicate a synchronous * serial line interface (see syncinit(1M), syncstat(1M), * syncloop(1M)), which is not a DLPI link. */ if (dip->dli_oflags & DLPI_SERIAL) { if ((retval = i_dlpi_style2_open(dip)) != DLPI_SUCCESS) { free(dip); return (retval); } *dhp = (dlpi_handle_t)dip; return (retval); } if (i_dlpi_style1_open(dip) != DLPI_SUCCESS) { if ((retval = i_dlpi_style2_open(dip)) != DLPI_SUCCESS) { free(dip); return (retval); } } if (dip->dli_oflags & DLPI_PASSIVE) i_dlpi_passive(dip); if ((dip->dli_oflags & DLPI_RAW) && ioctl(dip->dli_fd, DLIOCRAW, 0) < 0) { dlpi_close((dlpi_handle_t)dip); return (DLPI_ERAWNOTSUP); } /* * We intentionally do not care if this request fails, as this * indicates the underlying DLPI device does not support Native mode * (pre-GLDV3 device drivers). */ if (dip->dli_oflags & DLPI_NATIVE) { if ((retval = ioctl(dip->dli_fd, DLIOCNATIVE, 0)) > 0) dip->dli_mactype = retval; } *dhp = (dlpi_handle_t)dip; return (DLPI_SUCCESS); } void dlpi_close(dlpi_handle_t dh) { dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip != NULL) { (void) close(dip->dli_fd); free(dip); } } /* * NOTE: The opt argument must be zero and is reserved for future use to extend * fields to the dlpi_info_t structure (see dlpi_info(3DLPI)). */ int dlpi_info(dlpi_handle_t dh, dlpi_info_t *infop, uint_t opt) { int retval; dlpi_msg_t req, ack; dl_info_ack_t *infoackp; uint8_t *sapp, *addrp; caddr_t ackendp, datap; t_uscalar_t dataoff, datalen; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); if (infop == NULL || opt != 0) return (DLPI_EINVAL); (void) memset(infop, 0, sizeof (dlpi_info_t)); /* Set QoS range parameters to default unsupported value. */ infop->di_qos_range.dl_qos_type = (t_uscalar_t)DL_UNKNOWN; infop->di_qos_range.dl_trans_delay.dl_target_value = DL_UNKNOWN; infop->di_qos_range.dl_trans_delay.dl_accept_value = DL_UNKNOWN; infop->di_qos_range.dl_priority.dl_min = DL_UNKNOWN; infop->di_qos_range.dl_priority.dl_max = DL_UNKNOWN; infop->di_qos_range.dl_protection.dl_min = DL_UNKNOWN; infop->di_qos_range.dl_protection.dl_max = DL_UNKNOWN; infop->di_qos_range.dl_residual_error = DL_UNKNOWN; /* Set QoS parameters to default unsupported value. */ infop->di_qos_sel.dl_qos_type = (t_uscalar_t)DL_UNKNOWN; infop->di_qos_sel.dl_trans_delay = DL_UNKNOWN; infop->di_qos_sel.dl_priority = DL_UNKNOWN; infop->di_qos_sel.dl_protection = DL_UNKNOWN; infop->di_qos_sel.dl_residual_error = DL_UNKNOWN; DLPI_MSG_CREATE(req, DL_INFO_REQ); DLPI_MSG_CREATE(ack, DL_INFO_ACK); retval = i_dlpi_msg_common(dip, &req, &ack, DL_INFO_ACK_SIZE, RS_HIPRI); if (retval != DLPI_SUCCESS) return (retval); infoackp = &(ack.dlm_msg->info_ack); if (infoackp->dl_version != DL_VERSION_2) return (DLPI_EVERNOTSUP); if (infoackp->dl_service_mode != DL_CLDLS) return (DLPI_EMODENOTSUP); dip->dli_style = infoackp->dl_provider_style; dip->dli_mactype = infoackp->dl_mac_type; ackendp = (caddr_t)ack.dlm_msg + ack.dlm_msgsz; /* Check and save QoS selection information, if any. */ datalen = infoackp->dl_qos_length; dataoff = infoackp->dl_qos_offset; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)infoackp + dataoff; if (datalen > sizeof (dl_qos_cl_sel1_t) || dataoff < DL_INFO_ACK_SIZE || datap + datalen > ackendp) return (DLPI_EBADMSG); (void) memcpy(&infop->di_qos_sel, datap, datalen); if (infop->di_qos_sel.dl_qos_type != DL_QOS_CL_SEL1) return (DLPI_EMODENOTSUP); } /* Check and save QoS range information, if any. */ datalen = infoackp->dl_qos_range_length; dataoff = infoackp->dl_qos_range_offset; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)infoackp + dataoff; if (datalen > sizeof (dl_qos_cl_range1_t) || dataoff < DL_INFO_ACK_SIZE || datap + datalen > ackendp) return (DLPI_EBADMSG); (void) memcpy(&infop->di_qos_range, datap, datalen); if (infop->di_qos_range.dl_qos_type != DL_QOS_CL_RANGE1) return (DLPI_EMODENOTSUP); } /* Check and save physical address and SAP information. */ dip->dli_saplen = abs(infoackp->dl_sap_length); dip->dli_sapbefore = (infoackp->dl_sap_length > 0); infop->di_physaddrlen = infoackp->dl_addr_length - dip->dli_saplen; if (infop->di_physaddrlen > DLPI_PHYSADDR_MAX || dip->dli_saplen > DLPI_SAPLEN_MAX) return (DL_BADADDR); dataoff = infoackp->dl_addr_offset; datalen = infoackp->dl_addr_length; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)infoackp + dataoff; if (dataoff < DL_INFO_ACK_SIZE || datap + datalen > ackendp) return (DLPI_EBADMSG); sapp = addrp = (uint8_t *)datap; if (dip->dli_sapbefore) addrp += dip->dli_saplen; else sapp += infop->di_physaddrlen; (void) memcpy(infop->di_physaddr, addrp, infop->di_physaddrlen); infop->di_sap = i_dlpi_buildsap(sapp, dip->dli_saplen); } /* Check and save broadcast address information, if any. */ datalen = infoackp->dl_brdcst_addr_length; dataoff = infoackp->dl_brdcst_addr_offset; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)infoackp + dataoff; if (dataoff < DL_INFO_ACK_SIZE || datap + datalen > ackendp) return (DLPI_EBADMSG); if (datalen != infop->di_physaddrlen) return (DL_BADADDR); infop->di_bcastaddrlen = datalen; (void) memcpy(infop->di_bcastaddr, datap, datalen); } infop->di_max_sdu = infoackp->dl_max_sdu; infop->di_min_sdu = infoackp->dl_min_sdu; infop->di_state = infoackp->dl_current_state; infop->di_mactype = infoackp->dl_mac_type; /* Information retrieved from the handle. */ (void) strlcpy(infop->di_linkname, dip->dli_linkname, sizeof (infop->di_linkname)); infop->di_timeout = dip->dli_timeout; return (DLPI_SUCCESS); } /* * This function parses 'linkname' and stores the 'provider' name and 'PPA'. */ int dlpi_parselink(const char *linkname, char *provider, uint_t *ppa) { ifspec_t ifsp; if (linkname == NULL || !ifparse_ifspec(linkname, &ifsp)) return (DLPI_ELINKNAMEINVAL); if (provider != NULL) (void) strlcpy(provider, ifsp.ifsp_devnm, DLPI_LINKNAME_MAX); if (ppa != NULL) *ppa = ifsp.ifsp_ppa; return (DLPI_SUCCESS); } /* * This function takes a provider name and a PPA and stores a full linkname * as 'linkname'. If 'provider' already is a full linkname 'provider' name * is stored in 'linkname'. */ int dlpi_makelink(char *linkname, const char *provider, uint_t ppa) { int provlen = strlen(provider); if (linkname == NULL || provlen == 0 || provlen >= DLPI_LINKNAME_MAX) return (DLPI_ELINKNAMEINVAL); if (!isdigit(provider[provlen - 1])) { (void) snprintf(linkname, DLPI_LINKNAME_MAX, "%s%d", provider, ppa); } else { (void) strlcpy(linkname, provider, DLPI_LINKNAME_MAX); } return (DLPI_SUCCESS); } int dlpi_bind(dlpi_handle_t dh, uint_t sap, uint_t *boundsap) { int retval; dlpi_msg_t req, ack; dl_bind_req_t *bindreqp; dl_bind_ack_t *bindackp; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); DLPI_MSG_CREATE(req, DL_BIND_REQ); DLPI_MSG_CREATE(ack, DL_BIND_ACK); bindreqp = &(req.dlm_msg->bind_req); /* * If 'sap' is DLPI_ANY_SAP, bind to SAP 2 on token ring, else 0 on * other interface types (SAP 0 has special significance on token ring). */ if (sap == DLPI_ANY_SAP) bindreqp->dl_sap = ((dip->dli_mactype == DL_TPR) ? 2 : 0); else bindreqp->dl_sap = sap; bindreqp->dl_service_mode = DL_CLDLS; bindreqp->dl_conn_mgmt = 0; bindreqp->dl_max_conind = 0; bindreqp->dl_xidtest_flg = 0; retval = i_dlpi_msg_common(dip, &req, &ack, DL_BIND_ACK_SIZE, 0); if (retval != DLPI_SUCCESS) return (retval); bindackp = &(ack.dlm_msg->bind_ack); /* * Received a DLPI_BIND_ACK, now verify that the bound SAP * is equal to the SAP requested. Some DLPI MAC type may bind * to a different SAP than requested, in this case 'boundsap' * returns the actual bound SAP. For the case where 'boundsap' * is NULL and 'sap' is not DLPI_ANY_SAP, dlpi_bind fails. */ if (boundsap != NULL) { *boundsap = bindackp->dl_sap; } else if (sap != DLPI_ANY_SAP && bindackp->dl_sap != sap) { if (dlpi_unbind(dh) != DLPI_SUCCESS) return (DLPI_FAILURE); else return (DLPI_EUNAVAILSAP); } dip->dli_sap = bindackp->dl_sap; /* save sap value in handle */ return (DLPI_SUCCESS); } int dlpi_unbind(dlpi_handle_t dh) { dlpi_msg_t req, ack; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); DLPI_MSG_CREATE(req, DL_UNBIND_REQ); DLPI_MSG_CREATE(ack, DL_OK_ACK); return (i_dlpi_msg_common(dip, &req, &ack, DL_OK_ACK_SIZE, 0)); } /* * This function is invoked by dlpi_enabmulti() or dlpi_disabmulti() and * based on the "op" value, multicast address is enabled/disabled. */ static int i_dlpi_multi(dlpi_handle_t dh, t_uscalar_t op, const uint8_t *addrp, size_t addrlen) { dlpi_msg_t req, ack; dl_enabmulti_req_t *multireqp; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); if (addrlen > DLPI_PHYSADDR_MAX) return (DLPI_EINVAL); DLPI_MSG_CREATE(req, op); DLPI_MSG_CREATE(ack, DL_OK_ACK); multireqp = &(req.dlm_msg->enabmulti_req); multireqp->dl_addr_length = addrlen; multireqp->dl_addr_offset = sizeof (dl_enabmulti_req_t); (void) memcpy(&multireqp[1], addrp, addrlen); return (i_dlpi_msg_common(dip, &req, &ack, DL_OK_ACK_SIZE, 0)); } int dlpi_enabmulti(dlpi_handle_t dh, const void *addrp, size_t addrlen) { return (i_dlpi_multi(dh, DL_ENABMULTI_REQ, addrp, addrlen)); } int dlpi_disabmulti(dlpi_handle_t dh, const void *addrp, size_t addrlen) { return (i_dlpi_multi(dh, DL_DISABMULTI_REQ, addrp, addrlen)); } /* * This function is invoked by dlpi_promiscon() or dlpi_promiscoff(). Based * on the value of 'op', promiscuous mode is turned on/off at the specified * 'level'. */ static int i_dlpi_promisc(dlpi_handle_t dh, t_uscalar_t op, uint_t level) { dlpi_msg_t req, ack; dl_promiscon_req_t *promiscreqp; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); DLPI_MSG_CREATE(req, op); DLPI_MSG_CREATE(ack, DL_OK_ACK); promiscreqp = &(req.dlm_msg->promiscon_req); promiscreqp->dl_level = level; return (i_dlpi_msg_common(dip, &req, &ack, DL_OK_ACK_SIZE, 0)); } int dlpi_promiscon(dlpi_handle_t dh, uint_t level) { return (i_dlpi_promisc(dh, DL_PROMISCON_REQ, level)); } int dlpi_promiscoff(dlpi_handle_t dh, uint_t level) { return (i_dlpi_promisc(dh, DL_PROMISCOFF_REQ, level)); } int dlpi_get_physaddr(dlpi_handle_t dh, uint_t type, void *addrp, size_t *addrlenp) { int retval; dlpi_msg_t req, ack; dl_phys_addr_req_t *physreqp; dl_phys_addr_ack_t *physackp; t_uscalar_t dataoff, datalen; caddr_t datap, physackendp; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); if (addrlenp == NULL || addrp == NULL || *addrlenp < DLPI_PHYSADDR_MAX) return (DLPI_EINVAL); DLPI_MSG_CREATE(req, DL_PHYS_ADDR_REQ); DLPI_MSG_CREATE(ack, DL_PHYS_ADDR_ACK); physreqp = &(req.dlm_msg->physaddr_req); physreqp->dl_addr_type = type; retval = i_dlpi_msg_common(dip, &req, &ack, DL_PHYS_ADDR_ACK_SIZE, 0); if (retval != DLPI_SUCCESS) return (retval); /* Received DL_PHYS_ADDR_ACK, store the physical address and length. */ physackp = &(ack.dlm_msg->physaddr_ack); physackendp = (caddr_t)ack.dlm_msg + ack.dlm_msgsz; dataoff = physackp->dl_addr_offset; datalen = physackp->dl_addr_length; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)physackp + dataoff; if (datalen > DLPI_PHYSADDR_MAX) return (DL_BADADDR); if (dataoff < DL_PHYS_ADDR_ACK_SIZE || datap + datalen > physackendp) return (DLPI_EBADMSG); *addrlenp = physackp->dl_addr_length; (void) memcpy(addrp, datap, datalen); } else { *addrlenp = datalen; } return (DLPI_SUCCESS); } int dlpi_set_physaddr(dlpi_handle_t dh, uint_t type, const void *addrp, size_t addrlen) { dlpi_msg_t req, ack; dl_set_phys_addr_req_t *setphysreqp; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); if (addrp == NULL || type != DL_CURR_PHYS_ADDR || addrlen > DLPI_PHYSADDR_MAX) return (DLPI_EINVAL); DLPI_MSG_CREATE(req, DL_SET_PHYS_ADDR_REQ); DLPI_MSG_CREATE(ack, DL_OK_ACK); setphysreqp = &(req.dlm_msg->set_physaddr_req); setphysreqp->dl_addr_length = addrlen; setphysreqp->dl_addr_offset = sizeof (dl_set_phys_addr_req_t); (void) memcpy(&setphysreqp[1], addrp, addrlen); return (i_dlpi_msg_common(dip, &req, &ack, DL_OK_ACK_SIZE, 0)); } int dlpi_send(dlpi_handle_t dh, const void *daddrp, size_t daddrlen, const void *msgbuf, size_t msglen, const dlpi_sendinfo_t *sendp) { dlpi_msg_t req; dl_unitdata_req_t *udatareqp; uint_t sap; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); if (dip->dli_oflags & DLPI_RAW) return (i_dlpi_strputmsg(dip->dli_fd, NULL, msgbuf, msglen, 0)); if (daddrp == NULL || daddrlen > DLPI_PHYSADDR_MAX) return (DLPI_EINVAL); DLPI_MSG_CREATE(req, DL_UNITDATA_REQ); udatareqp = &(req.dlm_msg->unitdata_req); /* Set priority to default priority range. */ udatareqp->dl_priority.dl_min = 0; udatareqp->dl_priority.dl_max = 0; /* Use SAP value if specified otherwise use bound SAP value. */ if (sendp != NULL) { sap = sendp->dsi_sap; if (sendp->dsi_prio.dl_min != DL_QOS_DONT_CARE) udatareqp->dl_priority.dl_min = sendp->dsi_prio.dl_min; if (sendp->dsi_prio.dl_max != DL_QOS_DONT_CARE) udatareqp->dl_priority.dl_max = sendp->dsi_prio.dl_max; } else { sap = dip->dli_sap; } udatareqp->dl_dest_addr_length = daddrlen + dip->dli_saplen; udatareqp->dl_dest_addr_offset = DL_UNITDATA_REQ_SIZE; /* * Since `daddrp' only has the link-layer destination address, * we must prepend or append the SAP (according to dli_sapbefore) * to make a full DLPI address. */ if (dip->dli_sapbefore) { i_dlpi_writesap(&udatareqp[1], sap, dip->dli_saplen); (void) memcpy((caddr_t)&udatareqp[1] + dip->dli_saplen, daddrp, daddrlen); } else { (void) memcpy(&udatareqp[1], daddrp, daddrlen); i_dlpi_writesap((caddr_t)&udatareqp[1] + daddrlen, sap, dip->dli_saplen); } return (i_dlpi_strputmsg(dip->dli_fd, &req, msgbuf, msglen, 0)); } int dlpi_recv(dlpi_handle_t dh, void *saddrp, size_t *saddrlenp, void *msgbuf, size_t *msglenp, int msec, dlpi_recvinfo_t *recvp) { int retval; dlpi_msg_t ind; size_t totmsglen; dl_unitdata_ind_t *udatap; t_uscalar_t dataoff, datalen; caddr_t datap, indendp; dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); /* * If handle is in raw mode ignore everything except total message * length. */ if (dip->dli_oflags & DLPI_RAW) { retval = i_dlpi_strgetmsg(dip->dli_fd, msec, NULL, 0, 0, 0, msgbuf, msglenp, &totmsglen); if (retval == DLPI_SUCCESS && recvp != NULL) recvp->dri_totmsglen = totmsglen; return (retval); } DLPI_MSG_CREATE(ind, DL_UNITDATA_IND); udatap = &(ind.dlm_msg->unitdata_ind); indendp = (caddr_t)ind.dlm_msg + ind.dlm_msgsz; if ((retval = i_dlpi_strgetmsg(dip->dli_fd, msec, &ind, DL_UNITDATA_IND, DL_UNITDATA_IND, DL_UNITDATA_IND_SIZE, msgbuf, msglenp, &totmsglen)) != DLPI_SUCCESS) return (retval); /* * If DLPI link provides source address, store source address in * 'saddrp' and source length in 'saddrlenp', else set saddrlenp to 0. */ if (saddrp != NULL && saddrlenp != NULL) { if (*saddrlenp < DLPI_PHYSADDR_MAX) return (DLPI_EINVAL); dataoff = udatap->dl_src_addr_offset; datalen = udatap->dl_src_addr_length; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)udatap + dataoff; if (dataoff < DL_UNITDATA_IND_SIZE || datap + datalen > indendp) return (DLPI_EBADMSG); *saddrlenp = datalen - dip->dli_saplen; if (*saddrlenp > DLPI_PHYSADDR_MAX) return (DL_BADADDR); if (dip->dli_sapbefore) datap += dip->dli_saplen; (void) memcpy(saddrp, datap, *saddrlenp); } else { *saddrlenp = 0; } } /* * If destination address requested, check and save destination * address, if any. */ if (recvp != NULL) { dataoff = udatap->dl_dest_addr_offset; datalen = udatap->dl_dest_addr_length; if (dataoff != 0 && datalen != 0) { datap = (caddr_t)udatap + dataoff; if (dataoff < DL_UNITDATA_IND_SIZE || datap + datalen > indendp) return (DLPI_EBADMSG); recvp->dri_destaddrlen = datalen - dip->dli_saplen; if (recvp->dri_destaddrlen > DLPI_PHYSADDR_MAX) return (DL_BADADDR); if (dip->dli_sapbefore) datap += dip->dli_saplen; (void) memcpy(recvp->dri_destaddr, datap, recvp->dri_destaddrlen); } else { recvp->dri_destaddrlen = 0; } recvp->dri_dstaddrtype = udatap->dl_group_address; recvp->dri_totmsglen = totmsglen; } return (DLPI_SUCCESS); } int dlpi_fd(dlpi_handle_t dh) { dlpi_impl_t *dip = (dlpi_impl_t *)dh; return (dip != NULL ? dip->dli_fd : -1); } int dlpi_set_timeout(dlpi_handle_t dh, int sec) { dlpi_impl_t *dip = (dlpi_impl_t *)dh; if (dip == NULL) return (DLPI_EINHANDLE); dip->dli_timeout = sec; return (DLPI_SUCCESS); } const char * dlpi_linkname(dlpi_handle_t dh) { dlpi_impl_t *dip = (dlpi_impl_t *)dh; return (dip != NULL ? dip->dli_linkname : NULL); } /* * Returns DLPI style stored in the handle. * Note: This function is used for test purposes only. Do not remove without * fixing the DLPI testsuite. */ uint_t dlpi_style(dlpi_handle_t dh) { dlpi_impl_t *dip = (dlpi_impl_t *)dh; return (dip->dli_style); } /* * This function attempts to open linkname under the following namespaces: * - /dev * - /devices * If open doesn't succeed and link doesn't exist (ENOENT), this function * returns DLPI_ENOLINK, otherwise returns DL_SYSERR. */ static int i_dlpi_open(const char *provider, int *fd, uint_t flags) { char path[MAXPATHLEN]; int oflags; oflags = O_RDWR; if (flags & DLPI_EXCL) oflags |= O_EXCL; (void) snprintf(path, sizeof (path), "/dev/%s", provider); if ((*fd = open(path, oflags)) != -1) return (DLPI_SUCCESS); /* * On diskless boot, it's possible the /dev links have not yet * been created; fallback to /devices. When /dev links are * created on demand, this code can be removed. */ (void) snprintf(path, sizeof (path), "/devices/pseudo/clone@0:%s", provider); if ((*fd = open(path, oflags)) != -1) return (DLPI_SUCCESS); return (errno == ENOENT ? DLPI_ENOLINK : DL_SYSERR); } /* * Open a style 1 link. PPA is implicitly attached. */ static int i_dlpi_style1_open(dlpi_impl_t *dip) { int retval, save_errno; int fd; /* * In order to support open of syntax like device[.module[.module...]] * where modules need to be pushed onto the device stream, open only * device name, otherwise open the full linkname. */ retval = i_dlpi_open((dip->dli_mod_cnt != 0) ? dip->dli_provider : dip->dli_linkname, &fd, dip->dli_oflags); if (retval != DLPI_SUCCESS) { dip->dli_mod_pushed = 0; return (retval); } dip->dli_fd = fd; /* * Try to push modules (if any) onto the device stream. If I_PUSH * fails, we increment count of modules pushed (dli_mod_pushed) * expecting it is last module to be pushed and thus will be pushed * in i_dlpi_style2_open(). */ for (dip->dli_mod_pushed = 0; dip->dli_mod_pushed < dip->dli_mod_cnt; dip->dli_mod_pushed++) { if (ioctl(fd, I_PUSH, dip->dli_modlist[dip->dli_mod_pushed]) == -1) { dip->dli_mod_pushed++; return (DLPI_FAILURE); } } if ((retval = i_dlpi_checkstyle(dip, DL_STYLE1)) != DLPI_SUCCESS) { save_errno = errno; (void) close(dip->dli_fd); errno = save_errno; dip->dli_mod_pushed = 0; return (retval); } return (DLPI_SUCCESS); } /* * Open a style 2 link. PPA must be explicitly attached. */ static int i_dlpi_style2_open(dlpi_impl_t *dip) { int fd; int retval, save_errno; /* * If style 1 open failed, we need to determine how far it got and * finish up the open() call as a style 2 open. * * If no modules were pushed (mod_pushed == 0), then we need to * open it as a style 2 link. * * If the pushing of the last module failed, we need to * try pushing it as a style 2 module. Decrement dli_mod_pushed * count so it can be pushed onto the stream. * * Otherwise we failed during the push of an intermediate module and * must fail out and close the link. */ if (dip->dli_mod_pushed == 0) { if ((retval = i_dlpi_open(dip->dli_provider, &fd, dip->dli_oflags)) != DLPI_SUCCESS) return (retval); dip->dli_fd = fd; } else if (dip->dli_mod_pushed == dip->dli_mod_cnt) { if (i_dlpi_remove_ppa(dip->dli_modlist[dip->dli_mod_cnt - 1]) != DLPI_SUCCESS) return (DLPI_ELINKNAMEINVAL); dip->dli_mod_pushed--; fd = dip->dli_fd; } else { return (DLPI_ELINKNAMEINVAL); } /* Try and push modules (if any) onto the device stream. */ for (; dip->dli_mod_pushed < dip->dli_mod_cnt; dip->dli_mod_pushed++) { if (ioctl(fd, I_PUSH, dip->dli_modlist[dip->dli_mod_pushed]) == -1) { retval = DL_SYSERR; goto failure; } } /* * Special case: DLPI_SERIAL flag (synchronous serial lines) is not a * DLPI link so attach and ignore rest. */ if (dip->dli_oflags & DLPI_SERIAL) goto attach; if ((retval = i_dlpi_checkstyle(dip, DL_STYLE2)) != DLPI_SUCCESS) goto failure; /* * Succeeded opening the link and verified it is style2. Now attach to * PPA only if DLPI_NOATTACH is not set. */ if (dip->dli_oflags & DLPI_NOATTACH) return (DLPI_SUCCESS); attach: if ((retval = i_dlpi_attach(dip)) != DLPI_SUCCESS) goto failure; return (DLPI_SUCCESS); failure: save_errno = errno; (void) close(dip->dli_fd); errno = save_errno; return (retval); } /* * Verify with DLPI that the link is the expected DLPI 'style' device, * dlpi_info sets the DLPI style in the DLPI handle. */ static int i_dlpi_checkstyle(dlpi_impl_t *dip, t_uscalar_t style) { int retval; dlpi_info_t dlinfo; retval = dlpi_info((dlpi_handle_t)dip, &dlinfo, 0); if (retval == DLPI_SUCCESS && dip->dli_style != style) retval = DLPI_EBADLINK; return (retval); } /* * Remove PPA from end of linkname. * Return DLPI_SUCCESS if found, else return DLPI_FAILURE. */ static int i_dlpi_remove_ppa(char *linkname) { int i = strlen(linkname) - 1; if (i == -1 || !isdigit(linkname[i--])) return (DLPI_FAILURE); while (i >= 0 && isdigit(linkname[i])) i--; linkname[i + 1] = '\0'; return (DLPI_SUCCESS); } /* * For DLPI style 2 providers, an explicit attach of PPA is required. */ static int i_dlpi_attach(dlpi_impl_t *dip) { dlpi_msg_t req, ack; dl_attach_req_t *attachreqp; /* * Special case: DLPI_SERIAL flag (synchronous serial lines) * is not a DLPI link so ignore DLPI style. */ if (dip->dli_style != DL_STYLE2 && !(dip->dli_oflags & DLPI_SERIAL)) return (DLPI_ENOTSTYLE2); DLPI_MSG_CREATE(req, DL_ATTACH_REQ); DLPI_MSG_CREATE(ack, DL_OK_ACK); attachreqp = &(req.dlm_msg->attach_req); attachreqp->dl_ppa = dip->dli_ppa; return (i_dlpi_msg_common(dip, &req, &ack, DL_OK_ACK_SIZE, 0)); } /* * Enable DLPI passive mode on a DLPI handle. We intentionally do not care * if this request fails, as this indicates the underlying DLPI device does * not support link aggregation (pre-GLDV3 device drivers), and thus will * see the expected behavior without failing with DL_SYSERR/EBUSY when issuing * DLPI primitives like DL_BIND_REQ. For further info see dlpi(7p). */ static void i_dlpi_passive(dlpi_impl_t *dip) { dlpi_msg_t req, ack; DLPI_MSG_CREATE(req, DL_PASSIVE_REQ); DLPI_MSG_CREATE(ack, DL_OK_ACK); (void) i_dlpi_msg_common(dip, &req, &ack, DL_OK_ACK_SIZE, 0); } /* * Send a dlpi control message and/or data message on a stream. The inputs * for this function are: * int fd: file descriptor of open stream to send message * const dlpi_msg_t *dlreqp: request message structure * void *databuf: data buffer * size_t datalen: data buffer len * int flags: flags to set for putmsg() * Returns DLPI_SUCCESS if putmsg() succeeds, otherwise DL_SYSERR on failure. */ static int i_dlpi_strputmsg(int fd, const dlpi_msg_t *dlreqp, const void *databuf, size_t datalen, int flags) { int retval; struct strbuf ctl; struct strbuf data; if (dlreqp != NULL) { ctl.buf = (void *)dlreqp->dlm_msg; ctl.len = dlreqp->dlm_msgsz; } data.buf = (void *)databuf; data.len = datalen; retval = putmsg(fd, (dlreqp == NULL ? NULL: &ctl), (databuf == NULL ? NULL : &data), flags); return ((retval == 0) ? DLPI_SUCCESS : DL_SYSERR); } /* * Get a DLPI control message and/or data message from a stream. The inputs * for this function are: * int fd: file descriptor of open stream * int msec: timeout to wait for message * dlpi_msg_t *dlreplyp: reply message structure, the message size * member on return stores actual size received * t_uscalar_t dlreqprim: requested primitive * t_uscalar_t dlreplyprim:acknowledged primitive in response to request * size_t dlreplyminsz: minimum size of acknowledged primitive size * void *databuf: data buffer * size_t *datalenp: data buffer len * size_t *totdatalenp: total data received. Greater than 'datalenp' if * actual data received is larger than 'databuf' * Function returns DLPI_SUCCESS if requested message is retrieved * otherwise returns error code or timeouts. */ static int i_dlpi_strgetmsg(int fd, int msec, dlpi_msg_t *dlreplyp, t_uscalar_t dlreqprim, t_uscalar_t dlreplyprim, size_t dlreplyminsz, void *databuf, size_t *datalenp, size_t *totdatalenp) { int retval; int flags = 0; struct strbuf ctl, data; struct pollfd pfd; hrtime_t start, current; long bufc[DLPI_CHUNKSIZE / sizeof (long)]; long bufd[DLPI_CHUNKSIZE / sizeof (long)]; union DL_primitives *dlprim; boolean_t infinite = (msec < 0); /* infinite timeout */ if ((dlreplyp == NULL && databuf == NULL) || (databuf == NULL && datalenp != NULL) || (databuf != NULL && datalenp == NULL)) return (DLPI_EINVAL); pfd.fd = fd; pfd.events = POLLIN | POLLPRI; ctl.buf = (dlreplyp == NULL) ? bufc : (void *)dlreplyp->dlm_msg; ctl.len = 0; ctl.maxlen = (dlreplyp == NULL) ? sizeof (bufc) : dlreplyp->dlm_msgsz; data.buf = (databuf == NULL) ? bufd : databuf; data.len = 0; data.maxlen = (databuf == NULL) ? sizeof (bufd): *datalenp; for (;;) { if (!infinite) start = gethrtime() / (NANOSEC / MILLISEC); switch (poll(&pfd, 1, msec)) { default: break; case 0: return (DLPI_ETIMEDOUT); case -1: return (DL_SYSERR); } if ((retval = getmsg(fd, &ctl, &data, &flags)) < 0) return (DL_SYSERR); if (totdatalenp != NULL) *totdatalenp = data.len; /* * The supplied DLPI_CHUNKSIZE sized buffers are large enough * to retrieve all valid DLPI responses in one iteration. * If MORECTL or MOREDATA is set, we are not interested in the * remainder of the message. Temporary buffers are used to * drain the remainder of this message. * The special case we have to account for is if * a higher priority messages is enqueued whilst handling * this condition. We use a change in the flags parameter * returned by getmsg() to indicate the message has changed. */ while (retval & (MORECTL | MOREDATA)) { struct strbuf cscratch, dscratch; int oflags = flags; cscratch.buf = (char *)bufc; dscratch.buf = (char *)bufd; cscratch.len = dscratch.len = 0; cscratch.maxlen = dscratch.maxlen = sizeof (bufc); if ((retval = getmsg(fd, &cscratch, &dscratch, &flags)) < 0) return (DL_SYSERR); if (totdatalenp != NULL) *totdatalenp += dscratch.len; /* * In the special case of higher priority * message received, the low priority message * received earlier is discarded, if no data * or control message is left. */ if ((flags != oflags) && !(retval & (MORECTL | MOREDATA)) && (cscratch.len != 0)) { ctl.len = MIN(cscratch.len, DLPI_CHUNKSIZE); if (dlreplyp != NULL) (void) memcpy(dlreplyp->dlm_msg, bufc, ctl.len); break; } } /* * If we were expecting a data message, and we got one, set * *datalenp. If we aren't waiting on a control message, then * we're done. */ if (databuf != NULL && data.len >= 0) { *datalenp = data.len; if (dlreplyp == NULL) break; } /* * If we were expecting a control message, and the message * we received is at least big enough to be a DLPI message, * then verify it's a reply to something we sent. If it * is a reply to something we sent, also verify its size. */ if (dlreplyp != NULL && ctl.len >= sizeof (t_uscalar_t)) { dlprim = dlreplyp->dlm_msg; if (dlprim->dl_primitive == dlreplyprim) { if (ctl.len < dlreplyminsz) return (DLPI_EBADMSG); dlreplyp->dlm_msgsz = ctl.len; break; } else if (dlprim->dl_primitive == DL_ERROR_ACK) { if (ctl.len < DL_ERROR_ACK_SIZE) return (DLPI_EBADMSG); /* Is it ours? */ if (dlprim->error_ack.dl_error_primitive == dlreqprim) break; } } if (!infinite) { current = gethrtime() / (NANOSEC / MILLISEC); msec -= (current - start); if (msec <= 0) return (DLPI_ETIMEDOUT); } } return (DLPI_SUCCESS); } /* * Common routine invoked by all DLPI control routines. The inputs for this * function are: * dlpi_impl_t *dip: internal dlpi handle * const dlpi_msg_t *dlreqp: request message structure * dlpi_msg_t *dlreplyp: reply message structure * size_t dlreplyminsz: minimum size of reply primitive * int flags: flags to be set to send a message * This routine succeeds if the message is an expected request/acknowledged * message. Unexpected asynchronous messages (e.g. unexpected DL_NOTIFY_IND * messages) will be discarded. */ static int i_dlpi_msg_common(dlpi_impl_t *dip, const dlpi_msg_t *dlreqp, dlpi_msg_t *dlreplyp, size_t dlreplyminsz, int flags) { int retval; t_uscalar_t dlreqprim = dlreqp->dlm_msg->dl_primitive; t_uscalar_t dlreplyprim = dlreplyp->dlm_msg->dl_primitive; /* Put the requested primitive on the stream. */ retval = i_dlpi_strputmsg(dip->dli_fd, dlreqp, NULL, 0, flags); if (retval != DLPI_SUCCESS) return (retval); /* Retrieve acknowledged message for requested primitive. */ retval = i_dlpi_strgetmsg(dip->dli_fd, (dip->dli_timeout * MILLISEC), dlreplyp, dlreqprim, dlreplyprim, dlreplyminsz, NULL, NULL, NULL); if (retval != DLPI_SUCCESS) return (retval); /* * If primitive is DL_ERROR_ACK, set errno. */ if (dlreplyp->dlm_msg->dl_primitive == DL_ERROR_ACK) { errno = dlreplyp->dlm_msg->error_ack.dl_unix_errno; retval = dlreplyp->dlm_msg->error_ack.dl_errno; } return (retval); } /* * DLPI error codes. */ static const char *dlpi_errlist[] = { "bad LSAP selector", /* DL_BADSAP 0x00 */ "DLSAP address in improper format or invalid", /* DL_BADADDR 0x01 */ "improper permissions for request", /* DL_ACCESS 0x02 */ "primitive issued in improper state", /* DL_OUTSTATE 0x03 */ NULL, /* DL_SYSERR 0x04 */ "sequence number not from outstanding DL_CONN_IND", /* DL_BADCORR 0x05 */ "user data exceeded provider limit", /* DL_BADDATA 0x06 */ "requested service not supplied by provider", /* DL_UNSUPPORTED 0x07 */ "specified PPA was invalid", /* DL_BADPPA 0x08 */ "primitive received not known by provider", /* DL_BADPRIM 0x09 */ "QoS parameters contained invalid values", /* DL_BADQOSPARAM 0x0a */ "QoS structure type is unknown/unsupported", /* DL_BADQOSTYPE 0x0b */ "token used not an active stream", /* DL_BADTOKEN 0x0c */ "attempted second bind with dl_max_conind", /* DL_BOUND 0x0d */ "physical link initialization failed", /* DL_INITFAILED 0x0e */ "provider couldn't allocate alternate address", /* DL_NOADDR 0x0f */ "physical link not initialized", /* DL_NOTINIT 0x10 */ "previous data unit could not be delivered", /* DL_UNDELIVERABLE 0x11 */ "primitive is known but unsupported", /* DL_NOTSUPPORTED 0x12 */ "limit exceeded", /* DL_TOOMANY 0x13 */ "promiscuous mode not enabled", /* DL_NOTENAB 0x14 */ "other streams for PPA in post-attached", /* DL_BUSY 0x15 */ "automatic handling XID&TEST unsupported", /* DL_NOAUTO 0x16 */ "automatic handling of XID unsupported", /* DL_NOXIDAUTO 0x17 */ "automatic handling of TEST unsupported", /* DL_NOTESTAUTO 0x18 */ "automatic handling of XID response", /* DL_XIDAUTO 0x19 */ "automatic handling of TEST response", /* DL_TESTAUTO 0x1a */ "pending outstanding connect indications" /* DL_PENDING 0x1b */ }; /* * libdlpi error codes. */ static const char *libdlpi_errlist[] = { "DLPI operation succeeded", /* DLPI_SUCCESS */ "invalid argument", /* DLPI_EINVAL */ "invalid DLPI linkname", /* DLPI_ELINKNAMEINVAL */ "DLPI link does not exist", /* DLPI_ENOLINK */ "bad DLPI link", /* DLPI_EBADLINK */ "invalid DLPI handle", /* DLPI_EINHANDLE */ "DLPI operation timed out", /* DLPI_ETIMEDOUT */ "unsupported DLPI version", /* DLPI_EVERNOTSUP */ "unsupported DLPI connection mode", /* DLPI_EMODENOTSUP */ "unavailable DLPI SAP", /* DLPI_EUNAVAILSAP */ "DLPI operation failed", /* DLPI_FAILURE */ "DLPI style-2 node reports style-1", /* DLPI_ENOTSTYLE2 */ "bad DLPI message", /* DLPI_EBADMSG */ "DLPI raw mode not supported" /* DLPI_ERAWNOTSUP */ }; const char * dlpi_strerror(int err) { if (err == DL_SYSERR) return (strerror(errno)); else if (err >= 0 && err < NELEMS(dlpi_errlist)) return (dgettext(TEXT_DOMAIN, dlpi_errlist[err])); else if (err > DLPI_SUCCESS && err < DLPI_ERRMAX) return (dgettext(TEXT_DOMAIN, libdlpi_errlist[err - DLPI_SUCCESS])); else return (dgettext(TEXT_DOMAIN, "Unknown DLPI error")); } /* * Each table entry comprises a DLPI/Private mactype and the description. */ static const dlpi_mactype_t dlpi_mactypes[] = { { DL_CSMACD, "CSMA/CD" }, { DL_TPB, "Token Bus" }, { DL_TPR, "Token Ring" }, { DL_METRO, "Metro Net" }, { DL_ETHER, "Ethernet" }, { DL_HDLC, "HDLC" }, { DL_CHAR, "Sync Character" }, { DL_CTCA, "CTCA" }, { DL_FDDI, "FDDI" }, { DL_FRAME, "Frame Relay (LAPF)" }, { DL_MPFRAME, "MP Frame Relay" }, { DL_ASYNC, "Async Character" }, { DL_IPX25, "X.25 (Classic IP)" }, { DL_LOOP, "Software Loopback" }, { DL_FC, "Fiber Channel" }, { DL_ATM, "ATM" }, { DL_IPATM, "ATM (Classic IP)" }, { DL_X25, "X.25 (LAPB)" }, { DL_ISDN, "ISDN" }, { DL_HIPPI, "HIPPI" }, { DL_100VG, "100BaseVG Ethernet" }, { DL_100VGTPR, "100BaseVG Token Ring" }, { DL_ETH_CSMA, "Ethernet/IEEE 802.3" }, { DL_100BT, "100BaseT" }, { DL_IB, "Infiniband" }, { DL_IPV4, "IPv4 Tunnel" }, { DL_IPV6, "IPv6 Tunnel" }, { DL_WIFI, "IEEE 802.11" } }; const char * dlpi_mactype(uint_t mactype) { int i; for (i = 0; i < NELEMS(dlpi_mactypes); i++) { if (dlpi_mactypes[i].dm_mactype == mactype) return (dlpi_mactypes[i].dm_desc); } return ("Unknown MAC Type"); } /* * Each table entry comprises a DLPI primitive and the maximum buffer * size needed, in bytes, for the DLPI message (see for details). */ static const dlpi_primsz_t dlpi_primsizes[] = { { DL_INFO_REQ, DL_INFO_REQ_SIZE }, { DL_INFO_ACK, DL_INFO_ACK_SIZE + (2 * DLPI_PHYSADDR_MAX) + DLPI_SAPLEN_MAX + (2 * sizeof (union DL_qos_types))}, { DL_ATTACH_REQ, DL_ATTACH_REQ_SIZE }, { DL_BIND_REQ, DL_BIND_REQ_SIZE }, { DL_BIND_ACK, DL_BIND_ACK_SIZE + DLPI_PHYSADDR_MAX + DLPI_SAPLEN_MAX }, { DL_UNBIND_REQ, DL_UNBIND_REQ_SIZE }, { DL_ENABMULTI_REQ, DL_ENABMULTI_REQ_SIZE + DLPI_PHYSADDR_MAX }, { DL_DISABMULTI_REQ, DL_DISABMULTI_REQ_SIZE + DLPI_PHYSADDR_MAX }, { DL_PROMISCON_REQ, DL_PROMISCON_REQ_SIZE }, { DL_PROMISCOFF_REQ, DL_PROMISCOFF_REQ_SIZE }, { DL_PASSIVE_REQ, DL_PASSIVE_REQ_SIZE }, { DL_UNITDATA_REQ, DL_UNITDATA_REQ_SIZE + DLPI_PHYSADDR_MAX + DLPI_SAPLEN_MAX }, { DL_UNITDATA_IND, DL_UNITDATA_IND_SIZE + (2 * (DLPI_PHYSADDR_MAX + DLPI_SAPLEN_MAX)) }, { DL_PHYS_ADDR_REQ, DL_PHYS_ADDR_REQ_SIZE }, { DL_PHYS_ADDR_ACK, DL_PHYS_ADDR_ACK_SIZE + DLPI_PHYSADDR_MAX }, { DL_SET_PHYS_ADDR_REQ, DL_SET_PHYS_ADDR_REQ_SIZE + DLPI_PHYSADDR_MAX }, { DL_OK_ACK, MAX(DL_ERROR_ACK_SIZE, DL_OK_ACK_SIZE) } }; /* * Refers to the dlpi_primsizes[] table to return corresponding maximum * buffer size. */ static size_t i_dlpi_getprimsize(t_uscalar_t prim) { int i; for (i = 0; i < NELEMS(dlpi_primsizes); i++) { if (dlpi_primsizes[i].dp_prim == prim) return (dlpi_primsizes[i].dp_primsz); } return (sizeof (t_uscalar_t)); } /* * sap values vary in length and are in host byte order, build sap value * by writing saplen bytes, so that the sap value is left aligned. */ static uint_t i_dlpi_buildsap(uint8_t *sapp, uint_t saplen) { int i; uint_t sap = 0; #ifdef _LITTLE_ENDIAN for (i = saplen - 1; i >= 0; i--) { #else for (i = 0; i < saplen; i++) { #endif sap <<= 8; sap |= sapp[i]; } return (sap); } /* * Copy sap value to a buffer in host byte order. saplen is the number of * bytes to copy. */ static void i_dlpi_writesap(void *dstbuf, uint_t sap, uint_t saplen) { uint8_t *sapp; #ifdef _LITTLE_ENDIAN sapp = (uint8_t *)&sap; #else sapp = (uint8_t *)&sap + (sizeof (sap) - saplen); #endif (void) memcpy(dstbuf, sapp, saplen); }