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

#include <cstring>
#include "A.hh"

verdicttype run_tests()
{
  try {
    A::init_module(); // this also initializes module B
    
    // PER encoding & decoding tests
    // -----------------------------
    
    // Int encoding (calling the 'encode' function directly)
    B::Int x1(17);
    ASN_Buffer ASN_buf;
    x1.encode(B::Int_descr_, ASN_buf, ASN_EncDec::CT_PER, 0);
    OCTETSTRING res;
    ASN_buf.get_string(res);
    CHARSTRING exp("'00E0'O");
    if (res.log() != exp) {
      fprintf(stderr, "Invalid Int encoding: %s\n", (const char*) res.log());
      return FAIL;
    }
    
    // Int encoding (with the generated encoder function)
    OCTETSTRING res2;
    B::Int_encoder(x1, res2, ASN_EncDec::CT_PER, 0);
    if (res2.log() != exp) {
      fprintf(stderr, "Invalid Int encoding #2: %s\n", (const char*) res2.log());
      return FAIL;
    }
    
    // Oct encoding
    const unsigned char os_0_octets[] = { 0xAB, 0xCD };
    B::Oct x2(2, os_0_octets);
    OCTETSTRING res3;
    B::Oct_encoder(x2, res3, ASN_EncDec::CT_PER, PER_ALIGNED);
    exp = "'02ABCD'O";
    if (res3.log() != exp) {
      fprintf(stderr, "Invalid Oct encoding: %s\n", (const char*) res3.log());
      return FAIL;
    }
    
    //// SetOf encoding
    A::SetOf x3;
    x3.set_size(3);
    const unsigned char x3_0_os[] = { 0xAB, 0xCD };
    x3[0] = OCTETSTRING(2, x3_0_os);
    const unsigned char x3_1_os[] = { 0x12, 0x34, 0x56 };
    x3[1] = OCTETSTRING(3, x3_1_os);
    const unsigned char x3_2_os[] = { 0xEF, 0x01 };
    x3[2] = OCTETSTRING(2, x3_2_os);
    OCTETSTRING res4;
    A::SetOf_encoder(x3, res4, ASN_EncDec::CT_PER, PER_ALIGNED | PER_CANONICAL);
    exp = "'0302ABCD02EF0103123456'O";
    if (res4.log() != exp) {
      fprintf(stderr, "Invalid SetOf encoding: %s\n", (const char*) res4.log());
      return FAIL;
    }
    
    // SetOf decoding
    A::SetOf x4;
    INTEGER ret_val = A::SetOf_decoder(res4, x4, ASN_EncDec::CT_PER, PER_ALIGNED_CANONICAL);
    exp = "{ 'ABCD'O, 'EF01'O, '123456'O }";
    CHARSTRING ret_exp("0");
    CHARSTRING buf_exp("''O");
    if (x4.log() != exp || ret_val.log() != ret_exp || res4.log() != buf_exp) {
      fprintf(stderr, "Invalid SetOf decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x4.log(), (const char*) ret_val.log(), (const char*) res4.log());
      return FAIL;
    }
    
    // Seq encoding
    A::Seq x5;
    const unsigned char x5_f1_os[] = { 0x12 };
    x5.f1() = OCTETSTRING(1, x5_f1_os);
    const unsigned char x5_f2_os[] = { 0x34 };
    x5.f2() = OCTETSTRING(1, x5_f2_os);
    const unsigned char x5_f3_os[] = { 0x56 };
    x5.f3() = OCTETSTRING(1, x5_f3_os);
    const unsigned char x5_e2_os[] = { 0xCD };
    x5.e2() = OCTETSTRING(1, x5_e2_os);
    x5.e3() = INTEGER(2);
    const unsigned char x5_f4_os[] = { 0x78 };
    x5.f4() = OCTETSTRING(1, x5_f4_os);
    OCTETSTRING res5;
    A::Seq_encoder(x5, res5, ASN_EncDec::CT_PER, PER_UNALIGNED);
    exp = "'E0224026802AC02F011204039A0200'O";
    if (res5.log() != exp) {
      fprintf(stderr, "Invalid Seq encoding: %s\n", (const char*) res5.log());
      return FAIL;
    }
    
    // Seq decoding
    A::Seq x6;
    INTEGER ret_val2 = A::Seq_decoder(res5, x6, ASN_EncDec::CT_PER, PER_BASIC);
    exp = "{ f1 := '12'O, f2 := '34'O, f3 := '56'O, e1 := <unbound>, e2 := 'CD'O, g1 := <unbound>, g2 := omit, g3 := <unbound>, g4 := ''O, e3 := 2, f4 := '78'O }";
    if (x6.log() != exp || ret_val2.log() != ret_exp || res5.log() != buf_exp) {
      fprintf(stderr, "Invalid Seq decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x6.log(), (const char*) ret_val2.log(), (const char*) res5.log());
      return FAIL;
    }
    
    // Enum encoding
    A::Enum x7(A::Enum::two);
    OCTETSTRING res6;
    A::Enum_encoder(x7, res6, ASN_EncDec::CT_PER, PER_ALIGNED);
    exp = "'40'O";
    if (res6.log() != exp) {
      fprintf(stderr, "Invalid Enum encoding: %s\n", (const char*) res6.log());
      return FAIL;
    }
    
    // Enum decoding
    A::Enum x8;
    INTEGER ret_val3 = A::Enum_decoder(res6, x8, ASN_EncDec::CT_PER, PER_ALIGNED);
    exp = "two (2)";
    if (x8.log() != exp || ret_val3.log() != ret_exp || res6.log() != buf_exp) {
      fprintf(stderr, "Invalid Enum decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x8.log(), (const char*) ret_val3.log(), (const char*) res6.log());
      return FAIL;
    }
    
    // Choice encoding
    A::Choice x9;
    x9.a3() = TRUE;
    OCTETSTRING res7;
    A::Choice_encoder(x9, res7, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "'50'O";
    if (res7.log() != exp) {
      fprintf(stderr, "Invalid Choice encoding: %s\n", (const char*) res7.log());
      return FAIL;
    }
    
    // Choice decoding
    A::Choice x10;
    INTEGER ret_val4 = A::Choice_decoder(res7, x10, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "{ a3 := true }";
    if (x10.log() != exp || ret_val4.log() != ret_exp || res7.log() != buf_exp) {
      fprintf(stderr, "Invalid Choice decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x10.log(), (const char*) ret_val4.log(), (const char*) res7.log());
      return FAIL;
    }
    
    // ErrorReturn encoding
    A::ErrorReturn x11;
    x11.errorCategory()() = "A";
    x11.errors()().set_size(2);
    x11.errors()()[0].errorCode() = 1;
    x11.errors()()[0].errorInfo().iNTEGER() = 21;
    x11.errors()()[1].errorCode() = 2;
    x11.errors()()[1].errorInfo().rEAL() = 12.3;
    OCTETSTRING res8;
    A::ErrorReturn_encoder(x11, res8, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "'D04002010102011501020908033132332E452D31'O";
    if (res8.log() != exp) {
      fprintf(stderr, "Invalid ErrorReturn encoding: %s\n", (const char*) res8.log());
      return FAIL;
    }
    
    // ErrorReturn decoding
    A::ErrorReturn x12;
    INTEGER ret_val5 = A::ErrorReturn_decoder(res8, x12, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "{ errorCategory := \"A\", errors := { { errorCode := 1, errorInfo := { iNTEGER := 21 } }, { errorCode := 2, errorInfo := { rEAL := 12.300000 } } } }";
    if (x12.log() != exp || ret_val5.log() != ret_exp || res8.log() != buf_exp) {
      fprintf(stderr, "Invalid ErrorReturn decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x12.log(), (const char*) ret_val5.log(), (const char*) res8.log());
      return FAIL;
    }
    
    // EmbPdv encoding
    A::EmbPdv x13;
    x13.identification().fixed() = ASN_NULL_VALUE;
    x13.data__value__descriptor() = OMIT_VALUE;
    const unsigned char x13_data_os[] = { 0x12, 0x34 };
    x13.data__value() = OCTETSTRING(2, x13_data_os);
    OCTETSTRING res9;
    A::EmbPdv_encoder(x13, res9, ASN_EncDec::CT_PER, PER_UNALIGNED_BASIC);
    exp = "'021234'O";
    if (res9.log() != exp) {
      fprintf(stderr, "Invalid EmbPdv encoding: %s\n", (const char*) res9.log());
      return FAIL;
    }
    
    // EmbPdv decoding
    A::EmbPdv x14;
    INTEGER ret_val6 = A::EmbPdv_decoder(res9, x14, ASN_EncDec::CT_PER, PER_UNALIGNED_BASIC);
    exp = "{ identification := { fixed := NULL }, data_value_descriptor := omit, data_value := '1234'O }";
    if (x14.log() != exp || ret_val6.log() != ret_exp || res9.log() != buf_exp) {
      fprintf(stderr, "Invalid EmbPdv decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x14.log(), (const char*) ret_val6.log(), (const char*) res9.log());
      return FAIL;
    }
    
    // CharStr encoding
    A::CharStr x15;
    x15.identification().syntaxes().abstract() = A::objid__val;
    x15.identification().syntaxes().transfer() = A::objid__val;
    x15.data__value__descriptor() = OMIT_VALUE;
    const unsigned char x15_data_os[] = { 0x12, 0x34 };
    x15.string__value() = OCTETSTRING(2, x15_data_os);
    OCTETSTRING res10;
    A::CharStr_encoder(x15, res10, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "'021234'O";
    if (res10.log() != exp) {
      fprintf(stderr, "Invalid CharStr encoding: %s\n", (const char*) res10.log());
      return FAIL;
    }
    
    // CharStr decoding
    A::CharStr x16;
    INTEGER ret_val7 = A::CharStr_decoder(res10, x16, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "{ identification := { syntaxes := { abstract := objid { 0 0 1 3 }, transfer := objid { 0 0 1 3 } } }, data_value_descriptor := omit, string_value := '1234'O }";
    if (x16.log() != exp || ret_val7.log() != ret_exp || res10.log() != buf_exp) {
      fprintf(stderr, "Invalid CharStr decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x16.log(), (const char*) ret_val7.log(), (const char*) res10.log());
      return FAIL;
    }
    
    // External encoding
    A::External x17;
    x17.identification().presentation__context__id() = 117;
    x17.data__value__descriptor() = OMIT_VALUE;
    const unsigned char x17_data_os[] = { 0x12, 0x34 };
    x17.data__value() = OCTETSTRING(2, x17_data_os);
    OCTETSTRING res11;
    A::External_encoder(x17, res11, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "'40017540021234'O";
    if (res11.log() != exp) {
      fprintf(stderr, "Invalid External encoding: %s\n", (const char*) res11.log());
      return FAIL;
    }
    
    // External decoding
    A::External x18;
    INTEGER ret_val8 = A::External_decoder(res11, x18, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "{ identification := { presentation_context_id := 117 }, data_value_descriptor := omit, data_value := '1234'O }";
    if (x18.log() != exp || ret_val8.log() != ret_exp || res11.log() != buf_exp) {
      fprintf(stderr, "Invalid External decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x18.log(), (const char*) ret_val8.log(), (const char*) res11.log());
      return FAIL;
    }
    
    // Any encoding
    const unsigned char x19_os[] = { 0x12, 0x34 };
    A::Any x19(2, x19_os);
    OCTETSTRING res12;
    A::Any_encoder(x19, res12, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "'021234'O";
    if (res12.log() != exp) {
      fprintf(stderr, "Invalid Any encoding: %s\n", (const char*) res12.log());
      return FAIL;
    }
    
    // Any decoding
    A::Any x20;
    INTEGER ret_val9 = A::Any_decoder(res12, x20, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "'1234'O";
    if (x20.log() != exp || ret_val9.log() != ret_exp || res12.log() != buf_exp) {
      fprintf(stderr, "Invalid Any decoding: %s (returned: %s, remaining bytes: %s)\n", (const char*) x20.log(), (const char*) ret_val9.log(), (const char*) res12.log());
      return FAIL;
    }
    
    // Seq decoding, imitating a sliding decoder
    A::Seq y;
    OCTETSTRING buf1("E001120134015601780850030001"); // only a partial encoding
    TTCN_EncDec::set_error_behavior(TTCN_EncDec::ET_ALL, TTCN_EncDec::EB_WARNING);
    INTEGER ret_val_sliding = A::Seq_decoder(buf1, y, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    if (ret_val_sliding != INTEGER(2)) {
      fprintf(stderr, "Sliding PER decoder #1 failed. Return value: %d", (int) ret_val_sliding);
      return FAIL;
    }
    OCTETSTRING buf2("AB0100123456"); // the rest of the encoding, plus an extra '123456'O
    buf1 = buf1 + buf2; // octetstring concatenation
    ret_val_sliding = A::Seq_decoder(buf1, y, ASN_EncDec::CT_PER, PER_ALIGNED_BASIC);
    exp = "{ f1 := '12'O, f2 := '34'O, f3 := '56'O, e1 := <unbound>, e2 := <unbound>, g1 := 'AB'O, g2 := omit, g3 := <unbound>, g4 := ''O, e3 := 2, f4 := '78'O }";
    buf_exp = "'123456'O";
    if (y.log() != exp || ret_val_sliding != INTEGER(0) || buf1.log() != buf_exp) {
      fprintf(stderr, "Sliding PER decoder #2 failed. Decoded value: %s (returned: %s, remaining bytes: %s)\n", (const char*) y.log(), (const char*) ret_val_sliding.log(), (const char*) buf1.log());
      return FAIL;
    }
    TTCN_EncDec::set_error_behavior(TTCN_EncDec::ET_ALL, TTCN_EncDec::EB_DEFAULT);
    
    // API tests
    // ---------
    
    // initializing a BIT STRING from a null-terminated string
    BITSTRING bs1("");
    exp = "''B"; 
    if (bs1.log() != exp) {
      fprintf(stderr, "Invalid BIT STRING inilialization #1: %s\n", (const char*) bs1.log());
      return FAIL;
    }
    BITSTRING bs2("11010");
    exp = "'11010'B"; 
    if (bs2.log() != exp) {
      fprintf(stderr, "Invalid BIT STRING inilialization #2: %s\n", (const char*) bs2.log());
      return FAIL;
    }
    BITSTRING bs3("0000111101101001001");
    exp = "'0000111101101001001'B"; 
    if (bs3.log() != exp) {
      fprintf(stderr, "Invalid BIT STRING inilialization #3: %s\n", (const char*) bs3.log());
      return FAIL;
    }
    
    // initializing an OCTET STRING from a null-terminated string
    OCTETSTRING os1("");
    exp = "''O"; 
    if (os1.log() != exp) {
      fprintf(stderr, "Invalid OCTET STRING inilialization #1: %s\n", (const char*) os1.log());
      return FAIL;
    }
    OCTETSTRING os2("1234ABCD");
    exp = "'1234ABCD'O"; 
    if (os2.log() != exp) {
      fprintf(stderr, "Invalid OCTET STRING inilialization #2: %s\n", (const char*) os2.log());
      return FAIL;
    }
    OCTETSTRING os3("0f1e2d3c4b5a");
    exp = "'0F1E2D3C4B5A'O"; 
    if (os3.log() != exp) {
      fprintf(stderr, "Invalid OCTET STRING inilialization #3: %s\n", (const char*) os3.log());
      return FAIL;
    }
    
    // setting unbound optional fields to omit, and default fields to their default value
    A::Seq seq_io;
    seq_io.f1() = OCTETSTRING("12");
    seq_io.f2() = OCTETSTRING("34");
    seq_io.set_implicit_omit();
    exp = "{ f1 := '12'O, f2 := '34'O, f3 := omit, e1 := omit, e2 := omit, g1 := omit, g2 := omit, g3 := omit, g4 := ''O, e3 := omit, f4 := ''O }";
    if (seq_io.log() != exp) {
      fprintf(stderr, "Setting implicit omit failed: %s\n", (const char*) seq_io.log());
      return FAIL;
    }
  }
  catch (ASN_Error e) {
    fprintf(stderr, "%s\n", e.get_message());
    return ERROR;
  }
  
  // Negative tests
  // --------------
  
  try {
    // division by 0
    INTEGER x(1);
    INTEGER y(0);
    INTEGER z(x / y);
    return FAIL;
  }
  catch (ASN_Error e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: Integer division by zero.") != 0) {
      fprintf(stderr, "Invalid message #1: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  try {
    // decoding error
    ASN_Buffer buf;
    BITSTRING x;
    x.decode(BITSTRING_descr_, buf, ASN_EncDec::CT_PER, 0);
    return FAIL;
  }
  catch (const ASN_Error& e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: While PER-decoding type 'BIT STRING': Buffer overflow while PER-decoding. Requested bits: 1, remaining bits in buffer: 0") != 0) {
      fprintf(stderr, "Invalid message #2: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  try {
    // initializing a BIT STRING with a null pointer
    const char* s = NULL;
    BITSTRING x(s);
    return FAIL;
  }
  catch (ASN_Error e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: Cannot convert NULL pointer to BIT STRING.") != 0) {
      fprintf(stderr, "Invalid message #3: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  try {
    // initializing a BIT STRING with a string containing an invalid character
    BITSTRING x("01a");
    return FAIL;
  }
  catch (ASN_Error e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: Cannot convert `01a' to BIT STRING.") != 0) {
      fprintf(stderr, "Invalid message #4: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  try {
    // initializing an OCTET STRING with a null pointer
    const char* s = NULL;
    OCTETSTRING x(s);
    return FAIL;
  }
  catch (ASN_Error e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: Cannot convert NULL pointer to OCTET STRING.") != 0) {
      fprintf(stderr, "Invalid message #5: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  try {
    // initializing an OCTET STRING with a string containing an odd number of characters
    OCTETSTRING x("12345");
    return FAIL;
  }
  catch (ASN_Error e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: Cannot convert a string of length 5 to OCTET STRING.") != 0) {
      fprintf(stderr, "Invalid message #6: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  try {
    // initializing an OCTET STRING with a string containing an invalid character
    OCTETSTRING x("abcx");
    return FAIL;
  }
  catch (ASN_Error e) {
    if (strcmp(e.get_message(), "ASN.1 runtime error: Cannot convert `abcx' to OCTET STRING.") != 0) {
      fprintf(stderr, "Invalid message #7: %s\n", e.get_message());
      return FAIL;
    }
  }
  
  return PASS;
}

int main() {
  verdicttype v = run_tests();
  
  // Print fake verdict statistics, so the scripts treat this as a TTCN-3 test
  fprintf(stderr, "Verdict statistics: 0 none (0.00 %%), %d pass (%d.00 %%), 0 inconc (0.00 %%), %d fail (%d.00 %%), %d error (%d.00 %%).\n",
    v == PASS  ? 1 : 0, v == PASS  ? 100 : 0,
    v == FAIL  ? 1 : 0, v == FAIL  ? 100 : 0,
    v == ERROR ? 1 : 0, v == ERROR ? 100 : 0);
  return 0;
}
