// This sample program attempts to create a CSTA link with a Siemens 
// PBX via the TCP/IP port..

#include <stdio.h>
#include <stdlib.h>
#include "ACSE-1.h"
#include "CSTA-ROSE-PDU-types.h"
#include "CSTA-system-status.h"
#include "OSRTSocket.h"
#include "CSTA-application-context-information-csta3.h"

#define MAXMSGLEN 1024
#define SystemStatus_CODE 211

static int encodeACSEConnectionRequest (ASN1BEREncodeBuffer& encodeBuffer);
static int encodeACSEReleaseRequest (ASN1BEREncodeBuffer& encodeBuffer);
static int encodeCSTASystemStatusRes (ASN1BEREncodeBuffer& encodeBuffer);
static int encodeROSEResponseHeader (ASN1BEREncodeBuffer& encodeBuffer, 
                                     int msglen, int opcode);
static int recvMessage (OSRTSocket& cstaSocket);
static int sendMessage 
(OSRTSocket& cstaSocket, ASN1BEREncodeBuffer& encodeBuffer, size_t len);

int main (int argc, char** argv)
{
   /* Run-time support variables */

   const char*  ipaddr = 0;
   OSOCTET	msgbuf[MAXMSGLEN];
   int		i, len, port = 1110, stat;
   OSBOOL     trace = TRUE;

   /* Process command line arguments */

   if (argc > 1) {
      for (i = 1; i < argc; i++) {
         if (!strcmp (argv[i], "-v")) trace = TRUE;
         else if (!strcmp (argv[i], "-ipaddr")) ipaddr = argv[++i];
         else if (!strcmp (argv[i], "-port")) port = atoi (argv[++i]);
         else if (!strcmp (argv[i], "-notrace")) trace = FALSE;
         else {
            printf ("usage: writer -ipaddr x.x.x.x [ -port x ] [ -v ]\n");
            printf ("   -ipaddr  IP address to connect to\n");
            printf ("   -port    port number\n");
            printf ("   -v       verbose mode: print trace info\n");
            return 0;
         }
      }
   }

   if (0 == ipaddr) {
      printf ("IP address argument (-ipaddr) is required\n");
      return -1;
   }

      OSRTSocket cstaSocket;

      printf ("attempting to connect to %s, port %d..\n", ipaddr, port);

      cstaSocket.connect (ipaddr, port);

      printf ("connection established.\n");

      /* Initialize the encoding context */

      ASN1BEREncodeBuffer encodeBuffer (msgbuf, sizeof(msgbuf));

      /* Create ACSE AARQ PDU */

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

      if ((len = encodeACSEConnectionRequest (encodeBuffer)) < 0) return len;
      
      if (trace) {
         encodeBuffer.binDump();
      }

      /* Send message to PBX */

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

      /* Receive ACSE AARE message */

      stat = recvMessage (cstaSocket);
      if (stat != 0) return stat;

      /* Receive CSTA System Status message */

      stat = recvMessage (cstaSocket);
      if (stat != 0) return stat;

      /* Send system status response */

      printf ("formatting system status response..\n");

      if ((len = encodeCSTASystemStatusRes (encodeBuffer)) < 0) return len;
      
      if (trace) {
         encodeBuffer.binDump();
      }

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

      /* Connection with device should now be established, additional   */
      /* CSTA operations can now be invoked..                           */


      /* Now, release connection: format and send ACSE RLRQ */

      printf ("formatting ACSE RLRQ request..\n");

      if ((len = encodeACSEReleaseRequest (encodeBuffer)) < 0) return len;
      
      if (trace) {
         encodeBuffer.binDump();
      }

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

      /* Receive ACSE RLRE response */

      stat = recvMessage (cstaSocket);
      if (stat != 0) return stat;

   return 0;
}

static int encodeACSEConnectionRequest (ASN1BEREncodeBuffer& encodeBuffer)
{
   ASN1T_AARQ_apdu aarq_apdu;
   int len;
  
   /*
   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;

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

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

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

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

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

static int recvMessage (OSRTSocket& cstaSocket)
{
   OSOCTET msgbuf[MAXMSGLEN];
   OSOCTET lenbytes[2];
   int len;

   /* Receive length bytes */

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

   printf ("lenbytes = { %02x, %02x }\n", lenbytes[0], lenbytes[1]);
   int msglen = (lenbytes[0] * 256) + lenbytes[1];
   printf ("msglen = %d\n", msglen);

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

   if ((unsigned int)msglen > sizeof(msgbuf)) {
      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;
   }

   /* Dump BER message contents */

   ASN1BERDecodeBuffer decodeBuffer1 (msgbuf, sizeof(msgbuf));
   decodeBuffer1.binDump();
   printf ("***\n");

   return 0;
}

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

   OSCTXT* pctxt = encodeBuffer.getCtxtPtr();
   OSOCTET lenbytes[2];
   lenbytes[0] = (len >> 8) & 0x000000FF;
   lenbytes[1] = len & 0x000000FF;

   xe_memcpy (pctxt, lenbytes, 2);
   len += 2;

   const OSOCTET* msgptr = encodeBuffer.getMsgPtr();
   printf ("2 length bytes + encoded ACSE AARQ message:\n");
   rtxHexDump (msgptr, len);

   /* Send message to device socket */

   printf ("sending message to CAP server..\n");
   cstaSocket.send (msgptr, len);
   printf ("message sent\n");
      
   return 0;
}
