I'm trying to read from a byte buffer in Ada, such as a file or via buffer for a network connection. The messages are variable in size with a common header, in C++ it'd look something like this:
enum class MessageType : uint16_t {
Foo = 0, Bar = 1
};
// Force the layout.
#pragma pack(push,1)
// 4 byte message header
struct MessageHeader {
uint16_t checksum;
uint16_t type;
};
// 4 byte header + 4 byte message
struct FooMessage {
MessageHeader header;
uint32_t tomatoes;
};
// 4 byte header + 8 byte message
struct BarMessage {
MessageHeader header;
uint32_t oranges;
uint32_t apples;
};
#pragma pack(pop)
// For simplicity, assume the buffer is complete and only holds full messages.
void read(char* buffer, uint32_t bytesLeft) {
while (bytesLeft > 0) {
MessageHeader* header = reinterpret_cast<MessageHeader*>(buffer);
switch (header->type) {
case FooType: {
FooMessage* foo = reinterpret_case<FooMessage*>(buffer);
// process as const FooMessage&
processFoo(*foo);
}
case BarType: {
BarMessage* bar = reinterpret_cast<BarMessage*>(buffer);
// process as const BarMessage&
processBar(*bar);
}
}
const auto size = (header->type == Foo ? sizeof(FooMessage) : sizeof(BarMessage));
buffer += size;
bytesLeft -= size;
}
}
I'm not sure of the idiomatic way of doing it. Note that in some formats, the message type might not be the leading data member in the header, as well. Should you be writing to and reading off an array of Character or something from Interfaces.C.char_array, or an address of memory from System.Address or something else? Or should this be an address to an array elsewhere here, or just an array with "Convention => C" to prevent the leading size from being included?
This is what I have so far in Ada:
type Message_Type is (Foo, Bar) with Size => 16;
for Message_Type use (Foo => 0, Bar => 1);
-- Assume these work correctly and I don't need to do bit layout directly.
type Message_Header is record
Checksum : Interfaces.Integer_16;
Msg_Type : Message_Type;
end record
with Convention => C, Size => 32;
type Foo_Message is record
Header : Message_Header;
Tomatoes : Interfaces.Integer_32;
end record
with Convention => C, Size => 64;
type Bar_Message is record
Header : Message_Header;
Oranges : Interfaces.Integer_32;
Apples : Interfaces.Integer_32;
end record
with Convention => C, Size => 96;
procedure Read(
-- System.Address seems really weird here
Buffer : in out System.Address;
Bytes_Left : in out Interfaces.Integer_64)
is
use type Interfaces.Integer_64;
use type System.Address;
function To_Address is new Ada.Unchecked_Conversion (Interfaces.Integer_64, System.Address);
function To_Integer is new Ada.Unchecked_Conversion (System.Address, Interfaces.Integer_64);
procedure Process_Bar (B : aliased Bar_Message) is null;
procedure Process_Foo (F : aliased Foo_Message) is null;
begin
while Bytes_Left > 0 loop
declare
-- I'm really lost here.
--
-- Do you use access types to access the buffer or
-- setting the address with "for Foo'Address use Buffer"??
--
Header : Message_Header;
for Header'Address use Buffer;
enter code here
-- I'm assuming this doesn't initialize Foo and Bar here?
Foo_Msg : aliased Foo_Message;
Bar_Msg : aliased Bar_Message;
for Foo_Msg'Address use Buffer;
for Bar_Msg'Address use Buffer;
-- I'm assuming this doesn't initialize Foo and Bar here?
Size : System.Address := To_Address(0);
begin
case Header.Msg_Type is
when Foo => Process_Foo (Foo_Msg);
when Bar => Process_Bar (Bar_Msg);
end case;
Size := To_Address (if Header.Msg_Type = Foo then Foo'Size else Bar'Size);
-- There's probably a better way to do this.
Buffer := To_Address(To_Integer (Buffer) + To_Integer (Size));
Bytes_Left := Bytes_Left - To_Integer (Size);
end;
end loop;
end Read;
What's the idiomatic way to march in a variable way across bytes in buffers and read the data in place?