SMG Comms Chapter 7: Read/Write Serpent Keysets to/from Serpent Messages

November 10th, 2018

~ This is a work in progress towards an Ada implementation of Eulora's communication protocol. Start with Chapter 1.~

Over this past week I've been going deeper into Ada looking again and again -among other things- at record representation clauses and unions and serialization1 and basically anything and everything that could perhaps provide an elegant solution to serializing data structures to arrays of octets and back. And by "looking" I don't mean just reading but rather the usual combination of reading, writing and coding or at least attempting to code all sorts of ideas that seemed promising. What I have as a result is a much better grasp of Ada's neat representation clauses and parametrized records. What I don't have as a result is the very elegant and short solution I was hoping to find. Instead, I have a first pass at a solution together with the whole chain of reasoning that led me to it. So I'm publishing this as it is - a small step - and I'll gladly listen to better suggestions that are really fit for the job at hand.

The issue is in principle simple enough: write game data to the raw arrays of octets that are SMG messages ensuring the exact format specified by the communication protocol; conversely, read from those same raw arrays of octets that are SMG messages the actual game data. Stated like this, the solution suggesting itself is to define records matching exactly the format specified by the protocol, use representation clauses to control the exact place of all components and then simply convert the whole thing in one go. There are however a few things that I really don't like about this approach of using records matching exactly the full message structure as defined in Eulora's communication protocol:

  • The actual game data is of course only a part of each message. The rest includes padding or communication-level information that really has no business leaking in any way outside of the communication part itself. So I don't quite see the point in defining records for the full message since the read/write (that are supposedly called from outside the communication level itself) should NOT require/produce such records! Instead, the output should be *game data* for read and *array of octets* for write. As a result, I really don't see any benefit in defining message-matching records just for read/write to use *internally* - a sort of filling those component by component and doing the conversion as a whole instead of just doing the conversion component by component directly.
  • The internal structure of most messages is effectively parametrized: there is usually at least one component that represents the number of other following components. Ada handles this sort of thing very gracefully through parametrized records but having parametrized records inside a record doesn't seem to me like a very elegant solution nor one leading to a very clear representation of the whole. I admit I am still not 100% sure of the actual, exact representation of such a record containing itself parametrized records since my understanding is that Ada will allocated maximum space (i.e. space to fit potentially the largest structure) but then simply make available only as much as needed according to the actual value of the parameter.

Considering the above issues, my current solution aims to keep the communication-level parts of messages as an internal concern of the communication level and provide/require at read/write only the actual game data that is needed for each type of message. The conversion to/from octets is then done indeed component by component, as they are written/read and endianness is handled simply in one single place, at conversion: if native endianness is Big Endian then octets are flipped for any component that is represented on more than one octet; otherwise, on Little Endian iron, octets are kept as they are found (i.e. the values in messages are in Little Endian representation, yes).

To illustrate the approach above in practice, I've implemented already the read/write for Serpent Keysets into Serpent Messages (section 4.1 of the procotol specification). The communication-level data in this type of message consists in the type ID, the CRC information *for each key* and the message padding (variable-sized since it simply fills the message as needed). The message counter is kept separate since the server for instance will likely need to keep different counters for each client, so I'll leave for now the decision on this for a higher /different level. The keyset itself is the actual data really and it consists in the number of keys, their actual octets and the flag that indicates whether they are for client or for server use. So I define a parametrized record for representing a keyset only rather than the whole message. This is in the new file data_structs.ads that I see at the moment as the place for defining structures for game data. Hence, it contains for instance also the "SMG_Object" record (fixed size) that illustrates some use of representation clauses:

 -- Data structures for SMG Communication Protocol
 -- S.MG, 2018

with Interfaces; -- for fixed-size types
with Raw_Types;  -- for protocol raw types
with System;     -- for Bit_Order
with Serpent;    -- for Serpent.Key type

package Data_Structs is
  Pragma Pure(Data_Structs);

  -- an object in the world, with record layout fully specified
  -- a storage element is 8-bit aka 1 octet
  -- "at" gives number of storage element
  -- "range" gives bits inside storage element

  type SMG_Object is
    record
      -- ID of the object
      ID         : Interfaces.Unsigned_32;

      -- Position of the object in the world.
      -- For a world with map coordinates (MC) between -500 and +500,
      -- the relationship with given figure (GF) is:
      -- MC = GF / 65.535 - 500
      -- where 65.535 comes from the mapping 65535 / (500 - - 500)
      X, Y, Z    : Interfaces.Integer_16;

      -- Rotation of the object (RO) linked to GF by:
      -- RO = GF / 128*pi
      RX, RY, RZ : Interfaces.Unsigned_8;
    end record;
  for SMG_Object'Size use 104; -- in bits!
  for SMG_Object'Bit_Order use System.Low_Order_First;
  for SMG_Object use
    record
      ID at 0  range 0 .. 31;
      X  at 4  range 0 .. 15;
      Y  at 6  range 0 .. 15;
      Z  at 8  range 0 .. 15;
      RX at 10 range 0 .. 7;
      RY at 11 range 0 .. 7;
      RZ at 12 range 0 .. 7;
    end record;

  -------------------------
  -- A set of Serpent Keys
  -- parametrized record for a Serpent Keyset
  -- parameters:
  --   N is number of Serpent Keys contained
  -- this can be the content of messages 4.1 or 5.2 in protocol spec.

  -- an array of Serpent Keys
  -- MAXIMUM 40 keys allowed in a message, hence subtype for key count
  subtype Keys_Count is Interfaces.Unsigned_8 range 1..40;
  type SKeys_Array is array( Keys_Count range <>) of Serpent.Key;

  type Serpent_Keyset( N : Keys_Count := Keys_Count'Last) is
    record
      -- actual Serpent Keys
      Keys      : SKeys_Array( 1..N );
      -- whether for talking to client (LSB set) or talking to server (MSB set)
      Flag      : Interfaces.Unsigned_8;
    end record;

  ------------------------------
  -- Serpent Keys Management
  type Keys_Mgm (N_Burnt: Interfaces.Unsigned_8) is
    record
      -- count of server keys requested
      N_Server: Interfaces.Unsigned_8;
      -- count of client keys requested
      N_Client: Interfaces.Unsigned_8;
      -- ID of Serpent key preferred for further inbound Serpent msgs.
      Key_ID  : Interfaces.Unsigned_8;
      -- IDs of Serpent keys burnt by this message
      Burnt   : SKeys_Array( 1..N_Burnt );
    end record;

end Data_Structs;

The communication-level information such as CRC, type ids and padding are all handled by messages.ads/adb and are effectively private matters, of no concern to anything outside this messages package:

 -- Message reader & writers for SMG Communication Protocol
 -- This part effectively serializes/deserializes game data structures
 --   for transmission between client and server.
 -- Note that messages themeselves are simply arrays of octets.
 -- (see also raw_types.ads/adb and packing.ads/adb for related parts)
 -- NB: message ids and padding as per protocol spec are handled HERE ONLY.
 -- Relies on:
 --   RNG (for random padding)
 --   CRC32 (for CRC calculations)
 --   Raw_Types
 --   Data_Structs
 -- S.MG, 2018

with Raw_Types;
with RNG;
with CRC32;
with Data_Structs; use Data_Structs;
with Interfaces;

package Messages is
  -- exception raised when given message to read fails sanity checks
  Invalid_Msg: exception;

  ------------------------------------------------
  -- Writes a Serpent Keyset to the given Serpent Message
  --
  -- Keyset  - the set of keys to write to message
  -- Counter - the message count
  procedure Write_SKeys_SMsg( Keyset  : in Serpent_Keyset;
                              Counter : in Interfaces.Unsigned_16;
                              Msg     : out Raw_Types.Serpent_Msg);

  -- Reads a Serpent Keyset from the given Serpent Message
  -- The opposite of Write_SKeys_SMsg above
  -- Raises Invalid_Message exception if given message fails sanity checks
  procedure Read_SKeys_SMsg( Msg     : in Raw_Types.Serpent_Msg;
                             Counter : out Interfaces.Unsigned_16;
                             Keyset  : out Serpent_Keyset);

private

  -- if native is little endian, does nothing;
  -- if native is big endian, it flips the input's octets.
  -- (strictly for arrays of octets)
  procedure Cast_LE( LE: in out Raw_Types.Octets );

  -- protocol message types IDs, fixed as per protocol specification
  -- Serpent messages end in "S_Type"
  -- RSA messages end in "R_Type"
  -- Character action types end in "A_Type"

  -- Serpent message types
  SKeys_S_Type         : constant Interfaces.Unsigned_8 := 1;
  Key_Mgm_S_Type       : constant Interfaces.Unsigned_8 := 2;
  File_Transfer_S_Type : constant Interfaces.Unsigned_8 := 3;
  File_Req_S_Type      : constant Interfaces.Unsigned_8 := 4;
  Client_Action_S_Type : constant Interfaces.Unsigned_8 := 5;
  World_Bulletin_S_Type: constant Interfaces.Unsigned_8 := 6;
  Obj_Request_S_Type   : constant Interfaces.Unsigned_8 := 7;
  Obj_Info_S_Type      : constant Interfaces.Unsigned_8 := 8;

  -- RSA message types
  RKeys_R_Type         : constant Interfaces.Unsigned_8 := 251;
  SKeys_R_Type         : constant Interfaces.Unsigned_8 := 157;
  Key_Mgm_R_Type       : constant Interfaces.Unsigned_8 := 233;

  -- Character action types
  Lock_A_Type          : constant Interfaces.Unsigned_8 := 0;
  Make_A_Type          : constant Interfaces.Unsigned_8 := 1;
  Explore_A_Type       : constant Interfaces.Unsigned_8 := 2;
  Exchange_A_Type      : constant Interfaces.Unsigned_8 := 3;
  Attack_A_Type        : constant Interfaces.Unsigned_8 := 4;
  Repair_A_Type        : constant Interfaces.Unsigned_8 := 5;
  Move_A_Type          : constant Interfaces.Unsigned_8 := 6;
  Train_A_Type         : constant Interfaces.Unsigned_8 := 7;

end Messages;

The implementation in messages.adb, including read/write for Serpent keysets to/from Serpent messages and handling endianness for any multi-octet entity:

 -- Message reader & writers for SMG Communication Protocol
 -- S.MG, 2018

with Interfaces; use Interfaces;
with Serpent;
with System; use System;

package body Messages is

  procedure Write_SKeys_SMsg( Keyset  : in Serpent_Keyset;
                              Counter : in Interfaces.Unsigned_16;
                              Msg     : out Raw_Types.Serpent_Msg) is
    Pos   : Integer := Msg'First;
    Check : CRC32.CRC32;
    PadLen: Integer;
    K     : Serpent.Key;
  begin
    -- write Type ID
    Msg(Pos) := SKeys_S_Type;
    Pos := Pos + 1;

    -- write count of keys (NB: this IS 8 bits by definition)
    Msg(Pos) := Keyset.Keys'Length;
    Pos := Pos + 1;

    -- write keys
    for I in Keyset.Keys'Range loop
      -- retrieve Key to write
      K := Keyset.Keys( I );

      -- write key itself
      Msg(Pos..Pos+K'Length-1) := K;
      -- ensure little endian order in message
      Cast_LE(Msg(Pos..Pos+K'Length-1));
      Pos := Pos + K'Length;

      -- write CRC of key
      Check := CRC32.CRC( K );
      Msg(Pos..Pos+3) := Raw_Types.Cast(Check);
      Cast_LE(Msg(Pos..Pos+3));
      Pos := Pos + 4;
    end loop;

    -- write flag
    Msg(Pos) := Keyset.Flag;
    Pos := Pos + 1;

    -- write message counter
    Msg(Pos..Pos+1) := Raw_Types.Cast(Counter);
    Cast_LE(Msg(Pos..Pos+1));
    Pos := Pos + 2;

    -- write padding as needed; endianness is irrelevant here
    PadLen := Msg'Last - Pos + 1;
    if PadLen > 0 then
      declare
        Pad : Raw_Types.Octets(1..PadLen);
      begin
        RNG.Get_Octets( Pad );
        Msg(Pos..Pos+PadLen-1) := Pad;
      end;
    end if;
  end Write_SKeys_SMsg;

  -- Reads a Serpent keyset from given Serpent Message
  procedure Read_SKeys_SMsg( Msg     : in Raw_Types.Serpent_Msg;
                             Counter : out Interfaces.Unsigned_16;
                             Keyset  : out Serpent_Keyset) is
    Pos: Integer := Msg'First;
  begin
    -- read type and check
    if Msg(Pos) = SKeys_S_Type then
      Pos := Pos + 1;
    else
      raise Invalid_Msg;
    end if;

    -- read count of keys and check
    if Msg(Pos) in Keys_Count'Range then
      declare
        N     : Keys_Count := Keys_Count(Msg(Pos));
        KS    : Serpent_Keyset(N);
        K     : Serpent.Key;
        Check : CRC32.CRC32;
        O4    : Raw_Types.Octets_4;
        O2    : Raw_Types.Octets_2;
      begin
        Pos := Pos + 1;
        --read keys and check crc for each
        for I in 1 .. N loop
          -- read key and advance pos
          K := Msg(Pos..Pos+K'Length-1);
          Cast_LE(K);
          Pos := Pos + K'Length;
          -- read crc and compare to crc32(key)
          O4 := Msg(Pos..Pos+3);
          Cast_LE(O4);
          Check   := Raw_Types.Cast(O4);
          Pos := Pos + 4;
          if Check /= CRC32.CRC(K) then
            raise Invalid_Msg;
          end if;
          -- if it got here, key is fine so add to set
          KS.Keys(KS.Keys'First + I -1) := K;
        end loop;
        -- read and set flag
        KS.Flag := Msg(Pos);
        Pos := Pos + 1;
        -- read and set message counter
        O2 := Msg(Pos..Pos+1);
        Cast_LE(O2);
        Counter := Raw_Types.Cast(O2);
        -- rest of message is padding so it's ignored
        -- copy keyset to output variable
        Keyset := KS;
      end;
    else
      raise Invalid_Msg;
    end if;
  end Read_SKeys_SMsg;

  -- private part
  procedure Cast_LE( LE: in out Raw_Types.Octets ) is
  begin
    -- flip octets ONLY if native is big endian.
    if System.Default_Bit_Order = System.High_Order_First then
      declare
        BE: constant Raw_Types.Octets := LE;
      begin
        for I in 1..LE'Length loop
          LE(LE'First+I-1) := BE(BE'Last-I+1);
        end loop;
      end;
    end if;
    -- NOTHING to do for native little endian
  end Cast_LE;

end Messages;

Note that Messages calls directly RNG to obtain padding as and when it needs it. Similarly, it calls directly CRC32 to calculate the checksums as and when needed. The CRC32 is the previously published table-lookup CRC32 implementation from EuCrypt, now integrated into SMG Comms and slightly adapted to use the Octets type in Raw_Types:


------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- This file is part of 'CRC32'                                             --
--                                                                          --
-- You do not have, nor can you ever acquire the right to use, copy or      --
-- distribute this software ; Should you use this software for any purpose, --
-- or copy and distribute it to anyone or in any manner, you are breaking   --
-- the laws of whatever soi-disant jurisdiction, and you promise to         --
-- continue doing so for the indefinite future. In any case, please         --
-- always : read and understand any software ; verify any PGP signatures    --
-- that you use - for any purpose.                                          --
--                                                                          --
-- See also http://trilema.com/2015/a-new-software-licensing-paradigm .     --
------------------------------------------------------------------------------
------------------------------------------------------------------------------

  -- CRC32, lookup-based implementation
  -- S.MG, 2018
  --
  -- The CRC32 is a checksum calculated as the remainder of dividing
  -- the input data by the 0x04C11DB7 polynomial. Specifications:
  -- Name   : "CRC-32"
  -- Width  : 32        (number of bits)
  -- Poly   : 04C11DB7  (generator polynomial)
  -- Init   : FFFFFFFF
  -- RefIn  : True      (input is reflected)
  -- RefOut : True      (output is reflected)
  -- XorOut : FFFFFFFF
  -- Check  : CBF43926  (expected value for input "123456789")
  --
  -- This implementation is based on the CRC32 specification in:
  -- Sarwate, D.V. "Computation of Cyclic Redundancy Checks via Table Look-Up"
  -- in Communications of the ACM, Vol. 31 No. 8, pp.1008-1013, Aug. 1988

with Interfaces; use Interfaces;
with Raw_Types;

package CRC32 is
  -- local, shorthand version of Interfaces. types
  subtype CRC32 is Interfaces.Unsigned_32;
  subtype Octet is Interfaces.Unsigned_8;

  subtype Octet_Array is Raw_Types.Octets;

  -- interface for external callers
    -- calculate CRC32 for the given string
  function CRC( S: in String ) return CRC32;

    -- calculate CRC32 for the given array of octets
  function CRC( Data: in Octet_Array ) return CRC32;

  -- internal constants and helper methods
private
	function Shift_Right( Value  : CRC32;
                        Amount : Natural)
                        return CRC32;
  pragma Import(Intrinsic, Shift_Right); 

  Init_Value : constant CRC32 := 16#FFFF_FFFF#; -- Initial value
  Xor_Out    : constant CRC32 := 16#FFFF_FFFF#; -- For extracting result
  LSB_Mask   : constant CRC32 := 16#0000_00FF#; -- lsb mask for a CRC32 value

  -- lookup table with precomputed values for CRC32
  Lookup : constant array (CRC32 range 0 .. 255) of CRC32 :=
     (16#0000_0000#, 16#7707_3096#, 16#EE0E_612C#, 16#9909_51BA#,
      16#076D_C419#, 16#706A_F48F#, 16#E963_A535#, 16#9E64_95A3#,
      16#0EDB_8832#, 16#79DC_B8A4#, 16#E0D5_E91E#, 16#97D2_D988#,
      16#09B6_4C2B#, 16#7EB1_7CBD#, 16#E7B8_2D07#, 16#90BF_1D91#,
      16#1DB7_1064#, 16#6AB0_20F2#, 16#F3B9_7148#, 16#84BE_41DE#,
      16#1ADA_D47D#, 16#6DDD_E4EB#, 16#F4D4_B551#, 16#83D3_85C7#,
      16#136C_9856#, 16#646B_A8C0#, 16#FD62_F97A#, 16#8A65_C9EC#,
      16#1401_5C4F#, 16#6306_6CD9#, 16#FA0F_3D63#, 16#8D08_0DF5#,
      16#3B6E_20C8#, 16#4C69_105E#, 16#D560_41E4#, 16#A267_7172#,
      16#3C03_E4D1#, 16#4B04_D447#, 16#D20D_85FD#, 16#A50A_B56B#,
      16#35B5_A8FA#, 16#42B2_986C#, 16#DBBB_C9D6#, 16#ACBC_F940#,
      16#32D8_6CE3#, 16#45DF_5C75#, 16#DCD6_0DCF#, 16#ABD1_3D59#,
      16#26D9_30AC#, 16#51DE_003A#, 16#C8D7_5180#, 16#BFD0_6116#,
      16#21B4_F4B5#, 16#56B3_C423#, 16#CFBA_9599#, 16#B8BD_A50F#,
      16#2802_B89E#, 16#5F05_8808#, 16#C60C_D9B2#, 16#B10B_E924#,
      16#2F6F_7C87#, 16#5868_4C11#, 16#C161_1DAB#, 16#B666_2D3D#,
      16#76DC_4190#, 16#01DB_7106#, 16#98D2_20BC#, 16#EFD5_102A#,
      16#71B1_8589#, 16#06B6_B51F#, 16#9FBF_E4A5#, 16#E8B8_D433#,
      16#7807_C9A2#, 16#0F00_F934#, 16#9609_A88E#, 16#E10E_9818#,
      16#7F6A_0DBB#, 16#086D_3D2D#, 16#9164_6C97#, 16#E663_5C01#,
      16#6B6B_51F4#, 16#1C6C_6162#, 16#8565_30D8#, 16#F262_004E#,
      16#6C06_95ED#, 16#1B01_A57B#, 16#8208_F4C1#, 16#F50F_C457#,
      16#65B0_D9C6#, 16#12B7_E950#, 16#8BBE_B8EA#, 16#FCB9_887C#,
      16#62DD_1DDF#, 16#15DA_2D49#, 16#8CD3_7CF3#, 16#FBD4_4C65#,
      16#4DB2_6158#, 16#3AB5_51CE#, 16#A3BC_0074#, 16#D4BB_30E2#,
      16#4ADF_A541#, 16#3DD8_95D7#, 16#A4D1_C46D#, 16#D3D6_F4FB#,
      16#4369_E96A#, 16#346E_D9FC#, 16#AD67_8846#, 16#DA60_B8D0#,
      16#4404_2D73#, 16#3303_1DE5#, 16#AA0A_4C5F#, 16#DD0D_7CC9#,
      16#5005_713C#, 16#2702_41AA#, 16#BE0B_1010#, 16#C90C_2086#,
      16#5768_B525#, 16#206F_85B3#, 16#B966_D409#, 16#CE61_E49F#,
      16#5EDE_F90E#, 16#29D9_C998#, 16#B0D0_9822#, 16#C7D7_A8B4#,
      16#59B3_3D17#, 16#2EB4_0D81#, 16#B7BD_5C3B#, 16#C0BA_6CAD#,
      16#EDB8_8320#, 16#9ABF_B3B6#, 16#03B6_E20C#, 16#74B1_D29A#,
      16#EAD5_4739#, 16#9DD2_77AF#, 16#04DB_2615#, 16#73DC_1683#,
      16#E363_0B12#, 16#9464_3B84#, 16#0D6D_6A3E#, 16#7A6A_5AA8#,
      16#E40E_CF0B#, 16#9309_FF9D#, 16#0A00_AE27#, 16#7D07_9EB1#,
      16#F00F_9344#, 16#8708_A3D2#, 16#1E01_F268#, 16#6906_C2FE#,
      16#F762_575D#, 16#8065_67CB#, 16#196C_3671#, 16#6E6B_06E7#,
      16#FED4_1B76#, 16#89D3_2BE0#, 16#10DA_7A5A#, 16#67DD_4ACC#,
      16#F9B9_DF6F#, 16#8EBE_EFF9#, 16#17B7_BE43#, 16#60B0_8ED5#,
      16#D6D6_A3E8#, 16#A1D1_937E#, 16#38D8_C2C4#, 16#4FDF_F252#,
      16#D1BB_67F1#, 16#A6BC_5767#, 16#3FB5_06DD#, 16#48B2_364B#,
      16#D80D_2BDA#, 16#AF0A_1B4C#, 16#3603_4AF6#, 16#4104_7A60#,
      16#DF60_EFC3#, 16#A867_DF55#, 16#316E_8EEF#, 16#4669_BE79#,
      16#CB61_B38C#, 16#BC66_831A#, 16#256F_D2A0#, 16#5268_E236#,
      16#CC0C_7795#, 16#BB0B_4703#, 16#2202_16B9#, 16#5505_262F#,
      16#C5BA_3BBE#, 16#B2BD_0B28#, 16#2BB4_5A92#, 16#5CB3_6A04#,
      16#C2D7_FFA7#, 16#B5D0_CF31#, 16#2CD9_9E8B#, 16#5BDE_AE1D#,
      16#9B64_C2B0#, 16#EC63_F226#, 16#756A_A39C#, 16#026D_930A#,
      16#9C09_06A9#, 16#EB0E_363F#, 16#7207_6785#, 16#0500_5713#,
      16#95BF_4A82#, 16#E2B8_7A14#, 16#7BB1_2BAE#, 16#0CB6_1B38#,
      16#92D2_8E9B#, 16#E5D5_BE0D#, 16#7CDC_EFB7#, 16#0BDB_DF21#,
      16#86D3_D2D4#, 16#F1D4_E242#, 16#68DD_B3F8#, 16#1FDA_836E#,
      16#81BE_16CD#, 16#F6B9_265B#, 16#6FB0_77E1#, 16#18B7_4777#,
      16#8808_5AE6#, 16#FF0F_6A70#, 16#6606_3BCA#, 16#1101_0B5C#,
      16#8F65_9EFF#, 16#F862_AE69#, 16#616B_FFD3#, 16#166C_CF45#,
      16#A00A_E278#, 16#D70D_D2EE#, 16#4E04_8354#, 16#3903_B3C2#,
      16#A767_2661#, 16#D060_16F7#, 16#4969_474D#, 16#3E6E_77DB#,
      16#AED1_6A4A#, 16#D9D6_5ADC#, 16#40DF_0B66#, 16#37D8_3BF0#,
      16#A9BC_AE53#, 16#DEBB_9EC5#, 16#47B2_CF7F#, 16#30B5_FFE9#,
      16#BDBD_F21C#, 16#CABA_C28A#, 16#53B3_9330#, 16#24B4_A3A6#,
      16#BAD0_3605#, 16#CDD7_0693#, 16#54DE_5729#, 16#23D9_67BF#,
      16#B366_7A2E#, 16#C461_4AB8#, 16#5D68_1B02#, 16#2A6F_2B94#,
      16#B40B_BE37#, 16#C30C_8EA1#, 16#5A05_DF1B#, 16#2D02_EF8D#);

end CRC32;

The CRC32 implementation in crc32.adb:

------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- This file is part of 'CRC32'                                             --
--                                                                          --
-- You do not have, nor can you ever acquire the right to use, copy or      --
-- distribute this software ; Should you use this software for any purpose, --
-- or copy and distribute it to anyone or in any manner, you are breaking   --
-- the laws of whatever soi-disant jurisdiction, and you promise to         --
-- continue doing so for the indefinite future. In any case, please         --
-- always : read and understand any software ; verify any PGP signatures    --
-- that you use - for any purpose.                                          --
--                                                                          --
-- See also http://trilema.com/2015/a-new-software-licensing-paradigm .     --
------------------------------------------------------------------------------
------------------------------------------------------------------------------

  -- CRC32 implementation
  -- S.MG, 2018

package body CRC32 is
  function CRC( S: in String ) return CRC32 is
    Result : CRC32 := Init_Value;
    Value  : CRC32;
  begin
    -- process input character by character
    for C of S loop
      Value  := CRC32( Character'Pos( C ) );
      Result := Shift_Right(Result, 8) xor
                  Lookup( Value xor (Result and LSB_MASK));
    end loop;
    -- reflect result
    Result := Result xor Xor_Out;

    return Result;
  end CRC;

  function CRC( Data: in Octet_Array ) return CRC32 is
    Result : CRC32 := Init_Value;
  begin
    -- process input octet by octet
    for C of Data loop
      Result := Shift_Right(Result, 8) xor
                  Lookup( CRC32(C) xor (Result and LSB_MASK));
    end loop;
    -- reflect result
    Result := Result xor Xor_Out;

    return Result;
  end CRC;

end CRC32;

Other than the above, the .vpatch for this chapter contains also some basic testing of the read/write methods for Serpent keysets:

  -- Tests for the serialization of data structures in SMG Protocol
 -- S.MG, 2018

with RNG;
with Data_Structs; use Data_Structs;
with Messages; use Messages;
with Interfaces; use Interfaces;
with System;
with System.Storage_Elements; use System.Storage_Elements;
with Ada.Text_IO; use Ada.Text_IO;

package body Test_Serializing is

  procedure Serialize_Keyset_SS is
    Msg  : Serpent_Msg;
    KSet : Serpent_Keyset(5);
    LSB : Interfaces.Unsigned_8 := 16#01#;
    MSB : Interfaces.Unsigned_8 := 16#80#;
    LMSB: Interfaces.Unsigned_8 := 16#81#;
    Counter : Interfaces.Unsigned_16 := 101;
    NewSet: Serpent_Keyset;
    NewCounter: Interfaces.Unsigned_16:=0;
  begin
    Put_Line("Generating the Serpent Keys...");
    -- fill a set of Serpent Keys
    for I in 1..KSet.N loop
      RNG.Get_Octets(KSet.Keys(Interfaces.Unsigned_8(I)));
    end loop;
    KSet.Flag := LSB;

    Put_Line("Writing the keys to message...");
    -- write keyset to serpent message
    Write_SKeys_SMsg( KSet, Counter, Msg );

    Put_Line("Reading keys back from message...");
    -- read keyset from message
    Read_SKeys_SMsg( Msg, Counter, NewSet );

    Put_Line("Comparing the keysets...");
    -- compare the two keysets
    if NewSet /= KSet then
      Put_Line("FAIL: keysets are different!");
    else
      Put_Line("PASS: keysets are the same!");
    end if;

    Put_Line("Attempting to read from mangled message");
    begin
      Msg(Msg'First) := Msg(Msg'First)+25;
      Read_SKeys_SMsg( Msg, Counter, NewSet);
      Put_Line("FAIL: read failed to raise invalid message exception!");
    exception
      when Invalid_Msg =>
        Put_Line("PASS: exception correctly raised for invalid message");
    end;
  end Serialize_Keyset_SS;

end Test_Serializing;

As I don't currently have any access to a Big Endian computer, I couldn't quite test the whole thing properly. If you have a Big Endian computer and get around to test this code, please let me know how it behaves.

The above method seems to me robust at least and reasonably easy to follow. Feel free however to loudly disagree and shoot it down in the comments below - I'll gladly read any better proposal. As mentioned in the very beginning of this post, this is not exactly what I hoped to obtain although it is the thing I have so far and therefore the thing I'm publishing - to be discussed and improved on, hopefully.

The .vpatch and my signature for it are as usual on my Reference Code Shelf and linked below for your convenience:


  1. The official solution for anything touching serialization seems to boil down to "Streams or gtfo" apparently. And for my specific case here I don't quite see the point of bringing in the whole Streams mammouth