#include "client.h"
#include "ACSE-HiPath3000.h"


int Client::connect(OSRTSocket& cstaSocket)
{
   OSOCTET	msgbuf[MAXMSGLEN];
   int len;

   if (trace) {
      printf ("ACSE association \n" );
   }

   /* Initialize the encoding context */
   ASN1BEREncodeBuffer encodeBuffer (msgbuf, sizeof(msgbuf));

   /* Encode operation argument */
   if ((len = encodeACSEConnectionRequest (encodeBuffer)) < 0) {
      return len;
   }

   /* Write the encoded message out to the socket */
   sendMessage (cstaSocket, encodeBuffer, len);
   if (trace) {
      printf ("ACSE associate msg sent, len = %d \n", len);
   }

   int stat = receiveACSEConnectResponse (cstaSocket);	
   if (stat != 0) {
      printf ("recvACSEConnectResponse failed, %d \n", stat );
      return stat;
   }

   if(mMessageType == Client::siemens_cap ||
      mMessageType == Client::siemens_hipath_3000 ||
      mMessageType == Client::panasonic) {
      systemStatus (cstaSocket);
   }

   return 0;
}

int Client::encodeACSEConnectionRequest (ASN1BEREncodeBuffer& encodeBuffer)
{
   if(mMessageType == Client::siemens_cap) {
      return encodeSiemensCapACSEConnectionRequest (encodeBuffer);     
   }
   else if (mMessageType == Client::siemens_hipath_3000 ) {
      return encodeSiemensHiPathACSEConnectionRequest(encodeBuffer);
   }
   else if (mMessageType == Client::panasonic) {
      return encodePanKxTdaACSEConnectionRequest(encodeBuffer);
   }
   else {
      return encodeGeneralACSEConnectionRequest (encodeBuffer);     
   }
}

int Client::encodePanKxTdaACSEConnectionRequest (
   ASN1BEREncodeBuffer& tMainEncodeBuffer)
{
   ASN1T_AARQ_apdu tAARQ;

   ASN1T_AARQ_apdu_protocol_version tProtocolVersion;
   tProtocolVersion.numbits = 1;
   tProtocolVersion.data[0] = 0x80;
   tAARQ.protocol_version = tProtocolVersion;

   ASN1OBJID tObjectId1 = {5, {1, 3, 12, 0, 218} };
   tAARQ.aSO_context_name = tObjectId1;

   // This PBX wants the user-information field of the AARQ object populated
   // with an ACSEUserInformationForCSTA object.
   ASN1T_ACSEUserInformationForCSTA tUserInformation;
   ASN1T_NewACSEUserInformationForCSTA tNewUserInformation;

   // We need to populate a CSTAVersion object for the
   // NewACSEUserInformationForCSTA object.
   ASN1T_CSTAVersion tCSTAVersion;
   tCSTAVersion.data[0] = 0 | (1 << (ASN1C_CSTAVersion::versionFive - 1));
   tCSTAVersion.data[1] = 0;
   tCSTAVersion.numbits = 16;

   // Set the CSTAVersion object into the NewACSEUserInformationForCSTA
   // object.
   tNewUserInformation.cSTAVersion = tCSTAVersion;

   // Now set the NewACSEUserInformationForCSTA Object into the
   // ACSEUserInformationForCSTA object.
   tUserInformation.t = T_ACSEUserInformationForCSTA_newDefinition;
   tUserInformation.u.newDefinition = &tNewUserInformation;

   // Try to encode the ACSEUserInformationForCSTA object.  We will
   // need to wrap this encoded object up into an AssociationData
   // object in order to place it into the AARQ object.
   ASN1BEREncodeBuffer tUserInfoEncodeBuffer;
   ASN1C_ACSEUserInformationForCSTA tUserInformation_CC(tUserInformation);
   int iLen = tUserInformation_CC.EncodeTo(tUserInfoEncodeBuffer);
   if (iLen < 0) 
   {
      printf("\nEncode of ACSE Make Association failed.\n");
      tUserInfoEncodeBuffer.printErrorInfo();
      return iLen;
   }

   // Create an Asn1External instance.  We will place an object
   // identifier that we construct in this object plus the
   // already encoded ACSEUserInformationForCSTA object.
   ASN1T_External tAsn1External;
   tAsn1External.m.direct_referencePresent = 1;
   ASN1OBJID tObjectId2 = { 6, {1, 3, 12, 0, 285, 200 } };
   tAsn1External.direct_reference = tObjectId2;

   // Create an Asn1External_encoding object to hold the encoded
   // ACSEUserInformationForCSTA object.
   ASN1T_External_encoding tExternalEncoding;
   ASN1TOpenType tOpenType;
   tOpenType.data = tUserInfoEncodeBuffer.getMsgPtr();
   tOpenType.numocts = iLen;
   tExternalEncoding.t = T_External_encoding_single_ASN1_type;
   tExternalEncoding.u.single_ASN1_type = &tOpenType;

   // Connect the Asn1External_encoding object that holds the encoded
   // ACSEUserInformation object to the Asn1External object.
   tAsn1External.encoding = tExternalEncoding;

   // The user-information field of the AARQ object wants an AssociationData
   // instance, so let's create one of those and connect our Asn1External
   // object into it.
   ASN1T_Association_data tAssociationData;
   ASN1C_Association_data tAssociationData_CC(tAssociationData);
   tAssociationData_CC.Append(&tAsn1External);

   // Finally we can connect the AssociationData instance to our AARQ.
   tAARQ.m.user_informationPresent = 1;
   tAARQ.user_information = tAssociationData;

   // Now we can encode the final message.
   ASN1T_ACSE_apdu tEncodeACSE;
   tEncodeACSE.t = T_ACSE_apdu_aarq;
   tEncodeACSE.u.aarq = &tAARQ;
   ASN1C_ACSE_apdu tEncodeACSE_CC(tMainEncodeBuffer, tEncodeACSE);
   iLen = tEncodeACSE_CC.Encode();
   if (iLen < 0)
   {
      printf("\nEncode of ACSE Make Association failed.\n");
      tMainEncodeBuffer.printErrorInfo();
   }

   return iLen;
}

int Client::encodeGeneralACSEConnectionRequest (
   ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_AARQ_apdu aarq_apdu;
   int len;
  
   if (trace) {
	   printf ("encode ACSE Connection Request\n" );
   }

   /*
   set CSTA 3 OID value, for CSTA 3 communication 
   { iso( 1) identified-organization( 3) icd-ecma( 12)
	standard( 0) csta3( 285) }
   */
   ASN1OBJID csta3oid = { 5, {1, 3, 12, 0, 285 } };
   aarq_apdu.aSO_context_name = csta3oid;

   /*
   For simplicity, following data is ignored

   It is necessary for the requesting and responding systems to specify
   the CSTA services that they support. As with the protocol version 
   information, 
   this is also achieved by carrying additional information in the 
   AARQ_apdu.user-information field of the request and response PDUs. The
   application association requestor shall:
   - list the services required from the serving application;
   - list the services it can supply.
   */

   /* All other optional fields are ignored for simplicity */

   /* Encode */
   ASN1T_ACSE_apdu acse_apdu;
   acse_apdu.t = T_ACSE_apdu_aarq;
   acse_apdu.u.aarq = &aarq_apdu;

   ASN1C_ACSE_apdu pdu(encodeBuffer, acse_apdu);

   len = pdu.Encode();

   if (len < 0) {
      printf ("Encode of ACSE pdu failed.\n");
      encodeBuffer.printErrorInfo();
   }

   return len;
}

int Client::encodeSiemensCapACSEConnectionRequest (
   ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_AARQ_apdu aarq_apdu;
   int len;
  
   if (trace) {
	   printf ("encode ACSE Connection Request\n" );
   }

   /*
   set CSTA 2 OID value, for CSTA 3 communication 
   { iso( 1) identified-organization( 3) icd-ecma( 12)
	  standard( 0) csta2( 218) }
   */

   ASN1OBJID csta2oid = { 5, { 1, 3, 12, 0, 218} };
   aarq_apdu.aSO_context_name = csta2oid;

   aarq_apdu.m.calling_authentication_valuePresent = 1;
   aarq_apdu.calling_authentication_value.t = 
      T_Authentication_value_charstring;
   aarq_apdu.calling_authentication_value.u.charstring = 
      "CAP;passwd=123;native=true";
      //"CAP-A;login=120;passwd=120;native=true";

   /* Populate user information */
   ASN1T_NewACSEUserInformationForCSTA newacseuserinfo;
   newacseuserinfo.cSTAVersion.numbits = 8;
   newacseuserinfo.cSTAVersion.data[0] = 0x08; //verion 5 (CSTA III ASN.1)

   ASN1T_ACSEUserInformationForCSTA acseuserinfo;
   acseuserinfo.t = T_ACSEUserInformationForCSTA_newDefinition;
   acseuserinfo.u.newDefinition = &newacseuserinfo;
   
   ASN1BEREncodeBuffer userInfoEncodeBuffer;//using dynamic buffer of context
   ASN1C_ACSEUserInformationForCSTA opentypeC(userInfoEncodeBuffer, acseuserinfo);
   if((len = opentypeC.Encode()) < 0) {
      printf("Encode of ACSEUserInformationForCSTA failed\n");
      userInfoEncodeBuffer.printErrorInfo();
   }
   ASN1TOpenType single_ASN1_type;
   single_ASN1_type.numocts = len;
   single_ASN1_type.data = userInfoEncodeBuffer.getMsgPtr();

   ASN1T_External user_information_elem;
   user_information_elem.encoding.t = T_External_encoding_single_ASN1_type;
   user_information_elem.encoding.u.single_ASN1_type = &single_ASN1_type;

   aarq_apdu.m.user_informationPresent = 1;
   //add Associate-data to list aarq_apdu.user_information
   ASN1C_Association_data user_informationC(aarq_apdu.user_information);
   user_informationC.Append(&user_information_elem);

   /* All other optional fields are not required by Siemens CAP server */

   /* Encode */
   ASN1T_ACSE_apdu acse_apdu;
   acse_apdu.t = T_ACSE_apdu_aarq;
   acse_apdu.u.aarq = &aarq_apdu;

   ASN1C_ACSE_apdu pdu(encodeBuffer, acse_apdu);

   len = pdu.Encode();

   if (len < 0) {
      printf ("Encode of ACSE pdu failed.\n");
      encodeBuffer.printErrorInfo();
   }

   return len;
}

int Client::encodeSiemensHiPathACSEConnectionRequest (
   ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_AARQ_apdu aarq_apdu;
   int len;
  
   /* Populate aarq_apdu.aSO_context_name */
   /*
   set CSTA 2 OID value, for CSTA 3 communication 
   { iso( 1) identified-organization( 3) icd-ecma( 12)
	  standard( 0) csta2( 218) }
   */
   ASN1OBJID csta2oid = { 5, { 1, 3, 12, 0, 218} };
   aarq_apdu.aSO_context_name = csta2oid;


   /* Populate aarq_apdu.sender-acse-requirements*/
   aarq_apdu.m.sender_acse_requirementsPresent = 1;
   ASN1C_ACSE_requirements req(aarq_apdu.sender_acse_requirements);
   req.set(ASN1C_ACSE_requirements::authentication);

   /* Populate aarq_apdu.calling_authentication_value */
   //Siemens HiPath 3000 uses proprietary ASN.1 definition as follow, for
   //user authentication, using as per ASN.1 file
   ASN1T_External_authentication externalAuthentication;
   externalAuthentication.m.sender_authenticationPresent = 1;
   externalAuthentication.sender_authentication.
      authentication_name.numocts = 6;
   externalAuthentication.sender_authentication.
      authentication_name.data = (OSOCTET*)("AMHOST");
   externalAuthentication.sender_authentication.
      authentication_password.numocts = 5;
   externalAuthentication.sender_authentication.
      authentication_password.data = (OSOCTET*)("77777");

   OSCTXT ctxt;
   if (rtInitContext (&ctxt) != 0) return -1;
   xe_setp(&ctxt, 0, 0);
   if((len = asn1E_External_authentication (&ctxt, &externalAuthentication,
      ASN1IMPL)) < 0) {
      printf("Encode of External-authentication failed\n");
      rtxErrPrint(&ctxt);
   }

   ASN1TOpenType single_ASN1_type1;
   single_ASN1_type1.numocts = len;
   single_ASN1_type1.data = xe_getp(&ctxt);

   ASN1T_External calling_authentication_value_external;
   calling_authentication_value_external.encoding.t = 
      T_External_encoding_single_ASN1_type;
   calling_authentication_value_external.encoding.u.single_ASN1_type = 
      &single_ASN1_type1;

   aarq_apdu.m.calling_authentication_valuePresent = 1;
   aarq_apdu.calling_authentication_value.t = 
      T_Authentication_value_external;
   aarq_apdu.calling_authentication_value.u.external = 
      &calling_authentication_value_external;
   


   /* Populate aarq_apdu.user_information */
   ASN1T_NewACSEUserInformationForCSTA newacseuserinfo;
   newacseuserinfo.cSTAVersion.numbits = 8;
   newacseuserinfo.cSTAVersion.data[0] = 0x10; //version 4 (ISO 18052)

   ASN1T_ACSEUserInformationForCSTA acseuserinfo;
   acseuserinfo.t = T_ACSEUserInformationForCSTA_newDefinition;
   acseuserinfo.u.newDefinition = &newacseuserinfo;
   
   ASN1BEREncodeBuffer userInfoEncodeBuffer;//using dynamic buffer of context
   ASN1C_ACSEUserInformationForCSTA opentypeC(userInfoEncodeBuffer, acseuserinfo);
   if((len = opentypeC.Encode()) < 0) {
      printf("Encode of ACSEUserInformationForCSTA failed\n");
      userInfoEncodeBuffer.printErrorInfo();
   }
   ASN1TOpenType single_ASN1_type2;
   single_ASN1_type2.numocts = len;
   single_ASN1_type2.data = userInfoEncodeBuffer.getMsgPtr();

   ASN1T_External user_information_elem;
   user_information_elem.encoding.t = T_External_encoding_single_ASN1_type;
   user_information_elem.encoding.u.single_ASN1_type = &single_ASN1_type2;

   aarq_apdu.m.user_informationPresent = 1;
   //add Associate-data to list aarq_apdu.user_information
   ASN1C_Association_data user_informationC(aarq_apdu.user_information);
   user_informationC.Append(&user_information_elem);


   
   /* All other optional fields are not required by Siemens HiPath3000 */

   /* Encode */
   ASN1T_ACSE_apdu acse_apdu;
   acse_apdu.t = T_ACSE_apdu_aarq;
   acse_apdu.u.aarq = &aarq_apdu;

   ASN1C_ACSE_apdu pdu(encodeBuffer, acse_apdu);

   len = pdu.Encode();

   if (len < 0) {
      printf ("Encode of ACSE pdu failed.\n");
      encodeBuffer.printErrorInfo();
   }

   rtFreeContext(&ctxt);
   return len;
}

/* cleanup Handler */
int Client::release(OSRTSocket& cstaSocket)
{
   OSOCTET	msgbuf[MAXMSGLEN];
   int len;

   if (trace) {
      printf ("ACSE release \n" );
   }

   /* Initialize the encoding context */
   ASN1BEREncodeBuffer encodeBuffer (msgbuf, sizeof(msgbuf));

   /* Encode operation argument */
   if ((len = encodeACSEReleaseRequest (encodeBuffer)) < 0) {
      return len;
   }

   /* Write the encoded message out to the socket */
   sendMessage (cstaSocket, encodeBuffer, len);
   if (trace) {
      printf ("ACSE release msg sent, len = %d \n", len);
   }

   int stat = receiveACSEMessage (cstaSocket);	
   if (stat != 0) {
      printf ("recvMessage failed, %d \n", stat );
      return stat;
   }
   return 0;
}

/* Encode ACSE connection Release Request 

The ACSE_apdu.rlrq(request) is transmitted and the CSTA Application 
waits for an ACSE_apdu.rlre(response). If a response is 
received then the connection is released for CSTA Applications.

*/
int Client::encodeACSEReleaseRequest (ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_RLRQ_apdu rlrq_apdu;
   int len;

   if (trace) {
	   printf ("encode ACSE Release Request\n" );
   }

   rlrq_apdu.m.reasonPresent = 1;
   rlrq_apdu.reason = Release_request_reason::normal;

   /* All other optional fields are ignored for simplicity. */

   /* Encode */
   ASN1T_ACSE_apdu acse_apdu;
   acse_apdu.t = T_ACSE_apdu_rlrq;
   acse_apdu.u.rlrq = &rlrq_apdu;

   ASN1C_ACSE_apdu pdu(encodeBuffer, acse_apdu);

   len = pdu.Encode();

   if (len < 0) {
      printf ("Encode ACSE PDU failed.\n");
      encodeBuffer.printErrorInfo();
   }

   return len;
}

/* Encode ACSE connection abort, this is not a Request */

int Client::encodeACSEAbort (ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_ABRT_apdu abrt_apdu;
   int len;

   if (trace) {
	   printf ("encode ACSE Abort\n" );
   }

   abrt_apdu.abort_source = ABRT_source::acse_service_user;

   /* All other optional fields are ignored for simplicity. */

   /* Encode */
   ASN1T_ACSE_apdu acse_apdu;
   acse_apdu.t = T_ACSE_apdu_abrt;
   acse_apdu.u.abrt = &abrt_apdu;

   ASN1C_ACSE_apdu pdu(encodeBuffer, acse_apdu);

   len = pdu.Encode();

   if (len < 0) {
      printf ("Encode ACSE PDU failed.\n");
      encodeBuffer.printErrorInfo();
   }

   return len;
}

int Client::receiveACSEMessage(OSRTSocket& cstaSocket)
{
   ASN1BERDecodeBuffer decodeBuffer;
   if(receiveMessage(cstaSocket, decodeBuffer) < 0) {
      printf("error: failed to receive message\n");
      return -1;
   }

   ASN1T_ACSE_apdu pdu;
   ASN1C_ACSE_apdu pduC (decodeBuffer, pdu);

   int stat = pduC.Decode ();

   if (stat == 0) {
      if (trace) {
         printf ("Decode of ACSE PDU was successful\n");
         pduC.Print ("pdu");
      }
      if(pdu.t == T_ACSE_apdu_aare) {
         switch(pdu.u.aare->result) {
            case Associate_result::accepted:
               printf ("ACSE connection established\n");
               break;

            case Associate_result::rejected_permanent:
            case Associate_result::rejected_transient:
               printf ("error: ACSE connection rejected\n");
               printf ("Please try again or check the login/passwd in ACSE request\n");
               break;

            default:
               break;
         }
      }
   }
   else {
      printf ("Decode of ACSE PDU failed\n");
      decodeBuffer.printErrorInfo ();
      return -1;
   }

   /* Process the ACSE operation */
   //processACSEPDU (pdu, trace);
   printf ("***\n");

   return 0;
}

int Client::receiveACSEConnectResponse(OSRTSocket& cstaSocket)
{
   ASN1BERDecodeBuffer decodeBuffer;
   if(receiveMessage(cstaSocket, decodeBuffer) < 0) {
      printf("error: failed to receive message\n");
      return -1;
   }

   ASN1T_ACSE_apdu pdu;
   ASN1C_ACSE_apdu pduC (decodeBuffer, pdu);

   int stat = pduC.Decode ();

   if (stat == 0) {
      if (trace) {
         printf ("Decode of ACSE PDU was successful\n");
         pduC.Print ("pdu");
      }

      if (pdu.t == T_ACSE_apdu_aare) {
         switch(pdu.u.aare->result) {
            case Associate_result::accepted:
               printf ("ACSE connection established successfully\n");
               break;

            case Associate_result::rejected_permanent:
            case Associate_result::rejected_transient:
               printf ("error: ACSE connection rejected\n");
               printf ("       Please try again or check the login/passwd in ACSE request\n");
               return -1;
               break;

            default:
               break;
         }
      }
      else if (pdu.t == T_ACSE_apdu_abrt) {
         printf ("ACSE connection aborted by device.\n");
         asn1Print_ABRT_apdu ("ABRT_apdu", pdu.u.abrt);
         return -1;
      }
   }
   else {
      printf ("Decode of ACSE PDU failed\n");
      decodeBuffer.printErrorInfo ();
      return -1;
   }

   /* Process the ACSE operation */
   //processACSEPDU (pdu, trace);
   printf ("***\n");

   return 0;
}

