allow variable byte field size

* sequences of certain sizes or delimited with a marker can now
  be obtained through multiple invocations
This commit is contained in:
gutmet 2020-04-27 19:33:47 +02:00
parent 88c7d63adc
commit 279429d0bf
2 changed files with 128 additions and 52 deletions

50
README
View File

@ -22,32 +22,56 @@ comment = '#' { LETTER | DIGIT } '\n'.
endianness = ("little endian" | "big endian") '\n'. endianness = ("little endian" | "big endian") '\n'.
definition = name ':' type '\n'. definition = [ IDENT ] ':' type '\n'.
name = { LETTER | DIGIT }. type = bytefield
type = ( "byte[" INTEGER "]" )
| int8 | int16 | int32 | int64 | int8 | int16 | int32 | int64
| uint8 | uint16 | uint32 | uint64 | uint8 | uint16 | uint32 | uint64
| float32 | float64 | float32 | float64
.
bytefield = "byte[" ( INTEGER | IDENT ) "]".
``` ```
The name of a definition line can be left empty to ignore that particular value. The identifier of a definition line can be left empty to ignore that particular value.
Portions of the binary file not covered be the file description are ignored. Portions of the binary file not covered by the file description are ignored. Byte field
size can be variable - the specific value then needs to be set at program invocation (via -fvar).
Usage Usage
----- -----
To get values: laymanshex FORMATFILE BINARY To get values: `laymanshex FORMATFILE BINARY`
To set values: laymanshex -set "key1=value1,key2=value2" FORMATFILE BINARY To set values: `laymanshex -set "key1=value1,key2=value2" FORMATFILE BINARY`
To set a variable 'offset' to 113 in the file description and get values: `laymanshex -fvar="offset=113" FORMATFILE BINARY`
Examples of format descriptions can be found in the [laymanshex-files repo](/laymanshex-files/). Examples of format descriptions can be found in the [laymanshex-files repo](/laymanshex-files/).
TODO
----
There is no way to express offset values inside a file yet. Since this is Advanced
relatively common, I will add it at some point. Also: Repeated data limited by --------
marker or size field.
The variable byte field size allows to script around laymanshex and use a sliding frame
to obtain sequences. E.g. you can define a partial file format that only covers one
element of the sequence at a certain offset:
```
little endian
: byte[offset]
value : int32
```
Script:
```
#/bin/bash
for i in `seq 99`; do
laymanshex -fvar="offset=$((i*4))" FORMATFILE BINARY
done
```
If your sequence ends with a marker, add that marker to the file description and check
its value after each invocation of laymanshex.

View File

@ -159,10 +159,24 @@ func (part *filePart) Line() string {
return part.name + " : " + part.parttype return part.name + " : " + part.parttype
} }
func (part *filePart) setHandling() { type formatAssignment struct {
if strings.HasPrefix(part.parttype, "byte") { val int64
tmp := strings.TrimRight(strings.TrimLeft(part.parttype, "byte["), "]") used bool
bytes, err := strconv.ParseInt(tmp, 10, 64) }
func (part *filePart) setByteFieldHandling(assignments map[string]*formatAssignment) {
tmp := trim(strings.TrimSuffix(strings.TrimPrefix(part.parttype, "byte["), "]"))
var bytes int64
var err error
if bytes, err = strconv.ParseInt(tmp, 10, 64); err != nil {
if ass, ok := assignments[tmp]; ok {
bytes = ass.val
ass.used = true
err = nil
} else {
err = errors.New("Could neither parse size to int64 nor obtain value from -fvar")
}
}
optPanic(part.name, err) optPanic(part.name, err)
readFrom := func(r io.Reader, bo binary.ByteOrder) string { readFrom := func(r io.Reader, bo binary.ByteOrder) string {
buf := make([]byte, bytes) buf := make([]byte, bytes)
@ -182,6 +196,11 @@ func (part *filePart) setHandling() {
binaryWrite(w, bo, buf) binaryWrite(w, bo, buf)
} }
part.handling = handling{bytes, readFrom, writeTo} part.handling = handling{bytes, readFrom, writeTo}
}
func (part *filePart) setHandling(assignments map[string]*formatAssignment) {
if strings.HasPrefix(part.parttype, "byte") {
part.setByteFieldHandling(assignments)
} else { } else {
if handling, ok := handlings[part.parttype]; !ok { if handling, ok := handlings[part.parttype]; !ok {
@ -209,7 +228,7 @@ func info(i string, s string) {
} }
} }
func readPart(line string) *filePart { func readPart(line string, assignments map[string]*formatAssignment) *filePart {
part := &filePart{} part := &filePart{}
splitline := strings.Split(line, ":") splitline := strings.Split(line, ":")
if len(splitline) != 2 { if len(splitline) != 2 {
@ -218,11 +237,19 @@ func readPart(line string) *filePart {
part.name = trim(splitline[0]) part.name = trim(splitline[0])
part.parttype = trim(splitline[1]) part.parttype = trim(splitline[1])
info("definition", part.Line()) info("definition", part.Line())
part.setHandling() part.setHandling(assignments)
return part return part
} }
func readFileDescription(path string) fileDescription { func checkFormatAssignments(assignments map[string]*formatAssignment) {
for name, ass := range assignments {
if !ass.used {
panic("assignment to unknown variable: " + name)
}
}
}
func readFileDescription(path string, assignments map[string]*formatAssignment) fileDescription {
errmsg := "read format file" errmsg := "read format file"
descr := fileDescription{} descr := fileDescription{}
f, err := os.Open(path) f, err := os.Open(path)
@ -254,39 +281,62 @@ func readFileDescription(path string) fileDescription {
optPanic(errmsg, err) optPanic(errmsg, err)
line = trim(line) line = trim(line)
for err != io.EOF && line != "" { for err != io.EOF && line != "" {
part := readPart(line) part := readPart(line, assignments)
descr.parts = append(descr.parts, *part) descr.parts = append(descr.parts, *part)
line, err = buf.ReadString('\n') line, err = buf.ReadString('\n')
line = trim(line) line = trim(line)
} }
line = trim(line) line = trim(line)
if err == io.EOF && line != "" { if err == io.EOF && line != "" {
descr.parts = append(descr.parts, *readPart(line)) descr.parts = append(descr.parts, *readPart(line, assignments))
} else if err != io.EOF { } else if err != io.EOF {
optPanic(errmsg, err) optPanic(errmsg, err)
} }
/*--------------------------------*/
checkFormatAssignments(assignments)
return descr return descr
} }
func readAssignments(descr fileDescription, s string) map[string]string { func readAssignments(name string, s string) map[string]string {
assignments := strings.Split(s, ",")
m := make(map[string]string) m := make(map[string]string)
s = trim(s)
if s == "" {
return m
}
assignments := strings.Split(s, ",")
for _, assignment := range assignments { for _, assignment := range assignments {
kv := strings.Split(assignment, "=") kv := strings.Split(assignment, "=")
if len(kv) != 2 { if len(kv) != 2 {
panic("weird parameters in set: " + assignment + "\nWant list of comma-separated key=value assignments") panic("weird parameters in " + name + ": " + assignment + "\nWant list of comma-separated key=value assignments")
} }
key := trim(kv[0]) key := trim(kv[0])
value := trim(kv[1]) value := trim(kv[1])
m[key] = value
}
return m
}
func readFormatAssignments(s string) map[string]*formatAssignment {
tmp := readAssignments("fvar", s)
m := make(map[string]*formatAssignment)
for key, val := range tmp {
i, err := strconv.ParseInt(val, 0, 64)
optPanic("Could not parse format variable assignment "+val, err)
m[key] = &formatAssignment{i, false}
}
return m
}
func readSetAssignments(descr fileDescription, s string) map[string]string {
m := readAssignments("set", s)
for key, _ := range m {
found := false found := false
for _, part := range descr.parts { for _, part := range descr.parts {
if key == part.name { if key == part.name {
found = true found = true
} }
} }
if found { if !found {
m[key] = value
} else {
panic("unknown key: " + key) panic("unknown key: " + key)
} }
} }
@ -381,8 +431,10 @@ func printUsage() {
} }
func main() { func main() {
var assignments string var setAssignments string
flag.StringVar(&assignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")") var formatAssignments string
flag.StringVar(&setAssignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")")
flag.StringVar(&formatAssignments, "fvar", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")")
flag.BoolVar(&debug, "debug", false, "print recognized parts of the format file") flag.BoolVar(&debug, "debug", false, "print recognized parts of the format file")
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
@ -390,9 +442,9 @@ func main() {
printUsage() printUsage()
os.Exit(-1) os.Exit(-1)
} }
descr := readFileDescription(args[0]) descr := readFileDescription(args[0], readFormatAssignments(formatAssignments))
if assignments != "" { if setAssignments != "" {
setValues(descr, args[1], readAssignments(descr, assignments)) setValues(descr, args[1], readSetAssignments(descr, setAssignments))
} }
printValues(descr, getValues(descr, args[1])) printValues(descr, getValues(descr, args[1]))
} }