bindef 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|