openc3 5.0.9 → 5.0.10

Sign up to get free protection for your applications and to get access to all the features.
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