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.
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.
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?
Hi Matt,
Its just a CHARACTER.
Thanks,
Mark
I'm confused. Does SocketConnect convert it back to an INTEGER to store it in the MEMPTR?
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".
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.
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?
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.
"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?!
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.
Will all of the commands start with CSI + SIF_GENERAL? That will make it easier to fix the issue.
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?
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.
Many thanks :-)
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.
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!