iostruct 0.4.0 → 0.6.0
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 +4 -4
- data/.rubocop.yml +68 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +49 -9
- data/README.md +247 -6
- data/Rakefile +3 -1
- data/iostruct.gemspec +8 -9
- data/lib/iostruct/hash_fmt.rb +92 -0
- data/lib/iostruct/pack_fmt.rb +68 -0
- data/lib/iostruct/version.rb +1 -1
- data/lib/iostruct.rb +122 -98
- data/spec/.rubocop.yml +27 -0
- data/spec/hash_fmt_spec.rb +215 -0
- data/spec/iostruct_spec.rb +271 -16
- metadata +12 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a36ab3a3a2de5e2cb4f94175231aa664be8cee2c5361d83cc2f62def71f2b067
|
|
4
|
+
data.tar.gz: 2604c514757df2287d4652c0929ebfc09b1d54cd281599b6e7f52fd2d7f270d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e99bb3cdd28c0f1283332470cf1e88aca1c425d79545a28d448bde831630e272dca6a5eda4d88ea869482e21c9d7e2adf80d61ee62661fa4d2a09420bad82f36
|
|
7
|
+
data.tar.gz: 4bf4896671e0a04675210468855b5efa5ec0df22378ac4bad1bef3a193080be8f908fc571bdcc3325c1dc4b7d5d50c10dd98ac213376a5f1d4ed396fdd85c247
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rake
|
|
3
|
+
- rubocop-rspec
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
AllCops:
|
|
7
|
+
NewCops: enable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Gemspec/RequiredRubyVersion:
|
|
11
|
+
Enabled: false
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Layout/LineLength:
|
|
15
|
+
Max: 140
|
|
16
|
+
|
|
17
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
|
18
|
+
Enabled: false
|
|
19
|
+
|
|
20
|
+
Layout/SpaceInsideParens:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Metrics/AbcSize:
|
|
25
|
+
Enabled: false
|
|
26
|
+
|
|
27
|
+
Metrics/CyclomaticComplexity:
|
|
28
|
+
Enabled: false
|
|
29
|
+
|
|
30
|
+
Metrics/BlockLength:
|
|
31
|
+
Max: 50
|
|
32
|
+
|
|
33
|
+
Metrics/MethodLength:
|
|
34
|
+
Max: 55
|
|
35
|
+
|
|
36
|
+
Metrics/ParameterLists:
|
|
37
|
+
Enabled: false
|
|
38
|
+
|
|
39
|
+
Metrics/PerceivedComplexity:
|
|
40
|
+
Max: 18
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Style/CommentedKeyword:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
Style/Documentation:
|
|
47
|
+
Enabled: false
|
|
48
|
+
|
|
49
|
+
Style/FormatString:
|
|
50
|
+
Enabled: false
|
|
51
|
+
|
|
52
|
+
Style/NumericLiterals:
|
|
53
|
+
Enabled: false
|
|
54
|
+
|
|
55
|
+
Style/NumericPredicate:
|
|
56
|
+
Enabled: false
|
|
57
|
+
|
|
58
|
+
Style/StringConcatenation:
|
|
59
|
+
Enabled: false
|
|
60
|
+
|
|
61
|
+
Style/StringLiterals:
|
|
62
|
+
Enabled: false
|
|
63
|
+
|
|
64
|
+
Style/TrailingCommaInArguments:
|
|
65
|
+
Enabled: false
|
|
66
|
+
|
|
67
|
+
Style/TrailingCommaInHashLiteral:
|
|
68
|
+
Enabled: false
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
# 0.6.0
|
|
2
|
+
|
|
3
|
+
- added alternative hash-based struct definition with C type names:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Point = IOStruct.new(
|
|
7
|
+
struct_name: 'Point',
|
|
8
|
+
fields: {
|
|
9
|
+
x: 'int',
|
|
10
|
+
y: :int,
|
|
11
|
+
z: { type: :int, offset: 0x10 }, # explicit offset
|
|
12
|
+
}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# supports nested structs
|
|
16
|
+
Rect = IOStruct.new(fields: { topLeft: Point, bottomRight: Point })
|
|
17
|
+
|
|
18
|
+
# supports arrays
|
|
19
|
+
IOStruct.new(fields: { values: { type: 'int', count: 10 } })
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- added `pack` support for nested structs and arrays:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
r = Rect.read(data)
|
|
26
|
+
r.pack # now works!
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- added `to_table` method with decimal formatting (`:inspect => :dec`)
|
|
30
|
+
- added `get_type_size` helper method
|
|
31
|
+
- deprecated `inspect_name_override` in favor of `struct_name`
|
|
32
|
+
- fixed `to_table` handling of unknown field types
|
|
33
|
+
- fixed `_BYTE` type alias (was incorrectly mapped to both signed and unsigned)
|
|
34
|
+
- fixed operator precedence bug in `format_integer` methods
|
|
35
|
+
|
|
36
|
+
# 0.5.0
|
|
37
|
+
|
|
38
|
+
- added `inspect_name_override` constructor param, useful for dynamic declarations:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
IOStruct.new("NN").new.inspect # "<#<Class:0x000000011c45fa20> f0=nil f4=nil>"
|
|
42
|
+
IOStruct.new("NN", inspect_name_override: "Point").new.inspect # "<Point f0=nil f4=nil>"
|
|
43
|
+
```
|
|
44
|
+
|
|
1
45
|
# 0.4.0
|
|
2
46
|
|
|
3
47
|
- added `size` class method that returns SIZE constant
|
data/Gemfile
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
source 'https://rubygems.org'
|
|
2
4
|
|
|
3
5
|
# Specify your gem's dependencies in iostruct.gemspec
|
|
4
6
|
gemspec
|
|
7
|
+
|
|
8
|
+
group :development, :test do
|
|
9
|
+
gem 'rspec', require: false
|
|
10
|
+
gem 'rubocop', require: false
|
|
11
|
+
gem 'rubocop-rake', require: false
|
|
12
|
+
gem 'rubocop-rspec', require: false
|
|
13
|
+
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,28 +1,65 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
iostruct (0.
|
|
4
|
+
iostruct (0.6.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
ast (2.4.3)
|
|
10
|
+
diff-lcs (1.6.2)
|
|
11
|
+
json (2.18.0)
|
|
12
|
+
language_server-protocol (3.17.0.5)
|
|
13
|
+
lint_roller (1.1.0)
|
|
14
|
+
parallel (1.27.0)
|
|
15
|
+
parser (3.3.10.1)
|
|
16
|
+
ast (~> 2.4.1)
|
|
17
|
+
racc
|
|
18
|
+
prism (1.8.0)
|
|
19
|
+
racc (1.8.1)
|
|
20
|
+
rainbow (3.1.1)
|
|
21
|
+
rake (13.3.1)
|
|
22
|
+
regexp_parser (2.11.3)
|
|
23
|
+
rspec (3.13.2)
|
|
12
24
|
rspec-core (~> 3.13.0)
|
|
13
25
|
rspec-expectations (~> 3.13.0)
|
|
14
26
|
rspec-mocks (~> 3.13.0)
|
|
15
|
-
rspec-core (3.13.
|
|
27
|
+
rspec-core (3.13.6)
|
|
16
28
|
rspec-support (~> 3.13.0)
|
|
17
|
-
rspec-expectations (3.13.
|
|
29
|
+
rspec-expectations (3.13.5)
|
|
18
30
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
19
31
|
rspec-support (~> 3.13.0)
|
|
20
|
-
rspec-mocks (3.13.
|
|
32
|
+
rspec-mocks (3.13.7)
|
|
21
33
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
22
34
|
rspec-support (~> 3.13.0)
|
|
23
|
-
rspec-support (3.13.
|
|
35
|
+
rspec-support (3.13.6)
|
|
36
|
+
rubocop (1.84.0)
|
|
37
|
+
json (~> 2.3)
|
|
38
|
+
language_server-protocol (~> 3.17.0.2)
|
|
39
|
+
lint_roller (~> 1.1.0)
|
|
40
|
+
parallel (~> 1.10)
|
|
41
|
+
parser (>= 3.3.0.2)
|
|
42
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
43
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
44
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
45
|
+
ruby-progressbar (~> 1.7)
|
|
46
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
47
|
+
rubocop-ast (1.49.0)
|
|
48
|
+
parser (>= 3.3.7.2)
|
|
49
|
+
prism (~> 1.7)
|
|
50
|
+
rubocop-rake (0.7.1)
|
|
51
|
+
lint_roller (~> 1.1)
|
|
52
|
+
rubocop (>= 1.72.1)
|
|
53
|
+
rubocop-rspec (3.9.0)
|
|
54
|
+
lint_roller (~> 1.1)
|
|
55
|
+
rubocop (~> 1.81)
|
|
56
|
+
ruby-progressbar (1.13.0)
|
|
57
|
+
unicode-display_width (3.2.0)
|
|
58
|
+
unicode-emoji (~> 4.1)
|
|
59
|
+
unicode-emoji (4.2.0)
|
|
24
60
|
|
|
25
61
|
PLATFORMS
|
|
62
|
+
arm64-darwin-24
|
|
26
63
|
ruby
|
|
27
64
|
|
|
28
65
|
DEPENDENCIES
|
|
@@ -30,6 +67,9 @@ DEPENDENCIES
|
|
|
30
67
|
iostruct!
|
|
31
68
|
rake
|
|
32
69
|
rspec
|
|
70
|
+
rubocop
|
|
71
|
+
rubocop-rake
|
|
72
|
+
rubocop-rspec
|
|
33
73
|
|
|
34
74
|
BUNDLED WITH
|
|
35
|
-
2.
|
|
75
|
+
2.6.9
|
data/README.md
CHANGED
|
@@ -1,24 +1,261 @@
|
|
|
1
|
-
#
|
|
1
|
+
# IOStruct
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Ruby Struct that can read/write itself from/to IO-like objects. Perfect for parsing binary file formats, network protocols, and other structured binary data.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
Add this line to your application's Gemfile:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'iostruct'
|
|
11
|
+
```
|
|
10
12
|
|
|
11
13
|
And then execute:
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle install
|
|
17
|
+
```
|
|
14
18
|
|
|
15
19
|
Or install it yourself as:
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
```bash
|
|
22
|
+
$ gem install iostruct
|
|
23
|
+
```
|
|
18
24
|
|
|
19
25
|
## Usage
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
### Basic Usage with Pack Format
|
|
28
|
+
|
|
29
|
+
Define structs using Ruby's [pack/unpack format strings](https://ruby-doc.org/core/String.html#method-i-unpack):
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require 'iostruct'
|
|
33
|
+
|
|
34
|
+
# Define a struct with two 32-bit unsigned integers
|
|
35
|
+
Point = IOStruct.new('LL', :x, :y)
|
|
36
|
+
|
|
37
|
+
# Read from binary data
|
|
38
|
+
data = [100, 200].pack('LL')
|
|
39
|
+
point = Point.read(data)
|
|
40
|
+
point.x # => 100
|
|
41
|
+
point.y # => 200
|
|
42
|
+
|
|
43
|
+
# Write back to binary
|
|
44
|
+
point.pack # => "\x64\x00\x00\x00\xC8\x00\x00\x00"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Hash-Based Definition with C Types
|
|
48
|
+
|
|
49
|
+
For more readable code, define structs using C-style type names:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
Point = IOStruct.new(
|
|
53
|
+
struct_name: 'Point',
|
|
54
|
+
fields: {
|
|
55
|
+
x: 'int',
|
|
56
|
+
y: 'int',
|
|
57
|
+
z: 'int',
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
point = Point.read(binary_data)
|
|
62
|
+
point.inspect # => "<Point x=0x64 y=0xc8 z=0x0>"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Supported C Types
|
|
66
|
+
|
|
67
|
+
| Type | Aliases | Size |
|
|
68
|
+
|------|---------|------|
|
|
69
|
+
| `uint8_t` | `unsigned char`, `_BYTE` | 1 |
|
|
70
|
+
| `uint16_t` | `unsigned short` | 2 |
|
|
71
|
+
| `uint32_t` | `unsigned int`, `unsigned` | 4 |
|
|
72
|
+
| `uint64_t` | `unsigned long long` | 8 |
|
|
73
|
+
| `int8_t` | `char`, `signed char` | 1 |
|
|
74
|
+
| `int16_t` | `short`, `signed short` | 2 |
|
|
75
|
+
| `int32_t` | `int`, `signed int`, `signed` | 4 |
|
|
76
|
+
| `int64_t` | `long long`, `signed long long` | 8 |
|
|
77
|
+
| `float` | | 4 |
|
|
78
|
+
| `double` | | 8 |
|
|
79
|
+
|
|
80
|
+
### Reading from IO or String
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
Header = IOStruct.new('L S S', :magic, :version, :flags)
|
|
84
|
+
|
|
85
|
+
# Read from a File
|
|
86
|
+
File.open('binary_file', 'rb') do |f|
|
|
87
|
+
header = Header.read(f)
|
|
88
|
+
puts header.magic
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Read from a String
|
|
92
|
+
header = Header.read("\x7fELF\x01\x00\x00\x00")
|
|
93
|
+
|
|
94
|
+
# Track file position with __offset
|
|
95
|
+
io = StringIO.new(data)
|
|
96
|
+
record = MyStruct.read(io)
|
|
97
|
+
record.__offset # => position where the record was read from
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Explicit Field Offsets
|
|
101
|
+
|
|
102
|
+
Specify exact byte offsets for fields (useful for structs with padding or gaps):
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
MyStruct = IOStruct.new(
|
|
106
|
+
fields: {
|
|
107
|
+
magic: 'uint32_t',
|
|
108
|
+
flags: { type: 'uint16_t', offset: 0x10 }, # starts at byte 16
|
|
109
|
+
data: { type: 'uint32_t', offset: 0x20 }, # starts at byte 32
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Arrays
|
|
115
|
+
|
|
116
|
+
Define fixed-size arrays within structs:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
Matrix = IOStruct.new(
|
|
120
|
+
fields: {
|
|
121
|
+
rows: 'int',
|
|
122
|
+
cols: 'int',
|
|
123
|
+
data: { type: 'float', count: 16 }, # 16-element float array
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
m = Matrix.read(binary_data)
|
|
128
|
+
m.data # => [1.0, 2.0, 3.0, ...]
|
|
129
|
+
m.pack # serializes back to binary
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Nested Structs
|
|
133
|
+
|
|
134
|
+
Compose complex structures from simpler ones:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
Point = IOStruct.new(fields: { x: 'int', y: 'int' })
|
|
138
|
+
|
|
139
|
+
Rect = IOStruct.new(
|
|
140
|
+
struct_name: 'Rect',
|
|
141
|
+
fields: {
|
|
142
|
+
top_left: Point,
|
|
143
|
+
bottom_right: Point,
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
rect = Rect.read([0, 0, 100, 100].pack('i*'))
|
|
148
|
+
rect.top_left.x # => 0
|
|
149
|
+
rect.bottom_right.x # => 100
|
|
150
|
+
rect.pack # serializes entire structure including nested structs
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Inspect Modes
|
|
154
|
+
|
|
155
|
+
Choose between hexadecimal (default) or decimal display:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
# Hex display (default)
|
|
159
|
+
HexStruct = IOStruct.new('L L', :a, :b, inspect: :hex)
|
|
160
|
+
HexStruct.new(a: 255, b: 256).inspect
|
|
161
|
+
# => "<struct a=0xff b=0x100>"
|
|
162
|
+
|
|
163
|
+
# Decimal display
|
|
164
|
+
DecStruct = IOStruct.new('L L', :a, :b, inspect: :dec)
|
|
165
|
+
DecStruct.new(a: 255, b: 256).inspect
|
|
166
|
+
# => "#<struct DecStruct a=255, b=256>"
|
|
167
|
+
|
|
168
|
+
# Table format for aligned output
|
|
169
|
+
struct.to_table
|
|
170
|
+
# => "<struct a= ff b= 100>"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Auto-Generated Field Names
|
|
174
|
+
|
|
175
|
+
If you don't specify field names, they're generated based on byte offset:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
s = IOStruct.new('C S L')
|
|
179
|
+
s.members # => [:f0, :f1, :f3] (offsets 0, 1, 3)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Field Renaming
|
|
183
|
+
|
|
184
|
+
Rename auto-generated or explicit field names:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
# Rename auto-generated names
|
|
188
|
+
IOStruct.new('C S L', f0: :byte_val, f3: :long_val)
|
|
189
|
+
|
|
190
|
+
# Rename explicit names
|
|
191
|
+
IOStruct.new('C S L', :a, :b, :c, a: :first, c: :last)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## API Reference
|
|
195
|
+
|
|
196
|
+
### Class Methods
|
|
197
|
+
|
|
198
|
+
| Method | Description |
|
|
199
|
+
|--------|-------------|
|
|
200
|
+
| `IOStruct.new(fmt, *names, **options)` | Create a new struct class with pack format |
|
|
201
|
+
| `IOStruct.new(fields:, **options)` | Create a new struct class with hash definition |
|
|
202
|
+
| `IOStruct.get_type_size(typename)` | Get byte size for a C type name |
|
|
203
|
+
| `MyStruct.read(io_or_string)` | Read and parse binary data |
|
|
204
|
+
| `MyStruct.size` | Return struct size in bytes |
|
|
205
|
+
| `MyStruct::SIZE` | Struct size constant |
|
|
206
|
+
| `MyStruct::FORMAT` | Pack format string |
|
|
207
|
+
| `MyStruct::FIELDS` | Hash of field names to FieldInfo |
|
|
208
|
+
|
|
209
|
+
### Instance Methods
|
|
210
|
+
|
|
211
|
+
| Method | Description |
|
|
212
|
+
|--------|-------------|
|
|
213
|
+
| `#pack` | Serialize to binary string |
|
|
214
|
+
| `#empty?` | True if all fields are zero/nil/empty |
|
|
215
|
+
| `#to_table` | Formatted string with aligned values |
|
|
216
|
+
| `#__offset` | File position where struct was read (nil if from string) |
|
|
217
|
+
|
|
218
|
+
### Constructor Options
|
|
219
|
+
|
|
220
|
+
| Option | Description |
|
|
221
|
+
|--------|-------------|
|
|
222
|
+
| `struct_name:` | Custom name for inspect output |
|
|
223
|
+
| `inspect:` | `:hex` (default) or `:dec` for display format |
|
|
224
|
+
| `size:` | Override calculated struct size |
|
|
225
|
+
| `fields:` | Hash defining fields (for hash-based definition) |
|
|
226
|
+
|
|
227
|
+
## Examples
|
|
228
|
+
|
|
229
|
+
### Parsing a BMP File Header
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
BMPHeader = IOStruct.new(
|
|
233
|
+
struct_name: 'BMPHeader',
|
|
234
|
+
fields: {
|
|
235
|
+
magic: { type: 'uint16_t' },
|
|
236
|
+
file_size: { type: 'uint32_t' },
|
|
237
|
+
reserved: { type: 'uint32_t' },
|
|
238
|
+
data_offset: { type: 'uint32_t' },
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
File.open('image.bmp', 'rb') do |f|
|
|
243
|
+
header = BMPHeader.read(f)
|
|
244
|
+
puts "File size: #{header.file_size} bytes"
|
|
245
|
+
puts "Pixel data starts at: #{header.data_offset}"
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Network Protocol Packet
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
Packet = IOStruct.new('n n N', :src_port, :dst_port, :sequence,
|
|
253
|
+
struct_name: 'TCPHeader'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Big-endian format for network byte order
|
|
257
|
+
packet = Packet.read(socket.read(8))
|
|
258
|
+
```
|
|
22
259
|
|
|
23
260
|
## Contributing
|
|
24
261
|
|
|
@@ -27,3 +264,7 @@ TODO: Write usage instructions here
|
|
|
27
264
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
28
265
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
29
266
|
5. Create new Pull Request
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT License - see [LICENSE.txt](LICENSE.txt) for details.
|
data/Rakefile
CHANGED
data/iostruct.gemspec
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
6
|
require 'iostruct/version'
|
|
5
7
|
|
|
@@ -9,16 +11,13 @@ Gem::Specification.new do |s|
|
|
|
9
11
|
|
|
10
12
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
13
|
s.authors = ["Andrey \"Zed\" Zaikin"]
|
|
12
|
-
s.date = "2013-01-08"
|
|
13
14
|
s.email = "zed.0xff@gmail.com"
|
|
14
|
-
s.files = `git ls-files`.split(
|
|
15
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
15
16
|
s.homepage = "http://github.com/zed-0xff/iostruct"
|
|
16
17
|
s.licenses = ["MIT"]
|
|
17
|
-
s.require_paths = ["lib"]
|
|
18
18
|
s.summary = "A Struct that can read/write itself from/to IO-like objects"
|
|
19
19
|
|
|
20
|
-
s.
|
|
21
|
-
s.add_development_dependency "rake"
|
|
22
|
-
s.add_development_dependency "rspec"
|
|
23
|
-
end
|
|
20
|
+
s.require_paths = ["lib"]
|
|
24
21
|
|
|
22
|
+
s.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IOStruct
|
|
4
|
+
module HashFmt
|
|
5
|
+
KNOWN_FIELD_TYPES_REVERSED = {
|
|
6
|
+
'C' => ['uint8_t', 'unsigned char', '_BYTE'],
|
|
7
|
+
'S' => ['uint16_t', 'unsigned short'],
|
|
8
|
+
'I' => ['uint32_t', 'unsigned', 'unsigned int'],
|
|
9
|
+
'L' => ['unsigned long'],
|
|
10
|
+
'Q' => ['uint64_t', 'unsigned long long'],
|
|
11
|
+
|
|
12
|
+
'c' => ['int8_t', 'char', 'signed char'],
|
|
13
|
+
's' => ['int16_t', 'short', 'signed short'],
|
|
14
|
+
'i' => ['int32_t', 'int', 'signed', 'signed int'],
|
|
15
|
+
'l' => ['long', 'signed long'],
|
|
16
|
+
'q' => ['int64_t', 'long long', 'signed long long'],
|
|
17
|
+
|
|
18
|
+
'd' => ['double'],
|
|
19
|
+
'f' => ['float'],
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
KNOWN_FIELD_TYPES = KNOWN_FIELD_TYPES_REVERSED.map { |t, a| a.map { |v| [v, t] } }.flatten.each_slice(2).to_h
|
|
23
|
+
|
|
24
|
+
# for external use
|
|
25
|
+
def get_type_size(typename)
|
|
26
|
+
type_code = KNOWN_FIELD_TYPES[typename.to_s]
|
|
27
|
+
f_size, = PackFmt::FMTSPEC[type_code]
|
|
28
|
+
f_size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def parse_hash_format(fields:, size: nil, name: nil)
|
|
34
|
+
struct_name = name
|
|
35
|
+
offset = 0
|
|
36
|
+
names = []
|
|
37
|
+
fmt_arr = []
|
|
38
|
+
finfos = fields.map do |f_name, type|
|
|
39
|
+
f_offset = offset
|
|
40
|
+
f_count = 1
|
|
41
|
+
klass = f_fmt = type_code = nil
|
|
42
|
+
|
|
43
|
+
if type.is_a?(Hash)
|
|
44
|
+
f_offset = type.fetch(:offset, offset)
|
|
45
|
+
if f_offset > offset
|
|
46
|
+
fmt_arr << "x#{f_offset - offset}"
|
|
47
|
+
elsif f_offset < offset
|
|
48
|
+
raise "#{struct_name}: field #{f_name.inspect} overlaps previous field"
|
|
49
|
+
end
|
|
50
|
+
f_count = type.fetch(:count, f_count)
|
|
51
|
+
type = type[:type]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
case type
|
|
55
|
+
when String
|
|
56
|
+
# noop
|
|
57
|
+
when Symbol
|
|
58
|
+
type = type.to_s
|
|
59
|
+
when Class
|
|
60
|
+
klass = type
|
|
61
|
+
f_size = klass.size
|
|
62
|
+
f_fmt = klass
|
|
63
|
+
type_code = "a#{f_size}"
|
|
64
|
+
else
|
|
65
|
+
raise "#{f_name}: unexpected field desc type #{type.class}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless type_code
|
|
69
|
+
type_code = KNOWN_FIELD_TYPES[type]
|
|
70
|
+
raise "#{f_name}: unknown field type #{type.inspect}" unless type_code
|
|
71
|
+
|
|
72
|
+
f_size, klass = PackFmt::FMTSPEC[type_code] || raise("Unknown field type code #{type_code.inspect}")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if f_count != 1
|
|
76
|
+
f_fmt = "#{type_code}#{f_count}"
|
|
77
|
+
f_size *= f_count
|
|
78
|
+
type_code = "a#{f_size}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
offset = f_offset + f_size
|
|
82
|
+
fmt_arr << type_code
|
|
83
|
+
names << f_name
|
|
84
|
+
|
|
85
|
+
FieldInfo.new(klass, f_size, f_offset, f_count, f_fmt)
|
|
86
|
+
end
|
|
87
|
+
raise "#{struct_name}: actual struct size #{offset} is greater than forced size #{size}: #{fields.inspect}" if size && offset > size
|
|
88
|
+
|
|
89
|
+
[fmt_arr.join, names, finfos, size || offset]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|