Initial commit

This commit is contained in:
DataHoarder 2023-10-08 11:42:21 +02:00
commit a3860c8a97
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
8 changed files with 312 additions and 0 deletions

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2023 WeebDataHoarder, go-inthex Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# go-inthex
Go [Intel HEX](https://en.wikipedia.org/wiki/Intel_HEX) encoding / decoding library

80
decode.go Normal file
View file

@ -0,0 +1,80 @@
package go_inthex
import (
"encoding/binary"
"errors"
"strings"
)
func Decode(data []byte) (stream *Stream, err error) {
stream = &Stream{}
var currentRegion Region
var baseAddress uint32
emitRegion := func() {
if len(currentRegion.Data) > 0 || len(currentRegion.Extra) > 0 {
stream.Regions = append(stream.Regions, currentRegion)
}
currentRegion = Region{}
}
for _, d := range strings.Split(string(data), "\n") {
if len(d) == 0 {
continue
}
if d[0] != ':' {
currentRegion.Extra = append(currentRegion.Extra, strings.Trim(d, "\r\n"))
continue
}
r, err := RecordFromString(d)
if err != nil {
return nil, err
}
switch r.Code {
case RecordData:
address := baseAddress + uint32(r.Address)
if !currentRegion.IsContiguousAddress(address) {
emitRegion()
baseAddress = baseAddress + uint32(r.Address)
currentRegion.Address = baseAddress
}
currentRegion.Append(r.Data)
case RecordEOF:
emitRegion()
return stream, nil
case RecordExtendedSegmentAddress:
if len(r.Data) != 2 {
return nil, errors.New("invalid ExtendedSegmentAddress length")
}
baseAddress = uint32(binary.BigEndian.Uint16(r.Data)) * 16
if len(currentRegion.Data) == 0 {
currentRegion.Address = baseAddress
}
case RecordStartSegmentAddress:
if len(r.Data) != 4 {
return nil, errors.New("invalid StartSegmentAddress length")
}
stream.StartLinearAddress = binary.BigEndian.Uint32(r.Data)
case RecordExtendedLinearAddress:
if len(r.Data) != 2 {
return nil, errors.New("invalid ExtendedLinearAddress length")
}
baseAddress = uint32(binary.BigEndian.Uint16(r.Data)) << 16
if len(currentRegion.Data) == 0 {
currentRegion.Address = baseAddress
}
case RecordStartLinearAddress:
if len(r.Data) != 4 {
return nil, errors.New("invalid RecordStartLinearAddress length")
}
stream.StartLinearAddress = binary.BigEndian.Uint32(r.Data)
}
}
return
}

99
encode.go Normal file
View file

@ -0,0 +1,99 @@
package go_inthex
import (
"encoding/binary"
"errors"
"strings"
)
func Encode(stream *Stream) (hex []byte, err error) {
var entries []string
emitRecord := func(r Record) {
entries = append(entries, r.String()+"\r\n")
}
emitAddress := func(addr uint32) error {
if (addr & 0xFFFF) == 0 {
emitRecord(Record{
Code: RecordExtendedLinearAddress,
Address: uint16(addr >> 16),
Data: nil,
})
return nil
} else if (addr&0xFFF0000F) == 0 && (addr&0xF) == 0 {
emitRecord(Record{
Code: RecordExtendedSegmentAddress,
Address: uint16(addr / 16),
Data: nil,
})
return nil
} else {
return errors.New("non-aligned address")
}
}
const recordSize = 16
const sectionSize = 1 << 16
for _, r := range stream.Regions {
baseAddress := r.Address
if err = emitAddress(baseAddress); err != nil {
return nil, err
}
// Add extra entries as needed
for _, e := range r.Extra {
entries = append(entries, e+"\r\n")
}
endAddress := r.Address + uint32(len(r.Data))
for addr := r.Address; addr <= endAddress; addr += sectionSize {
if addr != r.Address {
if err = emitAddress(addr); err != nil {
return nil, err
}
}
for subAddr := addr; subAddr <= (addr+sectionSize) && subAddr <= endAddress; subAddr += recordSize {
writeSize := min(recordSize, endAddress-subAddr+1)
dataOffset := subAddr - r.Address
emitRecord(Record{
Code: RecordData,
Address: uint16(subAddr & 0xFFFF),
Data: r.Data[dataOffset : dataOffset+writeSize],
})
}
}
}
if stream.StartLinearAddress != 0 {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], stream.StartLinearAddress)
emitRecord(Record{
Code: RecordStartLinearAddress,
Address: 0,
Data: buf[:],
})
}
if stream.StartSegmentAddress != 0 {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], stream.StartSegmentAddress)
emitRecord(Record{
Code: RecordStartSegmentAddress,
Address: 0,
Data: buf[:],
})
}
emitRecord(Record{
Code: RecordEOF,
Address: 0,
Data: nil,
})
return []byte(strings.Join(entries, "\r\n")), nil
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module git.gammaspectra.live/WeebDataHoarder/go-inthex
go 1.21

79
record.go Normal file
View file

@ -0,0 +1,79 @@
package go_inthex
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"strings"
)
type RecordCode uint8
const (
RecordData = RecordCode(iota)
RecordEOF
RecordExtendedSegmentAddress
RecordStartSegmentAddress
RecordExtendedLinearAddress
RecordStartLinearAddress
)
type Record struct {
Code RecordCode
Address uint16
Data []byte
}
func (r Record) String() string {
if len(r.Data) > math.MaxUint8 {
panic("data too long")
}
buf := make([]byte, 1+1+2+len(r.Data)+1)
buf[0] = uint8(len(r.Data))
binary.BigEndian.PutUint16(buf[1:], r.Address)
buf[3] = uint8(r.Code)
copy(buf[4:], r.Data)
buf[len(buf)-1] = Checksum(buf[:len(buf)-1])
return ":" + strings.ToUpper(hex.EncodeToString(buf)) + "\r\n"
}
func RecordFromString(data string) (Record, error) {
if data[0] != ':' {
return Record{}, fmt.Errorf("expected :, got %s", data[:1])
}
if len(data) < 1+(1+2+1)*2 {
return Record{}, io.ErrUnexpectedEOF
}
b, err := hex.DecodeString(strings.Trim(data[1:], "\r\n"))
if err != nil {
return Record{}, err
}
if len(b) < 1 {
return Record{}, io.ErrUnexpectedEOF
}
byteCount := b[0]
if len(b) != 1+1+2+int(byteCount)+1 {
return Record{}, errors.New("data mismatch")
}
var r Record
r.Address = binary.BigEndian.Uint16(b[1:])
r.Code = RecordCode(b[3])
r.Data = b[4 : 4+int(byteCount)]
checkSum := b[4+int(byteCount)]
calculatedCksum := Checksum(b[:len(b)-1])
if calculatedCksum != checkSum {
return Record{}, errors.New("wrong checksum")
}
return r, nil
}

22
region.go Normal file
View file

@ -0,0 +1,22 @@
package go_inthex
type Region struct {
Address uint32
Data []byte
Extra []string
}
func (r *Region) IsContiguousAddress(addr uint32) bool {
return addr == (r.Address + uint32(len(r.Data)))
}
func (r *Region) Append(data []byte) {
r.Data = append(r.Data, data...)
}
type Stream struct {
Regions []Region
StartSegmentAddress uint32
StartLinearAddress uint32
}

8
utils.go Normal file
View file

@ -0,0 +1,8 @@
package go_inthex
func Checksum(data []byte) (sum uint8) {
for i := range data {
sum += data[i]
}
return -sum
}