How to concatenate bytes in Crystal
Asked Answered
K

2

6

I'm testing about serialization with bytes or slices, just learning and trying. I would like to bind 3 parameters in a single 10 bytes field, but I don't now how to concatenate them in Crystal or whether it is possible. I know I can achieve this by creating arrays or tuples, but I want to try whether it is possible to mix the parameters into a single buffer.

For instance, I want a self-descriptive binary record ID mixing 3 parameters:

Type (UInt8) | Category (UInt8) | Microseconds (UInt64) = Total 80 bits - 10 bytes

type = 1_u8 # 1 byte
categ = 4_u8 # 1 byte
usec = 1527987703211000_u64 # 8 bytes (Epoch)

How do I concatenate all this variables into a continuous 10 bytes buffer?

I want to retrieve the data by the index, like:

type = buff[0,1]
categ = buff[1,1]
usec = buff[2,8].to_u64 # (Actually not possible)
Kuster answered 3/6, 2018 at 1:46 Comment(0)
D
7
typ = 1_u8 # 1 byte
categ = 4_u8 # 1 byte
usec = 1527987703211000_u64 # 8 bytes (Epoch)

FORMAT = IO::ByteFormat::LittleEndian

io = IO::Memory.new(10)  # Specifying the capacity is optional

io.write_bytes(typ, FORMAT)  # Specifying the format is optional
io.write_bytes(categ, FORMAT)
io.write_bytes(usec, FORMAT)

buf = io.to_slice
puts buf

# --------

io2 = IO::Memory.new(buf)

typ2 = io2.read_bytes(UInt8, FORMAT)
categ2 = io2.read_bytes(UInt8, FORMAT)
usec2 = io2.read_bytes(UInt64, FORMAT)

pp typ2, categ2, usec2
Bytes[1, 4, 248, 99, 69, 92, 178, 109, 5, 0]
typ2   # => 1_u8
categ2 # => 4_u8
usec2  # => 1527987703211000_u64

This shows an example tailored to your use case, but IO::Memory should be used for "concatenating bytes" in general -- just write to it.

Decadence answered 3/6, 2018 at 8:20 Comment(0)
G
0

This is not an answer to your question, but I keep ending up here when I try to concatenate actual Bytes[]. The answer by Oleh still applies, but I try to give a more generic approach here:

# some bytes a
a = Bytes[131, 4, 254, 47]

# some other bytes b
b = Bytes[97, 98, 99]

# new buffer of sizes a and b
c = IO::Memory.new a.bytesize + b.bytesize

# without knowing the size of the slice, we just write byte by byte
a.each do |v|
    # each byte can be represented by an u8
    c.write_bytes UInt8.new v
end

# same for b
b.each do |v|
    c.write_bytes UInt8.new v
end

# here you have your new bytes slice
c = c.to_slice
# => Bytes[131, 4, 254, 47, 97, 98, 99]

Note, this only works with IO::ByteFormat::LittleEndian which is default and therefore omitted in the example

Gutter answered 23/1, 2020 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.