packed_struct 0.2.1 → 0.3.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.
- data/README.md +11 -1
- data/lib/packed_struct.rb +23 -0
- data/lib/packed_struct/directive.rb +255 -272
- data/lib/packed_struct/modifier.rb +76 -0
- data/lib/packed_struct/package.rb +26 -38
- data/lib/packed_struct/version.rb +1 -1
- metadata +3 -2
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# PackedStruct
|
1
|
+
# PackedStruct [](https://travis-ci.org/redjazz96/packed_struct)
|
2
2
|
|
3
3
|
`PackedStruct` is a way to define packing strings (see [`Array#pack`](http://ruby-doc.org/core-2.0/Array.html#method-i-pack)).
|
4
4
|
It was created after @charliesome suggested [a format](https://gist.github.com/redjazz96/6dda0554f62e4f77253a) for defining these strings, but never finished it.
|
@@ -10,6 +10,7 @@ class RconPacket
|
|
10
10
|
include PackedStruct
|
11
11
|
struct_layout :packet do
|
12
12
|
little_endian signed size[32] # defaults to a number of size 32.
|
13
|
+
# also the same as: `little_endian signed long size`
|
13
14
|
little_endian signed id[32]
|
14
15
|
little_endian signed type[32]
|
15
16
|
string body[size]
|
@@ -31,3 +32,12 @@ You can also unpack strings.
|
|
31
32
|
RconPacket.structs[:packet].unpack("\v\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00hello world\x00")
|
32
33
|
# => {:size => 11, :id => 1, :type => 0, :body => "hello world"}
|
33
34
|
```
|
35
|
+
|
36
|
+
From sockets, too. Anything that responds to `#read`.
|
37
|
+
|
38
|
+
```Ruby
|
39
|
+
file = File.open("/path/to/some/file", "r")
|
40
|
+
|
41
|
+
RconPacket.structs[:packet].unpack_from_socket(file)
|
42
|
+
# => ...
|
43
|
+
```
|
data/lib/packed_struct.rb
CHANGED
@@ -1,15 +1,33 @@
|
|
1
1
|
require 'packed_struct/package'
|
2
2
|
require 'packed_struct/directive'
|
3
|
+
require 'packed_struct/modifier'
|
3
4
|
|
5
|
+
# Create structs to manage packed strings.
|
4
6
|
module PackedStruct
|
5
7
|
|
8
|
+
# The structs that were defined on the module that included this.
|
9
|
+
# If the structs were defined without a name, this will be the one
|
10
|
+
# and only struct that was defined (or the last one that was
|
11
|
+
# defined).
|
12
|
+
#
|
13
|
+
# @return [Package, Hash<Symbol, Package>]
|
6
14
|
def structs
|
7
15
|
@structs ||= {}
|
8
16
|
end
|
9
17
|
|
18
|
+
alias_method :struct, :structs
|
19
|
+
|
20
|
+
# Define a struct. The name will be used for {#structs}, and the
|
21
|
+
# block will run in the context of a {Package}.
|
22
|
+
#
|
23
|
+
# @yield []
|
24
|
+
# @param name [Symbol, nil] the name of the struct. If it's nil, it
|
25
|
+
# is set as the only struct of the included module.
|
26
|
+
# @return (see #structs)
|
10
27
|
def struct_layout(name = nil, &block)
|
11
28
|
structs[name] = Package.new
|
12
29
|
structs[name].instance_exec &block
|
30
|
+
structs[name].finalize_directives!
|
13
31
|
|
14
32
|
if name == nil
|
15
33
|
@structs = structs[name]
|
@@ -18,6 +36,11 @@ module PackedStruct
|
|
18
36
|
structs
|
19
37
|
end
|
20
38
|
|
39
|
+
# Called when this is included into another module.
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @param reciever [Module] the reciever that included this one.
|
43
|
+
# @return [void]
|
21
44
|
def self.included(reciever)
|
22
45
|
reciever.extend self
|
23
46
|
end
|
@@ -1,8 +1,6 @@
|
|
1
|
-
|
1
|
+
require 'set'
|
2
2
|
|
3
|
-
|
4
|
-
# of the type, the endian(ness) of the type, the type of the type
|
5
|
-
# (short, int, long, etc.), and the signed(ness) of the type.
|
3
|
+
module PackedStruct
|
6
4
|
class Directive
|
7
5
|
|
8
6
|
# The name of the directive. This is passed as the first value of
|
@@ -11,249 +9,168 @@ module PackedStruct
|
|
11
9
|
# @return [Symbol]
|
12
10
|
attr_reader :name
|
13
11
|
|
14
|
-
# The
|
15
|
-
#
|
16
|
-
# @return [Array<Object>]
|
17
|
-
attr_reader :options
|
18
|
-
|
19
|
-
# The tags the directive has, such as type, signed(ness),
|
20
|
-
# endian(ness), and size. Not filled until {#to_s} is called.
|
21
|
-
#
|
22
|
-
# @return [Hash]
|
23
|
-
attr_accessor :tags
|
24
|
-
|
25
|
-
# The children of this directive. If this directive has a parent,
|
26
|
-
# this is nil.
|
27
|
-
#
|
28
|
-
# @return [nil, Array<Directive>]
|
29
|
-
attr_reader :subs
|
30
|
-
|
31
|
-
# The parent of this directive. The relationship is such that
|
32
|
-
# +parent.subs.include?(self)+ is true. If this has a parent, it
|
33
|
-
# is nil.
|
12
|
+
# The modifiers for this directive.
|
34
13
|
#
|
35
|
-
# @return [
|
36
|
-
|
14
|
+
# @return [Set<Modifier>]
|
15
|
+
attr_reader :modifiers
|
37
16
|
|
38
|
-
#
|
17
|
+
# Metadata about the directive. Created when {#finalize!} is
|
18
|
+
# called.
|
39
19
|
#
|
40
|
-
# @return [
|
41
|
-
|
42
|
-
|
43
|
-
# @!parse
|
44
|
-
# attr_reader :value
|
45
|
-
def value
|
46
|
-
@value || (@tags[:original].value if @tags[:original])
|
47
|
-
end
|
20
|
+
# @return [Hash<Symbol, Object>]
|
21
|
+
attr_reader :tags
|
48
22
|
|
49
23
|
# Initialize the directive.
|
50
24
|
#
|
51
25
|
# @param name [Symbol] the name of the directive.
|
52
|
-
|
26
|
+
# @param package [Package] the package this directive is a part
|
27
|
+
# of.
|
28
|
+
def initialize(name)
|
53
29
|
@name = name
|
54
|
-
|
55
|
-
@
|
56
|
-
|
57
|
-
@
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
30
|
+
@modifiers = []
|
31
|
+
@finalized = true
|
32
|
+
|
33
|
+
@tags = {
|
34
|
+
:endian => :native,
|
35
|
+
:signedness => :signed,
|
36
|
+
:size => nil,
|
37
|
+
:precision => :single,
|
38
|
+
:size_mod => 0
|
39
|
+
}
|
65
40
|
end
|
66
41
|
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
42
|
+
# Determines whether or not this directive is empty. It is
|
43
|
+
# considered empty when its tags has all of the default values,
|
44
|
+
# it has no modifiers, and its name is not +:null+.
|
70
45
|
#
|
71
|
-
# @
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
else
|
77
|
-
@_str = nil
|
78
|
-
@_sub_types = nil
|
79
|
-
@subs << child
|
80
|
-
child.parent = self
|
81
|
-
child
|
82
|
-
end
|
46
|
+
# @return [Boolean]
|
47
|
+
def empty?
|
48
|
+
tags == { :endian => :native, :signedness => :signed,
|
49
|
+
:size => nil, :precision => :single, :size_mod => 0
|
50
|
+
} && modifiers.length == 0 && name != :null
|
83
51
|
end
|
84
52
|
|
85
|
-
#
|
53
|
+
# Add a modifier to this directive.
|
86
54
|
#
|
55
|
+
# @param mod [Modifier]
|
87
56
|
# @return [self]
|
88
|
-
def
|
89
|
-
@
|
57
|
+
def add_modifier(mod)
|
58
|
+
@finalized = false
|
59
|
+
modifiers << mod
|
90
60
|
self
|
91
61
|
end
|
92
62
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
63
|
+
# Changes the size of the directive to the given size. It is
|
64
|
+
# possible for the given value to the a directive; if it is,
|
65
|
+
# it just uses the name of the directive.
|
96
66
|
#
|
97
|
-
# @
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
# Inspects the directive.
|
108
|
-
#
|
109
|
-
# @return [String]
|
110
|
-
def inspect
|
111
|
-
"#<#{self.class.name}:#{name}>"
|
112
|
-
end
|
67
|
+
# @param new_size [Numeric, Directive]
|
68
|
+
# @return [self]
|
69
|
+
def [](new_size)
|
70
|
+
if new_size.is_a? Directive
|
71
|
+
tags.merge! new_size.tags_for_sized_directive
|
72
|
+
else
|
73
|
+
tags[:size] = new_size
|
74
|
+
end
|
113
75
|
|
114
|
-
|
115
|
-
#
|
116
|
-
# @return [Directive]
|
117
|
-
def -(other)
|
118
|
-
dir = dup
|
119
|
-
dir.tags = tags.dup
|
120
|
-
dir.tags[:original ] = self
|
121
|
-
dir.tags[:size_modify] = -other
|
122
|
-
dir
|
76
|
+
self
|
123
77
|
end
|
124
78
|
|
125
|
-
#
|
79
|
+
# Returns a hash to be merged into the tags of a directive that
|
80
|
+
# recieved this directive for a size.
|
126
81
|
#
|
127
|
-
# @return [
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
dir
|
82
|
+
# @return [Hash<Symbol, Object>]
|
83
|
+
def tags_for_sized_directive
|
84
|
+
{
|
85
|
+
:size => name,
|
86
|
+
:size_mod => tags[:size_mod]
|
87
|
+
}
|
134
88
|
end
|
135
89
|
|
136
|
-
#
|
90
|
+
# Modifies +self+ such that it sizes itself (on {#to_s}ing), and
|
91
|
+
# keeping this size modification in mind. In other words, this
|
92
|
+
# is meant to be used in another directive's {#[]} method for
|
93
|
+
# telling it what size it should be, and this method modifies that
|
94
|
+
# size by the given amount.
|
137
95
|
#
|
138
|
-
# @
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
4
|
147
|
-
when :long
|
148
|
-
4
|
149
|
-
when :float
|
150
|
-
if sub_names.include?(:double)
|
151
|
-
8
|
152
|
-
else
|
153
|
-
4
|
154
|
-
end
|
155
|
-
when :null
|
156
|
-
size || 1
|
157
|
-
when :string
|
158
|
-
size
|
96
|
+
# @example
|
97
|
+
# some_directive[another_directive - 5]
|
98
|
+
# @param other [Numeric, #coerce]
|
99
|
+
# @return [self, Object]
|
100
|
+
def -(other)
|
101
|
+
if other.is_a? Numeric
|
102
|
+
tags[:size_mod] = -other
|
103
|
+
self
|
159
104
|
else
|
160
|
-
|
105
|
+
self_equiv, arg_equiv = other.coerce(self)
|
106
|
+
self_equiv - arg_equiv
|
161
107
|
end
|
162
108
|
end
|
163
109
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# @return [Array<Symbol>]
|
170
|
-
def sub_names(force = false)
|
171
|
-
if @_sub_types && !force
|
172
|
-
@_sub_types
|
110
|
+
# (see #-)
|
111
|
+
def +(other)
|
112
|
+
if other.is_a? Numeric
|
113
|
+
tags[:size_mod] = +other
|
114
|
+
self
|
173
115
|
else
|
174
|
-
|
116
|
+
self_equiv, arg_equiv = other.coerce(self)
|
117
|
+
self_equiv + arg_equiv
|
175
118
|
end
|
176
119
|
end
|
177
120
|
|
178
|
-
#
|
179
|
-
#
|
121
|
+
# Coerces +self+ into a format that can be used with +Numeric+ to
|
122
|
+
# add or subtract from this class.
|
180
123
|
#
|
181
|
-
# @
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
@tags[:size]
|
188
|
-
when nil
|
189
|
-
@subs.select { |s| s && s.tags[:size] }.map { |s| s.tags[:size] }.last
|
190
|
-
end
|
124
|
+
# @example
|
125
|
+
# some_directive[1 + another_directive]
|
126
|
+
# @param other [Object]
|
127
|
+
# @return [Array<(self, Object)>]
|
128
|
+
def coerce(other)
|
129
|
+
[self, other]
|
191
130
|
end
|
192
131
|
|
193
|
-
#
|
194
|
-
#
|
132
|
+
# Whether or not this directive has finalized. It is finalized
|
133
|
+
# until a modifier is added, and then {#finalize!} is required to
|
134
|
+
# finalize the directive.
|
195
135
|
#
|
196
|
-
# @return [
|
197
|
-
|
198
|
-
|
199
|
-
case true
|
200
|
-
when sub_names.include?(:short)
|
201
|
-
:short
|
202
|
-
when sub_names.include?(:int)
|
203
|
-
:int
|
204
|
-
when sub_names.include?(:long)
|
205
|
-
:long
|
206
|
-
when sub_names.include?(:char), sub_names.include?(:string)
|
207
|
-
:string
|
208
|
-
when sub_names.include?(:float)
|
209
|
-
:float
|
210
|
-
when sub_names.include?(:null)
|
211
|
-
:null
|
212
|
-
else
|
213
|
-
nil
|
214
|
-
end
|
136
|
+
# @return [Boolean]
|
137
|
+
def finalized?
|
138
|
+
@finalized
|
215
139
|
end
|
216
140
|
|
217
|
-
#
|
218
|
-
# to search for matching names. Defaults to +:native+.
|
141
|
+
# Finalizes the directive.
|
219
142
|
#
|
220
|
-
# @return [
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
:
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
143
|
+
# @return [void]
|
144
|
+
def finalize!
|
145
|
+
return if finalized?
|
146
|
+
|
147
|
+
modifiers.each do |modifier|
|
148
|
+
case modifier.type
|
149
|
+
when :endian, :signedness, :precision, :type, :string_type
|
150
|
+
tags[modifier.type] = modifier.value
|
151
|
+
when :size
|
152
|
+
tags[:size] = modifier.value unless tags[:size]
|
153
|
+
else
|
154
|
+
raise UnknownModifierError,
|
155
|
+
"Unknown modifier: #{modifier.type}"
|
156
|
+
end
|
233
157
|
end
|
234
|
-
end
|
235
158
|
|
236
|
-
|
237
|
-
|
238
|
-
#
|
239
|
-
# @return [Symbol] the return value can be any of +:unsigned+ or
|
240
|
-
# +:signed+.
|
241
|
-
def determine_signed
|
242
|
-
if sub_names.include?(:unsigned) && !sub_names.include?(:null)
|
243
|
-
:unsigned
|
244
|
-
else
|
245
|
-
:signed
|
246
|
-
end
|
159
|
+
@finalized = true
|
160
|
+
cache_string
|
247
161
|
end
|
248
162
|
|
249
|
-
#
|
250
|
-
# the
|
163
|
+
# Turn the directive into a string, with the given data. It
|
164
|
+
# shouldn't need the data unless +tags[:size]+ is a Symbol.
|
251
165
|
#
|
252
|
-
# @
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
166
|
+
# @param data [Hash<Symbol, Object>] the data that may be used for
|
167
|
+
# the length.
|
168
|
+
# @return [String]
|
169
|
+
def to_s(data = {})
|
170
|
+
return @_cache if !tags[:size].is_a?(Symbol) && @_cache
|
171
|
+
return "x" * (tags[:size] || 1) if name == :null
|
172
|
+
|
173
|
+
out = case tags[:type]
|
257
174
|
when :short
|
258
175
|
modify_if_needed "S"
|
259
176
|
when :int
|
@@ -264,119 +181,185 @@ module PackedStruct
|
|
264
181
|
handle_string_type
|
265
182
|
when :float
|
266
183
|
handle_float_type
|
267
|
-
when
|
268
|
-
|
184
|
+
when nil
|
185
|
+
handle_empty_type
|
269
186
|
else
|
270
187
|
nil
|
271
188
|
end
|
189
|
+
|
190
|
+
if tags[:size].is_a? Symbol
|
191
|
+
out << data.fetch(tags[:size]).to_s
|
192
|
+
elsif tags[:size] && ![:null, nil].include?(tags[:type])
|
193
|
+
out << tags[:size].to_s
|
194
|
+
end
|
195
|
+
|
196
|
+
out
|
272
197
|
end
|
273
198
|
|
274
|
-
#
|
199
|
+
# The number of bytes a type takes up in the string.
|
200
|
+
BYTES_IN_STRING = {
|
201
|
+
:char => [0].pack("c").bytesize,
|
202
|
+
:short => [0].pack("s").bytesize,
|
203
|
+
:int => [0].pack("i").bytesize,
|
204
|
+
:long => [0].pack("l").bytesize,
|
205
|
+
:float_single => [0].pack("f").bytesize,
|
206
|
+
:float_double => [0].pack("D").bytesize,
|
207
|
+
}
|
208
|
+
|
209
|
+
# The number of bytes this takes up in the resulting packed string.
|
275
210
|
#
|
276
|
-
# @
|
277
|
-
|
278
|
-
|
279
|
-
|
211
|
+
# @param (see #to_s)
|
212
|
+
# @return [Numeric]
|
213
|
+
def bytesize(data = {})
|
214
|
+
case tags[:type]
|
215
|
+
when nil
|
216
|
+
(size(data) || 8) / 8
|
217
|
+
when :short, :int, :long
|
218
|
+
BYTES_IN_STRING.fetch tags[:type]
|
219
|
+
when :float
|
220
|
+
if tags[:precision] == :double
|
221
|
+
BYTES_IN_STRING[:float_double]
|
222
|
+
else
|
223
|
+
BYTES_IN_STRING[:float_single]
|
224
|
+
end
|
225
|
+
when :null
|
226
|
+
size(data) || 1
|
227
|
+
when :string
|
228
|
+
size(data)
|
280
229
|
else
|
281
|
-
|
230
|
+
0
|
282
231
|
end
|
283
232
|
end
|
284
233
|
|
285
|
-
#
|
286
|
-
# the directive.
|
234
|
+
# The size of this directive.
|
287
235
|
#
|
236
|
+
# @param (see #to_s)
|
237
|
+
# @return [nil, Numeric]
|
238
|
+
def size(data = {})
|
239
|
+
if tags[:size].is_a? Symbol
|
240
|
+
data.fetch(tags[:size])
|
241
|
+
else
|
242
|
+
tags[:size]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# Tries to cache the string value of this directive. It cannot if
|
249
|
+
# +tags[:size]+ is a Symbol, since it depends on the value of the
|
250
|
+
# directive named by that symbol.
|
251
|
+
#
|
252
|
+
# @return [String]
|
253
|
+
def cache_string
|
254
|
+
return if tags[:size].is_a? Symbol
|
255
|
+
return @_cache = "x" * (tags[:size] || 1) if name == :null
|
256
|
+
|
257
|
+
@_cache = to_s
|
258
|
+
end
|
259
|
+
|
260
|
+
# Handles the type if there is no type, i.e. a type modifier was
|
261
|
+
# not specified. Can only handle directives with sizes
|
262
|
+
# 0 (default), 8, 16, 32, and 64.
|
263
|
+
#
|
264
|
+
# @see #modify_if_needed
|
288
265
|
# @return [String]
|
289
|
-
def
|
266
|
+
def handle_empty_type
|
290
267
|
maps = {
|
268
|
+
0 => "x",
|
291
269
|
8 => "C",
|
292
270
|
16 => "S",
|
293
271
|
32 => "L",
|
294
272
|
64 => "Q"
|
295
273
|
}
|
296
274
|
|
297
|
-
|
298
|
-
"Cannot make number of #{size} length" unless
|
299
|
-
maps.keys.include?(size)
|
300
|
-
|
301
|
-
modify_if_needed maps[size]
|
275
|
+
modify_if_needed maps.fetch(tags[:size] || 0), tags[:size] != 8
|
302
276
|
end
|
303
277
|
|
304
|
-
# Handles
|
305
|
-
#
|
306
|
-
#
|
307
|
-
# Otherwise, determines the type of string from the sub names;
|
308
|
-
# types of strings can include +:hex+, +:base64+, +:bit+, or
|
309
|
-
# +:binary+ (defaults to binary).
|
278
|
+
# Handles the type if it is string. Defaults to a null-padded
|
279
|
+
# string, but if a +:hex+, +:base64+, or +:bit+ modifier is
|
280
|
+
# specified, it will be used.
|
310
281
|
#
|
311
|
-
#
|
282
|
+
# If +:hex+ is specified, the endianness will be used to determine
|
283
|
+
# which nibble will go first.
|
284
|
+
#
|
285
|
+
# If +:base64+ is specified, the endianness will be used to
|
286
|
+
# determine whether +MSB+ or the +LSB+ will go first.
|
312
287
|
def handle_string_type
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
when sub_names.include?(:base64)
|
288
|
+
case tags[:string_type]
|
289
|
+
when :hex
|
290
|
+
modify_for_endianness "H", true
|
291
|
+
when :base64
|
318
292
|
"m"
|
319
|
-
when
|
320
|
-
|
293
|
+
when :bit
|
294
|
+
modify_for_endianness "B", true
|
321
295
|
else
|
322
|
-
|
296
|
+
modify_for_endianness "a", true
|
323
297
|
end
|
324
298
|
end
|
325
299
|
|
326
|
-
# Handles float
|
327
|
-
#
|
300
|
+
# Handles the float type. Handles the endianness and the
|
301
|
+
# precision, returning the correct character for the float type.
|
328
302
|
#
|
329
303
|
# @return [String]
|
330
304
|
def handle_float_type
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
when :native
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
when :
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
"e"
|
345
|
-
end
|
346
|
-
when :big
|
347
|
-
if double
|
348
|
-
"G"
|
349
|
-
else
|
350
|
-
"g"
|
351
|
-
end
|
305
|
+
case [tags[:endian], tags[:precision]]
|
306
|
+
when [:native, :double]
|
307
|
+
"D"
|
308
|
+
when [:native, :single]
|
309
|
+
"F"
|
310
|
+
when [:little, :double]
|
311
|
+
"E"
|
312
|
+
when [:little, :single]
|
313
|
+
"e"
|
314
|
+
when [:big, :double]
|
315
|
+
"G"
|
316
|
+
when [:big, :single]
|
317
|
+
"g"
|
352
318
|
end
|
353
319
|
end
|
354
320
|
|
355
|
-
# Modifies the given string
|
356
|
-
#
|
357
|
-
#
|
358
|
-
# endian, and that ">" added means big endian (and that nothing
|
359
|
-
# added stands for native).
|
321
|
+
# Modifies the given string if it's needed, according to
|
322
|
+
# signness and endianness. This assumes that a signed
|
323
|
+
# directive should be in lowercase.
|
360
324
|
#
|
361
|
-
# @param str [String]
|
325
|
+
# @param str [String] the string to modify.
|
326
|
+
# @param include_endian [Boolean] whether or not to include the
|
327
|
+
# endianness.
|
362
328
|
# @return [String]
|
363
329
|
def modify_if_needed(str, include_endian = true)
|
364
|
-
base = if @tags[:
|
365
|
-
str.
|
330
|
+
base = if @tags[:signedness] == :signed
|
331
|
+
str.swapcase
|
366
332
|
else
|
367
333
|
str
|
368
334
|
end
|
369
|
-
|
370
|
-
|
371
|
-
when :little
|
372
|
-
"<"
|
373
|
-
when :big
|
374
|
-
">"
|
335
|
+
if include_endian
|
336
|
+
modify_for_endianness(base)
|
375
337
|
else
|
376
|
-
|
377
|
-
end
|
338
|
+
base
|
339
|
+
end
|
340
|
+
end
|
378
341
|
|
379
|
-
|
342
|
+
# Modifies the given string to account for endianness. If
|
343
|
+
# +use_case+ is true, it modifies the case of the given string to
|
344
|
+
# represent endianness; otherwise, it appends data to the string
|
345
|
+
# to represent endianness.
|
346
|
+
#
|
347
|
+
# @param str [String] the string to modify.
|
348
|
+
# @param use_case [Boolean]
|
349
|
+
# @return [String]
|
350
|
+
def modify_for_endianness(str, use_case = false)
|
351
|
+
case [tags[:endian], use_case]
|
352
|
+
when [:little, true]
|
353
|
+
str.swapcase
|
354
|
+
when [:little, false]
|
355
|
+
str + "<"
|
356
|
+
when [:big, true]
|
357
|
+
str
|
358
|
+
when [:big, false]
|
359
|
+
str + ">"
|
360
|
+
else
|
361
|
+
str
|
362
|
+
end
|
380
363
|
end
|
381
364
|
|
382
365
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module PackedStruct
|
2
|
+
class Modifier
|
3
|
+
|
4
|
+
# Initializes the modifier.
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@type = nil
|
8
|
+
@value = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# The type of modifier it is. Has three possible values:
|
12
|
+
# +:endian+, +:size+, and +:signedness+.
|
13
|
+
#
|
14
|
+
# @return [Symbol]
|
15
|
+
# @!parse
|
16
|
+
# attr_reader :type
|
17
|
+
def type
|
18
|
+
compile! unless @compiled
|
19
|
+
@type
|
20
|
+
end
|
21
|
+
|
22
|
+
# The value of the modifier. Has multiple possible values,
|
23
|
+
# including: +:little+, +:big+, +:short+, +:int+, +:long+,
|
24
|
+
# +:float+, +:null+, +:string+, +:unsigned+, +:signed+.
|
25
|
+
#
|
26
|
+
# @return [Symbol]
|
27
|
+
# @!parse
|
28
|
+
# attr_reader :value
|
29
|
+
def value
|
30
|
+
compile! unless @compiled
|
31
|
+
@value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compiles the modifier into a usable format. Stores the values
|
35
|
+
# it determines in +@type+ and +@value+.
|
36
|
+
#
|
37
|
+
# @raises [UnknownModifierError] if it can't detect the type of
|
38
|
+
# modifier.
|
39
|
+
# @return [void]
|
40
|
+
def compile!
|
41
|
+
@compiled ||= begin
|
42
|
+
case @name
|
43
|
+
when :little_endian, :little, :lsb, :low
|
44
|
+
@type = :endian
|
45
|
+
@value = :little
|
46
|
+
when :big_endian, :big, :msb, :high, :network
|
47
|
+
@type = :endian
|
48
|
+
@value = :big
|
49
|
+
when :short, :int, :long, :float, :string
|
50
|
+
@type = :type
|
51
|
+
@value = @name
|
52
|
+
when :unsigned, :signed
|
53
|
+
@type = :signedness
|
54
|
+
@value = @name
|
55
|
+
when :null
|
56
|
+
@type = :signedness
|
57
|
+
@value = :signed
|
58
|
+
when :spaced
|
59
|
+
@type = :signedness
|
60
|
+
@value = :unsigned
|
61
|
+
when :double
|
62
|
+
@type = :length
|
63
|
+
@value = :double
|
64
|
+
when :hex, :base64, :bit
|
65
|
+
@type = :string_type
|
66
|
+
@value = @name
|
67
|
+
else
|
68
|
+
raise UnknownModifierError, "Unkown modifier: #{@name}"
|
69
|
+
end
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class UnknownModifierError < StandardError; end
|
76
|
+
end
|
@@ -9,7 +9,6 @@ module PackedStruct
|
|
9
9
|
#
|
10
10
|
# @return [Array<Directive>]
|
11
11
|
def directives
|
12
|
-
@directives = @directives.select { |x| x.parent.nil? }
|
13
12
|
@directives
|
14
13
|
end
|
15
14
|
|
@@ -21,9 +20,11 @@ module PackedStruct
|
|
21
20
|
# Turn the package into a string. Uses the directives (calls
|
22
21
|
# {Directive#to_s} on them), and joins the result.
|
23
22
|
#
|
23
|
+
# @param data [Hash<Symbol, Object>] the data to pass to
|
24
|
+
# {Directive#to_s}.
|
24
25
|
# @return [String] the string ready for #pack.
|
25
|
-
def to_s
|
26
|
-
directives.map(
|
26
|
+
def to_s(data = {})
|
27
|
+
directives.map { |x| x.to_s(data) }.join(' ')
|
27
28
|
end
|
28
29
|
|
29
30
|
alias_method :to_str, :to_s
|
@@ -31,7 +32,7 @@ module PackedStruct
|
|
31
32
|
# Packs the given data into a string. The keys of the data
|
32
33
|
# correspond to the names of the directives.
|
33
34
|
#
|
34
|
-
# @param [Hash<Symbol, Object>] the data.
|
35
|
+
# @param data [Hash<Symbol, Object>] the data.
|
35
36
|
# @return [String] the packed data.
|
36
37
|
def pack(data)
|
37
38
|
values = []
|
@@ -44,23 +45,11 @@ module PackedStruct
|
|
44
45
|
values = values.select { |x| mapped_directives.include?(x[0]) }
|
45
46
|
|
46
47
|
values.sort! do |a, b|
|
47
|
-
|
48
|
+
mapped_directives.index(a[0]) <=> mapped_directives.index(b[0])
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# Packs the directives into a string. Uses an array.
|
54
|
-
# The parameters can either be an array, or a set of values.
|
55
|
-
#
|
56
|
-
# @return [String]
|
57
|
-
def pack_with_array(*array)
|
58
|
-
array.flatten!
|
59
|
-
|
60
|
-
directives.each_with_index { |e, i| e.value = array[i] }
|
61
|
-
out = array.pack(self.to_s)
|
62
|
-
directives.each { |x| x.value = nil }
|
63
|
-
out
|
51
|
+
ary = values.map(&:last)
|
52
|
+
ary.pack to_s(data)
|
64
53
|
end
|
65
54
|
|
66
55
|
# Unpacks the given string with the directives. Returns a hash
|
@@ -73,18 +62,14 @@ module PackedStruct
|
|
73
62
|
total = ""
|
74
63
|
parts = {}
|
75
64
|
directives.each_with_index do |directive, i|
|
76
|
-
total << directive.to_s
|
77
|
-
|
78
|
-
directive.value = value
|
79
|
-
parts[directive.name] = value
|
65
|
+
total << directive.to_s(parts)
|
66
|
+
parts[directive.name] = string.unpack(total)[i]
|
80
67
|
end
|
81
68
|
|
82
|
-
|
83
|
-
directives.each { |x| x.value = nil }
|
84
|
-
|
85
69
|
parts.delete(:null) {}
|
86
70
|
parts
|
87
71
|
end
|
72
|
+
|
88
73
|
# Unpacks from a socket.
|
89
74
|
#
|
90
75
|
# @param sock [#read] the socket to unpack from.
|
@@ -95,15 +80,11 @@ module PackedStruct
|
|
95
80
|
parts = {}
|
96
81
|
|
97
82
|
directives.each_with_index do |directive, i|
|
98
|
-
total << directive.to_s
|
99
|
-
read << sock.read(directive.bytesize)
|
100
|
-
|
101
|
-
directive.value = value
|
102
|
-
parts[directive.name] = value
|
83
|
+
total << directive.to_s(parts)
|
84
|
+
read << sock.read(directive.bytesize parts)
|
85
|
+
parts[directive.name] = read.unpack(total)[i]
|
103
86
|
end
|
104
87
|
|
105
|
-
directives.each { |x| x.value = nil }
|
106
|
-
|
107
88
|
parts.delete(:null) {}
|
108
89
|
parts
|
109
90
|
end
|
@@ -126,6 +107,14 @@ module PackedStruct
|
|
126
107
|
parts
|
127
108
|
end
|
128
109
|
|
110
|
+
# Finalizes all of the directives.
|
111
|
+
#
|
112
|
+
# @return [void]
|
113
|
+
def finalize_directives!
|
114
|
+
@finalized = true
|
115
|
+
directives.reject!(&:empty?)
|
116
|
+
directives.map(&:finalize!)
|
117
|
+
end
|
129
118
|
|
130
119
|
# Inspects the package.
|
131
120
|
#
|
@@ -138,12 +127,11 @@ module PackedStruct
|
|
138
127
|
#
|
139
128
|
# @return [Directive] the new directive.
|
140
129
|
def method_missing(method, *arguments, &block)
|
141
|
-
if @
|
142
|
-
|
130
|
+
super if @finalized
|
131
|
+
if arguments.length == 1 && arguments.first.is_a?(Directive)
|
132
|
+
arguments.first.add_modifier Modifier.new(method)
|
143
133
|
else
|
144
|
-
|
145
|
-
@directives << directive
|
146
|
-
directive
|
134
|
+
(directives.push Directive.new(method)).last
|
147
135
|
end
|
148
136
|
end
|
149
137
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packed_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-27 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! ' Cleans up the string mess when packing items (in Array#pack) and
|
15
15
|
unpacking items (in String#unpack).
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- README.md
|
24
24
|
- LICENSE
|
25
25
|
- lib/packed_struct/version.rb
|
26
|
+
- lib/packed_struct/modifier.rb
|
26
27
|
- lib/packed_struct/package.rb
|
27
28
|
- lib/packed_struct/directive.rb
|
28
29
|
- lib/packed_struct.rb
|