bindef 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/LICENSE +21 -0
- data/README.md +85 -0
- data/SYNTAX.md +130 -0
- data/bin/bd +60 -0
- data/lib/bindef.rb +208 -0
- data/lib/bindef/exceptions.rb +19 -0
- data/lib/bindef/extras.rb +12 -0
- data/lib/bindef/extras/ctrl.rb +30 -0
- data/lib/bindef/extras/int128.rb +44 -0
- data/lib/bindef/extras/string.rb +57 -0
- data/lib/bindef/extras/tlv.rb +58 -0
- data/lib/bindef/schemas.rb +33 -0
- data/lib/bindef/version.rb +6 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1b724be52120b58e47c96e9f4b07571290196299c8a2a3c8b09dceee182516ee
|
4
|
+
data.tar.gz: 5d3953bb96c9622dddbd31590931b181fac46c20e942a8b51e23ac34db41d4b5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d1d74f2d7167ec0eac702b3a57daae8e36da2f46b3877578abd5a33e9c7d1779a593ac2f812a704869356b55003c290d5e1592e2d1b1ac32e70a9a56ddce4689
|
7
|
+
data.tar.gz: c19b67e1c2908207e2cd447e38d7943225b804acfb0698c28cf4d31f55f0e197583435964be4c13b737994ab8768fc4e9f3d8430af8e1cbc5fdf656804922462
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private --markup=markdown - *.md LICENSE
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 William Woodruff <william @ yossarian.net>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
bindef
|
2
|
+
========
|
3
|
+
|
4
|
+
`bindef` is a DSL and command-line tool for building binary files.
|
5
|
+
|
6
|
+
It's inspired by [t2b](https://github.com/thosakwe/t2b), but with a few crucial differences:
|
7
|
+
|
8
|
+
* `bindef` scripts run within a Ruby process, making the DSL a strict superset of Ruby
|
9
|
+
* Support for different (and multiple!) endians, string encodings, etc, is baked into the language
|
10
|
+
* Reports common mistakes loudly as warnings (or errors, if severe enough)
|
11
|
+
* Comes with a collection of user-selectable extensions for common binary formats (TLVs, control
|
12
|
+
codes, etc.)
|
13
|
+
|
14
|
+
## Syntax
|
15
|
+
|
16
|
+
`bindef`'s syntax is stream-oriented, with two primary expressions: commands and pragmas.
|
17
|
+
|
18
|
+
Commands cause `bindef` to emit data, while pragmas influence *how* commands act.
|
19
|
+
|
20
|
+
Here's a simple `bindef` script that emits a unsigned 32-bit integer twice, in different endians:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
# `bindef` starts in little-endian, so this is redundant
|
24
|
+
pragma endian: :little
|
25
|
+
|
26
|
+
u32 0xFF000000
|
27
|
+
|
28
|
+
# or `pragma :endian => :big`, if you prefer
|
29
|
+
pragma endian: :big
|
30
|
+
|
31
|
+
u32 0xFF000000
|
32
|
+
```
|
33
|
+
|
34
|
+
The [examples directory](examples/) has more. Read the [SYNTAX](SYNTAX.md) file for a
|
35
|
+
complete listing of commands and pragmas.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
`bindef` is available via RubyGems:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
$ gem install bindef
|
43
|
+
$ bd -h
|
44
|
+
```
|
45
|
+
|
46
|
+
You can also run it directly from this repository:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
$ ruby -Ilib ./bin/bd -h
|
50
|
+
```
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
In general, running a `bindef` script is as simple as:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
$ bd < path/to/input.bd > path/to/output.bin
|
58
|
+
```
|
59
|
+
|
60
|
+
or:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
$ bd -i path/to/input.bd -o /path/to/output.bin
|
64
|
+
```
|
65
|
+
|
66
|
+
You can also choose to enable one or more *extra* command sets via the `-e`, `--extra` flag:
|
67
|
+
|
68
|
+
```bash
|
69
|
+
# extra commands for building TLVs
|
70
|
+
$ bd -e tlv < input.bd > output.bin
|
71
|
+
|
72
|
+
# extra commands for ASCII control codes and string manipulation
|
73
|
+
$ bd -e ctrl,string < input.bd > output.bin
|
74
|
+
```
|
75
|
+
|
76
|
+
## Design goals
|
77
|
+
|
78
|
+
`bindef` should...
|
79
|
+
|
80
|
+
* ...have no runtime dependencies other than the current stable Ruby
|
81
|
+
* ...be easy to read, even without knowledge of Ruby's syntax
|
82
|
+
* ...be easy to write, even without knowledge of Ruby's syntax
|
83
|
+
* ...be easy for other programs to emit without being (too) aware of Ruby's syntax
|
84
|
+
* ...be easy to debug, with warnings and errors for common mistakes (overflows, negative
|
85
|
+
unsigned integers, etc.)
|
data/SYNTAX.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
`bindef` syntax
|
2
|
+
===============
|
3
|
+
|
4
|
+
As mentioned in the [README](README.md), `bindef` has two primary expressions: *commands* and
|
5
|
+
*pragmas*.
|
6
|
+
|
7
|
+
This page documents the default commands and all pragmas.
|
8
|
+
|
9
|
+
## Commands
|
10
|
+
|
11
|
+
`bindef` has commands for emitting integers, floating-point numbers, and strings.
|
12
|
+
|
13
|
+
All commands take a *value*. That value can be literal or a Ruby expression.
|
14
|
+
|
15
|
+
### Integers
|
16
|
+
|
17
|
+
The integer commands map directly to their `stdint.h` types: a `u8` is a `uint8_t`,
|
18
|
+
an `i64` is an `int64_t`, and so on.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# Emits a uint8_t with the value 127.
|
22
|
+
u8 127
|
23
|
+
|
24
|
+
# Emits a int32_t with the value 0.
|
25
|
+
i32 0
|
26
|
+
|
27
|
+
# Produces a warning (negative value used with unsigned command), but still works.
|
28
|
+
u16 -100
|
29
|
+
|
30
|
+
# Produces an error (value too large for command).
|
31
|
+
i16 (2**32) - 1
|
32
|
+
```
|
33
|
+
|
34
|
+
### Floating-point numbers
|
35
|
+
|
36
|
+
There are two floating-point commands: `f32` for single-precision, and `f64` for double-precision.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# Transparent promotion of an integer to a float.
|
40
|
+
f32 0
|
41
|
+
|
42
|
+
# Ruby floating-point constants work..
|
43
|
+
f64 Math::PI
|
44
|
+
f64 Float::INFINITY
|
45
|
+
```
|
46
|
+
|
47
|
+
### Strings
|
48
|
+
|
49
|
+
There is only one string command: `str`. `str` does *not* add a terminating NUL, a newline, or
|
50
|
+
anything else to the specified string.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
str "hello world!"
|
54
|
+
|
55
|
+
# Add a terminating NUL.
|
56
|
+
str "look ma, a C-string!\x00"
|
57
|
+
|
58
|
+
# Ruby's string interpolation works.
|
59
|
+
foo = "bar baz quux"
|
60
|
+
str "#{foo}!\n"
|
61
|
+
```
|
62
|
+
|
63
|
+
## Pragmas
|
64
|
+
|
65
|
+
Pragmas tell `bindef` *how* to emit commands. They tell commands what endianness they should
|
66
|
+
use, what encoding strings are in, and how loud to be about warnings and informational messages.
|
67
|
+
|
68
|
+
Pragmas are global settings, meaning that setting one will cause it to remain set until explicitly
|
69
|
+
changed to another value. This can be tedious to keep track of (and makes it easy to introduce
|
70
|
+
transposition bugs), so the `pragma` expression provides a block form:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
# Changes the verbosity setting to true, but only for the duration of the block.
|
74
|
+
pragma verbose: true do
|
75
|
+
str "foo"
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### `verbose`
|
80
|
+
|
81
|
+
Default: `false`.
|
82
|
+
|
83
|
+
Tells `bindef` how verbose to be. Verbose messages are prefixed with `V: ` and are logged to
|
84
|
+
`stderr`.
|
85
|
+
|
86
|
+
### `warnings`
|
87
|
+
|
88
|
+
Default: `true`.
|
89
|
+
|
90
|
+
Tells `bindef` whether or not to emit warnings, which are nonfatal (unlike errors). Warning
|
91
|
+
messages are prefixed with `W: ` and are logged to `stderr`.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# Or use the block form above.
|
95
|
+
pragma warnings: false
|
96
|
+
u8 -127
|
97
|
+
pragma warnings: true
|
98
|
+
```
|
99
|
+
|
100
|
+
### `endian`
|
101
|
+
|
102
|
+
Default: `:little`.
|
103
|
+
|
104
|
+
Tells `bindef` which endianness to use when emitting integers and floats.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
pragma endian: :big
|
108
|
+
u16 0x00FF
|
109
|
+
u16 0xFF00
|
110
|
+
```
|
111
|
+
|
112
|
+
### `encoding`
|
113
|
+
|
114
|
+
Default: `"utf-8"`
|
115
|
+
|
116
|
+
Tells `bindef` how to encode the strings it emits via `str`.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
pragma encoding: "ascii"
|
120
|
+
str "this is a plain old ASCII string"
|
121
|
+
|
122
|
+
pragma encoding: "utf-8"
|
123
|
+
str "this👏is👏a👏utf-8👏string"
|
124
|
+
|
125
|
+
pragma encoding: "utf-32"
|
126
|
+
str "this is a waste of space"
|
127
|
+
```
|
128
|
+
|
129
|
+
`encoding` expects a lowercase string, and can take any of the
|
130
|
+
[encodings supported by Ruby](https://ruby-doc.org/core/Encoding.html#method-c-name_list).
|
data/bin/bd
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
require "bindef"
|
7
|
+
|
8
|
+
options = {
|
9
|
+
input: STDIN,
|
10
|
+
output: STDOUT,
|
11
|
+
error: STDERR,
|
12
|
+
verbose: false,
|
13
|
+
warnings: true,
|
14
|
+
}
|
15
|
+
|
16
|
+
at_exit do
|
17
|
+
%i[input output error].each { |io| options[io].close }
|
18
|
+
end
|
19
|
+
|
20
|
+
OptionParser.new do |parser|
|
21
|
+
parser.banner = "Usage: bd [options]"
|
22
|
+
|
23
|
+
parser.on "-i", "--input FILE", String, "Read input from FILE (default: stdin)" do |input|
|
24
|
+
abort("Error: No such file: #{input}") unless File.file?(input)
|
25
|
+
options[:input] = File.open(input, "r")
|
26
|
+
end
|
27
|
+
|
28
|
+
parser.on "-o", "--output FILE", String, "Write output to FILE (default: stderr)" do |output|
|
29
|
+
abort("Error: #{output} exists, not overwriting") if File.file?(output)
|
30
|
+
options[:output] = File.open(output, "wb")
|
31
|
+
end
|
32
|
+
|
33
|
+
parser.on "-v", "--verbose", "Write verbose/debugging information to stderr" do
|
34
|
+
options[:verbose] = true
|
35
|
+
end
|
36
|
+
|
37
|
+
parser.on "-W", "--no-warnings", "Suppress warning messages" do
|
38
|
+
options[:warnings] = false
|
39
|
+
end
|
40
|
+
|
41
|
+
parser.on "-e", "--extra ext1,ext2,ext3", Array, "Extra command set(s) to load" do |extras|
|
42
|
+
extras.each { |e| require "bindef/extras/#{e}" }
|
43
|
+
end
|
44
|
+
end.parse!
|
45
|
+
|
46
|
+
bindef = Bindef.new(options[:output],
|
47
|
+
options[:error],
|
48
|
+
verbose: options[:verbose],
|
49
|
+
warnings: options[:warnings])
|
50
|
+
|
51
|
+
options[:error].puts "W: output looks like a tty" if options[:output].tty? && options[:verbose]
|
52
|
+
|
53
|
+
begin
|
54
|
+
bindef.instance_eval options[:input].read, "input", 1
|
55
|
+
rescue Bindef::EvaluationError => e
|
56
|
+
options[:error].puts "E: #{e.message}"
|
57
|
+
# Pop the main and instance_eval scopes off of the backtrace.
|
58
|
+
e.backtrace.pop 2
|
59
|
+
raise e
|
60
|
+
end
|
data/lib/bindef.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "bindef/version"
|
4
|
+
require_relative "bindef/schemas"
|
5
|
+
require_relative "bindef/exceptions"
|
6
|
+
|
7
|
+
# The primary namespace for {Bindef}.
|
8
|
+
class Bindef
|
9
|
+
include Bindef::Schemas
|
10
|
+
|
11
|
+
# @return [Hash] the map of current pragma settings
|
12
|
+
attr_reader :pragmas
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def initialize(output = STDOUT, error = STDERR, verbose: false, warnings: true)
|
16
|
+
@output = output
|
17
|
+
@error = error
|
18
|
+
@pragmas = DEFAULT_PRAGMAS.dup.update(verbose: verbose, warnings: warnings)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Writes a message to the error I/O if verbose mode is enabled.
|
22
|
+
# @note Uses the `:verbose` {#pragma}
|
23
|
+
# @param msg [String] the message to write
|
24
|
+
# @return [void]
|
25
|
+
# @api private
|
26
|
+
def verbose(msg)
|
27
|
+
@error.puts "V: #{msg}" if @pragmas[:verbose]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Writes a warning message to the error I/O.
|
31
|
+
# @param msg [String] the warning message to write
|
32
|
+
# @return [void]
|
33
|
+
# @api private
|
34
|
+
def warning(msg)
|
35
|
+
@error.puts "W: #{msg}" if @pragmas[:warnings]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Ensures that the given integer number can be represented within the given width of bits.
|
39
|
+
# @param num [Integer] the number to test
|
40
|
+
# @param width [Integer] the bit width to test against
|
41
|
+
# @return [void]
|
42
|
+
# @raise [CommandError] if the number is wider than `width` bits
|
43
|
+
# @api private
|
44
|
+
def validate_int_width!(num, width)
|
45
|
+
raise CommandError, "width of #{num} exceeds #{width} bits" if num.bit_length > width
|
46
|
+
end
|
47
|
+
|
48
|
+
# Builds a string containing the given value packed into the given format.
|
49
|
+
# @param value [Object] the value to emit
|
50
|
+
# @param fmt [String] the `Array#pack` format to emit it in
|
51
|
+
def blobify(value, fmt)
|
52
|
+
[value].pack(fmt)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Emits the given blob of data.
|
56
|
+
# @param blob [String] the data to emit
|
57
|
+
# @return [void]
|
58
|
+
# @api private
|
59
|
+
def emit(blob)
|
60
|
+
@output << blob
|
61
|
+
end
|
62
|
+
|
63
|
+
# Captures unknown commands and raises an appropriate error.
|
64
|
+
# @api private
|
65
|
+
def method_missing(*args)
|
66
|
+
raise CommandError, "unknown command: #{args.join(" ")}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Changes the values of the given pragma keys.
|
70
|
+
# @see PRAGMA_SCHEMA
|
71
|
+
# @param hsh [Hash] the keys and values to update the pragma state with
|
72
|
+
# @yield [void] A temporary scope for the pragma changes, if a block is given
|
73
|
+
# @return [void]
|
74
|
+
# @example
|
75
|
+
# pragma verbose: true # changes the `:verbose` pragma to `true` for the remainder of the script
|
76
|
+
# pragma encoding: "utf-16" { str "foobar" } # changes the `:encoding` pragma for the block only
|
77
|
+
def pragma(**hsh)
|
78
|
+
old_pragmas = pragmas.dup
|
79
|
+
|
80
|
+
hsh.each do |key, value|
|
81
|
+
raise PragmaError, "unknown pragma: #{key}" unless @pragmas.key? key
|
82
|
+
raise PragmaError, "bad pragma value: #{value}" unless PRAGMA_SCHEMA[key].include?(value)
|
83
|
+
|
84
|
+
pragmas[key] = value
|
85
|
+
end
|
86
|
+
|
87
|
+
if block_given?
|
88
|
+
yield
|
89
|
+
pragmas.replace old_pragmas
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Emits a string.
|
94
|
+
# @note Uses the `:encoding` {#pragma}
|
95
|
+
# @param string [String] the string to emit
|
96
|
+
# @return [void]
|
97
|
+
def str(string)
|
98
|
+
enc_string = string.encode pragmas[:encoding]
|
99
|
+
blob = blobify enc_string, "a#{enc_string.bytesize}"
|
100
|
+
|
101
|
+
yield blob if block_given?
|
102
|
+
|
103
|
+
emit blob
|
104
|
+
end
|
105
|
+
|
106
|
+
# Emits a `float`.
|
107
|
+
# @param num [Numeric] the number to emit
|
108
|
+
# @return [void]
|
109
|
+
def f32(num)
|
110
|
+
# NOTE: All floats are double-precision in Ruby, so I don't have a good
|
111
|
+
# (read: simple) way to validate single-precision floats yet.
|
112
|
+
|
113
|
+
fmt = pragmas[:endian] == :big ? "g" : "e"
|
114
|
+
blob = blobify num, fmt
|
115
|
+
|
116
|
+
yield blob if block_given?
|
117
|
+
|
118
|
+
emit blob
|
119
|
+
end
|
120
|
+
|
121
|
+
# Emits a `double`.
|
122
|
+
# @param num [Numeric] the number to emit
|
123
|
+
# @return [void]
|
124
|
+
def f64(num)
|
125
|
+
raise CommandError, "#{num} is an invalid double-precision float" if num.to_f.nan?
|
126
|
+
|
127
|
+
fmt = pragmas[:endian] == :big ? "G" : "E"
|
128
|
+
|
129
|
+
blob = blobify num, fmt
|
130
|
+
|
131
|
+
yield blob if block_given?
|
132
|
+
|
133
|
+
emit blob
|
134
|
+
end
|
135
|
+
|
136
|
+
# Emits a `uint8_t`.
|
137
|
+
# @param num [Integer] the number to emit
|
138
|
+
# @return [void]
|
139
|
+
def u8(num)
|
140
|
+
warning "#{num} in u8 command is negative" if num.negative?
|
141
|
+
validate_int_width! num, 8
|
142
|
+
|
143
|
+
blob = blobify num, "C"
|
144
|
+
|
145
|
+
yield blob if block_given?
|
146
|
+
|
147
|
+
emit blob
|
148
|
+
end
|
149
|
+
|
150
|
+
# Emits a `int8_t`.
|
151
|
+
# @param num [Integer] the number to emit
|
152
|
+
# @return [void]
|
153
|
+
def i8(num)
|
154
|
+
validate_int_width! num, 8
|
155
|
+
|
156
|
+
blob = blobify num, "c"
|
157
|
+
|
158
|
+
yield blob if block_given?
|
159
|
+
|
160
|
+
emit blob
|
161
|
+
end
|
162
|
+
|
163
|
+
# @!method u16(num)
|
164
|
+
# Emits a `uint16_t`.
|
165
|
+
# @note Uses the `:endian` {#pragma}
|
166
|
+
# @param num [Integer] the number to emit
|
167
|
+
# @return [void]
|
168
|
+
# @!method u32(num)
|
169
|
+
# Emits a `uint32_t`.
|
170
|
+
# @note Uses the `:endian` {#pragma}
|
171
|
+
# @param num [Integer] the number to emit
|
172
|
+
# @return [void]
|
173
|
+
# @!method u64(num)
|
174
|
+
# Emits a `uint64_t`.
|
175
|
+
# @note Uses the `:endian` {#pragma}
|
176
|
+
# @param num [Integer] the number to emit
|
177
|
+
# @return [void]
|
178
|
+
# @!method i16(num)
|
179
|
+
# Emits a `int16_t`.
|
180
|
+
# @note Uses the `:endian` {#pragma}
|
181
|
+
# @param num [Integer] the number to emit
|
182
|
+
# @return [void]
|
183
|
+
# @!method i32(num)
|
184
|
+
# Emits a `int32_t`.
|
185
|
+
# @note Uses the `:endian` {#pragma}
|
186
|
+
# @param num [Integer] the number to emit
|
187
|
+
# @return [void]
|
188
|
+
# @!method i64(num)
|
189
|
+
# Emits a `int64_t`.
|
190
|
+
# @note Uses the `:endian` {#pragma}
|
191
|
+
# @param num [Integer] the number to emit
|
192
|
+
# @return [void]
|
193
|
+
ENDIANDED_INTEGER_COMMAND_MAP.each do |cmd, fmt|
|
194
|
+
define_method cmd do |num, &block|
|
195
|
+
warning "#{num} in #{cmd} command is negative" if num.negative? && cmd[0] == "u"
|
196
|
+
validate_int_width! num, cmd[1..-1].to_i
|
197
|
+
|
198
|
+
end_fmt = pragmas[:endian] == :big ? "#{fmt}>" : "#{fmt}<"
|
199
|
+
|
200
|
+
blob = blobify num, end_fmt
|
201
|
+
|
202
|
+
# Fun fact: You can't use `yield` in `define_method`.
|
203
|
+
block&.call(blob)
|
204
|
+
|
205
|
+
emit blob
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bindef
|
4
|
+
# The base exception for all {Bindef} errors.
|
5
|
+
class BindefError < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Raised during an error in evaluation.
|
9
|
+
class EvaluationError < BindefError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raised during an error in pragma evaluation.
|
13
|
+
class PragmaError < EvaluationError
|
14
|
+
end
|
15
|
+
|
16
|
+
# Raised during an error in command evaluation.
|
17
|
+
class CommandError < EvaluationError
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bindef
|
4
|
+
module Extras
|
5
|
+
# Helpers for {Extras::Ctrl}.
|
6
|
+
module CtrlHelper
|
7
|
+
# A sequential list of symbolic names for control codes.
|
8
|
+
# @api private
|
9
|
+
CONTROL_NAMES = %i[
|
10
|
+
nul soh stx etx eot enq ack bel bs ht lf vt ff cr so si dle dc1 dc2 dc3 dc4 nak syn etb
|
11
|
+
can em sub esc fs gs rs us
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# A mapping of symbolic names for control codes to their numeric values.
|
15
|
+
# @api private
|
16
|
+
CONTROL_MAP = CONTROL_NAMES.zip(0x00..0x1F).to_h.freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
# Potentially useful control character commands.
|
20
|
+
module Ctrl
|
21
|
+
CtrlHelper::CONTROL_MAP.each do |cmd, value|
|
22
|
+
define_method cmd do
|
23
|
+
u8 value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
include Extras::Ctrl
|
30
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bindef
|
4
|
+
module Extras
|
5
|
+
# Potentially useful 128-bit integer emission commands.
|
6
|
+
module Int128
|
7
|
+
# Emits a `__uint128_t`.
|
8
|
+
# @note Uses the `:endian` {Bindef#pragma}
|
9
|
+
# @param num [Integer] the number to emit
|
10
|
+
# @return [void]
|
11
|
+
def u128(num)
|
12
|
+
upper = num >> 64
|
13
|
+
lower = num & (2**64 - 1)
|
14
|
+
|
15
|
+
if pragmas[:endian] == big
|
16
|
+
u64 upper
|
17
|
+
u64 lower
|
18
|
+
else
|
19
|
+
u64 lower
|
20
|
+
u64 upper
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Emits a `__int128_t`.
|
25
|
+
# @note Uses the `:endian` {Bindef#pragma}
|
26
|
+
# @param num [Integer] the number to emit
|
27
|
+
# @return [void]
|
28
|
+
def i128(num)
|
29
|
+
upper = num >> 64
|
30
|
+
lower = num & (2**64 - 1)
|
31
|
+
|
32
|
+
if pragmas[:endian] == big
|
33
|
+
i64 upper
|
34
|
+
u64 lower
|
35
|
+
else
|
36
|
+
u64 lower
|
37
|
+
i64 upper
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
include Extras::Int128
|
44
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bindef
|
4
|
+
module Extras
|
5
|
+
# Potentially useful extra string emission commands.
|
6
|
+
module String
|
7
|
+
# Emits a null-terminated string.
|
8
|
+
# @note Like {Bindef#str}, uses the `:encoding` {Bindef#pragma}
|
9
|
+
# @param string [String] the string to emit
|
10
|
+
# @return [void]
|
11
|
+
# @example
|
12
|
+
# strz "foobar" # Emits "foobar\x00"
|
13
|
+
def strz(string)
|
14
|
+
str string
|
15
|
+
str "\0"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Emits a string, NUL-padded up to the given length in bytes.
|
19
|
+
# @note Like {Bindef#str}, uses the `:encoding` {Bindef#pragma}
|
20
|
+
# @param string [String] the string to emit
|
21
|
+
# @param maxpad [Integer] the maximum number of padding NULs
|
22
|
+
# @return [void]
|
23
|
+
# @example
|
24
|
+
# strnz "foo", 5 # Emits "foo\x00\x00"
|
25
|
+
def strnz(string, maxpad)
|
26
|
+
pad = maxpad
|
27
|
+
|
28
|
+
str string do |enc_string|
|
29
|
+
pad = maxpad - enc_string.bytesize
|
30
|
+
end
|
31
|
+
|
32
|
+
raise CommandError, "maxpad < encoded string len" if pad.negative?
|
33
|
+
|
34
|
+
# Reset our encoding temporarily, to make sure we emit the right number of NULs.
|
35
|
+
pragma encoding: "utf-8" do
|
36
|
+
str("\x00" * pad)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Emits a length-prefixed string.
|
41
|
+
# @note Like {Bindef#str}, uses the `:encoding` {Bindef#pragma}
|
42
|
+
# @note Like {Bindef#u16} and wider, the `:endian` {Bindef#pragma}
|
43
|
+
# @param int_fmt [Symbol] the width of the length prefix, as one of the integer commands
|
44
|
+
# @param string [String] the string to emit
|
45
|
+
# @return [void]
|
46
|
+
# @example
|
47
|
+
# lstr :u8, "foo" # Emits "\x03foo"
|
48
|
+
def lstr(int_fmt, string)
|
49
|
+
str string do |enc_string|
|
50
|
+
send int_fmt, enc_string.bytesize
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
include Extras::String
|
57
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bindef
|
4
|
+
module Extras
|
5
|
+
# Potentially useful TLV (tag-length-value) commands.
|
6
|
+
#
|
7
|
+
# Writing a single sufficiently generic TLV command without
|
8
|
+
# really abusing Ruby's syntax is hard. Instead, we provide
|
9
|
+
# specialized commands for empirically common TLV widths.
|
10
|
+
module TLV
|
11
|
+
# Emit a `uint8_t` type, a `uint8_t` length, and a value.
|
12
|
+
# @param type [Integer] the type number
|
13
|
+
# @param hsh [Hash] a mapping of `command => value`
|
14
|
+
# @example
|
15
|
+
# tlv_u8 1, u32: 0xFF00FF00 # Emits: "\x01\x04\xFF\x00\xFF\x00"
|
16
|
+
# tlv_u8 2, str: "hello" # Emits: "\x02\x05hello"
|
17
|
+
def tlv_u8(type, hsh)
|
18
|
+
u8 type
|
19
|
+
|
20
|
+
cmd, value = hsh.shift
|
21
|
+
|
22
|
+
send cmd, value do |blob|
|
23
|
+
u8 blob.bytesize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Emit a `uint16_t` type, a `uint16_t` length, and a value.
|
28
|
+
# @param type [Integer] the type number
|
29
|
+
# @param hsh [Hash] a mapping of `command => value`
|
30
|
+
# @see tlv_u8
|
31
|
+
def tlv_u16(type, hsh)
|
32
|
+
u16 type
|
33
|
+
|
34
|
+
cmd, value = hsh.shift
|
35
|
+
|
36
|
+
send cmd, value do |blob|
|
37
|
+
u16 blob.bytesize
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Emit a `uint32_t` type, a `uint32_t` length, and a value.
|
42
|
+
# @param type [Integer] the type number
|
43
|
+
# @param hsh [Hash] a mapping of `command => value`
|
44
|
+
# @see tlv_u8
|
45
|
+
def tlv_u32(type, hsh)
|
46
|
+
u32 type
|
47
|
+
|
48
|
+
cmd, value = hsh.shift
|
49
|
+
|
50
|
+
send cmd, value do |blob|
|
51
|
+
u32 blob.bytesize
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
include Extras::TLV
|
58
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bindef
|
4
|
+
# Schemas used to validate commands and pragmas throughout {Bindef}.
|
5
|
+
module Schemas
|
6
|
+
# A mapping of valid pragma keys to lists of valid pragma values.
|
7
|
+
PRAGMA_SCHEMA = {
|
8
|
+
verbose: [true, false],
|
9
|
+
warnings: [true, false],
|
10
|
+
endian: %i[big little],
|
11
|
+
encoding: Encoding.name_list.map(&:downcase),
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
# The default pragma settings.
|
15
|
+
DEFAULT_PRAGMAS = {
|
16
|
+
verbose: false,
|
17
|
+
warnings: true,
|
18
|
+
endian: :little,
|
19
|
+
encoding: "utf-8",
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# A map of endianded integer commands to `Array#pack` formats.
|
23
|
+
# @api private
|
24
|
+
ENDIANDED_INTEGER_COMMAND_MAP = {
|
25
|
+
u16: "S",
|
26
|
+
u32: "L",
|
27
|
+
u64: "Q",
|
28
|
+
i16: "s",
|
29
|
+
i32: "l",
|
30
|
+
i64: "q",
|
31
|
+
}.freeze
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bindef
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- William Woodruff
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.9
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.9
|
27
|
+
description:
|
28
|
+
email: william@yossarian.net.com
|
29
|
+
executables:
|
30
|
+
- bd
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".yardopts"
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- SYNTAX.md
|
38
|
+
- bin/bd
|
39
|
+
- lib/bindef.rb
|
40
|
+
- lib/bindef/exceptions.rb
|
41
|
+
- lib/bindef/extras.rb
|
42
|
+
- lib/bindef/extras/ctrl.rb
|
43
|
+
- lib/bindef/extras/int128.rb
|
44
|
+
- lib/bindef/extras/string.rb
|
45
|
+
- lib/bindef/extras/tlv.rb
|
46
|
+
- lib/bindef/schemas.rb
|
47
|
+
- lib/bindef/version.rb
|
48
|
+
homepage:
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 2.3.0
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.7.6
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: bindef - A DSL and command-line tool for generating binary files
|
72
|
+
test_files: []
|