package inspurlegacy import ( "strings" "time" "git.mchus.pro/mchus/logpile/internal/models" ) // binaryFRU holds the fields decoded from a binary IPMI FRU image (FRU.bin). type binaryFRU struct { ChassisType string ChassisPart string ChassisSerial string BoardManufacturer string BoardProduct string BoardSerial string BoardPart string BoardMfgDate string ProductManufacturer string ProductName string ProductPart string ProductVersion string ProductSerial string ProductAssetTag string } // chassisTypeNames maps IPMI/SMBIOS chassis type codes to names. Only codes seen // on Inspur rack servers are listed; unknown codes fall back to the raw number. var chassisTypeNames = map[byte]string{ 0x03: "Desktop", 0x11: "Main Server Chassis", 0x17: "Rack Mount Chassis", 0x1C: "Blade", 0x1D: "Blade Enclosure", } // fruEpoch is the IPMI FRU manufacturing-date epoch (1996-01-01 00:00 UTC). var fruEpoch = time.Date(1996, 1, 1, 0, 0, 0, 0, time.UTC) // DecodeBinaryFRU parses a binary IPMI FRU image. It returns false when the // common header is missing or no area yields any field. func DecodeBinaryFRU(data []byte) (binaryFRU, bool) { var fru binaryFRU if len(data) < 8 || data[0] != 0x01 { return fru, false } // Common-header area offsets are stored in 8-byte units. chassisOff := int(data[2]) * 8 boardOff := int(data[3]) * 8 productOff := int(data[4]) * 8 if chassisOff >= 3 { decodeChassisArea(data, chassisOff, &fru) } if boardOff >= 3 { decodeBoardArea(data, boardOff, &fru) } if productOff >= 3 { decodeProductArea(data, productOff, &fru) } if fru == (binaryFRU{}) { return fru, false } return fru, true } // areaEnd returns the exclusive end offset of an info area beginning at off, // clamped to the buffer. ok is false when the area header is invalid. func areaEnd(data []byte, off int) (end int, ok bool) { if off < 0 || off+2 > len(data) || data[off] != 0x01 { return 0, false } end = off + int(data[off+1])*8 if end <= off || end > len(data) { end = len(data) } return end, true } // readField decodes one IPMI type/length-encoded field starting at pos. // done is true at the 0xC1 end marker or when the buffer is exhausted. func readField(data []byte, pos, end int) (value string, next int, done bool) { if pos >= end || pos >= len(data) { return "", pos, true } tl := data[pos] // Note: 0xC1 is the IPMI "end of fields" sentinel, but this Inspur FRU also // uses 0xC1 to encode legitimate single-character type-3 strings (the "0" // placeholders). Terminate only on area end, zero padding or 0xFF instead. if tl == 0x00 || tl == 0xFF { return "", pos + 1, true } typ := tl >> 6 length := int(tl & 0x3F) if pos+1+length > end || pos+1+length > len(data) { return "", end, true } raw := data[pos+1 : pos+1+length] next = pos + 1 + length switch typ { case 0x03, 0x00: // 8-bit ASCII+Latin1, or unspecified/binary treated as text return strings.TrimSpace(string(raw)), next, false case 0x02: // 6-bit packed ASCII return decode6bitASCII(raw), next, false case 0x01: // BCD plus return decodeBCDPlus(raw), next, false default: return "", next, false } } func decode6bitASCII(raw []byte) string { var b strings.Builder var acc uint32 var bits uint for _, by := range raw { acc |= uint32(by) << bits bits += 8 for bits >= 6 { b.WriteByte(byte(acc&0x3F) + 0x20) acc >>= 6 bits -= 6 } } return strings.TrimSpace(b.String()) } func decodeBCDPlus(raw []byte) string { const digits = "0123456789 -. " // 0xA space, 0xB dash, 0xC dot var b strings.Builder for _, by := range raw { hi, lo := by>>4, by&0x0F if int(hi) < len(digits) { b.WriteByte(digits[hi]) } if int(lo) < len(digits) { b.WriteByte(digits[lo]) } } return strings.TrimSpace(b.String()) } // fieldList reads consecutive type/length fields from start up to the area end // or the 0xC1 marker. Fixed-position fields map to slots by index; trailing // custom fields are ignored. func fieldList(data []byte, start, end int) []string { var out []string pos := start for { v, next, done := readField(data, pos, end) if done { break } out = append(out, v) pos = next if len(out) > 16 { break } } return out } func at(list []string, i int) string { if i < len(list) { return list[i] } return "" } func decodeChassisArea(data []byte, off int, fru *binaryFRU) { end, ok := areaEnd(data, off) if !ok || off+3 > len(data) { return } if name, known := chassisTypeNames[data[off+2]]; known { fru.ChassisType = name } f := fieldList(data, off+3, end) fru.ChassisPart = at(f, 0) fru.ChassisSerial = at(f, 1) } func decodeBoardArea(data []byte, off int, fru *binaryFRU) { end, ok := areaEnd(data, off) if !ok || off+6 > len(data) { return } mins := int(data[off+3]) | int(data[off+4])<<8 | int(data[off+5])<<16 if mins > 0 { fru.BoardMfgDate = fruEpoch.Add(time.Duration(mins) * time.Minute).Format("2006-01-02") } f := fieldList(data, off+6, end) fru.BoardManufacturer = at(f, 0) fru.BoardProduct = at(f, 1) fru.BoardSerial = at(f, 2) fru.BoardPart = at(f, 3) } func decodeProductArea(data []byte, off int, fru *binaryFRU) { end, ok := areaEnd(data, off) if !ok || off+3 > len(data) { return } f := fieldList(data, off+3, end) fru.ProductManufacturer = at(f, 0) fru.ProductName = at(f, 1) fru.ProductPart = at(f, 2) fru.ProductVersion = at(f, 3) fru.ProductSerial = at(f, 4) fru.ProductAssetTag = at(f, 5) } // placeholder reports whether a decoded FRU value is an empty-ish placeholder // ("", "0", "NULL", "N/A") that must not overwrite real data. func placeholder(s string) bool { switch strings.ToUpper(strings.TrimSpace(s)) { case "", "0", "NULL", "N/A", "NONE", "TO BE FILLED BY O.E.M.": return true } return false } func firstReal(vals ...string) string { for _, v := range vals { if !placeholder(v) { return strings.TrimSpace(v) } } return "" } // toFRUInfo renders the decoded image as a single builtin FRU record. func (f binaryFRU) toFRUInfo() models.FRUInfo { return models.FRUInfo{ Description: "Builtin FRU Device (ID 0)", ChassisType: f.ChassisType, Manufacturer: firstReal(f.ProductManufacturer, f.BoardManufacturer), ProductName: firstReal(f.ProductName, f.BoardProduct), SerialNumber: firstReal(f.ProductSerial, f.BoardSerial, f.ChassisSerial), PartNumber: firstReal(f.ProductPart, f.BoardPart, f.ChassisPart), Version: firstReal(f.ProductVersion), AssetTag: firstReal(f.ProductAssetTag), MfgDate: f.BoardMfgDate, } } // applyToBoardInfo fills empty BoardInfo identity fields. The product area holds // the operator-facing system serial; the board area holds the PCB serial and, // on this hardware, the only real motherboard part number. func (f binaryFRU) applyToBoardInfo(b *models.BoardInfo) { if b.Manufacturer == "" { b.Manufacturer = firstReal(f.ProductManufacturer, f.BoardManufacturer) } if b.ProductName == "" { b.ProductName = firstReal(f.ProductName, f.BoardProduct) } if b.SerialNumber == "" { b.SerialNumber = firstReal(f.ProductSerial, f.BoardSerial) } if b.PartNumber == "" { b.PartNumber = firstReal(f.BoardPart, f.ProductPart) } }