You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
4.0 KiB
Go

package s19
import (
"bufio"
"encoding/hex"
"errors"
"fmt"
"os"
"sort"
"strconv"
)
type S19Reader struct {
FilePath string
File *os.File
OsSize int64
}
func NewS19Reader(filePath string) (*S19Reader, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
stat, err := file.Stat()
if err != nil {
return nil, err
}
return &S19Reader{
FilePath: filePath,
File: file,
OsSize: stat.Size(),
}, nil
}
func (fr *S19Reader) ReadAllRecords() (records []*SRecord, err error) {
_, err = fr.File.Seek(0, 0)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(fr.File)
for scanner.Scan() {
if len(scanner.Text()) < 10 {
continue
}
record, err := parseSRecord(scanner.Text())
if err != nil {
return nil, err
}
records = append(records, record)
//log.Debug().Msg(fmt.Sprint(record))
}
if err := scanner.Err(); err != nil {
return nil, err
}
return records, nil
}
func parseSRecord(line string) (*SRecord, error) {
if len(line) < 10 || line[0] != 'S' {
return nil, fmt.Errorf("invalid S-record format '%s'", line)
}
record := &SRecord{}
record.Type = line[:2]
// Extract byte count (hexadecimal, 2 characters)
count, err := strconv.ParseInt(line[2:4], 16, 8)
if err != nil {
return nil, fmt.Errorf("invalid byte count: %v", err)
}
record.Count = int(count)
// Address length depends on the type
var addressLength int
switch record.Type {
case "S0", "S1", "S9": // Address is 2 bytes
addressLength = 4
case "S2", "S8": // Address is 3 bytes
addressLength = 6
case "S3", "S7": // Address is 4 bytes
addressLength = 8
default:
return nil, fmt.Errorf("unsupported record type: %s", record.Type)
}
// Extract and parse the address
if len(line) < 4+addressLength {
return nil, errors.New("record is too short for address")
}
address, err := strconv.ParseUint(line[4:4+addressLength], 16, 32)
if err != nil {
return nil, fmt.Errorf("invalid address: %v", err)
}
record.Address = uint32(address)
// Extract data and checksum
dataStart := 4 + addressLength
dataEnd := len(line) - 2 // Exclude checksum
if dataEnd > dataStart {
record.Data, err = hex.DecodeString(line[dataStart:dataEnd])
if err != nil {
return nil, fmt.Errorf("invalid data: %v", err)
}
}
// Validate checksum
checksum, err := strconv.ParseUint(line[len(line)-2:], 16, 8)
if err != nil {
return nil, fmt.Errorf("invalid checksum: %v", err)
}
if !validateChecksum(line, byte(checksum)) {
return nil, errors.New("checksum validation failed")
}
return record, nil
}
func (fr *S19Reader) Close() error {
return fr.File.Close()
}
func (*S19Reader) DetectAddressRanges(records []*SRecord) []AddressRange {
// Sort records by address
sort.Slice(records, func(i, j int) bool {
return records[i].Address < records[j].Address
})
var ranges []AddressRange
var currentRange *AddressRange
for sliceIndex, record := range records {
if currentRange == nil {
// Start a new range
currentRange = &AddressRange{
SliceStart: uint32(sliceIndex),
SliceEnd: 0,
StartAddress: record.Address,
EndAddress: record.Address + uint32(len(record.Data)) - 1,
Size: uint32(len(record.Data)),
}
continue
}
// Check if the current record is contiguous with the previous range
if record.Address == currentRange.EndAddress+1 {
currentRange.EndAddress = record.Address + uint32(len(record.Data)) - 1
currentRange.Size += uint32(len(record.Data))
currentRange.SliceEnd = uint32(sliceIndex)
} else {
// Save the current range and start a new one
if currentRange.SliceEnd == 0 {
currentRange.SliceEnd = currentRange.SliceStart
}
ranges = append(ranges, *currentRange)
currentRange = &AddressRange{
SliceStart: uint32(sliceIndex),
SliceEnd: 0,
StartAddress: record.Address,
EndAddress: record.Address + uint32(len(record.Data)) - 1,
Size: uint32(len(record.Data)),
}
}
}
// Append the last range
if currentRange != nil {
ranges = append(ranges, *currentRange)
}
return ranges
}