laymanshex/laymanshex.go
gutmet 69114e6782 make sure set bytes are not longer than byte field
* also little formatting issues
2020-04-27 17:58:34 +02:00

398 lines
10 KiB
Go

package main
import (
"bufio"
"encoding/binary"
"encoding/hex"
"errors"
"flag"
"fmt"
"io"
"os"
"strconv"
"strings"
"time"
)
func optPanic(msg string, err error) {
if err != nil {
panic(errors.New(msg + ": " + err.Error()))
}
}
type handling struct {
bytes int64
readFrom func(io.Reader, binary.ByteOrder) string
writeTo func(io.Writer, binary.ByteOrder, string)
}
func binaryRead(r io.Reader, order binary.ByteOrder, pointerTo interface{}) {
err := binary.Read(r, order, pointerTo)
optPanic(fmt.Sprintf("binary conversion failed (%T)", pointerTo), err)
}
func binaryWrite(w io.Writer, order binary.ByteOrder, pointerTo interface{}) {
err := binary.Write(w, order, pointerTo)
optPanic(fmt.Sprintf("binary write failed (%T)", pointerTo), err)
}
var handlings = map[string]handling{
"int8": handling{1,
func(r io.Reader, bo binary.ByteOrder) string {
var i int8
binaryRead(r, bo, &i)
return strconv.FormatInt(int64(i), 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseInt(s, 0, 8)
optPanic("conversion of "+s+" to int8 failed", err)
binaryWrite(w, bo, int8(i))
}},
"uint8": handling{1,
func(r io.Reader, bo binary.ByteOrder) string {
var i uint8
binaryRead(r, bo, &i)
return strconv.FormatUint(uint64(i), 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseUint(s, 0, 8)
optPanic("conversion of "+s+" to uint8 failed", err)
binaryWrite(w, bo, uint8(i))
}},
"int16": handling{2,
func(r io.Reader, bo binary.ByteOrder) string {
var i int16
binaryRead(r, bo, &i)
return strconv.FormatInt(int64(i), 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseInt(s, 0, 16)
optPanic("conversion of "+s+" to int16 failed", err)
binaryWrite(w, bo, int16(i))
}},
"uint16": handling{2,
func(r io.Reader, bo binary.ByteOrder) string {
var i uint16
binaryRead(r, bo, &i)
return strconv.FormatUint(uint64(i), 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseUint(s, 0, 16)
optPanic("conversion of "+s+" to uint16 failed", err)
binaryWrite(w, bo, uint16(i))
}},
"int32": handling{4,
func(r io.Reader, bo binary.ByteOrder) string {
var i int32
binaryRead(r, bo, &i)
return strconv.FormatInt(int64(i), 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseInt(s, 0, 32)
optPanic("conversion of "+s+" to int32 failed", err)
binaryWrite(w, bo, int32(i))
}},
"uint32": handling{4,
func(r io.Reader, bo binary.ByteOrder) string {
var i uint32
binaryRead(r, bo, &i)
return strconv.FormatUint(uint64(i), 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseUint(s, 0, 32)
optPanic("conversion of "+s+" to uint32 failed", err)
binaryWrite(w, bo, uint32(i))
}},
"int64": handling{8,
func(r io.Reader, bo binary.ByteOrder) string {
var i int64
binaryRead(r, bo, &i)
return strconv.FormatInt(i, 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseInt(s, 0, 64)
optPanic("conversion of "+s+" to int64 failed", err)
binaryWrite(w, bo, i)
}},
"uint64": handling{8,
func(r io.Reader, bo binary.ByteOrder) string {
var i uint64
binaryRead(r, bo, &i)
return strconv.FormatUint(i, 10)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
i, err := strconv.ParseUint(s, 0, 64)
optPanic("conversion of "+s+" to uint64 failed", err)
binaryWrite(w, bo, i)
}},
"float32": handling{4,
func(r io.Reader, bo binary.ByteOrder) string {
var f float32
binaryRead(r, bo, &f)
return strconv.FormatFloat(float64(f), 'f', -1, 64)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
f, err := strconv.ParseFloat(s, 32)
optPanic("conversion of "+s+" to float32 failed", err)
binaryWrite(w, bo, float32(f))
}},
"float64": handling{8,
func(r io.Reader, bo binary.ByteOrder) string {
var f float64
binaryRead(r, bo, &f)
return strconv.FormatFloat(f, 'f', -1, 64)
},
func(w io.Writer, bo binary.ByteOrder, s string) {
f, err := strconv.ParseFloat(s, 64)
optPanic("conversion of "+s+" to float64 failed", err)
binaryWrite(w, bo, float64(f))
}},
}
type filePart struct {
name string
parttype string
handling handling
}
func (part *filePart) Line() string {
return part.name + " : " + part.parttype
}
func (part *filePart) setHandling() {
if strings.HasPrefix(part.parttype, "byte") {
tmp := strings.TrimRight(strings.TrimLeft(part.parttype, "byte["), "]")
bytes, err := strconv.ParseInt(tmp, 10, 64)
optPanic(part.name, err)
readFrom := func(r io.Reader, bo binary.ByteOrder) string {
buf := make([]byte, bytes)
var i int64
for i = 0; i < bytes; i++ {
binaryRead(r, bo, &buf[i])
}
return hex.EncodeToString(buf)
}
writeTo := func(w io.Writer, bo binary.ByteOrder, s string) {
buf, err := hex.DecodeString(s)
optPanic("invalid string of hex values: "+s, err)
if int64(len(buf)) > bytes {
panic(fmt.Sprintf("string %s is too long for byte array %s : want %d bytes", s, part.name, bytes))
}
binaryWrite(w, bo, buf)
}
part.handling = handling{bytes, readFrom, writeTo}
} else {
if handling, ok := handlings[part.parttype]; !ok {
panic(part.Line() + ": Don't know type")
} else {
part.handling = handling
}
}
}
type fileDescription struct {
byteOrder binary.ByteOrder
parts []filePart
}
func trim(s string) string {
return strings.TrimSpace(s)
}
var debug bool
func info(i string, s string) {
if debug {
fmt.Fprintln(os.Stderr, i, "-->", s)
}
}
func readPart(line string) *filePart {
part := &filePart{}
splitline := strings.Split(line, ":")
if len(splitline) != 2 {
panic("invalid definition: " + line)
}
part.name = trim(splitline[0])
part.parttype = trim(splitline[1])
info("definition", part.Line())
part.setHandling()
return part
}
func readFileDescription(path string) fileDescription {
errmsg := "read format file"
descr := fileDescription{}
f, err := os.Open(path)
defer f.Close()
optPanic("open format file", err)
buf := bufio.NewReader(f)
/*----------------------------------*/
line, err := buf.ReadString('\n')
optPanic(errmsg, err)
line = trim(line)
for strings.HasPrefix(line, "#") {
info("comment", line)
line, err = buf.ReadString('\n')
optPanic(errmsg, err)
line = trim(line)
}
/*----------------------------------*/
line = trim(line)
if line == "big endian" {
descr.byteOrder = binary.BigEndian
} else if line == "little endian" {
descr.byteOrder = binary.LittleEndian
} else {
panic(errmsg + ": expected endianness ('little endian' or 'big endian'), got :" + line)
}
info("endianness", line)
/*----------------------------------*/
line, err = buf.ReadString('\n')
optPanic(errmsg, err)
line = trim(line)
for err != io.EOF && line != "" {
part := readPart(line)
descr.parts = append(descr.parts, *part)
line, err = buf.ReadString('\n')
line = trim(line)
}
line = trim(line)
if err == io.EOF && line != "" {
descr.parts = append(descr.parts, *readPart(line))
} else if err != io.EOF {
optPanic(errmsg, err)
}
return descr
}
func readAssignments(descr fileDescription, s string) map[string]string {
assignments := strings.Split(s, ",")
m := make(map[string]string)
for _, assignment := range assignments {
kv := strings.Split(assignment, "=")
if len(kv) != 2 {
panic("weird parameters in set: " + assignment + "\nWant list of comma-separated key=value assignments")
}
key := trim(kv[0])
value := trim(kv[1])
found := false
for _, part := range descr.parts {
if key == part.name {
found = true
}
}
if found {
m[key] = value
} else {
panic("unknown key: " + key)
}
}
return m
}
func backup(binpath string) {
bin, err := os.Open(binpath)
defer bin.Close()
optPanic("failed to open binary", err)
timestamp := time.Now().Format("20060102150405")
fi, err := bin.Stat()
optPanic("failed to get permissions of binary", err)
backuppath := binpath + ".bak" + timestamp
backup, err := os.OpenFile(backuppath, os.O_RDWR|os.O_CREATE, fi.Mode())
defer backup.Close()
_, err = io.Copy(backup, bin)
optPanic("failed to create backup", err)
backup.Chmod(fi.Mode())
fmt.Println("Created backup " + backuppath + "\n")
}
func seekErr(i int, name string, inner error) string {
if inner != nil {
namepart := ""
if name != "" {
namepart = " name: " + name
}
return fmt.Sprintf("could not advance in file (stuck at part %d%s)", i, namepart)
} else {
return ""
}
}
func setValues(descr fileDescription, binpath string, vals map[string]string) {
backup(binpath)
f, err := os.OpenFile(binpath, os.O_RDWR, 0666)
optPanic("failed to write-open binary", err)
defer f.Close()
for i, part := range descr.parts {
needSeek := true
if part.name != "" {
if val, ok := vals[part.name]; ok {
part.handling.writeTo(f, descr.byteOrder, val)
needSeek = false
}
}
if needSeek {
_, err = f.Seek(part.handling.bytes, 1)
optPanic(seekErr(i, part.name, err), err)
}
}
}
func getValues(descr fileDescription, binpath string) map[string]string {
vals := make(map[string]string)
f, err := os.Open(binpath)
defer f.Close()
optPanic("failed to open binary", err)
for i, part := range descr.parts {
if part.name == "" {
_, err = f.Seek(part.handling.bytes, 1)
optPanic(seekErr(i, part.name, err), err)
} else {
vals[part.name] = part.handling.readFrom(f, descr.byteOrder)
}
}
return vals
}
func fmtPadding(descr fileDescription) string {
pad := 0
for _, part := range descr.parts {
if n := len(part.name); n > pad {
pad = n
}
}
return strconv.Itoa(pad)
}
func printValues(descr fileDescription, vals map[string]string) {
for _, part := range descr.parts {
if part.name != "" {
fmt.Printf("%"+fmtPadding(descr)+"s = %s\n", part.name, vals[part.name])
}
}
}
func printUsage() {
fmt.Fprintf(os.Stderr, "Usage: %s [Options] FORMATFILE BINARY\n", os.Args[0])
flag.PrintDefaults()
}
func main() {
var assignments string
flag.StringVar(&assignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")")
flag.BoolVar(&debug, "debug", false, "print recognized parts of the format file")
flag.Parse()
args := flag.Args()
if len(args) != 2 {
printUsage()
os.Exit(-1)
}
descr := readFileDescription(args[0])
if assignments != "" {
setValues(descr, args[1], readAssignments(descr, assignments))
}
printValues(descr, getValues(descr, args[1]))
}