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

#define SystemStatus_CODE 211
#define MAXMSGLEN 1024 //maximum length of the message received by client from PBX
extern void processCSTAMessage(ASN1T_CSTA_ROSE_PDU& pdu, OSBOOL trace);


int SiemensClient::encodeACSEConnectionRequest (
   ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_AARQ_apdu aarq_apdu;
   int len;
  
   /* Populate aarq_apdu.aSO_context_name */
   /*
   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, 218} };
   aarq_apdu.aSO_context_name = csta3oid;



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

/* 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 SiemensClient::encodeACSEReleaseRequest (ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_RLRQ_apdu rlrq_apdu;
   int len;

   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;
}

int SiemensClient::encodeCSTASystemStatusRes (ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_SystemStatusRes systemStatusRes;
   int len;

   systemStatusRes.t = T_SystemStatusRes_noData;

   ASN1C_SystemStatusRes pdu (encodeBuffer, systemStatusRes);

   len = pdu.Encode();

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

   /* Add ROSE header */

   return encodeROSEResponseHeader (encodeBuffer, len, SystemStatus_CODE);
}

/* Encode ROSE header */

int SiemensClient::encodeROSEResponseHeader (ASN1BEREncodeBuffer& encodeBuffer, 
                                     int msglen, int opcode)
{
   ASN1T_CSTA_ROSE_PDU pdu;
   ASN1C_CSTA_ROSE_PDU pduC (encodeBuffer, pdu);
   ASN1T_ReturnResult returnResult;
   int len;

   /* Populate header structure */

   returnResult.m.resultPresent = 1;
   returnResult.invokeId.t = T_InvokeId_present;
   returnResult.invokeId.u.present = 1;
   returnResult.result.opcode.t = T_Code_local;
   returnResult.result.opcode.u.local = opcode;

   /* This is where we get the previously encoded message component */
   returnResult.result.result.numocts = msglen;
   returnResult.result.result.data = (OSOCTET*) encodeBuffer.getMsgPtr();

   pdu.t = T_CSTA_ROSE_PDU_returnResult;
   pdu.u.returnResult = &returnResult;

   /* Encode */

   len = pduC.Encode();

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

   return len;
}

int SiemensClient::recvMessage (OSRTSocket& cstaSocket, 
   OSOCTET* msgbuf, int msgbufsize)
{
   //OSOCTET msgbuf[MAXMSGLEN];
   OSOCTET lenbytes[2];
   int len;

   /* First byte should be system_application. */

   printf ("receiving first response byte from PBX..\n");
   OSOCTET b;
   len = cstaSocket.recv (&b, 1);
   if (len == 1) {
      if (b != 0x26) 
         printf ("WARNING: unexpected first byte = %02x\n", b);
   }
   else {
      printf ("recv failed, len = 0\n");
      return -1;
   }

   /* Receive length bytes */

   printf ("receiving length bytes from PBX..\n");
   lenbytes[0] = lenbytes[1] = 0;
   len = cstaSocket.blockingRead (lenbytes, 2);
   if (len != 2) {
      printf ("recv failed, len = %d\n", len);
      return -1;
   }

   printf ("message length = bytes: %02x %02x ", lenbytes[0], lenbytes[1]);
   lenbytes[0] &= 0x7F;   // mask off continuation bit
   int msglen = (lenbytes[0] * 256) + lenbytes[1];
   printf ("value: %d\n", msglen);

   /* Receive ASN.1 BER-encoded data */

   if (msglen > msgbufsize) {
      printf ("message larger than allocated buffer size\n");
      return -1;
   }
   len = cstaSocket.blockingRead (msgbuf, msglen);
   if (len != msglen) {
      printf ("blocking read failed: len = %d\n", len);
      return -1;
   }

   if(msglen > 0) {
      printf("message received:\n");
      rtxHexDump(msgbuf, msglen);
   }

   return 0;
}

int SiemensClient::sendMessage 
   (OSRTSocket& cstaSocket, ASN1BEREncodeBuffer& encodeBuffer, size_t len)
{
   /* Add special 2-byte length wrapper to encoded data (note: this  */
   /* is specific to the Siemens PBX)..                              */

   printf("adding header & length bytes..\n");
   OSCTXT* pctxt = encodeBuffer.getCtxtPtr();
   OSOCTET lenbytes[2];
   lenbytes[0] = len / 256;
   lenbytes[1] = len % 256;
   lenbytes[0] |= 0x80;
   xe_memcpy (pctxt, lenbytes, 2);

   /* Add system_application code (CTI3_APPLICATION = 0x26) */
   OSOCTET applcode = 0x26;
   xe_memcpy (pctxt, &applcode, 1);

   len += 3;   

   const OSOCTET* msgptr = encodeBuffer.getMsgPtr();

   /* Send message to device socket */

   printf ("sending message to Siemens HiPath 3000 PBX..\n");
   cstaSocket.send (msgptr, len);
   printf ("message sent:\n");
   rtxHexDump (msgptr, len);
      
   return 0;
}


int SiemensClient::sendACSEConnectionRequest (OSRTSocket& cstaSocket)
{
   int len, stat;

   /* Initialize the encoding context */

   ASN1BEREncodeBuffer encodeBuffer;

   /* Create ACSE AARQ PDU */

   printf ("send ACSE AARQ request..\n");

   if ((len = encodeACSEConnectionRequest (encodeBuffer)) < 0) return len;
   
   /* Send message to PBX */

   stat = sendMessage (cstaSocket, encodeBuffer, len);
   if (stat != 0) return stat;

   return stat;
}

int SiemensClient::recvACSEMessage (OSRTSocket& cstaSocket)
{
   OSOCTET msgbuf[MAXMSGLEN];

   printf ("receive ACSE response..\n");

   /* receive message into buffer */
   int stat = recvMessage(cstaSocket, msgbuf, sizeof(msgbuf));
   if (stat != 0) return stat;

   /* decode message */
   ASN1BERDecodeBuffer decodeBuffer (msgbuf, sizeof(msgbuf));

   ASN1T_ACSE_apdu pdu;
   ASN1C_ACSE_apdu pduC (decodeBuffer, pdu);

   stat = pduC.Decode ();

   if (stat == 0) {
      printf ("decoded message:\n");
      if (mTrace) {
         pduC.Print ("pdu");
      }
   }
   else {
      printf ("Decode of ACSE PDU failed\n");
      decodeBuffer.printErrorInfo ();
      return -1;
   }
   return 0;
}


int SiemensClient::recvCSTAMessage (OSRTSocket& cstaSocket)
{
   OSOCTET msgbuf[MAXMSGLEN];

   /* receive message into buffer */
   int stat = recvMessage(cstaSocket, msgbuf, sizeof(msgbuf));
   if (stat != 0) return stat;

   /* decode message */
   ASN1BERDecodeBuffer decodeBuffer (msgbuf, sizeof(msgbuf));

   ASN1T_CSTA_ROSE_PDU pdu;
   ASN1C_CSTA_ROSE_PDU pduC (decodeBuffer, pdu);

      stat = pduC.Decode ();
      printf ("decoded message:\n");
      if (stat == 0) {
         //printf ("Decode of ROSE PDU was successful\n");
         if (mTrace) {
            pduC.Print ("pdu");
         }
      }
      else {
         printf ("Decode of ROSE PDU failed\n");
         decodeBuffer.printErrorInfo ();
         return -1;
      }

   //call processCSTAMessage() or directly compare required binary data,
   //instead of decoding message
   processCSTAMessage(pdu, TRUE);

   return 0;
}


int SiemensClient::recvCSTASystemStatusRes (OSRTSocket& cstaSocket)
{
   printf ("receive CSTA system status request..\n");

   return recvCSTAMessage (cstaSocket);
}

int SiemensClient::sendCSTASystemStatusRes (OSRTSocket& cstaSocket)
{
   int len, stat;
   /* Initialize the encoding context */

   ASN1BEREncodeBuffer encodeBuffer;

   printf ("send CSTA system status response..\n");

   if ((len = encodeCSTASystemStatusRes (encodeBuffer)) < 0) return len;
   
   stat = sendMessage (cstaSocket, encodeBuffer, len);
   if (stat != 0) return stat;
   
   return stat;
}

int SiemensClient::setup (OSRTSocket& cstaSocket)
{
   int stat;
   printf ("establish ACSE connection ..\n");

   /* Send ACSE AARQ request */
   stat = sendACSEConnectionRequest (cstaSocket);
   if (stat != 0) return stat;

   /* Receive ACSE AARE message */
   stat = recvACSEMessage (cstaSocket); //change it to required data compare
   if (stat != 0) return stat;

   /* Receive CSTA System Status message */
   stat = recvCSTASystemStatusRes (cstaSocket); //change it to required data compare
   if (stat != 0) return stat;

   /* Send CSTA system status response */
   stat = sendCSTASystemStatusRes (cstaSocket);
   if (stat != 0) return stat;

   printf ("ACSE connection established.\n");
   printf ("\n");

   return stat;
}

int SiemensClient::release (OSRTSocket& cstaSocket)
{
   ASN1BEREncodeBuffer encodeBuffer;
   int stat, len;
   printf ("release ACSE connection..\n");

   /* Send ACSE RLRQ request */
   printf ("send ACSE RLRQ request..\n");
   if ((len = encodeACSEReleaseRequest (encodeBuffer)) < 0) return len;
   stat = sendMessage (cstaSocket, encodeBuffer, len);
   if (stat != 0) return stat;


   /* Receive ACSE RLRE response */
   stat = recvACSEMessage (cstaSocket); //change it to required data compare
   if (stat != 0) return stat;
   
   printf ("ACSE connection released.\n");
   printf ("\n");

   return stat;
}
