content: {-# LANGUAGE CPP                #-}
  {-# LANGUAGE GADTs              #-}
  {-# LANGUAGE DeriveDataTypeable #-}
  {-# LANGUAGE DeriveGeneric      #-}
  
  -- |
  -- Module      : System.ZMQ4
  -- Copyright   : (c) 2010-2013 Toralf Wittner
  -- License     : MIT
  -- Maintainer  : Toralf Wittner <tw@dtex.org>
  -- Stability   : experimental
  -- Portability : non-portable
  --
  -- 0MQ haskell binding. The API closely follows the C-API of 0MQ with
  -- the main difference being that sockets are typed.
  --
  -- /Notes/
  --
  -- Many option settings use a 'Restriction' to further constrain the
  -- range of possible values of their integral types. For example
  -- the maximum message size can be given as -1, which means no limit
  -- or by greater values, which denote the message size in bytes. The
  -- type of 'setMaxMessageSize' is therefore:
  --
  -- @setMaxMessageSize :: Integral i
  --                    => Restricted (Nneg1, Int64) i
  --                    -> Socket a
  --                    -> IO ()@
  --
  -- which means any integral value in the range of @-1@ to
  -- (@maxBound :: Int64@) can be given. To create a restricted
  -- value from plain value, use 'toRestricted' or 'restrict'.
  
  module System.ZMQ4
    ( -- * Type Definitions
      -- ** Socket Types
      Pair   (..)
    , Pub    (..)
    , Sub    (..)
    , XPub   (..)
    , XSub   (..)
    , Req    (..)
    , Rep    (..)
    , Dealer (..)
    , Router (..)
    , XReq
    , XRep
    , Pull   (..)
    , Push   (..)
    , Stream (..)
  
      -- ** Socket type-classes
    , SocketType
    , Sender
    , Receiver
    , Subscriber
    , SocketLike
    , Conflatable
    , SendProbe
  
      -- ** Various type definitions
    , Size
    , Context
    , Socket
    , Flag              (..)
    , Switch            (..)
    , Timeout
    , Event             (..)
    , EventType         (..)
    , EventMsg          (..)
    , Poll              (..)
    , KeyFormat         (..)
    , SecurityMechanism (..)
  
      -- * General Operations
    , withContext
    , withSocket
    , bind
    , unbind
    , connect
    , disconnect
    , send
    , send'
    , sendMulti
    , receive
    , receiveMulti
    , version
    , monitor
    , socketMonitor
    , poll
  
    , System.ZMQ4.subscribe
    , System.ZMQ4.unsubscribe
  
      -- * Context Options (Read)
    , ioThreads
    , maxSockets
  
      -- * Context Options (Write)
    , setIoThreads
    , setMaxSockets
  
      -- * Socket Options (Read)
    , System.ZMQ4.affinity
    , System.ZMQ4.backlog
    , System.ZMQ4.conflate
    , System.ZMQ4.curvePublicKey
    , System.ZMQ4.curveSecretKey
    , System.ZMQ4.curveServerKey
    , System.ZMQ4.delayAttachOnConnect
    , System.ZMQ4.events
    , System.ZMQ4.fileDescriptor
    , System.ZMQ4.identity
    , System.ZMQ4.immediate
    , System.ZMQ4.ipv4Only
    , System.ZMQ4.ipv6
    , System.ZMQ4.lastEndpoint
    , System.ZMQ4.linger
    , System.ZMQ4.maxMessageSize
    , System.ZMQ4.mcastHops
    , System.ZMQ4.mechanism
    , System.ZMQ4.moreToReceive
    , System.ZMQ4.plainServer
    , System.ZMQ4.plainPassword
    , System.ZMQ4.plainUserName
    , System.ZMQ4.rate
    , System.ZMQ4.receiveBuffer
    , System.ZMQ4.receiveHighWM
    , System.ZMQ4.receiveTimeout
    , System.ZMQ4.reconnectInterval
    , System.ZMQ4.reconnectIntervalMax
    , System.ZMQ4.recoveryInterval
    , System.ZMQ4.sendBuffer
    , System.ZMQ4.sendHighWM
    , System.ZMQ4.sendTimeout
    , System.ZMQ4.tcpKeepAlive
    , System.ZMQ4.tcpKeepAliveCount
    , System.ZMQ4.tcpKeepAliveIdle
    , System.ZMQ4.tcpKeepAliveInterval
    , System.ZMQ4.zapDomain
  
      -- * Socket Options (Write)
    , setAffinity
    , setBacklog
    , setConflate
    , setCurveServer
    , setCurvePublicKey
    , setCurveSecretKey
    , setCurveServerKey
    , setDelayAttachOnConnect
    , setIdentity
    , setImmediate
    , setIpv4Only
    , setIpv6
    , setLinger
    , setMaxMessageSize
    , setMcastHops
    , setPlainServer
    , setPlainPassword
    , setPlainUserName
    , setProbeRouter
    , setRate
    , setReceiveBuffer
    , setReceiveHighWM
    , setReceiveTimeout
    , setReconnectInterval
    , setReconnectIntervalMax
    , setRecoveryInterval
    , setReqCorrelate
    , setReqRelaxed
    , setRouterMandatory
    , setSendBuffer
    , setSendHighWM
    , setSendTimeout
    , setTcpAcceptFilter
    , setTcpKeepAlive
    , setTcpKeepAliveCount
    , setTcpKeepAliveIdle
    , setTcpKeepAliveInterval
    , setXPubVerbose
  
      -- * Restrictions
    , Data.Restricted.restrict
    , Data.Restricted.toRestricted
  
      -- * Error Handling
    , ZMQError
    , errno
    , source
    , message
  
      -- * Low-level Functions
    , init
    , term
    , shutdown
    , context
    , socket
    , close
    , waitRead
    , waitWrite
    , z85Encode
    , z85Decode
  
      -- * Utils
    , proxy
    , curveKeyPair
    ) where
  
  import Control.Applicative
  import Control.Exception
  import Control.Monad (unless)
  import Control.Monad.IO.Class
  import Data.List (intersect, foldl')
  import Data.List.NonEmpty (NonEmpty)
  import Data.Restricted
  import Data.Traversable (forM)
  import Data.Typeable
  import Foreign hiding (throwIf, throwIf_, throwIfNull, void)
  import Foreign.C.String
  import Foreign.C.Types (CInt, CShort)
  import System.Posix.Types (Fd(..))
  import System.ZMQ4.Internal
  import System.ZMQ4.Internal.Base
  import System.ZMQ4.Internal.Error
  import Prelude hiding (init)
  
  import qualified Data.ByteString           as SB
  import qualified Data.ByteString.Lazy      as LB
  import qualified Data.List.NonEmpty        as S
  import qualified Prelude                   as P
  import qualified System.ZMQ4.Internal.Base as B
  
  import GHC.Conc (threadWaitRead)
  import GHC.Generics(Generic)
  
  -----------------------------------------------------------------------------
  -- Socket Types
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_PAIR>
  data Pair = Pair deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_PUB>
  data Pub = Pub deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_SUB>
  data Sub = Sub deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_XPUB>
  data XPub = XPub deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_XSUB>
  data XSub = XSub deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_REQ>
  data Req = Req deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_REP>
  data Rep = Rep deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_DEALER>
  data Dealer = Dealer deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_ROUTER>
  data Router = Router deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_PULL>
  data Pull = Pull deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_PUSH>
  data Push = Push deriving (Eq, Typeable, Generic)
  
  -- | <http://api.zeromq.org/4-0:zmq-socket ZMQ_STREAM>
  data Stream = Stream deriving (Eq, Typeable, Generic)
  
  type XReq = Dealer
  {-# DEPRECATED XReq "Use Dealer" #-}
  
  type XRep = Router
  {-# DEPRECATED XRep "Use Router" #-}
  
  -----------------------------------------------------------------------------
  -- Socket Type Classifications
  
  -- | Sockets which can 'subscribe'.
  class Subscriber a
  
  -- | Sockets which can 'send'.
  class Sender a
  
  -- | Sockets which can 'receive'.
  class Receiver a
  
  -- | Sockets which can be 'conflate'd.
  class Conflatable a
  
  -- | Sockets which can send probes (cf. 'setProbeRouter').
  class SendProbe a
  
  instance SocketType Pair where zmqSocketType = const pair
  instance Sender     Pair
  instance Receiver   Pair
  
  instance SocketType  Pub where zmqSocketType = const pub
  instance Sender      Pub
  instance Conflatable Pub
  
  instance SocketType  Sub where zmqSocketType = const sub
  instance Subscriber  Sub
  instance Receiver    Sub
  instance Conflatable Sub
  
  instance SocketType XPub where zmqSocketType = const xpub
  instance Sender     XPub
  instance Receiver   XPub
  
  instance SocketType XSub where zmqSocketType = const xsub
  instance Sender     XSub
  instance Receiver   XSub
  
  instance SocketType Req where zmqSocketType = const request
  instance Sender     Req
  instance Receiver   Req
  instance SendProbe  Req
  
  instance SocketType Rep where zmqSocketType = const response
  instance Sender     Rep
  instance Receiver   Rep
  
  instance SocketType  Dealer where zmqSocketType = const dealer
  instance Sender      Dealer
  instance Receiver    Dealer
  instance Conflatable Dealer
  instance SendProbe   Dealer
  
  instance SocketType Router where zmqSocketType = const router
  instance Sender     Router
  instance Receiver   Router
  instance SendProbe  Router
  
  instance SocketType  Pull where zmqSocketType = const pull
  instance Receiver    Pull
  instance Conflatable Pull
  
  instance SocketType  Push where zmqSocketType = const push
  instance Sender      Push
  instance Conflatable Push
  
  instance SocketType Stream where zmqSocketType = const stream
  instance Sender     Stream
  instance Receiver   Stream
  
  -----------------------------------------------------------------------------
  
  -- | Socket events.
  data Event =
      In     -- ^ @ZMQ_POLLIN@ (incoming messages)
    | Out    -- ^ @ZMQ_POLLOUT@ (outgoing messages, i.e. at least 1 byte can be written)
    | Err    -- ^ @ZMQ_POLLERR@
    deriving (Eq, Ord, Read, Show)
  
  -- | A 'Poll' value contains the object to poll (a 0MQ socket or a file
  -- descriptor), the set of 'Event's which are of interest and--optionally--
  -- a callback-function which is invoked iff the set of interested events
  -- overlaps with the actual events.
  data Poll s m where
      Sock :: s t -> [Event] -> Maybe ([Event] -> m ()) -> Poll s m
      File :: Fd -> [Event] -> Maybe ([Event] -> m ()) -> Poll s m
  
  -- | Return the runtime version of the underlying 0MQ library as a
  -- (major, minor, patch) triple.
  version :: IO (Int, Int, Int)
  version =
      with 0 $ \major_ptr ->
      with 0 $ \minor_ptr ->
      with 0 $ \patch_ptr ->
          c_zmq_version major_ptr minor_ptr patch_ptr >>
          tupleUp <$> peek major_ptr <*> peek minor_ptr <*> peek patch_ptr
    where
      tupleUp a b c = (fromIntegral a, fromIntegral b, fromIntegral c)
  
  init :: Size -> IO Context
  init n = do
      c <- context
      setIoThreads n c
      return c
  {-# DEPRECATED init "Use context" #-}
  
  -- | Initialize a 0MQ context.
  -- Equivalent to <http://api.zeromq.org/4-0:zmq-ctx-new zmq_ctx_new>.
  context :: IO Context
  context = Context <$> throwIfNull "init" c_zmq_ctx_new
  
  -- | Terminate a 0MQ context.
  -- Equivalent to <http://api.zeromq.org/4-0:zmq-ctx-term zmq_ctx_term>.
  term :: Context -> IO ()
  term c = throwIfMinus1Retry_ "term" . c_zmq_ctx_term . _ctx $ c
  
  -- | Shutdown a 0MQ context.
  -- Equivalent to <http://api.zeromq.org/4-0:zmq-ctx-shutdown zmq_ctx_shutdown>.
  shutdown :: Context -> IO ()
  shutdown = throwIfMinus1_ "shutdown" . c_zmq_ctx_shutdown . _ctx
  
  -- | Run an action with a 0MQ context.  The 'Context' supplied to your
  -- action will /not/ be valid after the action either returns or
  -- throws an exception.
  withContext :: (Context -> IO a) -> IO a
  withContext act =
    bracket (throwIfNull "withContext (new)" $ c_zmq_ctx_new)
            (throwIfMinus1Retry_ "withContext (term)" . c_zmq_ctx_term)
            (act . Context)
  
  -- | Run an action with a 0MQ socket. The socket will be closed after running
  -- the supplied action even if an error occurs. The socket supplied to your
  -- action will /not/ be valid after the action terminates.
  withSocket :: SocketType a => Context -> a -> (Socket a -> IO b) -> IO b
  withSocket c t = bracket (socket c t) close
  
  -- | Create a new 0MQ socket within the given context. 'withSocket' provides
  -- automatic socket closing and may be safer to use.
  socket :: SocketType a => Context -> a -> IO (Socket a)
  socket c t = Socket <$> mkSocketRepr t c
  
  -- | Close a 0MQ socket. 'withSocket' provides automatic socket closing and may
  -- be safer to use.
  close :: Socket a -> IO ()
  close = closeSock . _socketRepr
  
  -- | Subscribe Socket to given subscription.
  subscribe :: Subscriber a => Socket a -> SB.ByteString -> IO ()
  subscribe s = setByteStringOpt s B.subscribe
  
  -- | Unsubscribe Socket from given subscription.
  unsubscribe :: Subscriber a => Socket a -> SB.ByteString -> IO ()
  unsubscribe s = setByteStringOpt s B.unsubscribe
  
  -- Read Only
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_EVENTS>.
  events :: Socket a -> IO [Event]
  events s = toEvents <$> getIntOpt s B.events 0
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_FD>.
  fileDescriptor :: Socket a -> IO Fd
  fileDescriptor s = Fd . fromIntegral <$> getInt32Option B.filedesc s
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RCVMORE>.
  moreToReceive :: Socket a -> IO Bool
  moreToReceive s = (== 1) <$> getInt32Option B.receiveMore s
  
  -- Read
  
  -- | <http://api.zeromq.org/4-0:zmq-ctx-get zmq_ctx_get ZMQ_IO_THREADS>.
  ioThreads :: Context -> IO Word
  ioThreads = ctxIntOption "ioThreads" _ioThreads
  
  -- | <http://api.zeromq.org/4-0:zmq-ctx-get zmq_ctx_get ZMQ_MAX_SOCKETS>.
  maxSockets :: Context -> IO Word
  maxSockets = ctxIntOption "maxSockets" _maxSockets
  
  -- | Restricts the outgoing and incoming socket buffers to a single message.
  conflate :: Conflatable a => Socket a -> IO Bool
  conflate s = (== 1) <$> getInt32Option B.conflate s
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_IMMEDIATE>.
  immediate :: Socket a -> IO Bool
  immediate s = (== 1) <$> getInt32Option B.immediate s
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_IDENTITY>.
  identity :: Socket a -> IO SB.ByteString
  identity s = getByteStringOpt s B.identity
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_AFFINITY>.
  affinity :: Socket a -> IO Word64
  affinity s = getIntOpt s B.affinity 0
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_MAXMSGSIZE>.
  maxMessageSize :: Socket a -> IO Int64
  maxMessageSize s = getIntOpt s B.maxMessageSize 0
  
  ipv4Only :: Socket a -> IO Bool
  ipv4Only s = (== 1) <$> getInt32Option B.ipv4Only s
  {-# DEPRECATED ipv4Only "Use ipv6" #-}
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_IPV6>.
  ipv6 :: Socket a -> IO Bool
  ipv6 s = (== 1) <$> getInt32Option B.ipv6 s
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_BACKLOG>.
  backlog :: Socket a -> IO Int
  backlog = getInt32Option B.backlog
  
  delayAttachOnConnect :: Socket a -> IO Bool
  delayAttachOnConnect s = (== 1) <$> getInt32Option B.delayAttachOnConnect s
  {-# DEPRECATED delayAttachOnConnect "Use immediate" #-}
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_LINGER>.
  linger :: Socket a -> IO Int
  linger = getInt32Option B.linger
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_LAST_ENDPOINT>.
  lastEndpoint :: Socket a -> IO String
  lastEndpoint s = getStrOpt s B.lastEndpoint
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RATE>.
  rate :: Socket a -> IO Int
  rate = getInt32Option B.rate
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RCVBUF>.
  receiveBuffer :: Socket a -> IO Int
  receiveBuffer = getInt32Option B.receiveBuf
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RECONNECT_IVL>.
  reconnectInterval :: Socket a -> IO Int
  reconnectInterval = getInt32Option B.reconnectIVL
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RECONNECT_IVL_MAX>.
  reconnectIntervalMax :: Socket a -> IO Int
  reconnectIntervalMax = getInt32Option B.reconnectIVLMax
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RECOVERY_IVL>.
  recoveryInterval :: Socket a -> IO Int
  recoveryInterval = getInt32Option B.recoveryIVL
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_SNDBUF>.
  sendBuffer :: Socket a -> IO Int
  sendBuffer = getInt32Option B.sendBuf
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_MULTICAST_HOPS>.
  mcastHops :: Socket a -> IO Int
  mcastHops = getInt32Option B.mcastHops
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RCVHWM>.
  receiveHighWM :: Socket a -> IO Int
  receiveHighWM = getInt32Option B.receiveHighWM
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_RCVTIMEO>.
  receiveTimeout :: Socket a -> IO Int
  receiveTimeout = getInt32Option B.receiveTimeout
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_SNDTIMEO>.
  sendTimeout :: Socket a -> IO Int
  sendTimeout = getInt32Option B.sendTimeout
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_SNDHWM>.
  sendHighWM :: Socket a -> IO Int
  sendHighWM = getInt32Option B.sendHighWM
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_TCP_KEEPALIVE>.
  tcpKeepAlive :: Socket a -> IO Switch
  tcpKeepAlive = fmap (toSwitch "Invalid ZMQ_TCP_KEEPALIVE")
               . getInt32Option B.tcpKeepAlive
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_TCP_KEEPALIVE_CNT>.
  tcpKeepAliveCount :: Socket a -> IO Int
  tcpKeepAliveCount = getInt32Option B.tcpKeepAliveCount
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_TCP_KEEPALIVE_IDLE>.
  tcpKeepAliveIdle :: Socket a -> IO Int
  tcpKeepAliveIdle = getInt32Option B.tcpKeepAliveIdle
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_TCP_KEEPALIVE_INTVL>.
  tcpKeepAliveInterval :: Socket a -> IO Int
  tcpKeepAliveInterval = getInt32Option B.tcpKeepAliveInterval
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_MECHANISM>.
  mechanism :: Socket a -> IO SecurityMechanism
  mechanism = fmap (fromMechanism "Invalid ZMQ_MECHANISM")
            . getInt32Option B.mechanism
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_PLAIN_SERVER>.
  plainServer :: Socket a -> IO Bool
  plainServer = fmap (== 1) . getInt32Option B.plainServer
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_PLAIN_USERNAME>.
  plainUserName :: Socket a -> IO SB.ByteString
  plainUserName s = getByteStringOpt s B.plainUserName
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_PLAIN_PASSWORD>.
  plainPassword :: Socket a -> IO SB.ByteString
  plainPassword s = getByteStringOpt s B.plainPassword
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_ZAP_DOMAIN>.
  zapDomain :: Socket a -> IO SB.ByteString
  zapDomain s = getByteStringOpt s B.zapDomain
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_CURVE_PUBLICKEY>.
  curvePublicKey :: KeyFormat f -> Socket a -> IO SB.ByteString
  curvePublicKey f s = getKey f s B.curvePublicKey
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_CURVE_SERVERKEY>.
  curveServerKey :: KeyFormat f -> Socket a -> IO SB.ByteString
  curveServerKey f s = getKey f s B.curveServerKey
  
  -- | <http://api.zeromq.org/4-0:zmq-getsockopt zmq_getsockopt ZMQ_CURVE_SECRETKEY>.
  curveSecretKey :: KeyFormat f -> Socket a -> IO SB.ByteString
  curveSecretKey f s = getKey f s B.curveSecretKey
  
  -- Write
  
  -- | <http://api.zeromq.org/4-0:zmq-ctx-set zmq_ctx_get ZMQ_IO_THREADS>.
  setIoThreads :: Word -> Context -> IO ()
  setIoThreads n = setCtxIntOption "ioThreads" _ioThreads n
  
  -- | <http://api.zeromq.org/4-0:zmq-ctx-set zmq_ctx_get ZMQ_MAX_SOCKETS>.
  setMaxSockets :: Word -> Context -> IO ()
  setMaxSockets n = setCtxIntOption "maxSockets" _maxSockets n
  
  -- | Restrict the outgoing and incoming socket buffers to a single message.
  setConflate :: Conflatable a => Bool -> Socket a -> IO ()
  setConflate x s = setIntOpt s B.conflate (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_IMMEDIATE>.
  setImmediate :: Bool -> Socket a -> IO ()
  setImmediate x s = setIntOpt s B.immediate (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_IDENTITY>.
  setIdentity :: Restricted (N1, N254) SB.ByteString -> Socket a -> IO ()
  setIdentity x s = setByteStringOpt s B.identity (rvalue x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_AFFINITY>.
  setAffinity :: Word64 -> Socket a -> IO ()
  setAffinity x s = setIntOpt s B.affinity x
  
  setDelayAttachOnConnect :: Bool -> Socket a -> IO ()
  setDelayAttachOnConnect x s = setIntOpt s B.delayAttachOnConnect (bool2cint x)
  {-# DEPRECATED setDelayAttachOnConnect "Use setImmediate" #-}
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_MAXMSGSIZE>.
  setMaxMessageSize :: Integral i => Restricted (Nneg1, Int64) i -> Socket a -> IO ()
  setMaxMessageSize x s = setIntOpt s B.maxMessageSize ((fromIntegral . rvalue $ x) :: Int64)
  
  setIpv4Only :: Bool -> Socket a -> IO ()
  setIpv4Only x s = setIntOpt s B.ipv4Only (bool2cint x)
  {-# DEPRECATED setIpv4Only "Use setIpv6" #-}
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_IPV6>.
  setIpv6 :: Bool -> Socket a -> IO ()
  setIpv6 x s = setIntOpt s B.ipv6 (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_PLAIN_SERVER>.
  setPlainServer :: Bool -> Socket a -> IO ()
  setPlainServer x s = setIntOpt s B.plainServer (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_CURVE_SERVER>.
  setCurveServer :: Bool -> Socket a -> IO ()
  setCurveServer x s = setIntOpt s B.curveServer (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_PLAIN_USERNAME>.
  setPlainUserName :: Restricted (N1, N254) SB.ByteString -> Socket a -> IO ()
  setPlainUserName x s = setByteStringOpt s B.plainUserName (rvalue x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_PLAIN_USERNAME>.
  setPlainPassword :: Restricted (N1, N254) SB.ByteString -> Socket a -> IO ()
  setPlainPassword x s = setByteStringOpt s B.plainPassword (rvalue x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_LINGER>.
  setLinger :: Integral i => Restricted (Nneg1, Int32) i -> Socket a -> IO ()
  setLinger = setInt32OptFromRestricted B.linger
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RCVTIMEO>.
  setReceiveTimeout :: Integral i => Restricted (Nneg1, Int32) i -> Socket a -> IO ()
  setReceiveTimeout = setInt32OptFromRestricted B.receiveTimeout
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_ROUTER_MANDATORY>.
  setRouterMandatory :: Bool -> Socket Router -> IO ()
  setRouterMandatory x s = setIntOpt s B.routerMandatory (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_SNDTIMEO>.
  setSendTimeout :: Integral i => Restricted (Nneg1, Int32) i -> Socket a -> IO ()
  setSendTimeout = setInt32OptFromRestricted B.sendTimeout
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RATE>.
  setRate :: Integral i => Restricted (N1, Int32) i -> Socket a -> IO ()
  setRate = setInt32OptFromRestricted B.rate
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_MULTICAST_HOPS>.
  setMcastHops :: Integral i => Restricted (N1, Int32) i -> Socket a -> IO ()
  setMcastHops = setInt32OptFromRestricted B.mcastHops
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_BACKLOG>.
  setBacklog :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setBacklog = setInt32OptFromRestricted B.backlog
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_CURVE_PUBLICKEY>.
  setCurvePublicKey :: KeyFormat f -> Restricted f SB.ByteString -> Socket a -> IO ()
  setCurvePublicKey _ k s = setByteStringOpt s B.curvePublicKey (rvalue k)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_CURVE_SECRETKEY>.
  setCurveSecretKey :: KeyFormat f -> Restricted f SB.ByteString -> Socket a -> IO ()
  setCurveSecretKey _ k s = setByteStringOpt s B.curveSecretKey (rvalue k)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_CURVE_SERVERKEY>.
  setCurveServerKey :: KeyFormat f -> Restricted f SB.ByteString -> Socket a -> IO ()
  setCurveServerKey _ k s = setByteStringOpt s B.curveServerKey (rvalue k)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_PROBE_ROUTER>.
  setProbeRouter :: SendProbe a => Bool -> Socket a -> IO ()
  setProbeRouter x s = setIntOpt s B.probeRouter (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RCVBUF>.
  setReceiveBuffer :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setReceiveBuffer = setInt32OptFromRestricted B.receiveBuf
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RECONNECT_IVL>.
  setReconnectInterval :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setReconnectInterval = setInt32OptFromRestricted B.reconnectIVL
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RECONNECT_IVL_MAX>.
  setReconnectIntervalMax :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setReconnectIntervalMax = setInt32OptFromRestricted B.reconnectIVLMax
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_REQ_CORRELATE>.
  setReqCorrelate :: Bool -> Socket Req -> IO ()
  setReqCorrelate x s = setIntOpt s B.reqCorrelate (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_REQ_RELAXED>.
  setReqRelaxed :: Bool -> Socket Req -> IO ()
  setReqRelaxed x s = setIntOpt s B.reqRelaxed (bool2cint x)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_SNDBUF>.
  setSendBuffer :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setSendBuffer = setInt32OptFromRestricted B.sendBuf
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RECOVERY_IVL>.
  setRecoveryInterval :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setRecoveryInterval = setInt32OptFromRestricted B.recoveryIVL
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_RCVHWM>.
  setReceiveHighWM :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setReceiveHighWM = setInt32OptFromRestricted B.receiveHighWM
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_SNDHWM>.
  setSendHighWM :: Integral i => Restricted (N0, Int32) i -> Socket a -> IO ()
  setSendHighWM = setInt32OptFromRestricted B.sendHighWM
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_TCP_ACCEPT_FILTER>.
  setTcpAcceptFilter :: Maybe SB.ByteString -> Socket a -> IO ()
  setTcpAcceptFilter Nothing sock = onSocket "setTcpAcceptFilter" sock $ \s ->
      throwIfMinus1Retry_ "setStrOpt" $
          c_zmq_setsockopt s (optVal tcpAcceptFilter) nullPtr 0
  setTcpAcceptFilter (Just dat) sock = setByteStringOpt sock tcpAcceptFilter dat
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_TCP_KEEPALIVE>.
  setTcpKeepAlive :: Switch -> Socket a -> IO ()
  setTcpKeepAlive x s = setIntOpt s B.tcpKeepAlive (fromSwitch x :: CInt)
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_TCP_KEEPALIVE_CNT>.
  setTcpKeepAliveCount :: Integral i => Restricted (Nneg1, Int32) i -> Socket a -> IO ()
  setTcpKeepAliveCount = setInt32OptFromRestricted B.tcpKeepAliveCount
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_TCP_KEEPALIVE_IDLE>.
  setTcpKeepAliveIdle :: Integral i => Restricted (Nneg1, Int32) i -> Socket a -> IO ()
  setTcpKeepAliveIdle = setInt32OptFromRestricted B.tcpKeepAliveIdle
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_TCP_KEEPALIVE_INTVL>.
  setTcpKeepAliveInterval :: Integral i => Restricted (Nneg1, Int32) i -> Socket a -> IO ()
  setTcpKeepAliveInterval = setInt32OptFromRestricted B.tcpKeepAliveInterval
  
  -- | <http://api.zeromq.org/4-0:zmq-setsockopt zmq_setsockopt ZMQ_XPUB_VERBOSE>.
  setXPubVerbose :: Bool -> Socket XPub -> IO ()
  setXPubVerbose x s = setIntOpt s B.xpubVerbose (bool2cint x)
  
  -- | Bind the socket to the given address
  -- (cf. <http://api.zeromq.org/4-0:zmq-bind zmq_bind>).
  bind :: Socket a -> String -> IO ()
  bind sock str = onSocket "bind" sock $
      throwIfMinus1Retry_ "bind" . withCString str . c_zmq_bind
  
  -- | Unbind the socket from the given address
  -- (cf. <http://api.zeromq.org/4-0:zmq-unbind zmq_unbind>).
  unbind :: Socket a -> String -> IO ()
  unbind sock str = onSocket "unbind" sock $
      throwIfMinus1Retry_ "unbind" . withCString str . c_zmq_unbind
  
  -- | Connect the socket to the given address
  -- (cf. <http://api.zeromq.org/4-0:zmq-connect zmq_connect>).
  connect :: Socket a -> String -> IO ()
  connect sock str = onSocket "connect" sock $
      throwIfMinus1Retry_ "connect" . withCString str . c_zmq_connect
  
  -- | Disconnect the socket from the given endpoint
  -- (cf. <http://api.zeromq.org/4-0:zmq-disconnect zmq_disconnect>).
  disconnect :: Socket a -> String -> IO ()
  disconnect sock str = onSocket "disconnect" sock $
      throwIfMinus1Retry_ "disconnect" . withCString str . c_zmq_disconnect
  
  -- | Send the given 'SB.ByteString' over the socket
  -- (cf. <http://api.zeromq.org/4-0:zmq-sendmsg zmq_sendmsg>).
  --
  -- /Note/: This function always calls @zmq_sendmsg@ in a non-blocking way,
  -- i.e. there is no need to provide the @ZMQ_DONTWAIT@ flag as this is used
  -- by default. Still 'send' is blocking the thread as long as the message
  -- can not be queued on the socket using GHC's 'threadWaitWrite'.
  send :: Sender a => Socket a -> [Flag] -> SB.ByteString -> IO ()
  send sock fls val = bracketOnError (messageOf val) messageClose $ \m -> do
      onSocket "send" sock $ \s ->
          retry "send" (waitWrite sock) $
  #ifdef mingw32_HOST_OS
              c_zmq_sendmsg s (msgPtr m) (combineFlags fls)
  #else
              c_zmq_sendmsg s (msgPtr m) (combineFlags (DontWait : fls))
  #endif
      messageFree m
  
  -- | Send the given 'LB.ByteString' over the socket
  -- (cf. <http://api.zeromq.org/4-0:zmq-sendmsg zmq_sendmsg>).
  --
  -- This is operationally identical to @send socket (Strict.concat
  -- (Lazy.toChunks lbs)) flags@ but may be more efficient.
  --
  -- /Note/: This function always calls @zmq_sendmsg@ in a non-blocking way,
  -- i.e. there is no need to provide the @ZMQ_DONTWAIT@ flag as this is used
  -- by default. Still 'send'' is blocking the thread as long as the message
  -- can not be queued on the socket using GHC's 'threadWaitWrite'.
  send' :: Sender a => Socket a -> [Flag] -> LB.ByteString -> IO ()
  send' sock fls val = bracketOnError (messageOfLazy val) messageClose $ \m -> do
      onSocket "send'" sock $ \s ->
          retry "send'" (waitWrite sock) $
  #ifdef mingw32_HOST_OS
              c_zmq_sendmsg s (msgPtr m) (combineFlags fls)
  #else
              c_zmq_sendmsg s (msgPtr m) (combineFlags (DontWait : fls))
  #endif
      messageFree m
  
  -- | Send a multi-part message.
  -- This function applies the 'SendMore' 'Flag' between all message parts.
  -- 0MQ guarantees atomic delivery of a multi-part message
  -- (cf. <http://api.zeromq.org/4-0:zmq-sendmsg zmq_sendmsg>).
  sendMulti :: Sender a => Socket a -> NonEmpty SB.ByteString -> IO ()
  sendMulti sock msgs = do
      mapM_ (send sock [SendMore]) (S.init msgs)
      send sock [] (S.last msgs)
  
  -- | Receive a 'ByteString' from socket
  -- (cf. <http://api.zeromq.org/4-0:zmq-recvmsg zmq_recvmsg>).
  --
  -- /Note/: This function always calls @zmq_recvmsg@ in a non-blocking way,
  -- i.e. there is no need to provide the @ZMQ_DONTWAIT@ flag as this is used
  -- by default. Still 'receive' is blocking the thread as long as no data
  -- is available using GHC's 'threadWaitRead'.
  receive :: Receiver a => Socket a -> IO (SB.ByteString)
  receive sock = bracket messageInit messageClose $ \m ->
    onSocket "receive" sock $ \s -> do
      retry "receive" (waitRead sock) $
  #ifdef mingw32_HOST_OS
            c_zmq_recvmsg s (msgPtr m) 0
  #else
            c_zmq_recvmsg s (msgPtr m) (flagVal dontWait)
  #endif
      data_ptr <- c_zmq_msg_data (msgPtr m)
      size     <- c_zmq_msg_size (msgPtr m)
      SB.packCStringLen (data_ptr, fromIntegral size)
  
  -- | Receive a multi-part message.
  -- This function collects all message parts send via 'sendMulti'.
  receiveMulti :: Receiver a => Socket a -> IO [SB.ByteString]
  receiveMulti sock = recvall []
    where
      recvall acc = do
          msg <- receive sock
          moreToReceive sock >>= next (msg:acc)
  
      next acc True  = recvall acc
      next acc False = return (reverse acc)
  
  -- | Setup socket monitoring, i.e. a 'Pair' socket which
  -- sends monitoring events about the given 'Socket' to the
  -- given address.
  socketMonitor :: [EventType] -> String -> Socket a -> IO ()
  socketMonitor es addr soc = onSocket "socketMonitor" soc $ \s ->
      withCString addr $ \a ->
          throwIfMinus1_ "zmq_socket_monitor" $
              c_zmq_socket_monitor s a (events2cint es)
  
  -- | Monitor socket events
  -- (cf. <http://api.zeromq.org/4-0:zmq-socket-monitor zmq_socket_monitor>).
  --
  -- This function returns a function which can be invoked to retrieve
  -- the next socket event, potentially blocking until the next one becomes
  -- available. When applied to 'False', monitoring will terminate, i.e.
  -- internal monitoring resources will be disposed. Consequently after
  -- 'monitor' has been invoked, the returned function must be applied
  -- /once/ to 'False'.
  monitor :: [EventType] -> Context -> Socket a -> IO (Bool -> IO (Maybe EventMsg))
  monitor es ctx sock = do
      let addr = "inproc://" ++ show (_socket . _socketRepr $ sock)
      s <- socket ctx Pair
      socketMonitor es addr sock
      connect s addr
      next s <$> messageInit
    where
      next soc m False = messageClose m `finally` close soc >> return Nothing
      next soc m True  = onSocket "recv" soc $ \s -> do
          retry "recv" (waitRead soc) $
  #ifdef mingw32_HOST_OS
              c_zmq_recvmsg s (msgPtr m) 0
  #else
              c_zmq_recvmsg s (msgPtr m) (flagVal dontWait)
  #endif
          evt <- peekZMQEvent (msgPtr m)
          str <- receive soc
          return . Just $ eventMessage str evt
  
  -- | Polls for events on the given 'Poll' descriptors. Returns a list of
  -- events per descriptor which have occured.
  -- (cf. <http://api.zeromq.org/4-0:zmq-poll zmq_poll>)
  poll :: (SocketLike s, MonadIO m) => Timeout -> [Poll s m] -> m [[Event]]
  poll _    [] = return []
  poll to desc = do
      let len = length desc
      let ps  = map toZMQPoll desc
      ps' <- liftIO $ withArray ps $ \ptr -> do
          throwIfMinus1Retry_ "poll" $
              c_zmq_poll ptr (fromIntegral len) (fromIntegral to)
          peekArray len ptr
      mapM fromZMQPoll (zip desc ps')
    where
      toZMQPoll :: (SocketLike s, MonadIO m) => Poll s m -> ZMQPoll
      toZMQPoll (Sock s e _) =
          ZMQPoll (_socket . _socketRepr . toSocket $ s) 0 (combine (map fromEvent e)) 0
  
      toZMQPoll (File (Fd s) e _) =
          ZMQPoll nullPtr (fromIntegral s) (combine (map fromEvent e)) 0
  
      fromZMQPoll :: (SocketLike s, MonadIO m) => (Poll s m, ZMQPoll) -> m [Event]
      fromZMQPoll (p, zp) = do
          let e = toEvents . fromIntegral . pRevents $ zp
          let (e', f) = case p of
                          (Sock _ x g) -> (x, g)
                          (File _ x g) -> (x, g)
          forM f (unless (P.null (e `intersect` e')) . ($ e)) >> return e
  
      fromEvent :: Event -> CShort
      fromEvent In   = fromIntegral . pollVal $ pollIn
      fromEvent Out  = fromIntegral . pollVal $ pollOut
      fromEvent Err  = fromIntegral . pollVal $ pollerr
  
  -- Convert bit-masked word into Event list.
  toEvents :: Word32 -> [Event]
  toEvents e = foldl' (\es f -> f e es) [] tests
    where
        tests =
          [ \i xs -> if i .&. (fromIntegral . pollVal $ pollIn)  /= 0 then In:xs else xs
          , \i xs -> if i .&. (fromIntegral . pollVal $ pollOut) /= 0 then Out:xs else xs
          , \i xs -> if i .&. (fromIntegral . pollVal $ pollerr) /= 0 then Err:xs else xs
          ]
  
  retry :: String -> IO () -> IO CInt -> IO ()
  retry msg wait act = throwIfMinus1RetryMayBlock_ msg act wait
  
  wait' :: ZMQPollEvent -> Socket a -> IO ()
  #ifdef mingw32_HOST_OS
  wait' _ _ = return ()
  #else
  wait' p s = do
      e <- getInt32Option B.events s
      unless (testev e) $ do
          fd <- getIntOpt s B.filedesc 0
          threadWaitRead (Fd fd)
          wait' p s
    where
      testev e = e .&. fromIntegral (pollVal p) /= 0
  #endif
  
  -- | Wait until data is available for reading from the given Socket.
  -- After this function returns, a call to 'receive' will essentially be
  -- non-blocking.
  waitRead :: Socket a -> IO ()
  waitRead = wait' pollIn
  
  -- | Wait until data can be written to the given Socket.
  -- After this function returns, a call to 'send' will essentially be
  -- non-blocking.
  waitWrite :: Socket a -> IO ()
  waitWrite = wait' pollOut
  
  -- | Starts built-in 0MQ proxy
  -- (cf. <http://api.zeromq.org/4-0:zmq-proxy zmq_proxy>)
  --
  -- Proxy connects front to back socket
  --
  -- Before calling proxy all sockets should be bound
  --
  -- If the capture socket is not Nothing, the proxy  shall send all
  -- messages, received on both frontend and backend, to the capture socket.
  proxy :: Socket a -> Socket b -> Maybe (Socket c) -> IO ()
  proxy front back capture =
      onSocket "proxy-front" front $ \f ->
      onSocket "proxy-back"  back  $ \b ->
          throwIfMinus1Retry_ "c_zmq_proxy" $ c_zmq_proxy f b c
    where
      c = maybe nullPtr (_socket . _socketRepr) capture
  
  -- | Generate a new curve key pair.
  -- (cf. <http://api.zeromq.org/4-0:zmq-curve-keypair zmq_curve_keypair>)
  curveKeyPair :: MonadIO m => m (Restricted Div5 SB.ByteString, Restricted Div5 SB.ByteString)
  curveKeyPair = liftIO $
      allocaBytes 41 $ \cstr1 ->
      allocaBytes 41 $ \cstr2 -> do
          throwIfMinus1_ "c_zmq_curve_keypair" $ c_zmq_curve_keypair cstr1 cstr2
          public  <- toRestricted <$> SB.packCString cstr1
          private <- toRestricted <$> SB.packCString cstr2
          maybe (fail errmsg) return ((,) <$> public <*> private)
        where
          errmsg = "curveKeyPair: invalid key-lengths produced"
  
  