0

I have a socket server listening for UDP packets from a GSM device. Some of the data comes in as multibytes, such as time, which requires multibytes for accuracy. This is an example:

179,248,164,14

The bytes are represented in decimal notation. My goal is to convert that into seconds:

245692595

I am trying to do that and was told:

"You must take those 4 bytes and place them into a single long integer in little endian format. If you are using Python to read and encode the data, you will need to look at using the .read() and struct.unpack() methods to successfully convert it to an integer. The resulting value is the number of seconds since 01/01/2000."

So, I tried to do this:

%w(179 248 164 14).sort.map(&:to_i).inject(&:+)
 => 605 

And I obviously am getting the wrong answer.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
JohnMerlino
  • 3,900
  • 4
  • 57
  • 89

1 Answers1

2

You should use the pack and unpack methods to do this:

[179,248,164,14].pack('C*').unpack('I')[0]
# => 245692595

It's not about adding them together, though. You're doing the math wrong. The correct way to do it with inject is this:

 [179,248,164,14].reverse.inject { |s,v| s * 256 + v }
 # => 245692595

Note you will have to account for byte ordering when representing binary numbers that are more than one byte long.

If the original data is already a binary string you won't have to perform the pack operation and can proceed directly to the unpack phase.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • +1 for `pack`/`unpack`. While the `inject` method works, I think it'd be slower if a lot of values are being parsed. – the Tin Man Jun 19 '13 at 20:00
  • You'd have to benchmark to be sure, but `unpack` is usually very fast. – tadman Jun 19 '13 at 20:46
  • what `pack('C*')` does? why did you use it? – Arup Rakshit Jun 19 '13 at 20:47
  • @OMG 1.9.3p0 :039 > 'h'.unpack('C*') => [104] 1.9.3p0 :040 > 'h'.unpack('B*') => ["01101000"] 1.9.3p0 :041 > "01101000".to_i(2).to_s(10) => "104". It gives the unsigned integer value of the binary. – JohnMerlino Jun 19 '13 at 20:55
  • @OMG `pack('C*')` is used to assemble a list of byte values (`C` standing for "unsigned char") into a binary string. – tadman Jun 19 '13 at 20:59
  • @tadman according to the documentation, using 'I'uses the native endian, which is the endianness of the computer it is being run on. However, I must ensure they are placed in little endian format. Is there a more suitable parameter to unpack? – JohnMerlino Jun 20 '13 at 15:46
  • I am not finding too many examples online of use of 'I' with unpack – JohnMerlino Jun 20 '13 at 16:31
  • You don't need many examples, just [read the documentation](http://ruby-doc.org/core-2.0/Array.html#method-i-pack). Most of these types work the same way conceptually. There are only a few exceptions. You can use either `N` or `V` depending on what ordering you require. – tadman Jun 20 '13 at 17:27
  • @tadman in your inject example, why are you adding the decimal values with 256? Shouldn't you be multiplying the decimal value (v) with 256? – JohnMerlino Jun 24 '13 at 16:18
  • Oh wait, I think I see why you did it that way. First iteration, s is 0, so you just return the value of v (14), and next iteration you do multiply 14 by 256. Each digit of the decimal represents 1 byte. – JohnMerlino Jun 24 '13 at 16:22
  • @tadman I have a situation where your second solution worked, but not your first: [0xBF, 0xB].pack('C*').unpack('I')[0] => nil. [0xBF, 0xB].reverse.inject { |s,v| s * 256 + v } => 3007 . 3007 was the correct value but nil was not. Shouldn't pack be accepting of hex values? Why does it return nil? – JohnMerlino Jun 24 '13 at 16:41
  • @JohnMerlino Type `I` requires four bytes, you've given only two, so it fails. Maybe you want [unpack](http://ruby-doc.org/core-2.0/String.html#method-i-unpack) with `S` (unsigned short) instead? – tadman Jun 24 '13 at 18:48