diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -55,7 +55,7 @@
 
         repl = do
           q <- getApiQueue
-          liftIO $ evalRepl ">>> " (cmd q) [] (Word completer) ini
+          liftIO $ evalRepl (pure ">>> ") (cmd q) [] Nothing (Word completer) ini
           liftIO $ atomically $ writeTBQueue q DoQuit
 
         cmd :: APIQueue -> String -> Repl ()
diff --git a/app/Match.hs b/app/Match.hs
--- a/app/Match.hs
+++ b/app/Match.hs
@@ -29,28 +29,11 @@
 app = do
   zjoin "a"
   zjoin "b"
+  zjoin "c"
+  zjoin "d"
   forever $ match [
-      handleGroup "a" echo
-    , handleGroup "b" rev
+      isGroupMsg "a" ==> echo
+    , isGroupMsg "b" ==> rev
+    , iff (isGroupMsg "c") echo
+    , decodeShouts (\x -> Right $ B.pack $ show x) (\x -> zshout "d" x)
     ]
-
-isGroupMsg :: Group -> Event -> Bool
-isGroupMsg group (Shout _uuid g _content _time) = g == group
-isGroupMsg _ _ = False
-
-handleGroup :: MonadPlus m => Group -> b -> Event -> m b
-handleGroup group action msg = do
-  guard $ isGroupMsg group msg
-  return $ action
-
-match :: [Event -> Maybe (ZRE ())] -> ZRE ()
-match acts = do
-  msg <- readZ
-  go acts msg
-  where
-    go (act:rest) m = do
-      case act m of
-        Nothing -> go rest m
-        Just a -> unReadZ m >> a
-
-    go [] _ = return ()
diff --git a/src/Data/ZGossip.hs b/src/Data/ZGossip.hs
--- a/src/Data/ZGossip.hs
+++ b/src/Data/ZGossip.hs
@@ -22,9 +22,6 @@
 
 import GHC.Word
 
-import Data.Binary.Strict.Get
-import Data.Binary.Put
-
 import Data.ZMQParse
 
 import Network.ZRE.Utils (bshow)
@@ -65,7 +62,7 @@
 encodeZGS :: ZGSMsg -> B.ByteString
 encodeZGS ZGSMsg{..} = msg
   where
-    msg = BL.toStrict $ runPut $ do
+    msg = runPut $ do
       putWord16be zgsSig
       putWord8 $ cmdCode zgsCmd
       putInt8 $ fromIntegral zgsVer
@@ -103,13 +100,13 @@
 
         return $ ZGSMsg (Just from) zcmd
 
-parseZGS :: [B.ByteString] -> (Either String ZGSMsg, B.ByteString)
+parseZGS :: [B.ByteString] -> Either String ZGSMsg
 parseZGS [from, msg] = parseZgs from msg
-parseZGS x = (Left "empty message", bshow x)
+parseZGS x = Left "empty message"
 
-parseZgs :: B.ByteString -> B.ByteString -> (Either String ZGSMsg, B.ByteString)
+parseZgs :: B.ByteString -> B.ByteString -> Either String ZGSMsg
 parseZgs from msg = flip runGet msg $ do
-  sig <- getWord16be
+  sig <- getInt16
   if sig /= zgsSig
     then fail "Signature mismatch"
     else do
diff --git a/src/Data/ZMQParse.hs b/src/Data/ZMQParse.hs
--- a/src/Data/ZMQParse.hs
+++ b/src/Data/ZMQParse.hs
@@ -1,15 +1,47 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
-module Data.ZMQParse where
+module Data.ZMQParse (
+    getInt8
+  , getInt16
+  , getInt32
+  , parseString
+  , parseStrings
+  , parseLongString
+  , parseKV
+  , parseMap
+  , putByteStringLen
+  , putByteStrings
+  , putLongByteStringLen
+  , putKV
+  , putMap
+  , Get.Get()
+  , runGet
+  , Get.getByteString
+  , Put.Put
+  , Put.PutM
+  , runPut
+  , Put.putInt8
+  , Put.putWord8
+  , Put.putByteString
+  , Put.putWord16be
+  , Put.putWord32be
+  , Put.putInt16be
+  , Put.putInt32be
+  )
+  where
 
 import Prelude hiding (putStrLn, take)
 import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy as BL
 
-import Data.Binary.Strict.Get
-import Data.Binary.Put
+import Data.Binary.Get hiding (getInt8, runGet)
+import Data.Binary.Put hiding (runPut)
 
 import qualified Data.Map as M
 
+import qualified Data.Binary.Get as Get
+import qualified Data.Binary.Put as Put
+
 getInt8 :: (Integral a) => Get a
 getInt8  = fromIntegral <$> getWord8
 getInt16 :: (Integral a) => Get a
@@ -72,3 +104,11 @@
   putInt32be $ fromIntegral $ length ml
   mapM_ putKV ml
   where ml = M.toList m
+
+runGet :: Get a -> B.ByteString -> Either String a
+runGet g b = case Get.runGetOrFail g (BL.fromStrict b) of
+  (Left (_unconsumed, _offset, err)) -> Left err
+  (Right (_unconsumed, _offset, res)) -> Right res
+
+runPut :: Put -> B.ByteString
+runPut = BL.toStrict . Put.runPut
diff --git a/src/Data/ZRE.hs b/src/Data/ZRE.hs
--- a/src/Data/ZRE.hs
+++ b/src/Data/ZRE.hs
@@ -22,9 +22,6 @@
 
 import GHC.Word
 
-import Data.Binary.Strict.Get
-import Data.Binary.Put
-
 import qualified Data.Map as M
 import qualified Data.Set as S
 import Data.UUID
@@ -64,7 +61,7 @@
   deriving (Show, Eq, Ord)
 
 zreBeacon :: B.ByteString -> Port -> B.ByteString
-zreBeacon uuid port = BL.toStrict $ runPut $ do
+zreBeacon uuid port = runPut $ do
   putByteString "ZRE"
   -- XXX: for compatibility with zyre implementation
   -- this should use 0x01 instead, but why when
@@ -84,12 +81,12 @@
     Nothing -> fail "Unable to parse UUID"
 
 parseBeacon :: B.ByteString
-            -> (Either String (B.ByteString, Integer, UUID, Integer), B.ByteString)
+            -> (Either String (B.ByteString, Integer, UUID, Integer))
 parseBeacon = runGet $ do
   lead <- getByteString 3
-  ver <- fromIntegral <$> getWord8
+  ver <- getInt8
   uuid <- parseUUID
-  port <- fromIntegral <$> getWord16be
+  port <- getInt16
   return (lead, ver, uuid, port)
 
 cmdCode :: ZRECmd -> Word8
@@ -112,7 +109,7 @@
 encodeZRE :: ZREMsg -> [B.ByteString]
 encodeZRE ZREMsg{..} = msg:(getContent msgCmd)
   where
-    msg = BL.toStrict $ runPut $ do
+    msg = runPut $ do
       putWord16be zreSig
       putWord8 $ cmdCode msgCmd
       putInt8 $ fromIntegral zreVer
@@ -165,8 +162,8 @@
     sqn <- getInt16
 
     case runGet parseUUID from of
-      (Left _err, _) -> fail "No UUID"
-      (Right uuid, _)-> do
+      (Left err) -> fail $ "No UUID: " ++ err
+      (Right uuid)-> do
         if ver /= zreVer
           then fail "Protocol version mismatch"
           else do
@@ -183,13 +180,13 @@
 
             return $ ZREMsg (Just uuid) sqn Nothing zcmd
 
-parseZRE :: [B.ByteString] -> (Either String ZREMsg, B.ByteString)
+parseZRE :: [B.ByteString] -> Either String ZREMsg
 parseZRE (from:msg:rest) = parseZre from msg rest
-parseZRE _ = (Left "empty message", "")
+parseZRE _ = Left "empty message"
 
-parseZre :: B.ByteString -> B.ByteString -> Content -> (Either String ZREMsg, B.ByteString)
+parseZre :: B.ByteString -> B.ByteString -> Content -> Either String ZREMsg
 parseZre from msg frames = flip runGet msg $ do
-  sig <- getWord16be
+  sig <- getInt16
   if sig /= zreSig
     then fail "Signature mismatch"
     else do
diff --git a/src/Network/ZGossip/ZMQ.hs b/src/Network/ZGossip/ZMQ.hs
--- a/src/Network/ZGossip/ZMQ.hs
+++ b/src/Network/ZGossip/ZMQ.hs
@@ -41,10 +41,10 @@
   let recv = forever $ do
         input <- ZMQ.receiveMulti d
         case parseZGS input of
-           (Left err, _) -> do
+           Left err -> do
              liftIO $ print $ "Malformed gossip message received: " ++ err
              liftIO $ print input
-           (Right msg@ZGSMsg{..}, _) -> do
+           Right msg@ZGSMsg{..} -> do
              liftIO $ handler msg
 
   sa <- ZMQ.async spam
@@ -64,8 +64,8 @@
   forever $ do
      input <- ZMQ.receiveMulti sock
      case parseZGS input of
-        (Left err, _) -> liftIO $ print $ "Malformed gossip message received: " ++ err
-        (Right ZGSMsg{..}, _) -> do
+        Left err -> liftIO $ print $ "Malformed gossip message received: " ++ err
+        Right ZGSMsg{..} -> do
             --liftIO $ print msg
             res <- liftIO $ handler (fromJust zgsFrom) zgsCmd
             flip mapM_ res $ \(to, cmd) -> do
diff --git a/src/Network/ZRE.hs b/src/Network/ZRE.hs
--- a/src/Network/ZRE.hs
+++ b/src/Network/ZRE.hs
@@ -20,10 +20,13 @@
   , zdebug
   , znodebug
   , zquit
+  , zrecv
   , pEndpoint
   , toASCIIBytes
   , getApiQueue
-  , getEventQueue) where
+  , getEventQueue
+  , module Network.ZRE.Lib
+  ) where
 
 import Prelude hiding (putStrLn, take)
 import Control.Monad hiding (join)
@@ -42,6 +45,7 @@
 import qualified Data.ZRE as Z
 import Network.ZRE.Beacon
 import Network.ZRE.Config
+import Network.ZRE.Lib
 import Network.ZRE.Options
 import Network.ZRE.Peer
 import Network.ZRE.Types
@@ -106,15 +110,15 @@
         zrePort <- randPort ipv4
 
         let zreEndpoint = newTCPEndpoint ipv4 zrePort
-        print zreEndpoint
-        B.putStrLn $ "Starting with " <> (bshow zreEndpoint)
+        when zreDbg $ B.putStrLn $ "Starting with " <> (bshow zreEndpoint)
 
         zreName <- getName zreNamed
 
-        inQ <- atomically $ newTBQueue 10000
-        outQ <- atomically $ newTBQueue 10000
+        -- 1M events both ways, not sure about this
+        inQ <- atomically $ newTBQueue 1000000
+        outQ <- atomically $ newTBQueue 1000000
 
-        s <- newZREState zreName zreEndpoint u inQ outQ
+        s <- newZREState zreName zreEndpoint u inQ outQ zreDbg
 
         -- FIXME: support multiple gossip clients
         case zreZGossip of
@@ -124,11 +128,13 @@
         (mCastAddr:_) <- toAddrInfo zreMCast
         _beaconAsync <- async $ beacon mCastAddr uuid zrePort
         _beaconRecvAsync <- async $ beaconRecv s zreMCast
-        apiAsync <- async $ api s
-        _userAppAsync <- async $ runZ app inQ outQ
 
         mapM_ (runIface s zrePort) ifaces
 
+        apiAsync <- async $ api s
+        threadDelay 500000
+        _userAppAsync <- async $ runZ (app >> zquit) inQ outQ
+
         wait apiAsync
         return ()
 
@@ -169,8 +175,16 @@
     DoDebug bool -> atomically $ modifyTVar s $ \x -> x { zreDebug = bool }
 
     DoQuit -> do
-      -- FIXME: wait for empty peer queues
-      threadDelay (sec (1.0 :: Float))
+      let chk = atomically $ do
+                   s' <- readTVar s
+                   pqs <- forM (M.toList $ zrePeers s') $ \(_, tp) -> readTVar tp >>= isEmptyTBQueue . peerQueue
+                   return $ and pqs
+
+      let loop = do
+              res <- chk
+              unless res $ threadDelay (sec (0.1 :: Float)) >> loop
+
+      loop
   where
     incGroupSeq = modifyTVar s $ \x -> x { zreGroupSeq = (zreGroupSeq x) + 1 }
 
@@ -237,7 +251,8 @@
 
         Z.Ping -> atomically $ do
           msgPeer peer Z.PingOk
-          emitdbg s $ "ping"
+          p <- readTVar peer
+          emitdbg s $ B.unwords ["sending pings to ", bshow p]
         Z.PingOk -> return ()
         Z.Hello endpoint groups groupSeq name headers -> do
           -- if this peer was already registered
diff --git a/src/Network/ZRE/Beacon.hs b/src/Network/ZRE/Beacon.hs
--- a/src/Network/ZRE/Beacon.hs
+++ b/src/Network/ZRE/Beacon.hs
@@ -27,8 +27,8 @@
     forever $ do
         (msg, addr) <- recvFrom sock 22
         case parseBeacon msg of
-          (Left err, _remainder) -> print err
-          (Right (_lead, _ver, uuid, port), _) -> do
+          Left err -> print err
+          Right (_lead, _ver, uuid, port) -> do
             case addr of
               x@(SockAddrInet _hisport _host) -> do
                 beaconHandle s (showSockAddrBS x) uuid (fromIntegral port)
diff --git a/src/Network/ZRE/Config.hs b/src/Network/ZRE/Config.hs
--- a/src/Network/ZRE/Config.hs
+++ b/src/Network/ZRE/Config.hs
@@ -3,6 +3,8 @@
 module Network.ZRE.Config where
 
 import System.Environment
+import System.Directory
+import System.FilePath.Posix
 import System.Exit (exitFailure)
 import qualified Data.ByteString.Char8 as B
 import qualified Data.Text as T
@@ -16,13 +18,14 @@
 
 iniParser :: IniParser ZRECfg
 iniParser = section "zre" $ do
-  zreNamed <- B.pack . T.unpack <$> fieldDef "name" (T.pack . B.unpack $ zreNamed def)
-  zreInterfaces <- fieldDefOf "interfaces" (return . map B.pack . words . T.unpack) []
-  zreQuietPeriod <- fieldDefOf "quiet-period"(fmap isec . number) (zreQuietPeriod def)
-  zreDeadPeriod <- fieldDefOf "dead-period"(fmap isec . number) (zreDeadPeriod def)
-  zreBeaconPeriod <- fieldDefOf "beacon-period"(fmap isec . number) (zreBeaconPeriod def)
-  zreZGossip <- fieldDefOf "gossip" (fmap Just . parseAttoTCPEndpoint . B.pack . T.unpack) (zreZGossip def)
-  zreMCast <- fieldDefOf "multicast-group" (parseAttoTCPEndpoint . B.pack . T.unpack) (zreMCast def)
+  zreNamed        <- B.pack . T.unpack <$> fieldDef "name" (T.pack . B.unpack $ zreNamed def)
+  zreInterfaces   <- fieldDefOf "interfaces" (return . map B.pack . words . T.unpack) []
+  zreQuietPeriod  <- fieldDefOf "quiet-period"  (fmap isec . number) (zreQuietPeriod def)
+  zreDeadPeriod   <- fieldDefOf "dead-period"   (fmap isec . number) (zreDeadPeriod def)
+  zreBeaconPeriod <- fieldDefOf "beacon-period" (fmap isec . number) (zreBeaconPeriod def)
+  zreZGossip      <- fieldDefOf "gossip" (fmap Just . parseAttoTCPEndpoint . B.pack . T.unpack) (zreZGossip def)
+  zreMCast        <- fieldDefOf "multicast-group" (parseAttoTCPEndpoint . B.pack . T.unpack) (zreMCast def)
+  zreDbg          <- fieldDefOf "debug" flag (zreDbg def)
   return $ ZRECfg {..}
 
 parseZRECfg :: FilePath -> IO (Either String ZRECfg)
@@ -40,9 +43,21 @@
   menv <- lookupEnv "ZRECFG"
   mname <- lookupEnv "ZRENAME"
   case menv of
-    Nothing -> return $ maybe (def) (\x -> def { zreNamed = B.pack x }) mname
+    Nothing -> do
+      hom <- getHomeDirectory
+      let homPth = hom </> ".zre.conf"
+      tst <- doesFileExist homPth
+      case tst of
+        False -> return $ maybeUpdateName def mname
+        True -> do
+          res <- parseZRECfg homPth
+          case res of
+           Left err -> putStrLn ("Unable to parse config: " ++ err) >> exitFailure
+           Right cfg -> return $ maybeUpdateName cfg mname
     Just env -> do
       res <- parseZRECfg env
       case res of
         Left err -> putStrLn ("Unable to parse config: " ++ err) >> exitFailure
-        Right cfg -> return cfg
+        Right cfg -> return $ maybeUpdateName cfg mname
+  where
+    maybeUpdateName cfg mname = maybe cfg (\x -> cfg { zreNamed = B.pack x}) mname
diff --git a/src/Network/ZRE/Lib.hs b/src/Network/ZRE/Lib.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/ZRE/Lib.hs
@@ -0,0 +1,70 @@
+module Network.ZRE.Lib where
+
+import Control.Applicative
+import Control.Monad
+
+import Data.Either
+import qualified Data.ByteString.Char8 as B
+
+import Data.ZRE (Group)
+import Network.ZRE.Types
+
+zrecvWithShout:: (B.ByteString -> ZRE ()) -> ZRE ()
+zrecvWithShout f = do
+  e <- zrecv
+  case e of
+    Shout _ _ content _time -> f (B.concat content)
+    _ -> return ()
+
+zrecvShouts :: (B.ByteString -> ZRE ()) -> ZRE b
+zrecvShouts fn = forever $ zrecvWithShout fn
+
+whenDecodes :: Monad m
+            => (msg -> Either a decoded)
+            -> (decoded -> m ())
+            -> msg
+            -> m ()
+whenDecodes decoder action content = case decoder content of
+  Left _ -> return ()
+  Right x -> action x
+
+decodeShouts :: (Monad m, Alternative m)
+             => (Event -> Either a decoded)
+             -> (decoded -> ZRE ())
+             -> Event
+             -> m (ZRE ())
+decodeShouts fn action msg = do
+  guard $ isShout msg
+  let res = fn msg
+  guard $ isRight res
+  case res of
+    Left _ -> return $ pure ()
+    Right x -> return $ readZ >> action x
+
+isShout :: Event -> Bool
+isShout (Shout _uuid _group _content _time) = True
+isShout _ = False
+
+isGroupMsg :: Group -> Event -> Bool
+isGroupMsg group (Shout _uuid g _content _time) = g == group
+isGroupMsg _ _ = False
+
+(==>) :: (Monad m, Alternative m) => (t -> Bool) -> b -> t -> m b
+(==>) f act = iff f act
+
+iff :: (Monad m, Alternative m) => (t -> Bool) -> b -> t -> m b
+iff f act msg = do
+  guard $ f msg
+  return $ act
+
+match :: [Event -> Maybe (ZRE ())] -> ZRE ()
+match acts = do
+  msg <- readZ
+  go acts msg
+  where
+    go (act:rest) m = do
+      case act m of
+        Nothing -> go rest m
+        Just a -> unReadZ m >> a
+
+    go [] _ = return ()
diff --git a/src/Network/ZRE/Options.hs b/src/Network/ZRE/Options.hs
--- a/src/Network/ZRE/Options.hs
+++ b/src/Network/ZRE/Options.hs
@@ -52,6 +52,7 @@
       <> short 'g'
       <> metavar "IP:PORT"
       <> help "IP:PORT of the gossip server"))
+  <*> (flag' False (long "debug" <> short 'd'))
 
 attoReadM :: (B.ByteString -> Either String a) -> ReadM a
 attoReadM p = eitherReader (p . B.pack)
diff --git a/src/Network/ZRE/Peer.hs b/src/Network/ZRE/Peer.hs
--- a/src/Network/ZRE/Peer.hs
+++ b/src/Network/ZRE/Peer.hs
@@ -115,7 +115,7 @@
 newPeerFromHello (Hello endpoint groups groupSeq name headers) t uuid s = do
   emitdbg s "New peer from hello"
   newPeer s endpoint uuid groups groupSeq (Just name) headers t
-newPeerFromHello _ _ _ _ = fail "not a hello message"
+newPeerFromHello _ _ _ _ = error "not a hello message"
 
 newPeerFromEndpoint :: MonadIO m
                     => Endpoint
@@ -176,14 +176,17 @@
 
 pinger :: TVar ZREState -> TVar Peer -> IO b
 pinger s peer = forever $ do
-  Peer{..} <- atomically $ readTVar peer
+  p@Peer{..} <- atomically $ readTVar peer
   now <- getCurrentTime
   if diffUTCTime now peerLastHeard > deadPeriod
-    then destroyPeer s peerUUID
+    then do
+      atomically $ emitdbg s $ B.unwords ["Peer over deadPeriod, destroying", bshow p]
+      destroyPeer s peerUUID
     else do
       let tdiff = diffUTCTime now peerLastHeard
       if tdiff > quietPeriod
         then do
+          atomically $ emitdbg s $ B.unwords ["Peer over quietPeriod, sending hugz", bshow p]
           atomically $ writeTBQueue peerQueue $ Ping
           threadDelay quietPingRate
         else do
diff --git a/src/Network/ZRE/Types.hs b/src/Network/ZRE/Types.hs
--- a/src/Network/ZRE/Types.hs
+++ b/src/Network/ZRE/Types.hs
@@ -61,6 +61,7 @@
   , zreInterfaces   :: [B.ByteString]
   , zreMCast        :: Endpoint
   , zreZGossip      :: Maybe Endpoint
+  , zreDbg          :: Bool
   } deriving (Show)
 
 defMCastEndpoint :: Endpoint
@@ -75,6 +76,7 @@
   , zreInterfaces   = []
   , zreZGossip      = Nothing
   , zreMCast        = defMCastEndpoint
+  , zreDbg          = False
   }
 
 instance Default ZRECfg where
@@ -148,10 +150,13 @@
 
 newtype ZRE a = Z {
   runZ' :: ReaderT (EventQueue, APIQueue) IO a
-}
-  deriving (Functor, Applicative, Monad, MonadIO,
-    MonadBase IO,
-    MonadReader (EventQueue, APIQueue))
+  } deriving (
+      Functor
+    , Applicative
+    , Monad
+    , MonadIO
+    , MonadBase IO
+    , MonadReader (EventQueue, APIQueue))
 
 instance MonadBaseControl IO ZRE where
   type StM ZRE a = a
@@ -206,6 +211,9 @@
 zquit :: ZRE ()
 zquit = writeZ $ DoQuit
 
+zrecv :: ZRE (Event)
+zrecv = readZ
+
 maybeM :: Monad m => m b -> (a -> m b) -> m (Maybe a) -> m b
 maybeM err f value = value >>= maybe err f
 
@@ -214,8 +222,9 @@
             -> UUID
             -> EventQueue
             -> APIQueue
+            -> Bool
             -> IO (TVar ZREState)
-newZREState name endpoint u inQ outQ = atomically $ newTVar $
+newZREState name endpoint u inQ outQ dbg = atomically $ newTVar $
   ZREState {
     zreUUID = u
     , zrePeers = M.empty
@@ -225,7 +234,7 @@
     , zreGroupSeq = 0
     , zreName = name
     , zreHeaders = M.empty
-    , zreDebug = False
+    , zreDebug = dbg
     , zreIn = inQ
     , zreOut = outQ
     , zreIfaces = M.empty
diff --git a/src/Network/ZRE/ZMQ.hs b/src/Network/ZRE/ZMQ.hs
--- a/src/Network/ZRE/ZMQ.hs
+++ b/src/Network/ZRE/ZMQ.hs
@@ -44,8 +44,8 @@
      input <- ZMQ.receiveMulti sock
      now <- liftIO $ getCurrentTime
      case parseZRE input of
-        (Left err, _) -> liftIO $ print $ "Malformed message received: " ++ err
-        (Right msg, _) -> do
+        Left err -> liftIO $ print $ "Malformed message received: " ++ err
+        Right msg -> do
           let updateTime = \x -> x { msgTime = Just now }
           void $ liftIO $ handler (updateTime msg)
           return ()
diff --git a/src/System/ZMQ4/Endpoint.hs b/src/System/ZMQ4/Endpoint.hs
--- a/src/System/ZMQ4/Endpoint.hs
+++ b/src/System/ZMQ4/Endpoint.hs
@@ -80,12 +80,13 @@
   t <- A.takeWhile (/=':')
   _ <- string "://"
   r <- case t of
-    "tcp" -> pure TCP
-    "ipc" -> pure IPC
+    "tcp"    -> pure TCP
+    "udp"    -> pure UDP
+    "ipc"    -> pure IPC
     "inproc" -> pure InProc
-    "pgm" -> pure PGM
-    "epgm" -> pure EPGM
-    _ -> fail $ "Unknown transport" ++ (B.unpack t)
+    "pgm"    -> pure PGM
+    "epgm"   -> pure EPGM
+    _ -> fail $ "Unknown transport " ++ (B.unpack t)
 
   return r
 
diff --git a/test/Arbitrary.hs b/test/Arbitrary.hs
new file mode 100644
--- /dev/null
+++ b/test/Arbitrary.hs
@@ -0,0 +1,74 @@
+module Arbitrary where
+
+import Test.QuickCheck
+import Test.QuickCheck.Instances
+
+import Data.ByteString
+import qualified Data.ByteString.Char8 as B
+
+import Data.ZRE
+import qualified Data.ZGossip as ZG
+import System.ZMQ4.Endpoint
+
+instance Arbitrary ZG.ZGSCmd where
+  arbitrary = oneof [
+      return ZG.Hello
+    , ZG.Publish <$> arbitrary <*> arbitrary <*> (abs <$> arbitrary)
+    , return ZG.Ping
+    , return ZG.PingOk
+    , return ZG.Invalid
+    ]
+
+instance Arbitrary ZG.ZGSMsg where
+  arbitrary = ZG.ZGSMsg <$> (Just <$> arbitrary) <*> arbitrary
+
+instance Arbitrary Transport where
+  arbitrary = oneof [
+      pure TCP
+    , pure UDP
+    , pure IPC
+    , pure InProc
+    , pure PGM
+    , pure EPGM
+    ]
+
+instance Arbitrary Endpoint where
+  arbitrary = Endpoint <$> arbitrary
+                       <*> (arbitrary `suchThat` (not . B.elem ':'))
+                       <*> (fmap abs <$> arbitrary)
+
+
+instance Arbitrary ZRECmd where
+  arbitrary = oneof [
+      Hello <$> arbitrary
+            <*> arbitrary
+            <*> (abs <$> arbitrary)
+            <*> arbitrary
+            <*> arbitrary
+
+    , Whisper <$> arbitrary
+    , Shout   <$> arbitrary <*> arbitrary
+
+    , Join  <$> arbitrary
+            <*> (abs <$> arbitrary)
+    , Leave <$> arbitrary
+            <*> (abs <$> arbitrary)
+
+    , pure Ping
+    , pure PingOk
+    ]
+
+instance Arbitrary ZREMsg where
+  arbitrary = ZREMsg <$> (Just <$> arbitrary)
+                     <*> (abs <$> arbitrary)
+                     <*> pure Nothing
+                     <*> arbitrary
+
+roundTrip :: Eq a
+          => (a -> b)
+          -> (b -> Either String a)
+          -> a
+          -> Bool
+roundTrip putter getter x =
+    Right x == (getter . putter $ x)
+
diff --git a/test/Binary.hs b/test/Binary.hs
new file mode 100644
--- /dev/null
+++ b/test/Binary.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Binary where
+
+import Test.QuickCheck
+import Test.QuickCheck.All
+import Test.QuickCheck.Instances
+import System.Exit
+
+import Data.ZMQParse
+
+roundTrip :: Eq a => (a -> Put) -> Get a -> a -> Bool
+roundTrip putter getter x =
+    Right x == runGet getter (runPut (putter x))
+
+prop_i8 = roundTrip putInt8 getInt8
+prop_i16 = roundTrip putInt16be getInt16
+prop_i32 = roundTrip putInt32be getInt32
+
+prop_str = roundTrip putByteStringLen parseString
+
+prop_long_str = roundTrip putLongByteStringLen parseLongString
+
+prop_strings =  roundTrip putByteStrings parseStrings
+
+prop_kv = roundTrip putKV parseKV
+
+prop_map = roundTrip putMap parseMap
+
+return []
+runTests :: IO Bool
+runTests = $quickCheckAll
diff --git a/test/Endpoint.hs b/test/Endpoint.hs
new file mode 100644
--- /dev/null
+++ b/test/Endpoint.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Endpoint where
+
+import Test.QuickCheck.All
+import System.ZMQ4.Endpoint
+
+import Arbitrary
+
+prop_endpoint = roundTrip pEndpoint parseAttoEndpoint
+
+return []
+runTests :: IO Bool
+runTests = $quickCheckAll
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,2 +1,20 @@
+module Main where
+
+import System.Exit
+
+import qualified Endpoint
+import qualified ZGossip
+import qualified ZRE
+import qualified Binary
+
 main :: IO ()
-main = putStrLn "Test suite not yet implemented"
+main = do
+  good <- and <$> sequence [
+      Binary.runTests
+    , Endpoint.runTests
+    , ZGossip.runTests
+    , ZRE.runTests
+    ]
+  if good
+     then exitSuccess
+     else exitFailure
diff --git a/test/ZGossip.hs b/test/ZGossip.hs
new file mode 100644
--- /dev/null
+++ b/test/ZGossip.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module ZGossip where
+
+import Test.QuickCheck.All
+import Data.Maybe
+
+import Data.ZGossip
+
+import Arbitrary
+
+prop_zgs = roundTrip
+  (\z -> (\x -> [fromJust $ zgsFrom z, x]) . encodeZGS $ z)
+  parseZGS
+
+return []
+runTests :: IO Bool
+runTests = $quickCheckAll
diff --git a/test/ZRE.hs b/test/ZRE.hs
new file mode 100644
--- /dev/null
+++ b/test/ZRE.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module ZRE where
+
+import Test.QuickCheck.All
+import Data.Maybe
+
+import Data.UUID (toByteString)
+import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy as BL
+
+import Arbitrary
+import Data.ZRE
+
+prop_zre = roundTrip
+  (\z -> (\x -> ('1' `B.cons` (BL.toStrict $ toByteString $ fromJust $ msgFrom z)):x) . encodeZRE $ z)
+  parseZRE
+
+return []
+runTests :: IO Bool
+runTests = $quickCheckAll
diff --git a/zre.cabal b/zre.cabal
--- a/zre.cabal
+++ b/zre.cabal
@@ -1,14 +1,18 @@
 name:                zre
-version:             0.1.0.1
+version:             0.1.0.2
 synopsis:            ZRE protocol implementation
-description:         See README.rst
-homepage:            https://github.com/vpsfreecz/haskell-zre/
+description:         Peer-to-peer local area networking with reliable group messaging
+                     and automatic peer discovery.
+
+                     ZRE protocol implementation according to https://rfc.zeromq.org/spec:36/ZRE/
+
+homepage:            https://github.com/sorki/haskell-zre/
 license:             BSD3
 license-file:        LICENSE
 author:              Richard Marko
 maintainer:          srk@48.io
-copyright:           2016 Richard Marko
-category:            Web
+copyright:           2020 Richard Marko
+category:            Networking
 build-type:          Simple
 -- extra-source-files:
 cabal-version:       >=1.10
@@ -18,6 +22,7 @@
   exposed-modules:       Network.ZRE
                        , Network.ZRE.Beacon
                        , Network.ZRE.Config
+                       , Network.ZRE.Lib
                        , Network.ZRE.Options
                        , Network.ZRE.Parse
                        , Network.ZRE.Peer
@@ -36,12 +41,14 @@
                      , attoparsec
                      , data-default
                      , network
+                     , network-bsd
                      , network-info
                      , network-multicast
                      , binary
-                     , binary-strict
                      , bytestring
                      , containers
+                     , directory
+                     , filepath
                      , mtl
                      , monad-control
                      , optparse-applicative
@@ -148,11 +155,20 @@
   type:                exitcode-stdio-1.0
   hs-source-dirs:      test
   main-is:             Spec.hs
+  other-modules:       Arbitrary
+                     , Binary
+                     , Endpoint
+                     , ZGossip
+                     , ZRE
   build-depends:       base
+                     , bytestring
                      , zre
+                     , QuickCheck
+                     , quickcheck-instances
+                     , uuid
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N
   default-language:    Haskell2010
 
 source-repository head
   type:     git
-  location: https://git.48.io/zre/
+  location: https://github.com/sorki/haskell-zre
