Initial commit
This commit is contained in:
commit
a3860c8a97
19
LICENSE
Normal file
19
LICENSE
Normal 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
2
README.md
Normal 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
80
decode.go
Normal 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
99
encode.go
Normal 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
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module git.gammaspectra.live/WeebDataHoarder/go-inthex
|
||||
|
||||
go 1.21
|
79
record.go
Normal file
79
record.go
Normal 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
22
region.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue