Search code examples
adaendiannessgnat

Extracting record from big endian data


I have the following code for network protocol implementation. As the protocol is big endian, I wanted to use the Bit_Order attribute and High_Order_First value but it seems I made a mistake.

With Ada.Unchecked_Conversion;
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;

procedure Bit_Extraction is

   type Byte is range 0 .. (2**8)-1 with Size => 8;

   type Command is (Read_Coils,
                    Read_Discrete_Inputs
                   ) with Size => 7;

   for Command use (Read_Coils => 1,
                    Read_Discrete_Inputs => 4);

   type has_exception is new Boolean with Size => 1;

    type Frame is record
      Function_Code : Command;
      Is_Exception : has_exception := False;
   end record
     with Pack => True,
     Size => 8;

   for Frame use
      record
         Function_Code at 0 range 0 .. 6;
         Is_Exception at 0 range 7 .. 7;
      end record;

   for Frame'Bit_Order use High_Order_First;
   for Frame'Scalar_Storage_Order use High_Order_First;

   function To_Frame is new Ada.Unchecked_Conversion (Byte, Frame);

   my_frame : Frame;
begin
   my_frame := To_Frame (Byte'(16#32#)); -- Big endian version of 16#4#
   Put_Line (Command'Image (my_frame.Function_Code)
             & " "
             & has_exception'Image (my_frame.Is_Exception));
end Bit_Extraction;

Compilation is ok but the result is

raised CONSTRAINT_ERROR : bit_extraction.adb:39 invalid data

What did I forget or misunderstand ?

UPDATE

The real record in fact is

type Frame is record
      Transaction_Id : Transaction_Identifier;
      Protocol_Id : Word := 0;
      Frame_Length : Length;
      Unit_Id : Unit_Identifier;
      Function_Code : Command;
      Is_Exception : Boolean := False;    
end record with Size => 8 * 8, Pack => True;

for Frame use
      record
         Transaction_Id at 0 range 0 .. 15;
         Protocol_Id at 2 range 0 .. 15;
         Frame_Length at 4 range 0 .. 15;
         Unit_id at 6 range 0 .. 7;
         Function_Code at 7 range 0 .. 6;
         Is_Exception at 7 range 7 .. 7;
      end record;

Where Transaction_Identifier, Word and Length are 16-bit wide.

These ones are displayed correctly if I remove the Is_Exception field and extend Function_Code to 8 bits.

The dump of the frame to decode is as following:

00000000  00 01 00 00 00 09 11 03  06 02 2b 00 64 00 7f

So my only problem is really to extract the 8th bit of the last byte.


Solution

  • I finally found what was wrong.

    In fact, the Modbus Ethernet Frame definition mentioned that, in case of exception, the returned code should be the function code plus 128 (0x80) (see explanation on Wikipedia). That's the reason why I wanted to represent it through a Boolean value but my representation clauses were wrong.

    The correct clauses are these ones :

       for Frame use
          record
             Transaction_Id at 0 range 0 .. 15;
             Protocol_Id at 2 range 0 .. 15;
             Frame_Length at 4 range 0 .. 15;
             Unit_id at 6 range 0 .. 7;
             Is_Exception at 6 range 8 .. 8;
             Function_Code at 6 range 9 .. 15;
          end record;
    

    This way, the Modbus network protocol is correctly modelled (or not but at least, my code is working).

    I really thank egilhh and simonwright for making me find what was wrong and explain the semantics behind the aspects.

    Obviously, I don't know who reward :)