4GL Sockets and Hexadecimal values...

Posted by MBeynon on 11-Jun-2015 03:49

Hello,

I have a requirement to connect via a raw socket connection to a server that is expecting data in a certain structure.

The connection is not a problem but the sending of the inital login request is proving problematic.

The API for the software I wish to connect to uses hex values, added together to build a particular request identifier, in this case the request to login:

DEFINE VARIABLE CSI                      AS INT64 INIT 0X80000000 NO-UNDO.
DEFINE VARIABLE SIF_GENERAL AS INT64 INIT 0X08000000 NO-UNDO.
DEFINE VARIABLE SIS_LOGIN       AS INT64 INIT 0X00000100 NO-UNDO.
DEFINE VARIABLE CSI_LOGIN       AS INT64                                  NO-UNDO. /* Request header */

ASSIGN
  CSI_LOGIN = CSI + SIF_GENERAL + SIS_LOGIN.

RUN SocketConnect(INPUT "IP",
                  INPUT "port",
                  INPUT "",
                  INPUT 0,
                  INPUT lvcUserName,
                  INPUT hexadecimal(CSI_LOGIN),   /* Add the hexadecimal values */
                  INPUT EncryptData(lvcPassword),
                  OUTPUT lvlErrorMsg).

So, for CSI_LOGIN we have 0X80000000 + 0X08000000 + 0X00000100 which gives us 0X88000100 using the Hexadecimal function below. This is then inserted into the sockets MEMPTR.

/* found this on this forum I think! */

FUNCTION Hexadecimal RETURN CHARACTER (lvcHex AS INT64):

    DEF VAR  hNum AS  CHARACTER NO-UNDO  EXTENT  16 INITIAL [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E' ].
    DEF VAR  chex AS  CHARACTER NO-UNDO.
    DEF VAR  pres AS  INT64 NO-UNDO.
    DEF VAR  pmod AS  INT64 NO-UNDO.

    pres = lvcHex.

    DO WHILE pres > 0:
      ASSIGN pmod = ( pres MOD 16 )
             chex = hNum[pmod + 1] + chex
             pres = ( pres - pmod ) / 16.
    END.
    RETURN chex.

END FUNCTION.

The only way to do the above as far as I’m aware is using an INT64 value which is 8 bytes (64bits), not 4 and 32 as required by the "listening" machine's software.

So, in order to get this to work, I need to pass 0X88000100 across in the MEMPTR represented as an INT (32) not INT64.

Have I missed something really obvious here?

Thanks,

Mark.

Posted by Matt Gilarde on 12-Jun-2015 09:24

I have attached (I hope) my modified version of the code. My changes are marked with ADDED, CHANGED, and REMOVED so you can find what I changed. There are also comments explaining why I made the changes.

All Replies

Posted by Matt Gilarde on 11-Jun-2015 04:44

The Hexadecimal function returns the number represented as a hexadecimal string. I don't think that's what you want. What type is the sixth parameter to SocketConnect? Is it an INTEGER?

Posted by MBeynon on 11-Jun-2015 04:47

Hi Matt,

Its just a CHARACTER.

Thanks,

Mark

Posted by Matt Gilarde on 11-Jun-2015 05:07

I'm confused. Does SocketConnect convert it back to an INTEGER to store it in the MEMPTR?

Posted by MBeynon on 11-Jun-2015 05:45

I've been looking at the HEX-DECODE function which according to the Progress help:

Converts a character string consisting of an even number of hexadecimal digits (0 through 9 and A through F) into a RAW value.

DEFINE VARIABLE vRaw AS RAW NO-UNDO.

vRaw = HEX-DECODE(hexadecimal(CSI_LOGIN)).

which gives me this (STRING(RAW)) "020004iAABAA==" from this "0X88000100".

Posted by MBeynon on 11-Jun-2015 07:07

No, it puts it into a MEMPTR using  PUT-UNSIGNED-LONG before placing it into the main socket memptr.

I do this as the API requires that: "All multi-byte numeric values are to use little-endian byte ordering."

PROCEDURE SocketConnect :

 /*------------------------------------------------------------------------------

   Purpose:     Handle the response from the remote server

   Parameters:  <none>

   Notes:      

 ------------------------------------------------------------------------------*/

 DEFINE INPUT PARAMETER  ipcHost     AS CHARACTER NO-UNDO.

 DEFINE INPUT PARAMETER  ipiPort     AS INTEGER   NO-UNDO.

 DEFINE INPUT PARAMETER  ipcProtocol AS CHARACTER NO-UNDO.

 DEFINE INPUT PARAMETER  ipiTimeout  AS INTEGER   NO-UNDO.

 DEFINE INPUT PARAMETER  ipcUserName AS CHARACTER NO-UNDO.

 DEFINE INPUT PARAMETER  lvi64Header AS INT64     NO-UNDO.

 DEFINE INPUT PARAMETER  ipcPassWord AS CHARACTER NO-UNDO.

 DEFINE OUTPUT PARAMETER oplErrorMsg AS LOGICAL   NO-UNDO.

 DEFINE VARIABLE lvmMEMPTRHeader   AS MEMPTR NO-UNDO.

 ASSIGN

     SET-SIZE(lvmMEMPTRHeader)                                  = 8

     SET-BYTE-ORDER(lvmMEMPTRHeader)                = LITTLE-ENDIAN

     PUT-UNSIGNED-LONG(lvmMEMPTRHeader,1)    = lvi64Header.

     do some stuff...

     /* Got a socket connection - now make the request */

     ASSIGN

       SET-SIZE   (lvhBuffer)    = 8 + 2 + 8186

       PUT-BYTES  (lvhBuffer,1)  = lvmMEMPTRHeader

       PUT-BYTES  (lvhBuffer,9)  = lvmMEMPTRDataSize

       PUT-STRING (lvhBuffer,11) = ipcUserName + "0"

       PUT-STRING (lvhBuffer,23) = ipcPassWord + "0".

      do more stuff....

Thanks,

Mark.

Posted by Matt Gilarde on 11-Jun-2015 07:31

SocketConnect expects an INT64 for the lvi64Header parameter. The code you showed in the first post is passing a character string.

There's nothing special about hexadecimal numbers. They're just a different way of expressing numeric values. You could change the definitions as follows and nothing would be different:

DEFINE VARIABLE CSI                      AS INT64 INIT 2147483648 NO-UNDO.

DEFINE VARIABLE SIF_GENERAL AS INT64 INIT 134217728 NO-UNDO.

DEFINE VARIABLE SIS_LOGIN       AS INT64 INIT 256 NO-UNDO.

DEFINE VARIABLE CSI_LOGIN       AS INT64                                  NO-UNDO. /* Request header */

ASSIGN

 CSI_LOGIN = CSI + SIF_GENERAL + SIS_LOGIN.

I think you just need to pass CSI_LOGIN to SocketConnect. And shouldn't the PUT-UNSIGNED-LONG in SocketConnect be PUT-UNSIGNED-INT64? Or does the API expect a 4-byte value? Can you show us the definition of the structure you're filling in SocketConnect?

Posted by MBeynon on 11-Jun-2015 08:02

Matt,

First of all, thanks for taking an ineterest in this :-)

"Or does the API expect a 4-byte value?"

That's the problem. The API expects a 4 byte value whereas I can only currently supply 8 (INT64).

Here's SocketConnect in its entirety followed by en excerpt from the API I'm trying to conform to:

PROCEDURE SocketConnect :

 /*------------------------------------------------------------------------------

   Purpose:     Handle the response from the remote server

   Parameters:  <none>

   Notes:      

 ------------------------------------------------------------------------------*/

 DEFINE INPUT PARAMETER  ipcHost     AS CHARACTER NO-UNDO.

 DEFINE INPUT PARAMETER  ipiPort     AS INTEGER   NO-UNDO.

 DEFINE INPUT PARAMETER  ipcProtocol AS CHARACTER NO-UNDO.

 DEFINE INPUT PARAMETER  ipiTimeout  AS INTEGER   NO-UNDO.

 DEFINE INPUT PARAMETER  ipcUserName AS CHARACTER NO-UNDO.

 DEFINE INPUT PARAMETER  lvi64Header AS INT64     NO-UNDO.

 DEFINE INPUT PARAMETER  ipcPassWord AS CHARACTER NO-UNDO.

 DEFINE OUTPUT PARAMETER oplErrorMsg AS LOGICAL   NO-UNDO.

 DEFINE VARIABLE lvmMEMPTRHeader   AS MEMPTR NO-UNDO.

 ASSIGN

     SET-SIZE(lvmMEMPTRHeader)               = 8

     SET-BYTE-ORDER(lvmMEMPTRHeader)         = LITTLE-ENDIAN

     PUT-UNSIGNED-LONG(lvmMEMPTRHeader,1)    = lvi64Header.

 DEFINE VARIABLE lvmMEMPTRDataSize AS MEMPTR NO-UNDO.

 ASSIGN

     SET-SIZE(lvmMEMPTRDataSize)               = 2

     SET-BYTE-ORDER(lvmMEMPTRDataSize)         = LITTLE-ENDIAN

     PUT-UNSIGNED-SHORT (lvmMEMPTRDataSize,1)  = (LENGTH(ipcUserName) + 1) + LENGTH(ipcPassWord) + 1.  

 _process:

 DO:

   DO ON ERROR UNDO, LEAVE:

     /* Open a socket connection to the server */

     CREATE SOCKET lvhSocket.

     lvlStatus = lvhSocket:SET-READ-RESPONSE-PROCEDURE("ReadHandler":U, THIS-PROCEDURE) NO-ERROR.

     IF NOT lvlStatus THEN

     DO:

       ASSIGN

         opiResultCode = {&ERR_CONNECT_FAILURE}

         opcResultDesc = "Unable to set readresponse : " + ERROR-STATUS:GET-MESSAGE(1).

       LEAVE _process.

     END.

     ASSIGN lvlStatus = lvhSocket:CONNECT(SUBSTITUTE("-H &1 -S &2 &3 &4":U,

                                                     ipcHost,

                                                     ipiPort,

                                                     IF ipcProtocol = {&PROTOCOL_HTTPS} OR ipcProtocol = {&PROTOCOL_HTTPS_UNVERIFIED} THEN "-ssl":U ELSE "":U,

                                                     IF ipcProtocol = {&PROTOCOL_HTTPS_UNVERIFIED} THEN "-nohostverify":U ELSE "":U )) NO-ERROR.

     /* Now make sure the socket is open */

     IF lvlStatus = NO OR NOT lvhSocket:CONNECTED() THEN

     DO:

       ASSIGN

         opiResultCode = {&ERR_CONNECT_FAILURE}

         opcResultDesc = "Unable to connect to socket : " + ERROR-STATUS:GET-MESSAGE(1).

       LEAVE _process.

     END.

     /* Set the timeout on the socket receive wait

     lvhSocket:SET-SOCKET-OPTION("SO-RCVTIMEO", STRING(ipiTimeout)).*/

     /* Got a socket connection - now make the request */

     ASSIGN

       SET-SIZE   (lvhBuffer)    = 8 + 2 + 8186

       PUT-BYTES  (lvhBuffer,1)  = lvmMEMPTRHeader

       PUT-BYTES  (lvhBuffer,9)  = lvmMEMPTRDataSize

       PUT-STRING (lvhBuffer,11) = ipcUserName + "0"

       PUT-STRING (lvhBuffer,23) = ipcPassWord + "0".

     ASSIGN lvlStatus = lvhSocket:WRITE(lvhBuffer, 1, LENGTH(lvcRequestString)) NO-ERROR.

     IF NOT lvlStatus OR ERROR-STATUS:ERROR THEN

     DO:

       ASSIGN

         opiResultCode = {&ERR_TRANSMIT_FAILURE}

         opcResultDesc = "Unable to transmit to socket : " + ERROR-STATUS:GET-MESSAGE(1).

       LEAVE _process.

     END.

     /* Wait for a response */

     ASSIGN

       lvlWaitingForResponse = TRUE  /* Turned off when request is done */

       lviStart              = ETIME.      

     WAIT-FOR READ-RESPONSE OF lvhSocket.

   END.

   LEAVE _process.

 END. /* _process */

 ASSIGN

   oplErrorMsg = opcResultDesc <> "":U.

 FINALLY:

   /* Cleanup our resources */

   SET-SIZE(lvhBuffer) = 0.

   SET-SIZE(lvmMEMPTRHeader) = 0.

   SET-SIZE(lvmMEMPTRDataSize) = 0.

   lvhSocket:DISCONNECT().

   DELETE OBJECT lvhSocket NO-ERROR.

 END FINALLY.

END PROCEDURE.

API:

All data sent over this interface must use 1 byte structure member alignment. All multi-byte numeric values are to use little-endian byte ordering.

 

A command is structured thus:

ULONG             ulCommand                  // Command code (CSI_xx)

USHORT           usLength                       // Length of data to follow (can be zero)

DATA               data                              // data for the command (max. 8186 bytes)

Message identifiers are unsigned 32-bit values (ULONG). This value is coded so as to allow simple decomposition of the messaging domain. The coding operates as follows starting with the most significant bit and working towards the least significant bit as in the table on the following page:

 

Number of bits

Purpose

4

Message type (t):

1000 = Command

1001 = Response

1002 = Event

Other values reserved for future expansion

12

Functional area (f):

100000000000 = General

100000000001 = Configuration

100000000010 = Archiving

100000000011 = Search

100000000100 = Replay

100000000101 = Statistics

100000000110 = Engineer

 

Other values reserved for future expansion

16

Specific message identifier (s) - these are to be defined as necessary.

 

This method will not necessarily be as human readable as a system oriented around standard decimal partitioning but as hexadecimal (0xtfffssss) it is still human readable and easily manipulated from a programming point of view.

Message identifiers are constructed from the three component parts. For example:

#define CSI                                           0x80000000

#define RSI                                           0x90000000

#define ESI                                           0xA0000000

 

#define SIF_GENERAL                          0x08000000

#define SIF_CONFIGURATION               0x08010000

#define SIF_ARCHIVE                           0x08020000

#define SIF_SEARCH                            0x08030000

#define SIF_REPLAY                            0x08040000

#define SIF_STATISTICS                       0x08050000

#define SIF_ENGINEER             0x08060000

 

#define SIS_UNKNOWN_COMMAND     0x00000000

#define SIS_BAD_DATA                        0x00000001

Thanks,

Mark.

Posted by MBeynon on 11-Jun-2015 08:14

"SocketConnect expects an INT64 for the lvi64Header parameter. The code you showed in the first post is passing a character string."

Your absolutely right but somehow it compiles and runs?!

Posted by Matt Gilarde on 11-Jun-2015 08:21

It compiles and runs because ABL converts types automatically when it can. The hexadecimal function returns "88000100". If you pass that string to an INTEGER parameter ABL converts it to a decimal value, 88000100, which is not the value you expect.

Posted by Matt Gilarde on 11-Jun-2015 09:18

Will all of the commands start with CSI + SIF_GENERAL? That will make it easier to fix the issue.

Posted by MBeynon on 12-Jun-2015 02:28

Unfortunately not. As well as logging in and out there are a couple of other commands we need to use.

I can supply all the ones that are relevant if you wish?

Posted by Matt Gilarde on 12-Jun-2015 04:34

I don't need to know all of the commands. I just needed to know how flexible the code needs to be. I'm working on a solution.

Posted by MBeynon on 12-Jun-2015 08:21

Many thanks :-)

Posted by Matt Gilarde on 12-Jun-2015 09:24

I have attached (I hope) my modified version of the code. My changes are marked with ADDED, CHANGED, and REMOVED so you can find what I changed. There are also comments explaining why I made the changes.

Posted by MBeynon on 15-Jun-2015 01:46

Thanks for that. It looks good. I wasn't far off by the looks of things. One other thing I missed that was also preventing it from working was "ASSIGN lvlStatus = lvhSocket:WRITE(lvhBuffer, 1, LENGTH(lvcRequestString)) NO-ERROR"

lvcRequestString is incorrect, it should be:

ASSIGN lvlStatus = lvhSocket:WRITE(lvhBuffer, 1, GET-SIZE(lvhBuffer)) NO-ERROR.

Once I'd fixed this I started getting back a response from the server! Now, to start decoding the response!

This thread is closed