bin_struct 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,514 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of BinStruct
4
+ # See https://github.com/lemontree55/bin_struct for more informations
5
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
+ # This program is published under MIT license.
8
+
9
+ module BinStruct
10
+ # Base integer class to handle binary integers
11
+ # @abstract
12
+ # @author Sylvain Daubert
13
+ # @author LemonTree55
14
+ class Int
15
+ include Fieldable
16
+
17
+ # Integer value
18
+ # @return [Integer,nil]
19
+ attr_accessor :value
20
+ # Integer endianness
21
+ # @return [:little,:big,:native,nil]
22
+ attr_accessor :endian
23
+ # Integer size, in bytes
24
+ # @return [Integer, nil]
25
+ attr_accessor :width
26
+ # Integer default value
27
+ # @return [Integer]
28
+ attr_accessor :default
29
+
30
+ # @param [Hash] options
31
+ # @option options [Integer, nil] :value
32
+ # @option options [:little,:big,nil] :endian
33
+ # @option options [Integer,nil] :width
34
+ # @option options [Integer] :default
35
+ # @author LemonTree55
36
+ def initialize(options = {})
37
+ @value = options[:value]
38
+ @endian = options[:endian]
39
+ @width = options[:width]
40
+ @default = options[:default] || 0
41
+ end
42
+
43
+ # @abstract
44
+ # Read an Int from a binary string or an integer
45
+ # @param [#to_s] str
46
+ # @return [self]
47
+ # @raise [Error] when reading +#to_s+ objects with abstract Int class.
48
+ # @author LemonTree55
49
+ def read(str)
50
+ raise Error, 'BinStruct::Int#read is abstract' unless defined? @packstr
51
+
52
+ @value = str.to_s.unpack1(@packstr[@endian])
53
+ self
54
+ end
55
+
56
+ # @abstract
57
+ # @return [::String]
58
+ # @raise [Error] This is an abstrat method and must be redefined
59
+ def to_s
60
+ raise Error, 'BinStruct::Int#to_s is abstract' unless defined? @packstr
61
+
62
+ [to_i].pack(@packstr[@endian])
63
+ end
64
+
65
+ # Convert Int to Integer
66
+ # @return [Integer]
67
+ def to_i
68
+ @value || @default
69
+ end
70
+ alias to_human to_i
71
+
72
+ # @param [Integer] value
73
+ # @return [self]
74
+ def from_human(value)
75
+ @value = value
76
+ self
77
+ end
78
+
79
+ # Convert Int to Float
80
+ # @return [Float]
81
+ def to_f
82
+ to_i.to_f
83
+ end
84
+
85
+ # Give size in bytes of self
86
+ # @return [Integer]
87
+ def sz
88
+ width
89
+ end
90
+
91
+ # Format Int type when inspecting header or packet
92
+ # @return [String]
93
+ def format_inspect
94
+ format_str % [to_i.to_s, to_i]
95
+ end
96
+
97
+ # Return the number of bits used to encode this Int
98
+ # @return [Integer]
99
+ def nbits
100
+ width * 8
101
+ end
102
+
103
+ private
104
+
105
+ def format_str
106
+ "%-16s (0x%0#{width * 2}x)"
107
+ end
108
+ end
109
+
110
+ # One byte unsigned integer
111
+ # @author Sylvain Daubert
112
+ # @author LemonTree55
113
+ class Int8 < Int
114
+ # @param [Hash] options
115
+ # @option options [Integer,nil] :value
116
+ # @author LemonTree55
117
+ def initialize(options = {})
118
+ options[:endian] = nil
119
+ options[:width] = 1
120
+ super
121
+ @packstr = { nil => 'C' }
122
+ end
123
+ end
124
+
125
+ # One byte signed integer
126
+ # @author Sylvain Daubert
127
+ # @author LemonTree55
128
+ class SInt8 < Int
129
+ # @param [Hash] options
130
+ # @option options [Integer,nil] :value
131
+ # @author LemonTree55
132
+ def initialize(options = {})
133
+ options[:endian] = nil
134
+ options[:width] = 1
135
+ super
136
+ @packstr = { nil => 'c' }
137
+ end
138
+ end
139
+
140
+ # 2-byte unsigned integer
141
+ # @author Sylvain Daubert
142
+ # @author LemonTree55
143
+ class Int16 < Int
144
+ # @param [Hash] options
145
+ # @option options [Integer,nil] :value
146
+ # @option [:big, :little, :native] :endian
147
+ def initialize(options = {})
148
+ opts = { value: options[:value], endian: options[:endian] || :big, width: 2 }
149
+ super(opts)
150
+ @packstr = { big: 'n', little: 'v', native: 'S' }
151
+ end
152
+ end
153
+
154
+ # big endian 2-byte unsigned integer
155
+ # @author Sylvain Daubert
156
+ class Int16be < Int16
157
+ undef endian=
158
+ end
159
+
160
+ # little endian 2-byte unsigned integer
161
+ # @author Sylvain Daubert
162
+ # @author LemonTree55
163
+ class Int16le < Int16
164
+ # @param [Integer,nil] value
165
+ undef endian=
166
+
167
+ # @param [Hash] options
168
+ # @option options [Integer,nil] :value
169
+ def initialize(options = {})
170
+ opts = { value: options[:value], endian: :little }
171
+ super(opts)
172
+ end
173
+ end
174
+
175
+ # native endian 2-byte unsigned integer
176
+ # @author Sylvain Daubert
177
+ # @author LemonTree55
178
+ class Int16n < Int16
179
+ # @param [Integer,nil] value
180
+ undef endian=
181
+
182
+ # @param [Hash] options
183
+ # @option options [Integer,nil] :value
184
+ # @author LemonTree55
185
+ def initialize(options = {})
186
+ opts = { value: options[:value], endian: :native }
187
+ super(opts)
188
+ end
189
+ end
190
+
191
+ # 2-byte signed integer
192
+ # @author Sylvain Daubert
193
+ # @author LemonTree55
194
+ class SInt16 < Int16
195
+ # @param [Hash] options
196
+ # @option options [Integer,nil] :value
197
+ # @author LemonTree55
198
+ def initialize(options = {})
199
+ opts = { value: options[:value], endian: options[:endian] || :big }
200
+ super(opts)
201
+ @packstr = { big: 's>', little: 's<', native: 's' }
202
+ end
203
+ end
204
+
205
+ # big endian 2-byte signed integer
206
+ # @author Sylvain Daubert
207
+ class SInt16be < SInt16
208
+ undef endian=
209
+ end
210
+
211
+ # little endian 2-byte signed integer
212
+ # @author Sylvain Daubert
213
+ # @author LemonTree55
214
+ class SInt16le < SInt16
215
+ # @param [Integer,nil] value
216
+ undef endian=
217
+
218
+ # @param [Integer, nil] value
219
+ # @author LemonTree55
220
+ def initialize(options = {})
221
+ opts = { value: options[:value], endian: :little }
222
+ super(opts)
223
+ end
224
+ end
225
+
226
+ # native endian 2-byte signed integer
227
+ # @author Sylvain Daubert
228
+ # @author LemonTree55
229
+ class SInt16n < SInt16
230
+ # @param [Integer,nil] value
231
+ undef endian=
232
+
233
+ # @param [Integer, nil] value
234
+ # @author LemonTree55
235
+ def initialize(options = {})
236
+ opts = { value: options[:value], endian: :native }
237
+ super(opts)
238
+ end
239
+ end
240
+
241
+ # 3-byte unsigned integer
242
+ # @author LemonTree55
243
+ class Int24 < Int
244
+ # @param [Hash] options
245
+ # @option options [Integer,nil] :value
246
+ # @option options [:big, :little, :native] :endian
247
+ def initialize(options = {})
248
+ opts = options.slice(:value, :endian)
249
+ opts[:endian] ||= :big
250
+ opts[:width] = 3
251
+
252
+ if opts[:endian] == :native
253
+ opts[:endian] = if [1].pack('S').unpack1('n') == 1
254
+ :big
255
+ else
256
+ :little
257
+ end
258
+ end
259
+ super(opts)
260
+ end
261
+
262
+ # Read an 3-byte Int from a binary string
263
+ # @param [String] str
264
+ # @return [self]
265
+ def read(value)
266
+ return self if value.nil?
267
+
268
+ up8 = down16 = 0
269
+ if @endian == :big
270
+ up8, down16 = value.to_s.unpack('Cn')
271
+ else
272
+ down16, up8 = value.to_s.unpack('vC')
273
+ end
274
+ @value = (up8 << 16) | down16
275
+ self
276
+ end
277
+
278
+ # @author Sylvain Daubert
279
+ # @return [::String]
280
+ def to_s
281
+ up8 = to_i >> 16
282
+ down16 = to_i & 0xffff
283
+ if @endian == :big
284
+ [up8, down16].pack('Cn')
285
+ else
286
+ [down16, up8].pack('vC')
287
+ end
288
+ end
289
+ end
290
+
291
+ # big endian 3-byte unsigned integer
292
+ # @author Sylvain Daubert
293
+ class Int24be < Int24
294
+ undef endian=
295
+ end
296
+
297
+ # little endian 3-byte unsigned integer
298
+ # @author LemonTree55
299
+ class Int24le < Int24
300
+ # @param [Integer,nil] value
301
+ undef endian=
302
+
303
+ # @param [Hash] options
304
+ # @option options [Integer] :value
305
+ def initialize(options = {})
306
+ opts = { value: options[:value], endian: :little }
307
+ super(opts)
308
+ end
309
+ end
310
+
311
+ # native endian 3-byte unsigned integer
312
+ # @author LemonTree55
313
+ class Int24n < Int24
314
+ # @param [Integer,nil] value
315
+ undef endian=
316
+
317
+ # @param [Hash] options
318
+ # @option options [Integer] :value
319
+ def initialize(options = {})
320
+ opts = { value: options[:value], endian: :little }
321
+ super(opts)
322
+ end
323
+ end
324
+
325
+ # 4-byte unsigned integer
326
+ # @author LemonTree55
327
+ class Int32 < Int
328
+ # @param [Hash] options
329
+ # @option options [Integer,nil] :value
330
+ # @option [:big, :little, :native] :endian
331
+ def initialize(options = {})
332
+ opts = { value: options[:value], endian: options[:endian] || :big, width: 4 }
333
+ super(opts)
334
+ @packstr = { big: 'N', little: 'V', native: 'L' }
335
+ end
336
+ end
337
+
338
+ # big endian 4-byte unsigned integer
339
+ # @author Sylvain Daubert
340
+ class Int32be < Int32
341
+ undef endian=
342
+ end
343
+
344
+ # little endian 4-byte unsigned integer
345
+ # @author LemonTree55
346
+ class Int32le < Int32
347
+ # @param [Integer,nil] value
348
+ undef endian=
349
+
350
+ # @param [Hash] options
351
+ # @option options [Integer,nil] :value
352
+ def initialize(options = {})
353
+ opts = { value: options[:value], endian: :little }
354
+ super(opts)
355
+ end
356
+ end
357
+
358
+ # native endian 4-byte unsigned integer
359
+ # @author LemonTree55
360
+ class Int32n < Int32
361
+ # @param [Integer,nil] value
362
+ undef endian=
363
+
364
+ # @param [Hash] options
365
+ # @option options [Integer,nil] :value
366
+ def initialize(options = {})
367
+ opts = { value: options[:value], endian: :native }
368
+ super(opts)
369
+ end
370
+ end
371
+
372
+ # 4-byte unsigned integer
373
+ # @author LemonTree55
374
+ class SInt32 < Int32
375
+ # @param [Hash] options
376
+ # @option options [Integer] :value
377
+ # @option options [:big, :little, :native] :endian
378
+ def initialize(options = {})
379
+ opts = { value: options[:value], endian: options[:endian] || :big }
380
+ super(opts)
381
+ @packstr = { big: 'l>', little: 'l<', native: 'l' }
382
+ end
383
+ end
384
+
385
+ # big endian 4-byte unsigned integer
386
+ # @author Sylvain Daubert
387
+ class SInt32be < SInt32
388
+ undef endian=
389
+ end
390
+
391
+ # little endian 4-byte unsigned integer
392
+ # @author LemonTree55
393
+ class SInt32le < SInt32
394
+ # @param [Integer,nil] value
395
+ undef endian=
396
+
397
+ # @param [Hash] options
398
+ # @option options [Integer] :value
399
+ def initialize(options = {})
400
+ opts = { value: options[:value], endian: :little }
401
+ super(opts)
402
+ end
403
+ end
404
+
405
+ # native endian 4-byte unsigned integer
406
+ # @author LemonTree55
407
+ class SInt32n < SInt32
408
+ # @param [Integer,nil] value
409
+ undef endian=
410
+
411
+ # @param [Hash] options
412
+ # @option options [Integer] :value
413
+ def initialize(options = {})
414
+ opts = { value: options[:value], endian: :native }
415
+ super(opts)
416
+ end
417
+ end
418
+
419
+ # 8-byte unsigned integer
420
+ # @author LemonTree55
421
+ class Int64 < Int
422
+ # @param [Hash] options
423
+ # @option options [Integer] :value
424
+ # @option options [:big, :little, :native] :endian
425
+ def initialize(options = {})
426
+ opts = options.slice(:value, :endian)
427
+ opts[:endian] ||= :big
428
+ opts[:width] = 8
429
+ super(opts)
430
+ @packstr = { big: 'Q>', little: 'Q<', native: 'Q' }
431
+ end
432
+ end
433
+
434
+ # big endian 8-byte unsigned integer
435
+ # @author Sylvain Daubert
436
+ class Int64be < Int64
437
+ undef endian=
438
+ end
439
+
440
+ # little endian 8-byte unsigned integer
441
+ # @author LemonTree55
442
+ class Int64le < Int64
443
+ # @param [Integer,nil] value
444
+ undef endian=
445
+
446
+ # @param [Hash] options
447
+ # @option options [Integer] :value
448
+ def initialize(options = {})
449
+ opts = { value: options[:value], endian: :little }
450
+ super(opts)
451
+ end
452
+ end
453
+
454
+ # native endian 8-byte unsigned integer
455
+ # @author LemonTree55
456
+ class Int64n < Int64
457
+ # @param [Integer,nil] value
458
+ undef endian=
459
+
460
+ # @param [Hash] options
461
+ # @option options [Integer] :value
462
+ def initialize(options = {})
463
+ opts = { value: options[:value], endian: :native }
464
+ super(opts)
465
+ end
466
+ end
467
+
468
+ # 8-byte unsigned integer
469
+ # @author LemonTree55
470
+ class SInt64 < Int64
471
+ # @param [Hash] options
472
+ # @option options [Integer] :value
473
+ # @option options [:big, :little, :native] :endian
474
+ def initialize(options = {})
475
+ opts = options.slice(:value, :endian)
476
+ super(opts)
477
+ @packstr = { big: 'q>', little: 'q<', native: 'q' }
478
+ end
479
+ end
480
+
481
+ # big endian 8-byte unsigned integer
482
+ # @author Sylvain Daubert
483
+ class SInt64be < SInt64
484
+ undef endian=
485
+ end
486
+
487
+ # little endian 8-byte unsigned integer
488
+ # @author LemonTree55
489
+ class SInt64le < SInt64
490
+ # @param [Integer,nil] value
491
+ undef endian=
492
+
493
+ # @param [Hash] options
494
+ # @option options [Integer] :value
495
+ def initialize(options = {})
496
+ opts = { value: options[:value], endian: :little }
497
+ super(opts)
498
+ end
499
+ end
500
+
501
+ # native endian 8-byte unsigned integer
502
+ # @author LemonTree55
503
+ class SInt64n < SInt64
504
+ # @param [Integer,nil] value
505
+ undef endian=
506
+
507
+ # @param [Hash] options
508
+ # @option options [Integer] :value
509
+ def initialize(options = {})
510
+ opts = { value: options[:value], endian: :native }
511
+ super(opts)
512
+ end
513
+ end
514
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of BinStruct
4
+ # see https://github.com/lemontree55/bin_struct for more informations
5
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
+ # This program is published under MIT license.
8
+
9
+ module BinStruct
10
+ # Provides a class for creating strings preceeded by their length as a {Int}.
11
+ # By default, a null string will have one byte length (length byte set to 0).
12
+ # @author Sylvain Daubert
13
+ class IntString
14
+ include Fieldable
15
+
16
+ # internal string
17
+ # @return [String]
18
+ attr_reader :string
19
+
20
+ # @param [Hash] options
21
+ # @option options [Class] :length_type should be a {Int} subclass. Default to {Int8}.
22
+ # @option options [::String] :string String value. Default to +''+
23
+ def initialize(options = {})
24
+ @string = BinStruct::String.new.read(options[:string] || '')
25
+ @length = (options[:length_type] || Int8).new
26
+ calc_length
27
+ end
28
+
29
+ # @param [::String] str
30
+ # @return [IntString] self
31
+ def read(str)
32
+ unless str[0, @length.width].size == @length.width
33
+ raise Error,
34
+ "String too short for type #{@length.class.to_s.gsub(/.*::/, '')}"
35
+ end
36
+ @length.read str[0, @length.width]
37
+ @string.read str[@length.width, @length.to_i]
38
+ self
39
+ end
40
+
41
+ # @param [Integer] len
42
+ # @return [Integer]
43
+ def length=(len)
44
+ @length.from_human(len)
45
+ end
46
+
47
+ # @return [Integer]
48
+ def length
49
+ @length.to_i
50
+ end
51
+
52
+ # @param [#to_s] str
53
+ # @return [String]
54
+ def string=(str)
55
+ @length.value = str.to_s.size
56
+ @string = str.to_s
57
+ end
58
+
59
+ # Get binary string
60
+ # @return [::String]
61
+ def to_s
62
+ @length.to_s << @string.to_s
63
+ end
64
+
65
+ # Set from a human readable string
66
+ # @param [String] str
67
+ # @return [self]
68
+ def from_human(str)
69
+ @string.read str
70
+ calc_length
71
+ self
72
+ end
73
+
74
+ # Get human readable string
75
+ # @return [::String]
76
+ # @since 2.2.0
77
+ def to_human
78
+ @string
79
+ end
80
+
81
+ # Set length from internal string length
82
+ # @return [Integer]
83
+ def calc_length
84
+ @length.read @string.length
85
+ end
86
+
87
+ # Give binary string length (including +length+ field)
88
+ # @return [Integer]
89
+ def sz
90
+ to_s.size
91
+ end
92
+
93
+ # Say if IntString is empty
94
+ # @return [Boolean]
95
+ def empty?
96
+ length.zero?
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of BinStruct
4
+ # see https://github.com/lemontree55/bin_struct for more informations
5
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
+ # This program is published under MIT license.
8
+
9
+ module BinStruct
10
+ # This module is a mixin adding +length_from+ capacity to a type.
11
+ # +length_from+ capacity is the capacity, for a type, to gets its
12
+ # length from another object.
13
+ # @author Sylvain Daubert
14
+ # @since 3.0.0
15
+ module LengthFrom
16
+ # Max value returned by {#sz_to_read}.
17
+ MAX_SZ_TO_READ = 65_535
18
+
19
+ # Initialize +length from+ capacity.
20
+ # Should be call by extensed object's initialize.
21
+ # @param [Hash] options
22
+ # @option options [Types::Int,Proc] :length_from object or proc from which
23
+ # takes length when reading
24
+ # @return [void]
25
+ def initialize_length_from(options)
26
+ @length_from = options[:length_from]
27
+ end
28
+
29
+ # Return a substring from +str+ of length given in another object.
30
+ # @param [#to_s] str
31
+ # @return [String]
32
+ def read_with_length_from(str)
33
+ s = BinStruct.force_binary(str.to_s)
34
+ s[0, sz_to_read]
35
+ end
36
+
37
+ # Size to read, from length_from
38
+ # @return [Integer]
39
+ def sz_to_read
40
+ len = case @length_from
41
+ when Int
42
+ @length_from.to_i
43
+ when Proc
44
+ @length_from.call
45
+ else
46
+ MAX_SZ_TO_READ
47
+ end
48
+ [0, len].max
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of BinStruct
4
+ # see https://github.com/lemontree55/bin_struct for more informations
5
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
+ # This program is published under MIT license.
8
+
9
+ module BinStruct
10
+ # OUI type, defined as a set of 3 bytes
11
+ # oui = OUI.new
12
+ # oui.from_human('00:01:02')
13
+ # oui.to_human # => "00:01:02"
14
+ # @author Sylvain Daubert
15
+ class OUI < Fields
16
+ include Fieldable
17
+
18
+ # @attribute b2
19
+ # @return [Integer] left-most byte
20
+ define_field :b2, Int8
21
+ # @attribute b1
22
+ # @return [Integer] center byte
23
+ define_field :b1, Int8
24
+ # @attribute b0
25
+ # @return [Integer] right-most byte
26
+ define_field :b0, Int8
27
+
28
+ # Read a human-readable string to populate object
29
+ # @param [String] str
30
+ # @return [OUI] self
31
+ def from_human(str)
32
+ return self if str.nil?
33
+
34
+ bytes = str.split(':')
35
+ raise ArgumentError, 'not a OUI' unless bytes.size == 3
36
+
37
+ self[:b2].from_human(bytes[0].to_i(16))
38
+ self[:b1].from_human(bytes[1].to_i(16))
39
+ self[:b0].from_human(bytes[2].to_i(16))
40
+ self
41
+ end
42
+
43
+ # Get OUI in human readable form (colon-separated bytes)
44
+ # @return [String]
45
+ def to_human
46
+ fields.map { |m| '%02x' % self[m] }.join(':')
47
+ end
48
+ end
49
+ end