////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// ZeroMQ Manager.                                                            //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20260324 - Created by A.K.                                                 //
// 20260407 - Monitor events                                                  //
// 20260701 - socket IoMode                                                   //
////////////////////////////////////////////////////////////////////////////////

unit _crw_zmqman; // ZeroMQ Manager.

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
{$WARN 5043 off : Symbol "$1" is deprecated}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math, ctypes,
 _crw_alloc, _crw_ef, _crw_hl, _crw_str, _crw_fio, _crw_dynar,
 _crw_dbglog, _crw_crypt, _crw_cnmap, _crw_errno, _crw_base64,
 _crw_rtc, _crw_polling, _crw_zmqlib;

 //////////////
 // TZmqManager
 //////////////
type
 TZmqSocket = class;
 TZmqContext = Pointer;
 TZmqSocketRef = Pointer;
 TZmqObject = class(TMasterObject)
 protected
  function  AskErrno:Integer; virtual;
  function  AskStrError(n:Integer):LongString; virtual;
  class function RetOk(Ret:Integer):Boolean;
  class function RefRet(aRef:Pointer):Integer;
 public
  function  NeedLog:Boolean;
  function  ZCall(Ret:Integer; const Who:LongString):Integer;
  function  FormatRet(Ret,Err:Integer; const Who:LongString):LongString;
 public
  class function ValidApi(aCheck:Boolean=False):Boolean;
  class function StrErrno(n:Integer):LongString;
  class function ErrnoMap:TCodeNameMap;
  class function CtxOptMap:TCodeNameMap;
  class function SockTypMap:TCodeNameMap;
  class function SockOptMap:TCodeNameMap;
  class function EventNameMap:TCodeNameMap;
  class function ValidSockOptDataType(S:LongString):Boolean;
  class function ListAllSockOptDataTypes(Delim:LongString=EOL):LongString;
  class function ListSockOptNames(Delim:LongString=EOL; Filter:LongString=''):LongString;
  class function AskSockOptTyp(Code:Integer; how:Char):LongString;
  class function IsEventsPollIn(aEvents:Integer):Boolean; inline;
  class function IsEventsPollOut(aEvents:Integer):Boolean; inline;
  class function AdaptEndpointName(const aEndpoint:LongString):LongString;
  class function ValidEndpointName(const aEndpoint:LongString):Boolean;
  class function FetchEndpointTransport(const aEndpoint:LongString):LongString;
  class function ConnectionOrientedTransports(const Delim:LongString=EOL):LongString;
  class function IsConnectionOriented(const aEndpoint:LongString):Boolean;
  class function NumConnectionOriented(const aEndpoints:LongString):Integer;
  class function AdaptSubFilter(const aSubFilter:LongString):LongString;
  class function ValidSubFilter(const aSubFilter:LongString):Boolean;
  class function ListAllTransports(const Delim:LongString=EOL):LongString;
  class function ValidEvent(aEvent:Integer):Boolean;
  class function EnsureZmqPrefix(const S:LongString):LongString;
  class function TextToList(const S,Delim:LongString):LongString;
  class function ListToText(const S,Delim:LongString):LongString;
  class function CookiesDelims:TCharSet; inline;
  class function CookiesDelim:Char; inline;
  class function CookiesSep:Char; inline;
 private
  class var TheErrnoMap:TCodeNameMap;
  class var TheCtxOptMap:TCodeNameMap;
  class var TheSockTypMap:TCodeNameMap;
  class var TheSockOptMap:TCodeNameMap;
  class var TheEventNameMap:TCodeNameMap;
  class var TheSockOptTypTab:array[Byte] of Integer;
  class procedure CheckMappers;
  class procedure InitMappers;
  class procedure FreeMappers;
 end;
 TZmqManager = class(TZmqObject)
 private
  myLib     : LongString;
  myCtx     : TZmqContext;
  mySockets : TObjectStorage;
  myCaps    : LongString;
 private
  procedure CtxNew;
  procedure CtxTerm;
  function  GetCtx:TZmqContext;
  function  GetErrno:Integer;
  function  GetVersionInt:Integer;
  function  GetVersionStr:LongString;
  function  GetLibZmqName:LongString;
  function  GetCtxOpt(n:Integer):Integer;
  procedure SetCtxOpt(n:Integer; v:Integer);
  function  GetCtxOptsAsText:LongString;
  procedure SetCtxOptsAsText(Lines:LongString);
  function  GetCtxOptsAsList:LongString;
  procedure AddSocket(aSocket:TZmqSocket);
  procedure RemoveSocket(aSocket:TZmqSocket);
  function  GetSocketsCount:Integer;
  function  GetSockets(i:Integer):TZmqSocket;
  procedure SocketsKillAll;
  procedure ZeroBlocky;
 protected
  function  CheckOk:Boolean; override;
  function  AskErrno:Integer; override;
  function  AskStrError(n:Integer):LongString; override;
 public
  constructor Create(const aLib:LongString=LIB_ZMQ);
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  property  Ctx                : TZmqContext read GetCtx;
  property  Errno              : Integer     read GetErrno;
  property  VersionInt         : Integer     read GetVersionInt;
  property  VersionStr         : LongString  read GetVersionStr;
  property  LibZmqName         : LongString  read GetLibZmqName;
  property  CtxOpt[n:Integer]  : Integer     read GetCtxOpt        write SetCtxOpt;
  property  CtxOptsAsText      : LongString  read GetCtxOptsAsText write SetCtxOptsAsText;
  property  CtxOptsAsList      : LongString  read GetCtxOptsAsList write SetCtxOptsAsText;
  property  SocketsCount       : Integer     read GetSocketsCount;
  property  Sockets[i:Integer] : TZmqSocket  read GetSockets;
 public
  function  HasCap(const aCap:LongString):Boolean;
  function  CapsList(const Delim:LongString=EOL):LongString;
 public
  function  BriefInfo:LongString;
  function  StrError(n:Integer):LongString;
  function  StrErrorMsg(n:Integer):LongString;
  function  Ctrl(const arg:LongString; const Delim:LongString=EOL; Sep:Char=#0):LongString;
 public
  function  NewSocket(aSockTyp:Integer):TZmqSocket;
 public
  function  CurveKeyPair(const Delim:LongString=' '):LongString;
  function  CurvePublic(const aSecrKey:LongString):LongString;
  function  Z85Encode(const aData:LongString):LongString;
  function  Z85Decode(const aText:LongString):LongString;
 public
  function  StartProxy(aFrontend,aBackend,aCapture:TZmqSocket):Boolean;
  function  FormatMonitorEvent(aEvent,aValue:Integer; const aAddress:LongString):LongString;
 public
  class function SecrKeyBinLeng:Integer; inline;
  class function SecrKeyZ85Leng:Integer; inline;
  class function CapsNames(const Delim:LongString=EOL):LongString;
  class function CtxOptSet(const rw:LongString='rw'):TByteSet;
  class function CtxOptNumToString(n:Integer):LongString;
  class function StringToCtxOptNum(s:LongString):Integer;
  class function CtxOptNames(const Delim:LongString=EOL):LongString;
  class function ZmqEngineNames(const Delim:LongString=EOL):LongString;
  class function IsZmqEngineName(const S:LongString):Boolean;
  class function AllButNoSpamMonitorEvents:Integer;
 public
  class function MonitorDefCloTimeout(aTimeout:Integer=-1):Integer;
  class function MonitorDefRcvTimeout(aTimeout:Integer=-1):Integer;
  class function MonitorDefPollPeriod(aPeriod:Integer=-1):Integer;
  class function MonitorDefViewEvents(aEvents:Integer=-1):Integer;
  class function MonitorDefLoopDelay(aDelay:Integer=-1):Integer;
 end;
 /////////////
 // TZmqSocket
 /////////////
 TZmqSocket = class(TZmqObject)
 private
  myParent        : TZmqManager;
  mySockRef       : TZmqSocketRef;
  mySockTyp       : Integer;
  myBindings      : LongString;
  myConnections   : LongString;
  mySubscriptions : LongString;
  myAskMeta       : TStringList;
  myAnsMeta       : TStringList;
  mySentCount     : Int64;
  mySentBytes     : Int64;
  myRecvCount     : Int64;
  myRecvBytes     : Int64;
  mySendMore      : Integer;
  myRecvMore      : Integer;
  myLinger        : Integer;
  myConnected     : Integer;
  myTrConnected   : Integer;
  myLastMonit     : QWord;
  myMonitorRef    : TZmqSocketRef;
  myMonitorPeriod : Integer;
  myMonitorEvents : Integer;
  myMonCloTimeout : Integer;
  myIoMode        : Integer;
  procedure CloseSocket;
  procedure CreateSocket;
  procedure CloseMonitor(WaitForEvents:Boolean);
  procedure CreateMonitor;
  procedure ConnectMonitor;
  function  GetHasMonitor:Boolean;
  function  GetMonitorRef:TZmqSocketRef;
  function  GetMonitorPollPeriod:Integer;
  procedure SetMonitorPollPeriod(aPeriod:Integer);
  function  GetMonitorViewEvents:Integer;
  procedure SetMonitorViewEvents(aEvents:Integer);
  function  GetMonitorRcvTimeout:Integer;
  procedure SetMonitorRcvTimeout(aTimeout:Integer);
  function  GetMonitorCloTimeout:Integer;
  procedure SetMonitorCloTimeout(aTimeout:Integer);
  function  GetLinger:Integer;
  procedure SetLinger(aLinger:Integer);
  function  GetIoMode:Integer;
  procedure SetIoMode(aMode:Integer);
  function  GetParent:TZmqManager;
  function  GetSockRef:TZmqSocketRef;
  function  GetSockTyp:Integer;
  function  GetSockOpt(opt:Integer):LongString;
  procedure SetSockOpt(opt:Integer; const optVal:LongString);
  function  GetSockOptByName(const opt:LongString):LongString;
  procedure SetSockOptByName(const opt,optVal:LongString);
  function  GetAskMeta:LongString;
  procedure SetAskMeta(aMeta:LongString);
  function  GetAnsMeta:LongString;
  function  GetSockEvents:Integer;
  function  GetCanPollIn:Boolean;
  function  GetCanPollOut:Boolean;
  function  GetSentBytes:Int64;
  function  GetSentCount:Int64;
  function  GetRecvBytes:Int64;
  function  GetRecvCount:Int64;
  function  GetConnected:Integer;
  procedure UpdateConnected(aDelta:Integer);
 protected
  function  CheckOk:Boolean; override;
  function  AskErrno:Integer; override;
  function  AskStrError(n:Integer):LongString; override;
 public
  constructor Create(aParent:TZmqManager; aSockTyp:Integer);
  destructor  Destroy; override;
  procedure   AfterConstruction; override;
  procedure   BeforeDestruction; override;
 public
  function  UnBind:Integer;
  function  Disconnect:Integer;
  function  Bind(const aEndpoint:LongString):Boolean;
  function  Connect(const aEndpoint:LongString):Boolean;
  function  Subscribe(const aSubFilter:LongString):Boolean;
  function  Bindings(const Delim:LongString=','):LongString;
  function  Connections(const Delim:LongString=','):LongString;
  function  Subscriptions(const Delim:LongString=','):LongString;
 public
  function  Recv(Flags:Integer=ZMQ_DONTWAIT):LongString;
  function  Send(const S:LongString; Flags:Integer=ZMQ_DONTWAIT):Integer;
  function  SendMore(aMore:Integer=-1):Integer;
  function  RecvMore:Integer;
 public
  function  SockOptCan(opt:Integer; what:Char):Boolean; overload;
  function  SockOptCan(const opt:LongString; what:Char):Boolean; overload;
 public
  property  Parent                      : TZmqManager   read GetParent;
  property  Linger                      : Integer       read GetLinger        write SetLinger;
  property  IoMode                      : Integer       read GetIoMode        write SetIoMode;
  property  SockRef                     : TZmqSocketRef read GetSockRef;
  property  SockTyp                     : Integer       read GetSockTyp;
  property  SockOpt[n:Integer]          : LongString    read GetSockOpt       write SetSockOpt;
  property  SockOptByName[n:LongString] : LongString    read GetSockOptByName write SetSockOptByName;
  property  AskMeta                     : LongString    read GetAskMeta write SetAskMeta;
  property  AnsMeta                     : LongString    read GetAnsMeta;
  property  SockEvents                  : Integer       read GetSockEvents;
  property  CanPollIn                   : Boolean       read GetCanPollIn;
  property  CanPollOut                  : Boolean       read GetCanPollOut;
  property  SentBytes                   : Int64         read GetSentBytes;
  property  SentCount                   : Int64         read GetSentCount;
  property  RecvBytes                   : Int64         read GetRecvBytes;
  property  RecvCount                   : Int64         read GetRecvCount;
  property  Connected                   : Integer       read GetConnected;
  property  MonitorRef                  : TZmqSocketRef read GetMonitorRef;
  property  HasMonitor                  : Boolean       read GetHasMonitor;
  property  MonitorPollPeriod           : Integer       read GetMonitorPollPeriod write SetMonitorPollPeriod;
  property  MonitorViewEvents           : Integer       read GetMonitorViewEvents write SetMonitorViewEvents;
  property  MonitorRcvTimeout           : Integer       read GetMonitorRcvTimeout write SetMonitorRcvTimeout;
  property  MonitorCloTimeout           : Integer       read GetMonitorCloTimeout write SetMonitorCloTimeout;
 public
  function  GetSockOptsByNames(const opts:LongString; const Delim:LongString=EOL):LongString;
  function  SetSockOptsByNames(const opts:LongString; const Delim:LongString=EOL):LongString;
 public
  function  MonitorEndpoint:LongString;
  function  StartMonitor(aEndpoint:LongString; aEvents:Integer):Boolean;
  function  GetMonitorEvent(out aValue:Integer; out aAddress:LongString):Integer;
  function  HandleMonitorEventsWithTimeout(aEvents,aTimeout:Integer):Integer;
  function  HandleMonitorEvents(aPeriod,aEvents:Integer):Integer;
  procedure PollMonitorEvents;
 public
  function  Ctrl(const arg:LongString; const Delim:LongString=EOL; Sep:Char=#0):LongString;
 end;

function ZmqMan:TZmqManager;

procedure Kill(var TheObject:TZmqManager); overload;
procedure Kill(var TheObject:TZmqSocket); overload;

function dlc_ZmqLog:Integer;
function dlc_ZmqBug:Integer;
function dlc_ZmqMon:Integer;

implementation

 /////////////////////////////////////////////////////
 // Private Dictionary for fast string identification.
 /////////////////////////////////////////////////////
type
 TStringIdentifier = (
  sid_Unknown,
  //////////////////////
  sid_ValidApi,
  sid_LibZmqName,
  sid_VersionInt,
  sid_VersionStr,
  sid_BriefInfo,
  sid_CapsList,
  sid_CapsNames,
  sid_CtxOptNames,
  sid_SockOptNames,
  sid_SockTypNames,
  sid_Transports,
  sid_ZmqEngineNames,
  sid_Ctx,
  sid_Errno,
  sid_StrErrno,
  sid_StrError,
  sid_StrErrorMsg,
  sid_CtxOpt,
  sid_Bind,
  sid_Connect,
  sid_Subscribe,
  sid_Connected,
  sid_Linger,
  sid_IoMode,
  sid_SockRef,
  sid_SockTyp,
  sid_SockOpt,
  sid_AskMeta,
  sid_AnsMeta,
  sid_SockEvents,
  sid_CanPollIn,
  sid_CanPollOut,
  sid_SentBytes,
  sid_SentCount,
  sid_RecvBytes,
  sid_RecvCount,
  sid_SendMore,
  sid_RecvMore,
  sid_CurveKeyPair,
  sid_CurvePublic,
  sid_Z85Encode,
  sid_Z85Decode,
  sid_Functions,
  //////////////////////
  sid_Asterisk,
  sid_Question,
  sid_Unused
 );

const
 Dictionary:TCodeNameMap=nil;

procedure FreeDictionary;
begin
 Kill(Dictionary);
end;

procedure InitDictionary;
 procedure AddSid(const key:LongString; sid:TStringIdentifier; const par:LongString);
 begin
  Dictionary.AddMap(Ord(sid),key,par);
 end;
begin
 if (Dictionary<>nil) then Exit;
 Dictionary:=NewCodeNameMap(False);
 Dictionary.Master:=@Dictionary;
 /////////////////////////////////////////////
 // Dictionary for fast strings identification
 /////////////////////////////////////////////
 AddSid('ValidApi'                       , sid_ValidApi                       , 'opt=rwc;' );
 AddSid('LibZmqName'                     , sid_LibZmqName                     , 'opt=roc;' );
 AddSid('VersionInt'                     , sid_VersionInt                     , 'opt=roc;' );
 AddSid('VersionStr'                     , sid_VersionStr                     , 'opt=roc;' );
 AddSid('BriefInfo'                      , sid_BriefInfo                      , 'opt=roc;' );
 AddSid('CapsList'                       , sid_CapsList                       , 'opt=roc;' );
 AddSid('CapsNames'                      , sid_CapsNames                      , 'opt=roc;' );
 AddSid('CtxOptNames'                    , sid_CtxOptNames                    , 'opt=roc;' );
 AddSid('SockOptNames'                   , sid_SockOptNames                   , 'opt=roc;' );
 AddSid('SockTypNames'                   , sid_SockTypNames                   , 'opt=roc;' );
 AddSid('Transports'                     , sid_Transports                     , 'opt=roc;' );
 AddSid('ZmqEngineNames'                 , sid_ZmqEngineNames                 , 'opt=roc;' );
 AddSid('Ctx'                            , sid_Ctx                            , 'opt=roc;' );
 AddSid('Bind'                           , sid_Bind                           , 'opt=rws;' );
 AddSid('Connect'                        , sid_Connect                        , 'opt=rws;' );
 AddSid('Subscribe'                      , sid_Subscribe                      , 'opt=rws;' );
 AddSid('Connected'                      , sid_Connected                      , 'opt=ros;' );
 AddSid('Linger'                         , sid_Linger                         , 'opt=rws;' );
 AddSid('IoMode'                         , sid_IoMode                         , 'opt=rws;' );
 AddSid('SockRef'                        , sid_SockRef                        , 'opt=ros;' );
 AddSid('SockTyp'                        , sid_SockTyp                        , 'opt=ros;' );
 AddSid('SockOpt'                        , sid_SockOpt                        , 'opt=rws;' );
 AddSid('AskMeta'                        , sid_AskMeta                        , 'opt=rws;' );
 AddSid('AnsMeta'                        , sid_AnsMeta                        , 'opt=ros;' );
 AddSid('SockEvents'                     , sid_SockEvents                     , 'opt=ros;' );
 AddSid('CanPollIn'                      , sid_CanPollIn                      , 'opt=ros;' );
 AddSid('CanPollOut'                     , sid_CanPollOut                     , 'opt=ros;' );
 AddSid('SentBytes'                      , sid_SentBytes                      , 'opt=ros;' );
 AddSid('SentCount'                      , sid_SentCount                      , 'opt=ros;' );
 AddSid('RecvBytes'                      , sid_RecvBytes                      , 'opt=ros;' );
 AddSid('RecvCount'                      , sid_RecvCount                      , 'opt=ros;' );
 AddSid('SendMore'                       , sid_SendMore                       , 'opt=rws;' );
 AddSid('RecvMore'                       , sid_RecvMore                       , 'opt=ros;' );
 AddSid('CurveKeyPair'                   , sid_CurveKeyPair                   , 'opt=foc;' );
 AddSid('CurvePublic'                    , sid_CurvePublic                    , 'opt=foc;' );
 AddSid('Z85Encode'                      , sid_Z85Encode                      , 'opt=foc;' );
 AddSid('Z85Decode'                      , sid_Z85Decode                      , 'opt=foc;' );
 AddSid('Functions'                      , sid_Functions                      , 'opt=roc;' );
 AddSid('Errno'                          , sid_Errno                          , 'opt=roc;' );
 AddSid('StrErrno'                       , sid_StrErrno                       , 'opt=rwc;' );
 AddSid('StrError'                       , sid_StrError                       , 'opt=rwc;' );
 AddSid('StrErrorMsg'                    , sid_StrErrorMsg                    , 'opt=rwc;' );
 AddSid('CtxOpt'                         , sid_CtxOpt                         , 'opt=rwc;' );
 AddSid( '*'                             , sid_Asterisk                       , 'opt=oon;' );
 AddSid( '?'                             , sid_Question                       , 'opt=oon;' );
end;

function Identify(const key:LongString):TStringIdentifier;
var sid:Integer;
begin
 if (Dictionary=nil) then InitDictionary;
 sid:=Dictionary.CodeByName(key);
 if InRange(sid,Ord(Low(TStringIdentifier)),Ord(High(TStringIdentifier)))
 then Result:=TStringIdentifier(sid)
 else Result:=sid_Unknown;
end;

//////////////////
// Logger routines
//////////////////

function dlc_ZmqLog:Integer;
const dlc:Integer=0;
begin
 if (dlc=0) then dlc:=RegisterDebugLogChannel('_ZmqLog');
 Result:=dlc;
end;

function dlc_ZmqBug:Integer;
const dlc:Integer=0;
begin
 if (dlc=0) then dlc:=RegisterDebugLogChannel('_ZmqBug');
 Result:=dlc;
end;

function dlc_ZmqMon:Integer;
const dlc:Integer=0;
begin
 if (dlc=0) then dlc:=RegisterDebugLogChannel('_ZmqMon');
 Result:=dlc;
end;

////////////////////////////
// TZmqObject implementation
////////////////////////////

function TZmqObject.ZCall(Ret:Integer; const Who:LongString):Integer;
var dlc,Err:Integer;
begin
 Result:=Ret;
 if Assigned(Self) and (who<>'') then begin
  if RetOk(Ret) then Err:=0 else Err:=AskErrno;
  if RetOk(Ret) then dlc:=dlc_ZmqLog else dlc:=dlc_ZmqBug;
  if DebugLogEnabled(dlc) then DebugLog(dlc,FormatRet(Ret,Err,Who));
 end;
end;

function TZmqObject.NeedLog:Boolean;
begin
 Result:=DebugLogEnabled(dlc_ZmqLog) or DebugLogEnabled(dlc_ZmqBug);
end;

function TZmqObject.FormatRet(Ret,Err:Integer; const Who:LongString):LongString;
begin
 Result:='';
 if Assigned(Self) then begin
  Result:=Format('%s ret %d errno %d %s "%s".',[Who,Ret,Err,StrErrno(Err),AskStrError(Err)]);
 end;
end;

function TZmqObject.AskErrno:Integer;
begin
 Result:=0;
end;

function TZmqObject.AskStrError(n:Integer):LongString;
begin
 Result:='';
end;

class function TZmqObject.RetOk(Ret:Integer):Boolean;
begin
 Result:=(Ret<>EFAIL);
end;

class function TZmqObject.RefRet(aRef:Pointer):Integer;
begin
 Result:=IfThen(Assigned(aRef),EOK,EFAIL);
end;

class function TZmqObject.ValidApi(aCheck:Boolean=False):Boolean;
begin
 if aCheck then CheckLibZmqDll;
 Result:=ValidLibZmq;
end;

class function TZmqObject.StrErrno(n:Integer):LongString;
begin
 Result:='';
 CheckMappers;
 Result:=TrimDef(ErrnoMap.NameByCode(n),'EUNKNOWN');
end;

class function TZmqObject.ErrnoMap:TCodeNameMap;
var i:Integer;
begin
 if not Assigned(TheErrnoMap) then begin
  TheErrnoMap:=NewCodeNameMap;
  TheErrnoMap.Master:=@TheErrnoMap;
  with TheErrnoMap do begin
   // On Windows platform some of the standard POSIX errnos are not defined.
   AddMap(ENOTSUP          , 'ENOTSUP'         );
   AddMap(EPROTONOSUPPORT  , 'EPROTONOSUPPORT' );
   AddMap(ENOBUFS          , 'ENOBUFS'         );
   AddMap(ENETDOWN         , 'ENETDOWN'        );
   AddMap(EADDRINUSE       , 'EADDRINUSE'      );
   AddMap(EADDRNOTAVAIL    , 'EADDRNOTAVAIL'   );
   AddMap(ECONNREFUSED     , 'ECONNREFUSED'    );
   AddMap(EINPROGRESS      , 'EINPROGRESS'     );
   AddMap(ENOTSOCK         , 'ENOTSOCK'        );
   AddMap(EMSGSIZE         , 'EMSGSIZE'        );
   AddMap(EAFNOSUPPORT     , 'EAFNOSUPPORT'    );
   AddMap(ENETUNREACH      , 'ENETUNREACH'     );
   AddMap(ECONNABORTED     , 'ECONNABORTED'    );
   AddMap(ECONNRESET       , 'ECONNRESET'      );
   AddMap(ENOTCONN         , 'ENOTCONN'        );
   AddMap(ETIMEDOUT        , 'ETIMEDOUT'       );
   AddMap(EHOSTUNREACH     , 'EHOSTUNREACH'    );
   AddMap(ENETRESET        , 'ENETRESET'       );
   // Native 0MQ error codes.
   AddMap(EFSM             , 'EFSM'            );
   AddMap(ENOCOMPATPROTO   , 'ENOCOMPATPROTO'  );
   AddMap(ETERM            , 'ETERM'           );
   AddMap(EMTHREAD         , 'EMTHREAD'        );
   // POSIX errno (error codes)
   for i:=Low(TErrnoCodes) to High(TErrnoCodes)
   do if not HasCode(i) then AddMap(i,ErrnoToString(i));
  end;
 end;
 Result:=TheErrnoMap;
end;

class function TZmqObject.CtxOptMap:TCodeNameMap;
begin
 if not Assigned(TheCtxOptMap) then begin
  TheCtxOptMap:=NewCodeNameMap;
  TheCtxOptMap.Master:=@TheCtxOptMap;
  with TheCtxOptMap do begin
   // Context options defined in SockOpt
   AddMap(ZMQ_BLOCKY                     , 'ZMQ_BLOCKY'                     , 'opt=rws;' );
   AddMap(ZMQ_IPV6                       , 'ZMQ_IPV6'                       , 'opt=rws;' );
   // Context options
   AddMap(ZMQ_IO_THREADS                 , 'ZMQ_IO_THREADS'                 , 'opt=rwc;' );
   AddMap(ZMQ_MAX_SOCKETS                , 'ZMQ_MAX_SOCKETS'                , 'opt=rwc;' );
   AddMap(ZMQ_SOCKET_LIMIT               , 'ZMQ_SOCKET_LIMIT'               , 'opt=roc;' );
   AddMap(ZMQ_THREAD_PRIORITY            , 'ZMQ_THREAD_PRIORITY'            , 'opt=owc;' );
   AddMap(ZMQ_THREAD_SCHED_POLICY        , 'ZMQ_THREAD_SCHED_POLICY'        , 'opt=rwc;' );
   AddMap(ZMQ_MAX_MSGSZ                  , 'ZMQ_MAX_MSGSZ'                  , 'opt=rwc;' );
   AddMap(ZMQ_MSG_T_SIZE                 , 'ZMQ_MSG_T_SIZE'                 , 'opt=roc;' );
   AddMap(ZMQ_THREAD_AFFINITY_CPU_ADD    , 'ZMQ_THREAD_AFFINITY_CPU_ADD'    , 'opt=owc;' );
   AddMap(ZMQ_THREAD_AFFINITY_CPU_REMOVE , 'ZMQ_THREAD_AFFINITY_CPU_REMOVE' , 'opt=owc;' );
   AddMap(ZMQ_THREAD_NAME_PREFIX         , 'ZMQ_THREAD_NAME_PREFIX'         , 'opt=rwc;' );
   {$IFDEF ZMQ_BUILD_DRAFT_API}
   // DRAFT Context options
   AddMap(ZMQ_ZERO_COPY_RECV             , 'ZMQ_ZERO_COPY_RECV'             , 'opt=rwc;' );
   {$ENDIF}
  end;
 end;
 Result:=TheCtxOptMap;
end;

// get/set types: 'no'=none, 'i4'=cint, 'i8'=cint64, 'u8'=cuint64,
// 'cs'=c_string, 'bd'=bin_data, 'bz'=binary_data(32)_or_Z85_text_string(41),
// 'su'=subscribe, 'gd'=gid_t, 'pd'=pid_t, 'ud'=uid_t, 'fd'=zmq_fd_t
class function TZmqObject.ListAllSockOptDataTypes(Delim:LongString=EOL):LongString;
const sTypes='no'+EOL+'i4'+EOL+'i8'+EOL+'u8'+EOL+'cs'+EOL+'bd'+EOL
            +'bz'+EOL+'su'+EOL+'gd'+EOL+'pd'+EOL+'ud'+EOL+'fd'+EOL;
begin
 Result:=TextToList(sTypes,Delim);
end;

class function TZmqObject.ValidSockOptDataType(S:LongString):Boolean;
begin
 Result:=False;
 if (S='') or (Length(S)<>2) then Exit;
 if not IsLexeme(S,lex_Name) then Exit;
 Result:=(Pos(S,ListAllSockOptDataTypes)>0);
end;

class function TZmqObject.ListSockOptNames(Delim:LongString=EOL; Filter:LongString=''):LongString;
var i,sc:Integer; sn:LongString;
 function Match(const sn:LongString; sc:Integer; Filter:LongString):Boolean;
 var sr,sw:LongString;
 begin
  Result:=False;
  if (Filter='') then Exit(True);
  sr:=AskSockOptTyp(sc,'r'); sw:=AskSockOptTyp(sc,'w');
  if HasChars(Filter,['n']) and (sr='no') and (sw='no') then Exit(True);
  if HasChars(Filter,['e']) and not ValidSockOptDataType(sr) then Exit(True);
  if HasChars(Filter,['e']) and not ValidSockOptDataType(sw) then Exit(True);
  if HasChars(Filter,['r','i','g']) and ValidSockOptDataType(sr) and (sr<>'no') then Exit(True);
  if HasChars(Filter,['w','o','s']) and ValidSockOptDataType(sw) and (sw<>'no') then Exit(True);
 end;
begin
 Result:='';
 Filter:=LoCaseStr(Filter);
 for i:=0 to SockOptMap.Count-1 do begin
  sn:=SockOptMap.Names[i];
  sc:=SockOptMap.Codes[i];
  if Match(sn,sc,Filter)
  then Result+=(sn+EOL);
 end;
 Result:=TextToList(Result,Delim);
end;

class function TZmqObject.SockOptMap:TCodeNameMap;
var i:Integer; sg,ss:LongString;
begin
 if not Assigned(TheSockOptMap) then begin
  TheSockOptMap:=NewCodeNameMap;
  TheSockOptMap.Master:=@TheSockOptMap;
  with TheSockOptMap do begin
   // Socket options
   AddMap(ZMQ_AFFINITY                          , 'ZMQ_AFFINITY'                          , 'get=u8;set=u8;' );
   AddMap(ZMQ_ROUTING_ID                        , 'ZMQ_ROUTING_ID'                        , 'get=bd;set=bd;' );
   AddMap(ZMQ_SUBSCRIBE                         , 'ZMQ_SUBSCRIBE'                         , 'get=no;set=su;' );
   AddMap(ZMQ_UNSUBSCRIBE                       , 'ZMQ_UNSUBSCRIBE'                       , 'get=no;set=su;' );
   AddMap(ZMQ_RATE                              , 'ZMQ_RATE'                              , 'get=i4;set=i4;' );
   AddMap(ZMQ_RECOVERY_IVL                      , 'ZMQ_RECOVERY_IVL'                      , 'get=i4;set=i4;' );
   AddMap(ZMQ_SNDBUF                            , 'ZMQ_SNDBUF'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_RCVBUF                            , 'ZMQ_RCVBUF'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_RCVMORE                           , 'ZMQ_RCVMORE'                           , 'get=i4;set=no;' );
   AddMap(ZMQ_FD                                , 'ZMQ_FD'                                , 'get=fd;set=fd;' );
   AddMap(ZMQ_EVENTS                            , 'ZMQ_EVENTS'                            , 'get=i4;set=no;' );
   AddMap(ZMQ_TYPE                              , 'ZMQ_TYPE'                              , 'get=i4;set=no;' );
   AddMap(ZMQ_LINGER                            , 'ZMQ_LINGER'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_RECONNECT_IVL                     , 'ZMQ_RECONNECT_IVL'                     , 'get=i4;set=i4;' );
   AddMap(ZMQ_BACKLOG                           , 'ZMQ_BACKLOG'                           , 'get=i4;set=i4;' );
   AddMap(ZMQ_RECONNECT_IVL_MAX                 , 'ZMQ_RECONNECT_IVL_MAX'                 , 'get=i4;set=i4;' );
   AddMap(ZMQ_MAXMSGSIZE                        , 'ZMQ_MAXMSGSIZE'                        , 'get=i8;set=i8;' );
   AddMap(ZMQ_SNDHWM                            , 'ZMQ_SNDHWM'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_RCVHWM                            , 'ZMQ_RCVHWM'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_MULTICAST_HOPS                    , 'ZMQ_MULTICAST_HOPS'                    , 'get=i4;set=i4;' );
   AddMap(ZMQ_RCVTIMEO                          , 'ZMQ_RCVTIMEO'                          , 'get=i4;set=i4;' );
   AddMap(ZMQ_SNDTIMEO                          , 'ZMQ_SNDTIMEO'                          , 'get=i4;set=i4;' );
   AddMap(ZMQ_LAST_ENDPOINT                     , 'ZMQ_LAST_ENDPOINT'                     , 'get=cs;set=no;' );
   AddMap(ZMQ_ROUTER_MANDATORY                  , 'ZMQ_ROUTER_MANDATORY'                  , 'get=no;set=i4;' );
   AddMap(ZMQ_TCP_KEEPALIVE                     , 'ZMQ_TCP_KEEPALIVE'                     , 'get=i4;set=i4;' );
   AddMap(ZMQ_TCP_KEEPALIVE_CNT                 , 'ZMQ_TCP_KEEPALIVE_CNT'                 , 'get=i4;set=i4;' );
   AddMap(ZMQ_TCP_KEEPALIVE_IDLE                , 'ZMQ_TCP_KEEPALIVE_IDLE'                , 'get=i4;set=i4;' );
   AddMap(ZMQ_TCP_KEEPALIVE_INTVL               , 'ZMQ_TCP_KEEPALIVE_INTVL'               , 'get=i4;set=i4;' );
   AddMap(ZMQ_IMMEDIATE                         , 'ZMQ_IMMEDIATE'                         , 'get=i4;set=i4;' );
   AddMap(ZMQ_XPUB_VERBOSE                      , 'ZMQ_XPUB_VERBOSE'                      , 'get=no;set=i4;' );
   AddMap(ZMQ_ROUTER_RAW                        , 'ZMQ_ROUTER_RAW'                        , 'get=no;set=i4;' );
   AddMap(ZMQ_IPV6                              , 'ZMQ_IPV6'                              , 'get=i4;set=i4;' );
   AddMap(ZMQ_MECHANISM                         , 'ZMQ_MECHANISM'                         , 'get=i4;set=no;' );
   AddMap(ZMQ_PLAIN_SERVER                      , 'ZMQ_PLAIN_SERVER'                      , 'get=i4;set=i4;' );
   AddMap(ZMQ_PLAIN_USERNAME                    , 'ZMQ_PLAIN_USERNAME'                    , 'get=cs;set=cs;' );
   AddMap(ZMQ_PLAIN_PASSWORD                    , 'ZMQ_PLAIN_PASSWORD'                    , 'get=cs;set=cs;' );
   AddMap(ZMQ_CURVE_SERVER                      , 'ZMQ_CURVE_SERVER'                      , 'get=no;set=i4;' );
   AddMap(ZMQ_CURVE_PUBLICKEY                   , 'ZMQ_CURVE_PUBLICKEY'                   , 'get=bz;set=bz;' );
   AddMap(ZMQ_CURVE_SECRETKEY                   , 'ZMQ_CURVE_SECRETKEY'                   , 'get=bz;set=bz;' );
   AddMap(ZMQ_CURVE_SERVERKEY                   , 'ZMQ_CURVE_SERVERKEY'                   , 'get=bz;set=no;' );
   AddMap(ZMQ_PROBE_ROUTER                      , 'ZMQ_PROBE_ROUTER'                      , 'get=no;set=i4;' );
   AddMap(ZMQ_REQ_CORRELATE                     , 'ZMQ_REQ_CORRELATE'                     , 'get=no;set=i4;' );
   AddMap(ZMQ_REQ_RELAXED                       , 'ZMQ_REQ_RELAXED'                       , 'get=no;set=i4;' );
   AddMap(ZMQ_CONFLATE                          , 'ZMQ_CONFLATE'                          , 'get=no;set=i4;' );
   AddMap(ZMQ_ZAP_DOMAIN                        , 'ZMQ_ZAP_DOMAIN'                        , 'get=cs;set=cs;' );
   AddMap(ZMQ_ROUTER_HANDOVER                   , 'ZMQ_ROUTER_HANDOVER'                   , 'get=no;set=i4;' );
   AddMap(ZMQ_TOS                               , 'ZMQ_TOS'                               , 'get=i4;set=i4;' );
   AddMap(ZMQ_CONNECT_ROUTING_ID                , 'ZMQ_CONNECT_ROUTING_ID'                , 'get=no;set=bd;' );
   AddMap(ZMQ_GSSAPI_SERVER                     , 'ZMQ_GSSAPI_SERVER'                     , 'get=i4;set=i4;' );
   AddMap(ZMQ_GSSAPI_PRINCIPAL                  , 'ZMQ_GSSAPI_PRINCIPAL'                  , 'get=cs;set=cs;' );
   AddMap(ZMQ_GSSAPI_SERVICE_PRINCIPAL          , 'ZMQ_GSSAPI_SERVICE_PRINCIPAL'          , 'get=cs;set=cs;' );
   AddMap(ZMQ_GSSAPI_PLAINTEXT                  , 'ZMQ_GSSAPI_PLAINTEXT'                  , 'get=i4;set=i4;' );
   AddMap(ZMQ_HANDSHAKE_IVL                     , 'ZMQ_HANDSHAKE_IVL'                     , 'get=i4;set=i4;' );
   AddMap(ZMQ_SOCKS_PROXY                       , 'ZMQ_SOCKS_PROXY'                       , 'get=cs;set=cs;' );
   AddMap(ZMQ_XPUB_NODROP                       , 'ZMQ_XPUB_NODROP'                       , 'get=no;set=i4;' );
   AddMap(ZMQ_BLOCKY                            , 'ZMQ_BLOCKY'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_XPUB_MANUAL                       , 'ZMQ_XPUB_MANUAL'                       , 'get=no;set=i4;' );
   AddMap(ZMQ_XPUB_WELCOME_MSG                  , 'ZMQ_XPUB_WELCOME_MSG'                  , 'get=no;set=bd;' );
   AddMap(ZMQ_STREAM_NOTIFY                     , 'ZMQ_STREAM_NOTIFY'                     , 'get=no;set=i4;' );
   AddMap(ZMQ_INVERT_MATCHING                   , 'ZMQ_INVERT_MATCHING'                   , 'get=i4;set=i4;' );
   AddMap(ZMQ_HEARTBEAT_IVL                     , 'ZMQ_HEARTBEAT_IVL'                     , 'get=no;set=i4;' );
   AddMap(ZMQ_HEARTBEAT_TTL                     , 'ZMQ_HEARTBEAT_TTL'                     , 'get=no;set=i4;' );
   AddMap(ZMQ_HEARTBEAT_TIMEOUT                 , 'ZMQ_HEARTBEAT_TIMEOUT'                 , 'get=no;set=i4;' );
   AddMap(ZMQ_XPUB_VERBOSER                     , 'ZMQ_XPUB_VERBOSER'                     , 'get=no;set=i4;' );
   AddMap(ZMQ_CONNECT_TIMEOUT                   , 'ZMQ_CONNECT_TIMEOUT'                   , 'get=i4;set=i4;' );
   AddMap(ZMQ_TCP_MAXRT                         , 'ZMQ_TCP_MAXRT'                         , 'get=i4;set=i4;' );
   AddMap(ZMQ_THREAD_SAFE                       , 'ZMQ_THREAD_SAFE'                       , 'get=i4;set=no;' );
   AddMap(ZMQ_MULTICAST_MAXTPDU                 , 'ZMQ_MULTICAST_MAXTPDU'                 , 'get=i4;set=i4;' );
   AddMap(ZMQ_VMCI_BUFFER_SIZE                  , 'ZMQ_VMCI_BUFFER_SIZE'                  , 'get=u8;set=u8;' );
   AddMap(ZMQ_VMCI_BUFFER_MIN_SIZE              , 'ZMQ_VMCI_BUFFER_MIN_SIZE'              , 'get=u8;set=u8;' );
   AddMap(ZMQ_VMCI_BUFFER_MAX_SIZE              , 'ZMQ_VMCI_BUFFER_MAX_SIZE'              , 'get=u8;set=u8;' );
   AddMap(ZMQ_VMCI_CONNECT_TIMEOUT              , 'ZMQ_VMCI_CONNECT_TIMEOUT'              , 'get=i4;set=i4;' );
   AddMap(ZMQ_USE_FD                            , 'ZMQ_USE_FD'                            , 'get=i4;set=i4;' );
   AddMap(ZMQ_GSSAPI_PRINCIPAL_NAMETYPE         , 'ZMQ_GSSAPI_PRINCIPAL_NAMETYPE'         , 'get=i4;set=i4;' );
   AddMap(ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE , 'ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE' , 'get=i4;set=i4;' );
   AddMap(ZMQ_BINDTODEVICE                      , 'ZMQ_BINDTODEVICE'                      , 'get=cs;set=cs;' );
   // Deprecated Socket options
   {$PUSH}
   {$WARN SYMBOL_DEPRECATED OFF}
   AddMap(ZMQ_TCP_ACCEPT_FILTER                 , 'ZMQ_TCP_ACCEPT_FILTER'                 , 'get=no;set=bd;' );
   AddMap(ZMQ_IPC_FILTER_PID                    , 'ZMQ_IPC_FILTER_PID'                    , 'get=no;set=pd;' );
   AddMap(ZMQ_IPC_FILTER_UID                    , 'ZMQ_IPC_FILTER_UID'                    , 'get=no;set=ud;' );
   AddMap(ZMQ_IPC_FILTER_GID                    , 'ZMQ_IPC_FILTER_GID'                    , 'get=no;set=gd;' );
   AddMap(ZMQ_IPV4ONLY                          , 'ZMQ_IPV4ONLY'                          , 'get=i4;set=i4;' );
   {$POP}
   {$IFDEF ZMQ_BUILD_DRAFT_API}
   // DRAFT Socket options
   AddMap(ZMQ_ZAP_ENFORCE_DOMAIN                , 'ZMQ_ZAP_ENFORCE_DOMAIN'                , 'get=i4;set=i4;' );
   AddMap(ZMQ_LOOPBACK_FASTPATH                 , 'ZMQ_LOOPBACK_FASTPATH'                 , 'get=no;set=no;' );
   AddMap(ZMQ_METADATA                          , 'ZMQ_METADATA'                          , 'get=no;set=cs;' );
   AddMap(ZMQ_MULTICAST_LOOP                    , 'ZMQ_MULTICAST_LOOP'                    , 'get=i4;set=i4;' );
   AddMap(ZMQ_ROUTER_NOTIFY                     , 'ZMQ_ROUTER_NOTIFY'                     , 'get=i4;set=i4;' );
   AddMap(ZMQ_XPUB_MANUAL_LAST_VALUE            , 'ZMQ_XPUB_MANUAL_LAST_VALUE'            , 'get=no;set=i4;' );
   AddMap(ZMQ_SOCKS_USERNAME                    , 'ZMQ_SOCKS_USERNAME'                    , 'get=no;set=cs;' );
   AddMap(ZMQ_SOCKS_PASSWORD                    , 'ZMQ_SOCKS_PASSWORD'                    , 'get=no;set=cs;' );
   AddMap(ZMQ_IN_BATCH_SIZE                     , 'ZMQ_IN_BATCH_SIZE'                     , 'get=i4;set=i4;' );
   AddMap(ZMQ_OUT_BATCH_SIZE                    , 'ZMQ_OUT_BATCH_SIZE'                    , 'get=i4;set=i4;' );
   AddMap(ZMQ_WSS_KEY_PEM                       , 'ZMQ_WSS_KEY_PEM'                       , 'get=no;set=no;' );
   AddMap(ZMQ_WSS_CERT_PEM                      , 'ZMQ_WSS_CERT_PEM'                      , 'get=no;set=no;' );
   AddMap(ZMQ_WSS_TRUST_PEM                     , 'ZMQ_WSS_TRUST_PEM'                     , 'get=no;set=no;' );
   AddMap(ZMQ_WSS_HOSTNAME                      , 'ZMQ_WSS_HOSTNAME'                      , 'get=no;set=no;' );
   AddMap(ZMQ_WSS_TRUST_SYSTEM                  , 'ZMQ_WSS_TRUST_SYSTEM'                  , 'get=no;set=no;' );
   AddMap(ZMQ_ONLY_FIRST_SUBSCRIBE              , 'ZMQ_ONLY_FIRST_SUBSCRIBE'              , 'get=no;set=i4;' );
   AddMap(ZMQ_RECONNECT_STOP                    , 'ZMQ_RECONNECT_STOP'                    , 'get=i4;set=i4;' );
   AddMap(ZMQ_HELLO_MSG                         , 'ZMQ_HELLO_MSG'                         , 'get=no;set=bd;' );
   AddMap(ZMQ_DISCONNECT_MSG                    , 'ZMQ_DISCONNECT_MSG'                    , 'get=no;set=bd;' );
   AddMap(ZMQ_PRIORITY                          , 'ZMQ_PRIORITY'                          , 'get=i4;set=i4;' );
   {$ENDIF}
  end;
  SafeFillChar(TheSockOptTypTab,SizeOf(TheSockOptTypTab),0);
  for i:=Low(TheSockOptTypTab) to High(TheSockOptTypTab) do begin
   if not TheSockOptMap.HasCode(i) then continue;
   sg:=Format('%2.2s',[CookieScan(TheSockOptMap.ParamByCode[i],'get',Ord(';'))]);
   ss:=Format('%2.2s',[CookieScan(TheSockOptMap.ParamByCode[i],'set',Ord(';'))]);
   if not ValidSockOptDataType(sg) then continue;
   if not ValidSockOptDataType(ss) then continue;
   TheSockOptTypTab[i]:=Dump2i(sg+ss);
  end;
 end;
 Result:=TheSockOptMap;
end;

class function TZmqObject.SockTypMap:TCodeNameMap;
begin
 if not Assigned(TheSockTypMap) then begin
  TheSockTypMap:=NewCodeNameMap;
  TheSockTypMap.Master:=@TheSockTypMap;
  with TheSockTypMap do begin
   // Socket types
   AddMap(ZMQ_PAIR    , 'ZMQ_PAIR'    );
   AddMap(ZMQ_PUB     , 'ZMQ_PUB'     );
   AddMap(ZMQ_SUB     , 'ZMQ_SUB'     );
   AddMap(ZMQ_REQ     , 'ZMQ_REQ'     );
   AddMap(ZMQ_REP     , 'ZMQ_REP'     );
   AddMap(ZMQ_DEALER  , 'ZMQ_DEALER'  );
   AddMap(ZMQ_ROUTER  , 'ZMQ_ROUTER'  );
   AddMap(ZMQ_PULL    , 'ZMQ_PULL'    );
   AddMap(ZMQ_PUSH    , 'ZMQ_PUSH'    );
   AddMap(ZMQ_XPUB    , 'ZMQ_XPUB'    );
   AddMap(ZMQ_XSUB    , 'ZMQ_XSUB'    );
   AddMap(ZMQ_STREAM  , 'ZMQ_STREAM'  );
   {$IFDEF ZMQ_BUILD_DRAFT_API}
   // DRAFT Socket types.
   AddMap(ZMQ_SERVER  , 'ZMQ_SERVER'  );
   AddMap(ZMQ_CLIENT  , 'ZMQ_CLIENT'  );
   AddMap(ZMQ_RADIO   , 'ZMQ_RADIO'   );
   AddMap(ZMQ_DISH    , 'ZMQ_DISH'    );
   AddMap(ZMQ_GATHER  , 'ZMQ_GATHER'  );
   AddMap(ZMQ_SCATTER , 'ZMQ_SCATTER' );
   AddMap(ZMQ_DGRAM   , 'ZMQ_DGRAM'   );
   AddMap(ZMQ_PEER    , 'ZMQ_PEER'    );
   AddMap(ZMQ_CHANNEL , 'ZMQ_CHANNEL' );
   {$ENDIF}
  end;
 end;
 Result:=TheSockTypMap;
end;

class function TZmqObject.EventNameMap:TCodeNameMap;
begin
 if not Assigned(TheEventNameMap) then begin
  TheEventNameMap:=NewCodeNameMap;
  TheEventNameMap.Master:=@TheEventNameMap;
  with TheEventNameMap do begin
   // Socket transport events (TCP, IPC and TIPC only)
   AddMap(ZMQ_EVENT_CONNECTED                   , 'ZMQ_EVENT_CONNECTED'                  , 'hint=Socket successfully connected to remote peer.;');
   AddMap(ZMQ_EVENT_CONNECT_DELAYED             , 'ZMQ_EVENT_CONNECT_DELAYED'            , 'hint=Connect request on the socket is pending.;');
   AddMap(ZMQ_EVENT_CONNECT_RETRIED             , 'ZMQ_EVENT_CONNECT_RETRIED'            , 'hint=Connect request failed, now being retried.;');
   AddMap(ZMQ_EVENT_LISTENING                   , 'ZMQ_EVENT_LISTENING'                  , 'hint=Socket successfully bound to network interface.;');
   AddMap(ZMQ_EVENT_BIND_FAILED                 , 'ZMQ_EVENT_BIND_FAILED'                , 'hint=Socket could not bind to a given interface.;');
   AddMap(ZMQ_EVENT_ACCEPTED                    , 'ZMQ_EVENT_ACCEPTED'                   , 'hint=Socket accepted connection from remote peer.;');
   AddMap(ZMQ_EVENT_ACCEPT_FAILED               , 'ZMQ_EVENT_ACCEPT_FAILED'              , 'hint=Socket rejected connection from remote peer.;');
   AddMap(ZMQ_EVENT_CLOSED                      , 'ZMQ_EVENT_CLOSED'                     , 'hint=Socket was closed.;');
   AddMap(ZMQ_EVENT_CLOSE_FAILED                , 'ZMQ_EVENT_CLOSE_FAILED'               , 'hint=Socket close failed.;');
   AddMap(ZMQ_EVENT_DISCONNECTED                , 'ZMQ_EVENT_DISCONNECTED'               , 'hint=Socket disconnected unexpectedly.;');
   AddMap(ZMQ_EVENT_MONITOR_STOPPED             , 'ZMQ_EVENT_MONITOR_STOPPED'            , 'hint=Monitoring on this socket ended.;');
   AddMap(ZMQ_EVENT_ALL                         , 'ZMQ_EVENT_ALL'                        , 'hint=Mask for all events.;');
   AddMap(ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL  , 'ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL' , 'hint=Unspecified error during handshake.;');
   AddMap(ZMQ_EVENT_HANDSHAKE_SUCCEEDED         , 'ZMQ_EVENT_HANDSHAKE_SUCCEEDED'        , 'hint=ZMTP security handshake succeeded.;');
   AddMap(ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL   , 'ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL'  , 'hint=ZMTP security handshake failed due to protocol.;');
   AddMap(ZMQ_EVENT_HANDSHAKE_FAILED_AUTH       , 'ZMQ_EVENT_HANDSHAKE_FAILED_AUTH'      , 'hint=ZMTP security handshake failed due to authentication.;');
   {$IFDEF ZMQ_BUILD_DRAFT_API}
   AddMap(ZMQ_EVENT_PIPES_STATS                 , 'ZMQ_EVENT_PIPES_STATS'                , 'hint=;');
   AddMap(ZMQ_EVENT_ALL_V1                      , 'ZMQ_EVENT_ALL_V1'                     , 'hint=Mask for all events V1.;');
   AddMap(ZMQ_EVENT_ALL_V2                      , 'ZMQ_EVENT_ALL_V2'                     , 'hint=Mask for all events V2.;');
   {$ENDIF}
  end;
 end;
 Result:=TheEventNameMap;
end;

class function TZmqManager.CtxOptSet(const rw:LongString='rw'):TByteSet;
const rset:TByteSet=[]; wset:TByteSet=[];
var i,code:Integer; opt:LongString;
begin
 Result:=[];
 CheckMappers;
 if (rset=[]) or (wset=[]) then begin
  rset:=[]; wset:=[];
  for i:=0 to CtxOptMap.Count-1 do begin
   code:=CtxOptMap.Codes[i];
   opt:=CookieScan(CtxOptMap.ParamByCode[code],'opt',Ord(CookiesDelim));
   if HasChars(opt,['r']) then Include(rset,Byte(code));
   if HasChars(opt,['w']) then Include(wset,Byte(code));
  end;
 end;
 if HasChars(rw,['r','i','g','R','I','G']) then Result+=rset;
 if HasChars(rw,['w','o','s','W','O','S']) then Result+=wset;
end;

class function TZmqObject.AskSockOptTyp(Code:Integer; how:Char):LongString;
var Data:Integer; s:LongString;
begin
 Result:='';
 CheckMappers;
 if InRange(Code,Low(TheSockOptTypTab),High(TheSockOptTypTab)) then begin
  Data:=TheSockOptTypTab[Code];
  if (Data=0) then Exit;
  s:=Dump(Data);
  case LoCase(how) of
   'r','i','g': Result:=Copy(s,1,2); // read,input,get
   'w','o','s': Result:=Copy(s,3,2); // write,output,set
  end;
 end;
end;

class function TZmqObject.IsEventsPollIn(aEvents:Integer):Boolean;
begin
 Result:=HasFlags(aEvents,ZMQ_POLLIN);
end;

class function TZmqObject.IsEventsPollOut(aEvents:Integer):Boolean;
begin
 Result:=HasFlags(aEvents,ZMQ_POLLOUT);
end;

class function TZmqObject.AdaptEndpointName(const aEndpoint:LongString):LongString;
begin
 Result:=LoCaseStr(Trim(aEndpoint));
end;

class function TZmqObject.ValidEndpointName(const aEndpoint:LongString):Boolean;
const Alphabet=['a'..'z','A'..'Z','0'..'9','_','-','+','/','\',':','*','.','#','$','@','&','?','%'];
var ept,sn,sv:LongString; p:Integer;
begin
 Result:=False;
 if (aEndpoint='') then Exit;
 ept:=AdaptEndpointName(aEndpoint);
 if (WordCount(ept,ScanSpaces)<>1) then Exit;
 sn:=''; sv:=''; p:=ExtractNameValuePair(ept,sn,sv,':',0);
 if (p=0) or (WordIndex(sn,ListAllTransports,ScanSpaces)=0) then Exit;
 if HasChars(sv,[#0..#255]-Alphabet) or not StartsStr('//',sv) then Exit;
 Result:=True;
end;

class function TZmqObject.FetchEndpointTransport(const aEndpoint:LongString):LongString;
begin
 Result:=StrAheadOf(aEndpoint,':');
end;

class function TZmqObject.ConnectionOrientedTransports(const Delim:LongString=EOL):LongString;
begin
 Result:=TextToList('tcp'+EOL+'ipc'+EOL+'tipc'+EOL,Delim);
end;

class function TZmqObject.IsConnectionOriented(const aEndpoint:LongString):Boolean;
begin
 Result:=(WordIndex(FetchEndpointTransport(aEndpoint),ConnectionOrientedTransports,EolnDelims)>0);
end;

class function TZmqObject.NumConnectionOriented(const aEndpoints:LongString):Integer;
var i:Integer;
begin
 Result:=0;
 for i:=1 to WordCount(aEndpoints,ScanSpaces) do
 if ValidEndpointName(ExtractWord(i,aEndpoints,ScanSpaces)) then
 if IsConnectionOriented(ExtractWord(i,aEndpoints,ScanSpaces)) then Inc(Result);
end;

class function TZmqObject.AdaptSubFilter(const aSubFilter:LongString):LongString;
begin
 Result:=aSubFilter;
 if (Result='') then Result:='*';
end;

class function TZmqObject.ValidSubFilter(const aSubFilter:LongString):Boolean;
var sub:LongString;
begin
 Result:=False;
 if (aSubFilter='') then Exit;
 sub:=AdaptSubFilter(aSubFilter);
 if IsEmptyStr(sub) then Exit;
 Result:=True;
end;

class function TZmqObject.ListAllTransports(const Delim:LongString=EOL):LongString;
const Transports='tcp'+EOL+'pgm'+EOL+'epgm'+EOL+'ipc'+EOL+'inproc'+EOL+'vmci'+EOL+'udp'+EOL;
begin
 Result:=TextToList(Transports,Delim);
end;

class function TZmqObject.ValidEvent(aEvent:Integer):Boolean;
begin
 Result:=((aEvent and ZMQ_EVENT_ALL)=aEvent) and (CalcNumberOfBits(aEvent)=1);
end;

class function TZmqObject.EnsureZmqPrefix(const S:LongString):LongString;
const Prefix='ZMQ_';
begin
 Result:=S; if (Result='') then Exit;
 if not StartsText(Prefix,Result) then Result:=Prefix+Result;
end;

class function TZmqObject.TextToList(const S,Delim:LongString):LongString;
begin
 if (Delim='') or (Delim=EOL) then Result:=S else
 Result:=ReplaceString(Trim(S),EOL,Delim,[rfReplaceAll]);
end;

class function TZmqObject.ListToText(const S,Delim:LongString):LongString;
begin
 if (Delim='') or (Delim=EOL) then Result:=S else
 Result:=ValidateEol(ReplaceString(Trim(S),Delim,EOL,[rfReplaceAll]),1);
end;

class function TZmqObject.CookiesDelims:TCharSet;
begin
 Result:=(EolnDelims+[';']);
end;

class function TZmqObject.CookiesDelim:Char;
begin
 Result:=';';
end;

class function TZmqObject.CookiesSep:Char;
begin
 Result:=',';
end;

class procedure TZmqObject.CheckMappers;
begin
 if (TheErrnoMap=nil)
 or (TheCtxOptMap=nil)
 or (TheSockOptMap=nil)
 or (TheSockTypMap=nil)
 or (TheEventNameMap=nil)
 then InitMappers;
end;

class procedure TZmqObject.InitMappers;
begin
 ErrnoMap.Ok;
 CtxOptMap.Ok;
 SockOptMap.Ok;
 SockTypMap.Ok;
 EventNameMap.Ok;
end;

class procedure TZmqObject.FreeMappers;
begin
 Kill(TheErrnoMap);
 Kill(TheCtxOptMap);
 Kill(TheSockOptMap);
 Kill(TheSockTypMap);
 Kill(TheEventNameMap);
end;

/////////////////////////////
// TZmqManager implementation
/////////////////////////////

constructor TZmqManager.Create(const aLib:LongString=LIB_ZMQ);
begin
 inherited Create;
 myLib:=AdaptDllFileName(aLib);
 mySockets:=TObjectStorage.Create(False);
 mySockets.Master:=@mySockets;
end;

destructor TZmqManager.Destroy;
begin
 Kill(mySockets);
 myCaps:='';
 myLib:='';
 inherited;
end;

procedure TZmqManager.AfterConstruction;
begin
 inherited;
 if not ValidLibZmq
 then LoadLibZmqDll(myLib);
 if ValidLibZmq then CtxNew;
 if ValidApi then ZeroBlocky;
end;

procedure TZmqManager.BeforeDestruction;
begin
 SocketsKillAll;
 CtxTerm;
 inherited;
end;

procedure TZmqManager.ZeroBlocky;
begin
 if Ok then
 try
  CheckLibZmqDll;
  if not ValidApi then Exit;
  // ZMQ_BLOCKY sets LINGER=0
  // for all uses sockets
  CtxOpt[ZMQ_BLOCKY]:=0;
 except
  on E:Exception do BugReport(E,Self,'ZeroBlocky');
 end;
end;

procedure TZmqManager.SocketsKillAll;
var i:Integer;
begin
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  if ValidApi then ZeroBlocky;
  for i:=SocketsCount-1 downto 0 do Sockets[i].Free;
 except
  on E:Exception do BugReport(E,Self,'SocketsKillAll');
 end;
end;

procedure TZmqManager.AddSocket(aSocket:TZmqSocket);
begin
 if Assigned(Self) and Assigned(aSocket)
 then mySockets.Add(aSocket);
end;

procedure TZmqManager.RemoveSocket(aSocket:TZmqSocket);
begin
 if Assigned(Self) and Assigned(aSocket)
 then mySockets.Remove(aSocket);
end;

function TZmqManager.GetSocketsCount:Integer;
begin
 if Assigned(Self)
 then Result:=mySockets.Count
 else Result:=0;
end;

function TZmqManager.GetSockets(i:Integer):TZmqSocket;
begin
 if Assigned(Self)
 then Result:=TZmqSocket(mySockets[i])
 else Result:=nil;
end;

procedure TZmqManager.CtxNew;
begin
 if Assigned(Self) then
 try
  myCtx:=zmq_ctx_new;
  ZCall(RefRet(myCtx),Format('zmq_ctx_new:0x%p',[myCtx]));
 except
  on E:Exception do BugReport(E,Self,'CtxNew');
 end;
end;

procedure TZmqManager.CtxTerm;
begin
 if Assigned(Self) then
 try
  if Assigned(myCtx) then begin
   ZCall(zmq_ctx_shutdown(myCtx),Format('zmq_ctx_shutdown(0x%p)',[myCtx]));
   ZCall(zmq_ctx_term(myCtx),Format('zmq_ctx_term(0x%p)',[myCtx]));
   myCtx:=nil;
  end;
 except
  on E:Exception do BugReport(E,Self,'CtxTerm');
 end;
end;

function TZmqManager.GetCtx:TZmqContext;
begin
 if Assigned(Self)
 then Result:=myCtx
 else Result:=nil;
end;

function TZmqManager.CheckOk:Boolean;
begin
 Result:=Assigned(myCtx);
end;

function TZmqManager.GetVersionInt:Integer;
var major,minor,patch:cint;
begin
 Result:=0;
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  if not ValidApi then Exit;
  zmq_version(@major,@minor,@patch);
  Result:=major*10000+minor*100+patch;
 except
  on E:Exception do BugReport(E,Self,'GetVersionInt');
 end;
end;

function TZmqManager.GetVersionStr:LongString;
var major,minor,patch:cint;
begin
 Result:='';
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  if not ValidApi then Exit;
  zmq_version(@major,@minor,@patch);
  Result:=Format('%u.%u.%u',[major,minor,patch]);
 except
  on E:Exception do BugReport(E,Self,'GetVersionStr');
 end;
end;

function TZmqManager.GetLibZmqName:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  Result:=LibZmqDllName;
 except
  on E:Exception do BugReport(E,Self,'GetLibZmqName');
 end;
end;

function TZmqManager.GetErrno:Integer;
begin
 Result:=EFAIL;
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  if not ValidApi then Exit;
  Result:=zmq_errno;
 except
  on E:Exception do BugReport(E,Self,'GetErrno');
 end;
end;

function TZmqManager.StrError(n:Integer):LongString;
const BadErr='Unknown error';
var s:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  if not ValidApi then Exit;
  Result:=StrPas(zmq_strerror(n));
  if SameText(Result,BadErr) then begin
   if InRange(n,Low(TErrnoCodes),High(TErrnoCodes))
   then s:=ErrnoToComment(n) else s:='';
   if (s<>'') then Result:=s;
  end;
 except
  on E:Exception do BugReport(E,Self,'StrError');
 end;
end;

function TZmqManager.StrErrorMsg(n:Integer):LongString;
begin
 if Assigned(Self)
 then Result:=Format('errno %d %s "%s"',[n,StrErrno(n),StrError(n)])
 else Result:='';
end;

function TZmqManager.AskErrno:Integer;
begin
 Result:=Errno;
end;

function TZmqManager.AskStrError(n:Integer):LongString;
begin
 Result:=StrError(n);
end;

function TZmqManager.HasCap(const aCap:LongString):Boolean;
begin
 Result:=False;
 if (aCap<>'') then
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  if not ValidApi then Exit;
  Result:=(zmq_has(PChar(aCap))=ZMQ_HAS_CAPABILITIES);
 except
  on E:Exception do BugReport(E,Self,'HasCap');
 end;
end;

function TZmqManager.CapsList(const Delim:LongString=EOL):LongString;
var i:Integer; item:LongString;
begin
 Result:='';
 if Assigned(Self) then
 try
  if (myCaps='') then begin
   for i:=1 to WordCount(CapsNames,ScanSpaces) do begin
    item:=ExtractWord(i,CapsNames,ScanSpaces);
    if HasCap(item) then myCaps+=(item+EOL);
   end;
  end;
  Result:=TextToList(myCaps,Delim);
 except
  on E:Exception do BugReport(E,Self,'CapsList');
 end;
end;

class function TZmqManager.CapsNames(const Delim:LongString=EOL):LongString;
begin
 Result:='ipc'+EOL+'pgm'+EOL+'tipc'+EOL+'norm'+EOL+'curve'+EOL+'gssapi'+EOL+'draft'+EOL;
 if (Delim<>'') and (Delim<>EOL) then Result:=TextToList(Result,Delim);
end;

function TZmqManager.BriefInfo:LongString;
var nsuc,nerr:Integer;
begin
 Result:='';
 if Assigned(Self) then
 try
  CheckLibZmqDll;
  nsuc:=WordCount(ListLibZmqApi(EOL,0),ScanSpaces);
  nerr:=WordCount(ListLibZmqApi(EOL,1),ScanSpaces);
  if ValidLibZmq then begin
   Result:='Valid '+LibZmqDllName(LIB_ZMQ)+' version '+VersionStr;
   Result+=', API Loaded:'+IntToStr(nsuc)+', Missed:'+IntToStr(nerr);
   Result+=', Caps: '+CapsList(',');
  end else begin
   Result:='Failed '+LibZmqDllName(LIB_ZMQ);
   Result+=', API Loaded:'+IntToStr(nsuc)+', Missed:'+IntToStr(nerr);
  end;
 except
  on E:Exception do BugReport(E,Self,'BriefInfo');
 end;
end;

function TZmqManager.GetCtxOpt(n:Integer):Integer;
begin
 Result:=0;
 if Ok then
 try
  if not (n in CtxOptSet('r')) then Exit; // Readable?
  Result:=zmq_ctx_get(myCtx,n);
  ZCall(RefRet(Ctx),Format('zmq_ctx_get(0x%p,%d):%d',[Ctx,n,Result]));
 except
  on E:Exception do BugReport(E,Self,'GetCtxOpt');
 end;
end;

procedure TZmqManager.SetCtxOpt(n:Integer; v:Integer);
begin
 if Ok then
 try
  if not (n in CtxOptSet('w')) then Exit; // Writeable?
  ZCall(zmq_ctx_set(Ctx,n,v),Format('zmq_ctx_set(0x%p,%d,%d)',[Ctx,n,v]));
 except
  on E:Exception do BugReport(E,Self,'SetCtxOpt');
 end;
end;

function TZmqManager.GetCtxOptsAsText:LongString;
var i,n,iv:Integer; List,sn:LongString;
begin
 Result:='';
 if Ok then
 try
  List:=CtxOptNames;
  for i:=1 to WordCount(List,ScanSpaces) do begin
   sn:=ExtractWord(i,List,ScanSpaces);
   n:=StringToCtxOptNum(sn);
   if (n=0) then continue;
   if not (n in CtxOptSet('r')) then continue;
   iv:=CtxOpt[n];
   Result:=Result+sn+'='+IntToStr(iv)+EOL;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetCtxOptsAsText');
 end;
end;

procedure TZmqManager.SetCtxOptsAsText(Lines:LongString);
var i,n,iv:Integer; line,sn,sv:LongString;
begin
 if Ok then
 try
  Lines:=ListToText(Lines,CookiesDelim);
  for i:=1 to WordCount(Lines,EolnDelims) do begin
   line:=ExtractWord(i,Lines,EolnDelims);
   if (ExtractNameValuePair(line,sn,sv)>0) then begin
    n:=StringToCtxOptNum(sn); if (n=0) then continue;
    if TryStrToInt(sv,iv) then CtxOpt[n]:=iv;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetCtxOptsAsText');
 end;
end;

function TZmqManager.GetCtxOptsAsList:LongString;
begin
 Result:=TextToList(CtxOptsAsText,CookiesDelim);
end;

function TZmqManager.Ctrl(const arg:LongString; const Delim:LongString=EOL; Sep:Char=#0):LongString;
var p,i,si,iv:Integer; sn,sv,par,opt:LongString;
begin
 Result:='?';
 if Assigned(Self) then
 try
  iv:=0;
  sn:=''; sv:=''; par:=''; opt:='';
  p:=ExtractNameValuePair(arg,sn,sv);
  if (Sep=#0) then Sep:=CookiesDelim;
  if (Sep=Delim) then Sep:=CookiesSep;
  case Identify(sn) of
   sid_ValidApi: begin
    if (p>0) and (StrToIntDef(sv,0)=1)
    then Result:=IntToStr(BoolToInt(ValidApi(True)))
    else Result:=IntToStr(BoolToInt(ValidApi));
   end;
   sid_LibZmqName: begin
    Result:=LibZmqName;
   end;
   sid_VersionInt: begin
    Result:=IntToStr(VersionInt);
   end;
   sid_VersionStr: begin
    Result:=VersionStr;
   end;
   sid_BriefInfo: begin
    Result:=BriefInfo;
   end;
   sid_CapsList: begin
    Result:=CapsList;
   end;
   sid_CapsNames: begin
    Result:=CapsNames(CookiesSep);
   end;
   sid_CtxOptNames: begin
    Result:=CtxOptNames(CookiesSep);
   end;
   sid_SockOptNames: begin
    Result:=ListSockOptNames(CookiesSep,'rw');
   end;
   sid_SockTypNames: begin
    Result:=TextToList(SockTypMap.NamesText,CookiesSep);
   end;
   sid_Transports: begin
    Result:=ListAllTransports(CookiesSep);
   end;
   sid_ZmqEngineNames: begin
    Result:=ZmqEngineNames(CookiesSep);
   end;
   sid_CurveKeyPair: begin
    Result:=CurveKeyPair;
   end;
   sid_CurvePublic: begin
    if (p>0) then Result:=CurvePublic(sv);
   end;
   sid_Z85Encode: begin
    if (p>0) then Result:=Z85Encode(StrAfterOf(arg,'='));
   end;
   sid_Z85Decode: begin
    if (p>0) then Result:=Z85Decode(StrAfterOf(arg,'='));
   end;
   sid_Functions: begin
    Result:='';
    if Assigned(Dictionary) then
    for i:=0 to Dictionary.Count-1 do begin
     si:=Dictionary.Codes[i]; par:=Dictionary.Names[i];
     if (si<=Ord(sid_Unknown)) or (si>=Ord(sid_Asterisk)) then continue;
     opt:=CookieScan(Dictionary.ParamByName[par],'opt',Ord(CookiesDelim));
     if not HasChars(opt,['c','s']) then continue; // context+socket
     if not HasChars(opt,['f']) then continue; // function
     Result+=(par+EOL);
    end;
   end;
   sid_Errno: begin
    Result:=IntToStr(Errno);
   end;
   sid_StrErrno: begin
    if (p<=0) then iv:=Errno else iv:=StrToIntDef(sv,MaxInt);
    Result:=StrErrno(iv);
   end;
   sid_StrError: begin
    if (p<=0) then iv:=Errno else iv:=StrToIntDef(sv,MaxInt);
    Result:=StrError(iv);
   end;
   sid_StrErrorMsg: begin
    if (p<=0) then iv:=Errno else iv:=StrToIntDef(sv,MaxInt);
    Result:=StrErrorMsg(iv);
   end;
   sid_Ctx: begin
    Result:=Format('0x%p',[Ctx]);
   end;
   sid_CtxOpt: begin
    if (p>0) and IsNonEmptyStr(sv) then CtxOptsAsList:=sv;
    Result:=TextToList(CtxOptsAsText,Sep);
   end;
   sid_Asterisk,
   sid_Question: begin
    Result:='';
    if Assigned(Dictionary) then
    for i:=0 to Dictionary.Count-1 do begin
     si:=Dictionary.Codes[i]; par:=Dictionary.Names[i];
     if (si<=Ord(sid_Unknown)) or (si>=Ord(sid_Asterisk)) then continue;
     opt:=CookieScan(Dictionary.ParamByName[par],'opt',Ord(CookiesDelim));
     if not HasChars(opt,['c']) then continue; // use only context items
     if (sn='*') and HasChars(opt,['r','w']) then Result+=(par+EOL);
     if (sn='?') and HasChars(opt,['r']) and (p=0) then Result+=(par+EOL);
     if (sn='?') and HasChars(opt,['r']) and (p>0) then Result+=(par+'='+Ctrl(par,CookiesDelim,Sep)+EOL);
    end;
   end;
  end;
  Result:=TextToList(Result,Delim);
  sn:=''; sv:='';
 except
  on E:Exception do BugReport(E,Self,'Ctrl');
 end;
end;

function TZmqManager.NewSocket(aSockTyp:Integer):TZmqSocket;
begin
 if Assigned(Self)
 then Result:=TZmqSocket.Create(Self,aSockTyp)
 else Result:=nil;
end;

function TZmqManager.CurveKeyPair(const Delim:LongString=' '):LongString;
var k1,k2:TParsingBuffer;
begin
 Result:='';
 if Ok then
 try
  k1:=Default(TParsingBuffer);
  k2:=Default(TParsingBuffer);
  if RetOk(zmq_curve_keypair(@k1,@k2))
  then Result:=StrPas(k1)+Delim+StrPas(k2);
 except
  on E:Exception do BugReport(E,Self,'CurveKeyPair');
 end;
end;

function TZmqManager.CurvePublic(const aSecrKey:LongString):LongString;
var k1,k2:TParsingBuffer; Len:Integer; Key:LongString;
begin
 Result:='';
 if Ok then
 try
  Key:=aSecrKey; Len:=Length(Key);
  if (Len=SecrKeyZ85Leng) then begin
   k1:=Default(TParsingBuffer);
   k2:=Default(TParsingBuffer);
   StrLCopy(k2,PChar(Key),Len);
   if RetOk(zmq_curve_public(@k1,@k2))
   then Result:=StrPas(k1);
  end;
 except
  on E:Exception do BugReport(E,Self,'CurvePublic');
 end;
end;

function TZmqManager.Z85Encode(const aData:LongString):LongString;
var Leng:Integer; Buff:LongString;
begin
 Result:='';
 if Ok then
 try
  Leng:=Length(aData);
  if (Leng>0) and ((Leng mod 4)=0) then begin
   Buff:=StringBuffer(Leng*2); // Need 1.25*Leng or more
   if Assigned(zmq_z85_encode(PChar(Buff),Pointer(aData),Leng))
   then Result:=StrPas(PChar(Buff));
  end;
 except
  on E:Exception do BugReport(E,Self,'Z85Encode');
 end;
end;

function TZmqManager.Z85Decode(const aText:LongString):LongString;
var Leng:Integer; Buff:LongString;
begin
 Result:='';
 if Ok then
 try
  Leng:=Length(aText);
  if (Leng>0) and ((Leng mod 5)=0) then begin
   Buff:=StringBuffer(Leng); // Need 0.8*Leng or more
   if Assigned(zmq_z85_decode(Pointer(Buff),PChar(aText)))
   then Result:=StrPas(PChar(Buff));
  end;
 except
  on E:Exception do BugReport(E,Self,'Z85Decode');
 end;
end;

function TZmqManager.StartProxy(aFrontend,aBackend,aCapture:TZmqSocket):Boolean;
var rc:Integer;Fr,Br,Cr:TZmqSocketRef;
begin
 Result:=False;
 if Ok then
 try
  Fr:=aFrontend.SockRef;
  Br:=aBackend.SockRef;
  Cr:=aCapture.SockRef;
  rc:=zmq_proxy(Fr,Br,Cr);
  if RetOk(rc) then Result:=True;
  if NeedLog then ZCall(rc,Format('StartProxy(0x%p,0x%p,0x%p)',[Fr,Br,Cr]));
 except
  on E:Exception do BugReport(E,Self,'StartProxy');
 end;
end;

function TZmqManager.FormatMonitorEvent(aEvent,aValue:Integer; const aAddress:LongString):LongString;
var eName,eHint:LongString;
begin
 Result:='';
 if Ok then
 try
  eName:=''; eHint:='';
  if ValidEvent(aEvent) then begin
   eHint:=CookieScan(EventNameMap.ParamByCode[aEvent],'Hint',Ord(';'));
   eName:=EventNameMap.NameByCode(aEvent);
  end;
  case aEvent of
   ZMQ_EVENT_CONNECTED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, FD:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_CONNECT_DELAYED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s',
                  [aEvent,eName,aAddress]);
   end;
   ZMQ_EVENT_CONNECT_RETRIED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, DelayMs:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_LISTENING: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, FD:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_BIND_FAILED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, %s',
                  [aEvent,eName,aAddress,StrErrorMsg(aValue)]);
   end;
   ZMQ_EVENT_ACCEPTED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, FD:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_ACCEPT_FAILED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, %s',
                  [aEvent,eName,aAddress,StrErrorMsg(aValue)]);
   end;
   ZMQ_EVENT_CLOSED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, FD:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_CLOSE_FAILED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, FD:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_DISCONNECTED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, FD:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_MONITOR_STOPPED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s',
                  [aEvent,eName,aAddress]);
   end;
   ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, %s',
                  [aEvent,eName,aAddress,StrErrorMsg(aValue)]);
   end;
   ZMQ_EVENT_HANDSHAKE_SUCCEEDED: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s',
                  [aEvent,eName,aAddress]);
   end;
   ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, Protocol:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
   ZMQ_EVENT_HANDSHAKE_FAILED_AUTH: begin
    Result:=Format('Event: 0x%4.4x %s, Endpoint: %s, Code:%d',
                  [aEvent,eName,aAddress,aValue]);
   end;
  end;
  if (Result<>'') and (eHint<>'') then Result+=', '+QArg(eHint);
 except
  on E:Exception do BugReport(E,Self,'FormatMonitorEvent');
 end;
end;

const
 TheSecrKeyBinLen=32;

class function TZmqManager.SecrKeyBinLeng:Integer;
begin
 Result:=TheSecrKeyBinLen;
end;

class function TZmqManager.SecrKeyZ85Leng:Integer;
begin
 Result:=(TheSecrKeyBinLen div 4)*5;
end;

class function TZmqManager.CtxOptNumToString(n:Integer):LongString;
var s:LongString;
begin
 Result:='';
 if (n in CtxOptSet) then begin
  s:=CtxOptMap.NameByCode(n);
  if (s<>'') then Exit(s);
  s:=SockOptMap.NameByCode(n);
  if (s<>'') then Exit(s);
 end;
end;

class function TZmqManager.StringToCtxOptNum(s:LongString):Integer;
var n:Integer;
begin
 Result:=0;
 n:=CtxOptMap.CodeByName(s);
 if (n in CtxOptSet) then Exit(n);
 n:=SockOptMap.CodeByName(s);
 if (n in CtxOptSet) then Exit(n);
end;

class function TZmqManager.CtxOptNames(const Delim:LongString=EOL):LongString;
var i:Integer; s:LongString;
begin
 Result:='';
 for i:=0 to High(Byte) do
 if (i in CtxOptSet) then begin
  s:=CtxOptMap.NameByCode(i);
  if (s='') then s:=SockOptMap.NameByCode(i);
  if (s<>'') then Result+=(s+EOL);
 end;
 if (Delim<>EOL) then Result:=TextToList(Result,Delim);
end;

class function TZmqManager.ZmqEngineNames(const Delim:LongString=EOL):LongString;
const Aliases='ZMQ'+EOL+'ZeroMQ'+EOL+'ZeroMessageQueue'+EOL
             +'OMQ'+EOL+'OpenMQ'+EOL+'OpenMessageQueue'+EOL
             +'0MQ'+EOL
             +'SMQ'+EOL
             +'SafeMQ'+EOL+'SafeMessageQueue'+EOL
             +'SuperMQ'+EOL+'SuperMessageQueue'+EOL
             +'SpeedMQ'+EOL+'SpeedMessageQueue'+EOL
             +'SimpleMQ'+EOL+'SimpleMessageQueue'+EOL
             +'StringMQ'+EOL+'StringMessageQueue'+EOL;
begin
 Result:=TextToList(Aliases,Delim);
end;

class function TZmqManager.IsZmqEngineName(const S:LongString):Boolean;
begin
 Result:=(WordIndex(S,ZmqEngineNames,ScanSpaces)>0);
end;

class function TZmqManager.AllButNoSpamMonitorEvents:Integer;
begin
 Result:=ZMQ_EVENT_ALL
 and not ZMQ_EVENT_CLOSED
 and not ZMQ_EVENT_CONNECT_DELAYED
 and not ZMQ_EVENT_CONNECT_RETRIED;
end;

class function TZmqManager.MonitorDefCloTimeout(aTimeout:Integer=-1):Integer;
const Timeout:Integer=200;
begin
 if InRange(aTimeout,0,MaxInt) then Timeout:=aTimeout;
 Result:=EnsureRange(Timeout,0,MaxInt);
end;

class function TZmqManager.MonitorDefRcvTimeout(aTimeout:Integer=-1):Integer;
const Timeout:Integer=50;
begin
 if InRange(aTimeout,0,MaxInt) then Timeout:=aTimeout;
 Result:=EnsureRange(Timeout,0,MaxInt);
end;

class function TZmqManager.MonitorDefPollPeriod(aPeriod:Integer=-1):Integer;
const Period:Integer=50;
begin
 if InRange(aPeriod,0,MaxInt) then Period:=aPeriod;
 Result:=EnsureRange(Period,0,MaxInt);
end;

class function TZmqManager.MonitorDefViewEvents(aEvents:Integer=-1):Integer;
const Events:Integer=-1;
begin
 if (Events=-1) then Events:=AllButNoSpamMonitorEvents;
 if InRange(aEvents,0,ZMQ_EVENT_ALL) then Events:=aEvents;
 Result:=EnsureRange(Events,0,ZMQ_EVENT_ALL);
end;

class function TZmqManager.MonitorDefLoopDelay(aDelay:Integer=-1):Integer;
const Delay:Integer=-1;
begin
 if (Delay=-1) then Delay:=DefaultOsTimeSlice;
 if InRange(aDelay,1,1000) then Delay:=aDelay;
 Result:=EnsureRange(Delay,1,1000);
end;

////////////////////////////
// TZmqSocket implementation
////////////////////////////

constructor TZmqSocket.Create(aParent:TZmqManager; aSockTyp:Integer);
begin
 inherited Create;
 myParent:=aParent;
 mySockTyp:=aSockTyp;
 myAskMeta:=TStringList.Create;
 myAnsMeta:=TStringList.Create;
 mySentBytes:=0; mySentCount:=0;
 myRecvBytes:=0; myRecvCount:=0;
 mySendMore:=0; myRecvMore:=0;
 myLinger:=0; myIoMode:=ZMQ_DONTWAIT;
 MonitorPollPeriod:=Parent.MonitorDefPollPeriod;
 MonitorViewEvents:=Parent.MonitorDefViewEvents;
end;

destructor TZmqSocket.Destroy;
begin
 Kill(myAskMeta);
 Kill(myAnsMeta);
 myBindings:='';
 myConnections:='';
 mySubscriptions:='';
 inherited;
end;

procedure TZmqSocket.AfterConstruction;
begin
 inherited;
 CreateSocket;
 Parent.AddSocket(Self);
 StartMonitor(MonitorEndpoint,ZMQ_EVENT_ALL);
 CreateMonitor;
 ConnectMonitor;
end;

procedure TZmqSocket.BeforeDestruction;
var WaitForEvents:Boolean;
begin
 WaitForEvents:=(NumConnectionOriented(Bindings(EOL)+Connections(EOL))>0);
 Parent.RemoveSocket(Self);
 CloseSocket;
 CloseMonitor(WaitForEvents);
 inherited;
end;

function TZmqSocket.CheckOk:Boolean;
begin
 Result:= Assigned(mySockRef);
end;

procedure TZmqSocket.CreateSocket;
begin
 if Assigned(Self) then
 if Parent.Ok then
 try
  if not Assigned(mySockRef) then begin
   mySockRef:=zmq_socket(Parent.Ctx,SockTyp);
   ZCall(RefRet(SockRef),Format('zmq_socket(0x%p,%d):0x%p',[Parent.Ctx,SockTyp,SockRef]));
  end;
 except
  on E:Exception do BugReport(E,Self,'CreateSocket');
 end;
end;

procedure TZmqSocket.CloseSocket;
begin
 if Assigned(Self) then
 try
  Unbind; Disconnect;
  if Assigned(SockRef) then begin
   SockOpt[ZMQ_LINGER]:=IntToStr(Linger);
   ZCall(zmq_close(SockRef),Format('zmq_close(0x%p)',[SockRef]));
   mySockRef:=nil;
  end;
 except
  on E:Exception do BugReport(E,Self,'CloseSocket');
 end;
end;

procedure TZmqSocket.CreateMonitor;
begin
 if Assigned(Self) then
 if Parent.Ok then
 try
  if not HasMonitor then begin
   myMonitorRef:=zmq_socket(Parent.Ctx,ZMQ_PAIR);
   ZCall(RefRet(MonitorRef),Format('zmq_socket(0x%p,%d):0x%p',[Parent.Ctx,ZMQ_PAIR,MonitorRef]));
  end;
  if HasMonitor then begin
   MonitorCloTimeout:=Parent.MonitorDefCloTimeout;
   MonitorRcvTimeout:=Parent.MonitorDefRcvTimeout;
  end;
 except
  on E:Exception do BugReport(E,Self,'CreateMonitor');
 end;
end;

procedure TZmqSocket.CloseMonitor(WaitForEvents:Boolean);
begin
 if Assigned(Self) then
 try
  if HasMonitor then begin
   if (WaitForEvents)
   then HandleMonitorEventsWithTimeout(MonitorViewEvents,MonitorCloTimeout);
   ZCall(zmq_close(MonitorRef),Format('zmq_close(0x%p)',[MonitorRef]));
   myMonitorRef:=nil;
  end;
 except
  on E:Exception do BugReport(E,Self,'CloseMonitor');
 end;
end;

procedure TZmqSocket.ConnectMonitor;
var ept,inf:LongString;
begin
 if Assigned(Self) then
 try
  inf:='';
  if not HasMonitor then CreateMonitor;
  ept:=AdaptEndpointName(MonitorEndpoint);
  if HasMonitor and ValidEndpointName(ept) then begin
   if NeedLog then inf:=Format('zmq_connect(0x%p,%s)',[MonitorRef,ept]);
   ZCall(zmq_connect(MonitorRef,PChar(ept)),inf);
  end;
 except
  on E:Exception do BugReport(E,Self,'ConnectMonitor');
 end;
end;

function TZmqSocket.GetMonitorRcvTimeout:Integer;
var rc,timout:cint; sz:csize_t; msg:LongString;
begin
 Result:=0;
 if Assigned(Self) then
 if Parent.Ok then
 try
  if HasMonitor then begin
   msg:=''; timout:=0; sz:=SizeOf(timout);
   rc:=zmq_getsockopt(MonitorRef,ZMQ_RCVTIMEO,@timout,@sz);
   if NeedLog then msg:=Format('zmq_getsockopt(0x%p,%d,%d)',[MonitorRef,ZMQ_RCVTIMEO,timout]);
   if RetOk(ZCall(rc,msg)) then Result:=timout;
  end;
 except
  on E:Exception do BugReport(E,Self,'GetMonitorTimeout');
 end;
end;

procedure TZmqSocket.SetMonitorRcvTimeout(aTimeOut:Integer);
var rc,timout:cint; sz:csize_t; msg:LongString;
begin
 if Assigned(Self) then
 if Parent.Ok then
 try
  if HasMonitor then begin
   msg:=''; timout:=aTimeOut; sz:=SizeOf(timout);
   rc:=zmq_setsockopt(MonitorRef,ZMQ_RCVTIMEO,@timout,sz);
   if NeedLog then msg:=Format('zmq_setsockopt(0x%p,%d,%d)',[MonitorRef,ZMQ_RCVTIMEO,timout]);
   ZCall(rc,msg);
  end;
 except
  on E:Exception do BugReport(E,Self,'SetMonitorTimeout');
 end;
end;

function TZmqSocket.GetMonitorCloTimeout:Integer;
begin
 if Assigned(Self)
 then Result:=myMonCloTimeout
 else Result:=0;
end;

procedure TZmqSocket.SetMonitorCloTimeout(aTimeOut:Integer);
begin
 if Assigned(Self)
 then myMonCloTimeout:=aTimeout;
end;

function TZmqSocket.GetParent:TZmqManager;
begin
 if Assigned(Self)
 then Result:=myParent
 else Result:=nil;
end;

function TZmqSocket.GetLinger:Integer;
begin
 if Assigned(Self)
 then Result:=myLinger
 else Result:=0;
end;

procedure TZmqSocket.SetLinger(aLinger:Integer);
begin
 if Assigned(Self)
 then myLinger:=aLinger;
end;

function TZmqSocket.GetIoMode:Integer;
begin
 if Assigned(Self)
 then Result:=myIoMode
 else Result:=0;
end;

procedure TZmqSocket.SetIoMode(aMode:Integer);
begin
 if Assigned(Self)
 then myIoMode:=aMode;
end;

function TZmqSocket.GetMonitorRef:TZmqSocketRef;
begin
 if Assigned(Self)
 then Result:=myMonitorRef
 else Result:=nil;
end;

function TZmqSocket.GetHasMonitor:Boolean;
begin
 if Assigned(Self)
 then Result:=Assigned(myMonitorRef)
 else Result:=False;
end;

function TZmqSocket.GetMonitorPollPeriod:Integer;
begin
 if Assigned(Self)
 then Result:=myMonitorPeriod
 else Result:=0;
end;

procedure TZmqSocket.SetMonitorPollPeriod(aPeriod:Integer);
begin
 if Assigned(Self)
 then myMonitorPeriod:=EnsureRange(aPeriod,0,MaxInt);
end;

function TZmqSocket.GetMonitorViewEvents:Integer;
begin
 if Assigned(Self)
 then Result:=myMonitorEvents
 else Result:=0;
end;

procedure TZmqSocket.SetMonitorViewEvents(aEvents:Integer);
begin
 if Assigned(Self)
 then myMonitorEvents:=(aEvents and ZMQ_EVENT_ALL);
end;

function TZmqSocket.Bindings(const Delim:LongString=','):LongString;
begin
 if Assigned(Self)
 then Result:=TextToList(myBindings,Delim)
 else Result:='';
end;

function TZmqSocket.Connections(const Delim:LongString=','):LongString;
begin
 if Assigned(Self)
 then Result:=TextToList(myConnections,Delim)
 else Result:='';
end;

function TZmqSocket.Subscriptions(const Delim:LongString=','):LongString;
begin
 if Assigned(Self)
 then Result:=TextToList(mySubscriptions,Delim)
 else Result:='';
end;

function TZmqSocket.GetConnected:Integer;
begin
 Result:=0;
 if Assigned(Self) then begin
  if HasMonitor then PollMonitorEvents;
  Result:=LockedGet(myConnected);
  Inc(Result,myTrConnected);
 end;
end;

procedure TZmqSocket.UpdateConnected(aDelta:Integer);
var i:Integer; lst,ept:LongString;
begin
 if Assigned(Self) then begin
  if (aDelta<>0) then begin
   LockedAdd(myConnected,aDelta);
  end else begin
   myTrConnected:=0;
   lst:=myBindings+myConnections;
   for i:=0 to WordCount(lst,EolnDelims) do begin
    ept:=ExtractWord(i,lst,EolnDelims);
    if not ValidEndpointName(ept) then continue;
    if IsConnectionOriented(ept) then continue;
    Inc(myTrConnected);
   end;
  end;
 end;
end;

function TZmqSocket.GetSockRef:TZmqSocketRef;
begin
 if Assigned(Self)
 then Result:=mySockRef
 else Result:=nil;
end;

function TZmqSocket.GetSockTyp:Integer;
begin
 if Assigned(Self)
 then Result:=mySockTyp
 else Result:=0;
end;

function TZmqSocket.AskErrno:Integer;
begin
 Result:=Parent.Errno;
end;

function TZmqSocket.AskStrError(n:Integer):LongString;
begin
 Result:=Parent.StrError(n);
end;

function TZmqSocket.Unbind:Integer;
var rc,i:Integer; ep:LongString;
begin
 Result:=0;
 if Ok then
 try
  if (Bindings<>'') then begin
   for i:=1 to WordCount(Bindings,ScanSpaces) do begin
    ep:=AdaptEndpointName(ExtractWord(i,Bindings,ScanSpaces));
    rc:=ZCall(zmq_unbind(SockRef,PChar(ep)),Format('zmq_unbind(0x%p,%s)',[SockRef,ep]));
    if RetOk(rc) then Inc(Result);
   end;
   myBindings:='';
   UpdateConnected(0);
  end;
 except
  on E:Exception do BugReport(E,Self,'Unbind');
 end;
end;

function TZmqSocket.Disconnect:Integer;
var rc,i:Integer; ep:LongString;
begin
 Result:=0;
 if Ok then
 try
  if (Connections<>'') then begin
   for i:=1 to WordCount(Connections,ScanSpaces) do begin
    ep:=AdaptEndpointName(ExtractWord(i,Connections,ScanSpaces));
    rc:=ZCall(zmq_disconnect(SockRef,PChar(ep)),Format('zmq_disconnect(0x%p,%s)',[SockRef,ep]));
    if RetOk(rc) then Inc(Result);
   end;
   myConnections:='';
   mySubscriptions:='';
   UpdateConnected(0);
  end;
 except
  on E:Exception do BugReport(E,Self,'Disconnect');
 end;
end;

function TZmqSocket.Bind(const aEndpoint:LongString):Boolean;
var rc:Integer; ep,ept:LongString;
begin
 Result:=False;
 if Ok and ValidEndpointName(aEndpoint) then
 try
  ep:=AdaptEndpointName(aEndpoint);
  rc:=ZCall(zmq_bind(SockRef,PChar(ep)),Format('zmq_bind(0x%p,%s)',[SockRef,ep]));
  if RetOk(rc) then begin
   ept:=TrimDef(SockOpt[ZMQ_LAST_ENDPOINT],ep);
   myBindings+=(ept+EOL);
   UpdateConnected(0);
   Result:=True;
  end;
 except
  on E:Exception do BugReport(E,Self,'Bind');
 end;
end;

function TZmqSocket.Connect(const aEndpoint:LongString):Boolean;
var rc:Integer; ep,ept:LongString;
begin
 Result:=False;
 if Ok and ValidEndpointName(aEndpoint) then
 try
  ep:=AdaptEndpointName(aEndpoint);
  rc:=ZCall(zmq_connect(SockRef,PChar(ep)),Format('zmq_connect(0x%p,%s)',[SockRef,ep]));
  if RetOk(rc) then begin
   ept:=TrimDef(SockOpt[ZMQ_LAST_ENDPOINT],ep);
   myConnections+=(ept+EOL);
   UpdateConnected(0);
   Result:=True;
  end;
 except
  on E:Exception do BugReport(E,Self,'Connect');
 end;
end;

function TZmqSocket.Subscribe(const aSubFilter:LongString):Boolean;
var sub:LongString;
begin
 Result:=False;
 if Ok and ValidSubFilter(aSubFilter) then
 try
  sub:=AdaptSubFilter(aSubFilter);
  SockOpt[ZMQ_SUBSCRIBE]:=sub;
  mySubscriptions+=(sub+EOL);
  Result:=True;
 except
  on E:Exception do BugReport(E,Self,'Subscribe');
 end;
end;

function TZmqSocket.send(const S:LongString; Flags:Integer=ZMQ_DONTWAIT):Integer;
var rc:Integer; Msg:zmq_msg_t; Buff,Data:Pointer; Size:SizeInt; who:LongString;
begin
 Result:=0;
 if Ok and (S<>'') then
 try
  if (SendMore=1) then begin
   LiftFlags(Flags,ZMQ_SNDMORE);
   SendMore(0);
  end;
  rc:=0;
  Buff:=Pointer(S); Size:=Length(S);
  if RetOk(zmq_msg_init_size(@Msg,Size)) then
  try
   Data:=zmq_msg_data(@Msg);
   if (Data<>nil) then begin
    SafeMove(Buff^,Data^,Size);
    if NeedLog then who:=Format('zmq_msg_send(0x%p,0x%p,%d,%d)',[@Msg,SockRef,Flags,Size]) else who:='';
    rc:=ZCall(zmq_msg_send(@Msg,SockRef,Flags),who);
    if RetOk(rc) then begin
     Inc(mySentBytes,Size);
     Inc(mySentCount);
    end;
    Result:=Max(0,rc);
   end;
  finally
   // NB: Close msg only if send failed
   if not RetOk(rc) then zmq_msg_close(@Msg);
  end;
  if HasMonitor then PollMonitorEvents;
 except
  on E:Exception do BugReport(E,Self,'send_string');
 end;
end;

function TZmqSocket.recv(Flags:Integer=ZMQ_DONTWAIT):LongString;
var Msg:zmq_msg_t; Data:Pointer; Size,i:Integer; who:LongString;
begin
 Result:='';
 if Ok then
 try
  myRecvMore:=0; who:='';
  if RetOk(zmq_msg_init(@Msg)) then
  try
   Size:=zmq_msg_recv(@Msg,SockRef,Flags);
   if NeedLog then begin
    who:=Format('zmq_msg_recv(0x%p,0x%p,%d):%d',[@Msg,SockRef,Flags,Size]);
    ZCall(Size,who);
   end;
   Data:=zmq_msg_data(@Msg);
   myRecvMore:=zmq_msg_more(@Msg);
   if (Size>0) and (Data<>nil) then begin
    Result:=StringBuffer(Data,Size);
    Inc(myRecvBytes,Size);
    Inc(myRecvCount);
   end;
   if (Result<>'') and (myAskMeta.Count>0) then begin
    myAnsMeta.Clear;
    for i:=0 to myAskMeta.Count-1 do begin
     who:=Trim(myAskMeta.Strings[i]); if (who='') then continue;
     myAnsMeta.AddPair(who,StrPas(zmq_msg_gets(@msg,PChar(who))));
    end;
   end;
  finally
   zmq_msg_close(@Msg);
  end;
  if HasMonitor then PollMonitorEvents;
 except
  on E:Exception do BugReport(E,Self,'zmq_send_string');
 end;
end;

function TZmqSocket.SendMore(aMore:Integer=-1):Integer;
begin
 Result:=0;
 if Assigned(Self) then begin
  if InRange(aMore,0,1)
  then mySendMore:=aMore;
  Result:=mySendMore;
 end;
end;

function TZmqSocket.RecvMore:Integer;
begin
 if Assigned(Self)
 then Result:=myRecvMore
 else Result:=0;
end;

function TZmqSocket.GetAskMeta:LongString;
begin
 if Assigned(Self) then
 try
  Result:=TextToList(myAskMeta.Text,CookiesSep);
 except
  on E:Exception do BugReport(E,Self,'GetAskMeta');
 end;
end;

procedure TZmqSocket.SetAskMeta(aMeta:LongString);
var i:Integer; line:LongString;
begin
 if Assigned(Self) then
 try
  if IsNonEmptyStr(aMeta) then begin
   aMeta:=ListToText(aMeta,CookiesSep);
   aMeta:=ListToText(aMeta,CookiesDelim);
   myAskMeta.Text:=Trim(aMeta);
   for i:=myAskMeta.Count-1 downto 0 do begin
    line:=Trim(myAskMeta.Strings[i]);
    if (line<>myAskMeta.Strings[i])
    then myAskMeta.Strings[i]:=line;
    if (line='') then myAskMeta.Delete(i);
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'SetAskMeta');
 end;
end;

function TZmqSocket.GetAnsMeta:LongString;
begin
 if Assigned(Self) then
 try
  Result:=TextToList(myAnsMeta.Text,';');
 except
  on E:Exception do BugReport(E,Self,'GetAnsMeta');
 end;
end;

function TZmqSocket.SockOptCan(opt:Integer; what:Char):Boolean;
var id:LongString;
begin
 Result:=False;
 if Ok then
 try
  if (opt<=0) then Exit;
  id:=AskSockOptTyp(opt,LoCase(what));
  Result:=ValidSockOptDataType(id) and not SameText(id,'no');
 except
  on E:Exception do BugReport(E,Self,'SockOptCan');
 end;
end;

function TZmqSocket.SockOptCan(const opt:LongString; what:Char):Boolean;
begin
 Result:=SockOptCan(SockOptMap.CodeByName(opt),what);
end;

function TZmqSocket.GetSockOpt(opt:Integer):LongString;
var rc,nb:cint; id:LongString;
 function get_i4(var rc,nb:cint):LongString;
 var sz:csize_t; iv:cint;
 begin
  Result:='';
  inc(nb); iv:=0; sz:=SizeOf(iv);
  rc:=zmq_getsockopt(SockRef,opt,@iv,@sz);
  if RetOk(rc) then Result:=Format('%d',[iv]);
 end;
 function get_i8(var rc,nb:cint):LongString;
 var sz:csize_t; iv:cint64;
 begin
  Result:='';
  inc(nb); iv:=0; sz:=SizeOf(iv);
  rc:=zmq_getsockopt(SockRef,opt,@iv,@sz);
  if RetOk(rc) then Result:=Format('%d',[iv]);
 end;
 function get_u8(var rc,nb:cint):LongString;
 var sz:csize_t; iv:cuint64;
 begin
  Result:='';
  inc(nb); iv:=0; sz:=SizeOf(iv);
  rc:=zmq_getsockopt(SockRef,opt,@iv,@sz);
  if RetOk(rc) then Result:=Format('%u',[iv]);
 end;
 function get_fd(var rc,nb:cint):LongString;
 var sz:csize_t; iv:zmq_fd_t;
 begin
  Result:='';
  inc(nb); iv:=0; sz:=SizeOf(iv);
  rc:=zmq_getsockopt(SockRef,opt,@iv,@sz);
  if RetOk(rc) then Result:=Format('%d',[iv]);
 end;
 function get_cs(var rc,nb:cint; sz:csize_t=SizeOf(TParsingBuffer)-1):LongString;
 var sb:TParsingBuffer; const maxlen=SizeOf(sb)-1;
 begin
  Result:=''; inc(nb);
  sb:=Default(TParsingBuffer);
  sz:=EnsureRange(sz,0,maxlen);
  rc:=zmq_getsockopt(SockRef,opt,@sb,@sz);
  if RetOk(rc) then Result:=StringBuffer(@sb,StrLLen(sb,EnsureRange(sz,0,maxlen)));
 end;
 function get_bd(var rc,nb:cint; sz:csize_t=SizeOf(TParsingBuffer)-1):LongString;
 var sb:TParsingBuffer; const maxlen=SizeOf(sb)-1;
 begin
  Result:=''; inc(nb);
  sb:=Default(TParsingBuffer);
  sz:=EnsureRange(sz,0,maxlen);
  rc:=zmq_getsockopt(SockRef,opt,@sb,@sz);
  if RetOk(rc) then Result:=hex_encode(StringBuffer(@sb,EnsureRange(sz,0,maxlen)));
 end;
 function get_bz(var rc,nb:cint; sz:csize_t=41):LongString; // Z85 encoded keys
 var sb:TParsingBuffer; const maxlen=SizeOf(sb)-1;
 begin
  Result:=''; inc(nb);
  sb:=Default(TParsingBuffer);
  sz:=EnsureRange(sz,0,maxlen);
  rc:=zmq_getsockopt(SockRef,opt,@sb,@sz);
  if RetOk(rc) then Result:=StringBuffer(@sb,StrLLen(sb,EnsureRange(sz,0,maxlen)));
 end;
 function get_by(var rc,nb:Integer):LongString;
 begin
  inc(nb); rc:=0;
  Result:=IntToStr(ZmqMan.CtxOpt[ZMQ_BLOCKY]);
 end;
begin
 Result:='';
 if Ok then
 try
  nb:=0; rc:=0;
  id:=AskSockOptTyp(opt,'r');
  if not ValidSockOptDataType(id) then Exit;
  if (opt=ZMQ_BLOCKY)  then Result:=get_by(rc,nb) else
  if SameText(id,'i4') then Result:=get_i4(rc,nb) else
  if SameText(id,'i8') then Result:=get_i8(rc,nb) else
  if SameText(id,'u8') then Result:=get_u8(rc,nb) else
  if SameText(id,'fd') then Result:=get_fd(rc,nb) else
  if SameText(id,'cs') then Result:=get_cs(rc,nb) else
  if SameText(id,'bd') then Result:=get_bd(rc,nb) else
  if SameText(id,'bz') then Result:=get_bz(rc,nb) else
  if SameText(id,'no') then Exit;
  if (nb>0) then begin
   if Parent.NeedLog
   then ZCall(rc,Format('GetSockOpt(%d):%s',[opt,Result]));
  end;
 except
  on E:Exception do BugReport(E,Self,'GetSockOpt');
 end;
end;

function TZmqSocket.GetSockOptByName(const opt:LongString):LongString;
begin
 Result:=GetSockOpt(SockOptMap.CodeByName(opt));
end;

procedure TZmqSocket.SetSockOpt(opt:Integer; const optVal:LongString);
var rc,nb:Integer; id:LongString;
 procedure set_i4(var rc,nb:Integer);
 var sz:csize_t; iv:cint;
 begin
  iv:=0; sz:=SizeOf(iv);
  if TryStrToInt(optVal,iv) then begin
   rc:=zmq_setsockopt(SockRef,opt,@iv,sz);
   inc(nb);
  end;
 end;
 procedure set_i8(var rc,nb:Integer);
 var sz:csize_t; iv:cint64;
 begin
  iv:=0; sz:=SizeOf(iv);
  if TryStrToInt64(optVal,iv) then begin
   rc:=zmq_setsockopt(SockRef,opt,@iv,sz);
   inc(nb);
  end;
 end;
 procedure set_u8(var rc,nb:Integer);
 var sz:csize_t; iv:cuint64;
 begin
  iv:=0; sz:=SizeOf(iv);
  if TryStrToQWord(optVal,iv) then begin
   rc:=zmq_setsockopt(SockRef,opt,@iv,sz);
   inc(nb);
  end;
 end;
 procedure set_fd(var rc,nb:Integer);
 var sz:csize_t; iv:zmq_fd_t; i4:cint; u8:cuint64;
 begin
  iv:=0; sz:=SizeOf(iv); i4:=0; u8:=0;
  if ((sz=4) and TryStrToInt(optVal,i4))
  or ((sz=8) and TryStrToQWord(optVal,u8)) then begin
   if (sz=8) then iv:=u8 else iv:=i4;
   rc:=zmq_setsockopt(SockRef,opt,@iv,sz);
   inc(nb);
  end;
 end;
 procedure set_cs(var rc,nb:Integer);
 var sz:csize_t; sp:PChar; sb:TParsingBuffer; const maxlen=SizeOf(sb)-1;
 begin
  sp:=nil;
  sz:=Length(optVal);
  sb:=Default(TParsingBuffer);
  sz:=EnsureRange(sz,0,maxlen);
  if (sz>0) then sp:=StrLCopy(sb,PChar(optVal),maxlen);
  if InRange(sz,0,maxlen) then begin
   rc:=zmq_setsockopt(SockRef,opt,sp,sz);
   inc(nb);
  end;
 end;
 procedure set_bd(var rc,nb:Integer);
 var sz:csize_t; sp:PChar; sb:LongString;
 begin
  sp:=nil;
  if IsLexeme(optVal,lex_xDigit)
  then sb:=hex_decode(optVal) else sb:='';
  sz:=Length(sb);
  if (sz>0) then sp:=Pointer(sb);
  if InRange(sz,1,MaxInt) then begin
   rc:=zmq_setsockopt(SockRef,opt,sp,sz);
   inc(nb);
  end;
 end;
 procedure set_bz(var rc,nb:Integer);
 var sz:csize_t; sp:PChar; sb:TParsingBuffer; const maxlen=SizeOf(sb)-1;
 var bf:LongString;
 begin
  sp:=nil;
  bf:=optVal;
  if IsLexeme(bf,lex_xDigit) and (Length(bf)=Parent.SecrKeyBinLeng*2)
  then bf:=hex_decode(bf);
  sz:=Length(bf);
  sb:=Default(TParsingBuffer);
  sz:=EnsureRange(sz,0,maxlen);
  if (sz>0) then sp:=StrLCopy(sb,PChar(bf),maxlen);
  if (sz=Cardinal(Parent.SecrKeyZ85Leng))
  or (sz=Cardinal(Parent.SecrKeyBinLeng)) then begin
   rc:=zmq_setsockopt(SockRef,opt,sp,sz+1);
   inc(nb);
  end;
 end;
 procedure CheckHex(var s:LongString);
 var sx:LongString;
 begin
  if StartsText('hex:',s) then begin
   sx:=Copy(s,5);
   if IsLexeme(sx,lex_xDigit) then begin
    s:=Hex_Decode(sx);
   end;
  end;
 end;
 procedure set_su(var rc,nb:Integer);
 var sz:csize_t; sp:PChar; sb:TParsingBuffer; const maxlen=SizeOf(sb)-1;
 var sv:LongString;
 begin
  sp:=nil;
  sv:=optVal;
  CheckHex(sv);
  sz:=Length(sv);
  if (optVal='*') then sz:=0;
  sb:=Default(TParsingBuffer);
  sz:=EnsureRange(sz,0,maxlen);
  if (sz>0) then sp:=StrLCopy(sb,PChar(sv),maxlen);
  if InRange(sz,0,maxlen) then begin
   rc:=zmq_setsockopt(SockRef,opt,sp,sz);
   inc(nb);
  end;
 end;
 procedure set_pd(var rc,nb:Integer);
 var sz:csize_t; iv:TPid; i4:cint; i8:cint64;
 begin
  iv:=0; sz:=SizeOf(iv); i4:=0; i8:=0;
  if ((sz=4) and TryStrToInt(optVal,i4))
  or ((sz=8) and TryStrToInt64(optVal,i8)) then begin
   if (sz=8) then iv:=i8 else iv:=i4;
   rc:=zmq_setsockopt(SockRef,opt,@iv,sz);
   inc(nb);
  end;
 end;
 procedure set_by(var rc,nb:Integer);
 var iv:Integer;
 begin
  iv:=0;
  if TryStrToInt(optVal,iv) then begin
   rc:=zmq_ctx_set(Parent.Ctx,ZMQ_BLOCKY,iv);
   inc(nb);
  end;
 end;
begin
 if Ok then
 try
  rc:=0; nb:=0;
  id:=AskSockOptTyp(opt,'w');
  if not ValidSockOptDataType(id) then Exit;
  if (opt=ZMQ_BLOCKY)  then set_by(rc,nb) else
  if SameText(id,'i4') then set_i4(rc,nb) else
  if SameText(id,'i8') then set_i8(rc,nb) else
  if SameText(id,'u8') then set_u8(rc,nb) else
  if SameText(id,'fd') then set_fd(rc,nb) else
  if SameText(id,'cs') then set_cs(rc,nb) else
  if SameText(id,'bd') then set_bd(rc,nb) else
  if SameText(id,'bz') then set_bz(rc,nb) else
  if SameText(id,'pd') then set_pd(rc,nb) else
  if SameText(id,'gd') then set_pd(rc,nb) else
  if SameText(id,'ud') then set_pd(rc,nb) else
  if SameText(id,'su') then set_su(rc,nb) else
  if SameText(id,'no') then Exit;
  if (nb>0) then begin
   if Parent.NeedLog
   then ZCall(rc,Format('SetSockOpt(%d,%s)',[opt,optVal]));
  end;
 except
  on E:Exception do BugReport(E,Self,'SetSockOpt');
 end;
end;

procedure TZmqSocket.SetSockOptByName(const opt,optVal:LongString);
begin
 SetSockOpt(SockOptMap.CodeByName(opt),optVal);
end;

function TZmqSocket.GetSockOptsByNames(const opts:LongString; const Delim:LongString=EOL):LongString;
var sn,sv:LongString; i:Integer;
const Delims=EolnDelims+[';'];
begin
 Result:='';
 if Ok then
 try
  sn:=''; sv:='';
  for i:=1 to WordCount(opts,Delims) do
  if (ExtractNameValuePair(ExtractWord(i,opts,Delims),sn,sv)>=0) then begin
   if SockOptCan(sn,'r') then sv:=GetSockOptByName(sn) else continue;
   Result+=(UpCaseStr(sn)+'='+sv+EOL);
  end;
  Result:=TextToList(Result,Delim);
 except
  on E:Exception do BugReport(E,Self,'GetSockOptsByNames');
 end;
end;

function TZmqSocket.SetSockOptsByNames(const opts:LongString; const Delim:LongString=EOL):LongString;
var sn,sv:LongString; i:Integer;
const Delims=EolnDelims+[';'];
begin
 Result:='';
 if Ok then
 try
  sn:=''; sv:='';
  for i:=1 to WordCount(opts,Delims) do
  if (ExtractNameValuePair(ExtractWord(i,opts,Delims),sn,sv)>=0) then begin
   if not SockOptCan(sn,'w') then continue;
   SetSockOptByName(sn,sv);
  end;
  Result:=GetSockOptsByNames(opts,Delim);
 except
  on E:Exception do BugReport(E,Self,'SetSockOptsByNames');
 end;
end;

function TZmqSocket.GetSockEvents:Integer;
var rc:Integer; sz:csize_t; iv:cint;
begin
 Result:=0;
 if Ok then
 try
  iv:=0; sz:=SizeOf(iv);
  rc:=zmq_getsockopt(SockRef,ZMQ_EVENTS,@iv,@sz);
  if RetOk(rc) then Result:=iv;
  if HasMonitor then PollMonitorEvents;
 except
  on E:Exception do BugReport(E,Self,'GetSockEvents');
 end;
end;

function TZmqSocket.GetCanPollIn:Boolean;
begin
 if Assigned(Self)
 then Result:=IsEventsPollIn(SockEvents)
 else Result:=False;
end;

function TZmqSocket.GetCanPollOut:Boolean;
begin
 if Assigned(Self)
 then Result:=IsEventsPollOut(SockEvents)
 else Result:=False;
end;

function TZmqSocket.GetSentCount:Int64;
begin
 if Assigned(Self)
 then Result:=mySentCount
 else Result:=0;
end;

function TZmqSocket.GetSentBytes:Int64;
begin
 if Assigned(Self)
 then Result:=mySentBytes
 else Result:=0;
end;

function TZmqSocket.GetRecvCount:Int64;
begin
 if Assigned(Self)
 then Result:=myRecvCount
 else Result:=0;
end;

function TZmqSocket.GetRecvBytes:Int64;
begin
 if Assigned(Self)
 then Result:=myRecvBytes
 else Result:=0;
end;

function TZmqSocket.MonitorEndpoint:LongString;
begin
 Result:=AdaptEndpointName('inproc://'+UniqName+'-monitor');
end;

function TZmqSocket.StartMonitor(aEndpoint:LongString; aEvents:Integer):Boolean;
var rc:Integer;
begin
 Result:=False;
 if Ok then
 try
  aEndpoint:=AdaptEndpointName(aEndpoint);
  if not ValidEndpointName(aEndpoint) then Exit;
  if not StartsText('inproc://',aEndpoint) then Exit;
  rc:=zmq_socket_monitor(SockRef,PChar(aEndpoint),aEvents);
  if NeedLog then ZCall(rc,Format('StartMonitor(0x%p,%s,%d)',[SockRef,aEndpoint,aEvents]));
  if RetOk(rc) then Result:=True;
 except
  on E:Exception do BugReport(E,Self,'StartMonitor');
 end;
end;

function TZmqSocket.GetMonitorEvent(out aValue:Integer; out aAddress:LongString):Integer;
type TEventNumValRec=packed record event:cuint16; value:cuint32; end; PEventNumValRec=^TEventNumValRec;
var msg:zmq_msg_t; pe:PEventNumValRec; event,value:Integer; Buf:PChar; Len:Integer; ept:LongString;
begin
 Result:=0; aValue:=0; aAddress:='';
 if Ok then
 if HasMonitor then
 try
  event:=0; value:=0; ept:='';
  // First frame in message contains event number and value
  if RetOk(zmq_msg_init(@msg)) then
  try
   pe:=nil; Len:=0;
   if RetOk(zmq_msg_recv(@msg,MonitorRef,ZMQ_DONTWAIT)) then
   if (zmq_msg_more(@msg)=1) then pe:=zmq_msg_data(@msg);
   if (pe<>nil) then Len:=zmq_msg_size(@msg) else Exit;
   if (Len<SizeOf(TEventNumValRec)) then Exit;
   event:=pe.event; value:=pe.value;
  finally
   zmq_msg_close(@msg);
  end;
  // Second frame in message contains event address
  if RetOk(zmq_msg_init(@msg)) then
  try
   if RetOk(zmq_msg_recv(@msg,MonitorRef,0)) then
   if (zmq_msg_more(@msg)<>0) then Exit;
   Buf:=zmq_msg_data(@msg); if (Buf=nil) then Exit;
   Len:=zmq_msg_size(@msg); if (Len<=0) then Exit;
   ept:=StringBuffer(Buf,Len);
  finally
   zmq_msg_close(@msg);
  end;
  // Result is ready
  Result:=event; aValue:=value; aAddress:=ept;
  if (Pos(#0,aAddress)>0) then aAddress:=StrAheadOf(aAddress,#0);
 except
  on E:Exception do BugReport(E,Self,'GetMonitorEvent');
 end;
end;

function TZmqSocket.HandleMonitorEvents(aPeriod,aEvents:Integer):Integer;
var event,value:Integer; ept,msg:LongString; ms:QWord;
begin
 Result:=0;
 if Ok then
 try
  event:=0; value:=0; ept:='';
  if HasMonitor then begin
   ms:=GetTickCount64;
   if (ms>=myLastMonit+aPeriod) or (aPeriod<=0)
   then myLastMonit:=ms
   else Exit;
   repeat
    event:=GetMonitorEvent(value,ept);
    if ValidEvent(event) then Inc(Result) else break;
    if HasFlags(event,aEvents) and DebugLogEnabled(dlc_ZmqMon) then begin
     msg:=Parent.FormatMonitorEvent(event,value,ept);
     DebugLog(dlc_ZmqMon,msg);
    end;
    if HasFlags(event,ZMQ_EVENT_HANDSHAKE_SUCCEEDED)
    then UpdateConnected(+1);
    if HasFlags(event,ZMQ_EVENT_DISCONNECTED)
    then UpdateConnected(-1);
   until not ValidEvent(event);
  end;
 except
  on E:Exception do BugReport(E,Self,'HandleMonitorEvents');
 end;
end;

function TZmqSocket.HandleMonitorEventsWithTimeout(aEvents,aTimeout:Integer):Integer;
var ms:QWord;
begin
 Result:=0;
 if Assigned(Self) then begin
  ms:=GetTickCount64;
  repeat
   Result:=HandleMonitorEvents(0,aEvents);
   if (Result>0) or (aTimeout<=0) then break;
   Sleep(Max(1,Parent.MonitorDefLoopDelay));
  until (GetTickCount64>ms+aTimeout);
 end;
end;

procedure TZmqSocket.PollMonitorEvents;
begin
 if Assigned(Self) and HasMonitor
 then HandleMonitorEvents(MonitorPollPeriod,MonitorViewEvents);
end;

function TZmqSocket.Ctrl(const arg:LongString; const Delim:LongString=EOL; Sep:Char=#0):LongString;
var p,i,iv,si:Integer; sn,sv,par,opt:LongString;
begin
 Result:='?';
 if Assigned(Self) then
 try
  iv:=0;
  sn:=''; sv:=''; par:=''; opt:='';
  p:=ExtractNameValuePair(arg,sn,sv);
  if (Sep=#0) then Sep:=CookiesDelim;
  if (Sep=Delim) then Sep:=CookiesSep;
  case Identify(sn) of
   sid_ValidApi: begin
    Result:=Parent.Ctrl(arg,Delim,Sep);
   end;
   sid_LibZmqName: begin
    Result:=Parent.LibZmqName;
   end;
   sid_VersionInt: begin
    Result:=IntToStr(Parent.VersionInt);
   end;
   sid_VersionStr: begin
    Result:=Parent.VersionStr;
   end;
   sid_BriefInfo: begin
    Result:=Parent.BriefInfo;
   end;
   sid_CapsList: begin
    Result:=Parent.CapsList;
   end;
   sid_CapsNames: begin
    Result:=Parent.CapsNames(CookiesSep);
   end;
   sid_CtxOptNames: begin
    Result:=Parent.CtxOptNames(CookiesSep);
   end;
   sid_SockOptNames: begin
    Result:=Parent.ListSockOptNames(CookiesSep,'rw');
   end;
   sid_SockTypNames: begin
    Result:=Parent.TextToList(SockTypMap.NamesText,CookiesSep);
   end;
   sid_Transports: begin
    Result:=Parent.ListAllTransports(CookiesSep);
   end;
   sid_ZmqEngineNames: begin
    Result:=Parent.ZmqEngineNames(CookiesSep);
   end;
   sid_CurveKeyPair: begin
    Result:=Parent.CurveKeyPair;
   end;
   sid_CurvePublic: begin
    if (p>0) then Result:=Parent.CurvePublic(sv);
   end;
   sid_Z85Encode: begin
    if (p>0) then Result:=Parent.Z85Encode(StrAfterOf(arg,'='));
   end;
   sid_Z85Decode: begin
    if (p>0) then Result:=Parent.Z85Decode(StrAfterOf(arg,'='));
   end;
   sid_Functions: begin
    Result:=Parent.Ctrl(arg,Delim,Sep);
   end;
   sid_Errno: begin
    Result:=IntToStr(Parent.Errno);
   end;
   sid_StrErrno: begin
    Result:=Parent.Ctrl(arg,Delim,Sep);
   end;
   sid_StrError: begin
    Result:=Parent.Ctrl(arg,Delim,Sep);
   end;
   sid_StrErrorMsg: begin
    Result:=Parent.Ctrl(arg,Delim,Sep);
   end;
   sid_Ctx: begin
    Result:=Format('0x%p',[Parent.Ctx]);
   end;
   sid_CtxOpt: begin
    Result:=Parent.Ctrl(arg,Delim,Sep);
   end;
   sid_Bind: begin
    if (p>0) and ValidEndpointName(sv) then Bind(sv);
    Result:=Bindings;
   end;
   sid_Connect: begin
    if (p>0) and ValidEndpointName(sv) then Connect(sv);
    Result:=Connections;
   end;
   sid_Subscribe: begin
    if (p>0) and ValidSubFilter(sv) then Subscribe(sv);
    Result:=Subscriptions;
   end;
   sid_Connected: begin
    Result:=IntToStr(Connected);
   end;
   sid_Linger: begin
    if (p>0) and TryStrToInt(sv,iv) then Linger:=iv;
    Result:=IntToStr(Linger);
   end;
   sid_IoMode: begin
    if (p>0) and TryStrToInt(sv,iv) then IoMode:=iv;
    Result:=IntToStr(IoMode);
   end;
   sid_SockRef: begin
    Result:=Format('0x%p',[SockRef]);
   end;
   sid_SockTyp: begin
    Result:=SockTypMap.NameByCode(SockTyp);
   end;
   sid_SockOpt: begin
    if (p=0) or IsEmptyStr(sv) then sv:=ListSockOptNames(CookiesDelim,'r');
    if (p>0) and HasChars(sv,'=')
    then Result:=TextToList(SetSockOptsByNames(sv),Sep)
    else Result:=TextToList(GetSockOptsByNames(sv),Sep);
   end;
   sid_AskMeta: begin
    if (p>0) then AskMeta:=Trim(sv);
    Result:=AskMeta;
   end;
   sid_AnsMeta: begin
    Result:=AnsMeta;
    if (Sep<>CookiesDelim) and HasChars(Result,CookiesDelim)
    then Result:=TextToList(ListToText(Result,CookiesDelim),CookiesSep);
   end;
   sid_SockEvents: begin
    Result:=IntToStr(SockEvents);
   end;
   sid_CanPollIn: begin
    Result:=IntToStr(BoolToInt(CanPollIn));
   end;
   sid_CanPollOut: begin
    Result:=IntToStr(BoolToInt(CanPollOut));
   end;
   sid_SentBytes: begin
    Result:=IntToStr(SentBytes);
   end;
   sid_SentCount: begin
    Result:=IntToStr(SentCount);
   end;
   sid_RecvBytes: begin
    Result:=IntToStr(RecvBytes);
   end;
   sid_RecvCount: begin
    Result:=IntToStr(RecvCount);
   end;
   sid_SendMore: begin
    if (p>0) then SendMore(StrToIntDef(sv,-1));
    Result:=IntToStr(SendMore);
   end;
   sid_RecvMore: begin
    Result:=IntToStr(RecvMore);
   end;
   sid_Asterisk,
   sid_Question: begin
    Result:='';
    if Assigned(Dictionary) then
    for i:=0 to Dictionary.Count-1 do begin
     si:=Dictionary.Codes[i]; par:=Dictionary.Names[i];
     if (si<=Ord(sid_Unknown)) or (si>=Ord(sid_Asterisk)) then continue;
     opt:=CookieScan(Dictionary.ParamByName[par],'opt',Ord(CookiesDelim));
     if not HasChars(opt,['c','s']) then continue; // use ctx/sock items
     if (sn='*') and HasChars(opt,['r','w']) then Result+=(par+EOL);
     if (sn='?') and HasChars(opt,['r']) and (p=0) then Result+=(par+EOL);
     if (sn='?') and HasChars(opt,['r']) and (p>0) then Result+=(par+'='+Ctrl(par,CookiesDelim,Sep)+EOL);
    end;
   end;
  end;
  Result:=TextToList(Result,Delim);
  sn:=''; sv:='';
 except
  on E:Exception do BugReport(E,Self,'Ctrl');
 end;
end;

//////////////////
// Helper routines
//////////////////

procedure Kill(var TheObject:TZmqManager); overload;
begin
 try
  FreeAndNil(TheObject);
 except
  on E:Exception do BugReport(E,nil,'Kill');
 end;
end;

procedure Kill(var TheObject:TZmqSocket); overload;
begin
 try
  FreeAndNil(TheObject);
 except
  on E:Exception do BugReport(E,nil,'Kill');
 end;
end;

////////////////////////
// ZmqMan implementation
////////////////////////

const
 TheZmqMan:TZmqManager=nil;

function ZmqMan:TZmqManager;
begin
 if not Assigned(TheZmqMan) then begin
  TheZmqMan:=TZmqManager.Create;
  TheZmqMan.Master:=@TheZmqMan;
 end;
 Result:=TheZmqMan;
end;

procedure ZmqManLeakageLog;
begin
 ResourceLeakageLog(Format('%-60s = %d',['ZmqMan.SocketsCount',TheZmqMan.SocketsCount]));
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_zmqman;
begin
 dlc_ZmqLog; dlc_ZmqBug; dlc_ZmqMon;
 TZmqObject.InitMappers;
 InitDictionary;
end;

procedure Free_crw_zmqman;
begin
 ZmqManLeakageLog;
 Kill(TheZmqMan);
 FreeDictionary;
 TZmqObject.FreeMappers;
end;

initialization

 Init_crw_zmqman;

finalization

 Free_crw_zmqman;

end.

//////////////
// END OF FILE
//////////////

