package faultRecordFile import ( "archive/tar" "bytes" "compress/bzip2" "compress/gzip" "errors" "fmt" "io" "path" "sort" "strconv" "strings" ) const ( maxEntrySize = 16 << 20 // 单个文件上限 maxArchiveRead = 64 << 20 // 解压总量上限 fileFailure = "failuredata" fileWaveform = "waveformdata" indexDirPrefix = "index_" ) // RecordFiles 压缩包中一条记录的两个文件(十六进制文本原文) type RecordFiles struct { Batch string `json:"batch"` Sn string `json:"sn"` Index int `json:"index"` FailureData []byte `json:"-"` WaveformData []byte `json:"-"` } // openArchive 按文件头判断压缩格式;扩展名为 .tar.bz2 的文件实际可能是 gzip func openArchive(data []byte) (io.Reader, error) { switch { case len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b: return gzip.NewReader(bytes.NewReader(data)) case len(data) >= 3 && string(data[:3]) == "BZh": return bzip2.NewReader(bytes.NewReader(data)), nil default: return bytes.NewReader(data), nil // 未压缩的 tar } } // ExtractArchive 解出 //index_/{failuredata,waveformdata},其他文件忽略 func ExtractArchive(data []byte) ([]RecordFiles, error) { zr, err := openArchive(data) if err != nil { return nil, fmt.Errorf("open archive: %w", err) } tr := tar.NewReader(io.LimitReader(zr, maxArchiveRead)) records := map[string]*RecordFiles{} for { h, err := tr.Next() if errors.Is(err, io.EOF) { break } if err != nil { return nil, fmt.Errorf("read archive: %w", err) } if h.Typeflag != tar.TypeReg { continue } parts := strings.Split(strings.Trim(path.Clean(h.Name), "/"), "/") if len(parts) < 4 { continue } name := parts[len(parts)-1] if name != fileFailure && name != fileWaveform { continue } idxStr, ok := strings.CutPrefix(parts[len(parts)-2], indexDirPrefix) if !ok { continue } idx, err := strconv.Atoi(idxStr) if err != nil { continue } if h.Size > maxEntrySize { return nil, fmt.Errorf("%s too large: %d bytes", h.Name, h.Size) } content, err := io.ReadAll(io.LimitReader(tr, maxEntrySize)) if err != nil { return nil, fmt.Errorf("read %s: %w", h.Name, err) } batch, sn := parts[len(parts)-4], parts[len(parts)-3] key := batch + "/" + sn + "/" + idxStr rf, ok := records[key] if !ok { rf = &RecordFiles{Batch: batch, Sn: sn, Index: idx} records[key] = rf } if name == fileFailure { rf.FailureData = bytes.TrimSpace(content) } else { rf.WaveformData = bytes.TrimSpace(content) } } list := make([]RecordFiles, 0, len(records)) for _, rf := range records { list = append(list, *rf) } sort.Slice(list, func(i, j int) bool { if list[i].Sn != list[j].Sn { return list[i].Sn < list[j].Sn } return list[i].Index < list[j].Index }) return list, nil } // ParseArchive 解压并解析全部记录;缺少任一文件的记录返回错误 func ParseArchive(data []byte, deviceType string) ([]Record, error) { files, err := ExtractArchive(data) if err != nil { return nil, err } if len(files) == 0 { return nil, fmt.Errorf("no record found in archive") } records := make([]Record, 0, len(files)) for _, f := range files { if len(f.FailureData) == 0 || len(f.WaveformData) == 0 { return nil, fmt.Errorf("record %s/%s/index_%d is incomplete", f.Batch, f.Sn, f.Index) } rec, err := ParseRecordFiles(f, deviceType) if err != nil { return nil, fmt.Errorf("record %s/%s/index_%d: %w", f.Batch, f.Sn, f.Index, err) } records = append(records, rec) } return records, nil }