/******************************************************************************
 * Copyright (c) 2000-2021 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Balasko, Jeno
 *   Raduly, Csaba
 *
 ******************************************************************************/
module dual { // straight off 4.21.8 of reference guide

// PDU types to be sent and received on the ports
type record ControlRequest {
  charstring text
}

type record ControlResponse{
  charstring text
}

type record ErrorSignal {
  charstring err
}

type record PDUType1 {
  charstring text
}
with { variant "/* dummy */" }

type record PDUType2 {
  charstring text
}
with { variant (text) "FIELDLENGTH(8)" }

type record of charstring strings
with { variant "/* dummy */" }

type record of octetstring blob
with { variant "/* dummy */" }


// the encoder/decoder functions are written in C++
external function enc_PDUType1(in PDUType1 par) return octetstring
// there was a typo here ------------^ (was lowercase t)
with { extension "prototype(convert)" }

// Same as above, but generated by the compiler
external function enc_PDUType1_gen(in PDUType1 par) return octetstring
with { extension "prototype(convert) encode(RAW)" }


// another conversion function in C++
external function dec_PDUType1(in octetstring stream,
                               out PDUType1 result) return integer
with { extension "prototype(backtrack)" }

// Again, generated by the compiler
external function dec_PDUType1_gen(in octetstring stream,
                               out PDUType1 result) return integer
with { extension "prototype(backtrack) decode(RAW)" }

// And now, something completely different! Sliding decoding!

// Hand-written decoder function
external function dec_slider(inout octetstring stream, out PDUType1 result) return integer
with { extension "prototype(sliding)" }

// Compiler-generated
external function dec_slider_gen(inout octetstring stream, out PDUType1 result) return integer
with { extension "prototype(sliding) decode(RAW) errorbehavior(INCOMPL_MSG:IGNORE, LEN_ERR:IGNORE)" }

// Compiler-generated
external function dec_slider_gen2(inout octetstring stream, out PDUType2 result) return integer
with { extension "prototype(sliding) decode(RAW) errorbehavior(INCOMPL_MSG:IGNORE, LEN_ERR:IGNORE)" }

// Various other prototypes
external function dec_blob_fast(in octetstring stream, out blob val)
with { extension "prototype(fast) decode(RAW)" }

external function dec_strings(in octetstring stream) return strings
with { extension "prototype(convert) decode(RAW)" }

external function bk_strings(in octetstring stream, out strings s) return integer
with { extension "prototype(backtrack) decode(RAW)" }

external function slide_strings(inout octetstring strm, out strings val) return integer
with { extension "prototype(sliding) decode(RAW)" }

function bogus(in octetstring i, out ControlRequest o) return integer
{
  // Yes, you can do this.
  // If you can decode a ControlRequest in TTCN-3 code, go right ahead.
  // It's more likely to be useful when mapping from one user-defined type
  // to another (e.g. shuffling fields from one record to another).
  // Implementing RAW decoding is going to be a bit more tricky...
  o := { oct2char(i) }; // living dangerously...
  return 0;
}
with { extension "prototype(backtrack)" }


/************************************ The ports ************************************/

// port type PT1 is the external interface of the dual-faced port
// with its own Test Port. See section "The purpose of Test Ports" in the API guide.
type port PT1 message {
  out ControlRequest;
  in  ControlResponse;
  in  ErrorSignal;
  inout octetstring;
  out PDUType1;
} with { extension "provider" }

// port type PT2 is the internal interface of the dual-faced port
// This port is communicating (in)directly with the SUT using the Test Port of PT1.
type port PT2 message {
  in  ControlResponse;
  out ControlRequest;
//  out ErrorSignal; // this is bogus
  inout PDUType1, PDUType2;
//  out octetstring;
  inout octetstring;
  in strings, blob;
} with { extension "user PT1
  out(ControlRequest -> ControlRequest: simple;       /* no conversion */
    PDUType1 -> octetstring: function(enc_PDUType1);  /* call function */
    PDUType2 -> octetstring: encode(RAW))             /* call built-in codec */

  /* Multi-target out mapping ???? */
  out(octetstring -> octetstring : decode(RAW),
                     ControlRequest : function(bogus),
                     - : discard)

  in(ControlResponse -> ControlResponse  : simple;    /* no conversion */
     ErrorSignal     -> -  : discard;                 /* drop */
    octetstring -> PDUType1: function(dec_PDUType1),  /* call function */
                   PDUType2: decode(RAW),             /* built-in decoder */
                   -       : discard)"
}

// As above, but uses the generated converter functions
type port PT2GEN message {
  in  ControlResponse;
  out ControlRequest;
//  out ErrorSignal; // this is bogus
  inout PDUType1, PDUType2;
  inout octetstring;
  in strings, blob;
} with { extension "user PT1
  out(ControlRequest -> ControlRequest: simple;       /* no conversion */
    PDUType1 -> octetstring: function(enc_PDUType1_gen);  /* call function */
    PDUType2 -> octetstring: encode(RAW);             /* call built-in codec */
    octetstring -> PDUType1: function(dec_slider_gen) /* outgoing map with a prototype(sliding) decode(...) !! */
    )

  in(ControlResponse -> ControlResponse  : simple;     /* no conversion */
     ErrorSignal     -> -  : discard;                  /* drop */
    octetstring -> PDUType2: function(dec_slider_gen2) /* call function; prototype(sliding) */
                ,  PDUType1: decode(RAW)               /* built-in decoder */
                , strings : function(slide_strings)
                , blob : decode(RAW)
                /*,  strings : function(bk_strings)     /* call function; protoype(backtrack)
          /*      ,  strings : function(dec_strings)     /* call function; protoype(convert) */
          /*      ,  blob    : function(dec_blob_fast)   /* call function; protoype(fast) */
                /* prototype(backtrack) cannot be used when prototype(sliding) is present,
                   although code gen for sliding is not supported */
                /*, -       : discard cannot be used when proto sliding is present */)"
}

type port STRINGPORT message
{
  in strings;
}
with {
  extension "user PT1
    in(octetstring     -> strings : function(dec_strings)     /* call function; protoype(convert) */
    ;  ControlResponse -> - : discard
    ;  ErrorSignal     -> - : discard )"
}

type port FASTPORT message
{
  in blob
}
with {
  extension "user PT1
    in(octetstring -> blob : function(dec_blob_fast)     /* call function; protoype(fast) */
    ;  ControlResponse -> - : discard
    ;  ErrorSignal     -> - : discard )"

}



type component MTC_CT {
//   port PT2 MTC_PORT;
  port PT2GEN MTC_PORT;
}

type component SYSTEM_SCT {
  port PT1 SYSTEM_PORT;
  //port PT1 ANOTHER_SYSTEM_PORT;
}

testcase tc_DUALFACED_func () runs on MTC_CT system SYSTEM_SCT
{
  map(mtc:MTC_PORT, system:SYSTEM_PORT);
  //map(mtc:MTC_PORT, system:ANOTHER_SYSTEM_PORT);
  // a second (different) mapping completely prevents sending of messages on the port

  // Send in twice the FIELDLENGTH of PDUType2. This should be decoded
  // as *two* consecutive incoming messages.
  MTC_PORT.send(PDUType1 : {"type: 1!type: 2!"});
  /** MTC_PORT is a PT2GEN, a "user" port. It uses the PT1 "provider" port.
	* It has no user-conrollable parts; no skeleton is generated.
	* Sending a PDUType1 calls enc_PDUType1_gen to transform it into octetstring
	* which is the sent through PT1_PROVIDER::otgoing_send.
	* PT1 is the provder; it is a "real" port with a skeleton.
	*
	* PT1_PROVIDER::otgoing_send is written to call PT2GEN::incoming_message
	* (because it's a fake port; a real one would call incoming_message
	* from an event handler). PT2GEN is derived from PT1_PROVIDER.
	* Here the incoming mapping happens.
  ***/
  timer t := 3.0;
  t.start;
  alt {
    [] MTC_PORT.receive(PDUType2 : {"type: 1!"}) { setverdict(pass, "got PDU2"); }
    [] MTC_PORT.receive /* anything else */{ setverdict(fail, "got unknown response"); }
    [] t.timeout { setverdict(fail, "time-out"); }
  }
  t.stop;
  t.start;
  alt {
    [] MTC_PORT.receive(PDUType2 : {"type: 2!"}) { setverdict(pass, "got PDU2"); }
    [] MTC_PORT.receive /* anything else */{ setverdict(fail, "got unknown response"); }
    [] t.timeout { setverdict(fail, "time-out"); }
  }
  unmap(mtc:MTC_PORT, system:SYSTEM_PORT);
}

testcase tc_DUALFACED_simple () runs on MTC_CT system SYSTEM_SCT
{
  map(mtc:MTC_PORT, system:SYSTEM_PORT);

  MTC_PORT.send(ControlRequest:{ "CReq from simple" });
  timer t := 3.0;
  t.start;
  // When the port gets a ControlRequest which begins with 'C', it sends back a ControlResponse
  alt {
    [] MTC_PORT.receive(ControlResponse:?) { setverdict(pass /* , "got response" */); }
    [] MTC_PORT.receive /* anything else */{ setverdict(fail, "got unknown response"); }
    [] t.timeout { setverdict(fail, "time-out"); }
  }

  // v2
  t.stop;
  MTC_PORT.send(ControlRequest:{ "Another CReq from simple" });
  t.start;
  // Because the text in the ControlRequest does not start with a 'C',
  // the port sends back an ErrorSignal... which is discarded by the incoming mapping.
  alt {
    [] MTC_PORT.receive(ControlResponse:?) { setverdict(fail, "got WRONG response"); }
    [] MTC_PORT.receive /* anything else */{ setverdict(fail, "got unknown response"); }
    [else] /* nothing received */ { setverdict(pass); }
  }

  unmap(mtc:MTC_PORT, system:SYSTEM_PORT);
}

testcase tc_DUALFACED_slid() runs on MTC_CT system SYSTEM_SCT
{
  map(mtc:MTC_PORT, system:SYSTEM_PORT);
  var PDUType1 p1 := { "Ulysses!" };
  var PDUType2 p2 := { p1.text };
  var octetstring output := enc_PDUType1(p1);
  MTC_PORT.send(output);
  timer t := 3.0;
  t.start;
  //
  alt {
    [] MTC_PORT.receive(PDUType2 : p2) { setverdict(pass); }
    [] MTC_PORT.receive /* anything else */ { setverdict(fail, "got unknown response"); }
    [] t.timeout { setverdict(fail, "time-out at ", __LINE__); }
  }
  t.stop;

  /*** test that sending a message in two parts gets reassembled ***/
  var integer len := lengthof(output);
  var integer half_len := len / 2;
  // Send the first half \\
  var octetstring first_half := substr(output, 0, half_len);
  MTC_PORT.send(first_half);
  t.start;
  alt {
    [] MTC_PORT.receive /* anything */ {
      setverdict(fail, "It wasn't supposed to receive anything yet");
    }
    [else] /* nothing received */ { setverdict(pass); }
  }
  t.stop;

  // Send the second half \\
  var octetstring second_half := substr(output, half_len, len - half_len);
  MTC_PORT.send(second_half);
  t.start;
  alt {
    [] MTC_PORT.receive(PDUType2 : p2) { setverdict(pass); }
    [] MTC_PORT.receive /* anything else */ { setverdict(fail, "got unknown response"); }
    [] t.timeout { setverdict(fail, "time-out at ", __LINE__); }
  }

  unmap(mtc:MTC_PORT, system:SYSTEM_PORT);
}

control {
  execute( tc_DUALFACED_simple() );
  execute( tc_DUALFACED_func() );
  execute( tc_DUALFACED_slid() );
}

}
with {
  encode "RAW";
}
