/******************************************************************************
 * Copyright (c) 2000-2025 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:
 *   Baranyi, Botond – initial implementation
 *
 ******************************************************************************/
module ChoiceTests {

import from Types all;
import from Tools all;

// unaligned
external function enc_cho1(in Choice1 x) return bitstring with { extension "prototype(convert) encode(PER)" }
external function enc_cho2(in Choice2 x) return bitstring with { extension "prototype(convert) encode(PER)" }
external function enc_cho3(in Choice3 x) return bitstring with { extension "prototype(convert) encode(PER)" }
external function enc_cho4(in Choice4 x) return bitstring with { extension "prototype(convert) encode(PER)" }
external function enc_cho5(in Choice5 x) return bitstring with { extension "prototype(convert) encode(PER)" }

external function dec_cho1(in bitstring x) return Choice1 with { extension "prototype(convert) decode(PER)" }
external function dec_cho2(in bitstring x) return Choice2 with { extension "prototype(convert) decode(PER)" }
external function dec_cho3(in bitstring x) return Choice3 with { extension "prototype(convert) decode(PER)" }
external function dec_cho4(in bitstring x) return Choice4 with { extension "prototype(convert) decode(PER)" }
external function dec_cho5(in bitstring x) return Choice5 with { extension "prototype(convert) decode(PER)" }

// aligned
external function enc_cho1_a(in Choice1 x) return bitstring with { extension "prototype(convert) encode(PER:ALIGNED)" }
external function enc_cho2_a(in Choice2 x) return bitstring with { extension "prototype(convert) encode(PER:ALIGNED)" }
external function enc_cho3_a(in Choice3 x) return bitstring with { extension "prototype(convert) encode(PER:ALIGNED)" }
external function enc_cho4_a(in Choice4 x) return bitstring with { extension "prototype(convert) encode(PER:ALIGNED)" }
external function enc_cho5_a(in Choice5 x) return bitstring with { extension "prototype(convert) encode(PER:ALIGNED)" }

external function dec_cho1_a(in bitstring x) return Choice1 with { extension "prototype(convert) decode(PER:ALIGNED)" }
external function dec_cho2_a(in bitstring x) return Choice2 with { extension "prototype(convert) decode(PER:ALIGNED)" }
external function dec_cho3_a(in bitstring x) return Choice3 with { extension "prototype(convert) decode(PER:ALIGNED)" }
external function dec_cho4_a(in bitstring x) return Choice4 with { extension "prototype(convert) decode(PER:ALIGNED)" }
external function dec_cho5_a(in bitstring x) return Choice5 with { extension "prototype(convert) decode(PER:ALIGNED)" }

testcase tc_per_choice_encode_unaligned() runs on CT {
  // choice with 3 alternatives, not extendable
  var Choice1 x1 := { a1 := 10 };
  var bitstring enc_exp := '010000000100001010000000'B;
  var bitstring enc := enc_cho1(x1);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #1 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice1 x2 := { a2 := '1B'O };
  enc_exp := '000000000100011011000000'B;
  enc := enc_cho1(x2);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #2 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice1 x3 := { a3 := true };
  enc_exp := '10100000'B;
  enc := enc_cho1(x3);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #3 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with 3 alternatives and 4 extension additions
  var Choice2 x4 := { a1 := 10 };
  enc_exp := '001000000010000101000000'B;
  enc := enc_cho2(x4);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #4 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice2 x5 := { e1 := 15 };
  enc_exp := '10000000000000100000000100001111'B;
  enc := enc_cho2(x5);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #5 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice2 x6 := { e2 := 'DF'O };
  enc_exp := '10000001000000100000000111011111'B;
  enc := enc_cho2(x6);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #6 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice2 x7 := { e3 := f_DFE(20000, false) }; // 20000 = 16K + 3616
  // here the segmenting of large values is done twice: once when encoding the octetstring of 20000 octets,
  // and again, when the encoded octetstring is transformed into an open type encoding
  enc_exp := '100000101100000111000001'B & oct2bit(substr(x7.e3, 0, c_16K - 1)) &
    '1000111000100011'B & oct2bit(substr(x7.e3, c_16K - 1, 1)) & '1000111000100000'B &
    oct2bit(substr(x7.e3, c_16K, 3616));
  enc := enc_cho2(x7);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #7 failed.");
  }
  var Choice2 x8 := { e4 := 1 };
  enc_exp := '100000110000000100000000'B;
  enc := enc_cho2(x8);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #8 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with 1 alternative, not extendable
  var Choice3 x9 := { a := 2 };
  enc_exp := '0000000100000010'B;
  enc := enc_cho3(x9);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #9 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with 1 alternative, extendable
  var Choice4 x10 := { a := 2 };
  enc_exp := '000000001000000100000000'B;
  enc := enc_cho4(x10);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #10 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with embedded untagged choices
  var Choice5 x11 := { a1 := 20 };
  enc_exp := '100000000100010100000000'B;
  enc := enc_cho5(x11);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #11 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice5 x12 := { a2 := { be := '11011'B } };
  enc_exp := '0110000000000000100000010111011000000000'B;
  enc := enc_cho5(x12);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #12 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice5 x13 := { a3 := { c2 := { d1 := 'AB'O } } };
  enc_exp := '000100000001101010110000'B;
  enc := enc_cho5(x13);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #13 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  setverdict(pass);
}

testcase tc_per_choice_encode_aligned() runs on CT {
  // choice with 3 alternatives, not extendable
  var Choice1 x1 := { a1 := 10 };
  var bitstring enc_exp := '010000000000000100001010'B;
  var bitstring enc := enc_cho1_a(x1);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #1 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice1 x2 := { a2 := '1B'O };
  enc_exp := '000000000000000100011011'B;
  enc := enc_cho1_a(x2);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #2 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice1 x3 := { a3 := true };
  enc_exp := '10100000'B;
  enc := enc_cho1_a(x3);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #3 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with 3 alternatives and 4 extension additions
  var Choice2 x4 := { a1 := 10 };
  enc_exp := '001000000000000100001010'B;
  enc := enc_cho2_a(x4);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #4 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice2 x5 := { e1 := 15 };
  enc_exp := '10000000000000100000000100001111'B;
  enc := enc_cho2_a(x5);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #5 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice2 x6 := { e2 := 'DF'O };
  enc_exp := '10000001000000100000000111011111'B;
  enc := enc_cho2_a(x6);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #6 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice2 x7 := { e3 := f_DFE(20000, false) }; // 20000 = 16K + 3616
  // here the segmenting of large values is done twice: once when encoding the octetstring of 20000 octets,
  // and again, when the encoded octetstring is transformed into an open type encoding
  enc_exp := '100000101100000111000001'B & oct2bit(substr(x7.e3, 0, c_16K - 1)) &
    '1000111000100011'B & oct2bit(substr(x7.e3, c_16K - 1, 1)) & '1000111000100000'B &
    oct2bit(substr(x7.e3, c_16K, 3616));
  enc := enc_cho2_a(x7);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #7 failed.");
  }
  var Choice2 x8 := { e4 := 1 };
  enc_exp := '100000110000000100000000'B;
  enc := enc_cho2_a(x8);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #8 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with 1 alternative, not extendable
  var Choice3 x9 := { a := 2 };
  enc_exp := '0000000100000010'B;
  enc := enc_cho3_a(x9);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #9 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with 1 alternative, extendable
  var Choice4 x10 := { a := 2 };
  enc_exp := '000000000000000100000010'B;
  enc := enc_cho4_a(x10);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #10 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  
  // choice with embedded untagged choices
  var Choice5 x11 := { a1 := 20 };
  enc_exp := '100000000000000100010100'B;
  enc := enc_cho5_a(x11);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #11 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice5 x12 := { a2 := { be := '11011'B } };
  enc_exp := '0110000000000000000000100000010111011000'B;
  enc := enc_cho5_a(x12);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #12 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  var Choice5 x13 := { a3 := { c2 := { d1 := 'AB'O } } };
  enc_exp := '000100000000000110101011'B;
  enc := enc_cho5_a(x13);
  if (enc != enc_exp) {
    setverdict(fail, "Encoding choice #13 failed. Got: ", enc, ", expected: ", enc_exp);
  }
  setverdict(pass);
}

testcase tc_per_choice_decode_unaligned() runs on CT {
  // choice with 3 alternatives, not extendable
  var bitstring enc := '010000000100001010000000'B;
  var Choice1 x1 := dec_cho1(enc);
  var Choice1 x1_exp := { a1 := 10 };
  if (x1 != x1_exp) {
    setverdict(fail, "Decoding choice #1 failed. Got: ", x1, ", expected: ", x1_exp);
  }
  enc := '000000000100011011000000'B;
  var Choice1 x2 := dec_cho1(enc);
  var Choice1 x2_exp := { a2 := '1B'O };
  if (x2 != x2_exp) {
    setverdict(fail, "Decoding choice #2 failed. Got: ", x2, ", expected: ", x2_exp);
  }
  enc := '10100000'B;
  var Choice1 x3 := dec_cho1(enc);
  var Choice1 x3_exp := { a3 := true };
  if (x3 != x3_exp) {
    setverdict(fail, "Decoding choice #3 failed. Got: ", x3, ", expected: ", x3_exp);
  }
  
  // choice with 3 alternatives and 4 extension additions
  enc := '001000000010000101000000'B;
  var Choice2 x4 := dec_cho2(enc);
  var Choice2 x4_exp := { a1 := 10 };
  if (x4 != x4_exp) {
    setverdict(fail, "Decoding choice #4 failed. Got: ", x4, ", expected: ", x4_exp);
  }
  enc := '10000000000000100000000100001111'B;
  var Choice2 x5 := dec_cho2(enc);
  var Choice2 x5_exp := { e1 := 15 };
  if (x5 != x5_exp) {
    setverdict(fail, "Decoding choice #5 failed. Got: ", x5, ", expected: ", x5_exp);
  }
  enc := '10000001000000100000000111011111'B;
  var Choice2 x6 := dec_cho2(enc);
  var Choice2 x6_exp := { e2 := 'DF'O };
  if (x6 != x6_exp) {
    setverdict(fail, "Decoding choice #6 failed. Got: ", x6, ", expected: ", x6_exp);
  }
  var Choice2 x7_exp := { e3 := f_DFE(20000, false) }; // 20000 = 16K + 3616
  // here the segmenting of large values is done twice: once when encoding the octetstring of 20000 octets,
  // and again, when the encoded octetstring is transformed into an open type encoding
  enc := '100000101100000111000001'B & oct2bit(substr(x7_exp.e3, 0, c_16K - 1)) &
    '1000111000100011'B & oct2bit(substr(x7_exp.e3, c_16K - 1, 1)) & '1000111000100000'B &
    oct2bit(substr(x7_exp.e3, c_16K, 3616));
  var Choice2 x7 := dec_cho2(enc);
  if (x7 != x7_exp) {
    setverdict(fail, "Decoding choice #7 failed.");
  }
  enc := '100000110000000100000000'B;
  var Choice2 x8 := dec_cho2(enc);
  var Choice2 x8_exp := { e4 := 1 };
  if (x8 != x8_exp) {
    setverdict(fail, "Decoding choice #8 failed. Got: ", x8, ", expected: ", x8_exp);
  }
  
  // choice with 1 alternative, not extendable
  enc := '0000000100000010'B;
  var Choice3 x9 := dec_cho3(enc);
  var Choice3 x9_exp := { a := 2 };
  if (x9 != x9_exp) {
    setverdict(fail, "Decoding choice #9 failed. Got: ", x9, ", expected: ", x9_exp);
  }
  
  // choice with 1 alternative, extendable
  enc := '000000001000000100000000'B;
  var Choice4 x10 := dec_cho4(enc);
  var Choice4 x10_exp := { a := 2 };
  if (x10 != x10_exp) {
    setverdict(fail, "Decoding choice #10 failed. Got: ", x10, ", expected: ", x10_exp);
  }
  
  // choice with embedded untagged choices
  enc := '100000000100010100000000'B;
  var Choice5 x11 := dec_cho5(enc);
  var Choice5 x11_exp := { a1 := 20 };
  if (x11 != x11_exp) {
    setverdict(fail, "Decoding choice #11 failed. Got: ", x11, ", expected: ", x11_exp);
  }
  enc := '0110000000000000100000010111011000000000'B;
  var Choice5 x12 := dec_cho5(enc);
  var Choice5 x12_exp := { a2 := { be := '11011'B } };
  if (x12 != x12_exp) {
    setverdict(fail, "Decoding choice #12 failed. Got: ", x12, ", expected: ", x12_exp);
  }
  enc := '000100000001101010110000'B;
  var Choice5 x13 := dec_cho5(enc);
  var Choice5 x13_exp := { a3 := { c2 := { d1 := 'AB'O } } };
  if (x13 != x13_exp) {
    setverdict(fail, "Decoding choice #13 failed. Got: ", x13, ", expected: ", x13_exp);
  }
  
  // test with an unknown extension addition
  enc := '100010110000000100000000'B;
  var Choice2 x14 := dec_cho2(enc);
  if (isbound(x14)) {
    setverdict(fail, "Decoding choice #14 failed. Got: ", x14, ", expected unbound value.");
  }
  setverdict(pass);
}

testcase tc_per_choice_decode_aligned() runs on CT {
  // choice with 3 alternatives, not extendable
  var bitstring enc := '010000000000000100001010'B;
  var Choice1 x1 := dec_cho1_a(enc);
  var Choice1 x1_exp := { a1 := 10 };
  if (x1 != x1_exp) {
    setverdict(fail, "Decoding choice #1 failed. Got: ", x1, ", expected: ", x1_exp);
  }
  enc := '000000000000000100011011'B;
  var Choice1 x2 := dec_cho1_a(enc);
  var Choice1 x2_exp := { a2 := '1B'O };
  if (x2 != x2_exp) {
    setverdict(fail, "Decoding choice #2 failed. Got: ", x2, ", expected: ", x2_exp);
  }
  enc := '10100000'B;
  var Choice1 x3 := dec_cho1_a(enc);
  var Choice1 x3_exp := { a3 := true };
  if (x3 != x3_exp) {
    setverdict(fail, "Decoding choice #3 failed. Got: ", x3, ", expected: ", x3_exp);
  }
  
  // choice with 3 alternatives and 4 extension additions
  enc := '001000000000000100001010'B;
  var Choice2 x4 := dec_cho2_a(enc);
  var Choice2 x4_exp := { a1 := 10 };
  if (x4 != x4_exp) {
    setverdict(fail, "Decoding choice #4 failed. Got: ", x4, ", expected: ", x4_exp);
  }
  enc := '10000000000000100000000100001111'B;
  var Choice2 x5 := dec_cho2_a(enc);
  var Choice2 x5_exp := { e1 := 15 };
  if (x5 != x5_exp) {
    setverdict(fail, "Decoding choice #5 failed. Got: ", x5, ", expected: ", x5_exp);
  }
  enc := '10000001000000100000000111011111'B;
  var Choice2 x6 := dec_cho2_a(enc);
  var Choice2 x6_exp := { e2 := 'DF'O };
  if (x6 != x6_exp) {
    setverdict(fail, "Decoding choice #6 failed. Got: ", x6, ", expected: ", x6_exp);
  }
  var Choice2 x7_exp := { e3 := f_DFE(20000, false) }; // 20000 = 16K + 3616
  // here the segmenting of large values is done twice: once when encoding the octetstring of 20000 octets,
  // and again, when the encoded octetstring is transformed into an open type encoding
  enc := '100000101100000111000001'B & oct2bit(substr(x7_exp.e3, 0, c_16K - 1)) &
    '1000111000100011'B & oct2bit(substr(x7_exp.e3, c_16K - 1, 1)) & '1000111000100000'B &
    oct2bit(substr(x7_exp.e3, c_16K, 3616));
  var Choice2 x7 := dec_cho2_a(enc);
  if (x7 != x7_exp) {
    setverdict(fail, "Decoding choice #7 failed.");
  }
  enc := '100000110000000100000000'B;
  var Choice2 x8 := dec_cho2_a(enc);
  var Choice2 x8_exp := { e4 := 1 };
  if (x8 != x8_exp) {
    setverdict(fail, "Decoding choice #8 failed. Got: ", x8, ", expected: ", x8_exp);
  }
  
  // choice with 1 alternative, not extendable
  enc := '0000000100000010'B;
  var Choice3 x9 := dec_cho3_a(enc);
  var Choice3 x9_exp := { a := 2 };
  if (x9 != x9_exp) {
    setverdict(fail, "Decoding choice #9 failed. Got: ", x9, ", expected: ", x9_exp);
  }
  
  // choice with 1 alternative, extendable
  enc := '000000000000000100000010'B;
  var Choice4 x10 := dec_cho4_a(enc);
  var Choice4 x10_exp := { a := 2 };
  if (x10 != x10_exp) {
    setverdict(fail, "Decoding choice #10 failed. Got: ", x10, ", expected: ", x10_exp);
  }
  
  // choice with embedded untagged choices
  enc := '100000000000000100010100'B;
  var Choice5 x11 := dec_cho5_a(enc);
  var Choice5 x11_exp := { a1 := 20 };
  if (x11 != x11_exp) {
    setverdict(fail, "Decoding choice #11 failed. Got: ", x11, ", expected: ", x11_exp);
  }
  enc := '0110000000000000000000100000010111011000'B;
  var Choice5 x12 := dec_cho5_a(enc);
  var Choice5 x12_exp := { a2 := { be := '11011'B } };
  if (x12 != x12_exp) {
    setverdict(fail, "Decoding choice #12 failed. Got: ", x12, ", expected: ", x12_exp);
  }
  enc := '000100000000000110101011'B;
  var Choice5 x13 := dec_cho5_a(enc);
  var Choice5 x13_exp := { a3 := { c2 := { d1 := 'AB'O } } };
  if (x13 != x13_exp) {
    setverdict(fail, "Decoding choice #13 failed. Got: ", x13, ", expected: ", x13_exp);
  }
  
  // test with an unknown extension addition
  enc := '100010110000000100000000'B;
  var Choice2 x14 := dec_cho2_a(enc);
  if (isbound(x14)) {
    setverdict(fail, "Decoding choice #14 failed. Got: ", x14, ", expected unbound value.");
  }
  setverdict(pass);
}

testcase tc_per_choice_negtest() runs on CT {
  var Choice2 x;
  var bitstring buf;
  @try {
    buf := enc_cho2(x);
    setverdict(fail, "Choice #1 encoded successfully.");
  }
  @catch (msg) {
    if (not match(msg, pattern "*Encoding an unbound CHOICE value.")) {
      setverdict(fail, "Invalid error message received (#1): ", msg);
    }
  }
  buf := ''B;
  @try {
    x := dec_cho2(buf);
    setverdict(fail, "Choice #2 decoded successfully.");
  }
  @catch (msg) {
    if (not match(msg, pattern "*Buffer overflow while PER-decoding. Requested bits: 1, remaining bits in buffer: 0")) {
      setverdict(fail, "Invalid error message received (#2): ", msg);
    }
  }
  buf := '01100000'B;
  @try {
    x := dec_cho2(buf);
    setverdict(fail, "Choice #3 decoded successfully.");
  }
  @catch (msg) {
    if (not match(msg, pattern "*Decoded invalid CHOICE selection \(3\).")) {
      setverdict(fail, "Invalid error message received (#3): ", msg);
    }
  }
  setverdict(pass);
}
  
control {
  execute(tc_per_choice_encode_unaligned());
  execute(tc_per_choice_encode_aligned());
  execute(tc_per_choice_decode_unaligned());
  execute(tc_per_choice_decode_aligned());
  execute(tc_per_choice_negtest());
}

}
