root/sbin/efi/devpath3.c
/* $NetBSD: devpath3.c,v 1.7 2025/12/15 17:06:42 joe Exp $ */

/*
 * 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 ``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.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: devpath3.c,v 1.7 2025/12/15 17:06:42 joe Exp $");
#endif /* not lint */

#include <arpa/inet.h>
#include <sys/uuid.h>

#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <netdb.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <util.h>

#include "defs.h"
#include "devpath.h"
#include "devpath3.h"
#include "utils.h"

#define easprintf       (size_t)easprintf

#if 0
GUID= EFI_PC_ANSI_GUID
GUID= EFI_VT_100_GIUD
GUID= EFI_VT_100_PLUS_GUID
GUID= EFI_VT_UTF8_GUID
GUID= DEVICE_PATH_MESSAGING_UART_FLOW_CONTROL
GUID= EFI_DEBUGPORT_PROTOCOL_GUID
GUID= d487ddb4-008b-11d9-afdc-001083ffca4d
#endif

/* Used in sub-type 10 */
#define EFI_PC_ANSI_GUID\
  {0xe0c14753,0xf9be,0x11d2,0x9a,0x0c,{0x00,0x90,0x27,0x3f,0xc1,0x4d}}

#define EFI_VT_100_GUID\
  {0xdfa66065,0xb419,0x11d3,0x9a,0x2d,{0x00,0x90,0x27,0x3f,0xc1,0x4d}}

#define EFI_VT_100_PLUS_GUID\
  {0x7baec70b,0x57e0,0x4c76,0x8e,0x87,{0x2f,0x9e,0x28,0x08,0x83,0x43}}

#define EFI_VT_UTF8_GUID\
  {0xad15a0d6,0x8bec,0x4acf,0xa0,0x73,{0xd0,0x1d,0xe7,0x7e,0x2d,0x88}}

#define EFI_DEBUGPORT_PROTOCOL_GUID \
  {0xeba4e8d2,0x3858,0x41ec,0xa2,0x81,{0x26,0x47,0xba,0x96,0x60,0xd0}}

#define DEVICE_PATH_MESSAGING_UART_FLOW_CONTROL \
  {0x37499a9d,0x542f,0x4c89,0xa0,0x26,{0x35,0xda,0x14,0x20,0x94,0xe4}}

#define EFI_VENDOR_SAS\
  {0xd487ddb4,0x008b,0x11d9,0xaf,0xdc,{0x00,0x10,0x83,0xff,0xca,0x4d}}

/************************************************************************/

static inline char *
upcase(char *str)
{

        for (char *p = str; *p != '\0'; p++)
                *p = (char)toupper((int)(unsigned char)*p);
        return str;
}

static inline char *
proto_name(uint16_t proto)      /* See RFC3233/RFC1700 */
{
        struct protoent *ent;
        char *name;
        char *bp;

        ent = getprotobynumber(proto);
        if (ent == NULL) {
                easprintf(&bp, "0x%04x", proto);
                return bp;
        }
        name = estrdup(ent->p_name);
        return upcase(name);
}

static inline char *
ipv4_addr(uint32_t addr)
{
        char *bp;

        bp = ecalloc(1, INET_ADDRSTRLEN);
        if (inet_ntop(AF_INET, &addr, bp, INET_ADDRSTRLEN) == NULL)
                err(EXIT_FAILURE, "%s: inet_ntop(AF_INET)", __func__);
        return bp;
}

static inline char *
ipv4_type(uint8_t type)
{

        switch (type) {
        case 0:         return estrdup("DHCP");
        case 1:         return estrdup("Static");
        default:        return estrdup("Unknown");
        }
}

static inline char *
ipv6_addr(struct in6_addr *addr)
{
        char *bp;

        bp = ecalloc(1, INET6_ADDRSTRLEN);
        if (inet_ntop(AF_INET6, addr, bp, INET6_ADDRSTRLEN) == NULL)
                err(EXIT_FAILURE, "%s: inet_ntop(AF_INET6)", __func__);
        return bp;
}

static inline char *
ipv6_type(uint8_t type)
{

        switch (type) {
        case 0:         return estrdup("Static");
        case 1:         return estrdup("StatelessAutoConfigure");
        case 2:         return estrdup("StatefulAutoConfigure");
        default:        return estrdup("Unknown");
        }
}

/************************************************************************
 * Type 3 - Messaging Device Path
 ************************************************************************/
static void
devpath_msg_atapi(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.1 */
        struct {                        /* Sub-Type 1 */
                devpath_t       hdr;    /* Length = 8 */
                uint8_t         IsSecondary;
                uint8_t         IsSlave;
                uint16_t        LUN;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 8);

        path->sz = easprintf(&path->cp, "ATAPI(%u,%u,%u)",
            p->IsSecondary, p->IsSlave, p->LUN);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(IsSecondary: %u\n)
                    DEVPATH_FMT(IsPrimary: %u\n)
                    DEVPATH_FMT(LUN: %u\n),
                    DEVPATH_DAT_HDR(dp),
                    p->IsSecondary,
                    p->IsSlave,
                    p->LUN);
        }
}

static void
devpath_msg_scsi(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.2 */
        struct {                        /* Sub-Type 2 */
                devpath_t       hdr;    /* Length = 8 */
                uint16_t        SCSITargetID;           /* PUN */
                uint16_t        SCSILogicalUnitNum;     /* LUN */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 8);

        path->sz = easprintf(&path->cp, "SCSI(%u,%u)",
            p->SCSITargetID, p->SCSILogicalUnitNum);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(SCSITargetID: %u\n)
                    DEVPATH_FMT(SCSILogicalUnitNum: %u\n),
                    DEVPATH_DAT_HDR(dp),
                    p->SCSITargetID,
                    p->SCSILogicalUnitNum);
        }
}

static void
devpath_msg_fibre(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.3 */
        struct {                        /* Sub-Type 3 */
                devpath_t       hdr;    /* Length = 24 */
                uint32_t        Reserved;
                uint64_t        WWName;
                uint64_t        LUN;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 24);

        path->sz = easprintf(&path->cp, "Fibre(0x%016" PRIx64 ",0x%016"
            PRIx64 ")", p->WWName, p->LUN);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Reserved: 0x%08x\n)
                    DEVPATH_FMT(WWName:) " 0x%016" PRIx64 "\n"
                    DEVPATH_FMT(LUN:) " 0x%016" PRIx64 "\n",
                    DEVPATH_DAT_HDR(dp),
                    p->Reserved,
                    p->WWName,
                    p->LUN);
        }
}

static void
devpath_msg_11394(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.4 */
        struct {                        /* Sub-Type 4 */
                devpath_t       hdr;    /* Length = 16 */
                uint32_t        Reserved;
                uint64_t        GUID;   /* XXX: not an EFI_GUID */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 16);

        path->sz = easprintf(&path->cp, "11394(0x%016" PRIx64 ")", p->GUID);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Reserved: 0x%08x\n)
                    DEVPATH_FMT(GUID:) " 0x%016" PRIx64 "\n",
                    DEVPATH_DAT_HDR(dp),
                    p->Reserved,
                    p->GUID);
        }
}

static void
devpath_msg_usb(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.5 */
        struct {                        /* Sub-Type 5 */
                devpath_t       hdr;    /* Length = 6 */
                uint8_t         USBParentPortNum;
                uint8_t         USBInterfaceNum;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 6);

        path->sz = easprintf(&path->cp, "USB(%u,%u)",
            p->USBParentPortNum, p->USBInterfaceNum);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(USBParentPortNum: %u\n)
                    DEVPATH_FMT(USBInterfaceNum: %u\n),
                    DEVPATH_DAT_HDR(dp),
                    p->USBParentPortNum,
                    p->USBInterfaceNum);
        }
}

static void
devpath_msg_i2o(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.9 */
        struct {                        /* Sub-Type 6 */
                devpath_t       hdr;    /* Length = 8 */
                uint32_t        TID;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 8);

        path->sz = easprintf(&path->cp, "I2O(0x%08x)", p->TID);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(TID: 0x%08x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->TID);
        }
}

#define INFINIBAND_FLAG_BITS \
        "\177\020"              \
        "b\x0""Service\0"       \
        "b\x1""BootEnv\0"       \
        "b\x2""ConProto\0"      \
        "b\x3""Storage\0"       \
        "b\x4""NetWork\0"       \
        "f\x5\11""Resvd\0"

static void
devpath_msg_infiniband(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.14 */
        struct {                        /* Sub-Type 9 */
                devpath_t       hdr;    /* Length = 48 */
                uint32_t        Flags;
                uuid_t          GUID;
                uint64_t        ServiceID;
                uint64_t        PortID;
                uint64_t        DeviceID;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 48);
        char uuid_str[UUID_STR_LEN];

        uuid_snprintf(uuid_str, sizeof(uuid_str), &p->GUID);

        path->sz = easprintf(&path->cp, "Infiniband(%#x,%s,0x%016" PRIx64
            ",0x%016" PRIx64 ",0x%016" PRIx64 ")",
            p->Flags, uuid_str, p->ServiceID, p->PortID, p->DeviceID);

        if (dbg != NULL) {
                char flags_str[128];

                snprintb(flags_str, sizeof(flags_str), INFINIBAND_FLAG_BITS,
                    p->Flags);

                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Flags: %s\n)
                    DEVPATH_FMT(GUID: %s\n)
                    DEVPATH_FMT(ServiceID:) " 0x%016" PRIx64 "\n"
                    DEVPATH_FMT(PortID:) " 0x%016" PRIx64 "\n"
                    DEVPATH_FMT(DeviceID:) " 0x%016" PRIx64 "\n",
                    DEVPATH_DAT_HDR(dp),
                    flags_str, uuid_str,
                    p->ServiceID, p->PortID, p->DeviceID);
        }
}

/****************************************
 * Sub-Type 10 variants
 */
static void
devpath_msg_debugport(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 18.3.7 */
        struct {                        /* Sub-Type 10 */
                devpath_t       hdr;    /* Length = 20 */
                uuid_t          GUID;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 20);

        path->sz = easprintf(&path->cp, "DebugPort()");

        if (dbg != NULL) {
                char uuid_str[UUID_STR_LEN];

                uuid_snprintf(uuid_str, sizeof(uuid_str), &p->GUID);

                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(GUID: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    uuid_str);
        }
}

static void
devpath_msg_uartflowctl(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.17 */
        struct {                        /* Sub-Type 10 */
                devpath_t       hdr;    /* Length = 24 */
                uuid_t          GUID;
                uint32_t        FlowCtl;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 24);
        const char *fc_str;

        switch (p->FlowCtl) {
        case 0:         fc_str = "None";        break;
        case 1:         fc_str = "Hardware";    break;
        case 2:         fc_str = "XonXoff";     break;
        default:        fc_str = "????";        break;
        }

        path->sz = easprintf(&path->cp, "UartFlowCtrl(%s)", fc_str);

        if (dbg != NULL) {
                char uuid_str[UUID_STR_LEN];

                uuid_snprintf(uuid_str, sizeof(uuid_str), &p->GUID);

                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(GUID: %s\n)
                    DEVPATH_FMT(FlowCtl: 0x%04x(%s)\n),
                    DEVPATH_DAT_HDR(dp),
                    uuid_str,
                    p->FlowCtl, fc_str);
        }
}

/****************************************
 * Macros for dealing with SAS/SATA device and topology info field.
 */

#define SASINFO_BYTES_MASK      __BITS(3,0)
#define SASINFO_SATA_BIT        __BIT(4)
#define SASINFO_EXTERNAL_BIT    __BIT(5)
#define SASINFO_EXPANDER_BIT    __BIT(6)

#define SASINFO_BYTES(b)        __SHIFTOUT((b), SASINFO_BYTES_MASK)
#define SASINFO_IS_SATA(b)      ((b) % SASINFO_SATA_BIT)
#define SASINFO_IS_EXTERNAL(b)  ((b) % SASINFO_EXTERNAL_BIT)
#define SASINFO_IS_EXPANDER(b)  ((b) % SASINFO_EXPANDER_BIT)

#define SASINFO_SASSATA(b)  (SASINFO_IS_SATA(b)     ? "SATA"     : "SAS")
#define SASINFO_EXTERNAL(b) (SASINFO_IS_EXTERNAL(b) ? "External" : "Internal")
#define SASINFO_EXPANDER(b) (SASINFO_IS_EXPANDER(b) ? "Expander" : "direct")

/****************************************/

static void
devpath_msg_sas(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.18 */
        struct {                        /* Sub-Type 10 */
                devpath_t       hdr;    /* Length = 44 */
                uuid_t          GUID;
                uint32_t        resv;
                uint64_t        addr;   /* display as big-endian */
                uint64_t        LUN;    /* display as big-endian */
                union {
                        uint8_t b[2];
                        uint16_t val;
                } __packed info;        /* device/topology info */
                uint16_t        RTP;    /* Relative Target Port */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 44);

        /*
         * There seems to be a discrepency between the display
         * examples in 10.3.4.18, 10.3.4.18.4, and Table 10.67.  The
         * first example in 10.3.4.18 also refers to a third number
         * (RTP) which isn't present.  I also find the info in Table
         * 10.67 a bit unclear.
         */
        if (SASINFO_BYTES(p->info.b[0]) == 0) {
                path->sz = easprintf(&path->cp, "SAS(0x%064" PRIx64
                    ",0x%064" PRIx64 ")",
                    htobe64(p->addr), htobe64(p->LUN));
        }
        else if (SASINFO_BYTES(p->info.b[0]) == 1) {
                if (SASINFO_IS_SATA(p->info.b[0])) {
                        path->sz = easprintf(&path->cp, "SAS(0x%064" PRIx64
                            ",0x%064" PRIx64 ",SATA)",
                            htobe64(p->addr), htobe64(p->LUN));
                }
                else {
                        path->sz = easprintf(&path->cp, "SAS(0x%064" PRIx64
                            ",SAS)", htobe64(p->addr));
                }
        }
        else {
                assert(SASINFO_BYTES(p->info.b[0]) == 2);
                uint drivebay = p->info.b[1] + 1;
                path->sz = easprintf(&path->cp, "SAS(0x%064" PRIx64
                    ",0x%064" PRIx64 ",0x%04x,%s,%s,%s,%d,0x%04x)",
                    htobe64(p->addr), htobe64(p->LUN), p->RTP,
                    SASINFO_SASSATA(p->info.b[0]),
                    SASINFO_EXTERNAL(p->info.b[0]),
                    SASINFO_EXPANDER(p->info.b[0]),
                    drivebay,
                    p->resv);
        }

        if (dbg != NULL) {
                char uuid_str[UUID_STR_LEN];

                uuid_snprintf(uuid_str, sizeof(uuid_str), &p->GUID);

                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(GUID: %s\n)
                    DEVPATH_FMT(resv: 0x%08x\n)
                    DEVPATH_FMT(addr:) " 0x%064" PRIx64 "(0x%064" PRIx64 ")\n"
                    DEVPATH_FMT(LUN:) "  0x%064" PRIx64 "(0x%064" PRIx64 ")\n"
                    DEVPATH_FMT(info: 0x%02x 0x%02x\n)
                    DEVPATH_FMT(RPT:  0x%04x\n),
                    DEVPATH_DAT_HDR(dp),
                    uuid_str,
                    p->resv,
                    p->addr, htobe64(p->addr),
                    p->LUN,  htobe64(p->LUN),
                    p->info.b[0], p->info.b[1],
                    p->RTP);
        }
}

static struct vendor_type {
        uuid_t GUID;
        void(*fn)(devpath_t *, devpath_elm_t *, devpath_elm_t *);
        const char *name;
} *
devpath_msg_vendor_type(uuid_t *uuid)
{
        static struct vendor_type tbl[] = {
                { .GUID = EFI_PC_ANSI_GUID,
                  .fn   = NULL,
                  .name = "VenPcAnsi",
                },
                { .GUID = EFI_VT_100_GUID,
                  .fn   = NULL,
                  .name = "VenVt100",
                },
                { .GUID = EFI_VT_100_PLUS_GUID,
                  .fn   = NULL,
                  .name = "VenVt100Plus",
                },
                { .GUID = EFI_VT_UTF8_GUID,
                  .fn   = NULL,
                  .name = "VenUtf8",
                },
                /********/
                /* See 18.3.7 */
                { .GUID = EFI_DEBUGPORT_PROTOCOL_GUID,
                  .fn   = devpath_msg_debugport,
                  .name = "DebugPort",
                },
                /********/
                /* See 10.3.4.17 */
                { .GUID = DEVICE_PATH_MESSAGING_UART_FLOW_CONTROL,
                  .fn   = devpath_msg_uartflowctl,
                  .name = "UartFlowCtrl",
                },
                /* See 10.3.4.18 */
                { .GUID = EFI_VENDOR_SAS,
                  .fn   = devpath_msg_sas,
                  .name = "SAS",
                },
        };

        for (size_t i = 0; i < __arraycount(tbl); i++) {
                if (memcmp(uuid, &tbl[i].GUID, sizeof(*uuid)) == 0) {
                        return &tbl[i];
                }
        }
        return NULL;
}

static void
devpath_msg_vendor(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.16 */
        struct {                        /* Sub-Type 10 */
                devpath_t       hdr;    /* Length = 20 + n */
                uuid_t          GUID;
                uint8_t         data[];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 20);
        char uuid_str[UUID_STR_LEN];
        size_t len;
        struct vendor_type *vt;
        char *data_str;

        uuid_snprintf(uuid_str, sizeof(uuid_str), &p->GUID);

        len = dp->Length - sizeof(*p);
        data_str = encode_data(p->data, len);

        vt = devpath_msg_vendor_type(&p->GUID);
        if (vt == NULL) {
                path->sz = easprintf(&path->cp, "VenMsg(%s,%s)",
                    uuid_str, data_str);
        }
        else if (vt->fn == NULL) {
                assert(vt->name != NULL);
                path->sz = easprintf(&path->cp, "%s(%s)", vt->name, data_str);
        }
        else {
                vt->fn(dp, path, dbg);
                return;
        }
        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(GUID: %s\n)
                    DEVPATH_FMT(Data: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    uuid_str,
                    data_str);
        }
        free(data_str);
}

static void
devpath_msg_mac(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.10 */
        struct {                        /* Sub-Type 11 */
                devpath_t       hdr;    /* Length = 37 */
                uint8_t         mac_addr[32];
                uint8_t         IfType;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 37);
        size_t addr_len;
        char *mac_addr;

        switch (p->IfType) {
        case 0:
        case 1:
                addr_len = 6;
                break;
        default:
                addr_len = 32;
                break;
        }
#if 0
 From: RFC3232 says everything is now online, found here:
 https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml

XXX: Should we make a table so we can show a name?

    0  Reserved [RFC5494]
    1  Ethernet (10Mb) [Jon_Postel]
    2  Experimental Ethernet (3Mb) [Jon_Postel]
    3  Amateur Radio AX.25 [Philip_Koch]
    4  Proteon ProNET Token Ring [Avri_Doria]
    5  Chaos [Gill_Pratt]
    6  IEEE 802 Networks [Jon_Postel]
    7  ARCNET [RFC1201]
    8  Hyperchannel [Jon_Postel]
    9  Lanstar [Tom_Unger]
    10 Autonet Short Address [Mike_Burrows]
    11 LocalTalk [Joyce_K_Reynolds]
    12 LocalNet (IBM PCNet or SYTEK LocalNET) [Joseph Murdock]
    13 Ultra link [Rajiv_Dhingra]
    14 SMDS [George_Clapp]
    15 Frame Relay [Andy_Malis]
    16 Asynchronous Transmission Mode (ATM) [[JXB2]]
    17 HDLC [Jon_Postel]
    18 Fibre Channel [RFC4338]
    19 Asynchronous Transmission Mode (ATM) [RFC2225]
    20 Serial Line [Jon_Postel]
    21 Asynchronous Transmission Mode (ATM) [Mike_Burrows]
    22 MIL-STD-188-220 [Herb_Jensen]
    23 Metricom [Jonathan_Stone]
    24 IEEE 1394.1995 [Myron_Hattig]
    25 MAPOS [Mitsuru_Maruyama][RFC2176]
    26 Twinaxial [Marion_Pitts]
    27 EUI-64 [Kenji_Fujisawa]
    28 HIPARP [Jean_Michel_Pittet]
    29 IP and ARP over ISO 7816-3 [Scott_Guthery]
    30 ARPSec [Jerome_Etienne]
    31 IPsec tunnel [RFC3456]
    32 InfiniBand (TM) [RFC4391]
    33 TIA-102 Project 25 Common Air Interface (CAI) [Jeff Anderson, Telecommunications Industry of America (TIA) TR-8.5 Formulating Group, <cja015&motorola.com>, June 2004]
    34 Wiegand Interface [Scott_Guthery_2]
    35 Pure IP [Inaky_Perez-Gonzalez]
    36 HW_EXP1 [RFC5494]
    37 HFI [Tseng-Hui_Lin]
    38 Unified Bus (UB) [Wei_Pan]
    39-255 Unassigned
    256 HW_EXP2 [RFC5494]
    257 AEthernet [Geoffroy_Gramaize]
    258-65534 Unassigned
    65535 Reserved [RFC5494]
#endif

        mac_addr = encode_data(p->mac_addr, addr_len);
        path->sz = easprintf(&path->cp, "MAC(%s,%d)", mac_addr, p->IfType);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(MAC_addr: %s\n)
                    DEVPATH_FMT(IfType: %x\n),
                    DEVPATH_DAT_HDR(dp),
                    mac_addr,
                    p->IfType);
        }
        free(mac_addr);
}

static void
devpath_msg_ipv4(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.11 */
        struct {                        /* Sub-Type 12 */
                devpath_t       hdr;    /* Length = 27 */
                uint32_t        LocalIPAddr;
                uint32_t        RemoteIPAddr;
                uint16_t        LocalPort;
                uint16_t        RemotePort;
                uint16_t        Protocol;
                uint8_t         StaticIPAddr;
                uint32_t        GatewayIPAddr;
                uint32_t        SubnetMask;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 27);
        char *gaddr, *laddr, *raddr, *mask;
        char *proto, *atype;

        laddr = ipv4_addr(p->LocalIPAddr);
        gaddr = ipv4_addr(p->GatewayIPAddr);
        raddr = ipv4_addr(p->RemoteIPAddr);
        mask  = ipv4_addr(p->SubnetMask);
        proto = proto_name(p->Protocol);
        atype = ipv4_type(p->StaticIPAddr);

        path->sz = easprintf(&path->cp, "IPv4(%s,%s,%s,%s,%s,%s)",
            raddr, proto, atype, laddr, gaddr, mask);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(LocalIPAddr: 0x%08x(%s)\n)
                    DEVPATH_FMT(RemoteIPAddr: 0x%08x(%s)\n)
                    DEVPATH_FMT(LocalPort: 0x%04x\n)
                    DEVPATH_FMT(RemotePort: 0x%04x\n)
                    DEVPATH_FMT(Protocol: 0x%04x(%s)\n)
                    DEVPATH_FMT(StaticIPAddr: 0x%02x(%s)\n)
                    DEVPATH_FMT(GatewayIPAddr: 0x%08x(%s)\n)
                    DEVPATH_FMT(SubnetMask: 0x%08x(%s)\n),
                    DEVPATH_DAT_HDR(dp),
                    p->LocalIPAddr, laddr,
                    p->RemoteIPAddr, raddr,
                    p->LocalPort,
                    p->RemotePort,
                    p->Protocol, proto,
                    p->StaticIPAddr, atype,
                    p->GatewayIPAddr, gaddr,
                    p->SubnetMask, mask);
        }
        free(atype);
        free(proto);
        free(mask);
        free(raddr);
        free(gaddr);
        free(laddr);
}

static void
devpath_msg_ipv6(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.12 */
        struct {                        /* Sub-Type 13 */
                devpath_t       hdr;    /* Length = 60 */
                struct in6_addr LocalIPAddr;
                struct in6_addr RemoteIPAddr;
                uint16_t        LocalPort;
                uint16_t        RemotePort;
                uint16_t        Protocol;
                uint8_t         IPAddrOrigin;
                uint8_t         PrefixLength;
                struct in6_addr GatewayIPAddr;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 60);
        char *gaddr, *laddr, *raddr;
        char *proto, *atype;

        laddr = ipv6_addr(&p->LocalIPAddr);
        gaddr = ipv6_addr(&p->GatewayIPAddr);
        raddr = ipv6_addr(&p->RemoteIPAddr);
        proto = proto_name(p->Protocol);
        atype = ipv6_type(p->IPAddrOrigin);

        path->sz = easprintf(&path->cp, "IPv6(%s,%s,%s,%s,%s)",
            raddr, proto, atype, laddr, gaddr);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(LocalIPAddr: %s\n)
                    DEVPATH_FMT(RemoteIPAddr: %s\n)
                    DEVPATH_FMT(LocalPort: 0x%04x\n)
                    DEVPATH_FMT(RemotePort: 0x%04x\n)
                    DEVPATH_FMT(Protocol: 0x%04x(%s)\n)
                    DEVPATH_FMT(IPAddrOrigin: 0x%02x(%s)\n)
                    DEVPATH_FMT(PrefixLength: 0x%02x\n)
                    DEVPATH_FMT(GatewayIPAddr: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    laddr,
                    raddr,
                    p->LocalPort,
                    p->RemotePort,
                    p->Protocol, proto,
                    p->IPAddrOrigin, atype,
                    p->PrefixLength,
                    gaddr);
        }
        free(atype);
        free(proto);
        free(raddr);
        free(gaddr);
        free(laddr);
}

static inline const char *
uart_parity(uint8_t n)
{

        switch (n) {
        case 0:         return "D";
        case 1:         return "N";
        case 2:         return "E";
        case 3:         return "O";
        case 4:         return "M";
        case 5:         return "S";
        default:        return "?";
        }
}

static inline const char *
uart_stopbits(uint8_t n)
{

        switch (n) {
        case 0:         return "D";
        case 1:         return "1";
        case 2:         return "1.5";
        case 3:         return "2";
        default:        return "?";
        }
}

static void
devpath_msg_uart(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.15 */
        struct {                        /* Sub-Type 14 */
                devpath_t       hdr;    /* Length = 19 */
                uint32_t        Reserved;
                uint64_t        BaudRate;
                uint8_t         DataBits;
                uint8_t         Parity;
                uint8_t         StopBits;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 19);
        const char *parity, *stopbits;

        parity = uart_parity(p->Parity);
        stopbits = uart_stopbits(p->StopBits);

        path->sz = easprintf(&path->cp, "Uart(%" PRIx64 ",%u,%s,%s)",
            p->BaudRate, p->DataBits, parity, stopbits);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Reserved: 0x%08x\n)
                    DEVPATH_FMT(BaudRate:) " 0x%016" PRIx64 "\n"
                    DEVPATH_FMT(DataBits: 0x%1x\n)
                    DEVPATH_FMT(Parity:   0x%1x(%s)\n)
                    DEVPATH_FMT(StopBits: 0x%1x(%s)\n),
                    DEVPATH_DAT_HDR(dp),
                    p->Reserved,
                    p->BaudRate,
                    p->DataBits,
                    p->Parity, parity,
                    p->StopBits, stopbits);
        }
}

static inline const char *
usbclass_name(uint8_t class, uint8_t subclass)
{

        switch (class) {
        case 1:         return "UsbAudio";
        case 2:         return "UsbCDCControl";
        case 3:         return "UsbHID";
        case 6:         return "UsbImage";
        case 7:         return "UsbPrintr";
        case 8:         return "UsbMassStorage";
        case 9:         return "UsbHub";
        case 10:        return "UsbCDCData";
        case 11:        return "UsbSmartCard";
        case 14:        return "UsbVideo";
        case 220:       return "UsbDiagnostic";
        case 224:       return "UsbWireless";
        case 254:
                switch (subclass) {
                case 1:         return "UsbDeviceFirmwareUpdate";
                case 2:         return "UsbIrdaBridge";
                case 3:         return "UsbTestAndMeasurement";
                default:        return NULL;
                }
        default:        return NULL;
        }
}

static void
devpath_msg_usbclass(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.8 */
        struct {                        /* Sub-Type 15 */
                devpath_t       hdr;    /* Length = 11 */
                uint16_t        VendorID;
                uint16_t        ProductID;
                uint8_t         DeviceClass;
                uint8_t         DeviceSubClass;
                uint8_t         DeviceProtocol;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 11);
        const char *name;

        name = usbclass_name(p->DeviceClass, p->DeviceSubClass);
        if (name == NULL) {
                path->sz = easprintf(&path->cp,
                    "UsbClass(0x%04x,0x%04x,0x%02x,0x%02x,0x%02x,)",
                    p->VendorID, p->ProductID, p->DeviceClass,
                    p->DeviceSubClass, p->DeviceProtocol);
        }
        else if (p->DeviceClass != 254) {
                path->sz = easprintf(&path->cp,
                    "%s(0x%04x,0x%04x,0x%02x,0x%02x,)",
                    name, p->VendorID, p->ProductID, p->DeviceSubClass,
                    p->DeviceProtocol);
        }
        else {
                path->sz = easprintf(&path->cp, "%s(0x%04x,0x%04x,0x%02x,)",
                    name, p->VendorID, p->ProductID, p->DeviceProtocol);
        }

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(VendorID:  0x%04x\n)
                    DEVPATH_FMT(ProductID: 0x%04x\n)
                    DEVPATH_FMT(DeviceClass: 0x%02x(%u)\n)
                    DEVPATH_FMT(DeviceSubClass: 0x%02x(%u)\n)
                    DEVPATH_FMT(DeviceProtocol: 0x%02x(%u)\n),
                    DEVPATH_DAT_HDR(dp),
                    p->VendorID,
                    p->ProductID,
                    p->DeviceClass, p->DeviceClass,
                    p->DeviceSubClass, p->DeviceSubClass,
                    p->DeviceProtocol, p->DeviceProtocol);
        }
}

static void
devpath_msg_usbwwid(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.7 */
        struct {                        /* Sub-Type 16 */
                devpath_t       hdr;    /* Length = 10 + n */
                uint16_t        IFaceNum;
                uint16_t        VendorID;
                uint16_t        ProductID;
                uint8_t         SerialNum[];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 10);
        char *serialnum;
        size_t seriallen;

        seriallen = p->hdr.Length - offsetof(typeof(*p), SerialNum);
        if (seriallen == 0) {
                serialnum = __UNCONST("WWID");  /* XXX: This is an error! */
                assert(0);
        }
        else {
                size_t sz = p->hdr.Length - sizeof(*p);
                serialnum = ucs2_to_utf8((uint16_t *)p->SerialNum, sz, NULL,
                    NULL);
        }

        path->sz = easprintf(&path->cp, "UsbWwid(0x%04x,0x%04x,0x%02x,%s)",
            p->VendorID, p->ProductID, p->IFaceNum, serialnum);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(IFaceNum: 0x%04x\n)
                    DEVPATH_FMT(VendorID: 0x%04x\n)
                    DEVPATH_FMT(ProductID: 0x%04x\n)
                    DEVPATH_FMT(SerialNum: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    p->IFaceNum, p->VendorID, p->ProductID, serialnum);
        }

        if (seriallen)
                free(serialnum);
}

static void
devpath_msg_unit(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.8 */
        struct {                        /* Sub-Type 17 */
                devpath_t       hdr;    /* Length = 5 */
                uint8_t         LUN;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 5);

        path->sz = easprintf(&path->cp, "Unit(%u)", p->LUN);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(LUN: %u\n),
                    DEVPATH_DAT_HDR(dp),
                    p->LUN);
        }
}

static void
devpath_msg_sata(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.6 */
        struct {                        /* Sub-Type 18 */
                devpath_t       hdr;    /* Length = 10 */
                uint16_t        SATAHBAPortNum;
                uint16_t        SATAPortMultiplierPortNum;
                uint16_t        SATALogicalUnitNum;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 10);

        path->sz = easprintf(&path->cp, "SATA(%u,%u,%u)", p->SATAHBAPortNum,
            p->SATAPortMultiplierPortNum, p->SATALogicalUnitNum);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(SATAHBAPortNum: %u\n)
                    DEVPATH_FMT(SATAPortMultiplierPortNum: %u\n)
                    DEVPATH_FMT(SATALogicalUnitNum: %u\n),
                    DEVPATH_DAT_HDR(dp),
                    p->SATAHBAPortNum,
                    p->SATAPortMultiplierPortNum,
                    p->SATALogicalUnitNum);
        }
}

static inline const char *
hdrdgst_name(uint16_t LoginOptions)
{

        switch (__SHIFTOUT(LoginOptions, __BITS(1,0))) {
        case 0:         return "None";
        case 2:         return "CRC32C";
        default:        return "???";
        }
}

static inline const char *
datdgst_name(uint16_t LoginOptions)
{

        switch (__SHIFTOUT(LoginOptions, __BITS(3,2))) {
        case 0:         return "None";
        case 2:         return "CRC32C";
        default:        return "???";
        }
}

static inline const char *
auth_name(uint16_t LoginOptions)
{

        switch (__SHIFTOUT(LoginOptions, __BITS(12,10))) {
        case 0:         return "CHAP_BI";
        case 2:         return "None";
        case 4:         return "CHAP_UNI";
        default:        return "???";
        }
}

static inline const char *
protocol_name(uint16_t Protocol)
{

        return Protocol == 0 ? "TCP" : "RSVD";
}

static void
devpath_msg_iscsi(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.20 */
#define LOGIN_OPTION_BITS \
        "\177\020"              \
        "f\0\2""HdrDgst\0"      \
        "=\x0""None\0"          \
        "=\x2""CRC32C\0"        \
        "f\x2\x2""DatDgst\0"    \
        "=\x0""None\0"          \
        "=\x2""CRC32C\0"        \
        "f\xA\x3""Auth\0"       \
        "=\x0""CHAP_BI\0"       \
        "=\x2""None\0"          \
        "=\x4""CHAP_UNI\0"
        struct {                        /* Sub-Type 19 */
                devpath_t       hdr;    /* Length = 18 + n */
                uint16_t        Protocol;
                uint16_t        LoginOptions;
                uint64_t        LUN;    /* display as big-endian */
                uint16_t        TargetPortalGrp;
                char            TargetName[];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 18);
        char *name;
        const char *auth, *datdgst, *hdrdgst, *proto;
        size_t len;

        /*
         * Make sure the TargetName is NUL terminated.  The spec is
         * unclear on this, though their example is asciiz.  I have
         * no real examples to test, so be safe.
         */
        len = p->hdr.Length - sizeof(*p);
        name = emalloc(len + 1);
        memcpy(name, p->TargetName, len);
        name[len] = '\0';

        auth = auth_name(p->LoginOptions);
        datdgst = datdgst_name(p->LoginOptions);
        hdrdgst = hdrdgst_name(p->LoginOptions);
        proto = protocol_name(p->Protocol);

        path->sz = easprintf(&path->cp, "iSCSI(%s,0x%04x,0x%064" PRIx64
            ",%s,%s,%s,%s)", p->TargetName, p->TargetPortalGrp,
            htobe64(p->LUN), hdrdgst, datdgst, auth, proto);

        if (dbg != NULL) {
                char liopt[256];

                snprintb(liopt, sizeof(liopt), LOGIN_OPTION_BITS,
                    p->LoginOptions);
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Protocol: 0x%04x(%s)\n)
                    DEVPATH_FMT(LoginOptions: %s\n)
                    DEVPATH_FMT(LUN:) " 0x%064" PRIx64 "\n"
                    DEVPATH_FMT(TargetPortalGrp: 0x%04x\n)
                    DEVPATH_FMT(TargetName: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    p->Protocol, proto,
                    liopt,
                    htobe64(p->LUN),
                    p->TargetPortalGrp,
                    name);
        }

        free(name);
}

static void
devpath_msg_vlan(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.13 */
        struct {                        /* Sub-Type 20 */
                devpath_t       hdr;    /* Length = 6 */
                uint16_t        Vlanid;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 6);

        path->sz = easprintf(&path->cp, "Vlan(0x%04x)", p->Vlanid);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Vlanid: 0x%04x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->Vlanid);
        }
}

static void
devpath_msg_fibreex(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.3 */
        struct {                        /* Sub-Type 21 */
                devpath_t       hdr;    /* Length = 24 */
                uint32_t        Reserved;
                uint64_t        WorldWideName;  /* bit-endian */
                uint64_t        LUN;            /* bit-endian */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 24);

        path->sz = easprintf(&path->cp, "FibreEx(0x%064" PRIx64 ",0x%064"
            PRIx64 ")", htobe64(p->WorldWideName), htobe64(p->LUN));

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(WorldWideName:) " 0x%064" PRIx64 "\n"
                    DEVPATH_FMT(LUN:) " 0x%064" PRIx64 "\n",
                    DEVPATH_DAT_HDR(dp),
                    htobe64(p->WorldWideName),
                    htobe64(p->LUN));
        }
}

static void
devpath_msg_sasex(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.18 */
        struct {                        /* Sub-Type 22 */
                devpath_t       hdr;    /* Length = 24 XXX: 32 in text */
                uint64_t        addr;   /* display as big-endian */
                uint64_t        LUN;    /* display as big-endian */
                union {
                        uint8_t b[2];
                        uint16_t val;
                } __packed info;        /* device/topology info */
                uint16_t        RTP;    /* Relative Target Port */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 24);

        /*
         * XXX: This should match what is in devpath_msg_sas().
         * Should we share code?
         */
        if (SASINFO_BYTES(p->info.b[0]) == 0) {
                path->sz = easprintf(&path->cp, "SasEx(0x%064" PRIx64
                    ",0x%064" PRIx64 ")",
                    htobe64(p->addr), htobe64(p->LUN));
        }
        else if (SASINFO_BYTES(p->info.b[0]) == 1) {
                if (SASINFO_IS_SATA(p->info.b[0])) {
                        path->sz = easprintf(&path->cp, "SasEx(0x%064" PRIx64
                            ",0x%064" PRIx64 ",SATA)",
                            htobe64(p->addr), htobe64(p->LUN));
                }
                else {
                        path->sz = easprintf(&path->cp, "SasEx(0x%064" PRIx64
                            ",SAS)", htobe64(p->addr));
                }
        }
        else {
                assert(SASINFO_BYTES(p->info.b[0]) == 2);
                uint drivebay = p->info.b[1] + 1;
                path->sz = easprintf(&path->cp, "SasEx(0x%064" PRIx64
                    ",0x%064" PRIx64 ",0x%04x,%s,%s,%s,%d)",
                    htobe64(p->addr), htobe64(p->LUN), p->RTP,
                    SASINFO_SASSATA(p->info.b[0]),
                    SASINFO_EXTERNAL(p->info.b[0]),
                    SASINFO_EXPANDER(p->info.b[0]),
                    drivebay);
        }

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(addr:) " 0x%064" PRIx64 "(0x%064" PRIx64 ")\n"
                    DEVPATH_FMT(LUN:) "  0x%064" PRIx64 "(0x%064" PRIx64 ")\n"
                    DEVPATH_FMT(info: 0x%02x 0x%02x\n)
                    DEVPATH_FMT(RPT:  0x%04x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->addr, htobe64(p->addr),
                    p->LUN,  htobe64(p->LUN),
                    p->info.b[0], p->info.b[1],
                    p->RTP);
        }
}

static void
devpath_msg_nvme(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.21 */
        struct {                        /* Sub-Type 23 */
                devpath_t       hdr;    /* Length = 16 */
                uint32_t        NSID;   /* Name Space Identifier */
                union {
                        uint8_t         b[8];
                        uint64_t        val;
                } EUI;  /* IEEE Extended Unique Identifier */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 16);

        path->sz = easprintf(&path->cp, "NVMe(0x%x,"
            "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X)",
            p->NSID,
            p->EUI.b[0], p->EUI.b[1], p->EUI.b[2], p->EUI.b[3],
            p->EUI.b[4], p->EUI.b[5], p->EUI.b[6], p->EUI.b[7]);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(NSID: 0x%08x\n)
                    DEVPATH_FMT(EUI:) "  0x%016" PRIx64 "\n",
                    DEVPATH_DAT_HDR(dp),
                    p->NSID, p->EUI.val);
        }
}

static void
devpath_msg_uri(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.22 */
        struct {                        /* Sub-Type 24 */
                devpath_t       hdr;    /* Length = 4 + n */
                uint8_t         data[];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 4);
        size_t len;
        char *buf;

        len = dp->Length - 4;

        buf = emalloc(len + 1);
        if (len > 0)
                memcpy(buf, p->data, len);
        buf[len] = '\0';

        path->sz = easprintf(&path->cp, "Uri(%s)", buf);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(Data: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    buf);
        }
        free(buf);
}

static void
devpath_msg_ufs(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.23 */
        struct {                        /* Sub-Type 25 */
                devpath_t       hdr;    /* Length = 6 */
                uint8_t         PUN;    /* Target ID */
                uint8_t         LUN;    /* Logical Unit Number */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 6);

        path->sz = easprintf(&path->cp, "UFS(%#x,0x%02x)", p->PUN, p->LUN);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(PUN: 0x%02x\n)
                    DEVPATH_FMT(LUN: 0x%02x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->PUN, p->LUN);
        }
}

static void
devpath_msg_sd(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.24 */
        struct {                        /* Sub-Type 26 */
                devpath_t       hdr;    /* Length = 5 */
                uint8_t         SlotNum;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 5);

        path->sz = easprintf(&path->cp, "SD(%d)", p->SlotNum);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(SlotNum: 0x%02x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->SlotNum);
        }
}

static void
devpath_msg_bluetooth(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.25 */
        struct {                        /* Sub-Type 27 */
                devpath_t       hdr;    /* Length = 10 */
                uint8_t         bdaddr[6];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 10);

        path->sz = easprintf(&path->cp,
            "Bluetooth(%02x:%02x:%02x:%02x:%02x:%02x)",
            p->bdaddr[0], p->bdaddr[1], p->bdaddr[2],
            p->bdaddr[3], p->bdaddr[4], p->bdaddr[5]);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(bdaddr: 0x%02x%02x%02x%02x%02x%02x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->bdaddr[0], p->bdaddr[1], p->bdaddr[2],
                    p->bdaddr[3], p->bdaddr[4], p->bdaddr[5]);
        }
}

static void
devpath_msg_wifi(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.26 */
        struct {                        /* Sub-Type 28 */
                devpath_t       hdr;    /* Length = 36 */
                char            SSID[32];/* XXX: ascii? */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 36);

        path->sz = easprintf(&path->cp, "Wi-Fi(%s)", p->SSID);

        if (dbg != NULL) {
                char *ssid;

                ssid = encode_data((uint8_t *)p->SSID, sizeof(p->SSID));
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(SSID: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    ssid);
                free(ssid);
        }
}

static void
devpath_msg_emmc(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.27 */
        struct {                        /* Sub-Type 29 */
                devpath_t       hdr;    /* Length = 5 */
                uint8_t         SlotNum;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 5);

        path->sz = easprintf(&path->cp, "eMMC(%d)", p->SlotNum);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(SlotNum: 0x%02x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->SlotNum);
        }
}

static void
devpath_msg_bluetoothle(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.28 */
        struct {                        /* Sub-Type 30 */
                devpath_t       hdr;    /* Length = 11 */
                uint8_t         bdaddr[6];
                uint8_t         addr_type;
#define BDADDR_TYPE_PUBLIC      0
#define BDADDR_TYPE_RANDOM      1
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 11);

        path->sz = easprintf(&path->cp,
            "BluetoothLE(%02x:%02x:%02x:%02x:%02x:%02x,%d)",
            p->bdaddr[0], p->bdaddr[1], p->bdaddr[2],
            p->bdaddr[3], p->bdaddr[4], p->bdaddr[5],
            p->addr_type);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(bdaddr: 0x%02x%02x%02x%02x%02x%02x\n)
                    DEVPATH_FMT(addr_type: 0x%02x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->bdaddr[0], p->bdaddr[1], p->bdaddr[2],
                    p->bdaddr[3], p->bdaddr[4], p->bdaddr[5],
                    p->addr_type);
        }
}

static void
devpath_msg_dns(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.29 */
        struct {                        /* Sub-Type 31 */
                devpath_t       hdr;    /* Length = 5 + n */
                uint8_t         IsIPv6;
                uint8_t         addr[];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 5);
        size_t cnt, n, sz;

        /* XXX: This is ugly at best */

        n = p->hdr.Length - sizeof(*p);
        sz = p->IsIPv6 ? sizeof(struct in6_addr) : sizeof(uint32_t);
        cnt = n / sz;
        assert(n == cnt * sz);
        assert(cnt > 0);

        if (cnt > 0) { /* XXX: should always be true */
                char *bp;

#define DNS_PREFIX      "Dns("
                if (p->IsIPv6) {
                        struct in6_addr *ip6addr = (void *)p->addr;

                        bp = path->cp = malloc(sizeof(DNS_PREFIX) +
                            cnt * INET6_ADDRSTRLEN);
                        bp = stpcpy(bp, DNS_PREFIX);
                        bp = stpcpy(bp, ipv6_addr(&ip6addr[0]));
                        for (size_t i = 1; i < cnt; i++) {
                                *bp++ = ',';
                                bp = stpcpy(bp, ipv6_addr(&ip6addr[i]));
                        }
                        *bp = ')';
                }
                else {  /* IPv4 */
                        uint32_t *ip4addr = (void *)p->addr;

                        bp = path->cp = malloc(sizeof(DNS_PREFIX) +
                            cnt * INET_ADDRSTRLEN);
                        bp = stpcpy(bp, DNS_PREFIX);
                        bp = stpcpy(bp, ipv4_addr(ip4addr[0]));
                        for (size_t i = 1; i < cnt; i++) {
                                *bp++ = ',';
                                bp = stpcpy(bp, ipv4_addr(ip4addr[i]));
                        }
                        *bp = ')';
                }
#undef DNS_PREFIX
                path->sz = strlen(bp);
        }

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(IsIPv6: %d\n),
                    DEVPATH_DAT_HDR(dp),
                    p->IsIPv6);

                if (cnt > 0) { /* XXX: should always be true */
                        char *bp, *tp;

                        bp = dbg->cp;
                        if (p->IsIPv6) {
                                struct in6_addr *ip6addr = (void *)p->addr;

                                for (size_t i = 0; i < cnt; i++) {
                                        uint8_t *cp;

                                        cp = ip6addr[i].__u6_addr.__u6_addr8;
                                        tp = bp;
                                        dbg->sz += easprintf(&bp,
                                            "%s"
                                            DEVPATH_FMT(addr[%zu]:)
                                            " %02x %02x %02x %02x"
                                            " %02x %02x %02x %02x"
                                            " %02x %02x %02x %02x"
                                            " %02x %02x %02x %02x\n",
                                            tp,
                                            i,
                                            cp[0],  cp[1],  cp[2],  cp[3],
                                            cp[4],  cp[5],  cp[6],  cp[7],
                                            cp[8],  cp[9],  cp[10], cp[11],
                                            cp[12], cp[13], cp[14], cp[15]);
                                        free(tp);
                                }
                        }
                        else {  /* IPv4 */
                                uint32_t *ip4addr = (void *)p->addr;

                                for (size_t i = 0; i < cnt; i++) {
                                        tp = bp;
                                        dbg->sz += easprintf(&bp,
                                            "%s"
                                            DEVPATH_FMT(addr[i]: 0x%08x\n),
                                            tp, ip4addr[i]);
                                        free(tp);
                                }
                        }
                }
        }
}

static void
devpath_msg_nvdimm(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.30 */
        struct {                        /* Sub-Type 32 */
                devpath_t       hdr;    /* Length = 20 */
                uuid_t          UUID;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 20);
        char uuid_str[UUID_STR_LEN];

        uuid_snprintf(uuid_str, sizeof(uuid_str), &p->UUID);
        path->sz = easprintf(&path->cp, "NVDIMM(%s)", uuid_str);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR,
                    DEVPATH_DAT_HDR(dp)
                );
        }
}

static void
devpath_msg_restservice(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.31 */
        struct {                        /* Sub-Type 33 */
                devpath_t       hdr;    /* Length = 6 */
                uint8_t         RestService;
#define REST_SERVICE_REDFISH    0x01
#define REST_SERVICE_ODATA      0x02
                uint8_t         AccessMode;
#define ACCESS_MODE_IN_BAND     0x01
#define ACCESS_MODE_OUT_OF_BAND 0x02
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 6);

        path->sz = easprintf(&path->cp, "RestService(%d,%d)",
            p->RestService, p->AccessMode);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(RestService: 0x%02x(%s)\n)
                    DEVPATH_FMT(AccessMode:  0x%02x(%s)\n),
                    DEVPATH_DAT_HDR(dp),
                    p->RestService,
                    p->RestService == REST_SERVICE_REDFISH ? "RedFish" :
                    p->RestService == REST_SERVICE_ODATA ? "OData" : "???",
                    p->AccessMode,
                    p->AccessMode == ACCESS_MODE_IN_BAND ? "In-Band" :
                    p->AccessMode == ACCESS_MODE_OUT_OF_BAND ? "Out-of-Band"
                        : "???");
        }
}

static void
devpath_msg_nvmeof(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.4.32 */
        struct {                        /* Sub-Type 34 */
                devpath_t       hdr;    /* Length = 21 XXX: 20 in text */
                uint8_t         NIDT;
                union {
                        uint64_t        dw[2];
                        uint8_t         csi;
                        uint64_t        ieuid;
                        uuid_t          uuid;
                } NID;  /* big-endian */
                uint8_t         SubsystemNQN[];  /* UTF-8 null-terminated */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 21);
        char uuid_str[UUID_STR_LEN];

        /*
         * See 5.1.13.2.3 of NVM-Express Base Specification Rev 2.1,
         * in particular, Fig 315 on page 332.
         */
        switch (p->NIDT) {
        case 0:
                path->sz = easprintf(&path->cp, "NVMEoF(%s)", p->SubsystemNQN);
                break;
        case 1:
                path->sz = easprintf(&path->cp, "NVMEoF(%s,0x%016" PRIx64
                    ")", p->SubsystemNQN, p->NID.ieuid);
                break;
        case 2:
        case 3:
                uuid_snprintf(uuid_str, sizeof(uuid_str), &p->NID.uuid);
                path->sz = easprintf(&path->cp, "NVMEoF(%s,%s)",
                    p->SubsystemNQN, uuid_str);
                break;
        case 4:
                path->sz = easprintf(&path->cp, "NVMEoF(%s,0x%02x)",
                    p->SubsystemNQN, p->NID.csi);
                break;
        default:
                path->sz = easprintf(&path->cp, "NVMEoF(%s,unknown)",
                    p->SubsystemNQN);
                break;
        }

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(NIDT: 0x%02x\n)
                    DEVPATH_FMT(NID:) " 0x%016" PRIx64 "%016" PRIx64 "\n"
                    DEVPATH_FMT(SubsystemNQN: '%s'\n),
                    DEVPATH_DAT_HDR(dp),
                    p->NIDT,
                    p->NID.dw[0], p->NID.dw[1],
                    p->SubsystemNQN);
        }
}

PUBLIC void
devpath_msg(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{

        assert(dp->Type == 3);

        switch (dp->SubType) {
        case 1:     devpath_msg_atapi(dp, path, dbg);           return;
        case 2:     devpath_msg_scsi(dp, path, dbg);            return;
        case 3:     devpath_msg_fibre(dp, path, dbg);           return;
        case 4:     devpath_msg_11394(dp, path, dbg);           return;
        case 5:     devpath_msg_usb(dp, path, dbg);             return;
        case 6:     devpath_msg_i2o(dp, path, dbg);             return;
        case 9:     devpath_msg_infiniband(dp, path, dbg);      return;
        case 10:    devpath_msg_vendor(dp, path, dbg);          return;
        case 11:    devpath_msg_mac(dp, path, dbg);             return;
        case 12:    devpath_msg_ipv4(dp, path, dbg);            return;
        case 13:    devpath_msg_ipv6(dp, path, dbg);            return;
        case 14:    devpath_msg_uart(dp, path, dbg);            return;
        case 15:    devpath_msg_usbclass(dp, path, dbg);        return;
        case 16:    devpath_msg_usbwwid(dp, path, dbg);         return;
        case 17:    devpath_msg_unit(dp, path, dbg);            return;
        case 18:    devpath_msg_sata(dp, path, dbg);            return;
        case 19:    devpath_msg_iscsi(dp, path, dbg);           return;
        case 20:    devpath_msg_vlan(dp, path, dbg);            return;
        case 21:    devpath_msg_fibreex(dp, path, dbg);         return;
        case 22:    devpath_msg_sasex(dp, path, dbg);           return;
        case 23:    devpath_msg_nvme(dp, path, dbg);            return;
        case 24:    devpath_msg_uri(dp, path, dbg);             return;
        case 25:    devpath_msg_ufs(dp, path, dbg);             return;
        case 26:    devpath_msg_sd(dp, path, dbg);              return;
        case 27:    devpath_msg_bluetooth(dp, path, dbg);       return;
        case 28:    devpath_msg_wifi(dp, path, dbg);            return;
        case 29:    devpath_msg_emmc(dp, path, dbg);            return;
        case 30:    devpath_msg_bluetoothle(dp, path, dbg);     return;
        case 31:    devpath_msg_dns(dp, path, dbg);             return;
        case 32:    devpath_msg_nvdimm(dp, path, dbg);          return;
        case 33:    devpath_msg_restservice(dp, path, dbg);     return;
        case 34:    devpath_msg_nvmeof(dp, path, dbg);          return;
        default:    devpath_unsupported(dp, path, dbg);         return;
        }
}