openc3 5.0.9 → 5.0.10

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of openc3 might be problematic. Click here for more details.

Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/data/config/_id_items.yaml +2 -1
  3. data/data/config/_id_params.yaml +2 -1
  4. data/data/config/_items.yaml +2 -1
  5. data/data/config/_params.yaml +2 -1
  6. data/data/config/command_modifiers.yaml +30 -0
  7. data/data/config/item_modifiers.yaml +10 -0
  8. data/data/config/microservice.yaml +12 -0
  9. data/data/config/param_item_modifiers.yaml +10 -0
  10. data/data/config/plugins.yaml +0 -9
  11. data/data/config/telemetry_modifiers.yaml +10 -0
  12. data/ext/openc3/ext/packet/packet.c +20 -2
  13. data/ext/openc3/ext/structure/structure.c +12 -17
  14. data/lib/openc3/accessors/accessor.rb +71 -0
  15. data/lib/openc3/accessors/binary_accessor.rb +1226 -0
  16. data/lib/openc3/accessors/cbor_accessor.rb +83 -0
  17. data/lib/openc3/accessors/html_accessor.rb +28 -0
  18. data/lib/openc3/accessors/json_accessor.rb +131 -0
  19. data/lib/openc3/accessors/xml_accessor.rb +67 -0
  20. data/lib/openc3/accessors.rb +23 -0
  21. data/lib/openc3/config/config_parser.rb +10 -4
  22. data/lib/openc3/core_ext/tempfile.rb +20 -0
  23. data/lib/openc3/models/cvt_model.rb +1 -10
  24. data/lib/openc3/models/microservice_model.rb +26 -0
  25. data/lib/openc3/packets/binary_accessor.rb +2 -1207
  26. data/lib/openc3/packets/packet.rb +106 -6
  27. data/lib/openc3/packets/packet_config.rb +30 -7
  28. data/lib/openc3/packets/parsers/limits_response_parser.rb +1 -3
  29. data/lib/openc3/packets/parsers/processor_parser.rb +1 -2
  30. data/lib/openc3/packets/structure.rb +39 -14
  31. data/lib/openc3/packets/structure_item.rb +15 -1
  32. data/lib/openc3/script/storage.rb +1 -0
  33. data/lib/openc3/utilities/local_mode.rb +3 -0
  34. data/lib/openc3/utilities/simulated_target.rb +3 -2
  35. data/lib/openc3/version.rb +5 -5
  36. data/lib/openc3.rb +1 -0
  37. metadata +38 -2
@@ -0,0 +1,1226 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU Affero General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ # Modified by OpenC3, Inc.
17
+ # All changes Copyright 2022, OpenC3, Inc.
18
+ # All Rights Reserved
19
+
20
+ # This file contains the implementation of the BinaryAccessor class.
21
+ # This class allows for easy reading and writing of binary data in Ruby
22
+
23
+ require 'openc3/accessors/accessor'
24
+ require 'openc3/ext/packet' if RUBY_ENGINE == 'ruby' and !ENV['OPENC3_NO_EXT']
25
+
26
+ module OpenC3
27
+ # Provides methods for binary reading and writing
28
+ class BinaryAccessor < Accessor
29
+ # Constants for ruby packing directives
30
+ PACK_8_BIT_INT = 'c'
31
+ PACK_NATIVE_16_BIT_INT = 's'
32
+ PACK_LITTLE_ENDIAN_16_BIT_UINT = 'v'
33
+ PACK_BIG_ENDIAN_16_BIT_UINT = 'n'
34
+ PACK_NATIVE_32_BIT_INT = 'l'
35
+ PACK_NATIVE_32_BIT_UINT = 'L'
36
+ PACK_NATIVE_64_BIT_INT = 'q'
37
+ PACK_NATIVE_64_BIT_UINT = 'Q'
38
+ PACK_LITTLE_ENDIAN_32_BIT_UINT = 'V'
39
+ PACK_BIG_ENDIAN_32_BIT_UINT = 'N'
40
+ PACK_LITTLE_ENDIAN_32_BIT_FLOAT = 'e'
41
+ PACK_LITTLE_ENDIAN_64_BIT_FLOAT = 'E'
42
+ PACK_BIG_ENDIAN_32_BIT_FLOAT = 'g'
43
+ PACK_BIG_ENDIAN_64_BIT_FLOAT = 'G'
44
+ PACK_NULL_TERMINATED_STRING = 'Z*'
45
+ PACK_BLOCK = 'a*'
46
+ PACK_8_BIT_INT_ARRAY = 'c*'
47
+ PACK_8_BIT_UINT_ARRAY = 'C*'
48
+ PACK_NATIVE_16_BIT_INT_ARRAY = 's*'
49
+ PACK_BIG_ENDIAN_16_BIT_UINT_ARRAY = 'n*'
50
+ PACK_LITTLE_ENDIAN_16_BIT_UINT_ARRAY = 'v*'
51
+ PACK_NATIVE_32_BIT_INT_ARRAY = 'l*'
52
+ PACK_BIG_ENDIAN_32_BIT_UINT_ARRAY = 'N*'
53
+ PACK_LITTLE_ENDIAN_32_BIT_UINT_ARRAY = 'V*'
54
+ PACK_NATIVE_64_BIT_INT_ARRAY = 'q*'
55
+ PACK_NATIVE_64_BIT_UINT_ARRAY = 'Q*'
56
+ PACK_LITTLE_ENDIAN_32_BIT_FLOAT_ARRAY = 'e*'
57
+ PACK_LITTLE_ENDIAN_64_BIT_FLOAT_ARRAY = 'E*'
58
+ PACK_BIG_ENDIAN_32_BIT_FLOAT_ARRAY = 'g*'
59
+ PACK_BIG_ENDIAN_64_BIT_FLOAT_ARRAY = 'G*'
60
+
61
+ if RUBY_ENGINE != 'ruby' or ENV['OPENC3_NO_EXT']
62
+ MIN_INT8 = -128
63
+ MAX_INT8 = 127
64
+ MAX_UINT8 = 255
65
+ MIN_INT16 = -32768
66
+ MAX_INT16 = 32767
67
+ MAX_UINT16 = 65535
68
+ MIN_INT32 = -(2**31)
69
+ MAX_INT32 = (2**31) - 1
70
+ MAX_UINT32 = (2**32) - 1
71
+ MIN_INT64 = -(2**63)
72
+ MAX_INT64 = (2**63) - 1
73
+ MAX_UINT64 = (2**64) - 1
74
+ end
75
+
76
+ # Additional Constants
77
+ ZERO_STRING = "\000"
78
+
79
+ # Valid data types
80
+ DATA_TYPES = [:INT, :UINT, :FLOAT, :STRING, :BLOCK]
81
+
82
+ # Valid overflow types
83
+ OVERFLOW_TYPES = [:TRUNCATE, :SATURATE, :ERROR, :ERROR_ALLOW_HEX]
84
+
85
+ protected
86
+
87
+ # Determines the endianness of the host running this code
88
+ #
89
+ # This method is protected to force the use of the constant
90
+ # HOST_ENDIANNESS rather than this method
91
+ #
92
+ # @return [Symbol] :BIG_ENDIAN or :LITTLE_ENDIAN
93
+ def self.get_host_endianness
94
+ value = 0x01020304
95
+ packed = [value].pack(PACK_NATIVE_32_BIT_UINT)
96
+ unpacked = packed.unpack(PACK_LITTLE_ENDIAN_32_BIT_UINT)[0]
97
+ if unpacked == value
98
+ :LITTLE_ENDIAN
99
+ else
100
+ :BIG_ENDIAN
101
+ end
102
+ end
103
+
104
+ def self.raise_buffer_error(read_write, buffer, data_type, given_bit_offset, given_bit_size)
105
+ raise ArgumentError, "#{buffer.length} byte buffer insufficient to #{read_write} #{data_type} at bit_offset #{given_bit_offset} with bit_size #{given_bit_size}"
106
+ end
107
+
108
+ public
109
+
110
+ # Store the host endianness so that it only has to be determined once
111
+ HOST_ENDIANNESS = get_host_endianness()
112
+ # Valid endianess
113
+ ENDIANNESS = [:BIG_ENDIAN, :LITTLE_ENDIAN]
114
+
115
+ def self.read_item(item, buffer)
116
+ return nil if item.data_type == :DERIVED
117
+ if item.array_size
118
+ return read_array(item.bit_offset, item.bit_size, item.data_type, item.array_size, buffer, item.endianness)
119
+ else
120
+ return read(item.bit_offset, item.bit_size, item.data_type, buffer, item.endianness)
121
+ end
122
+ end
123
+
124
+ def self.write_item(item, value, buffer)
125
+ return nil if item.data_type == :DERIVED
126
+ if item.array_size
127
+ return write_array(value, item.bit_offset, item.bit_size, item.data_type, item.array_size, buffer, item.endianness, item.overflow)
128
+ else
129
+ return write(value, item.bit_offset, item.bit_size, item.data_type, buffer, item.endianness, item.overflow)
130
+ end
131
+ end
132
+
133
+ if RUBY_ENGINE != 'ruby' or ENV['OPENC3_NO_EXT']
134
+ # Reads binary data of any data type from a buffer
135
+ #
136
+ # @param bit_offset [Integer] Bit offset to the start of the item. A
137
+ # negative number means to offset from the end of the buffer.
138
+ # @param bit_size [Integer] Size of the item in bits
139
+ # @param data_type [Symbol] {DATA_TYPES}
140
+ # @param buffer [String] Binary string buffer to read from
141
+ # @param endianness [Symbol] {ENDIANNESS}
142
+ # @return [Integer] value read from the buffer
143
+ def self.read(bit_offset, bit_size, data_type, buffer, endianness)
144
+ given_bit_offset = bit_offset
145
+ given_bit_size = bit_size
146
+
147
+ bit_offset = check_bit_offset_and_size(:read, given_bit_offset, given_bit_size, data_type, buffer)
148
+
149
+ # If passed a negative bit size with strings or blocks
150
+ # recalculate based on the buffer length
151
+ if (bit_size <= 0) && ((data_type == :STRING) || (data_type == :BLOCK))
152
+ bit_size = (buffer.length * 8) - bit_offset + bit_size
153
+ if bit_size == 0
154
+ return ""
155
+ elsif bit_size < 0
156
+ raise_buffer_error(:read, buffer, data_type, given_bit_offset, given_bit_size)
157
+ end
158
+ end
159
+
160
+ result, lower_bound, upper_bound = check_bounds_and_buffer_size(bit_offset, bit_size, buffer.length, endianness, data_type)
161
+ raise_buffer_error(:read, buffer, data_type, given_bit_offset, given_bit_size) unless result
162
+
163
+ if (data_type == :STRING) || (data_type == :BLOCK)
164
+ #######################################
165
+ # Handle :STRING and :BLOCK data types
166
+ #######################################
167
+
168
+ if byte_aligned(bit_offset)
169
+ if data_type == :STRING
170
+ return buffer[lower_bound..upper_bound].unpack('Z*')[0]
171
+ else
172
+ return buffer[lower_bound..upper_bound].unpack('a*')[0]
173
+ end
174
+ else
175
+ raise(ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}")
176
+ end
177
+
178
+ elsif (data_type == :INT) || (data_type == :UINT)
179
+ ###################################
180
+ # Handle :INT and :UINT data types
181
+ ###################################
182
+
183
+ if byte_aligned(bit_offset) && even_bit_size(bit_size)
184
+
185
+ if data_type == :INT
186
+ ###########################################################
187
+ # Handle byte-aligned 8, 16, 32, and 64 bit :INT
188
+ ###########################################################
189
+
190
+ case bit_size
191
+ when 8
192
+ return buffer[lower_bound].unpack(PACK_8_BIT_INT)[0]
193
+ when 16
194
+ if endianness == HOST_ENDIANNESS
195
+ return buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_16_BIT_INT)[0]
196
+ else # endianness != HOST_ENDIANNESS
197
+ temp = buffer[lower_bound..upper_bound].reverse
198
+ return temp.unpack(PACK_NATIVE_16_BIT_INT)[0]
199
+ end
200
+ when 32
201
+ if endianness == HOST_ENDIANNESS
202
+ return buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_32_BIT_INT)[0]
203
+ else # endianness != HOST_ENDIANNESS
204
+ temp = buffer[lower_bound..upper_bound].reverse
205
+ return temp.unpack(PACK_NATIVE_32_BIT_INT)[0]
206
+ end
207
+ when 64
208
+ if endianness == HOST_ENDIANNESS
209
+ return buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_64_BIT_INT)[0]
210
+ else # endianness != HOST_ENDIANNESS
211
+ temp = buffer[lower_bound..upper_bound].reverse
212
+ return temp.unpack(PACK_NATIVE_64_BIT_INT)[0]
213
+ end
214
+ end
215
+ else # data_type == :UINT
216
+ ###########################################################
217
+ # Handle byte-aligned 8, 16, 32, and 64 bit :UINT
218
+ ###########################################################
219
+
220
+ case bit_size
221
+ when 8
222
+ return buffer.getbyte(lower_bound)
223
+ when 16
224
+ if endianness == :BIG_ENDIAN
225
+ return buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_16_BIT_UINT)[0]
226
+ else # endianness == :LITTLE_ENDIAN
227
+ return buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_16_BIT_UINT)[0]
228
+ end
229
+ when 32
230
+ if endianness == :BIG_ENDIAN
231
+ return buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_32_BIT_UINT)[0]
232
+ else # endianness == :LITTLE_ENDIAN
233
+ return buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_32_BIT_UINT)[0]
234
+ end
235
+ when 64
236
+ if endianness == HOST_ENDIANNESS
237
+ return buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_64_BIT_UINT)[0]
238
+ else # endianness != HOST_ENDIANNESS
239
+ temp = buffer[lower_bound..upper_bound].reverse
240
+ return temp.unpack(PACK_NATIVE_64_BIT_UINT)[0]
241
+ end
242
+ end
243
+ end
244
+
245
+ else
246
+ ##########################
247
+ # Handle :INT and :UINT Bitfields
248
+ ##########################
249
+
250
+ # Extract Data for Bitfield
251
+ if endianness == :LITTLE_ENDIAN
252
+ # Bitoffset always refers to the most significant bit of a bitfield
253
+ num_bytes = (((bit_offset % 8) + bit_size - 1) / 8) + 1
254
+ upper_bound = bit_offset / 8
255
+ lower_bound = upper_bound - num_bytes + 1
256
+
257
+ if lower_bound < 0
258
+ raise(ArgumentError, "LITTLE_ENDIAN bitfield with bit_offset #{given_bit_offset} and bit_size #{given_bit_size} is invalid")
259
+ end
260
+
261
+ temp_data = buffer[lower_bound..upper_bound].reverse
262
+ else
263
+ temp_data = buffer[lower_bound..upper_bound]
264
+ end
265
+
266
+ # Determine temp upper bound
267
+ temp_upper = upper_bound - lower_bound
268
+
269
+ # Handle Bitfield
270
+ start_bits = bit_offset % 8
271
+ start_mask = ~(0xFF << (8 - start_bits))
272
+ total_bits = (temp_upper + 1) * 8
273
+ right_shift = total_bits - start_bits - bit_size
274
+
275
+ # Mask off unwanted bits at beginning
276
+ temp = temp_data.getbyte(0) & start_mask
277
+
278
+ if upper_bound > lower_bound
279
+ # Combine bytes into a FixNum
280
+ temp_data[1..temp_upper].each_byte { |temp_value| temp = temp << 8; temp = temp + temp_value }
281
+ end
282
+
283
+ # Shift off unwanted bits at end
284
+ temp = temp >> right_shift
285
+
286
+ if data_type == :INT
287
+ # Convert to negative if necessary
288
+ if (bit_size > 1) && (temp[bit_size - 1] == 1)
289
+ temp = -((1 << bit_size) - temp)
290
+ end
291
+ end
292
+
293
+ return temp
294
+ end
295
+
296
+ elsif data_type == :FLOAT
297
+ ##########################
298
+ # Handle :FLOAT data type
299
+ ##########################
300
+
301
+ if byte_aligned(bit_offset)
302
+ case bit_size
303
+ when 32
304
+ if endianness == :BIG_ENDIAN
305
+ return buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_32_BIT_FLOAT)[0]
306
+ else # endianness == :LITTLE_ENDIAN
307
+ return buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_32_BIT_FLOAT)[0]
308
+ end
309
+ when 64
310
+ if endianness == :BIG_ENDIAN
311
+ return buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_64_BIT_FLOAT)[0]
312
+ else # endianness == :LITTLE_ENDIAN
313
+ return buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_64_BIT_FLOAT)[0]
314
+ end
315
+ else
316
+ raise(ArgumentError, "bit_size is #{given_bit_size} but must be 32 or 64 for data_type #{data_type}")
317
+ end
318
+ else
319
+ raise(ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}")
320
+ end
321
+
322
+ else
323
+ ############################
324
+ # Handle Unknown data types
325
+ ############################
326
+
327
+ raise(ArgumentError, "data_type #{data_type} is not recognized")
328
+ end
329
+
330
+ return return_value
331
+ end
332
+
333
+ # Writes binary data of any data type to a buffer
334
+ #
335
+ # @param value [Varies] Value to write into the buffer
336
+ # @param bit_offset [Integer] Bit offset to the start of the item. A
337
+ # negative number means to offset from the end of the buffer.
338
+ # @param bit_size [Integer] Size of the item in bits
339
+ # @param data_type [Symbol] {DATA_TYPES}
340
+ # @param buffer [String] Binary string buffer to write to
341
+ # @param endianness [Symbol] {ENDIANNESS}
342
+ # @param overflow [Symbol] {OVERFLOW_TYPES}
343
+ # @return [Integer] value passed in as a parameter
344
+ def self.write(value, bit_offset, bit_size, data_type, buffer, endianness, overflow)
345
+ given_bit_offset = bit_offset
346
+ given_bit_size = bit_size
347
+
348
+ bit_offset = check_bit_offset_and_size(:write, given_bit_offset, given_bit_size, data_type, buffer)
349
+
350
+ # If passed a negative bit size with strings or blocks
351
+ # recalculate based on the value length in bytes
352
+ if (bit_size <= 0) && ((data_type == :STRING) || (data_type == :BLOCK))
353
+ value = value.to_s
354
+ bit_size = value.length * 8
355
+ end
356
+
357
+ result, lower_bound, upper_bound = check_bounds_and_buffer_size(bit_offset, bit_size, buffer.length, endianness, data_type)
358
+ raise_buffer_error(:write, buffer, data_type, given_bit_offset, given_bit_size) if !result && (given_bit_size > 0)
359
+
360
+ # Check overflow type
361
+ if (overflow != :TRUNCATE) && (overflow != :SATURATE) && (overflow != :ERROR) && (overflow != :ERROR_ALLOW_HEX)
362
+ raise(ArgumentError, "unknown overflow type #{overflow}")
363
+ end
364
+
365
+ if (data_type == :STRING) || (data_type == :BLOCK)
366
+ #######################################
367
+ # Handle :STRING and :BLOCK data types
368
+ #######################################
369
+ value = value.to_s
370
+
371
+ if byte_aligned(bit_offset)
372
+ temp = value
373
+ if given_bit_size <= 0
374
+ end_bytes = -(given_bit_size / 8)
375
+ old_upper_bound = buffer.length - 1 - end_bytes
376
+ # Lower bound + end_bytes can never be more than 1 byte outside of the given buffer
377
+ if (lower_bound + end_bytes) > buffer.length
378
+ raise_buffer_error(:write, buffer, data_type, given_bit_offset, given_bit_size)
379
+ end
380
+
381
+ if old_upper_bound < lower_bound
382
+ # String was completely empty
383
+ if end_bytes > 0
384
+ # Preserve bytes at end of buffer
385
+ buffer << "\000" * value.length
386
+ buffer[lower_bound + value.length, end_bytes] = buffer[lower_bound, end_bytes]
387
+ end
388
+ elsif bit_size == 0
389
+ # Remove entire string
390
+ buffer[lower_bound, old_upper_bound - lower_bound + 1] = ''
391
+ elsif upper_bound < old_upper_bound
392
+ # Remove extra bytes from old string
393
+ buffer[upper_bound + 1, old_upper_bound - upper_bound] = ''
394
+ elsif (upper_bound > old_upper_bound) && (end_bytes > 0)
395
+ # Preserve bytes at end of buffer
396
+ diff = upper_bound - old_upper_bound
397
+ buffer << "\000" * diff
398
+ buffer[upper_bound + 1, end_bytes] = buffer[old_upper_bound + 1, end_bytes]
399
+ end
400
+ else # given_bit_size > 0
401
+ byte_size = bit_size / 8
402
+ if value.length < byte_size
403
+ # Pad the requested size with zeros
404
+ temp = value.ljust(byte_size, "\000")
405
+ elsif value.length > byte_size
406
+ if overflow == :TRUNCATE
407
+ # Resize the value to fit the field
408
+ value[byte_size, value.length - byte_size] = ''
409
+ else
410
+ raise(ArgumentError, "value of #{value.length} bytes does not fit into #{byte_size} bytes for data_type #{data_type}")
411
+ end
412
+ end
413
+ end
414
+ if bit_size != 0
415
+ buffer[lower_bound, temp.length] = temp
416
+ end
417
+ else
418
+ raise(ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}")
419
+ end
420
+
421
+ elsif (data_type == :INT) || (data_type == :UINT)
422
+ ###################################
423
+ # Handle :INT data type
424
+ ###################################
425
+ value = Integer(value)
426
+ min_value, max_value, hex_max_value = get_check_overflow_ranges(bit_size, data_type)
427
+ value = check_overflow(value, min_value, max_value, hex_max_value, bit_size, data_type, overflow)
428
+
429
+ if byte_aligned(bit_offset) && even_bit_size(bit_size)
430
+ ###########################################################
431
+ # Handle byte-aligned 8, 16, 32, and 64 bit
432
+ ###########################################################
433
+
434
+ if data_type == :INT
435
+ ###########################################################
436
+ # Handle byte-aligned 8, 16, 32, and 64 bit :INT
437
+ ###########################################################
438
+
439
+ case bit_size
440
+ when 8
441
+ buffer.setbyte(lower_bound, value)
442
+ when 16
443
+ if endianness == HOST_ENDIANNESS
444
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_16_BIT_INT)
445
+ else # endianness != HOST_ENDIANNESS
446
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_16_BIT_INT).reverse
447
+ end
448
+ when 32
449
+ if endianness == HOST_ENDIANNESS
450
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_32_BIT_INT)
451
+ else # endianness != HOST_ENDIANNESS
452
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_32_BIT_INT).reverse
453
+ end
454
+ when 64
455
+ if endianness == HOST_ENDIANNESS
456
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_64_BIT_INT)
457
+ else # endianness != HOST_ENDIANNESS
458
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_64_BIT_INT).reverse
459
+ end
460
+ end
461
+ else # data_type == :UINT
462
+ ###########################################################
463
+ # Handle byte-aligned 8, 16, 32, and 64 bit :UINT
464
+ ###########################################################
465
+
466
+ case bit_size
467
+ when 8
468
+ buffer.setbyte(lower_bound, value)
469
+ when 16
470
+ if endianness == :BIG_ENDIAN
471
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_BIG_ENDIAN_16_BIT_UINT)
472
+ else # endianness == :LITTLE_ENDIAN
473
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_LITTLE_ENDIAN_16_BIT_UINT)
474
+ end
475
+ when 32
476
+ if endianness == :BIG_ENDIAN
477
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_BIG_ENDIAN_32_BIT_UINT)
478
+ else # endianness == :LITTLE_ENDIAN
479
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_LITTLE_ENDIAN_32_BIT_UINT)
480
+ end
481
+ when 64
482
+ if endianness == HOST_ENDIANNESS
483
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_64_BIT_UINT)
484
+ else # endianness != HOST_ENDIANNESS
485
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_NATIVE_64_BIT_UINT).reverse
486
+ end
487
+ end
488
+ end
489
+
490
+ else
491
+ ###########################################################
492
+ # Handle bit fields
493
+ ###########################################################
494
+
495
+ # Extract Existing Data
496
+ if endianness == :LITTLE_ENDIAN
497
+ # Bitoffset always refers to the most significant bit of a bitfield
498
+ num_bytes = (((bit_offset % 8) + bit_size - 1) / 8) + 1
499
+ upper_bound = bit_offset / 8
500
+ lower_bound = upper_bound - num_bytes + 1
501
+ if lower_bound < 0
502
+ raise(ArgumentError, "LITTLE_ENDIAN bitfield with bit_offset #{given_bit_offset} and bit_size #{given_bit_size} is invalid")
503
+ end
504
+
505
+ temp_data = buffer[lower_bound..upper_bound].reverse
506
+ else
507
+ temp_data = buffer[lower_bound..upper_bound]
508
+ end
509
+
510
+ # Determine temp upper bound
511
+ temp_upper = upper_bound - lower_bound
512
+
513
+ # Determine Values needed to Handle Bitfield
514
+ start_bits = bit_offset % 8
515
+ start_mask = (0xFF << (8 - start_bits))
516
+ total_bits = (temp_upper + 1) * 8
517
+ end_bits = total_bits - start_bits - bit_size
518
+ end_mask = ~(0xFF << end_bits)
519
+
520
+ # Add in Start Bits
521
+ temp = temp_data.getbyte(0) & start_mask
522
+
523
+ # Adjust value to correct number of bits
524
+ temp_mask = (2**bit_size) - 1
525
+ temp_value = value & temp_mask
526
+
527
+ # Add in New Data
528
+ temp = (temp << (bit_size - (8 - start_bits))) + temp_value
529
+
530
+ # Add in Remainder of Existing Data
531
+ temp = (temp << end_bits) + (temp_data.getbyte(temp_upper) & end_mask)
532
+
533
+ # Extract into an array of bytes
534
+ temp_array = []
535
+ (0..temp_upper).each { temp_array.insert(0, (temp & 0xFF)); temp = temp >> 8 }
536
+
537
+ # Store into data
538
+ if endianness == :LITTLE_ENDIAN
539
+ buffer[lower_bound..upper_bound] = temp_array.pack(PACK_8_BIT_UINT_ARRAY).reverse
540
+ else
541
+ buffer[lower_bound..upper_bound] = temp_array.pack(PACK_8_BIT_UINT_ARRAY)
542
+ end
543
+
544
+ end
545
+
546
+ elsif data_type == :FLOAT
547
+ ##########################
548
+ # Handle :FLOAT data type
549
+ ##########################
550
+ value = Float(value)
551
+
552
+ if byte_aligned(bit_offset)
553
+ case bit_size
554
+ when 32
555
+ if endianness == :BIG_ENDIAN
556
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_BIG_ENDIAN_32_BIT_FLOAT)
557
+ else # endianness == :LITTLE_ENDIAN
558
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_LITTLE_ENDIAN_32_BIT_FLOAT)
559
+ end
560
+ when 64
561
+ if endianness == :BIG_ENDIAN
562
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_BIG_ENDIAN_64_BIT_FLOAT)
563
+ else # endianness == :LITTLE_ENDIAN
564
+ buffer[lower_bound..upper_bound] = [value].pack(PACK_LITTLE_ENDIAN_64_BIT_FLOAT)
565
+ end
566
+ else
567
+ raise(ArgumentError, "bit_size is #{given_bit_size} but must be 32 or 64 for data_type #{data_type}")
568
+ end
569
+ else
570
+ raise(ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}")
571
+ end
572
+
573
+ else
574
+ ############################
575
+ # Handle Unknown data types
576
+ ############################
577
+
578
+ raise(ArgumentError, "data_type #{data_type} is not recognized")
579
+ end
580
+
581
+ return value
582
+ end
583
+
584
+ protected
585
+
586
+ # Check the bit size and bit offset for problems. Recalulate the bit offset
587
+ # and return back through the passed in pointer.
588
+ def self.check_bit_offset_and_size(read_or_write, given_bit_offset, given_bit_size, data_type, buffer)
589
+ bit_offset = given_bit_offset
590
+
591
+ if (given_bit_size <= 0) && (data_type != :STRING) && (data_type != :BLOCK)
592
+ raise(ArgumentError, "bit_size #{given_bit_size} must be positive for data types other than :STRING and :BLOCK")
593
+ end
594
+
595
+ if (given_bit_size <= 0) && (given_bit_offset < 0)
596
+ raise(ArgumentError, "negative or zero bit_sizes (#{given_bit_size}) cannot be given with negative bit_offsets (#{given_bit_offset})")
597
+ end
598
+
599
+ if given_bit_offset < 0
600
+ bit_offset = (buffer.length * 8) + bit_offset
601
+ if bit_offset < 0
602
+ raise_buffer_error(read_or_write, buffer, data_type, given_bit_offset, given_bit_size)
603
+ end
604
+ end
605
+
606
+ return bit_offset
607
+ end
608
+
609
+ # Calculate the bounds of the string to access the item based on the bit_offset and bit_size.
610
+ # Also determine if the buffer size is sufficient.
611
+ def self.check_bounds_and_buffer_size(bit_offset, bit_size, buffer_length, endianness, data_type)
612
+ result = true # Assume ok
613
+
614
+ # Define bounds of string to access this item
615
+ lower_bound = bit_offset / 8
616
+ upper_bound = (bit_offset + bit_size - 1) / 8
617
+
618
+ # Sanity check buffer size
619
+ if upper_bound >= buffer_length
620
+ # If it's not the special case of little endian bit field then we fail and return false
621
+ if !((endianness == :LITTLE_ENDIAN) &&
622
+ ((data_type == :INT) || (data_type == :UINT)) &&
623
+ # Not byte aligned with an even bit size
624
+ (!((byte_aligned(bit_offset)) && (even_bit_size(bit_size)))) &&
625
+ (lower_bound < buffer_length)
626
+ )
627
+ result = false
628
+ end
629
+ end
630
+ return result, lower_bound, upper_bound
631
+ end
632
+
633
+ def self.get_check_overflow_ranges(bit_size, data_type)
634
+ min_value = 0 # Default for UINT cases
635
+
636
+ case bit_size
637
+ when 8
638
+ hex_max_value = MAX_UINT8
639
+ if data_type == :INT
640
+ min_value = MIN_INT8
641
+ max_value = MAX_INT8
642
+ else
643
+ max_value = MAX_UINT8
644
+ end
645
+ when 16
646
+ hex_max_value = MAX_UINT16
647
+ if data_type == :INT
648
+ min_value = MIN_INT16
649
+ max_value = MAX_INT16
650
+ else
651
+ max_value = MAX_UINT16
652
+ end
653
+ when 32
654
+ hex_max_value = MAX_UINT32
655
+ if data_type == :INT
656
+ min_value = MIN_INT32
657
+ max_value = MAX_INT32
658
+ else
659
+ max_value = MAX_UINT32
660
+ end
661
+ when 64
662
+ hex_max_value = MAX_UINT64
663
+ if data_type == :INT
664
+ min_value = MIN_INT64
665
+ max_value = MAX_INT64
666
+ else
667
+ max_value = MAX_UINT64
668
+ end
669
+ else # Bitfield
670
+ if data_type == :INT
671
+ # Note signed integers must allow up to the maximum unsigned value to support values given in hex
672
+ if bit_size > 1
673
+ max_value = 2**(bit_size - 1)
674
+ # min_value = -(2 ** bit_size - 1)
675
+ min_value = -max_value
676
+ # max_value = (2 ** bit_size - 1) - 1
677
+ max_value -= 1
678
+ # hex_max_value = (2 ** bit_size) - 1
679
+ hex_max_value = (2**bit_size) - 1
680
+ else # 1-bit signed
681
+ min_value = -1
682
+ max_value = 1
683
+ hex_max_value = 1
684
+ end
685
+ else
686
+ max_value = (2**bit_size) - 1
687
+ hex_max_value = max_value
688
+ end
689
+ end
690
+
691
+ return min_value, max_value, hex_max_value
692
+ end
693
+
694
+ def self.byte_aligned(value)
695
+ (value % 8) == 0
696
+ end
697
+
698
+ def self.even_bit_size(bit_size)
699
+ (bit_size == 8) || (bit_size == 16) || (bit_size == 32) || (bit_size == 64)
700
+ end
701
+
702
+ public
703
+
704
+ end
705
+
706
+ # Reads an array of binary data of any data type from a buffer
707
+ #
708
+ # @param bit_offset [Integer] Bit offset to the start of the array. A
709
+ # negative number means to offset from the end of the buffer.
710
+ # @param bit_size [Integer] Size of each item in the array in bits
711
+ # @param data_type [Symbol] {DATA_TYPES}
712
+ # @param array_size [Integer] Size in bits of the array. 0 or negative means
713
+ # fill the array with as many bit_size number of items that exist (negative
714
+ # means excluding the final X number of bits).
715
+ # @param buffer [String] Binary string buffer to read from
716
+ # @param endianness [Symbol] {ENDIANNESS}
717
+ # @return [Array] Array created from reading the buffer
718
+ def self.read_array(bit_offset, bit_size, data_type, array_size, buffer, endianness)
719
+ # Save given values of bit offset, bit size, and array_size
720
+ given_bit_offset = bit_offset
721
+ given_bit_size = bit_size
722
+ given_array_size = array_size
723
+
724
+ # Handle negative and zero bit sizes
725
+ raise ArgumentError, "bit_size #{given_bit_size} must be positive for arrays" if bit_size <= 0
726
+
727
+ # Handle negative bit offsets
728
+ if bit_offset < 0
729
+ bit_offset = ((buffer.length * 8) + bit_offset)
730
+ raise_buffer_error(:read, buffer, data_type, given_bit_offset, given_bit_size) if bit_offset < 0
731
+ end
732
+
733
+ # Handle negative and zero array sizes
734
+ if array_size <= 0
735
+ if given_bit_offset < 0
736
+ raise ArgumentError, "negative or zero array_size (#{given_array_size}) cannot be given with negative bit_offset (#{given_bit_offset})"
737
+ else
738
+ array_size = ((buffer.length * 8) - bit_offset + array_size)
739
+ if array_size == 0
740
+ return []
741
+ elsif array_size < 0
742
+ raise_buffer_error(:read, buffer, data_type, given_bit_offset, given_bit_size)
743
+ end
744
+ end
745
+ end
746
+
747
+ # Calculate number of items in the array
748
+ # If there is a remainder then we have a problem
749
+ raise ArgumentError, "array_size #{given_array_size} not a multiple of bit_size #{given_bit_size}" if array_size % bit_size != 0
750
+
751
+ num_items = array_size / bit_size
752
+
753
+ # Define bounds of string to access this item
754
+ lower_bound = bit_offset / 8
755
+ upper_bound = (bit_offset + array_size - 1) / 8
756
+
757
+ # Check for byte alignment
758
+ byte_aligned = ((bit_offset % 8) == 0)
759
+
760
+ case data_type
761
+ when :STRING, :BLOCK
762
+ #######################################
763
+ # Handle :STRING and :BLOCK data types
764
+ #######################################
765
+
766
+ if byte_aligned
767
+ value = []
768
+ num_items.times do
769
+ value << self.read(bit_offset, bit_size, data_type, buffer, endianness)
770
+ bit_offset += bit_size
771
+ end
772
+ else
773
+ raise ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}"
774
+ end
775
+
776
+ when :INT, :UINT
777
+ ###################################
778
+ # Handle :INT and :UINT data types
779
+ ###################################
780
+
781
+ if byte_aligned and (bit_size == 8 or bit_size == 16 or bit_size == 32 or bit_size == 64)
782
+ ###########################################################
783
+ # Handle byte-aligned 8, 16, 32, and 64 bit :INT and :UINT
784
+ ###########################################################
785
+
786
+ case bit_size
787
+ when 8
788
+ if data_type == :INT
789
+ value = buffer[lower_bound..upper_bound].unpack(PACK_8_BIT_INT_ARRAY)
790
+ else # data_type == :UINT
791
+ value = buffer[lower_bound..upper_bound].unpack(PACK_8_BIT_UINT_ARRAY)
792
+ end
793
+
794
+ when 16
795
+ if data_type == :INT
796
+ if endianness == HOST_ENDIANNESS
797
+ value = buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_16_BIT_INT_ARRAY)
798
+ else # endianness != HOST_ENDIANNESS
799
+ temp = self.byte_swap_buffer(buffer[lower_bound..upper_bound], 2)
800
+ value = temp.to_s.unpack(PACK_NATIVE_16_BIT_INT_ARRAY)
801
+ end
802
+ else # data_type == :UINT
803
+ if endianness == :BIG_ENDIAN
804
+ value = buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_16_BIT_UINT_ARRAY)
805
+ else # endianness == :LITTLE_ENDIAN
806
+ value = buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_16_BIT_UINT_ARRAY)
807
+ end
808
+ end
809
+
810
+ when 32
811
+ if data_type == :INT
812
+ if endianness == HOST_ENDIANNESS
813
+ value = buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_32_BIT_INT_ARRAY)
814
+ else # endianness != HOST_ENDIANNESS
815
+ temp = self.byte_swap_buffer(buffer[lower_bound..upper_bound], 4)
816
+ value = temp.to_s.unpack(PACK_NATIVE_32_BIT_INT_ARRAY)
817
+ end
818
+ else # data_type == :UINT
819
+ if endianness == :BIG_ENDIAN
820
+ value = buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_32_BIT_UINT_ARRAY)
821
+ else # endianness == :LITTLE_ENDIAN
822
+ value = buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_32_BIT_UINT_ARRAY)
823
+ end
824
+ end
825
+
826
+ when 64
827
+ if data_type == :INT
828
+ if endianness == HOST_ENDIANNESS
829
+ value = buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_64_BIT_INT_ARRAY)
830
+ else # endianness != HOST_ENDIANNESS
831
+ temp = self.byte_swap_buffer(buffer[lower_bound..upper_bound], 8)
832
+ value = temp.to_s.unpack(PACK_NATIVE_64_BIT_INT_ARRAY)
833
+ end
834
+ else # data_type == :UINT
835
+ if endianness == HOST_ENDIANNESS
836
+ value = buffer[lower_bound..upper_bound].unpack(PACK_NATIVE_64_BIT_UINT_ARRAY)
837
+ else # endianness != HOST_ENDIANNESS
838
+ temp = self.byte_swap_buffer(buffer[lower_bound..upper_bound], 8)
839
+ value = temp.to_s.unpack(PACK_NATIVE_64_BIT_UINT_ARRAY)
840
+ end
841
+ end
842
+ end
843
+
844
+ else
845
+ ##################################
846
+ # Handle :INT and :UINT Bitfields
847
+ ##################################
848
+ raise ArgumentError, "read_array does not support little endian bit fields with bit_size greater than 1-bit" if endianness == :LITTLE_ENDIAN and bit_size > 1
849
+
850
+ value = []
851
+ num_items.times do
852
+ value << self.read(bit_offset, bit_size, data_type, buffer, endianness)
853
+ bit_offset += bit_size
854
+ end
855
+ end
856
+
857
+ when :FLOAT
858
+ ##########################
859
+ # Handle :FLOAT data type
860
+ ##########################
861
+
862
+ if byte_aligned
863
+ case bit_size
864
+ when 32
865
+ if endianness == :BIG_ENDIAN
866
+ value = buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_32_BIT_FLOAT_ARRAY)
867
+ else # endianness == :LITTLE_ENDIAN
868
+ value = buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_32_BIT_FLOAT_ARRAY)
869
+ end
870
+
871
+ when 64
872
+ if endianness == :BIG_ENDIAN
873
+ value = buffer[lower_bound..upper_bound].unpack(PACK_BIG_ENDIAN_64_BIT_FLOAT_ARRAY)
874
+ else # endianness == :LITTLE_ENDIAN
875
+ value = buffer[lower_bound..upper_bound].unpack(PACK_LITTLE_ENDIAN_64_BIT_FLOAT_ARRAY)
876
+ end
877
+
878
+ else
879
+ raise ArgumentError, "bit_size is #{given_bit_size} but must be 32 or 64 for data_type #{data_type}"
880
+ end
881
+
882
+ else
883
+ raise ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}"
884
+ end
885
+
886
+ else
887
+ ############################
888
+ # Handle Unknown data types
889
+ ############################
890
+
891
+ raise ArgumentError, "data_type #{data_type} is not recognized"
892
+ end
893
+
894
+ value
895
+ end # def read_array
896
+
897
+ # Writes an array of binary data of any data type to a buffer
898
+ #
899
+ # @param values [Array] Values to write into the buffer
900
+ # @param bit_offset [Integer] Bit offset to the start of the array. A
901
+ # negative number means to offset from the end of the buffer.
902
+ # @param bit_size [Integer] Size of each item in the array in bits
903
+ # @param data_type [Symbol] {DATA_TYPES}
904
+ # @param array_size [Integer] Size in bits of the array as represented in the buffer.
905
+ # Size 0 means to fill the buffer with as many bit_size number of items that exist
906
+ # (negative means excluding the final X number of bits).
907
+ # @param buffer [String] Binary string buffer to write to
908
+ # @param endianness [Symbol] {ENDIANNESS}
909
+ # @return [Array] values passed in as a parameter
910
+ def self.write_array(values, bit_offset, bit_size, data_type, array_size, buffer, endianness, overflow)
911
+ # Save given values of bit offset, bit size, and array_size
912
+ given_bit_offset = bit_offset
913
+ given_bit_size = bit_size
914
+ given_array_size = array_size
915
+
916
+ # Verify an array was given
917
+ raise ArgumentError, "values must be an Array type class is #{values.class}" unless values.kind_of? Array
918
+
919
+ # Handle negative and zero bit sizes
920
+ raise ArgumentError, "bit_size #{given_bit_size} must be positive for arrays" if bit_size <= 0
921
+
922
+ # Handle negative bit offsets
923
+ if bit_offset < 0
924
+ bit_offset = ((buffer.length * 8) + bit_offset)
925
+ raise_buffer_error(:write, buffer, data_type, given_bit_offset, given_bit_size) if bit_offset < 0
926
+ end
927
+
928
+ # Handle negative and zero array sizes
929
+ if array_size <= 0
930
+ if given_bit_offset < 0
931
+ raise ArgumentError, "negative or zero array_size (#{given_array_size}) cannot be given with negative bit_offset (#{given_bit_offset})"
932
+ else
933
+ end_bytes = -(given_array_size / 8)
934
+ lower_bound = bit_offset / 8
935
+ upper_bound = (bit_offset + (bit_size * values.length) - 1) / 8
936
+ old_upper_bound = buffer.length - 1 - end_bytes
937
+
938
+ if upper_bound < old_upper_bound
939
+ # Remove extra bytes from old buffer
940
+ buffer[(upper_bound + 1)..old_upper_bound] = ''
941
+ elsif upper_bound > old_upper_bound
942
+ # Grow buffer and preserve bytes at end of buffer if necesssary
943
+ buffer_length = buffer.length
944
+ diff = upper_bound - old_upper_bound
945
+ buffer << ZERO_STRING * diff
946
+ if end_bytes > 0
947
+ buffer[(upper_bound + 1)..(buffer.length - 1)] = buffer[(old_upper_bound + 1)..(buffer_length - 1)]
948
+ end
949
+ end
950
+
951
+ array_size = ((buffer.length * 8) - bit_offset + array_size)
952
+ end
953
+ end
954
+
955
+ # Get data bounds for this array
956
+ lower_bound = bit_offset / 8
957
+ upper_bound = (bit_offset + array_size - 1) / 8
958
+ num_bytes = upper_bound - lower_bound + 1
959
+
960
+ # Check for byte alignment
961
+ byte_aligned = ((bit_offset % 8) == 0)
962
+
963
+ # Calculate the number of writes
964
+ num_writes = array_size / bit_size
965
+ # Check for a negative array_size and adjust the number of writes
966
+ # to simply be the number of values in the passed in array
967
+ if given_array_size <= 0
968
+ num_writes = values.length
969
+ end
970
+
971
+ # Ensure the buffer has enough room
972
+ if bit_offset + num_writes * bit_size > buffer.length * 8
973
+ raise_buffer_error(:write, buffer, data_type, given_bit_offset, given_bit_size)
974
+ end
975
+
976
+ # Ensure the given_array_size is an even multiple of bit_size
977
+ raise ArgumentError, "array_size #{given_array_size} not a multiple of bit_size #{given_bit_size}" if array_size % bit_size != 0
978
+
979
+ raise ArgumentError, "too many values #{values.length} for given array_size #{given_array_size} and bit_size #{given_bit_size}" if num_writes < values.length
980
+
981
+ # Check overflow type
982
+ raise "unknown overflow type #{overflow}" unless OVERFLOW_TYPES.include?(overflow)
983
+
984
+ case data_type
985
+ when :STRING, :BLOCK
986
+ #######################################
987
+ # Handle :STRING and :BLOCK data types
988
+ #######################################
989
+
990
+ if byte_aligned
991
+ num_writes.times do |index|
992
+ self.write(values[index], bit_offset, bit_size, data_type, buffer, endianness, overflow)
993
+ bit_offset += bit_size
994
+ end
995
+ else
996
+ raise ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}"
997
+ end
998
+
999
+ when :INT, :UINT
1000
+ ###################################
1001
+ # Handle :INT and :UINT data types
1002
+ ###################################
1003
+
1004
+ if byte_aligned and (bit_size == 8 or bit_size == 16 or bit_size == 32 or bit_size == 64)
1005
+ ###########################################################
1006
+ # Handle byte-aligned 8, 16, 32, and 64 bit :INT and :UINT
1007
+ ###########################################################
1008
+
1009
+ case bit_size
1010
+ when 8
1011
+ if data_type == :INT
1012
+ values = self.check_overflow_array(values, MIN_INT8, MAX_INT8, MAX_UINT8, bit_size, data_type, overflow)
1013
+ packed = values.pack(PACK_8_BIT_INT_ARRAY)
1014
+ else # data_type == :UINT
1015
+ values = self.check_overflow_array(values, 0, MAX_UINT8, MAX_UINT8, bit_size, data_type, overflow)
1016
+ packed = values.pack(PACK_8_BIT_UINT_ARRAY)
1017
+ end
1018
+
1019
+ when 16
1020
+ if data_type == :INT
1021
+ values = self.check_overflow_array(values, MIN_INT16, MAX_INT16, MAX_UINT16, bit_size, data_type, overflow)
1022
+ if endianness == HOST_ENDIANNESS
1023
+ packed = values.pack(PACK_NATIVE_16_BIT_INT_ARRAY)
1024
+ else # endianness != HOST_ENDIANNESS
1025
+ packed = values.pack(PACK_NATIVE_16_BIT_INT_ARRAY)
1026
+ self.byte_swap_buffer!(packed, 2)
1027
+ end
1028
+ else # data_type == :UINT
1029
+ values = self.check_overflow_array(values, 0, MAX_UINT16, MAX_UINT16, bit_size, data_type, overflow)
1030
+ if endianness == :BIG_ENDIAN
1031
+ packed = values.pack(PACK_BIG_ENDIAN_16_BIT_UINT_ARRAY)
1032
+ else # endianness == :LITTLE_ENDIAN
1033
+ packed = values.pack(PACK_LITTLE_ENDIAN_16_BIT_UINT_ARRAY)
1034
+ end
1035
+ end
1036
+
1037
+ when 32
1038
+ if data_type == :INT
1039
+ values = self.check_overflow_array(values, MIN_INT32, MAX_INT32, MAX_UINT32, bit_size, data_type, overflow)
1040
+ if endianness == HOST_ENDIANNESS
1041
+ packed = values.pack(PACK_NATIVE_32_BIT_INT_ARRAY)
1042
+ else # endianness != HOST_ENDIANNESS
1043
+ packed = values.pack(PACK_NATIVE_32_BIT_INT_ARRAY)
1044
+ self.byte_swap_buffer!(packed, 4)
1045
+ end
1046
+ else # data_type == :UINT
1047
+ values = self.check_overflow_array(values, 0, MAX_UINT32, MAX_UINT32, bit_size, data_type, overflow)
1048
+ if endianness == :BIG_ENDIAN
1049
+ packed = values.pack(PACK_BIG_ENDIAN_32_BIT_UINT_ARRAY)
1050
+ else # endianness == :LITTLE_ENDIAN
1051
+ packed = values.pack(PACK_LITTLE_ENDIAN_32_BIT_UINT_ARRAY)
1052
+ end
1053
+ end
1054
+
1055
+ when 64
1056
+ if data_type == :INT
1057
+ values = self.check_overflow_array(values, MIN_INT64, MAX_INT64, MAX_UINT64, bit_size, data_type, overflow)
1058
+ if endianness == HOST_ENDIANNESS
1059
+ packed = values.pack(PACK_NATIVE_64_BIT_INT_ARRAY)
1060
+ else # endianness != HOST_ENDIANNESS
1061
+ packed = values.pack(PACK_NATIVE_64_BIT_INT_ARRAY)
1062
+ self.byte_swap_buffer!(packed, 8)
1063
+ end
1064
+ else # data_type == :UINT
1065
+ values = self.check_overflow_array(values, 0, MAX_UINT64, MAX_UINT64, bit_size, data_type, overflow)
1066
+ if endianness == HOST_ENDIANNESS
1067
+ packed = values.pack(PACK_NATIVE_64_BIT_UINT_ARRAY)
1068
+ else # endianness != HOST_ENDIANNESS
1069
+ packed = values.pack(PACK_NATIVE_64_BIT_UINT_ARRAY)
1070
+ self.byte_swap_buffer!(packed, 8)
1071
+ end
1072
+ end
1073
+ end
1074
+
1075
+ # Adjust packed size to hold number of items written
1076
+ buffer[lower_bound..upper_bound] = adjust_packed_size(num_bytes, packed) if num_bytes > 0
1077
+
1078
+ else
1079
+ ##################################
1080
+ # Handle :INT and :UINT Bitfields
1081
+ ##################################
1082
+
1083
+ raise ArgumentError, "write_array does not support little endian bit fields with bit_size greater than 1-bit" if endianness == :LITTLE_ENDIAN and bit_size > 1
1084
+
1085
+ num_writes.times do |index|
1086
+ self.write(values[index], bit_offset, bit_size, data_type, buffer, endianness, overflow)
1087
+ bit_offset += bit_size
1088
+ end
1089
+ end
1090
+
1091
+ when :FLOAT
1092
+ ##########################
1093
+ # Handle :FLOAT data type
1094
+ ##########################
1095
+
1096
+ if byte_aligned
1097
+ case bit_size
1098
+ when 32
1099
+ if endianness == :BIG_ENDIAN
1100
+ packed = values.pack(PACK_BIG_ENDIAN_32_BIT_FLOAT_ARRAY)
1101
+ else # endianness == :LITTLE_ENDIAN
1102
+ packed = values.pack(PACK_LITTLE_ENDIAN_32_BIT_FLOAT_ARRAY)
1103
+ end
1104
+
1105
+ when 64
1106
+ if endianness == :BIG_ENDIAN
1107
+ packed = values.pack(PACK_BIG_ENDIAN_64_BIT_FLOAT_ARRAY)
1108
+ else # endianness == :LITTLE_ENDIAN
1109
+ packed = values.pack(PACK_LITTLE_ENDIAN_64_BIT_FLOAT_ARRAY)
1110
+ end
1111
+
1112
+ else
1113
+ raise ArgumentError, "bit_size is #{given_bit_size} but must be 32 or 64 for data_type #{data_type}"
1114
+ end
1115
+
1116
+ # Adjust packed size to hold number of items written
1117
+ buffer[lower_bound..upper_bound] = adjust_packed_size(num_bytes, packed) if num_bytes > 0
1118
+
1119
+ else
1120
+ raise ArgumentError, "bit_offset #{given_bit_offset} is not byte aligned for data_type #{data_type}"
1121
+ end
1122
+
1123
+ else
1124
+ ############################
1125
+ # Handle Unknown data types
1126
+ ############################
1127
+ raise ArgumentError, "data_type #{data_type} is not recognized"
1128
+ end # case data_type
1129
+
1130
+ values
1131
+ end # def write_array
1132
+
1133
+ # Adjusts the packed array to be the given number of bytes
1134
+ #
1135
+ # @param num_bytes [Integer] The desired number of bytes
1136
+ # @param packed [Array] The packed data buffer
1137
+ def self.adjust_packed_size(num_bytes, packed)
1138
+ difference = num_bytes - packed.length
1139
+ if difference > 0
1140
+ packed << (ZERO_STRING * difference)
1141
+ elsif difference < 0
1142
+ packed = packed[0..(packed.length - 1 + difference)]
1143
+ end
1144
+ packed
1145
+ end
1146
+
1147
+ # Byte swaps every X bytes of data in a buffer overwriting the buffer
1148
+ #
1149
+ # @param buffer [String] Buffer to modify
1150
+ # @param num_bytes_per_word [Integer] Number of bytes per word that will be swapped
1151
+ # @return [String] buffer passed in as a parameter
1152
+ def self.byte_swap_buffer!(buffer, num_bytes_per_word)
1153
+ num_swaps = buffer.length / num_bytes_per_word
1154
+ index = 0
1155
+ num_swaps.times do
1156
+ range = index..(index + num_bytes_per_word - 1)
1157
+ buffer[range] = buffer[range].reverse
1158
+ index += num_bytes_per_word
1159
+ end
1160
+ buffer
1161
+ end
1162
+
1163
+ # Byte swaps every X bytes of data in a buffer into a new buffer
1164
+ #
1165
+ # @param buffer [String] Buffer that will be copied then modified
1166
+ # @param num_bytes_per_word [Integer] Number of bytes per word that will be swapped
1167
+ # @return [String] modified buffer
1168
+ def self.byte_swap_buffer(buffer, num_bytes_per_word)
1169
+ buffer = buffer.clone
1170
+ self.byte_swap_buffer!(buffer, num_bytes_per_word)
1171
+ end
1172
+
1173
+ # Checks for overflow of an integer data type
1174
+ #
1175
+ # @param value [Integer] Value to write into the buffer
1176
+ # @param min_value [Integer] Minimum allowed value
1177
+ # @param max_value [Integer] Maximum allowed value
1178
+ # @param hex_max_value [Integer] Maximum allowed value if specified in hex
1179
+ # @param bit_size [Integer] Size of the item in bits
1180
+ # @param data_type [Symbol] {DATA_TYPES}
1181
+ # @param overflow [Symbol] {OVERFLOW_TYPES}
1182
+ # @return [Integer] Potentially modified value
1183
+ def self.check_overflow(value, min_value, max_value, hex_max_value, bit_size, data_type, overflow)
1184
+ if overflow == :TRUNCATE
1185
+ # Note this will always convert to unsigned equivalent for signed integers
1186
+ value = value % (hex_max_value + 1)
1187
+ else
1188
+ if value > max_value
1189
+ if overflow == :SATURATE
1190
+ value = max_value
1191
+ else
1192
+ if overflow == :ERROR or value > hex_max_value
1193
+ raise ArgumentError, "value of #{value} invalid for #{bit_size}-bit #{data_type}"
1194
+ end
1195
+ end
1196
+ elsif value < min_value
1197
+ if overflow == :SATURATE
1198
+ value = min_value
1199
+ else
1200
+ raise ArgumentError, "value of #{value} invalid for #{bit_size}-bit #{data_type}"
1201
+ end
1202
+ end
1203
+ end
1204
+ value
1205
+ end
1206
+
1207
+ # Checks for overflow of an array of integer data types
1208
+ #
1209
+ # @param values [Array[Integer]] Values to write into the buffer
1210
+ # @param min_value [Integer] Minimum allowed value
1211
+ # @param max_value [Integer] Maximum allowed value
1212
+ # @param hex_max_value [Integer] Maximum allowed value if specified in hex
1213
+ # @param bit_size [Integer] Size of the item in bits
1214
+ # @param data_type [Symbol] {DATA_TYPES}
1215
+ # @param overflow [Symbol] {OVERFLOW_TYPES}
1216
+ # @return [Array[Integer]] Potentially modified values
1217
+ def self.check_overflow_array(values, min_value, max_value, hex_max_value, bit_size, data_type, overflow)
1218
+ if overflow != :TRUNCATE
1219
+ values.each_with_index do |value, index|
1220
+ values[index] = check_overflow(value, min_value, max_value, hex_max_value, bit_size, data_type, overflow)
1221
+ end
1222
+ end
1223
+ values
1224
+ end
1225
+ end # class BinaryAccessor
1226
+ end