5

I'm a C programmer and started learning Ada a few weeks ago. I have been puzzled by how Ada handles foreign binary data, such as when decoding a communications packet stored in a serial input buffer.

In C, I would define a packed structure to reflect the layout of the packet, and then cast the pointer-to-buffer to pointer-to-structure to access the individual elements in the communications data. What is the typical way to do such decoding in Ada?

I have tried to replicate the same method for C in Ada with the following

-- Fundamental types for fields in the packet

type Station_Addr_Type is mod 2**8;

type Func_Code_Type is (FUNC1, FUNC2);
for Func_Code_Type use
   (
    FUNC1 => 1,
    FUNC2 => 2
   );
type Packet is
    record
       Station_Addr : Station_Addr_Type;
       Func_Code : Func_Code_Type;
    end record;


-- attempts to reflect packet binary layout

for Packet use
    record at mod 1;
       Station_Addr at 0 range 0 .. 7;
       Func_Code at 1 range 0 .. 7;
    end record;

I then defined the array for the communications receive buffer that accepts foreign binary data (possibly from a different architecture):

type Communication_Data is mod 2**8;
for Communication_Data'Size use 8;

type Communication_Buffer is array (Natural range <>) of Communication_Data;

Buffer : Communication_Buffer (0 .. 20);

Then a procedure that decodes such communications

procedure Decode_Packet (Packet_Provided : in Packet);
-- non-working sample
declare
begin

  -- Attempts to sanity-check packet by object casting
  Decode_Packet (Packet (Buffer));
  ---------------^----
  Error: invalid conversion, not compatible with type "Communication_Buffer"

exception
  when others => 
     raise Decode_Failure;
end;

However, the compiler forbids such casting with error as shown. Thanks for reading this far. My question is,

Regarding the correct way of decoding foreign binary data, am I "in the ball park", or is there a better way of doing this?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • 1
    Ada is a high-level language, while pointer-everywhere languages like C are low level, so if you think of things in C terms, or try to directly apply approaches you'd use in C, you'll be fighting the language. You'll note the total lack of access types in Reznik's solutions, for example. This kind of low-level implementation detail is usually encapsulated (in a package), hidden (in the pkg body), and implemented with Ada.Unchecked_Conversion. – Jeffrey R. Carter Jul 10 '21 at 11:55
  • Another technique you can do is double-down on the enforced public-interface/private-body structure of Ada: (1) internally have a `NULL RECORD` of your required size, or a native-ordered record; (2) have an optionally-used heterogeneous-record; and (3) present out the appropriate interfacing-functions, which perform the appropriate conversion/swapping. — see: https://stackoverflow.com/a/55923228/608963 – Shark8 Jul 14 '21 at 15:09

1 Answers1

7
  1. If you sure that the data has a right alignment, you can map a Packet object to space allocated by Communication_Data:
Buffer : Communication_Buffer (0 .. 20);
Pkg    : Packet;
pragma Import (Ada, Pkg);
for Pkg'Address use Buffer (0)'Address;

The same with aspect syntax:

Buffer : Communication_Buffer (0 .. 20);
Pkg    : Packet
 with Import, Address => Buffer (0)'Address;
  1. Another way is to use Ada.Unchecked_Conversion, but you should be sure that Buffer and Packet have the same size:
subtype Packet_Buffer is Communication_Buffer (1 .. 2);

function To_Packet is new Ada.Unchecked_Conversion
  (Packet_Buffer, Packet);

Pkg : Packet := To_Packet (Buffer (0 .. 1));

PS. If you want an endianness independent code you may also need a Scalar_Storage_Order (GNAT implementation defined) aspect.

PS. I recommend also take a look at "Safe Communication" chapter of Safe and Secure Software booklet.

Maxim Reznik
  • 1,216
  • 9
  • 12