diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 585717a..747ba70 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -47,19 +47,8 @@ jobs: strategy: matrix: go-version: ['1.21.x', '1.22.x'] - vmarch: [ 'amd64', 'arm64', 'riscv64' ] + vmarch: [ 'amd64' ] goarm: [''] - include: - # QEMU arm -M virt seems to support only v5. GOARM default as of 1.21 - # is v7. - - go-version: '1.21.x' - vmarch: 'arm' - goarm: '5' - # QEMU arm -M virt seems to support only v5. GOARM default as of 1.21 - # is v7. - - go-version: '1.22.x' - vmarch: 'arm' - goarm: '5' env: VMTEST_ARCH: ${{ matrix.vmarch }} diff --git a/dmidecode/struct_parser.go b/dmidecode/struct_parser.go index fb87140..b6113c9 100644 --- a/dmidecode/struct_parser.go +++ b/dmidecode/struct_parser.go @@ -5,8 +5,10 @@ package dmidecode import ( + "errors" "fmt" "io" + "math" "reflect" "strconv" "strings" @@ -14,6 +16,11 @@ import ( "github.com/u-root/smbios" ) +// Generic errors. +var ( + ErrInvalidArg = errors.New("invalid argument") +) + // We need this for testing. type parseStructure func(t *smbios.Table, off int, complete bool, sp interface{}) (int, error) @@ -21,9 +28,14 @@ type fieldParser interface { ParseField(t *smbios.Table, off int) (int, error) } +type fieldWriter interface { + WriteField(t *smbios.Table) (int, error) +} + var ( fieldTagKey = "smbios" // Tag key for annotations. fieldParserInterfaceType = reflect.TypeOf((*fieldParser)(nil)).Elem() + fieldWriterInterfaceType = reflect.TypeOf((*fieldWriter)(nil)).Elem() ) func parseStruct(t *smbios.Table, off int, complete bool, sp interface{}) (int, error) { @@ -49,9 +61,6 @@ func parseStruct(t *smbios.Table, off int, complete bool, sp interface{}) (int, switch tp[0] { case "-": ignore = true - case "skip": - numBytes, _ := strconv.Atoi(tp[1]) - off += numBytes } } if ignore { @@ -83,6 +92,11 @@ func parseStruct(t *smbios.Table, off int, complete bool, sp interface{}) (int, v, _ := t.GetStringAt(off) fv.SetString(v) off++ + case reflect.Array: + v, err := t.GetBytesAt(off, fv.Len()) + reflect.Copy(fv, reflect.ValueOf(v)) + verr = err + off += fv.Len() default: if reflect.PtrTo(ft).Implements(fieldParserInterfaceType) { off, err = fv.Addr().Interface().(fieldParser).ParseField(t, off) @@ -125,9 +139,6 @@ func parseStruct(t *smbios.Table, off int, complete bool, sp interface{}) (int, switch tp[0] { case "-": ignore = true - case "skip": - numBytes, _ := strconv.Atoi(tp[1]) - off += numBytes case "default": defValue, _ = strconv.ParseUint(tp[1], 0, 64) } @@ -139,6 +150,10 @@ func parseStruct(t *smbios.Table, off int, complete bool, sp interface{}) (int, case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: fv.SetUint(defValue) off += int(ft.Size()) + + case reflect.Array: + return off, fmt.Errorf("%w: array does not support default values", ErrInvalidArg) + case reflect.Struct: off, err := parseStruct(t, off, false /* complete */, fv) if err != nil { @@ -149,3 +164,99 @@ func parseStruct(t *smbios.Table, off int, complete bool, sp interface{}) (int, return off, nil } + +// Table is an SMBIOS table. +type Table interface { + Typ() smbios.TableType +} + +const headerLen = 4 + +// ToTable converts a struct to an smbios.Table. +func ToTable(val Table, handle uint16) (*smbios.Table, error) { + t := smbios.Table{ + Header: smbios.Header{ + Type: val.Typ(), + Handle: handle, + }, + } + m, err := toTable(&t, val) + if err != nil { + return nil, err + } + if m+headerLen > math.MaxUint8 { + return nil, fmt.Errorf("%w: table is too long, got %d bytes, max is 256", ErrInvalidArg, m+headerLen) + } + t.Length = uint8(m + headerLen) + return &t, nil +} + +func toTable(t *smbios.Table, sp any) (int, error) { + sv, ok := sp.(reflect.Value) + if !ok { + sv = reflect.Indirect(reflect.ValueOf(sp)) // must be a pointer to struct then, dereference it + } + svtn := sv.Type().Name() + + n := 0 + for i := 0; i < sv.NumField(); i++ { + f := sv.Type().Field(i) + fv := sv.Field(i) + ft := fv.Type() + tags := f.Tag.Get(fieldTagKey) + // Check tags first + ignore := false + for _, tag := range strings.Split(tags, ",") { + tp := strings.Split(tag, "=") + switch tp[0] { //nolint + case "-": + ignore = true + } + } + if ignore { + continue + } + // fieldWriter takes precedence. + if reflect.PtrTo(ft).Implements(fieldWriterInterfaceType) { + m, err := fv.Addr().Interface().(fieldWriter).WriteField(t) + n += m + if err != nil { + return n, err + } + continue + } + + var verr error + switch fv.Kind() { + case reflect.Uint8: + t.WriteByte(uint8(fv.Uint())) + n++ + case reflect.Uint16: + t.WriteWord(uint16(fv.Uint())) + n += 2 + case reflect.Uint32: + t.WriteDWord(uint32(fv.Uint())) + n += 4 + case reflect.Uint64: + t.WriteQWord(fv.Uint()) + n += 8 + case reflect.String: + t.WriteString(fv.String()) + n++ + case reflect.Array: + t.WriteBytes(fv.Slice(0, fv.Len()).Bytes()) + n += fv.Len() + case reflect.Struct: + var m int + // If it's a struct, just invoke toTable recursively. + m, verr = toTable(t, fv) + n += m + default: + return n, fmt.Errorf("%s.%s: unsupported type %s", svtn, f.Name, fv.Kind()) + } + if verr != nil { + return n, fmt.Errorf("failed to parse %s.%s: %w", svtn, f.Name, verr) + } + } + return n, nil +} diff --git a/dmidecode/struct_parser_test.go b/dmidecode/struct_parser_test.go index 2692e61..0d331fd 100644 --- a/dmidecode/struct_parser_test.go +++ b/dmidecode/struct_parser_test.go @@ -52,11 +52,13 @@ func TestParseStruct(t *testing.T) { Off9 string _ uint8 `smbios:"-"` Off10 uint16 - Off14 uint8 `smbios:"skip=2"` + Off12 uint8 `smbios:"default=0xf"` _ uint8 `smbios:"-"` - Off15 uint8 `smbios:"skip=2,default=0x1"` - Off17 uint8 `smbios:"default=0xf"` - Off18 foobar + Off13 foobar + } + type withArray struct { + Off0 uint8 + Off1 [4]byte } for _, tt := range []struct { @@ -73,8 +75,7 @@ func TestParseStruct(t *testing.T) { 0xff, // Off8 0x1, // Off9 0x2, 0x1, // Off10 - 0xff, 0xff, // skipped - 0x5, // Off14 + 0x5, // Off12 }, Strings: []string{ "foobar", @@ -86,14 +87,22 @@ func TestParseStruct(t *testing.T) { Off8: 0xff, Off9: "foobar", Off10: 0x102, - Off14: 0x05, - Off15: 0x1, - Off17: 0x0f, - Off18: foobar{ + Off12: 0x05, + Off13: foobar{ Foo: 0xe, }, }, }, + { + table: &smbios.Table{ + Data: []byte{ + 0x1, // Off0 + }, + }, + value: &withArray{}, + want: &withArray{Off0: 0x1}, + err: ErrInvalidArg, + }, } { t.Run("", func(t *testing.T) { if _, err := parseStruct(tt.table, 0, false, tt.value); !errors.Is(err, tt.err) { @@ -122,7 +131,7 @@ func TestParseStructWithTPMDevice(t *testing.T) { Length: 32, }, Data: []byte{ - 0x01, 0x00, 0x00, 0x00, // VendorID + 'G', 'O', 'O', 'G', // VendorID 0x02, // Major 0x03, // Minor 0x01, 0x00, // FirmwareVersion1 @@ -136,7 +145,7 @@ func TestParseStructWithTPMDevice(t *testing.T) { }, complete: false, want: &TPMDevice{ - VendorID: [4]byte{0x00, 0x00, 0x00, 0x00}, + VendorID: [4]byte{'G', 'O', 'O', 'G'}, MajorSpecVersion: 2, MinorSpecVersion: 3, FirmwareVersion1: 0x00020001, @@ -154,7 +163,7 @@ func TestParseStructWithTPMDevice(t *testing.T) { Length: 16, }, Data: []byte{ - 0x00, 0x00, 0x00, 0x00, // VendorID + 'G', 'O', 'O', 'G', // VendorID 0x02, // Major 0x03, // Minor 0x01, 0x00, // FirmwareVersion1 @@ -167,7 +176,7 @@ func TestParseStructWithTPMDevice(t *testing.T) { }, complete: true, want: &TPMDevice{ - VendorID: [4]byte{0x00, 0x00, 0x00, 0x00}, + VendorID: [4]byte{'G', 'O', 'O', 'G'}, MajorSpecVersion: 2, MinorSpecVersion: 3, FirmwareVersion1: 0x00020001, @@ -190,5 +199,190 @@ func TestParseStructWithTPMDevice(t *testing.T) { } }) } +} + +type toTableFoobar struct { + Foo uint8 `smbios:"default=0xe"` +} +type someToTableStruct struct { + Off0 uint64 + Off8 uint8 + Off9 string + _ uint8 `smbios:"-"` + Off10 uint16 + _ uint8 `smbios:"-"` + Off12 uint8 `smbios:"default=0xf"` + Off13 toTableFoobar +} + +func (someToTableStruct) Typ() smbios.TableType { + return smbios.TableTypeSystemInfo +} + +type tableTooLong struct { + Off0 [257]byte +} +func (tableTooLong) Typ() smbios.TableType { + return smbios.TableTypeSystemInfo +} + +func TestToTable(t *testing.T) { + for _, tt := range []struct { + value Table + err error + want *smbios.Table + }{ + { + value: &someToTableStruct{ + Off0: 0x01, + Off8: 0xff, + Off9: "foobar", + Off10: 0x102, + Off12: 0x05, + Off13: toTableFoobar{ + Foo: 0xe, + }, + }, + want: &smbios.Table{ + Header: smbios.Header{ + Type: smbios.TableTypeSystemInfo, + Length: 18, + Handle: 0x1, + }, + Data: []byte{ + 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + 0xff, // Off8 + 0x1, // Off9 + 0x2, 0x1, // Off10 + 0x5, // Off11 + 0xe, // foobar + }, + Strings: []string{ + "foobar", + }, + }, + }, + { + value: &tableTooLong{}, + err: ErrInvalidArg, + }, + { + value: &BaseboardInfo{ + ObjectHandles: ObjectHandles{ + 0x1211, + 0x1413, + }, + }, + want: &smbios.Table{ + Header: smbios.Header{ + Type: smbios.TableTypeBaseboardInfo, + Length: 15 + 4, + Handle: 0x1, + }, + Data: []byte{ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, // board features + 0x00, // location in chassis + 0x00, 0x00, // location + 0x00, // board type + 0x02, // number of handles + // handles + 0x11, 0x12, + 0x13, 0x14, + }, + }, + }, + { + value: &ChassisInfo{ + Type: 0x01, + BootupState: 0x03, + PowerSupplyState: 0x03, + ThermalState: 0x03, + SecurityStatus: 0x03, + OEMInfo: 0x1234, + ContainedElements: []ChassisContainedElement{ + {Type: 0x11, Min: 0x12, Max: 0x13}, + {Type: 0x14, Min: 0x15, Max: 0x16}, + {Type: 0x17, Min: 0x18, Max: 0x19}, + }, + SKUNumber: "SKU!", + }, + want: &smbios.Table{ + Header: smbios.Header{ + Type: smbios.TableTypeChassisInfo, + Length: 27 + 4, + Handle: 0x1, + }, + Data: []byte{ + 0x00, + 0x01, // type + 0x00, + 0x00, + 0x00, + 0x03, 0x03, 0x03, // states + 0x03, // security + 0x34, 0x12, 0x00, 0x00, // oem info + 0x00, // height + 0x00, // num power + 0x03, // num elements + 0x03, // element size + 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, + 0x01, // SKU + }, + Strings: []string{"SKU!"}, + }, + }, + { + value: &ChassisInfo{ + Type: 0x01, + BootupState: 0x03, + PowerSupplyState: 0x03, + ThermalState: 0x03, + SecurityStatus: 0x03, + OEMInfo: 0x1234, + SKUNumber: "SKU!", + }, + want: &smbios.Table{ + Header: smbios.Header{ + Type: smbios.TableTypeChassisInfo, + Length: 18 + 4, + Handle: 0x1, + }, + Data: []byte{ + 0x00, + 0x01, // type + 0x00, + 0x00, + 0x00, + 0x03, 0x03, 0x03, // states + 0x03, // security + 0x34, 0x12, 0x00, 0x00, // oem info + 0x00, // height + 0x00, // num power + 0x00, // num elements + 0x00, // element size + 0x01, // SKU + }, + Strings: []string{"SKU!"}, + }, + }, + } { + t.Run("", func(t *testing.T) { + got, err := ToTable(tt.value, 0x1) + if !errors.Is(err, tt.err) { + t.Errorf("toTable = %v, want %v", err, tt.err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("toTable = %v, want %v", got, tt.want) + } + }) + } } diff --git a/dmidecode/type0_bios_information.go b/dmidecode/type0_bios_information.go index 781532e..e5c3f40 100644 --- a/dmidecode/type0_bios_information.go +++ b/dmidecode/type0_bios_information.go @@ -47,6 +47,11 @@ func ParseBIOSInfo(t *smbios.Table) (*BIOSInfo, error) { return bi, nil } +// Typ implements Table.Typ. +func (bi BIOSInfo) Typ() smbios.TableType { + return smbios.TableTypeBIOSInfo +} + // ROMSizeBytes returns ROM size in bytes. func (bi *BIOSInfo) ROMSizeBytes() uint64 { if bi.ROMSize != 0xff || bi.ExtendedROMSize == 0 { diff --git a/dmidecode/type17_memory_device.go b/dmidecode/type17_memory_device.go index c43b24b..97a7e37 100644 --- a/dmidecode/type17_memory_device.go +++ b/dmidecode/type17_memory_device.go @@ -89,6 +89,11 @@ func ParseMemoryDevice(t *smbios.Table) (*MemoryDevice, error) { return md, nil } +// Typ implements Table.Typ. +func (md MemoryDevice) Typ() smbios.TableType { + return smbios.TableTypeMemoryDevice +} + // GetSizeBytes returns size of the memory device, in bytes. func (md *MemoryDevice) GetSizeBytes() uint64 { switch md.Size { diff --git a/dmidecode/type1_system_information.go b/dmidecode/type1_system_information.go index 4a395bf..b6276a8 100644 --- a/dmidecode/type1_system_information.go +++ b/dmidecode/type1_system_information.go @@ -41,14 +41,9 @@ func ParseSystemInfo(t *smbios.Table) (*SystemInfo, error) { return si, nil } -// ParseField parses UUD field within a table. -func (u *UUID) ParseField(t *smbios.Table, off int) (int, error) { - ub, err := t.GetBytesAt(off, 16) - if err != nil { - return off, err - } - copy(u[:], ub) - return off + 16, nil +// Typ implements Table.Typ. +func (si SystemInfo) Typ() smbios.TableType { + return smbios.TableTypeSystemInfo } func (si *SystemInfo) String() string { diff --git a/dmidecode/type1_system_information_test.go b/dmidecode/type1_system_information_test.go index aa2eaea..8469246 100644 --- a/dmidecode/type1_system_information_test.go +++ b/dmidecode/type1_system_information_test.go @@ -109,36 +109,6 @@ System Information } } -func TestUUIDParseField(t *testing.T) { - tests := []struct { - name string - val smbios.Table - want string - }{ - { - name: "Valid UUID", - val: smbios.Table{ - Data: []byte{ - 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, - 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, - }, - }, - want: "03020100-0100-0302-0001-020300010203", - }, - } - - for _, tt := range tests { - uuid := UUID([16]byte{}) - _, err := uuid.ParseField(&tt.val, 0) - if err != nil { - t.Errorf("ParseField(): '%v', want nil", err) - } - if uuid.String() != tt.want { - t.Errorf("ParseField(): '%s', want '%s'", uuid.String(), tt.want) - } - } -} - func TestParseSystemInfo(t *testing.T) { for _, tt := range []struct { table *smbios.Table diff --git a/dmidecode/type2_baseboard_information.go b/dmidecode/type2_baseboard_information.go index fe3b1ef..0e2b096 100644 --- a/dmidecode/type2_baseboard_information.go +++ b/dmidecode/type2_baseboard_information.go @@ -7,6 +7,7 @@ package dmidecode import ( "fmt" "io" + "math" "strings" "github.com/u-root/smbios" @@ -44,6 +45,11 @@ func ParseBaseboardInfo(t *smbios.Table) (*BaseboardInfo, error) { return bi, nil } +// Typ implements Table.Typ. +func (bi BaseboardInfo) Typ() smbios.TableType { + return smbios.TableTypeBaseboardInfo +} + func (bi *BaseboardInfo) String() string { lines := []string{ bi.Header.String(), @@ -153,6 +159,19 @@ func (oh ObjectHandles) str() string { return strings.Join(lines, "\n") } +// WriteField writes a object handles as defined by DSP0134 Section 7.3. +func (oh *ObjectHandles) WriteField(t *smbios.Table) (int, error) { + num := len(*oh) + if num > math.MaxUint8 { + return 0, fmt.Errorf("%w: too many object handles defined, can be maximum of 256", ErrInvalidArg) + } + t.WriteByte(uint8(num)) + for _, handle := range *oh { + t.WriteWord(handle) + } + return 1 + 2*num, nil +} + // ParseField parses object handles as defined by DSP0134 Section 7.3. func (oh *ObjectHandles) ParseField(t *smbios.Table, off int) (int, error) { num, err := t.GetByteAt(off) diff --git a/dmidecode/type38_ipmi_device_information.go b/dmidecode/type38_ipmi_device_information.go index 3551da0..783e4a8 100644 --- a/dmidecode/type38_ipmi_device_information.go +++ b/dmidecode/type38_ipmi_device_information.go @@ -39,6 +39,11 @@ func ParseIPMIDeviceInfo(t *smbios.Table) (*IPMIDeviceInfo, error) { return di, nil } +// Typ implements Table.Typ. +func (di IPMIDeviceInfo) Typ() smbios.TableType { + return smbios.TableTypeIPMIDeviceInfo +} + func (di *IPMIDeviceInfo) String() string { nvs := "Not Present" if di.NVStorageDeviceAddress != 0xff { diff --git a/dmidecode/type3_chassis_information.go b/dmidecode/type3_chassis_information.go index 8ac3b05..44025cc 100644 --- a/dmidecode/type3_chassis_information.go +++ b/dmidecode/type3_chassis_information.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "fmt" "io" + "math" "os" "strings" @@ -33,6 +34,11 @@ type ChassisInfo struct { SKUNumber string // 15h + CEC * CERL } +// Typ implements Table.Typ. +func (ci ChassisInfo) Typ() smbios.TableType { + return smbios.TableTypeChassisInfo +} + // ChassisContainedElement is defined in DSP0134 7.4.4. type ChassisContainedElement struct { Type ChassisElementType // 00h @@ -280,7 +286,28 @@ func (cec ChassisContainedElements) str() string { return strings.Join(lines, "\n") } -// ParseField parses object handles as defined by DSP0134 Section 7.4.4. +// WriteField writes contained elements as defined by DSP0134 Section 7.4.4. +func (cec *ChassisContainedElements) WriteField(t *smbios.Table) (int, error) { + num := len(*cec) + if num > math.MaxUint8 { + return 0, fmt.Errorf("%w: too many object handles defined, can be maximum of 256", ErrInvalidArg) + } + t.WriteByte(uint8(num)) + + if num == 0 { + t.WriteByte(0) + return 2, nil + } + t.WriteByte(3) + for _, el := range *cec { + if err := binary.Write(t, binary.LittleEndian, el); err != nil { + return 0, err + } + } + return 2 + 3*num, nil +} + +// ParseField parses contained elements as defined by DSP0134 Section 7.4.4. func (cec *ChassisContainedElements) ParseField(t *smbios.Table, off int) (int, error) { num, err := t.GetByteAt(off) if err != nil { diff --git a/dmidecode/type43_tpm_device.go b/dmidecode/type43_tpm_device.go index 1ab3830..ce573c1 100644 --- a/dmidecode/type43_tpm_device.go +++ b/dmidecode/type43_tpm_device.go @@ -15,7 +15,7 @@ import ( // TPMDevice is defined in DSP0134 7.44. type TPMDevice struct { smbios.Header `smbios:"-"` - VendorID TPMDeviceVendorID `smbios:"-,skip=4"` // 04h + VendorID TPMDeviceVendorID // 04h MajorSpecVersion uint8 // 08h MinorSpecVersion uint8 // 09h FirmwareVersion1 uint32 // 0Ah @@ -37,11 +37,14 @@ func ParseTPMDevice(t *smbios.Table) (*TPMDevice, error) { if _, err := parseStruct(t, 0 /* off */, false /* complete */, di); err != nil { return nil, err } - vid, _ := t.GetBytesAt(0, 4) - copy(di.VendorID[:], vid) return di, nil } +// Typ implements Table.Typ. +func (di TPMDevice) Typ() smbios.TableType { + return smbios.TableTypeTPMDevice +} + func (di *TPMDevice) String() string { lines := []string{ di.Header.String(), diff --git a/dmidecode/type4_processor_information.go b/dmidecode/type4_processor_information.go index 3cf96ed..f67e7c9 100644 --- a/dmidecode/type4_processor_information.go +++ b/dmidecode/type4_processor_information.go @@ -12,8 +12,6 @@ import ( "github.com/u-root/smbios" ) -// Much of this is auto-generated. If adding a new type, see README for instructions. - // ProcessorInfo is defined in DSP0134 x.x. type ProcessorInfo struct { smbios.Header `smbios:"-"` @@ -61,6 +59,11 @@ func ParseProcessorInfo(t *smbios.Table) (*ProcessorInfo, error) { return pi, nil } +// Typ implements Table.Typ. +func (pi ProcessorInfo) Typ() smbios.TableType { + return smbios.TableTypeProcessorInfo +} + // GetFamily returns the processor family, taken from the appropriate field. func (pi *ProcessorInfo) GetFamily() ProcessorFamily { if pi.Family == 0xfe && pi.Header.Length >= 0x2a { diff --git a/dmidecode/type7_cache_information.go b/dmidecode/type7_cache_information.go index 121d6f5..78c9bec 100644 --- a/dmidecode/type7_cache_information.go +++ b/dmidecode/type7_cache_information.go @@ -45,6 +45,11 @@ func ParseCacheInfo(t *smbios.Table) (*CacheInfo, error) { return ci, nil } +// Typ implements Table.Typ. +func (ci CacheInfo) Typ() smbios.TableType { + return smbios.TableTypeCacheInfo +} + func cacheSizeBytes2Or1(size1 uint16, size2 uint32) uint64 { mul2 := uint64(1024) if size2&0x80000000 != 0 { diff --git a/dmidecode/type9_system_slots.go b/dmidecode/type9_system_slots.go index 57d36c7..71c34aa 100644 --- a/dmidecode/type9_system_slots.go +++ b/dmidecode/type9_system_slots.go @@ -38,10 +38,15 @@ func ParseSystemSlots(t *smbios.Table) (*SystemSlots, error) { return nil, fmt.Errorf("%w: system slots table must be at least %d bytes", io.ErrUnexpectedEOF, 0xb) } - ss := &SystemSlots{Table: *t} - _, err := parseStruct(t, 0 /* off */, false /* complete */, ss) + s := &SystemSlots{Table: *t} + _, err := parseStruct(t, 0 /* off */, false /* complete */, s) if err != nil { return nil, err } - return ss, nil + return s, nil +} + +// Typ implements Table.Typ. +func (s SystemSlots) Typ() smbios.TableType { + return smbios.TableTypeSystemSlots } diff --git a/table.go b/table.go index 4387d23..e574b2a 100644 --- a/table.go +++ b/table.go @@ -19,7 +19,7 @@ import ( type Table struct { Header `smbios:"-"` - Data []byte `smbios:"-"` // Structured part of the table. + Data []byte `smbios:"-"` // Structured part of the table, not including header. Strings []string `smbios:"-"` // Strings section. } @@ -28,11 +28,77 @@ var ( ErrTableNotFound = errors.New("table not found") ) +// MarshalBinary encodes the table content into binary. +func (t *Table) MarshalBinary() ([]byte, error) { + return t.ToBytes(), nil +} + +// ToBytes encodes the table content into binary. +func (t *Table) ToBytes() []byte { + result := t.Header.ToBytes() + result = append(result, t.Data...) + for _, s := range t.Strings { + result = append(result, []byte(s)...) + result = append(result, 0x0) + } + if len(t.Strings) == 0 { + // If there's no strings, table ends with double 0. + result = append(result, 0x0) + } + result = append(result, 0x0) + return result +} + // Len returns length of the structured part of the table. func (t *Table) Len() int { return len(t.Data) + headerLen } +// WriteByte writes one byte to the table's data. +func (t *Table) WriteByte(b uint8) { + t.Data = append(t.Data, b) +} + +// WriteWord writes two little endian bytes to the table's data. +func (t *Table) WriteWord(v uint16) { + t.Data = append(t.Data, 0, 0) + binary.LittleEndian.PutUint16(t.Data[len(t.Data)-2:], v) +} + +// WriteDWord writes four little endian bytes to the table's data. +func (t *Table) WriteDWord(v uint32) { + t.Data = append(t.Data, 0, 0, 0, 0) + binary.LittleEndian.PutUint32(t.Data[len(t.Data)-4:], v) +} + +// WriteQWord writes eight little endian bytes to the table's data. +func (t *Table) WriteQWord(v uint64) { + t.Data = append(t.Data, 0, 0, 0, 0, 0, 0, 0, 0) + binary.LittleEndian.PutUint64(t.Data[len(t.Data)-8:], v) +} + +// WriteString writes a string index and appends the string to table. +func (t *Table) WriteString(s string) { + if s == "" { + t.WriteByte(0) + } else { + t.Strings = append(t.Strings, s) + // strings are 1-indexed bytes. + t.WriteByte(uint8(len(t.Strings))) + } +} + +// WriteBytes writes arbitrary bytes to the Table. +func (t *Table) WriteBytes(v []byte) { + t.Data = append(t.Data, v...) +} + +// Write writes arbitrary bytes to the Table. +func (t *Table) Write(p []byte) (int, error) { + t.Data = append(t.Data, p...) + return len(p), nil +} + // GetByteAt returns a byte from the structured part at the specified offset. func (t *Table) GetByteAt(offset int) (uint8, error) { if offset > len(t.Data)-1 { diff --git a/table_test.go b/table_test.go index 0937001..c8df7b4 100644 --- a/table_test.go +++ b/table_test.go @@ -260,17 +260,10 @@ func TestParseTables(t *testing.T) { } } -func Test64GetByteAt(t *testing.T) { +func TestGetByteAt(t *testing.T) { testStruct := Table{ - Header: Header{ - Type: TableTypeBIOSInfo, - Length: 20, - Handle: 0, - }, - Data: []byte{1, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - Strings: []string{"BIOS Boot Complete", "TestString #1"}, + Data: []byte{1, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, } - for _, tt := range []struct { name string offset int @@ -300,17 +293,10 @@ func Test64GetByteAt(t *testing.T) { } } -func Test64GetBytesAt(t *testing.T) { +func TestGetBytesAt(t *testing.T) { testStruct := Table{ - Header: Header{ - Type: TableTypeBIOSInfo, - Length: 16, - Handle: 0, - }, - Data: []byte{1, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - Strings: []string{"BIOS Boot Complete", "TestString #1"}, + Data: []byte{1, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, } - for _, tt := range []struct { name string offset int @@ -341,28 +327,21 @@ func Test64GetBytesAt(t *testing.T) { if !errors.Is(err, tt.err) { t.Errorf("GetBytesAt = %v, want %v", err, tt.err) } - if !reflect.DeepEqual(got, tt.want) { + if !bytes.Equal(got, tt.want) { t.Errorf("GetBytesAt = %v, want %v", got, tt.want) } }) } } -func Test64GetWordAt(t *testing.T) { +func TestGetWordAt(t *testing.T) { testStruct := Table{ - Header: Header{ - Type: TableTypeBIOSInfo, - Length: 16, - Handle: 0, - }, Data: []byte{ 213, 0, 0, 11, 12, 0, 0, 0, 0, 0, 0, 0, }, - Strings: []string{"BIOS Boot Complete", "TestString #1"}, } - for _, tt := range []struct { name string offset int @@ -391,29 +370,22 @@ func Test64GetWordAt(t *testing.T) { if !errors.Is(err, tt.err) { t.Errorf("GetWordAt = %v, want %v", err, tt.err) } - if !reflect.DeepEqual(got, tt.want) { + if got != tt.want { t.Errorf("GetWordAt = %v, want %v", got, tt.want) } }) } } -func Test64GetDWordAt(t *testing.T) { +func TestGetDWordAt(t *testing.T) { testStruct := Table{ - Header: Header{ - Type: TableTypeBIOSInfo, - Length: 20, - Handle: 0, - }, Data: []byte{ 1, 0, 0, 0, 213, 0, 0, 11, 12, 13, 14, 0, 0, 0, 0, 0, }, - Strings: []string{"BIOS Boot Complete", "TestString #1"}, } - for _, tt := range []struct { name string offset int @@ -442,29 +414,22 @@ func Test64GetDWordAt(t *testing.T) { if !errors.Is(err, tt.err) { t.Errorf("GetDWordAt = %v, want %v", err, tt.err) } - if !reflect.DeepEqual(got, tt.want) { + if got != tt.want { t.Errorf("GetDWordAt = %v, want %v", got, tt.want) } }) } } -func Test64GetQWordAt(t *testing.T) { +func TestGetQWordAt(t *testing.T) { testStruct := Table{ - Header: Header{ - Type: TableTypeBIOSInfo, - Length: 20, - Handle: 0, - }, Data: []byte{ 1, 0, 0, 0, 213, 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 0, }, - Strings: []string{"BIOS Boot Complete", "TestString #1"}, } - for _, tt := range []struct { name string offset int @@ -494,24 +459,18 @@ func Test64GetQWordAt(t *testing.T) { if !errors.Is(err, tt.err) { t.Errorf("GetQWordAt = %v, want %v", err, tt.err) } - if !reflect.DeepEqual(got, tt.want) { + if got != tt.want { t.Errorf("GetQWordAt = %v, want %v", got, tt.want) } }) } } -func Test64GetStringAt(t *testing.T) { +func TestGetStringAt(t *testing.T) { testStruct := Table{ - Header: Header{ - Type: TableTypeBIOSInfo, - Length: 20, - Handle: 0, - }, Data: []byte{1, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Strings: []string{"BIOS Boot Complete", "TestString #1"}, } - for _, tt := range []struct { name string offset int @@ -629,3 +588,95 @@ OEM-specific Type t.Errorf("Wrong length: Got %d want %d", got, 14) } } + +func TestTableMarshalBinary(t *testing.T) { + for _, tt := range []struct { + name string + table Table + want []byte + }{ + { + name: "full table", + table: Table{ + Header: Header{ + Type: 224, + Length: 14, + Handle: 0, + }, + Data: []byte{1, 153, 0, 3, 16, 1, 32, 2, 48, 3}, + Strings: []string{"Memory Init Complete", "End of DXE Phase", "BIOS Boot Complete"}, + }, + want: []byte{ + // Header and Data + 224, 14, 0, 0, + 1, 153, 0, 3, 16, 1, 32, 2, 48, 3, + // Strings + 77, 101, 109, 111, 114, 121, 32, 73, 110, 105, 116, 32, 67, 111, 109, 112, 108, 101, 116, 101, 0, // Memory Init Complete + 69, 110, 100, 32, 111, 102, 32, 68, 88, 69, 32, 80, 104, 97, 115, 101, 0, // End of DXE Phase + 66, 73, 79, 83, 32, 66, 111, 111, 116, 32, 67, 111, 109, 112, 108, 101, 116, 101, 0, // BIOS Boot Complete + 0, // Table terminator + }, + }, + { + name: "NoString", + table: Table{ + Header: Header{ + Type: 224, + Length: 14, + Handle: 0, + }, + Data: []byte{1, 153, 0, 3, 16, 1, 32, 2, 48, 3}, + }, + want: []byte{ + // Header and Data + 224, 14, 0, 0, + 1, 153, 0, 3, 16, 1, 32, 2, 48, 3, + // Strings + 0, // String terminator + 0, // Table terminator + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.table.MarshalBinary() + if err != nil { + t.Errorf("MarshalBinary returned error: %v", err) + } + if !bytes.Equal(got, tt.want) { + t.Errorf("Wrong raw data: Got %v, want %v", got, tt.want) + } + }) + } +} + +func TestTableWrite(t *testing.T) { + var tt Table + tt.WriteByte(1) + tt.WriteWord(0x0203) + tt.WriteDWord(0x04050607) + tt.WriteQWord(0x0102030405060708) + tt.WriteBytes([]byte{0, 1}) + tt.WriteString("foo") + tt.WriteString("") + _, _ = tt.Write([]byte{0x1, 0x2}) + + want := []byte{ + 1, + 0x03, 0x02, + 0x07, 0x06, 0x05, 0x04, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + 0, 1, + 0x1, + 0, + 0x1, 0x2, + } + if !reflect.DeepEqual(tt.Data, want) { + t.Errorf("Data = %#v, want %#v", tt.Data, want) + } + wantS := []string{ + "foo", + } + if !reflect.DeepEqual(tt.Strings, wantS) { + t.Errorf("Strings = %#v, want %#v", tt.Strings, wantS) + } +}