diff --git a/AUTHORS b/AUTHORS
new file mode 100644
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,3 @@
+Full list of copyright-holders:
+Ivar Nymoen
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+
+Copyright (c) 2013 the nanomsg-haskell authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+# nanomsg-haskell
+
+This is a Haskell binding for the nanomsg library: <http://nanomsg.org/>.
+
+There's support for [blocking](http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Concurrent.html#v:threadWaitRead) send and recv, a non-blocking receive,
+and for all the socket types and the functions you need to wire them up and
+tear them down again.
+
+Most socket options are available through accessor and mutator
+functions. Sockets are typed, transports are not.
+
+
+## Building
+
+You would normally make sure the nanomsg library is on your system and then
+install from Hackage using "cabal update && cabal install nanomsg-haskell",
+but can build from the repository following these steps:
+
+  1. Build and install nanomsg (and zmq3, if you are building benchmarks)
+  1. git clone https://github.com/ivarnymoen/nanomsg-haskell
+  1. cd nanomsg-haskell && cabal sandbox init
+  1. cabal install --dependencies-only [--enable-tests] [--enable-benchmarks]
+  1. cabal configure [--enable-tests] [--enable-benchmarks]
+  1. cabal build
+  1. [cabal test]
+
+
+## Contributing
+
+Just submit a pull request for small stuff, but please get in touch
+beforehand before sinking a lot of effort into major/API changes.
+
+Remember adding your name to the AUTHORS file.
+
+
+## Usage
+
+Simple Pub/sub example:
+
+Server:
+
+    module Main where
+
+    import Nanomsg
+    import qualified Data.ByteString.Char8 as C
+    import Control.Monad (mapM_)
+    import Control.Concurrent (threadDelay)
+
+    main :: IO ()
+    main = do
+        withSocket Pub $ \s -> do
+            _ <- bind s "tcp://*:5560"
+            mapM_ (\num -> sendNumber s num) (cycle [1..1000000])
+        where
+            sendNumber s number = do
+                threadDelay 1000        -- let's conserve some cycles
+                let numAsString = show number
+                send s (C.pack numAsString)
+
+Client:
+
+    module Main where
+
+    import Nanomsg
+    import qualified Data.ByteString.Char8 as C
+    import Control.Monad (forever)
+
+    main :: IO ()
+    main = do
+        withSocket Sub $ \s -> do
+            _ <- connect s "tcp://localhost:5560"
+            subscribe s $ C.pack ""
+            forever $ do
+                msg <- recv s
+                C.putStrLn msg
+
+Nonblocking client:
+
+    module Main where
+
+    import Nanomsg
+    import qualified Data.ByteString.Char8 as C
+    import Control.Monad (forever)
+    import Control.Concurrent (threadDelay)
+
+    main :: IO ()
+    main =
+        withSocket Sub $ \s -> do
+            _ <- connect s "tcp://localhost:5560"
+            subscribe s $ C.pack ""
+            forever $ do
+                threadDelay 700           -- let's conserve some cycles
+                msg <- recv' s
+                C.putStrLn $ case msg of
+                    Nothing -> C.pack "No message"
+                    Just s  -> s
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/benchmarks/SendMessages.hs b/benchmarks/SendMessages.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/SendMessages.hs
@@ -0,0 +1,25 @@
+module Main where
+
+import Nanomsg
+import Criterion.Main
+import qualified Data.ByteString.Char8 as C
+import Control.Monad (replicateM_)
+
+pair :: Int -> Int -> IO ()
+pair size count = do
+    sender <- socket Pair
+    _ <- bind sender "inproc://pairtest"
+    recipient <- socket Pair
+    _ <- connect recipient "inproc://pairtest"
+    let msg = C.pack $ replicate size 'a'
+    replicateM_ count (send sender msg >> recv recipient)
+    close sender
+    close recipient
+    return ()
+
+main :: IO ()
+main = defaultMain
+    [ bench "40 bytes x 10k messages" $ nfIO $ pair    40 10000
+    , bench "20k bytes x 20 messages" $ nfIO $ pair 20000    20
+    ]
+
diff --git a/benchmarks/Zmq.hs b/benchmarks/Zmq.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/Zmq.hs
@@ -0,0 +1,44 @@
+module Main where
+
+import qualified Nanomsg as N
+import qualified System.ZMQ3.Monadic as Z
+import Criterion.Main
+import qualified Data.ByteString.Char8 as C
+import Control.Monad (replicateM_)
+
+nPair :: Int -> Int -> String -> String -> IO ()
+nPair size count bindString connString = do
+    s1 <- N.socket N.Pair
+    _ <- N.bind s1 bindString
+    s2 <- N.socket N.Pair
+    _ <- N.connect s2 connString
+    let msg = C.pack $ replicate size 'a'
+    replicateM_ count (N.send s1 msg >> N.recv s2 >>= N.send s2 >> N.recv s1)
+    N.close s1
+    N.close s2
+    return ()
+
+zPair :: Int -> Int -> String -> String -> IO ()
+zPair size count bindString connString = Z.runZMQ $ do
+    s1 <- Z.socket Z.Pair
+    _ <- Z.bind s1 bindString
+    s2 <- Z.socket Z.Pair
+    _ <- Z.connect s2 connString
+    let msg = C.pack $ replicate size 'a'
+    replicateM_ count (Z.send s1 [] msg >> Z.receive s2 >>= Z.send s2 [] >> Z.receive s1)
+    Z.close s1
+    Z.close s2
+    return ()
+
+main :: IO ()
+main = defaultMain
+    [ bench "nanomsg-haskell: 40 bytes x 1k messages, roundtrip, tcp"     $ nfIO $ nPair    40 1000 "tcp://*:5566" "tcp://localhost:5566"
+    , bench "zeromq3-haskell: 40 bytes x 1k messages, roundtrip, tcp"     $ nfIO $ zPair    40 1000 "tcp://*:5566" "tcp://localhost:5566"
+    , bench "nanomsg-haskell: 20k bytes x 20 messages, roundtrip, tcp"    $ nfIO $ nPair 20000   20 "tcp://*:5566" "tcp://localhost:5566"
+    , bench "zeromq3-haskell: 20k bytes x 20 messages, roundtrip, tcp"    $ nfIO $ zPair 20000   20 "tcp://*:5566" "tcp://localhost:5566"
+    , bench "nanomsg-haskell: 40 bytes x 1k messages, roundtrip, inproc"  $ nfIO $ nPair    40 1000 "inproc://bench" "inproc://bench"
+    , bench "zeromq3-haskell: 40 bytes x 1k messages, roundtrip, inproc"  $ nfIO $ zPair    40 1000 "inproc://bench" "inproc://bench"
+    , bench "nanomsg-haskell: 20k bytes x 20 messages, roundtrip, inproc" $ nfIO $ nPair 20000   20 "inproc://bench" "inproc://bench"
+    , bench "zeromq3-haskell: 20k bytes x 20 messages, roundtrip, inproc" $ nfIO $ zPair 20000   20 "inproc://bench" "inproc://bench"
+    ]
+
diff --git a/nanomsg-haskell.cabal b/nanomsg-haskell.cabal
new file mode 100644
--- /dev/null
+++ b/nanomsg-haskell.cabal
@@ -0,0 +1,77 @@
+name:                nanomsg-haskell
+version:             0.1.0
+synopsis:
+  Bindings to the nanomsg library
+description:
+  This is a Haskell binding for the nanomsg library: <http://nanomsg.org/>.
+
+  There's support for blocking send and recv, a non-blocking receive,
+  and for all the socket types and the functions you need to wire
+  them up and tear them down again.
+
+  Most sockets options are available through accessor and mutator
+  functions. Sockets are typed, transports are not.
+
+homepage:            https://github.com/ivarnymoen/nanomsg-haskell
+license:             MIT
+license-file:        LICENSE
+author:              See AUTHORS file
+maintainer:          <ivar.nymoen@gmail.com>
+copyright:           Copyright (c) 2013 the nanomsg-haskell authors
+category:            Network
+build-type:          Simple
+cabal-version:       >=1.8
+extra-source-files:  AUTHORS, README.md
+
+library
+  hs-source-dirs:    src
+  ghc-options:       -O2 -Wall
+  exposed-modules:   Nanomsg
+  extensions:        ForeignFunctionInterface, DeriveDataTypeable
+  includes:          nanomsg/nn.h
+  extra-libraries:   nanomsg
+  build-depends:
+    base == 4.*,
+    bytestring >= 0.9.0 && < 0.11
+
+test-suite tests
+  type:              exitcode-stdio-1.0
+  hs-source-dirs:    tests
+  main-is:           Properties.hs
+  ghc-options:       -O2 -Wall -threaded
+  build-depends:
+    base == 4.*,
+    bytestring >= 0.9.0 && < 0.11,
+    nanomsg-haskell,
+    QuickCheck,
+    test-framework,
+    test-framework-quickcheck2,
+    test-framework-th
+
+source-repository head
+  type:              git
+  location:          https://github.com/ivarnymoen/nanomsg-haskell
+
+benchmark send-messages
+  type:              exitcode-stdio-1.0
+  main-is:           SendMessages.hs
+  ghc-options:       -O2 -Wall -threaded
+  hs-source-dirs:    benchmarks
+  build-depends:
+    base == 4.*,
+    bytestring >= 0.9.0 && < 0.11,
+    nanomsg-haskell,
+    criterion
+
+benchmark vs-zeromq-bindings
+  type:              exitcode-stdio-1.0
+  main-is:           Zmq.hs
+  ghc-options:       -O2 -Wall -threaded
+  hs-source-dirs:    benchmarks
+  build-depends:
+    base == 4.*,
+    bytestring >= 0.9.0 && < 0.11,
+    nanomsg-haskell,
+    zeromq3-haskell,
+    criterion
+
diff --git a/src/Nanomsg.hsc b/src/Nanomsg.hsc
new file mode 100644
--- /dev/null
+++ b/src/Nanomsg.hsc
@@ -0,0 +1,754 @@
+{-# LANGUAGE ForeignFunctionInterface, DeriveDataTypeable #-}
+-- |
+-- Module:          Nanomsg
+-- Copyright:       (c) 2013 Ivar Nymoen
+-- License:         MIT
+-- Stability:       experimental
+--
+-- This is a Haskell binding for the nanomsg library: <http://nanomsg.org/>.
+--
+-- There's support for blocking send and recv, a non-blocking receive,
+-- and for all the socket types and the functions you need to wire
+-- them up and tear them down again.
+--
+-- Most socket options are available through accessor and mutator
+-- functions. Sockets are typed, transports are not.
+--
+-- Socket type documentation is adapted or quoted verbatim from the
+-- nanomsg manual. Please refer to nanomsg.org for information on
+-- how to use the library.
+module Nanomsg
+        (
+        -- * Socket types
+          Pair(..)
+        , Req(..)
+        , Rep(..)
+        , Pub(..)
+        , Sub(..)
+        , Surveyor(..)
+        , Respondent(..)
+        , Push(..)
+        , Pull(..)
+        , Bus(..)
+        -- * Other types
+        , Socket
+        , Endpoint
+        , NNException
+        -- * Functions
+        , socket
+        , withSocket
+        , bind
+        , connect
+        , send
+        , recv
+        , recv'
+        , subscribe
+        , unsubscribe
+        , shutdown
+        , close
+        , term
+        -- ** Socket option accessors and mutators
+        , linger
+        , setLinger
+        , sndBuf
+        , setSndBuf
+        , rcvBuf
+        , setRcvBuf
+        , reconnectInterval
+        , setReconnectInterval
+        , reconnectIntervalMax
+        , setReconnectIntervalMax
+        , sndPrio
+        , setSndPrio
+        , ipv4Only
+        , setIpv4Only
+        , requestResendInterval
+        , setRequestResendInterval
+        , tcpNoDelay
+        , setTcpNoDelay
+    ) where
+
+#include "nanomsg/nn.h"
+#include "nanomsg/pair.h"
+#include "nanomsg/reqrep.h"
+#include "nanomsg/pubsub.h"
+#include "nanomsg/survey.h"
+#include "nanomsg/pipeline.h"
+#include "nanomsg/bus.h"
+#include "nanomsg/tcp.h"
+
+import Data.ByteString (ByteString)
+-- import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Char8 as C
+import qualified Data.ByteString.Unsafe as U
+import Foreign (peek, poke, alloca)
+import Foreign.Ptr
+import Foreign.C.Types
+import Foreign.C.String
+import Foreign.Storable (sizeOf)
+import Control.Applicative ( (<$>) )
+import Control.Exception.Base (bracket)
+import Control.Exception (Exception, throwIO)
+import Data.Typeable (Typeable)
+import Control.Monad (void)
+import Text.Printf (printf)
+import Control.Concurrent (threadWaitRead, threadWaitWrite)
+import System.Posix.Types (Fd(..))
+
+
+-- * Data and typedefs
+
+-- | Socket for communication with exactly one peer. Each
+-- party can send messages at any time. If the peer is not
+-- available or send buffer is full, subsequent calls to
+-- 'send' will block until it’s possible to send the message.
+data Pair = Pair
+
+-- | Used to implement a client application that sends requests
+-- and receives replies. The socket will resend requests automatically
+-- if there's no reply within a given time. The default timeout
+-- is 1 minute.
+--
+-- See also 'setRequestResendInterval'.
+data Req = Req
+
+-- | Used to implement a stateless worker that receives requests
+-- and sends replies.
+data Rep = Rep
+
+-- | This socket is used to distribute messages to multiple destinations.
+-- Can not receive.
+data Pub = Pub
+
+-- | Receives messages from the publisher. Only messages that the socket is
+-- subscribed to are received. When the socket is created there are no
+-- subscriptions and thus no messages will be received.
+--
+-- Send is not defined on this socket. The socket can be connected
+-- to at most one peer.
+--
+-- See also 'subscribe' and 'unsubscribe'.
+data Sub = Sub
+
+-- | Surveyor and respondent are used to broadcast a survey to multiple
+-- locations and gather the responses.
+--
+-- This socket is used to send the survey. The survey is delivered to all
+-- the connected respondents. Once the query is sent, the socket can be used
+-- to receive the responses.
+--
+-- When the survey deadline expires, receive will return ETIMEDOUT error.
+--
+-- See also 'setSurveyorDeadline'
+data Surveyor = Surveyor
+
+-- | Used to respond to a survey. Survey is received using receive function,
+-- response is sent using send function. This socket can be connected to
+-- at most one peer.
+data Respondent = Respondent
+
+-- | Push and Pull sockets fair queue messages from one processing step, load
+-- balancing them among instances of the next processing step.
+--
+-- This socket is used to send messages to a cluster of load-balanced nodes.
+--
+-- Receive operation is not implemented on this socket type.
+data Push = Push
+
+-- | This socket is used to receive a message from a cluster of nodes.
+--
+-- Send operation is not implemented on this socket type.
+data Pull = Pull
+
+-- | Broadcasts messages from any node to all other nodes in the topology.
+-- The socket should never receives messages that it sent itself.
+--
+-- This pattern scales only to local level (within a single machine or
+-- within a single LAN). Trying to scale it further can result in overloading
+-- individual nodes with messages.
+data Bus = Bus
+
+-- | Endpoint identifier. Created by 'connect' or 'bind'.
+--
+-- Close connections using 'shutdown'.
+data Endpoint = Endpoint CInt
+    deriving (Eq, Show)
+
+-- | Sockets are created by 'socket' and connections are established with 'connect' or 'bind'.
+--
+-- Free sockets using 'close'.
+data Socket a = Socket a CInt
+    deriving (Eq, Show)
+
+-- | Typeclass used by all sockets, to extract their C type.
+class Protocol a where
+    -- | Returns the C enum value for each type. E.g. Pair => #const NN_PAIR
+    protocolId :: a -> CInt
+
+instance Protocol Pair where
+    protocolId Pair = #const NN_PAIR
+
+instance Protocol Req where
+    protocolId Req = #const NN_REQ
+
+instance Protocol Rep where
+    protocolId Rep = #const NN_REP
+
+instance Protocol Pub where
+    protocolId Pub = #const NN_PUB
+
+instance Protocol Sub where
+    protocolId Sub = #const NN_SUB
+
+instance Protocol Surveyor where
+    protocolId Surveyor = #const NN_SURVEYOR
+
+instance Protocol Respondent where
+    protocolId Respondent = #const NN_RESPONDENT
+
+instance Protocol Push where
+    protocolId Push = #const NN_PUSH
+
+instance Protocol Pull where
+    protocolId Pull = #const NN_PULL
+
+instance Protocol Bus where
+    protocolId Bus = #const NN_BUS
+
+
+-- | Typeclass restricting which sockets can use the send function.
+class SendType a
+instance SendType Pair
+instance SendType Req
+instance SendType Rep
+instance SendType Pub
+instance SendType Surveyor
+instance SendType Respondent
+instance SendType Push
+instance SendType Bus
+
+-- | Typeclass for sockets that implement recv
+class RecvType a
+instance RecvType Pair
+instance RecvType Req
+instance RecvType Rep
+instance RecvType Sub
+instance RecvType Surveyor
+instance RecvType Respondent
+instance RecvType Pull
+instance RecvType Bus
+
+-- | Sub socket functionality
+class SubscriberType a
+instance SubscriberType Sub
+
+-- | Surveyor socket functionality
+class SurvType a
+instance SurvType Surveyor
+
+-- | Req socket functionality
+class ReqType a
+instance ReqType Req
+
+
+-- * Error handling
+--
+-- Reimplementing some of Foreign.C.Error here, to substitute nanomsg's errno
+-- and strerror functions for the posix ones.
+
+-- | Pretty much any error condition throws this exception.
+data NNException = NNException String
+        deriving (Eq, Show, Typeable)
+
+instance Exception NNException
+
+mkErrorString :: String -> IO String
+mkErrorString loc = do
+    errNo <- c_nn_errno
+    errCString <- c_nn_strerror errNo
+    errString <- peekCString errCString
+    return $ printf "nanomsg-haskell error at %s. Errno %d: %s" loc (fromIntegral errNo :: Int) errString
+
+throwErrno :: String -> IO a
+throwErrno loc = do
+    s <- mkErrorString loc
+    throwIO $ NNException s
+
+throwErrnoIf :: (a -> Bool) -> String -> IO a -> IO a
+throwErrnoIf p loc action = do
+    res <- action
+    if p res then throwErrno loc else return res
+
+throwErrnoIf_ :: (a -> Bool) -> String -> IO a -> IO ()
+throwErrnoIf_ p loc action = void $ throwErrnoIf p loc action
+
+throwErrnoIfMinus1 :: (Eq a, Num a) => String -> IO a -> IO a
+throwErrnoIfMinus1 = throwErrnoIf (== -1)
+
+throwErrnoIfMinus1_ :: (Eq a, Num a) => String -> IO a -> IO ()
+throwErrnoIfMinus1_ = throwErrnoIf_ (== -1)
+
+throwErrnoIfRetry :: (a -> Bool) -> String -> IO a -> IO a
+throwErrnoIfRetry p loc f = do
+    res <- f
+    if p res
+        then do
+            err <- c_nn_errno
+            if err == (#const EAGAIN) || err == (#const EINTR)
+                then throwErrnoIfRetry p loc f
+                else throwErrno loc
+        else return res
+
+throwErrnoIfRetry_ :: (a -> Bool) -> String -> IO a -> IO ()
+throwErrnoIfRetry_ p loc f = void $ throwErrnoIfRetry p loc f
+
+throwErrnoIfMinus1Retry :: (Eq a, Num a) => String -> IO a -> IO a
+throwErrnoIfMinus1Retry = throwErrnoIfRetry (== -1)
+
+throwErrnoIfMinus1Retry_ :: (Eq a, Num a) => String -> IO a -> IO ()
+throwErrnoIfMinus1Retry_ = throwErrnoIfRetry_ (== -1)
+
+throwErrnoIfRetryMayBlock :: (a -> Bool) -> String -> IO a -> IO b -> IO a
+throwErrnoIfRetryMayBlock p loc f on_block = do
+    res <- f
+    if p res
+        then do
+            err <- c_nn_errno
+            if err `elem` [ (#const EAGAIN), (#const EINTR), (#const EWOULDBLOCK) ]
+                then do
+                    void on_block
+                    throwErrnoIfRetryMayBlock p loc f on_block
+                else throwErrno loc
+        else return res
+
+throwErrnoIfRetryMayBlock_ :: (a -> Bool) -> String -> IO a -> IO b -> IO ()
+throwErrnoIfRetryMayBlock_ p loc f on_block = void $ throwErrnoIfRetryMayBlock p loc f on_block
+
+throwErrnoIfMinus1RetryMayBlock :: (Eq a, Num a) => String -> IO a -> IO b -> IO a
+throwErrnoIfMinus1RetryMayBlock = throwErrnoIfRetryMayBlock (== -1)
+
+throwErrnoIfMinus1RetryMayBlock_ :: (Eq a, Num a) => String -> IO a -> IO b -> IO ()
+throwErrnoIfMinus1RetryMayBlock_ = throwErrnoIfRetryMayBlock_ (== -1)
+
+
+-- * FFI functions
+
+-- NN_EXPORT int nn_socket (int domain, int protocol);
+foreign import ccall unsafe "nn.h nn_socket"
+    c_nn_socket :: CInt -> CInt -> IO CInt
+
+-- NN_EXPORT int nn_bind (int s, const char *addr);
+foreign import ccall unsafe "nn.h nn_bind"
+    c_nn_bind :: CInt -> CString -> IO CInt
+
+-- NN_EXPORT int nn_connect (int s, const char *addr);
+foreign import ccall unsafe "nn.h nn_connect"
+    c_nn_connect :: CInt -> CString -> IO CInt
+
+-- NN_EXPORT int nn_shutdown (int s, int how);
+foreign import ccall unsafe "nn.h nn_shutdown"
+    c_nn_shutdown :: CInt -> CInt -> IO CInt
+
+-- NN_EXPORT int nn_send (int s, const void *buf, size_t len, int flags);
+foreign import ccall unsafe "nn.h nn_send"
+    c_nn_send :: CInt -> CString -> CInt -> CInt -> IO CInt
+
+-- NN_EXPORT int nn_recv (int s, void *buf, size_t len, int flags);
+foreign import ccall unsafe "nn.h nn_recv"
+    c_nn_recv_foreignbuf :: CInt -> Ptr CString -> CInt -> CInt -> IO CInt
+
+-- NN_EXPORT int nn_freemsg (void *msg);
+foreign import ccall unsafe "nn.h nn_freemsg"
+    c_nn_freemsg :: Ptr CChar -> IO CInt
+
+-- NN_EXPORT int nn_close (int s);
+foreign import ccall unsafe "nn.h nn_close"
+    c_nn_close :: CInt -> IO CInt
+
+-- NN_EXPORT void nn_term (void);
+foreign import ccall unsafe "nn.h nn_term"
+    c_nn_term :: IO ()
+
+-- NN_EXPORT int nn_setsockopt (int s, int level, int option, const void *optval, size_t optvallen);
+foreign import ccall unsafe "nn.h nn_setsockopt"
+    c_nn_setsockopt :: CInt -> CInt -> CInt -> Ptr a -> CInt -> IO CInt
+
+-- NN_EXPORT int nn_getsockopt (int s, int level, int option, void *optval, size_t *optvallen);
+foreign import ccall unsafe "nn.h nn_getsockopt"
+    c_nn_getsockopt :: CInt -> CInt -> CInt -> Ptr a -> Ptr CInt -> IO CInt
+
+-- /*  Resolves system errors and native errors to human-readable string.        */
+-- NN_EXPORT const char *nn_strerror (int errnum);
+foreign import ccall unsafe "nn.h nn_strerror"
+    c_nn_strerror :: CInt -> IO CString
+
+-- /*  This function retrieves the errno as it is known to the library.          */
+-- /*  The goal of this function is to make the code 100% portable, including    */
+-- /*  where the library is compiled with certain CRT library (on Windows) and   */
+-- /*  linked to an application that uses different CRT library.                 */
+-- NN_EXPORT int nn_errno (void);
+foreign import ccall unsafe "nn.h nn_errno"
+    c_nn_errno :: IO CInt
+
+{-
+
+Unbound FFI functions:
+
+NN_EXPORT int nn_sendmsg (int s, const struct nn_msghdr *msghdr, int flags);
+NN_EXPORT int nn_recvmsg (int s, struct nn_msghdr *msghdr, int flags);
+
+NN_EXPORT void *nn_allocmsg (size_t size, int type);
+-}
+
+-- * General functions
+
+-- | Creates a socket. Connections are formed using 'bind' or 'connect'.
+--
+-- See also: 'close'.
+socket :: (Protocol a) => a -> IO (Socket a)
+socket t = do
+    sid <- throwErrnoIfMinus1 "socket" $ c_nn_socket (#const AF_SP) (protocolId t)
+    return $ Socket t sid
+
+-- | Creates a socket and runs your action with it.
+--
+-- E.g. collecting 10 messages:
+--
+-- > withSocket Sub $ \sub -> do
+-- >     _ <- connect sub "tcp://localhost:5560"
+-- >     subscribe sub (C.pack "")
+-- >     replicateM 10 (recv sub)
+--
+-- Ensures the socket is closed when your action is done.
+withSocket :: (Protocol a) => a -> (Socket a -> IO b) -> IO b
+withSocket t = bracket (socket t) close
+
+-- | Binds the socket to a local interface.
+--
+-- See the nanomsg documentation for specifics on transports.
+-- Note that host names do not work for tcp. Some examples are:
+--
+-- > bind sock "tcp://*:5560"
+-- > bind sock "tcp://eth0:5560"
+-- > bind sock "tcp://127.0.0.1:5560"
+-- > bind sock "inproc://test"
+-- > bind sock "ipc:///tmp/test.ipc"
+--
+-- This function returns an 'Endpoint', which can be supplied
+-- to 'shutdown' to remove a connection.
+--
+-- See also: 'connect', 'shutdown'.
+bind :: Socket a -> String -> IO Endpoint
+bind (Socket _ sid) addr =
+    withCString addr $ \adr -> do
+        epid <- throwErrnoIfMinus1 "bind" $ c_nn_bind sid adr
+        return $ Endpoint epid
+
+-- | Connects the socket to an endpoint.
+--
+-- e.g. :
+--
+-- > connect sock "tcp://localhost:5560"
+-- > connect sock "inproc://test"
+--
+-- See also: 'bind', 'shutdown'.
+connect :: Socket a -> String -> IO Endpoint
+connect (Socket _ sid) addr =
+    withCString addr $ \adr -> do
+        epid <- throwErrnoIfMinus1 "connect" $ c_nn_connect sid adr
+        return $ Endpoint epid
+
+-- | Removes an endpoint from a socket.
+--
+-- See also: 'bind', 'connect'.
+shutdown :: Socket a -> Endpoint -> IO ()
+shutdown (Socket _ sid) (Endpoint eid) =
+    throwErrnoIfMinus1_ "shutdown" $ c_nn_shutdown sid eid
+
+-- | Blocking function for sending a message
+--
+-- See also: 'recv', 'recv''.
+send :: (SendType a, Protocol a) => Socket a -> ByteString -> IO ()
+send (Socket t sid) string =
+    U.unsafeUseAsCStringLen string $ \(ptr, len) ->
+        throwErrnoIfMinus1RetryMayBlock_
+            "send"
+            (c_nn_send sid ptr (fromIntegral len) (#const NN_DONTWAIT))
+            (getOptionFd (Socket t sid) (#const NN_SNDFD) >>= threadWaitWrite)
+
+-- | Blocking receive.
+recv :: (RecvType a, Protocol a) => Socket a -> IO ByteString
+recv (Socket t sid) =
+    alloca $ \ptr -> do
+        len <- throwErrnoIfMinus1RetryMayBlock
+                "recv"
+                (c_nn_recv_foreignbuf sid ptr (#const NN_MSG) (#const NN_DONTWAIT))
+                (getOptionFd (Socket t sid) (#const NN_RCVFD) >>= threadWaitRead)
+        buf <- peek ptr
+        str <- C.packCStringLen (buf, fromIntegral len)
+        throwErrnoIfMinus1_ "recv freeing message buffer" $ c_nn_freemsg buf
+        return str
+
+-- | Nonblocking receive function.
+recv' :: (RecvType a, Protocol a) => Socket a -> IO (Maybe ByteString)
+recv' (Socket _ sid) =
+    alloca $ \ptr -> do
+        len <- c_nn_recv_foreignbuf sid ptr (#const NN_MSG) (#const NN_DONTWAIT)
+        if len >= 0
+            then do
+                buf <- peek ptr
+                str <- C.packCStringLen (buf, fromIntegral len)
+                throwErrnoIfMinus1_ "recv' freeing message buffer" $ c_nn_freemsg buf
+                return $ Just str
+            else do
+                errno <- c_nn_errno
+                if errno == (#const EAGAIN) || errno == (#const EINTR)
+                    then return Nothing
+                    else throwErrno "recv'"
+
+-- | Subscribe to a given subject string.
+subscribe :: (SubscriberType a, Protocol a) => Socket a -> ByteString -> IO ()
+subscribe (Socket t sid) string =
+    setOption (Socket t sid) (protocolId t) (#const NN_SUB_SUBSCRIBE) (StringOption string)
+
+-- | Unsubscribes from a subject.
+unsubscribe :: (SubscriberType a, Protocol a) => Socket a -> ByteString -> IO ()
+unsubscribe (Socket t sid) string =
+    setOption (Socket t sid) (protocolId t) (#const NN_SUB_UNSUBSCRIBE) (StringOption string)
+
+-- | Closes the socket. Any buffered inbound messages that were not yet
+-- received by the application will be discarded. The library will try to
+-- deliver any outstanding outbound messages for the time specified by
+-- NN_LINGER socket option. The call will block in the meantime.
+close :: Socket a -> IO ()
+close (Socket _ sid) =
+    throwErrnoIfMinus1Retry_ "close" $ c_nn_close sid
+
+-- | Switches nanomsg into shutdown modus and interrupts any waiting
+-- function calls.
+term :: IO ()
+term = c_nn_term
+
+
+-- * Socket option accessors and mutators
+
+-- not sure if this beats having setOptionInt and setOptionString..
+data SocketOption = IntOption Int | StringOption ByteString
+    deriving (Show)
+
+-- Used for setting a socket option.
+setOption :: Socket a -> CInt -> CInt -> SocketOption -> IO ()
+
+setOption (Socket _ sid) level option (IntOption val) =
+    alloca $ \ptr -> do
+        poke ptr (fromIntegral val :: CInt)
+        let cintSize = fromIntegral $ sizeOf (fromIntegral val :: CInt) :: CInt
+        throwErrnoIfMinus1_ "setOption (int)" $ c_nn_setsockopt sid level option ptr cintSize
+
+setOption (Socket _ sid) level option (StringOption str) =
+    throwErrnoIfMinus1_ "setOption (string)" <$> U.unsafeUseAsCStringLen str $
+        \(ptr, len) -> c_nn_setsockopt sid level option ptr (fromIntegral len)
+
+-- Reads a socket option.
+getOption :: Socket a -> CInt -> CInt -> IO CInt
+getOption (Socket _ sid) level option =
+    alloca $ \ptr ->
+        alloca $ \sizePtr -> do
+            let a = 1 :: CInt
+            let cintSize = fromIntegral $ sizeOf a
+            poke sizePtr cintSize
+            throwErrnoIfMinus1_ "getOption" $ c_nn_getsockopt sid level option (ptr :: Ptr CInt) sizePtr
+            value <- peek ptr
+            size <- peek sizePtr
+            if cintSize /= size then throwErrno "getOption: output size not as expected" else return value
+
+-- Retrieves a nanomsg file descriptor for polling ready status.
+getOptionFd :: Socket a -> CInt -> IO Fd
+getOptionFd (Socket _ sid) option =
+    alloca $ \ptr ->
+        alloca $ \sizePtr -> do
+            let a = 1 :: Fd
+            let fdSize = fromIntegral $ sizeOf a
+            poke sizePtr fdSize
+            throwErrnoIfMinus1_ "getOptionFd" $ c_nn_getsockopt sid (#const NN_SOL_SOCKET) option (ptr :: Ptr Fd) sizePtr
+            value <- peek ptr
+            size <- peek sizePtr
+            if fdSize /= size then throwErrno "getOptionFd: output size not as expected" else return value
+
+-- | Specifies how long should the socket try to send pending outbound
+-- messages after close has been called, in milliseconds.
+--
+-- Negative value means infinite linger. Default value is 1000 (1 second).
+linger :: Socket a -> IO Int
+linger s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_LINGER)
+
+-- | Specifies how long should the socket try to send pending outbound
+-- messages after close has been called, in milliseconds.
+--
+-- Negative value means infinite linger. Default value is 1000 (1 second).
+setLinger :: Socket a -> Int -> IO ()
+setLinger s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_LINGER) (IntOption val)
+
+-- | Size of the send buffer, in bytes. To prevent blocking for messages
+-- larger than the buffer, exactly one message may be buffered in addition
+-- to the data in the send buffer.
+--
+-- Default value is 128kB.
+sndBuf :: Socket a -> IO Int
+sndBuf s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_SNDBUF)
+
+-- | Size of the send buffer, in bytes. To prevent blocking for messages
+-- larger than the buffer, exactly one message may be buffered in addition
+-- to the data in the send buffer.
+--
+-- Default value is 128kB.
+setSndBuf :: Socket a -> Int -> IO ()
+setSndBuf s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_SNDBUF) (IntOption val)
+
+-- | Size of the receive buffer, in bytes. To prevent blocking for messages
+-- larger than the buffer, exactly one message may be buffered in addition
+-- to the data in the receive buffer.
+--
+-- Default value is 128kB.
+rcvBuf :: Socket a -> IO Int
+rcvBuf s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_RCVBUF)
+
+-- | Size of the receive buffer, in bytes. To prevent blocking for messages
+-- larger than the buffer, exactly one message may be buffered in addition
+-- to the data in the receive buffer.
+--
+-- Default value is 128kB.
+setRcvBuf :: Socket a -> Int -> IO ()
+setRcvBuf s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_RCVBUF) (IntOption val)
+
+-- Think I'll just skip these. There's recv' for nonblocking receive, and
+-- adding a return value to send seems awkward.
+--sendTimeout
+--recvTimeout
+
+-- | For connection-based transports such as TCP, this option specifies
+-- how long to wait, in milliseconds, when connection is broken before
+-- trying to re-establish it.
+--
+-- Note that actual reconnect interval may be randomised to some extent
+-- to prevent severe reconnection storms.
+--
+-- Default value is 100 (0.1 second).
+reconnectInterval :: Socket a -> IO Int
+reconnectInterval s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_RECONNECT_IVL)
+
+-- | For connection-based transports such as TCP, this option specifies
+-- how long to wait, in milliseconds, when connection is broken before
+-- trying to re-establish it.
+--
+-- Note that actual reconnect interval may be randomised to some extent
+-- to prevent severe reconnection storms.
+--
+-- Default value is 100 (0.1 second).
+setReconnectInterval :: Socket a -> Int -> IO ()
+setReconnectInterval s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_RECONNECT_IVL) (IntOption val)
+
+-- | This option is to be used only in addition to NN_RECONNECT_IVL option.
+-- It specifies maximum reconnection interval. On each reconnect attempt,
+-- the previous interval is doubled until NN_RECONNECT_IVL_MAX is reached.
+--
+-- Value of zero means that no exponential backoff is performed and reconnect
+-- interval is based only on NN_RECONNECT_IVL. If NN_RECONNECT_IVL_MAX is
+-- less than NN_RECONNECT_IVL, it is ignored.
+--
+-- Default value is 0.
+reconnectIntervalMax :: Socket a -> IO Int
+reconnectIntervalMax s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_RECONNECT_IVL_MAX)
+
+-- | This option is to be used only in addition to NN_RECONNECT_IVL option.
+-- It specifies maximum reconnection interval. On each reconnect attempt,
+-- the previous interval is doubled until NN_RECONNECT_IVL_MAX is reached.
+--
+-- Value of zero means that no exponential backoff is performed and reconnect
+-- interval is based only on NN_RECONNECT_IVL. If NN_RECONNECT_IVL_MAX is
+-- less than NN_RECONNECT_IVL, it is ignored.
+--
+-- Default value is 0.
+setReconnectIntervalMax :: Socket a -> Int -> IO ()
+setReconnectIntervalMax s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_RECONNECT_IVL_MAX) (IntOption val)
+
+-- | Sets outbound priority for endpoints subsequently added to the socket.
+-- This option has no effect on socket types that send messages to all the
+-- peers. However, if the socket type sends each message to a single peer
+-- (or a limited set of peers), peers with high priority take precedence over
+-- peers with low priority.
+--
+-- Highest priority is 1, lowest priority is 16. Default value is 8.
+sndPrio :: Socket a -> IO Int
+sndPrio s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_SNDPRIO)
+
+-- | Sets outbound priority for endpoints subsequently added to the socket.
+-- This option has no effect on socket types that send messages to all the
+-- peers. However, if the socket type sends each message to a single peer
+-- (or a limited set of peers), peers with high priority take precedence over
+-- peers with low priority.
+--
+-- Highest priority is 1, lowest priority is 16. Default value is 8.
+setSndPrio :: Socket a -> Int -> IO ()
+setSndPrio s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_SNDPRIO) (IntOption val)
+
+-- | If set to 1, only IPv4 addresses are used. If set to 0, both IPv4
+-- and IPv6 addresses are used.
+--
+-- Default value is 1.
+ipv4Only :: Socket a -> IO Int
+ipv4Only s =
+    fromIntegral <$> getOption s (#const NN_SOL_SOCKET) (#const NN_IPV4ONLY)
+
+-- | If set to 1, only IPv4 addresses are used. If set to 0, both IPv4
+-- and IPv6 addresses are used.
+--
+-- Default value is 1.
+setIpv4Only :: Socket a -> Int -> IO ()
+setIpv4Only s val =
+    setOption s (#const NN_SOL_SOCKET) (#const NN_IPV4ONLY) (IntOption val)
+
+-- | This option is defined on the full REQ socket. If reply is not received
+-- in specified amount of milliseconds, the request will be automatically
+-- resent.
+--
+-- Default value is 60000 (1 minute).
+requestResendInterval :: (ReqType a) => Socket a -> IO Int
+requestResendInterval s =
+    fromIntegral <$> getOption s (#const NN_REQ) (#const NN_REQ_RESEND_IVL)
+
+-- | This option is defined on the full REQ socket. If reply is not received
+-- in specified amount of milliseconds, the request will be automatically
+-- resent.
+--
+-- Default value is 60000 (1 minute).
+setRequestResendInterval :: (ReqType a) => Socket a -> Int -> IO ()
+setRequestResendInterval s val =
+    setOption s (#const NN_REQ) (#const NN_REQ_RESEND_IVL) (IntOption val)
+
+-- | This option, when set to 1, disables Nagle's algorithm.
+--
+-- Default value is 0.
+tcpNoDelay :: Socket a -> IO Int
+tcpNoDelay s =
+    fromIntegral <$> getOption s (#const NN_TCP) (#const NN_TCP_NODELAY)
+
+-- | This option, when set to 1, disables Nagle's algorithm.
+--
+-- Default value is 0.
+setTcpNoDelay :: Socket a -> Int -> IO ()
+setTcpNoDelay s val =
+    setOption s (#const NN_TCP) (#const NN_TCP_NODELAY) (IntOption val)
+
diff --git a/tests/Properties.hs b/tests/Properties.hs
new file mode 100644
--- /dev/null
+++ b/tests/Properties.hs
@@ -0,0 +1,206 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Main where
+
+import Nanomsg
+import Test.Framework.TH (defaultMainGenerator)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+import Test.QuickCheck
+import Test.QuickCheck.Monadic
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as C
+import Control.Concurrent (threadDelay)
+import Control.Applicative ( (<$>) )
+import Data.Maybe (catMaybes)
+
+instance Arbitrary ByteString where
+    arbitrary = C.pack <$> arbitrary
+
+-- dummy test
+prop_reverse :: [Int] -> Bool
+prop_reverse xs =
+    xs == reverse (reverse xs)
+
+-- test Pub and Sub sockets
+prop_PubSub :: Property
+prop_PubSub = monadicIO $ do
+    msgs <- pick arbitrary
+    pre $ not (null msgs)
+    res <- run $ do
+        pub <- socket Pub
+        ep1 <- bind pub "inproc://pubsub"
+        sub1 <- socket Sub
+        ep2 <- connect sub1 "inproc://pubsub"
+        subscribe sub1 $ C.pack ""
+        sub2 <- socket Sub
+        ep3 <- connect sub2 "inproc://pubsub"
+        subscribe sub2 $ C.pack ""
+        threadDelay 1000
+        r <- mapM (sendMsg pub sub1 sub2) msgs
+        unsubscribe sub2 $ C.pack ""
+        unsubscribe sub1 $ C.pack ""
+        shutdown sub2 ep3
+        shutdown sub1 ep2
+        shutdown pub ep1
+        close pub
+        close sub1
+        close sub2
+        threadDelay 1000
+        return r
+    assert $ and res
+        where
+            sendMsg pub sub1 sub2 msg = do
+                send pub msg
+                send pub msg
+                a <- recv sub1
+                b <- recv sub1
+                c <- recv sub2
+                d <- recv sub2
+                return $ a == msg && b == msg && c == msg && d == msg
+
+-- test Pair sockets
+prop_Pair :: Property
+prop_Pair = monadicIO $ do
+    msgs <- pick arbitrary
+    pre $ not (null msgs)
+    res <- run $ do
+        s1 <- socket Pair
+        _ <- bind s1 "inproc://pair"
+        s2 <- socket Pair
+        _ <- connect s2 "inproc://pair"
+        threadDelay 1000
+        -- Send message from s1 to s2, then back from s2 to s1, then make sure it hasn't changed
+        r <- mapM (\m -> send s1 m >> recv s2 >>= send s2 >> recv s1 >>= return . (== m)) msgs
+        close s1
+        close s2
+        threadDelay 1000
+        return r
+    assert $ and res
+
+-- test Pipeline (Push & Pull) sockets
+prop_Pipeline :: Property
+prop_Pipeline = monadicIO $ do
+    msgs <- pick arbitrary
+    pre $ not (null msgs)
+    res <- run $ do
+        push <- socket Push
+        _ <- bind push "inproc://pipeline"
+        pull1 <- socket Pull
+        pull2 <- socket Pull
+        _ <- connect pull1 "inproc://pipeline"
+        _ <- connect pull2 "inproc://pipeline"
+        threadDelay 1000
+        r <- mapM (testSockets push pull1 pull2) msgs
+        close push
+        close pull1
+        close pull2
+        threadDelay 1000
+        return r
+    assert $ and res
+        where
+            testSockets push pull1 pull2 msg = do
+                send push msg
+                send push msg
+                send push msg
+                threadDelay 1000
+                a <- recv' pull1
+                b <- recv' pull1
+                c <- recv' pull1
+                d <- recv' pull2
+                e <- recv' pull2
+                f <- recv' pull2
+                let xs = catMaybes [a, b, c, d, e, f]
+                return $ all (== msg) xs && (length xs == 3)
+
+-- test Req and Rep sockets
+prop_ReqRep :: Property
+prop_ReqRep = monadicIO $ do
+    msgs <- pick arbitrary
+    pre $ not (null msgs)
+    res <- run $ do
+        req <- socket Req
+        _ <- bind req "inproc://reqrep"
+        rep <- socket Rep
+        _ <- connect rep "inproc://reqrep"
+        threadDelay 1000
+        r <- mapM (\m -> send req m >> recv rep >>= send rep >> recv req >>= return . (== m)) msgs
+        close req
+        close rep
+        threadDelay 1000
+        return r
+    assert $ and res
+
+-- test Bus socket
+prop_Bus :: Property
+prop_Bus = monadicIO $ do
+    msgs <- pick arbitrary
+    pre $ not (null msgs)
+    res <- run $ do
+        -- Probably not how you're supposed to connect Bus nodes..
+        b1 <- socket Bus
+        _ <- bind b1 "inproc://bus1"
+        b2 <- socket Bus
+        _ <- connect b2 "inproc://bus1"
+        _ <- bind b2 "inproc://bus2"
+        b3 <- socket Bus
+        _ <- connect b3 "inproc://bus2"
+        _ <- bind b3 "inproc://bus3"
+        _ <- connect b1 "inproc://bus3"
+        threadDelay 1000
+        r <- mapM (testSockets b1 b2 b3) msgs
+        close b1
+        close b2
+        close b3
+        threadDelay 1000
+        return r
+    assert $ and res
+        where
+            testSockets b1 b2 b3 msg = do
+                send b1 msg
+                a <- recv b2
+                b <- recv b3
+                send b2 msg
+                c <- recv b1
+                d <- recv b3
+                send b3 msg
+                e <- recv b1
+                f <- recv b2
+                return $ all (== msg) [a, b, c, d, e, f]
+
+prop_TestOptions :: Property
+prop_TestOptions = monadicIO $ do
+    res <- run $ do
+        req <- socket Req
+        _ <- bind req "tcp://*:5560"
+        threadDelay 1000
+        setTcpNoDelay req 1
+        v1 <- tcpNoDelay req
+        setTcpNoDelay req 0
+        v2 <- tcpNoDelay req
+        setRequestResendInterval req 30000
+        v3 <- requestResendInterval req
+        setIpv4Only req 0
+        v4 <- ipv4Only req
+        setIpv4Only req 1
+        v5 <- ipv4Only req
+        setSndPrio req 7
+        v6 <- sndPrio req
+        setReconnectInterval req 50
+        v7 <- reconnectInterval req
+        setReconnectIntervalMax req 400
+        v8 <- reconnectIntervalMax req
+        setRcvBuf req 200000
+        v9 <- rcvBuf req
+        setSndBuf req 150000
+        v10 <- sndBuf req
+        setLinger req 500
+        v11 <- linger req
+        close req
+        threadDelay 1000
+        return [v1 == 1, v2 == 0, v3 == 30000, v4 == 0, v5 == 1, v6 == 7,
+            v7 == 50, v8 == 400, v9 == 200000, v10 == 150000, v11 == 500]
+    assert $ and res
+
+main :: IO ()
+main = $defaultMainGenerator
+
