diff --git a/Database/Memcache/Client.hs b/Database/Memcache/Client.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/Client.hs
@@ -0,0 +1,3 @@
+-- | A memcache client.
+module Database.Memcache.Client where
+
diff --git a/Database/Memcache/Errors.hs b/Database/Memcache/Errors.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/Errors.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
+-- | Memcache related errors and exception handling.
+module Database.Memcache.Errors (
+        MemcacheError(..),
+        statusToError,
+        throwStatus,
+        throwIncorrectRes
+    ) where
+
+import Database.Memcache.Types
+
+import Control.Exception
+import Data.Typeable
+
+-- | Exceptions that may be thrown by Memcache.
+data MemcacheError
+    = MemErrNoKey
+    | MemErrKeyExists
+    | MemErrValueTooLarge
+    | MemErrInvalidArgs
+    | MemErrStoreFailed
+    | MemErrValueNonNumeric
+    | MemErrUnknownCmd
+    | MemErrOutOfMemory
+    deriving (Eq, Show, Typeable)
+
+instance Exception MemcacheError
+
+-- | Convert a status to an error. Note, not all status's are errors and so
+-- this is a partial function!
+statusToError :: Status -> MemcacheError
+statusToError NoError            = error "statusToError: called on NoError"
+statusToError ErrKeyNotFound     = MemErrNoKey
+statusToError ErrKeyExists       = MemErrKeyExists
+statusToError ErrValueTooLarge   = MemErrValueTooLarge
+statusToError ErrInvalidArgs     = MemErrInvalidArgs
+statusToError ErrItemNotStored   = MemErrStoreFailed
+statusToError ErrValueNonNumeric = MemErrValueNonNumeric
+statusToError ErrUnknownCommand  = MemErrUnknownCmd
+statusToError ErrOutOfMemory     = MemErrOutOfMemory
+statusToError SaslAuthFail       = error "statusToError: called on SaslAuthFail"
+statusToError SaslAuthContinue   = error "statusToError: called on SaslAuthContinue"
+
+-- | Convert a status to an exception. Note, not all status's are errors and so
+-- this is not a complete function!
+throwStatus :: Response -> IO a
+throwStatus = throwIO . statusToError . resStatus
+
+-- | Throw an IncorrectResponse exception for a wrong received response.
+throwIncorrectRes :: Response -> String -> IO a
+throwIncorrectRes r msg = throwIO $
+    IncorrectResponse {
+        increspMessage = "Expected " ++ msg ++ " response! Got: " ++ show (resOp r),
+        increspActual  = r
+    }
+
diff --git a/Database/Memcache/Protocol.hs b/Database/Memcache/Protocol.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/Protocol.hs
@@ -0,0 +1,243 @@
+-- | A raw, low level interface to the memcache protocol.
+--
+-- The various operations are represented in full as they appear at the
+-- protocol level and so aren't generaly well suited for application use.
+-- Instead, applications should use Database.Memcache.Client which presents a
+-- higher level API suited for application use.
+module Database.Memcache.Protocol (
+        get, gat, touch,
+        set, set', add, replace,
+        delete,
+        increment, decrement,
+        append, prepend,
+        flush, noop, version, stats, quit
+    ) where
+
+import Control.Concurrent.MVar
+import Database.Memcache.Errors
+import Database.Memcache.Server
+import Database.Memcache.Types
+
+import Control.Exception
+import Control.Monad
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import Data.Word
+import qualified Network.Socket as N
+
+get :: Connection -> Key -> IO (Maybe (Value, Flags, Version))
+get c k = do
+    let msg = emptyReq { reqOp = ReqGet Loud NoKey k }
+    r <- sendRecv c msg
+    (v, f) <- case resOp r of
+        ResGet Loud v f -> return (v, f)
+        _               -> throwIncorrectRes r "GET"
+    case resStatus r of
+        NoError        -> return $ Just (v, f, resCas r)
+        ErrKeyNotFound -> return Nothing
+        -- XXX: Exception Vs. Either?
+        _              -> throwStatus r
+
+-- XXX: Maybe collapse data structures into single...
+gat :: Connection -> Key -> Expiration -> IO (Maybe (Value, Flags, Version))
+gat c k e = do
+    let msg = emptyReq { reqOp = ReqGAT Loud NoKey k (SETouch e) }
+    r <- sendRecv c msg
+    (v, f) <- case resOp r of
+        ResGAT Loud v f -> return (v, f)
+        _               -> throwIncorrectRes r "GAT"
+    case resStatus r of
+        NoError        -> return $ Just (v, f, resCas r)
+        ErrKeyNotFound -> return Nothing
+        _              -> throwStatus r
+
+touch :: Connection -> Key -> Expiration -> IO (Maybe Version)
+touch c k e = do
+    let msg = emptyReq { reqOp = ReqTouch k (SETouch e) }
+    r <- sendRecv c msg
+    when (resOp r /= ResTouch) $ throwIncorrectRes r "TOUCH"
+    case resStatus r of
+        NoError        -> return $ Just (resCas r)
+        ErrKeyNotFound -> return Nothing
+        _              -> throwStatus r
+
+--
+
+set :: Connection -> Key -> Value -> Flags -> Expiration -> IO Version
+set c k v f e = do
+    let msg = emptyReq { reqOp = ReqSet Loud k v (SESet f e) }
+    r <- sendRecv c msg
+    when (resOp r /= ResSet Loud) $ throwIncorrectRes r "SET"
+    case resStatus r of
+        NoError -> return $ resCas r
+        _       -> throwStatus r
+
+set' :: Connection -> Key -> Value -> Flags -> Expiration -> Version -> IO (Maybe Version)
+set' c k v f e ver = do
+    let msg = emptyReq { reqOp = ReqSet Loud k v (SESet f e), reqCas = ver }
+    r <- sendRecv c msg
+    when (resOp r /= ResSet Loud) $ throwIncorrectRes r "SET"
+    case resStatus r of
+        NoError        -> return $ Just (resCas r)
+        -- version specified and key doesn't exist...
+        ErrKeyNotFound -> return Nothing
+        -- version specified and doesn't match key...
+        ErrKeyExists   -> return Nothing
+        _              -> throwStatus r
+
+add :: Connection -> Key -> Value -> Flags -> Expiration -> IO (Maybe Version)
+add c k v f e = do
+    let msg = emptyReq { reqOp = ReqAdd Loud k v (SESet f e) }
+    r <- sendRecv c msg
+    when (resOp r /= ResAdd Loud) $ throwIncorrectRes r "ADD"
+    case resStatus r of
+        NoError      -> return $ Just (resCas r)
+        ErrKeyExists -> return Nothing
+        _            -> throwStatus r
+
+-- XXX: Structure Vs. Args?
+replace :: Connection -> Key -> Value -> Flags -> Expiration -> Version -> IO (Maybe Version)
+replace c k v f e ver = do
+    let msg = emptyReq { reqOp = ReqReplace Loud k v (SESet f e), reqCas = ver }
+    r <- sendRecv c msg
+    when (resOp r /= ResReplace Loud) $ throwIncorrectRes r "REPLACE"
+    case resStatus r of
+        NoError        -> return $ Just (resCas r)
+        -- replace only applies to an existing key...
+        ErrKeyNotFound -> return Nothing
+        -- version specified and doesn't match key...
+        ErrKeyExists   -> return Nothing
+        _              -> throwStatus r
+
+--
+
+delete :: Connection -> Key -> Version -> IO Bool
+delete c k ver = do
+    let msg = emptyReq { reqOp = ReqDelete Loud k, reqCas = ver }
+    r <- sendRecv c msg
+    when (resOp r /= ResDelete Loud) $ throwIncorrectRes r "DELETE"
+    case resStatus r of
+        NoError        -> return True
+        -- delete only applies to an existing key...
+        ErrKeyNotFound -> return False
+        -- version specified and doesn't match key...
+        ErrKeyExists   -> return False
+        _              -> throwStatus r
+
+--
+
+increment :: Connection -> Key -> Initial -> Delta -> Expiration -> Version -> IO (Maybe (Word64, Version))
+increment c k i d e ver = do
+    let msg = emptyReq { reqOp = ReqIncrement Loud k (SEIncr i d e), reqCas = ver }
+    r <- sendRecv c msg
+    n <- case resOp r of
+        ResIncrement Loud n -> return n
+        _                   -> throwIncorrectRes r "INCREMENT"
+    case resStatus r of
+        NoError        -> return $ Just (n, resCas r)
+        ErrKeyNotFound -> return Nothing
+        ErrKeyExists   -> return Nothing
+        -- XXX: Exception or Nothing for nonnumeric status?
+        _              -> throwStatus r
+
+decrement :: Connection -> Key -> Initial -> Delta -> Expiration -> Version -> IO (Maybe (Word64, Version))
+decrement c k i d e ver = do
+    let msg = emptyReq { reqOp = ReqDecrement Loud k (SEIncr i d e), reqCas = ver }
+    r <- sendRecv c msg
+    n <- case resOp r of
+        ResDecrement Loud n -> return n
+        _                   -> throwIncorrectRes r "DECREMENT"
+    case resStatus r of
+        NoError        -> return $ Just (n, resCas r)
+        -- XXX: Should differentiate, use custom sum, NOT either.
+        ErrKeyNotFound -> return Nothing
+        ErrKeyExists   -> return Nothing
+        -- XXX: Exception or Nothing for nonnumeric status?
+        _              -> throwStatus r
+
+--
+
+-- XXX: Maybe? perhaps should be either so I can indicate why...
+append :: Connection -> Key -> Value -> Version -> IO (Maybe Version)
+append c k v ver = do
+    let msg = emptyReq { reqOp = ReqAppend Loud k v, reqCas = ver }
+    r <- sendRecv c msg
+    when (resOp r /= ResAppend Loud) $ throwIncorrectRes r "APPEND"
+    case resStatus r of
+        NoError        -> return $ Just (resCas r)
+        ErrKeyNotFound -> return Nothing
+        ErrKeyExists   -> return Nothing
+        _              -> throwStatus r
+
+prepend :: Connection -> Key -> Value -> Version -> IO (Maybe Version)
+prepend c k v ver = do
+    let msg = emptyReq { reqOp = ReqPrepend Loud k v, reqCas = ver }
+    r <- sendRecv c msg
+    when (resOp r /= ResPrepend Loud) $ throwIncorrectRes r "PREPEND"
+    case resStatus r of
+        NoError        -> return $ Just (resCas r)
+        ErrKeyNotFound -> return Nothing
+        ErrKeyExists   -> return Nothing
+        _              -> throwStatus r
+
+--
+
+flush :: Connection -> Maybe Expiration -> IO ()
+flush c e = do
+    let e'  = maybe Nothing (\xp -> Just (SETouch xp)) e
+        msg = emptyReq { reqOp = ReqFlush Loud e' }
+    r <- sendRecv c msg
+    when (resOp r /= ResFlush Loud) $ throwIncorrectRes r "FLUSH"
+    case resStatus r of
+        NoError -> return ()
+        _       -> throwStatus r
+
+noop :: Connection -> IO ()
+noop c = do
+    let msg = emptyReq { reqOp = ReqNoop }
+    r <- sendRecv c msg
+    when (resOp r /= ResNoop) $ throwIncorrectRes r "NOOP"
+    case resStatus r of
+        NoError -> return ()
+        _       -> throwStatus r
+
+version :: Connection -> IO ByteString
+version c = do
+    let msg = emptyReq { reqOp = ReqVersion }
+    r <- sendRecv c msg
+    v <- case resOp r of
+        ResVersion v -> return v
+        _            -> throwIncorrectRes r "VERSION"
+    case resStatus r of
+        NoError -> return v
+        _       -> throwStatus r
+
+stats :: Connection -> Maybe Key -> IO (Maybe [(ByteString, ByteString)])
+stats c key = withMVar (conn c) $ \s -> do
+    let msg = emptyReq { reqOp = ReqStat key }
+    send s msg
+    getAllStats s []
+  where
+    getAllStats s xs = do
+        r <- recv s
+        (k, v) <- case resOp r of
+            ResStat k v -> return (k, v)
+            _           -> throwIncorrectRes r "STATS"
+        case resStatus r of
+            NoError | B.null k && B.null v -> return $ Just xs
+                    | otherwise            -> getAllStats s $ (k, v):xs
+            ErrKeyNotFound                 -> return Nothing
+            _                              -> throwStatus r
+
+quit :: Connection -> IO ()
+-- XXX: close can throw, need to handle...
+quit c = withMVar (conn c) $ \s -> finally (N.close s) $ do
+    let msg = emptyReq { reqOp = ReqQuit Loud }
+    send s msg
+    N.shutdown s N.ShutdownSend
+    r <- recv s
+    when (resOp r /= ResQuit Loud) $ throwIncorrectRes r "QUIT"
+    case resStatus r of
+        NoError -> return ()
+        _       -> throwStatus r
+
diff --git a/Database/Memcache/SASL.hs b/Database/Memcache/SASL.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/SASL.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-unused-binds #-}
+
+-- | SASL authentication support for memcached.
+module Database.Memcache.SASL (
+        authenticate, Username, Password
+    ) where
+
+import Database.Memcache.Errors
+import Database.Memcache.Server
+import Database.Memcache.Types
+
+import Control.Monad
+import Data.ByteString
+import qualified Data.ByteString.Char8 as BC
+import Data.Monoid
+
+-- | Username for authentication.
+type Username = ByteString
+
+-- | Password for authentication.
+type Password = ByteString
+
+-- | Perform SASL authentication with the server.
+authenticate :: Connection -> Username -> Password -> IO Bool
+-- NOTE: For correctness really should check that PLAIN auth is supported first
+-- but we'll just assume it is as that's all mainline and other implementations
+-- support and one exception is nearly as good as another.
+authenticate = saslAuthPlain
+
+-- | Perform SASL PLAIN authentication.
+saslAuthPlain :: Connection -> Username -> Password -> IO Bool
+saslAuthPlain c u p = do
+    let credentials = singleton 0 <> u <> singleton 0 <> p
+        msg = emptyReq { reqOp = ReqSASLStart (BC.pack "PLAIN") credentials }
+    r <- sendRecv c msg
+    when (resOp r /= ResSASLStart) $ throwIncorrectRes r "SASL_START"
+    case resStatus r of
+        NoError      -> return True
+        SaslAuthFail -> return False
+        _            -> throwStatus r
+
+-- | List available SASL authentication methods. We could call this but as we
+-- only support PLAIN as does the memcached server, we simply assume PLAIN
+-- authentication is supprted and try that.
+saslListMechs :: Connection -> IO ByteString
+saslListMechs c = do
+    let msg = emptyReq { reqOp = ReqSASLList }
+    r <- sendRecv c msg
+    v <- case resOp r of
+        ResSASLList v -> return v
+        _             -> throwIncorrectRes r "SASL_LIST"
+    case resStatus r of
+        NoError        -> return v
+        _              -> throwStatus r
+
diff --git a/Database/Memcache/Server.hs b/Database/Memcache/Server.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/Server.hs
@@ -0,0 +1,69 @@
+-- | Handles the connections between a memcache client and the various servers
+-- that make up the cluster.
+module Database.Memcache.Server (
+        Connection(..), newMemcacheClient, send, sendRecv, recv
+    ) where
+
+import Control.Concurrent.MVar
+import Database.Memcache.Types
+import Database.Memcache.Wire
+
+import Blaze.ByteString.Builder
+import Control.Exception
+import qualified Data.ByteString.Lazy as L
+
+import Network.BSD (getProtocolNumber, getHostByName, hostAddress)
+import Network.Socket hiding (send, recv)
+import qualified Network.Socket.ByteString as N
+
+-- | A Memcache connection handle.
+-- XXX: Should make abstract
+data Connection = Conn {
+        conn :: MVar Socket
+    }
+
+-- | Establish a new connection to a memcache backend.
+newMemcacheClient :: HostName -> PortNumber -> IO Connection
+newMemcacheClient h p = do
+    s <- connectTo h p
+    m <- newMVar s
+    setSocketOption s KeepAlive 1
+    setSocketOption s NoDelay 1
+    return (Conn m)
+
+-- | Connect to a host. (Internal, socket version of connectTo).
+connectTo :: HostName -> PortNumber -> IO Socket
+connectTo host port = do
+    proto <- getProtocolNumber "tcp"
+    bracketOnError
+        (socket AF_INET Stream proto)
+        (close)
+        (\sock -> do
+            h <- getHostByName host
+            connect sock (SockAddrInet port (hostAddress h))
+            return sock
+        )
+
+-- | Send a request to the memcache cluster.
+send :: Socket -> Request -> IO ()
+-- XXX: catch errors and rethrow as MemcacheErrors?
+send s m = N.sendAll s (toByteString $ szRequest m)
+
+-- | Send a receieve a single request/response pair to the memcache cluster.
+sendRecv :: Connection -> Request -> IO Response
+sendRecv c m = withMVar (conn c) $ \s -> do
+    send s m
+    recv s
+
+-- | Retrieve a single response from the memcache cluster.
+recv :: Socket -> IO Response
+recv s = do
+    -- XXX: recv may return less.
+    header <- N.recv s mEMCACHE_HEADER_SIZE
+    let h = dzHeader' (L.fromChunks [header])
+    if (bodyLen h > 0) then do
+      body <- N.recv s (fromIntegral $ bodyLen h)
+      return $ dzBody' h (L.fromChunks [body])
+      else
+        return $ dzBody' h L.empty
+
diff --git a/Database/Memcache/Types.hs b/Database/Memcache/Types.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/Types.hs
@@ -0,0 +1,155 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
+-- | Stores the various types needed by memcache. Mostly concerned with the
+-- representation of the protocol.
+module Database.Memcache.Types (
+        Q(..), K(..), Key, Value, Extras, Initial, Delta, Expiration, Flags, Version,
+        mEMCACHE_HEADER_SIZE, Header(..),
+        Request(..), OpRequest(..), SESet(..), SEIncr(..), SETouch(..), emptyReq,
+        Response(..), OpResponse(..), Status(..),
+        ProtocolError(..), IncorrectResponse(..)
+    ) where
+
+import Control.Exception
+import Data.ByteString (ByteString)
+import Data.Typeable
+import Data.Word
+
+{- MEMCACHE MESSAGE:
+
+    header {
+        magic    :: Word8
+        op       :: Word8
+        keyLen   :: Word16
+        extraLen :: Word8
+        datatype :: Word8
+        status / reserved :: Word16
+        bodyLen  :: Word32 (total body length)
+        opaque   :: Word32
+        cas      :: Word64
+    }
+    extras :: ByteString
+    key    :: ByteString
+    value  :: ByteString
+ -}
+
+mEMCACHE_HEADER_SIZE :: Int
+mEMCACHE_HEADER_SIZE = 24
+
+data Q          = Loud  | Quiet      deriving (Eq, Show, Typeable)
+data K          = NoKey | IncludeKey deriving (Eq, Show, Typeable)
+type Key        = ByteString
+type Value      = ByteString
+type Extras     = ByteString
+type Initial    = Word64
+type Delta      = Word64
+type Expiration = Word32
+type Flags      = Word32
+type Version    = Word64
+
+-- XXX: Which ones care about version? (Encode?)
+data OpRequest
+    = ReqGet       Q K Key
+    | ReqSet       Q   Key Value SESet
+    | ReqAdd       Q   Key Value SESet
+    | ReqReplace   Q   Key Value SESet
+    | ReqDelete    Q   Key
+    | ReqIncrement Q   Key       SEIncr
+    | ReqDecrement Q   Key       SEIncr
+    | ReqAppend    Q   Key Value
+    | ReqPrepend   Q   Key Value
+    | ReqTouch         Key       SETouch
+    | ReqGAT       Q K Key       SETouch
+    | ReqFlush     Q             (Maybe SETouch)
+    | ReqNoop
+    | ReqVersion
+    | ReqStat          (Maybe Key)
+    | ReqQuit      Q
+    | ReqSASLList
+    | ReqSASLStart     Key Value -- key: auth method, value: auth data
+    | ReqSASLStep      Key Value -- key: auth method, value: auth data (continued)
+    deriving (Eq, Show, Typeable)
+
+data SESet   = SESet   Flags Expiration         deriving (Eq, Show, Typeable)
+data SEIncr  = SEIncr  Initial Delta Expiration deriving (Eq, Show, Typeable)
+data SETouch = SETouch Expiration               deriving (Eq, Show, Typeable)
+
+data Request = Req {
+        reqOp     :: OpRequest,
+        reqOpaque :: Word32,
+        reqCas    :: Version
+    } deriving (Eq, Show, Typeable)
+
+emptyReq :: Request
+emptyReq = Req { reqOp = ReqNoop, reqOpaque = 0, reqCas = 0 }
+
+data OpResponse
+    = ResGet       Q     Value Flags
+    | ResGetK      Q Key Value Flags
+    | ResSet       Q
+    | ResAdd       Q
+    | ResReplace   Q
+    | ResDelete    Q
+    | ResIncrement Q     Word64
+    | ResDecrement Q     Word64
+    | ResAppend    Q
+    | ResPrepend   Q
+    | ResTouch
+    | ResGAT       Q     Value Flags
+    | ResGATK      Q Key Value Flags
+    | ResFlush     Q
+    | ResNoop
+    | ResVersion         Value
+    | ResStat        Key Value
+    | ResQuit      Q
+    | ResSASLList           Value
+    | ResSASLStart
+    | ResSASLStep
+    deriving (Eq, Show, Typeable)
+
+data Status
+    = NoError             -- All
+    | ErrKeyNotFound      -- Get, GAT, Touch, Replace, Del, Inc, Dec, App, Pre, Set (key not there and version specified...)
+    | ErrKeyExists        -- Add, (version): Set, Rep, Del, Inc, Dec, App, Pre
+    | ErrValueTooLarge    -- Set, Add, Rep, Pre, App
+    | ErrInvalidArgs      -- All
+    | ErrItemNotStored    -- ?
+    | ErrValueNonNumeric  -- Incr, Decr
+    | ErrUnknownCommand   -- All
+    | ErrOutOfMemory      -- All
+    | SaslAuthFail        -- SASL
+    | SaslAuthContinue    -- SASL
+    deriving (Eq, Show, Typeable)
+
+data Response = Res {
+        resOp     :: OpResponse,
+        resStatus :: Status,
+        resOpaque :: Word32,
+        resCas    :: Version
+    } deriving (Eq, Show, Typeable)
+
+data Header = Header {
+        op       :: Word8,
+        keyLen   :: Word16,
+        extraLen :: Word8,
+        status   :: Status,
+        bodyLen  :: Word32,
+        opaque   :: Word32,
+        cas      :: Version
+    } deriving (Eq, Show, Typeable)
+
+data ProtocolError = ProtocolError {
+        protocolMessage :: String,
+        protocolHeader  :: Maybe Header,
+        protocolParams  :: [String]
+    } deriving (Eq, Show, Typeable)
+
+instance Exception ProtocolError
+
+data IncorrectResponse = IncorrectResponse {
+        increspMessage :: String,
+        increspActual  :: Response
+    } deriving (Eq, Show, Typeable)
+
+instance Exception IncorrectResponse
+
diff --git a/Database/Memcache/Wire.hs b/Database/Memcache/Wire.hs
new file mode 100644
--- /dev/null
+++ b/Database/Memcache/Wire.hs
@@ -0,0 +1,334 @@
+-- | Deals with serializing and parsing memcached requests and responses.
+module Database.Memcache.Wire (
+        szRequest, szRequest',
+        dzResponse, dzResponse', dzHeader, dzHeader', dzBody, dzBody'
+    ) where
+
+import Database.Memcache.Types
+
+import Control.Exception
+import Control.Monad
+import Blaze.ByteString.Builder
+import Data.Binary.Get
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+import Data.Monoid
+import Data.Word
+
+-- | Serialize a request to a ByteString.
+szRequest' :: Request -> L.ByteString
+szRequest' = toLazyByteString . szRequest
+
+-- | Serialize a request to a ByteString Builder.
+szRequest :: Request -> Builder
+szRequest req =
+       fromWord8 0x80
+    <> fromWord8 c
+    <> fromWord16be (fromIntegral keyl)
+    <> fromWord8 (fromIntegral extl)
+    <> fromWord8 0
+    <> fromWord16be 0
+    <> fromWord32be (fromIntegral $ extl + keyl + vall)
+    <> fromWord32be (reqOpaque req)
+    <> fromWord64be (reqCas req)
+    <> ext'
+    <> key'
+    <> val'
+  where
+    (c, k', v', ext', extl) = getCodeKeyValue (reqOp req)
+    (keyl, key') = case k' of
+        Just k  -> (B.length k, fromByteString k)
+        Nothing -> (0, mempty)
+    (vall, val') = case v' of
+        Just v  -> (B.length v, fromByteString v)
+        Nothing -> (0, mempty)
+
+-- Extract needed info from an OpRequest for serialization.
+-- XXX: Make sure this is optimized well (no tuple, boxing, unboxing, inlined)
+getCodeKeyValue :: OpRequest -> (Word8, Maybe Key, Maybe Value, Builder, Int)
+getCodeKeyValue o = case o of
+    -- XXX: make sure this isn't a thunk! (c)
+    ReqGet      q k key   -> let c | q == Loud && k == NoKey      = 0x00
+                                   | q == Loud && k == IncludeKey = 0x0C
+                                   |              k == NoKey      = 0x09 -- Quiet
+                                   | otherwise                    = 0x0D -- Quiet && IncludeKey
+                             in (c, Just key, Nothing, mempty, 0)
+
+    ReqSet       Loud  key v e -> (0x01, Just key, Just v, szSESet e, 8)
+    ReqSet       Quiet key v e -> (0x11, Just key, Just v, szSESet e, 8)
+
+    ReqAdd       Loud  key v e -> (0x02, Just key, Just v, szSESet e, 8)
+    ReqAdd       Quiet key v e -> (0x12, Just key, Just v, szSESet e, 8)
+
+    ReqReplace   Loud  key v e -> (0x03, Just key, Just v, szSESet e, 8)
+    ReqReplace   Quiet key v e -> (0x13, Just key, Just v, szSESet e, 8)
+
+    ReqDelete    Loud  key     -> (0x04, Just key, Nothing, mempty, 0)
+    ReqDelete    Quiet key     -> (0x14, Just key, Nothing, mempty, 0)
+
+    ReqIncrement Loud  key   e -> (0x05, Just key, Nothing, szSEIncr e, 20)
+    ReqIncrement Quiet key   e -> (0x15, Just key, Nothing, szSEIncr e, 20)
+
+    ReqDecrement Loud  key   e -> (0x06, Just key, Nothing, szSEIncr e, 20)
+    ReqDecrement Quiet key   e -> (0x16, Just key, Nothing, szSEIncr e, 20)
+
+    ReqAppend    Loud  key v   -> (0x0E, Just key, Just v, mempty, 0)
+    ReqAppend    Quiet key v   -> (0x19, Just key, Just v, mempty, 0)
+
+    ReqPrepend   Loud  key v   -> (0x0F, Just key, Just v, mempty, 0)
+    ReqPrepend   Quiet key v   -> (0x1A, Just key, Just v, mempty, 0)
+
+    ReqTouch           key   e -> (0x1C, Just key, Nothing, szSETouch e, 4)
+
+    -- XXX: beware allocation.
+    ReqGAT       q k   key   e -> let c | q == Quiet && k == IncludeKey = 0x24
+                                        | q == Quiet && k == NoKey      = 0x1E
+                                        | k == IncludeKey               = 0x23
+                                        | otherwise                     = 0x1D
+                                  in (c, Just key, Nothing, szSETouch e, 4)
+
+    ReqFlush    Loud  (Just e) -> (0x08, Nothing, Nothing, szSETouch e, 4)
+    ReqFlush    Quiet (Just e) -> (0x18, Nothing, Nothing, szSETouch e, 4)
+    ReqFlush    Loud  Nothing  -> (0x08, Nothing, Nothing, mempty, 0)
+    ReqFlush    Quiet Nothing  -> (0x18, Nothing, Nothing, mempty, 0)
+    ReqNoop                    -> (0x0A, Nothing, Nothing, mempty, 0)
+    ReqVersion                 -> (0x0B, Nothing, Nothing, mempty, 0)
+    ReqStat           key      -> (0x10, key, Nothing, mempty, 0)
+    ReqQuit     Loud           -> (0x07, Nothing, Nothing, mempty, 0)
+    ReqQuit     Quiet          -> (0x17, Nothing, Nothing, mempty, 0)
+
+    ReqSASLList                -> (0x20, Nothing, Nothing, mempty, 0)
+    ReqSASLStart      key v    -> (0x21, Just key, Just v, mempty, 0)
+    ReqSASLStep       key v    -> (0x22, Just key, Just v, mempty, 0)
+
+  where
+    szSESet   (SESet    f e) = fromWord32be f <> fromWord32be e
+    szSEIncr  (SEIncr i d e) = fromWord64be i <> fromWord64be d <> fromWord32be e
+    szSETouch (SETouch    e) = fromWord32be e
+
+-- | Deserialize a Response from a ByteString.
+dzResponse' :: L.ByteString -> Response
+dzResponse' = runGet dzResponse
+
+-- | Deserialize a Response.
+dzResponse :: Get Response
+dzResponse = do
+    h <- dzHeader
+    dzBody h
+
+-- | Deserialize a Header from a ByteString.
+dzHeader' :: L.ByteString -> Header
+dzHeader' = runGet dzHeader
+
+-- | Deserialize a Header.
+dzHeader :: Get Header
+dzHeader = do
+    m   <- getWord8
+    when (m /= 0x81) $ throw
+        ProtocolError {
+            protocolMessage = "Bad magic value for a response packet",
+            protocolHeader  = Nothing,
+            protocolParams  = [show $ m]
+        }
+    o   <- getWord8
+    kl  <- getWord16be
+    el  <- getWord8
+    skip 1 -- unused data type field
+    st  <- dzStatus
+    bl  <- getWord32be
+    opq <- getWord32be
+    ver <- getWord64be
+    return Header {
+            op       = o,
+            keyLen   = kl,
+            extraLen = el,
+            status   = st,
+            bodyLen  = bl,
+            opaque   = opq,
+            cas      = ver
+        }
+
+-- | Deserialize a Response body from a ByteString.
+dzBody' :: Header -> L.ByteString -> Response
+dzBody' h = runGet (dzBody h)
+
+-- | Deserialize a Response body.
+dzBody :: Header -> Get Response
+dzBody h = do
+    case op h of
+        0x00 -> dzGetResponse h $ ResGet Loud
+        0x09 -> dzGetResponse h $ ResGet Quiet
+        0x1D -> dzGetResponse h $ ResGAT Loud
+        0x1E -> dzGetResponse h $ ResGAT Quiet
+        0x0C -> dzGetKResponse h $ ResGetK Loud
+        0x0D -> dzGetKResponse h $ ResGetK Quiet
+        0x23 -> dzGetKResponse h $ ResGATK Loud
+        0x24 -> dzGetKResponse h $ ResGATK Quiet
+        0x05 -> dzNumericResponse h $ ResIncrement Loud
+        0x15 -> dzNumericResponse h $ ResIncrement Quiet
+        0x06 -> dzNumericResponse h $ ResDecrement Loud
+        0x16 -> dzNumericResponse h $ ResDecrement Quiet
+        0x01 -> dzGenericResponse h $ ResSet Loud
+        0x11 -> dzGenericResponse h $ ResSet Quiet
+        0x02 -> dzGenericResponse h $ ResAdd Loud
+        0x12 -> dzGenericResponse h $ ResAdd Quiet
+        0x03 -> dzGenericResponse h $ ResReplace Loud
+        0x13 -> dzGenericResponse h $ ResReplace Quiet
+        0x04 -> dzGenericResponse h $ ResDelete Loud
+        0x14 -> dzGenericResponse h $ ResDelete Quiet
+        0x0E -> dzGenericResponse h $ ResAppend Loud
+        0x19 -> dzGenericResponse h $ ResAppend Quiet
+        0x0F -> dzGenericResponse h $ ResPrepend Loud
+        0x1A -> dzGenericResponse h $ ResPrepend Quiet
+        0x1C -> dzGenericResponse h $ ResTouch
+        0x07 -> dzGenericResponse h $ ResQuit Loud
+        0x17 -> dzGenericResponse h $ ResQuit Quiet
+        0x08 -> dzGenericResponse h $ ResFlush Loud
+        0x18 -> dzGenericResponse h $ ResFlush Quiet
+        0x0A -> dzGenericResponse h ResNoop
+        0x10 -> dzKeyValueResponse h ResStat
+        0x0B -> dzValueResponse h ResVersion
+        -- SASL
+        0x20 -> dzValueResponse h ResSASLList
+        0x21 -> dzGenericResponse h ResSASLStart
+        0x22 -> dzGenericResponse h ResSASLStep
+
+        _    -> throw $ ProtocolError {
+                    protocolMessage = "Unknown operation type",
+                    protocolHeader  = Just h,
+                    protocolParams  = [show $ op h]
+                }
+
+-- | Deserialize the body of a Response that contains nothing.
+dzGenericResponse :: Header -> OpResponse -> Get Response
+dzGenericResponse h o = do
+    skip (fromIntegral $ bodyLen h)
+    chkLength h 0 (extraLen h) "Extra length expected to be zero"
+    chkLength h 0 (keyLen   h) "Key length expected to be zero"
+    chkLength h 0 (bodyLen  h) "Body length expected to be zero"
+    return Res {
+            resOp     = o,
+            resStatus = status h,
+            resOpaque = opaque h,
+            resCas    = cas h
+        }
+
+-- | Deserialize the body of a Get Response (Extras [flags] & Value).
+dzGetResponse :: Header -> (Value -> Flags -> OpResponse) -> Get Response
+dzGetResponse h o = do
+    e <- if status h == NoError && el == 4
+            then getWord32be
+            else skip el >> return 0
+    v <- getByteString vl
+    chkLength h 4 el "Extra length expected to be four"
+    chkLength h 0 (keyLen h) "Key length expected to be zero"
+    return Res {
+            resOp     = o v e,
+            resStatus = status h,
+            resOpaque = opaque h,
+            resCas    = cas h
+        }
+  where
+    el = fromIntegral $ extraLen h
+    vl = fromIntegral (bodyLen h) - el
+    
+-- | Deserialize the body of a GetK Response (Extras [flags] & Key & Value).
+dzGetKResponse :: Header -> (Key -> Value -> Flags -> OpResponse) -> Get Response
+dzGetKResponse h o = do
+    e <- if status h == NoError && el == 4
+            then getWord32be
+            else skip el >> return 0
+    k <- getByteString kl
+    v <- getByteString vl
+    chkLength h 4 el "Extra length expected to be four"
+    -- XXX: check strictness ($!)
+    return Res {
+            resOp     = o k v e,
+            resStatus = status h,
+            resOpaque = opaque h,
+            resCas    = cas h
+        }
+  where
+    el = fromIntegral $ extraLen h
+    kl = fromIntegral $ keyLen h
+    vl = fromIntegral (bodyLen h) - el - kl
+
+-- | Deserialize the body of a Incr/Decr Response (Value [Word64]).
+dzNumericResponse :: Header -> (Word64 -> OpResponse) -> Get Response
+dzNumericResponse h o = do
+    v <- if status h == NoError && bodyLen h == 8
+            then getWord64be
+            else skip (fromIntegral $ bodyLen h) >> return 0
+    chkLength h 0 (extraLen h) "Extra length expected to be zero"
+    chkLength h 0 (keyLen   h) "Key length expected to be zero"
+    chkLength h 8 (bodyLen  h) "body length expected to be eight"
+    return Res {
+            resOp     = o v,
+            resStatus = status h,
+            resOpaque = opaque h,
+            resCas    = cas h
+        }
+
+-- | Deserialize the body of a general response that just has a value (no key
+-- or extras).
+dzValueResponse :: Header -> (Value -> OpResponse) -> Get Response
+dzValueResponse h o = do
+    v <- getByteString (fromIntegral $ bodyLen h)
+    chkLength h 0 (extraLen h) "Extra length expected to be zero"
+    chkLength h 0 (keyLen   h) "Key length expected to be zero"
+    return Res {
+            resOp     = o v,
+            resStatus = status h,
+            resOpaque = opaque h,
+            resCas    = cas h
+        }
+
+-- | Deserialize the body of a general response that just has a key and value
+-- (no extras).
+dzKeyValueResponse :: Header -> (Key -> Value -> OpResponse) -> Get Response
+dzKeyValueResponse h o = do
+    k <- getByteString kl
+    v <- getByteString vl
+    chkLength h 0 (extraLen h) "Extra length expected to be zero"
+    return Res {
+            resOp     = o k v,
+            resStatus = status h,
+            resOpaque = opaque h,
+            resCas    = cas h
+        }
+  where
+    kl = fromIntegral $ keyLen h
+    vl = fromIntegral (bodyLen h) - kl
+
+-- | Deserialize a Response status code.
+dzStatus :: Get Status
+dzStatus = do
+    st <- getWord16be
+    return $ case st of
+        0x00 -> NoError
+        0x01 -> ErrKeyNotFound
+        0x02 -> ErrKeyExists
+        0x03 -> ErrValueTooLarge
+        0x04 -> ErrInvalidArgs
+        0x05 -> ErrItemNotStored
+        0x06 -> ErrValueNonNumeric
+        0x81 -> ErrUnknownCommand
+        0x82 -> ErrOutOfMemory
+        0x20 -> SaslAuthFail
+        0x21 -> SaslAuthContinue
+        _    -> throw $ ProtocolError {
+                    protocolMessage = "Unknown status type",
+                    protocolHeader  = Nothing,
+                    protocolParams  = [show st]
+                }
+
+-- | Check the length of a header field is as expected, throwing a
+-- ProtocolError exception if it is not.
+chkLength :: (Eq a, Show a) => Header -> a -> a -> String -> Get ()
+chkLength h expected l msg =
+    when (l /= expected) $ return $ throw ProtocolError {
+            protocolMessage = msg,
+            protocolHeader  = Just h,
+            protocolParams  = [show l]
+        }
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2012, David Terei.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+# memcache: Haskell Memcache Client
+
+This library provides a client interface to a Memcache cluster. It is
+aimed at full binary protocol support, ease of use and speed.
+
+## Licensing
+
+This library is BSD-licensed.
+
+## To do
+
+* Timeouts
+* Ring / CHORD support
+* Thread-safe?
+
+* mutli(-get)
+
+* Connection drop / reconnect resiliance?
+* Asynchronous support?
+* Connection pooling?
+* Tweaking? (e.g., drop in hash algorithm?, timeout, max connection
+  retries...)
+* Max value validation...?
+
+* Optimizations? http://code.google.com/p/spymemcached/wiki/Optimizations
+
+* Typeclass for serialization?
+* Monad / Typeclass for memcache?
+
+* UDP
+* ASCII
+
+## Other clients
+
+* [C: libmemcached](http://libmemcached.org/libMemcached.html)
+* [Java: SpyMemcached](http://code.google.com/p/spymemcached/)
+* [Ruby: Dalli](https://github.com/mperham/dalli)
+
+## Get involved!
+
+We are happy to receive bug reports, fixes, documentation enhancements,
+and other improvements.
+
+Please report bugs via the
+[github issue tracker](http://github.com/dterei/mc-hs/issues).
+
+Master [git repository](http://github.com/dterei/mc-hs):
+
+* `git clone git://github.com/dterei/mc-hs.git`
+
+## Authors
+
+This library is written and maintained by David Terei,
+<code@davidterei.com>.
+
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/bench/Parser.hs b/bench/Parser.hs
new file mode 100644
--- /dev/null
+++ b/bench/Parser.hs
@@ -0,0 +1,59 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Benchmark the parsing and serialization aspects of memcache.
+module Main where
+
+import Database.Memcache.Types
+import Database.Memcache.Wire
+
+import Criterion.Main
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Lazy.Char8 as LC
+import Data.Monoid
+
+main :: IO ()
+main =
+    defaultMain [
+        bgroup "serialize" [
+            bench "get" $ whnf szRequest' getReqMsg,
+            bench "set" $ whnf szRequest' setReqMsg
+        ],
+        bgroup "deserialize" [
+            bench "get" $ whnf dzResponse' getRespBytes
+        ]
+    ]
+
+getReqMsg :: Request
+getReqMsg = Req {
+        reqOp     = ReqGet Loud NoKey "key!",
+        reqOpaque = 123,
+        reqCas    = 999
+    }
+
+setReqMsg :: Request
+setReqMsg = Req {
+        reqOp     = ReqSet Loud "key!" "hello world" (SESet 10 0),
+        reqOpaque = 123,
+        reqCas    = 999
+    }
+
+getRespHeaderBytes :: L.ByteString
+getRespHeaderBytes =
+    --        magic, op,               extral, 0
+    L.pack $ [0x81, 0x00] ++ keyl' ++ [0x04, 0x00] ++ status' ++ bodyl' ++ opaque' ++ cas'
+  where
+    keyl'   = [0x00, 0x00]
+    status' = [0x00, 0x00, 0x00, 0x00]
+    bodyl'  = [0x00, 0x00, 0x00, 0x08]
+    opaque' = [0x00, 0x00, 0x00, 0x07]
+    cas'    = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09]
+
+getRespBytes :: L.ByteString
+getRespBytes = getRespHeaderBytes <> extras' <> key' <> value'
+  where
+    extras' = L.pack  [0x00, 0x00, 0x00, 0x01] -- BE: so 1?
+    key'    = L.pack  []
+    value'  = LC.pack "12345678"
+
diff --git a/memcache.cabal b/memcache.cabal
new file mode 100644
--- /dev/null
+++ b/memcache.cabal
@@ -0,0 +1,71 @@
+name:           memcache
+version:        0.0.0
+homepage:       https://github.com/dterei/memcache-hs
+bug-reports:    https://github.com/dterei/memcache-hs/issues
+synopsis:       A memcached client library.
+description:    
+  A client library for a memcached cluster. It is aimed at full binary protocol
+  support, ease of use and speed.
+license:        BSD3
+license-file:   LICENSE
+author:         David Terei <code@davidterei.com>
+maintainer:     David Terei <code@davidterei.com>
+copyright:      2013 David Terei.
+category:       Database
+build-type:     Simple
+cabal-version:  >= 1.8
+extra-source-files:
+  README.md
+
+library
+  exposed-modules:
+    Database.Memcache.Client
+    Database.Memcache.Errors
+    Database.Memcache.Protocol
+    Database.Memcache.SASL
+    Database.Memcache.Server
+    Database.Memcache.Types
+    Database.Memcache.Wire
+
+  build-depends:
+    base < 5,
+    binary >= 0.6.2.0,
+    blaze-builder >= 0.3.1.0,
+    bytestring >= 0.9.2.1,
+    network >= 2.4
+
+  ghc-options: -Wall
+  if impl(ghc >= 6.8)
+    ghc-options: -fwarn-tabs
+
+test-suite full
+  type:           exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is:        Full.hs
+
+  build-depends:
+    base < 5,
+    bytestring >= 0.9.2.1,
+    memcache
+
+  if impl(ghc >= 6.8)
+    ghc-options: -fwarn-tabs
+
+benchmark parser
+  type:           exitcode-stdio-1.0
+  hs-source-dirs: bench
+  main-is:        Parser.hs
+
+  build-depends:
+    base < 5,
+    bytestring >= 0.9.2.1,
+    criterion > 0.6.0.0,
+    memcache
+
+  if impl(ghc >= 6.8)
+    ghc-options: -fwarn-tabs
+
+source-repository head
+  type:     git
+  location: http://github.com/dterei/memcache-hs
+
diff --git a/test/Full.hs b/test/Full.hs
new file mode 100644
--- /dev/null
+++ b/test/Full.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import qualified Database.Memcache.Protocol as M
+import Database.Memcache.Server
+
+import Control.Monad
+import qualified Data.ByteString.Char8 as BC
+import System.Exit
+
+main :: IO ()
+main = do
+    c <- newMemcacheClient "localhost" 11211
+    getTest c
+    exitSuccess
+
+getTest :: Connection -> IO ()
+getTest c = do
+    v <- M.set c (BC.pack "key") (BC.pack "world") 0 0
+    Just (v', _, _) <- M.get c "key"
+    when (v' /= "world") $ do
+        putStrLn $ "bad value returned! " ++ show v'
+        exitFailure 
+
+deleteTest :: Connection -> IO ()
+deleteTest c = do
+    v1 <- M.set c "key" "world" 0 0
+    v2 <- M.set c "key" "world22" 0 0
+    when (v1 == v2) $ do
+        putStrLn $ "bad versions! " ++ show v1 ++ ", " ++ show v2
+        exitFailure
+    r <- M.delete c "key" 0
+    when (not r) $ do
+        putStrLn "delete failed!"
+        exitFailure
+
