| // Package smbios implements parsing of SMBIOS data structures. | 
 | // SMBIOS data is commonly populated by platform firmware to convey various | 
 | // metadata (including name, vendor, slots and serial numbers) about the | 
 | // platform to the operating system. | 
 | // The SMBIOS standard is maintained by DMTF and available at | 
 | // https://www.dmtf.org/sites/default/files/standards/documents/ | 
 | // DSP0134_3.6.0.pdf. The rest of this package just refers to it as "the | 
 | // standard". | 
 | package smbios | 
 |  | 
 | import ( | 
 | 	"bufio" | 
 | 	"bytes" | 
 | 	"encoding/binary" | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"reflect" | 
 | 	"strings" | 
 | ) | 
 |  | 
 | // See spec section 6.1.2 | 
 | type structureHeader struct { | 
 | 	// Types 128 through 256 are reserved for OEM and system-specific use. | 
 | 	Type uint8 | 
 | 	// Length of the structure including this header, excluding the string | 
 | 	// set. | 
 | 	Length uint8 | 
 | 	// Unique handle for this structure. | 
 | 	Handle uint16 | 
 | } | 
 |  | 
 | type Structure struct { | 
 | 	Type             uint8 | 
 | 	Handle           uint16 | 
 | 	FormattedSection []byte | 
 | 	Strings          []string | 
 | } | 
 |  | 
 | // Table represents a decoded SMBIOS table consisting of its structures. | 
 | // A few known structures are parsed if present, the rest is put into | 
 | // Structures unparsed. | 
 | type Table struct { | 
 | 	BIOSInformationRaw       *BIOSInformationRaw | 
 | 	SystemInformationRaw     *SystemInformationRaw | 
 | 	BaseboardsInformationRaw []*BaseboardInformationRaw | 
 | 	SystemSlotsRaw           []*SystemSlotRaw | 
 | 	MemoryDevicesRaw         []*MemoryDeviceRaw | 
 |  | 
 | 	Structures []Structure | 
 | } | 
 |  | 
 | const ( | 
 | 	structTypeInactive   = 126 | 
 | 	structTypeEndOfTable = 127 | 
 | ) | 
 |  | 
 | func Unmarshal(table *bufio.Reader) (*Table, error) { | 
 | 	var tbl Table | 
 | 	for { | 
 | 		var structHdr structureHeader | 
 | 		if err := binary.Read(table, binary.LittleEndian, &structHdr); err != nil { | 
 | 			if err == io.EOF { | 
 | 				// Be tolerant of EOFs on structure boundaries even though | 
 | 				// the EOT marker is specified as a type 127 structure. | 
 | 				break | 
 | 			} | 
 | 			return nil, fmt.Errorf("unable to read structure header: %w", err) | 
 | 		} | 
 | 		if int(structHdr.Length) < binary.Size(structHdr) { | 
 | 			return nil, fmt.Errorf("invalid structure: header length (%d) smaller than header", structHdr.Length) | 
 | 		} | 
 | 		if structHdr.Type == structTypeEndOfTable { | 
 | 			break | 
 | 		} | 
 | 		var s Structure | 
 | 		s.Type = structHdr.Type | 
 | 		s.Handle = structHdr.Handle | 
 | 		s.FormattedSection = make([]byte, structHdr.Length-uint8(binary.Size(structHdr))) | 
 | 		if _, err := io.ReadFull(table, s.FormattedSection); err != nil { | 
 | 			return nil, fmt.Errorf("error while reading structure (handle %d) contents: %w", structHdr.Handle, err) | 
 | 		} | 
 | 		// Read string-set | 
 | 		for { | 
 | 			str, err := table.ReadString(0x00) | 
 | 			if err != nil { | 
 | 				return nil, fmt.Errorf("error while reading string table (handle %d): %w", structHdr.Handle, err) | 
 | 			} | 
 | 			// Remove trailing null byte | 
 | 			str = strings.TrimSuffix(str, "\x00") | 
 | 			// Don't populate a zero-length first string if the string-set is | 
 | 			// empty. | 
 | 			if len(str) != 0 { | 
 | 				s.Strings = append(s.Strings, str) | 
 | 			} | 
 | 			maybeTerminator, err := table.ReadByte() | 
 | 			if err != nil { | 
 | 				return nil, fmt.Errorf("error while reading string table (handle %d): %w", structHdr.Handle, err) | 
 | 			} | 
 | 			if maybeTerminator == 0 { | 
 | 				// We have a valid string-set terminator, exit the loop | 
 | 				break | 
 | 			} | 
 | 			// The next byte was not a terminator, put it back | 
 | 			if err := table.UnreadByte(); err != nil { | 
 | 				panic(err) // Cannot happen operationally | 
 | 			} | 
 | 		} | 
 | 		switch structHdr.Type { | 
 | 		case structTypeInactive: | 
 | 			continue | 
 | 		case structTypeBIOSInformation: | 
 | 			var biosInfo BIOSInformationRaw | 
 | 			if err := UnmarshalStructureRaw(s, &biosInfo); err != nil { | 
 | 				return nil, fmt.Errorf("failed unmarshaling BIOS Information: %w", err) | 
 | 			} | 
 | 			tbl.BIOSInformationRaw = &biosInfo | 
 | 		case structTypeSystemInformation: | 
 | 			var systemInfo SystemInformationRaw | 
 | 			if err := UnmarshalStructureRaw(s, &systemInfo); err != nil { | 
 | 				return nil, fmt.Errorf("failed unmarshaling System Information: %w", err) | 
 | 			} | 
 | 			tbl.SystemInformationRaw = &systemInfo | 
 | 		case structTypeBaseboardInformation: | 
 | 			fmt.Println(s) | 
 | 			var baseboardInfo BaseboardInformationRaw | 
 | 			if err := UnmarshalStructureRaw(s, &baseboardInfo); err != nil { | 
 | 				return nil, fmt.Errorf("failed unmarshaling Baseboard Information: %w", err) | 
 | 			} | 
 | 			tbl.BaseboardsInformationRaw = append(tbl.BaseboardsInformationRaw, &baseboardInfo) | 
 | 		case structTypeSystemSlot: | 
 | 			var sysSlot SystemSlotRaw | 
 | 			if err := UnmarshalStructureRaw(s, &sysSlot); err != nil { | 
 | 				return nil, fmt.Errorf("failed unmarshaling System Slot: %w", err) | 
 | 			} | 
 | 			tbl.SystemSlotsRaw = append(tbl.SystemSlotsRaw, &sysSlot) | 
 | 		case structTypeMemoryDevice: | 
 | 			var memoryDev MemoryDeviceRaw | 
 | 			if err := UnmarshalStructureRaw(s, &memoryDev); err != nil { | 
 | 				return nil, fmt.Errorf("failed unmarshaling Memory Device: %w", err) | 
 | 			} | 
 | 			tbl.MemoryDevicesRaw = append(tbl.MemoryDevicesRaw, &memoryDev) | 
 | 		default: | 
 | 			// Just pass through the raw structure | 
 | 			tbl.Structures = append(tbl.Structures, s) | 
 | 		} | 
 | 	} | 
 | 	return &tbl, nil | 
 | } | 
 |  | 
 | // Version contains a two-part version number consisting of a major and minor | 
 | // version. This is a common structure in SMBIOS. | 
 | type Version struct { | 
 | 	Major uint8 | 
 | 	Minor uint8 | 
 | } | 
 |  | 
 | func (v *Version) String() string { | 
 | 	return fmt.Sprintf("%d.%d", v.Major, v.Minor) | 
 | } | 
 |  | 
 | // AtLeast returns true if the version in v is at least the given version. | 
 | func (v *Version) AtLeast(major, minor uint8) bool { | 
 | 	return v.Major >= major && v.Minor >= minor | 
 | } | 
 |  | 
 | // UnmarshalStructureRaw unmarshals a SMBIOS structure into a Go struct which | 
 | // has some constraints. The first two fields need to be a `uint16 handle` and | 
 | // a `StructureVersion Version` field. After that any number of fields may | 
 | // follow as long as they are either of type `string` (which will be looked up | 
 | // in the string table) or readable by binary.Read. To determine the structure | 
 | // version, the smbios_min_vers struct tag needs to be put on the first field | 
 | // of a newer structure version. The version implicitly starts with 2.0. | 
 | // The version determined is written to the second target struct field. | 
 | // Fields which do not have a fixed size need to be typed as a slice and tagged | 
 | // with smbios_repeat set to the name of the field containing the count. The | 
 | // count field itself needs to be some width of uint. | 
 | func UnmarshalStructureRaw(rawStruct Structure, target any) error { | 
 | 	v := reflect.ValueOf(target) | 
 | 	if v.Kind() != reflect.Pointer { | 
 | 		return errors.New("target needs to be a pointer") | 
 | 	} | 
 | 	v = v.Elem() | 
 | 	if v.Kind() != reflect.Struct { | 
 | 		return errors.New("target needs to be a pointer to a struct") | 
 | 	} | 
 | 	v.Field(0).SetUint(uint64(rawStruct.Handle)) | 
 | 	r := bytes.NewReader(rawStruct.FormattedSection) | 
 | 	completedVersion := Version{Major: 0, Minor: 0} | 
 | 	parsingVersion := Version{Major: 2, Minor: 0} | 
 | 	numFields := v.NumField() | 
 | 	hasAborted := false | 
 | 	for i := 2; i < numFields; i++ { | 
 | 		fieldType := v.Type().Field(i) | 
 | 		if min := fieldType.Tag.Get("smbios_min_ver"); min != "" { | 
 | 			var ver Version | 
 | 			if _, err := fmt.Sscanf(min, "%d.%d", &ver.Major, &ver.Minor); err != nil { | 
 | 				panic(fmt.Sprintf("invalid smbios_min_ver tag in %v: %v", fieldType.Name, err)) | 
 | 			} | 
 | 			completedVersion = parsingVersion | 
 | 			parsingVersion = ver | 
 | 		} | 
 | 		f := v.Field(i) | 
 |  | 
 | 		if repeat := fieldType.Tag.Get("smbios_repeat"); repeat != "" { | 
 | 			repeatCountField := v.FieldByName(repeat) | 
 | 			if !repeatCountField.IsValid() { | 
 | 				panic(fmt.Sprintf("invalid smbios_repeat tag in %v: no such field %q", fieldType.Name, repeat)) | 
 | 			} | 
 | 			if !repeatCountField.CanUint() { | 
 | 				panic(fmt.Sprintf("invalid smbios_repeat tag in %v: referenced field %q is not uint-compatible", fieldType.Name, repeat)) | 
 | 			} | 
 | 			if f.Kind() != reflect.Slice { | 
 | 				panic(fmt.Sprintf("cannot repeat a field (%q) which is not a slice", fieldType.Name)) | 
 | 			} | 
 | 			if repeatCountField.Uint() > 65536 { | 
 | 				return fmt.Errorf("refusing to read a field repeated more than 65536 times (given %d times)", repeatCountField.Uint()) | 
 | 			} | 
 | 			repeatCount := int(repeatCountField.Uint()) | 
 | 			f.Set(reflect.MakeSlice(f.Type(), repeatCount, repeatCount)) | 
 | 			for j := 0; j < repeatCount; j++ { | 
 | 				fs := f.Index(j) | 
 | 				err := unmarshalField(&rawStruct, fs, r) | 
 | 				if errors.Is(err, io.EOF) { | 
 | 					hasAborted = true | 
 | 					break | 
 | 				} else if err != nil { | 
 | 					return fmt.Errorf("error unmarshaling field %q: %w", fieldType.Name, err) | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		err := unmarshalField(&rawStruct, f, r) | 
 | 		if errors.Is(err, io.EOF) { | 
 | 			hasAborted = true | 
 | 			break | 
 | 		} else if err != nil { | 
 | 			return fmt.Errorf("error unmarshaling field %q: %w", fieldType.Name, err) | 
 | 		} | 
 | 	} | 
 | 	if !hasAborted { | 
 | 		completedVersion = parsingVersion | 
 | 	} | 
 | 	if completedVersion.Major == 0 { | 
 | 		return fmt.Errorf("structure's formatted section (%d bytes) is smaller than its minimal size", len(rawStruct.FormattedSection)) | 
 | 	} | 
 | 	v.Field(1).Set(reflect.ValueOf(completedVersion)) | 
 | 	return nil | 
 | } | 
 |  | 
 | func unmarshalField(rawStruct *Structure, field reflect.Value, r *bytes.Reader) error { | 
 | 	if field.Kind() == reflect.String { | 
 | 		var stringTableIdx uint8 | 
 | 		err := binary.Read(r, binary.LittleEndian, &stringTableIdx) | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		if stringTableIdx == 0 { | 
 | 			return nil | 
 | 		} | 
 | 		if int(stringTableIdx)-1 >= len(rawStruct.Strings) { | 
 | 			return fmt.Errorf("string index (%d) bigger than string table (%q)", stringTableIdx-1, rawStruct.Strings) | 
 | 		} | 
 | 		field.SetString(rawStruct.Strings[stringTableIdx-1]) | 
 | 		return nil | 
 | 	} | 
 | 	return binary.Read(r, binary.LittleEndian, field.Addr().Interface()) | 
 | } |