Extracting record from big endian data
Asked Answered
A

5

5

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.

Attract answered 18/6, 2019 at 19:55 Comment(6)
are you sure about needing Bit_Order ? network convention is big endian on the bytes, not on the bits. You probably just need Scalar_Storage_Order.Strati
It's only an extract of the real record. In the real one, there are field which are size of a word that's the reason why I thought I would need it but I might be wrong.Sarsenet
@Jean-FrançoisFabre moreover you can't set Scalar_Storage_Order to a value different from the Bit_Order one which is the case there.Sarsenet
I just don't agree with Byte'(16#32#)); -- Big endian version of 16#4#. Big or little endian affect bytes, not bits. At the byte level, there's no endiannessStrati
Right but it doesn't work with 16#4# netherSarsenet
I have seen it done before, will have to dust off memory on how.Johannisberger
A
5

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 :)

Attract answered 19/6, 2019 at 17:39 Comment(0)
T
5

So,

    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;

It seems you want Is_Exception to be the the LSB of the last byte? With for Frame'Bit_Order use System.High_Order_First; the LSB will be bit 7,

(also, 16#32# will never be -- Big endian version of 16#4#, the bit pattern just doesn't match)

It may be more intuitive and clear to specify all of your fields relative to the word they're in, rather than the byte:

         Unit_ID at 6 range 0..7;
         Function_Code at 6 range 8 .. 14;
         Is_Exception at 6 range 15 .. 15;

Given the definition of Command above, the legal values for the last byte will then be:

  • 2 -> READ_COILS FALSE
  • 3 -> READ_COILS TRUE
  • 8 -> READ_DISCRETE_INPUTS FALSE
  • 9 -> READ_DISCRETE_INPUTS TRUE

BTW, by applying your update to your original program, and adding/changing the following, you program works for me

add

    with Interfaces;

add

    type Byte_Array is array(1..8) of Byte with Pack;

change, since we don't know the definition

    Transaction_ID : Interfaces.Unsigned_16;
    Protocol_ID : Interfaces.Unsigned_16; 
    Frame_Length : Interfaces.Unsigned_16;
    Unit_ID : Interfaces.Unsigned_8;

change

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

change

    my_frame := To_Frame (Byte_Array'(00, 01, 00, 00, 00, 09, 16#11#, 16#9#));
Tractarianism answered 19/6, 2019 at 6:46 Comment(1)
Ok, I think I got it thanks to you and Simon's layoutSarsenet
A
5

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 :)

Attract answered 19/6, 2019 at 17:39 Comment(0)
P
4

Take a look at this AdaCore post on bit order and byte order to see how they handle it. After reading that, you will probably find that the bit order of your frame value is really 16#08#, which probably is not what you are expecting.

Big Endian / Little Endian typically refers to Byte order rather than bit order, so when you see that Network protocols are Big Endian, they mean Byte order. Avoid setting Bit_Order for your records. In modern systems, you will almost never need that.

Your record is only one byte in size, so byte order won't matter for it by itself. Byte order comes into play when you have larger field values (>8 bits long).

Pessimist answered 18/6, 2019 at 22:7 Comment(1)
I already read it and still can't find a way to make it work. When you set Scalar_Storage_Order to High_Order_First, you have to also put Bit_Order to the same value. If not, there's a compilation error.Sarsenet
P
4

Your original record declaration works fine (GNAT complains about the Pack, warning: pragma Pack has no effect, no unplaced components). The problem is with working out the little-endian Byte.

---------------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |    BE bit numbers
---------------------------------
| c   c   c   c   c   c   c | e |
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |    LE bit numbers
---------------------------------

so if you want the Command to be Read_Discrete_Inputs, the Byte needs to have BE bit 4 (LE bit 3) set i.e. LE 16#8#.

Painless answered 19/6, 2019 at 7:12 Comment(1)
I added the Pack pragme because I was using Boolean in the first place. Now that I use a new type specifying the Size aspect, I think it's enoughSarsenet
S
0

The bit_order pragma doesn't reverse the order that the bits appear in memory. It simply defines whether the most significant bit (left most) will be logically referred to as zero (High_Order_First) or the least significant bit will be referred to as zero (Low_Order_First) when interpreting the First_Bit and Last_Bit offsets from the byte position in the representation clause. Keep in mind that these offsets are taken from the MSB or LSB of the scalar the record component belongs to AS A VALUE. So in order for the byte positions to carry the same meaning on a little endian CPU as they do on a big endian CPU (as well as the in memory representation of multibyte machine scalars, which exist when one or more record components with the same byte position have a last_bit value which exceeds the capacity of a single byte) then 'Scalar_Storage_Order must also be specified.

Sitology answered 7/2, 2020 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.