/******************************************************************************
 * 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 dual2
{

/*** *** *** *** PDU *** *** *** ***/
type record PDU1 {
  charstring s
}
with { variant "" }

type record PDU2 {
  charstring s
}
with { variant (s) "FIELDLENGTH(8)" }

/*** *** *** *** Ports *** *** *** ***/
type port UNDER message {
  inout octetstring
}
with { extension "provider" }

type port SENDER message {
  out PDU1;
}
with { extension "user UNDER
  out(PDU1 -> octetstring : function(p1o))
  in (octetstring -> - : discard)
" }

type port RECEIVER message {
  in PDU2
}
with { extension "user UNDER
  //out(PDU1 -> - : discard)
  in (octetstring -> PDU2 : function(op2sl))
" }

/*** *** *** *** mapper functions *** *** *** ***/

// A "prototype(fast)" implemented in TTCN
function p1o(in PDU1 p, out octetstring o) {
  o:= char2oct(p.s);
}
with { extension "prototype(fast)" }

function op2(in octetstring o, out PDU2 p) {
  p.s := oct2char(o);
}
with { extension "prototype(fast)" }

// A "prototype(sliding)" implemented in TTCN
function op2sl(inout octetstring o, out PDU2 p) return integer {
  //log(">>> op2sl ", o);
  var integer lo := lengthof(o); // octets, 'ABCD' = 2
  if (lo < 8) {
    //log("<<< return 2: not enough:", lo)
    return 2;
  }
  //else { log("=== length is ", lo) }
  p.s := oct2char(substr(o, 0, 8)); // "decode" the PDU
  o := substr(o, 8, lo-8); // return the rest
  //log(">>>   res=", p.s);
  //log(">>>   rem=", o);
  //log(">>> return 0");
  return 0;
}
with { extension "prototype(sliding)" }

/*** *** *** *** Components *** *** *** ***/
type component D2 {
  port SENDER stringport;
  port RECEIVER reverseport;
  port SENDER another_stringport;
}

type component SYS {
  //port PT1 SYSTEM_PORT2;
}

/*** *** *** *** Testcases *** *** *** ***/

// Intrernal communication with mapped ports
testcase intern() runs on D2
{
  connect   (mtc:stringport, mtc:reverseport);
  var PDU1 cr := { "howdy!" }
  stringport.send(cr);
  alt {
    [] reverseport.receive /* anything */ { setverdict(fail, "premature receive"); }
    [else] { setverdict(pass); } // less than eight bytes: shouldn't receive anything
  }

  stringport.send(PDU1 : { "??" }) // the missing two bytes
  alt {
    [] reverseport.receive(PDU2 : {"howdy!??"}) { setverdict(pass); }
    [] reverseport.receive { setverdict(fail, "wrong receive"); }
    [else] { setverdict(fail, "no receive"); }
  }
  disconnect(mtc:stringport, mtc:reverseport);
}

type set PDU3 {
  //charstring s,
  octetstring s, // EXTENSION_BIT not implemented for charstring
  integer i
}
with { variant ""; /*FIELDLENGTH(8)*/
  variant (s) "EXTENSION_BIT(yes)";
  variant (i) "FIELDLENGTH(24)"; // bits
}

external function enc3(in PDU3 p) return octetstring
with { extension "prototype(convert) encode(RAW)" }

external function dec3(in octetstring o) return PDU3
with { extension "prototype(convert) decode(RAW)" }

external function dec3sl(inout octetstring o, out PDU3 p) return integer
with { extension "prototype(sliding) decode(RAW) errorbehavior(INVAL_MSG:WARNING,INCOMPL_MSG:WARNING)" }

// Try to elicit various return values from the decoder function
testcase errored() runs on D2 system D2
{
  connect(mtc:stringport, mtc:reverseport);
  var PDU1 p1 := { "yay....." }
  stringport.send(p1);

  alt {
    [] reverseport.receive(PDU2 : ?) { setverdict(pass, "got PDU2"); }
    [else] { setverdict(fail, "no receive at ", __LINE__); }
  }

  var octetstring three := char2oct("three!!!");
  var PDU3 p0 := { s := three, i := 3 };
  var octetstring o := enc3(p0);
  var octetstring incompl;
  var integer retval;
  {
  var PDU3 p;
  log("=== === === ===    encoded as ", o);
  incompl := '74687265652121A11B0000'O; // "three!!!"
  //p := dec3(incompl);
  retval := dec3sl(incompl, p);

  if (retval == 0) { setverdict(pass) }
  else { setverdict(fail, "nonzero at ", __LINE__); }
  }

  {
  var PDU3 p;
  log("___ string only ___");
  incompl := '74687265652121A1'O;
  retval := dec3sl(incompl, p);

  if (retval == 2) { setverdict(pass) }
  else { setverdict(fail, "not 2 at ", __LINE__); }
  }

  {
  var PDU3 p;
  log("___ plus nine ___");
  incompl := hex2oct('74687265652121A130'H);
  retval := dec3sl(incompl, p);

  if (retval == 1) { setverdict(pass) }
  else { setverdict(fail, "not 1 at ", __LINE__); }
  }
  disconnect(mtc:stringport, mtc:reverseport);
}

// Test sliding decoding with multiple connections
testcase two_conn() runs on D2
{
  connect(mtc:stringport,         mtc:reverseport);
  connect(mtc:another_stringport, mtc:reverseport);

  var PDU1 p1a := { "ABCD" }, p1e := { "EFGH" }

  stringport.send(p1a); // first half
  alt {
    [] reverseport.receive /* anything */{
      setverdict(fail, "It wasn't supposed to receive anything yet");
    }
    [else] /* nothing received */ { setverdict(pass); }
  }

  another_stringport.send(p1a); // send first half on another connection
  alt {
    [] reverseport.receive /* anything */{
      // If the different connections were not properly separated,
      // a message would appear at this point (eight characters => one message)
      setverdict(fail, "It STILL wasn't supposed to receive anything yet");
    }
    [else] /* nothing received */ { setverdict(pass); }
  }

  stringport.send(p1e); // send second half on the first connection
  alt {
    [] reverseport.receive( PDU2 : {"ABCDEFGH"} ) { setverdict(pass); }
    [else] { setverdict(fail, "no receive"); }
  }

  another_stringport.send(p1e); // send second half on the second connection
  alt {
    [] reverseport.receive( PDU2 : {"ABCDEFGH"} ) { setverdict(pass); }
    [else] { setverdict(fail, "no receive"); }
  }

  disconnect(mtc:stringport,         mtc:reverseport);
  disconnect(mtc:another_stringport, mtc:reverseport);
}

control {
  execute(intern());
  execute(errored());
  execute(two_conn());
}

}  // end of module
with {
  encode "RAW"
}
