const std = @import("std");
const mem = std.mem;
const uefi = std.os.uefi;
const Allocator = mem.Allocator;
const Guid = uefi.Guid;

// All Device Path Nodes are byte-packed and may appear on any byte boundary.
// All code references to device path nodes must assume all fields are unaligned.

pub const DevicePathProtocol = extern struct {
    type: DevicePathType,
    subtype: u8,
    length: u16 align(1),

    pub const guid align(8) = Guid{
        .time_low = 0x09576e91,
        .time_mid = 0x6d3f,
        .time_high_and_version = 0x11d2,
        .clock_seq_high_and_reserved = 0x8e,
        .clock_seq_low = 0x39,
        .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
    };

    /// Returns the next DevicePathProtocol node in the sequence, if any.
    pub fn next(self: *DevicePathProtocol) ?*DevicePathProtocol {
        if (self.type == .End and @intToEnum(EndDevicePath.Subtype, self.subtype) == .EndEntire)
            return null;

        return @ptrCast(*DevicePathProtocol, @ptrCast([*]u8, self) + self.length);
    }

    /// Calculates the total length of the device path structure in bytes, including the end of device path node.
    pub fn size(self: *DevicePathProtocol) usize {
        var node = self;

        while (node.next()) |next_node| {
            node = next_node;
        }

        return (@ptrToInt(node) + node.length) - @ptrToInt(self);
    }

    /// Creates a file device path from the existing device path and a file path.
    pub fn create_file_device_path(self: *DevicePathProtocol, allocator: Allocator, path: [:0]align(1) const u16) !*DevicePathProtocol {
        var path_size = self.size();

        // 2 * (path.len + 1) for the path and its null terminator, which are u16s
        // DevicePathProtocol for the extra node before the end
        var buf = try allocator.alloc(u8, path_size + 2 * (path.len + 1) + @sizeOf(DevicePathProtocol));

        mem.copy(u8, buf, @ptrCast([*]const u8, self)[0..path_size]);

        // Pointer to the copy of the end node of the current chain, which is - 4 from the buffer
        // as the end node itself is 4 bytes (type: u8 + subtype: u8 + length: u16).
        var new = @ptrCast(*MediaDevicePath.FilePathDevicePath, buf.ptr + path_size - 4);

        new.type = .Media;
        new.subtype = .FilePath;
        new.length = @sizeOf(MediaDevicePath.FilePathDevicePath) + 2 * (@intCast(u16, path.len) + 1);

        // The same as new.getPath(), but not const as we're filling it in.
        var ptr = @ptrCast([*:0]align(1) u16, @ptrCast([*]u8, new) + @sizeOf(MediaDevicePath.FilePathDevicePath));

        for (path) |s, i|
            ptr[i] = s;

        ptr[path.len] = 0;

        var end = @ptrCast(*EndDevicePath.EndEntireDevicePath, @ptrCast(*DevicePathProtocol, new).next().?);
        end.type = .End;
        end.subtype = .EndEntire;
        end.length = @sizeOf(EndDevicePath.EndEntireDevicePath);

        return @ptrCast(*DevicePathProtocol, buf.ptr);
    }

    pub fn getDevicePath(self: *const DevicePathProtocol) ?DevicePath {
        inline for (@typeInfo(DevicePath).Union.fields) |ufield| {
            const enum_value = std.meta.stringToEnum(DevicePathType, ufield.name);

            // Got the associated union type for self.type, now
            // we need to initialize it and its subtype
            if (self.type == enum_value) {
                var subtype = self.initSubtype(ufield.field_type);

                if (subtype) |sb| {
                    // e.g. return .{ .Hardware = .{ .Pci = @ptrCast(...) } }
                    return @unionInit(DevicePath, ufield.name, sb);
                }
            }
        }

        return null;
    }

    pub fn initSubtype(self: *const DevicePathProtocol, comptime TUnion: type) ?TUnion {
        const type_info = @typeInfo(TUnion).Union;
        const TTag = type_info.tag_type.?;

        inline for (type_info.fields) |subtype| {
            // The tag names match the union names, so just grab that off the enum
            const tag_val: u8 = @enumToInt(@field(TTag, subtype.name));

            if (self.subtype == tag_val) {
                // e.g. expr = .{ .Pci = @ptrCast(...) }
                return @unionInit(TUnion, subtype.name, @ptrCast(subtype.field_type, self));
            }
        }

        return null;
    }
};

comptime {
    std.debug.assert(4 == @sizeOf(DevicePathProtocol));
    std.debug.assert(1 == @alignOf(DevicePathProtocol));

    std.debug.assert(0 == @offsetOf(DevicePathProtocol, "type"));
    std.debug.assert(1 == @offsetOf(DevicePathProtocol, "subtype"));
    std.debug.assert(2 == @offsetOf(DevicePathProtocol, "length"));
}

pub const DevicePath = union(DevicePathType) {
    Hardware: HardwareDevicePath,
    Acpi: AcpiDevicePath,
    Messaging: MessagingDevicePath,
    Media: MediaDevicePath,
    BiosBootSpecification: BiosBootSpecificationDevicePath,
    End: EndDevicePath,
};

pub const DevicePathType = enum(u8) {
    Hardware = 0x01,
    Acpi = 0x02,
    Messaging = 0x03,
    Media = 0x04,
    BiosBootSpecification = 0x05,
    End = 0x7f,
    _,
};

pub const HardwareDevicePath = union(Subtype) {
    Pci: *const PciDevicePath,
    PcCard: *const PcCardDevicePath,
    MemoryMapped: *const MemoryMappedDevicePath,
    Vendor: *const VendorDevicePath,
    Controller: *const ControllerDevicePath,
    Bmc: *const BmcDevicePath,

    pub const Subtype = enum(u8) {
        Pci = 1,
        PcCard = 2,
        MemoryMapped = 3,
        Vendor = 4,
        Controller = 5,
        Bmc = 6,
        _,
    };

    pub const PciDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        function: u8,
        device: u8,
    };

    comptime {
        std.debug.assert(6 == @sizeOf(PciDevicePath));
        std.debug.assert(1 == @alignOf(PciDevicePath));

        std.debug.assert(0 == @offsetOf(PciDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(PciDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(PciDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(PciDevicePath, "function"));
        std.debug.assert(5 == @offsetOf(PciDevicePath, "device"));
    }

    pub const PcCardDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        function_number: u8,
    };

    comptime {
        std.debug.assert(5 == @sizeOf(PcCardDevicePath));
        std.debug.assert(1 == @alignOf(PcCardDevicePath));

        std.debug.assert(0 == @offsetOf(PcCardDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(PcCardDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(PcCardDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(PcCardDevicePath, "function_number"));
    }

    pub const MemoryMappedDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        memory_type: u32 align(1),
        start_address: u64 align(1),
        end_address: u64 align(1),
    };

    comptime {
        std.debug.assert(24 == @sizeOf(MemoryMappedDevicePath));
        std.debug.assert(1 == @alignOf(MemoryMappedDevicePath));

        std.debug.assert(0 == @offsetOf(MemoryMappedDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(MemoryMappedDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(MemoryMappedDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(MemoryMappedDevicePath, "memory_type"));
        std.debug.assert(8 == @offsetOf(MemoryMappedDevicePath, "start_address"));
        std.debug.assert(16 == @offsetOf(MemoryMappedDevicePath, "end_address"));
    }

    pub const VendorDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        vendor_guid: Guid align(1),
    };

    comptime {
        std.debug.assert(20 == @sizeOf(VendorDevicePath));
        std.debug.assert(1 == @alignOf(VendorDevicePath));

        std.debug.assert(0 == @offsetOf(VendorDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(VendorDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(VendorDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(VendorDevicePath, "vendor_guid"));
    }

    pub const ControllerDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        controller_number: u32 align(1),
    };

    comptime {
        std.debug.assert(8 == @sizeOf(ControllerDevicePath));
        std.debug.assert(1 == @alignOf(ControllerDevicePath));

        std.debug.assert(0 == @offsetOf(ControllerDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(ControllerDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(ControllerDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(ControllerDevicePath, "controller_number"));
    }

    pub const BmcDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        interface_type: u8,
        base_address: u64 align(1),
    };

    comptime {
        std.debug.assert(13 == @sizeOf(BmcDevicePath));
        std.debug.assert(1 == @alignOf(BmcDevicePath));

        std.debug.assert(0 == @offsetOf(BmcDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(BmcDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(BmcDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(BmcDevicePath, "interface_type"));
        std.debug.assert(5 == @offsetOf(BmcDevicePath, "base_address"));
    }
};

pub const AcpiDevicePath = union(Subtype) {
    Acpi: *const BaseAcpiDevicePath,
    ExpandedAcpi: *const ExpandedAcpiDevicePath,
    Adr: *const AdrDevicePath,

    pub const Subtype = enum(u8) {
        Acpi = 1,
        ExpandedAcpi = 2,
        Adr = 3,
        _,
    };

    pub const BaseAcpiDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        hid: u32 align(1),
        uid: u32 align(1),
    };

    comptime {
        std.debug.assert(12 == @sizeOf(BaseAcpiDevicePath));
        std.debug.assert(1 == @alignOf(BaseAcpiDevicePath));

        std.debug.assert(0 == @offsetOf(BaseAcpiDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(BaseAcpiDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(BaseAcpiDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(BaseAcpiDevicePath, "hid"));
        std.debug.assert(8 == @offsetOf(BaseAcpiDevicePath, "uid"));
    }

    pub const ExpandedAcpiDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        hid: u32 align(1),
        uid: u32 align(1),
        cid: u32 align(1),
        // variable length u16[*:0] strings
        // hid_str, uid_str, cid_str
    };

    comptime {
        std.debug.assert(16 == @sizeOf(ExpandedAcpiDevicePath));
        std.debug.assert(1 == @alignOf(ExpandedAcpiDevicePath));

        std.debug.assert(0 == @offsetOf(ExpandedAcpiDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(ExpandedAcpiDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(ExpandedAcpiDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(ExpandedAcpiDevicePath, "hid"));
        std.debug.assert(8 == @offsetOf(ExpandedAcpiDevicePath, "uid"));
        std.debug.assert(12 == @offsetOf(ExpandedAcpiDevicePath, "cid"));
    }

    pub const AdrDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        adr: u32 align(1),

        // multiple adr entries can optionally follow
        pub fn adrs(self: *const AdrDevicePath) []align(1) const u32 {
            // self.length is a minimum of 8 with one adr which is size 4.
            var entries = (self.length - 4) / @sizeOf(u32);
            return @ptrCast([*]align(1) const u32, &self.adr)[0..entries];
        }
    };

    comptime {
        std.debug.assert(8 == @sizeOf(AdrDevicePath));
        std.debug.assert(1 == @alignOf(AdrDevicePath));

        std.debug.assert(0 == @offsetOf(AdrDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(AdrDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(AdrDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(AdrDevicePath, "adr"));
    }
};

pub const MessagingDevicePath = union(Subtype) {
    Atapi: *const AtapiDevicePath,
    Scsi: *const ScsiDevicePath,
    FibreChannel: *const FibreChannelDevicePath,
    FibreChannelEx: *const FibreChannelExDevicePath,
    @"1394": *const F1394DevicePath,
    Usb: *const UsbDevicePath,
    Sata: *const SataDevicePath,
    UsbWwid: *const UsbWwidDevicePath,
    Lun: *const DeviceLogicalUnitDevicePath,
    UsbClass: *const UsbClassDevicePath,
    I2o: *const I2oDevicePath,
    MacAddress: *const MacAddressDevicePath,
    Ipv4: *const Ipv4DevicePath,
    Ipv6: *const Ipv6DevicePath,
    Vlan: *const VlanDevicePath,
    InfiniBand: *const InfiniBandDevicePath,
    Uart: *const UartDevicePath,
    Vendor: *const VendorDefinedDevicePath,

    pub const Subtype = enum(u8) {
        Atapi = 1,
        Scsi = 2,
        FibreChannel = 3,
        FibreChannelEx = 21,
        @"1394" = 4,
        Usb = 5,
        Sata = 18,
        UsbWwid = 16,
        Lun = 17,
        UsbClass = 15,
        I2o = 6,
        MacAddress = 11,
        Ipv4 = 12,
        Ipv6 = 13,
        Vlan = 20,
        InfiniBand = 9,
        Uart = 14,
        Vendor = 10,
        _,
    };

    pub const AtapiDevicePath = extern struct {
        const Role = enum(u8) {
            Master = 0,
            Slave = 1,
        };

        const Rank = enum(u8) {
            Primary = 0,
            Secondary = 1,
        };

        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        primary_secondary: Rank,
        slave_master: Role,
        logical_unit_number: u16 align(1),
    };

    comptime {
        std.debug.assert(8 == @sizeOf(AtapiDevicePath));
        std.debug.assert(1 == @alignOf(AtapiDevicePath));

        std.debug.assert(0 == @offsetOf(AtapiDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(AtapiDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(AtapiDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(AtapiDevicePath, "primary_secondary"));
        std.debug.assert(5 == @offsetOf(AtapiDevicePath, "slave_master"));
        std.debug.assert(6 == @offsetOf(AtapiDevicePath, "logical_unit_number"));
    }

    pub const ScsiDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        target_id: u16 align(1),
        logical_unit_number: u16 align(1),
    };

    comptime {
        std.debug.assert(8 == @sizeOf(ScsiDevicePath));
        std.debug.assert(1 == @alignOf(ScsiDevicePath));

        std.debug.assert(0 == @offsetOf(ScsiDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(ScsiDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(ScsiDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(ScsiDevicePath, "target_id"));
        std.debug.assert(6 == @offsetOf(ScsiDevicePath, "logical_unit_number"));
    }

    pub const FibreChannelDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        reserved: u32 align(1),
        world_wide_name: u64 align(1),
        logical_unit_number: u64 align(1),
    };

    comptime {
        std.debug.assert(24 == @sizeOf(FibreChannelDevicePath));
        std.debug.assert(1 == @alignOf(FibreChannelDevicePath));

        std.debug.assert(0 == @offsetOf(FibreChannelDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(FibreChannelDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(FibreChannelDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(FibreChannelDevicePath, "reserved"));
        std.debug.assert(8 == @offsetOf(FibreChannelDevicePath, "world_wide_name"));
        std.debug.assert(16 == @offsetOf(FibreChannelDevicePath, "logical_unit_number"));
    }

    pub const FibreChannelExDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        reserved: u32 align(1),
        world_wide_name: u64 align(1),
        logical_unit_number: u64 align(1),
    };

    comptime {
        std.debug.assert(24 == @sizeOf(FibreChannelExDevicePath));
        std.debug.assert(1 == @alignOf(FibreChannelExDevicePath));

        std.debug.assert(0 == @offsetOf(FibreChannelExDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(FibreChannelExDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(FibreChannelExDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(FibreChannelExDevicePath, "reserved"));
        std.debug.assert(8 == @offsetOf(FibreChannelExDevicePath, "world_wide_name"));
        std.debug.assert(16 == @offsetOf(FibreChannelExDevicePath, "logical_unit_number"));
    }

    pub const F1394DevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        reserved: u32 align(1),
        guid: u64 align(1),
    };

    comptime {
        std.debug.assert(16 == @sizeOf(F1394DevicePath));
        std.debug.assert(1 == @alignOf(F1394DevicePath));

        std.debug.assert(0 == @offsetOf(F1394DevicePath, "type"));
        std.debug.assert(1 == @offsetOf(F1394DevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(F1394DevicePath, "length"));
        std.debug.assert(4 == @offsetOf(F1394DevicePath, "reserved"));
        std.debug.assert(8 == @offsetOf(F1394DevicePath, "guid"));
    }

    pub const UsbDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        parent_port_number: u8,
        interface_number: u8,
    };

    comptime {
        std.debug.assert(6 == @sizeOf(UsbDevicePath));
        std.debug.assert(1 == @alignOf(UsbDevicePath));

        std.debug.assert(0 == @offsetOf(UsbDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(UsbDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(UsbDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(UsbDevicePath, "parent_port_number"));
        std.debug.assert(5 == @offsetOf(UsbDevicePath, "interface_number"));
    }

    pub const SataDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        hba_port_number: u16 align(1),
        port_multiplier_port_number: u16 align(1),
        logical_unit_number: u16 align(1),
    };

    comptime {
        std.debug.assert(10 == @sizeOf(SataDevicePath));
        std.debug.assert(1 == @alignOf(SataDevicePath));

        std.debug.assert(0 == @offsetOf(SataDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(SataDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(SataDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(SataDevicePath, "hba_port_number"));
        std.debug.assert(6 == @offsetOf(SataDevicePath, "port_multiplier_port_number"));
        std.debug.assert(8 == @offsetOf(SataDevicePath, "logical_unit_number"));
    }

    pub const UsbWwidDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        interface_number: u16 align(1),
        device_vendor_id: u16 align(1),
        device_product_id: u16 align(1),

        pub fn serial_number(self: *const UsbWwidDevicePath) []align(1) const u16 {
            var serial_len = (self.length - @sizeOf(UsbWwidDevicePath)) / @sizeOf(u16);
            return @ptrCast([*]align(1) const u16, @ptrCast([*]const u8, self) + @sizeOf(UsbWwidDevicePath))[0..serial_len];
        }
    };

    comptime {
        std.debug.assert(10 == @sizeOf(UsbWwidDevicePath));
        std.debug.assert(1 == @alignOf(UsbWwidDevicePath));

        std.debug.assert(0 == @offsetOf(UsbWwidDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(UsbWwidDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(UsbWwidDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(UsbWwidDevicePath, "interface_number"));
        std.debug.assert(6 == @offsetOf(UsbWwidDevicePath, "device_vendor_id"));
        std.debug.assert(8 == @offsetOf(UsbWwidDevicePath, "device_product_id"));
    }

    pub const DeviceLogicalUnitDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        lun: u8,
    };

    comptime {
        std.debug.assert(5 == @sizeOf(DeviceLogicalUnitDevicePath));
        std.debug.assert(1 == @alignOf(DeviceLogicalUnitDevicePath));

        std.debug.assert(0 == @offsetOf(DeviceLogicalUnitDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(DeviceLogicalUnitDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(DeviceLogicalUnitDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(DeviceLogicalUnitDevicePath, "lun"));
    }

    pub const UsbClassDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        vendor_id: u16 align(1),
        product_id: u16 align(1),
        device_class: u8,
        device_subclass: u8,
        device_protocol: u8,
    };

    comptime {
        std.debug.assert(11 == @sizeOf(UsbClassDevicePath));
        std.debug.assert(1 == @alignOf(UsbClassDevicePath));

        std.debug.assert(0 == @offsetOf(UsbClassDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(UsbClassDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(UsbClassDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(UsbClassDevicePath, "vendor_id"));
        std.debug.assert(6 == @offsetOf(UsbClassDevicePath, "product_id"));
        std.debug.assert(8 == @offsetOf(UsbClassDevicePath, "device_class"));
        std.debug.assert(9 == @offsetOf(UsbClassDevicePath, "device_subclass"));
        std.debug.assert(10 == @offsetOf(UsbClassDevicePath, "device_protocol"));
    }

    pub const I2oDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        tid: u32 align(1),
    };

    comptime {
        std.debug.assert(8 == @sizeOf(I2oDevicePath));
        std.debug.assert(1 == @alignOf(I2oDevicePath));

        std.debug.assert(0 == @offsetOf(I2oDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(I2oDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(I2oDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(I2oDevicePath, "tid"));
    }

    pub const MacAddressDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        mac_address: uefi.MacAddress,
        if_type: u8,
    };

    comptime {
        std.debug.assert(37 == @sizeOf(MacAddressDevicePath));
        std.debug.assert(1 == @alignOf(MacAddressDevicePath));

        std.debug.assert(0 == @offsetOf(MacAddressDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(MacAddressDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(MacAddressDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(MacAddressDevicePath, "mac_address"));
        std.debug.assert(36 == @offsetOf(MacAddressDevicePath, "if_type"));
    }

    pub const Ipv4DevicePath = extern struct {
        pub const IpType = enum(u8) {
            Dhcp = 0,
            Static = 1,
        };

        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        local_ip_address: uefi.Ipv4Address align(1),
        remote_ip_address: uefi.Ipv4Address align(1),
        local_port: u16 align(1),
        remote_port: u16 align(1),
        network_protocol: u16 align(1),
        static_ip_address: IpType,
        gateway_ip_address: u32 align(1),
        subnet_mask: u32 align(1),
    };

    comptime {
        std.debug.assert(27 == @sizeOf(Ipv4DevicePath));
        std.debug.assert(1 == @alignOf(Ipv4DevicePath));

        std.debug.assert(0 == @offsetOf(Ipv4DevicePath, "type"));
        std.debug.assert(1 == @offsetOf(Ipv4DevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(Ipv4DevicePath, "length"));
        std.debug.assert(4 == @offsetOf(Ipv4DevicePath, "local_ip_address"));
        std.debug.assert(8 == @offsetOf(Ipv4DevicePath, "remote_ip_address"));
        std.debug.assert(12 == @offsetOf(Ipv4DevicePath, "local_port"));
        std.debug.assert(14 == @offsetOf(Ipv4DevicePath, "remote_port"));
        std.debug.assert(16 == @offsetOf(Ipv4DevicePath, "network_protocol"));
        std.debug.assert(18 == @offsetOf(Ipv4DevicePath, "static_ip_address"));
        std.debug.assert(19 == @offsetOf(Ipv4DevicePath, "gateway_ip_address"));
        std.debug.assert(23 == @offsetOf(Ipv4DevicePath, "subnet_mask"));
    }

    pub const Ipv6DevicePath = extern struct {
        pub const Origin = enum(u8) {
            Manual = 0,
            AssignedStateless = 1,
            AssignedStateful = 2,
        };

        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        local_ip_address: uefi.Ipv6Address,
        remote_ip_address: uefi.Ipv6Address,
        local_port: u16 align(1),
        remote_port: u16 align(1),
        protocol: u16 align(1),
        ip_address_origin: Origin,
        prefix_length: u8,
        gateway_ip_address: uefi.Ipv6Address,
    };

    comptime {
        std.debug.assert(60 == @sizeOf(Ipv6DevicePath));
        std.debug.assert(1 == @alignOf(Ipv6DevicePath));

        std.debug.assert(0 == @offsetOf(Ipv6DevicePath, "type"));
        std.debug.assert(1 == @offsetOf(Ipv6DevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(Ipv6DevicePath, "length"));
        std.debug.assert(4 == @offsetOf(Ipv6DevicePath, "local_ip_address"));
        std.debug.assert(20 == @offsetOf(Ipv6DevicePath, "remote_ip_address"));
        std.debug.assert(36 == @offsetOf(Ipv6DevicePath, "local_port"));
        std.debug.assert(38 == @offsetOf(Ipv6DevicePath, "remote_port"));
        std.debug.assert(40 == @offsetOf(Ipv6DevicePath, "protocol"));
        std.debug.assert(42 == @offsetOf(Ipv6DevicePath, "ip_address_origin"));
        std.debug.assert(43 == @offsetOf(Ipv6DevicePath, "prefix_length"));
        std.debug.assert(44 == @offsetOf(Ipv6DevicePath, "gateway_ip_address"));
    }

    pub const VlanDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        vlan_id: u16 align(1),
    };

    comptime {
        std.debug.assert(6 == @sizeOf(VlanDevicePath));
        std.debug.assert(1 == @alignOf(VlanDevicePath));

        std.debug.assert(0 == @offsetOf(VlanDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(VlanDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(VlanDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(VlanDevicePath, "vlan_id"));
    }

    pub const InfiniBandDevicePath = extern struct {
        pub const ResourceFlags = packed struct(u32) {
            pub const ControllerType = enum(u1) {
                Ioc = 0,
                Service = 1,
            };

            ioc_or_service: ControllerType,
            extend_boot_environment: bool,
            console_protocol: bool,
            storage_protocol: bool,
            network_protocol: bool,

            // u1 + 4 * bool = 5 bits, we need a total of 32 bits
            reserved: u27,
        };

        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        resource_flags: ResourceFlags align(1),
        port_gid: [16]u8,
        service_id: u64 align(1),
        target_port_id: u64 align(1),
        device_id: u64 align(1),
    };

    comptime {
        std.debug.assert(48 == @sizeOf(InfiniBandDevicePath));
        std.debug.assert(1 == @alignOf(InfiniBandDevicePath));

        std.debug.assert(0 == @offsetOf(InfiniBandDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(InfiniBandDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(InfiniBandDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(InfiniBandDevicePath, "resource_flags"));
        std.debug.assert(8 == @offsetOf(InfiniBandDevicePath, "port_gid"));
        std.debug.assert(24 == @offsetOf(InfiniBandDevicePath, "service_id"));
        std.debug.assert(32 == @offsetOf(InfiniBandDevicePath, "target_port_id"));
        std.debug.assert(40 == @offsetOf(InfiniBandDevicePath, "device_id"));
    }

    pub const UartDevicePath = extern struct {
        pub const Parity = enum(u8) {
            Default = 0,
            None = 1,
            Even = 2,
            Odd = 3,
            Mark = 4,
            Space = 5,
            _,
        };

        pub const StopBits = enum(u8) {
            Default = 0,
            One = 1,
            OneAndAHalf = 2,
            Two = 3,
            _,
        };

        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        reserved: u32 align(1),
        baud_rate: u64 align(1),
        data_bits: u8,
        parity: Parity,
        stop_bits: StopBits,
    };

    comptime {
        std.debug.assert(19 == @sizeOf(UartDevicePath));
        std.debug.assert(1 == @alignOf(UartDevicePath));

        std.debug.assert(0 == @offsetOf(UartDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(UartDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(UartDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(UartDevicePath, "reserved"));
        std.debug.assert(8 == @offsetOf(UartDevicePath, "baud_rate"));
        std.debug.assert(16 == @offsetOf(UartDevicePath, "data_bits"));
        std.debug.assert(17 == @offsetOf(UartDevicePath, "parity"));
        std.debug.assert(18 == @offsetOf(UartDevicePath, "stop_bits"));
    }

    pub const VendorDefinedDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        vendor_guid: Guid align(1),
    };

    comptime {
        std.debug.assert(20 == @sizeOf(VendorDefinedDevicePath));
        std.debug.assert(1 == @alignOf(VendorDefinedDevicePath));

        std.debug.assert(0 == @offsetOf(VendorDefinedDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(VendorDefinedDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(VendorDefinedDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(VendorDefinedDevicePath, "vendor_guid"));
    }
};

pub const MediaDevicePath = union(Subtype) {
    HardDrive: *const HardDriveDevicePath,
    Cdrom: *const CdromDevicePath,
    Vendor: *const VendorDevicePath,
    FilePath: *const FilePathDevicePath,
    MediaProtocol: *const MediaProtocolDevicePath,
    PiwgFirmwareFile: *const PiwgFirmwareFileDevicePath,
    PiwgFirmwareVolume: *const PiwgFirmwareVolumeDevicePath,
    RelativeOffsetRange: *const RelativeOffsetRangeDevicePath,
    RamDisk: *const RamDiskDevicePath,

    pub const Subtype = enum(u8) {
        HardDrive = 1,
        Cdrom = 2,
        Vendor = 3,
        FilePath = 4,
        MediaProtocol = 5,
        PiwgFirmwareFile = 6,
        PiwgFirmwareVolume = 7,
        RelativeOffsetRange = 8,
        RamDisk = 9,
        _,
    };

    pub const HardDriveDevicePath = extern struct {
        pub const Format = enum(u8) {
            LegacyMbr = 0x01,
            GuidPartitionTable = 0x02,
        };

        pub const SignatureType = enum(u8) {
            NoSignature = 0x00,
            /// "32-bit signature from address 0x1b8 of the type 0x01 MBR"
            MbrSignature = 0x01,
            GuidSignature = 0x02,
        };

        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        partition_number: u32 align(1),
        partition_start: u64 align(1),
        partition_size: u64 align(1),
        partition_signature: [16]u8,
        partition_format: Format,
        signature_type: SignatureType,
    };

    comptime {
        std.debug.assert(42 == @sizeOf(HardDriveDevicePath));
        std.debug.assert(1 == @alignOf(HardDriveDevicePath));

        std.debug.assert(0 == @offsetOf(HardDriveDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(HardDriveDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(HardDriveDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(HardDriveDevicePath, "partition_number"));
        std.debug.assert(8 == @offsetOf(HardDriveDevicePath, "partition_start"));
        std.debug.assert(16 == @offsetOf(HardDriveDevicePath, "partition_size"));
        std.debug.assert(24 == @offsetOf(HardDriveDevicePath, "partition_signature"));
        std.debug.assert(40 == @offsetOf(HardDriveDevicePath, "partition_format"));
        std.debug.assert(41 == @offsetOf(HardDriveDevicePath, "signature_type"));
    }

    pub const CdromDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        boot_entry: u32 align(1),
        partition_start: u64 align(1),
        partition_size: u64 align(1),
    };

    comptime {
        std.debug.assert(24 == @sizeOf(CdromDevicePath));
        std.debug.assert(1 == @alignOf(CdromDevicePath));

        std.debug.assert(0 == @offsetOf(CdromDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(CdromDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(CdromDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(CdromDevicePath, "boot_entry"));
        std.debug.assert(8 == @offsetOf(CdromDevicePath, "partition_start"));
        std.debug.assert(16 == @offsetOf(CdromDevicePath, "partition_size"));
    }

    pub const VendorDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        guid: Guid align(1),
    };

    comptime {
        std.debug.assert(20 == @sizeOf(VendorDevicePath));
        std.debug.assert(1 == @alignOf(VendorDevicePath));

        std.debug.assert(0 == @offsetOf(VendorDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(VendorDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(VendorDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(VendorDevicePath, "guid"));
    }

    pub const FilePathDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),

        pub fn getPath(self: *const FilePathDevicePath) [*:0]align(1) const u16 {
            return @ptrCast([*:0]align(1) const u16, @ptrCast([*]const u8, self) + @sizeOf(FilePathDevicePath));
        }
    };

    comptime {
        std.debug.assert(4 == @sizeOf(FilePathDevicePath));
        std.debug.assert(1 == @alignOf(FilePathDevicePath));

        std.debug.assert(0 == @offsetOf(FilePathDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(FilePathDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(FilePathDevicePath, "length"));
    }

    pub const MediaProtocolDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        guid: Guid align(1),
    };

    comptime {
        std.debug.assert(20 == @sizeOf(MediaProtocolDevicePath));
        std.debug.assert(1 == @alignOf(MediaProtocolDevicePath));

        std.debug.assert(0 == @offsetOf(MediaProtocolDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(MediaProtocolDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(MediaProtocolDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(MediaProtocolDevicePath, "guid"));
    }

    pub const PiwgFirmwareFileDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        fv_filename: Guid align(1),
    };

    comptime {
        std.debug.assert(20 == @sizeOf(PiwgFirmwareFileDevicePath));
        std.debug.assert(1 == @alignOf(PiwgFirmwareFileDevicePath));

        std.debug.assert(0 == @offsetOf(PiwgFirmwareFileDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(PiwgFirmwareFileDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(PiwgFirmwareFileDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(PiwgFirmwareFileDevicePath, "fv_filename"));
    }

    pub const PiwgFirmwareVolumeDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        fv_name: Guid align(1),
    };

    comptime {
        std.debug.assert(20 == @sizeOf(PiwgFirmwareVolumeDevicePath));
        std.debug.assert(1 == @alignOf(PiwgFirmwareVolumeDevicePath));

        std.debug.assert(0 == @offsetOf(PiwgFirmwareVolumeDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(PiwgFirmwareVolumeDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(PiwgFirmwareVolumeDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(PiwgFirmwareVolumeDevicePath, "fv_name"));
    }

    pub const RelativeOffsetRangeDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        reserved: u32 align(1),
        start: u64 align(1),
        end: u64 align(1),
    };

    comptime {
        std.debug.assert(24 == @sizeOf(RelativeOffsetRangeDevicePath));
        std.debug.assert(1 == @alignOf(RelativeOffsetRangeDevicePath));

        std.debug.assert(0 == @offsetOf(RelativeOffsetRangeDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(RelativeOffsetRangeDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(RelativeOffsetRangeDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(RelativeOffsetRangeDevicePath, "reserved"));
        std.debug.assert(8 == @offsetOf(RelativeOffsetRangeDevicePath, "start"));
        std.debug.assert(16 == @offsetOf(RelativeOffsetRangeDevicePath, "end"));
    }

    pub const RamDiskDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        start: u64 align(1),
        end: u64 align(1),
        disk_type: Guid align(1),
        instance: u16 align(1),
    };

    comptime {
        std.debug.assert(38 == @sizeOf(RamDiskDevicePath));
        std.debug.assert(1 == @alignOf(RamDiskDevicePath));

        std.debug.assert(0 == @offsetOf(RamDiskDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(RamDiskDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(RamDiskDevicePath, "length"));
        std.debug.assert(4 == @offsetOf(RamDiskDevicePath, "start"));
        std.debug.assert(12 == @offsetOf(RamDiskDevicePath, "end"));
        std.debug.assert(20 == @offsetOf(RamDiskDevicePath, "disk_type"));
        std.debug.assert(36 == @offsetOf(RamDiskDevicePath, "instance"));
    }
};

pub const BiosBootSpecificationDevicePath = union(Subtype) {
    BBS101: *const BBS101DevicePath,

    pub const Subtype = enum(u8) {
        BBS101 = 1,
        _,
    };

    pub const BBS101DevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
        device_type: u16 align(1),
        status_flag: u16 align(1),

        pub fn getDescription(self: *const BBS101DevicePath) [*:0]const u8 {
            return @ptrCast([*:0]const u8, self) + @sizeOf(BBS101DevicePath);
        }
    };

    comptime {
        std.debug.assert(8 == @sizeOf(BBS101DevicePath));
        std.debug.assert(1 == @alignOf(BBS101DevicePath));

        std.debug.assert(0 == @offsetOf(BBS101DevicePath, "type"));
        std.debug.assert(1 == @offsetOf(BBS101DevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(BBS101DevicePath, "length"));
        std.debug.assert(4 == @offsetOf(BBS101DevicePath, "device_type"));
        std.debug.assert(6 == @offsetOf(BBS101DevicePath, "status_flag"));
    }
};

pub const EndDevicePath = union(Subtype) {
    EndEntire: *const EndEntireDevicePath,
    EndThisInstance: *const EndThisInstanceDevicePath,

    pub const Subtype = enum(u8) {
        EndEntire = 0xff,
        EndThisInstance = 0x01,
        _,
    };

    pub const EndEntireDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
    };

    comptime {
        std.debug.assert(4 == @sizeOf(EndEntireDevicePath));
        std.debug.assert(1 == @alignOf(EndEntireDevicePath));

        std.debug.assert(0 == @offsetOf(EndEntireDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(EndEntireDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(EndEntireDevicePath, "length"));
    }

    pub const EndThisInstanceDevicePath = extern struct {
        type: DevicePathType,
        subtype: Subtype,
        length: u16 align(1),
    };

    comptime {
        std.debug.assert(4 == @sizeOf(EndEntireDevicePath));
        std.debug.assert(1 == @alignOf(EndEntireDevicePath));

        std.debug.assert(0 == @offsetOf(EndEntireDevicePath, "type"));
        std.debug.assert(1 == @offsetOf(EndEntireDevicePath, "subtype"));
        std.debug.assert(2 == @offsetOf(EndEntireDevicePath, "length"));
    }
};
