Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping values of two scalar types #907

Open
senier opened this issue Jan 17, 2022 · 2 comments
Open

Mapping values of two scalar types #907

senier opened this issue Jan 17, 2022 · 2 comments
Labels
architectural decision Discussion of design decision specification Related to specification package (e.g., specification parsing)

Comments

@senier
Copy link
Member

senier commented Jan 17, 2022

Context and Problem Statement

In protocols the values of two scalar types sometimes have to be mapped onto / converted into each other in a way that cannot be done by a calculation. The most prominent example are algorithms selected in a protocol session which result in response messages of different lengths:

package Test is

   type Alg is (Alg1,  -- 1 byte response
                Alg2,  -- 2 byte response
                Alg3)  -- 4 byte response
      with Size => 8;
   type Len is mod 2 ** 16;

   type Request is
      message
         Alg : Alg;
      end message;

   type Response (Len : Len) is
      message
         Data : Opaque
            with Size => 8 * Len;
      end message;

   generic
      Transport : Channel with Readable, Writable;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Prepare_Response
      end Receive;

      state Prepare_Response
      is
      begin
         Response := Response'(Len => 1, -- FIXME: Depends on Request.Alg
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

end Test;

Use Cases

  • Determine size of DIGESTS response based on selected hash algorithm in SPDM protocol (7.4)

Considered Options

O1

Use states to select the corresponding value:

   generic
      Transport : Channel with Readable, Writable;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
      Len : Len;
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Set_Alg1_Length
            if Request.Alg = Alg1
         goto Set_Alg2_Length
            if Request.Alg = Alg2
         goto Set_Alg3_Length
            if Request.Alg = Alg3
         goto Prepare_Response
      end Receive;

      state Set_Alg1_Length is
      begin
         Len := 1;
      transition
         goto Prepare_Response
      end Set_Alg1_Length;

      state Set_Alg2_Length is
      begin
         Len := 2;
      transition
         goto Prepare_Response
      end Set_Alg2_Length;

      state Set_Alg3_Length is
      begin
         Len := 4;
      transition
         goto Prepare_Response
      end Set_Alg3_Length;

      state Prepare_Response
      is
      begin
         Response := Response'(Len => Len,
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

+ No changes required
Very verbose
Potentially many additional states
Totality not enforced

O2

Use sequences and list comprehension:

   generic
      Transport : Channel with Readable, Writable;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Prepare_Response
      end Receive;

      state Prepare_Response
      is
         Maps : Maps;
         Lens : Maps;
         Sel  : Map;
      begin
         Maps'Append (Map'(Alg => Alg1, Len => 1));
         Maps'Append (Map'(Alg => Alg2, Len => 2));
         Maps'Append (Map'(Alg => Alg3, Len => 4));
         Lens := [for M in Maps if M.Alg = Request.Alg => M];
         Sel := Lens'Head;
         Response := Response'(Len => Sel.Len,
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

+ No changes required
Convoluted syntax
Inefficient code (space and time)
Intent is not conveyed clearly in specification
Totality not enforced

O3

Introduce case expression:

   generic
      Transport : Channel with Readable, Writable;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Prepare_Response
      end Receive;

      state Prepare_Response
      is
      begin
         Response := Response'(Len => (case Request.Alg is
                                       when Alg1 => 1,
                                       when Alg2 => 2,
                                       when Alg3 => 4),
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

+ Concise
+ Conveys the intent clearly
+ Totality can be enforced
+ Probably easy to transform to SPARK
Somewhat redundant to existing state transitions
Inefficient when same mapping is required in multiple places
Reusability questionable

O4

Use external function:

   generic
      Transport : Channel with Readable, Writable;
      with function Alg_Len (Alg : Alg) return Len;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Prepare_Response
      end Receive;

      state Prepare_Response
      is
         Len : Len;
         Alg : Alg;
      begin
         Alg := Request.Alg;
         Len := Alg_Len (Alg);
         Response := Response'(Len => Len,
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

+ Concise
+ No changes required
Intent is not conveyed in specification at all
Totality not enforced
Code changes may break specification
Proof may become hard or impossible

O5

Introduce a dedicated mapping type (concrete syntax to be defined):

package Test is

   type Alg is (Alg1,  -- 1 byte response
                Alg2,  -- 2 byte response
                Alg3)  -- 4 byte response
      with Size => 8;
   type Len is mod 2 ** 16;

   type Alg_Len is map (Alg) of Len;

   type Request is
      message
         Alg : Alg;
      end message;

   type Response (Len : Len) is
      message
         Data : Opaque
            with Size => 8 * Len;
      end message;

   generic
      Transport : Channel with Readable, Writable;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
      Alg_Len : Alg_Len := (Alg1 => 1, Alg2 => 2, Alg3 => 4);
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Prepare_Response
      end Receive;

      state Prepare_Response
      is
      begin
         Response := Response'(Len => Alg_Len (Request.Alg),
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

end Test;

+ Concise
+ Intent conveyed clearly
+ Potentially easy to translate into SPARK (using arrays)
+ Totality can be enforced
+ Efficient
Reusability questionable

O6

Introduce expression functions and case expressions:

package Test is

   type Alg is (Alg1,  -- 1 byte response
                Alg2,  -- 2 byte response
                Alg3)  -- 4 byte response
      with Size => 8;
   type Len is mod 2 ** 16;

   function Alg_Len (Alg : Alg) return Len is (case Alg is
                                               when Alg1 => 1,
                                               when Alg2 => 2,
                                               when Alg3 => 4);

   type Request is
      message
         Alg : Alg;
      end message;

   type Response (Len : Len) is
      message
         Data : Opaque
            with Size => 8 * Len;
      end message;

   generic
      Transport : Channel with Readable, Writable;
   session S with
      Initial => Receive,
      Final => Done
   is
      Request : Request;
      Response : Response;
   begin
      state Receive is
      begin
         Transport'Read (Request);
      transition
         goto Done
            if Request'Valid /= True
         goto Prepare_Response
      end Receive;

      state Prepare_Response
      is
      begin
         Response := Response'(Len => Alg_Len (Request.Alg),
                               Data => [1]);
      transition
         goto Send_Response
      exception
         goto Done
      end Prepare_Response;

      state Send_Response is
      begin
         Transport'Write (Response);
      transition
         goto Done
      end Send_Response;

      state Done is null state;
   end S;

end Test;

+ Concise
+ Intent conveyed clearly
+ Totality can be enforced
+ Efficient
+ Reusable
+ Expressive
May be overkill for problem to solve

O7a

Introduce a dedicated mapping type:

package Test is

   type Alg is (Alg1,  -- 1 byte response
                Alg2,  -- 2 byte response
                Alg3)  -- 4 byte response
      with Size => 8;
   type Len is mod 2 ** 16;

   for Alg use mapping Alg_Len to Len is (Alg1 => 1,
                                          Alg2 => 2,
                                          Alg3 => 4);
end Test;

O7b

Introduce a dedicated mapping type:

package Test is

   type Alg is (Alg1,  -- 1 byte response
                Alg2,  -- 2 byte response
                Alg3)  -- 4 byte response
      with Size => 8;
   type Len is mod 2 ** 16;

   mapping Alg_Len (Alg) to Len is 
      (Alg1 => 1,
       Alg2 => 2,
       Alg3 => 4);

end Test;

+ Concise
+ Intent conveyed clearly
+ Potentially easy to translate into SPARK (using arrays)
+ Totality can be enforced
+ Efficient
Not reusable
Unusual syntax
Two new keywords for one new feature

Decision Outcome

O6

@senier senier added specification Related to specification package (e.g., specification parsing) architectural decision Discussion of design decision labels Jan 17, 2022
@senier senier added this to To do in RecordFlux 0.6 via automation Jan 17, 2022
@senier senier moved this from To do to Design in RecordFlux 0.6 Jan 17, 2022
@senier senier moved this from Design to To do in RecordFlux 0.6 Jan 18, 2022
@sttaft
Copy link

sttaft commented Feb 10, 2022

FWIW, the list comprehension syntax does not match that provided by Ada 2022 "filters" -- filters use "when M.Alg = Request_Alg" rather than "if ...", hence "[for M in Maps when M.Alg = Request.Alg => M];" This also might not produce exactly one result.

The "dedicated mapping type" could use Ada 2022 syntax for container aggregates (these use "[...]" rather than "(...)") and Ada 2012 syntax for indexing and be supported by SPARK directly. Not sure what you mean by "reusability" concerns.

A simple array aggregate would seem to be the simplest, however, in that that provides totality and efficiency.

@treiher
Copy link
Collaborator

treiher commented Feb 10, 2022

@sttaft Thanks for your input!

FWIW, the list comprehension syntax does not match that provided by Ada 2022 "filters" -- filters use "when M.Alg = Request_Alg" rather than "if ...", hence "[for M in Maps when M.Alg = Request.Alg => M];" This also might not produce exactly one result.

We actually changed syntax for list comprehensions in the meantime to be more consistent to Ada 2022 (cf. #702), although we decided to still deviate by using the if keyword instead of when. when would have been a new keyword for the RecordFlux language, and we did not see a good reason for introducing when just for comprehensions.

The "dedicated mapping type" could use Ada 2022 syntax for container aggregates (these use "[...]" rather than "(...)") and Ada 2012 syntax for indexing and be supported by SPARK directly. Not sure what you mean by "reusability" concerns.

The concern is that we are adding this new language construct just for this very specific issue. I think newly added feature should ideally cover multiple use cases.

A simple array aggregate would seem to be the simplest, however, in that that provides totality and efficiency.

Do you propose adding an array type similar to Ada? That is not yet part of the language, but we could certainly consider it as an option.

@treiher treiher moved this from To do to Implementation in RecordFlux 0.6 Jun 13, 2022
@senier senier self-assigned this Jun 13, 2022
senier added a commit that referenced this issue Jul 1, 2022
senier added a commit that referenced this issue Jul 1, 2022
senier added a commit that referenced this issue Jul 1, 2022
senier added a commit that referenced this issue Jul 1, 2022
senier added a commit that referenced this issue Jul 1, 2022
senier added a commit that referenced this issue Jul 1, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 5, 2022
senier added a commit that referenced this issue Jul 8, 2022
senier added a commit that referenced this issue Jul 8, 2022
senier added a commit that referenced this issue Jul 11, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
senier added a commit that referenced this issue Jul 12, 2022
@senier senier moved this from Implementation to To do in RecordFlux 0.6 Jul 14, 2022
@senier senier removed this from To do in RecordFlux 0.6 Aug 23, 2022
@senier senier added this to To do in RecordFlux Future via automation Aug 23, 2022
@senier senier moved this from To do to Long-term in RecordFlux Future Aug 24, 2022
@senier senier removed their assignment Aug 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
architectural decision Discussion of design decision specification Related to specification package (e.g., specification parsing)
Projects
No open projects
Development

No branches or pull requests

3 participants