bin_struct 0.1.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.
@@ -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