--------------------------------------
Short description of Harbour functions
--------------------------------------
 
   cAMF := AMF3_ENCODE( xVal, symConvOut, lBinaryStrings )
   xVal           - any supported datatype:
                    Character (String/MEMO), Numeric (Integer/Double), NIL, Logical, Date (encoded as DateTime),
                    DateTime, Array, Hash (String and Integer keys only),
                    Object (anonymous hash-like, externalizable, class-mapped)

                    AMF supports references, so this example a1 value will be serialized correctly.
                      a1 := { NIL }
                      a2 := { a1 }
                      a1[1] := a2

   symConvOut     - function symbol for outbound conversion
                    Acts recursively, so if xVal is array,
                    all of it's values will be passed to this
                    function.

   lBinaryStrings - treat strings as binary, resulting AMF
                    datatype will be ByteArray. Normally
                    a string is encoded to UTF-8. 

   xVal := AMF3_DECODE( cAMF, symConvIn )
   cAMF           - AMF3 serialized binary string
   symConvIn      - function symbol for inbound conversion
  
   cAMF := AMF3_FROMWA( [ <bWhile> ], [ <bFor> ], [ <aFields> ], [ <nCount> ], [ <lStrTrim> ], [ <nPackage> ], [ pContext ] )

   Function to convert current workarea to AMF3 Array.

   bWhile   - COPY TO like WHILE codeblock
   bFor     - COPY TO like FOR codeblock
   aFields  - array of fieldnames (codeblocks are going to be supported here too)
   nCount   - NEXT like, process only specified count of records
   lStrTrim - RTrim() strings, default is .T.

   nPackage - determine the exact output AMF format
              0 - Array of records contains Arrays of fields (default)
              1 - Array of records contains Anonymous objects
              2 - ArrayCollection object
              Lower number means lesser packet size on the network.

   pContext - when this function is used inside AMF3_ENCODE specified 
              outbound conversion function, you must pass pointer to
              encoding context, otherwise AMF3 references will encoded
              incorrectly. Example code of such case:
              STATIC FUNCTION ConvOut( xVal, pOuterContext )
                 LOCAL lClose  

                 IF ValType( xVal ) = "O"
                     IF xVal:className == "WORKAREAEXPORT"
                       lClose := xVal:lCloseWA
                       SELECT ( xVal:nWorkArea )
   
                       xVal := RawAMF():New( AMF3_FROMWA( xVal:bWhile, xVal:bFor, xVal:aFields, xVal:nCount, xVal:lStrTrim, 1, pOuterContext ) )
                       IF lClose
                          CLOSE
                       ENDIF
                    ENDIF
                 ENDIF

              RETURN xVal

   There are also ideas for functions where the serialized data is not fully buffered, like:
   lSent := AMF3_ENCODETOSOCKET( xVal, symConvOut, pSocket )


   pListenSocket := AMFIO_MTSERVER( <nPort>, <cIfAddr>, [ <hAnonFuncs> ], [ <lUserControl> ], [ <symConvIn> ], [ <symConvOut> ], [ <lObjectExt> ] )
   pListenSocket := AMFWS_MTSERVER( <nPort>, <cIfAddr>, [ <hAnonFuncs> ], [ <lUserControl> ], [ <symConvIn> ], [ <symConvOut> ], [ <lObjectExt> ] )

   Function starts a new thread which then listens for multiple network connections
   Program should be compiled with multi-threading enabled (hbmk -mt switch).
   The one with AMFIO prefix listens for Flash clients, while AMFWS for WebSocket/JS clients.
  
   nPort        - TCP port
   cIfAddr      - IP bind address
   hAnonFuncs   - hash with a list of functions allowed for every connection,
                  without authentication
                  with lUserControl set to .F., { => } would set up a not usable
                  server with no function exposed, whereas NIL would set up
                  a _INSECURE_ server, with all functions exposed.
                  with lUserControl set to .T., { => } and NIL are working
                  exactly the same, the only allowed function is "AUTH".
                  { "SOMETHING" => } results in "SOMETHING" and "AUTH"
                  functions being allowed for anonymous connections.
  
   lUserControl - if .T. is specified then AUTH function is used for authentication,
                  list of allowed functions could be extended after successful result
  
   symConvIn    - function symbol for inbound conversion
   symConvOut   - function symbol for outbound conversion
  
   lObjectExt   - object extensions enabled
                  Server exposes a interface, which allows a client to create a new
                  object instance - a list of allowed functions is also used for this.
                  Such objects are not serialized through the connection, but client
                  has a possibility to execute its methods. Think of it as exposing
                  Object as a Service.

----------------------------------
Short overview of "AMFIO" protocol
----------------------------------

Request packet consists of:
[ little-endian UINT32 length prefix ]
   [ single byte specifying the type of the message ]
   < little-endian UINT16 reference ID of request* >
   [ AMF3 serialized array of parameters** ]
   [ XOR excluding length prefix ]

* - not being sent when type of the message is AMFIO_PROCEXEC.
    It's up to a client to manage these ID's: which can
    be done by creating a list or hash with currently pending
    operations ID, to do not reuse particular ID.
    So in fact the limitation is that a client can have
    maximum of 65534 jobs at the same time.

** - first parameter holds function/procedure identifier,
     typically a string, but numeric identifiers are possible
     if server exposes symbol table to hash of allowed functions

Reply packet:
[ little-endian UINT32 length prefix ]
   [ little-endian UINT16 reference ID from request* ]
   [ AMF3 serialized return value ]
   [ XOR excluding length prefix ]

* - a value of zero is not used and reserved for future purposes
    f.e. if we want the server also make requests to the client,
    then we could tag it with zero value

"AMFWS" variant is basically the same, but instead of UINT32 prefix
it has full WebSocket header - https://tools.ietf.org/html/rfc6455

Basic messages sent over the protocol:

Byte | Example function/method on the client side
     |
 8   |  AMFIO_PROCEXISTS( <cProcName> ) -> <lExists>
 9   |  AMFIO_PROCEXEC( <cProcName> [, <params,...>] ) -> <lSent>
 10  |  AMFIO_FUNCEXEC( <cFuncName> [, <params,...>] ) -> <xFuncRetVal>
 16  |  AMFIO_OBJECTNEW( <cClassName> [, <params,...>] ) -> <nObject> or null
 17  |  AMFIO_OBJECTDEL( <nObject> ) -> <nObject> or -1
 18  |  AMFIO_OBJECTWITH( <nObject> ) -> <nObject>
 19  |  AMFIO_OBJECTSEND( <cMethodName>[, <params,...>] ) -> <xFuncRetVal>

Authentication is moved to upper layer, usually server is limited
to "AUTH" function for new connections.

An example of secure password authentication is easy to implement:
  Auth() -> <cChallenge>
  Auth( cUser, HB_SHA1( HB_SHA1( cPass + cSalt ) + cChallenge ) ) -> <lOk>
