Read bytes from reader into a bounded array?

How can I read bytes from a reader into a bounded array?

Something like this:

pub const SDOServerNormal = struct {
    mbx_header: MailboxHeader,
    coe_header: CoEHeader,
    sdo_header: SDOHeaderServer,
    complete_size: u32,
    data: std.BoundedArray(u8, max_mailbox_size - 10),

    pub fn deserialize(buf: []const u8) !SDOServerNormal {
        var fbs = std.io.fixedBufferStream(buf);
        var reader = fbs.reader();
        const mbx_header = try nic.packFromECatReader(MailboxHeader, &reader);
        const coe_header = try nic.packFromECatReader(CoEHeader, &reader);
        const sdo_header = try nic.packFromECatReader(SDOHeaderServer, &reader);
        const complete_size = try nic.packFromECatReader(u32, &reader);

        if (mbx_header.length < 10) {
            return error.InvalidMbxHeaderLength;
        }
        const data_length: u16 = mbx_header.length -| 10;
        var data = try std.BoundedArray(u8, max_mailbox_size - 10).init(0);

        // read the next data_length bytes into the data bounded array
        // using the reader
        // data_length can be zero!

        return SDOServerNormal{
            .mbx_header = mbx_header,
            .coe_header = coe_header,
            .sdo_header = sdo_header,
            .complete_size = complete_size,
            .data = data,
        };
    }
};

Something like:

const data_writer = data.writer();
var i: u16 = 0;
while (i < data_len) {
    const max_chunk_len = 256;
    var chunk_buf: [max_chunk_len]u8 = undefined;
    const chunk_len = @min(data_len - i, max_chunk_len);
    const chunk = try reader.readAll(chunk_buf[0..chunk_len]);
    try data_writer.writeAll(chunk);
    i += chunk_len;
}

EDIT: Another option for the same sort of pattern is via pump from std.fifo:

// wrap the reader so that it returns EOF after data_len bytes
var limited_reader = std.io.limitedReader(reader, data_len);

// 256 is arbitrary, size it appropriately for your use-case
const FifoBuffer = std.fifo.LinearFifo(u8, .{ .Static = 256 });
var fifo = FifoBuffer.init();
try fifo.pump(limited_reader.reader(), writer);

(the fifo approach is what I use in resinator here)

3 Likes
var data: std.BoundedArray(u8, max_mailbox_size - 10) = 
  .{ .len = data_length };
// The buffer field is implicitly undefined.
try reader.readNoEof(data.slice());
3 Likes

Went with a variant of this but used the init method of the bounded array instead of modifying length directly (which provides an error on overflow).

var data = try std.BoundedArray(u8, data_max_size).init(data_length);
try reader.readNoEof(data.slice());

This was the full implementation:

pub const SDOServerNormal = struct {
    mbx_header: MailboxHeader,
    coe_header: CoEHeader,
    sdo_header: SDOHeaderServer,
    complete_size: u32,
    data: std.BoundedArray(u8, data_max_size),

    pub const data_max_size = max_mailbox_size - 16;

    pub fn deserialize(buf: []const u8) !SDOServerNormal {
        var fbs = std.io.fixedBufferStream(buf);
        const reader = fbs.reader();
        const mbx_header = try nic.packFromECatReader(MailboxHeader, reader);
        const coe_header = try nic.packFromECatReader(CoEHeader, reader);
        const sdo_header = try nic.packFromECatReader(SDOHeaderServer, reader);
        const complete_size = try nic.packFromECatReader(u32, reader);

        if (mbx_header.length < 10) {
            return error.InvalidMbxHeaderLength;
        }
        const data_length: u16 = mbx_header.length -| 10;
        var data = try std.BoundedArray(u8, data_max_size).init(data_length);
        try reader.readNoEof(data.slice());

        return SDOServerNormal{
            .mbx_header = mbx_header,
            .coe_header = coe_header,
            .sdo_header = sdo_header,
            .complete_size = complete_size,
            .data = data,
        };
    }

    pub fn serialize(self: *const SDOServerNormal, out: []u8) !usize {
        var fbs = std.io.fixedBufferStream(out);
        const writer = fbs.writer();
        try nic.eCatFromPackToWriter(self.mbx_header, writer);
        try nic.eCatFromPackToWriter(self.coe_header, writer);
        try nic.eCatFromPackToWriter(self.sdo_header, writer);
        try nic.eCatFromPackToWriter(self.complete_size, writer);
        try writer.writeAll(self.data.slice());
        return fbs.getWritten().len;
    }

    comptime {
        assert(data_max_size == max_mailbox_size -
            @divExact(@bitSizeOf(MailboxHeader), 8) -
            @divExact(@bitSizeOf(CoEHeader), 8) -
            @divExact(@bitSizeOf(SDOHeaderServer), 8) -
            @divExact(@bitSizeOf(u32), 8));
    }
};

test "serialize and deserialize sdo server normal" {
    const expected = SDOServerNormal{
        .mbx_header = .{
            .length = 14, // 4 bytes of payload
            .address = 0x0,
            .channel = 0,
            .priority = 0,
            .type = .CoE,
            .cnt = 2,
        },
        .coe_header = .{
            .number = 0,
            .service = .sdo_response,
        },
        .sdo_header = .{
            .size_indicator = true,
            .transfer_type = .normal,
            .data_set_size = .four_octets,
            .complete_access = false,
            .command = .initiate_upload_response,
            .index = 1234,
            .subindex = 0,
        },
        .complete_size = 12345,
        .data = try std.BoundedArray(u8, SDOServerNormal.data_max_size).fromSlice(&.{ 1, 2, 3, 4 }),
    };
    var bytes = std.mem.zeroes([max_mailbox_size]u8);
    const byte_size = try expected.serialize(&bytes);
    try std.testing.expectEqual(@as(usize, 6 + 2 + 12), byte_size);
    const actual = try SDOServerNormal.deserialize(&bytes);
    try std.testing.expectEqualDeep(expected, actual);
}
3 Likes