diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT
+OWNER 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,21 @@
+# FTP Client
+
+ftp-client is a client library for the FTP protocol in Haskell.
+
+# Examples
+
+## Insecure
+```haskell
+withFTP "ftp.server.com" 21 $ \h welcome -> do
+    print welcome
+    login h "username" "password"
+    print =<< nlst h []
+```
+
+## Secured with TLS
+```haskell
+withFTPS "ftps.server.com" 21 $ \h welcome -> do
+    print welcome
+    login h "username" "password"
+    print =<< nlstS h []
+```
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/ftp-client.cabal b/ftp-client.cabal
new file mode 100644
--- /dev/null
+++ b/ftp-client.cabal
@@ -0,0 +1,37 @@
+name:                ftp-client
+version:             0.1.0.0
+synopsis:            Transfer files with FTP and FTPS
+description:         Please see README.md
+homepage:            https://github.com/mr/ftp-client
+license:             PublicDomain
+maintainer:          mrobinson7627@gmail.com
+license-file:        LICENSE
+author:              Matthew Robinson
+category:            Web
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Network.FTP.Client
+  build-depends:       base >= 4.7 && < 5
+                     , bytestring
+                     , network
+                     , attoparsec >= 0.10
+                     , connection >= 0.2
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
+
+test-suite ftp-client-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:       base
+                     , ftp-client
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/mr/ftp-client
diff --git a/src/Network/FTP/Client.hs b/src/Network/FTP/Client.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/FTP/Client.hs
@@ -0,0 +1,579 @@
+{-|
+Module      : Network.FTP.Client
+Description : Transfer files over FTP and FTPS
+License     : Public Domain
+Stability   : experimental
+Portability : POSIX
+-}
+module Network.FTP.Client (
+    -- * Main Entrypoints
+    withFTP,
+    withFTPS,
+    -- * Control Commands
+    login,
+    pasv,
+    rename,
+    dele,
+    cwd,
+    size,
+    mkd,
+    rmd,
+    pwd,
+    quit,
+    -- * Data Commands
+    nlst,
+    retr,
+    list,
+    stor,
+    -- * Secure Data Commands
+    nlstS,
+    retrS,
+    listS,
+    storS,
+    -- * Types
+    FTPCommand(..),
+    FTPResponse(..),
+    ResponseStatus(..),
+    RTypeCode(..),
+    PortActivity(..),
+    Handle(..),
+    -- * Handle Implementations
+    sIOHandleImpl,
+    tlsHandleImpl,
+    -- * Lower Level Functions
+    sendCommand,
+    sendCommands,
+    getLineResp,
+    getMultiLineResp,
+    sendCommandLine,
+    createDataSocket,
+    createSendDataCommand,
+    createTLSSendDataCommand
+) where
+
+import qualified Data.ByteString.Char8 as C
+import qualified Data.ByteString as B
+import Data.ByteString (ByteString)
+import Data.List
+import Data.Attoparsec.ByteString.Char8
+import qualified Network.Socket as S
+import qualified System.IO as SIO
+import Data.Monoid ((<>), mconcat)
+import Control.Exception
+import Control.Monad
+import Data.Bits
+import Network.Connection
+import System.IO.Error
+import Data.ByteString.Lazy.Internal (defaultChunkSize)
+
+-- | Can send and recieve a 'Data.ByteString.ByteString'.
+data Handle = Handle
+    { send :: ByteString -> IO ()
+    , sendLine :: ByteString -> IO ()
+    , recv :: Int -> IO ByteString
+    , recvLine :: IO ByteString
+    }
+
+-- | Response from an FTP command. ex "200 Welcome!"
+data FTPResponse = FTPResponse {
+    frStatus :: ResponseStatus, -- ^ Interpretation of the first digit of an FTP response code
+    frCode :: Int, -- ^ The three digit response code
+    frMessage :: ByteString -- ^ Text of the response
+}
+
+instance Show FTPResponse where
+    show fr = (show $ frCode fr) <> " " <> (C.unpack $ frMessage fr)
+
+-- | First digit of an FTP response
+data ResponseStatus
+    = Wait -- ^ 1
+    | Success -- ^ 2
+    | Continue -- ^ 3
+    | FailureRetry -- ^ 4
+    | Failure -- ^ 5
+    deriving (Show)
+
+responseStatus :: ByteString -> ResponseStatus
+responseStatus cbs =
+    case C.uncons cbs of
+        Just ('1', _) -> Wait
+        Just ('2', _) -> Success
+        Just ('3', _) -> Continue
+        Just ('4', _) -> FailureRetry
+        _             -> Failure
+
+data RTypeCode = TA | TI
+
+serialzeRTypeCode :: RTypeCode -> String
+serialzeRTypeCode TA = "A"
+serialzeRTypeCode TI = "I"
+
+data PortActivity = Active | Passive
+
+data ProtType = P | C
+
+-- | Commands according to the FTP specification
+data FTPCommand
+    = User String
+    | Pass String
+    | Acct String
+    | RType RTypeCode
+    | Retr String
+    | Nlst [String]
+    | Port S.HostAddress S.PortNumber
+    | Stor String
+    | List [String]
+    | Rnfr String
+    | Rnto String
+    | Dele String
+    | Size String
+    | Mkd String
+    | Rmd String
+    | Pbsz Int
+    | Prot ProtType
+    | Cwd String
+    | Cdup
+    | Ccc
+    | Auth
+    | Pwd
+    | Abor
+    | Pasv
+    | Quit
+
+instance Show FTPCommand where
+    show = serializeCommand
+
+formatPort :: S.HostAddress -> S.PortNumber -> String
+formatPort ha pn =
+    let (w1, w2, w3, w4) = S.hostAddressToTuple ha
+        hn = show <$> [w1, w2, w3, w4]
+        portParts = show <$> [pn `quot` 256, pn `mod` 256]
+    in  intercalate "," (hn <> portParts)
+
+serializeCommand :: FTPCommand -> String
+serializeCommand (User user)  = "USER " <> user
+serializeCommand (Pass pass)  = "PASS " <> pass
+serializeCommand (Acct acct)  = "ACCT " <> acct
+serializeCommand (RType rt)   = "TYPE " <> serialzeRTypeCode rt
+serializeCommand (Retr file)  = "RETR " <> file
+serializeCommand (Nlst [])    = "NLST"
+serializeCommand (Nlst args)  = "NLST " <> intercalate " " args
+serializeCommand (Port ha pn) = "PORT " <> formatPort ha pn
+serializeCommand (Stor loc)   = "STOR " <> loc
+serializeCommand (List [])    = "LIST"
+serializeCommand (List args)  = "LIST " <> intercalate " " args
+serializeCommand (Rnfr from)  = "RNFR " <> from
+serializeCommand (Rnto to)    = "RNTO " <> to
+serializeCommand (Dele file)  = "DELE " <> file
+serializeCommand (Size file)  = "SIZE " <> file
+serializeCommand (Mkd dir)    = "MKD " <> dir
+serializeCommand (Rmd dir)    = "RMD " <> dir
+serializeCommand (Pbsz buf)   = "PBSZ " <> show buf
+serializeCommand (Prot P)     = "PROT P"
+serializeCommand (Prot C)     = "PROT C"
+serializeCommand (Cwd dir)    = "CWD " <> dir
+serializeCommand Cdup         = "CDUP"
+serializeCommand Ccc          = "CCC"
+serializeCommand Auth         = "AUTH TLS"
+serializeCommand Pwd          = "PWD"
+serializeCommand Abor         = "ABOR"
+serializeCommand Pasv         = "PASV"
+serializeCommand Quit         = "QUIT"
+
+stripCLRF :: ByteString -> ByteString
+stripCLRF = C.takeWhile $ (&&) <$> (/= '\r') <*> (/= '\n')
+
+-- | Get a line from the server
+getLineResp :: Handle -> IO ByteString
+getLineResp h = stripCLRF <$> recvLine h
+
+-- | Get a full response from the server
+-- Used in 'sendCommand'
+getMultiLineResp :: Handle -> IO FTPResponse
+getMultiLineResp h = do
+    line <- getLineResp h
+    let (code, rest) = C.splitAt 3 line
+    message <- if C.head rest == '-'
+        then loopMultiLine h code line
+        else return line
+    return $ FTPResponse
+        (responseStatus code)
+        (read $ C.unpack code)
+        (C.drop 4 message)
+
+loopMultiLine :: Handle -> ByteString -> ByteString -> IO ByteString
+loopMultiLine h code line = do
+    nextLine <- getLineResp h
+    let multiLine = line <> "\n" <> nextLine
+        nextCode = C.take 3 nextLine
+    if nextCode == code
+        then return multiLine
+        else loopMultiLine h nextCode multiLine
+
+sendCommandLine :: Handle -> ByteString -> IO ()
+sendCommandLine h dat = send h $ dat <> "\r\n"
+
+-- | Send a command to the server and get a response back.
+-- Some commands use a data 'Handle', and their data is not returned here.
+sendCommand :: Handle -> FTPCommand -> IO FTPResponse
+sendCommand h fc = do
+    let command = serializeCommand fc
+    print $ "Sending: " <> command
+    sendCommandLine h $ C.pack command
+    resp <- getMultiLineResp h
+    print $ "Recieved: " <> (show resp)
+    return resp
+
+-- | Equvalent to
+-- > mapM . sendCommand
+sendCommands :: Handle -> [FTPCommand] -> IO [FTPResponse]
+sendCommands = mapM . sendCommand
+
+-- Control connection
+
+createSocket :: Maybe String -> Int -> S.AddrInfo -> IO (S.Socket, S.AddrInfo)
+createSocket host portNum hints = do
+    addr:_ <- S.getAddrInfo (Just hints) host (Just $ show portNum)
+    print $ "Addr: " <> show addr
+    sock <- S.socket
+        (S.addrFamily addr)
+        (S.addrSocketType addr)
+        (S.addrProtocol addr)
+    return (sock, addr)
+
+createSocketPassive :: String -> Int -> IO S.Socket
+createSocketPassive host portNum = do
+    let hints = S.defaultHints {
+        S.addrSocketType = S.Stream
+    }
+    (sock, addr) <- createSocket (Just host) portNum hints
+    S.connect sock (S.addrAddress addr)
+    print "Connected"
+    return sock
+
+createSocketActive :: IO S.Socket
+createSocketActive = do
+    let hints = S.defaultHints {
+        S.addrSocketType = S.Stream,
+        S.addrFlags = [S.AI_PASSIVE]
+    }
+    (sock, addr) <- createSocket Nothing 0 hints
+    S.bind sock (S.addrAddress addr)
+    S.listen sock 1
+    print "Listening"
+    return sock
+
+createSIOHandle :: String -> Int -> IO SIO.Handle
+createSIOHandle host portNum = do
+    sock <- createSocketPassive host portNum
+    S.socketToHandle sock SIO.ReadWriteMode
+
+sIOHandleImpl :: SIO.Handle -> Handle
+sIOHandleImpl h = Handle
+    { send = C.hPut h
+    , sendLine = C.hPutStrLn h
+    , recv = C.hGetSome h
+    , recvLine = C.hGetLine h
+    }
+
+withSIOHandle :: String -> Int -> (Handle -> IO a) -> IO a
+withSIOHandle host portNum f = bracket
+    (createSIOHandle host portNum)
+    SIO.hClose
+    (f . sIOHandleImpl)
+
+-- | Takes a host name and port. A handle for interacting with the server
+-- will be returned in a callback.
+-- @
+--      withFTP "ftp.server.com" 21 $ \h welcome -> do
+--          print welcome
+--          login h "username" "password"
+--          print =<< nlst h []
+-- @
+withFTP :: String -> Int -> (Handle -> FTPResponse -> IO a) -> IO a
+withFTP host portNum f = withSIOHandle host portNum $ \h -> do
+    resp <- getMultiLineResp h
+    f h resp
+
+-- Data connection
+
+createDataSocketPasv :: Handle -> IO S.Socket
+createDataSocketPasv h = do
+    (host, portNum) <- pasv h
+    print $ "Host: " <> host
+    print $ "Port: " <> show portNum
+    createSocketPassive host portNum
+
+createDataSocketActive :: Handle -> IO S.Socket
+createDataSocketActive h = do
+    socket <- createSocketActive
+    (S.SockAddrInet sPort sHost) <- S.getSocketName socket
+    port h sHost sPort
+    return socket
+
+-- | Open a socket that can be used for data transfers
+createDataSocket :: PortActivity -> Handle -> IO S.Socket
+createDataSocket Active  = createDataSocketActive
+createDataSocket Passive = createDataSocketPasv
+
+acceptData :: S.Socket -> PortActivity -> IO S.Socket
+acceptData sock Passive = return sock
+acceptData sock Active = do
+    (socket, _) <- S.accept sock
+    return socket
+
+-- | Send setup commands to the server and
+-- create a data 'System.IO.Handle'
+createSendDataCommand
+    :: Handle
+    -> PortActivity
+    -> [FTPCommand]
+    -> IO SIO.Handle
+createSendDataCommand h pa cmds = do
+    socket <- createDataSocket pa h
+    sendCommands h cmds
+    acceptedSock <- acceptData socket pa
+    S.socketToHandle acceptedSock SIO.ReadWriteMode
+
+-- | Provides a data 'Handle' in a callback for a command
+withDataCommand
+    :: Show a
+    => Handle
+    -> PortActivity
+    -> [FTPCommand]
+    -> (Handle -> IO a)
+    -> IO a
+withDataCommand ch pa cmds f = do
+    x <- bracket
+        (createSendDataCommand ch pa cmds)
+        SIO.hClose
+        (f . sIOHandleImpl)
+    resp <- getMultiLineResp ch
+    print $ "Recieved: " <> (show resp)
+    return x
+
+-- | Recieve data and interpret it linewise
+getAllLineResp :: Handle -> IO ByteString
+getAllLineResp h = getAllLineResp' h []
+    where
+        getAllLineResp' h ret = (do
+            line <- getLineResp h
+            getAllLineResp' h (ret <> [line]))
+                `catchIOError` (\_ -> return $ C.intercalate "\n" ret)
+
+-- | Recieve all data and return it as a 'Data.ByteString.ByteString'
+recvAll :: Handle -> IO ByteString
+recvAll h = recvAll' ""
+    where
+        recvAll' bs = (do
+            chunk <- recv h defaultChunkSize
+            recvAll' $ bs <> chunk)
+                `catchIOError` (\_ -> return bs)
+
+-- TLS connection
+
+connectTLS :: SIO.Handle -> String -> Int -> IO Connection
+connectTLS h host portNum = do
+    context <- initConnectionContext
+    let tlsSettings = TLSSettingsSimple
+            { settingDisableCertificateValidation = True
+            , settingDisableSession = False
+            , settingUseServerName = False
+            }
+        connectionParams = ConnectionParams
+            { connectionHostname = host
+            , connectionPort = toEnum . fromEnum $ portNum
+            , connectionUseSecure = Just tlsSettings
+            , connectionUseSocks = Nothing
+            }
+    connectFromHandle context h connectionParams
+
+createTLSConnection :: String -> Int -> IO (FTPResponse, Connection)
+createTLSConnection host portNum = do
+    h <- createSIOHandle host portNum
+    let insecureH = sIOHandleImpl h
+    resp <- getMultiLineResp insecureH
+    sendCommand insecureH Auth
+    conn <- connectTLS h host portNum
+    return (resp, conn)
+
+tlsHandleImpl :: Connection -> Handle
+tlsHandleImpl c = Handle
+    { send = connectionPut c
+    , sendLine = connectionPut c . (<> "\n")
+    , recv = connectionGet c
+    , recvLine = connectionGetLine maxBound c
+    }
+
+withTLSHandle :: String -> Int -> (Handle -> FTPResponse -> IO a) -> IO a
+withTLSHandle host portNum f = bracket
+    (createTLSConnection host portNum)
+    (\(_, conn) -> connectionClose conn)
+    (\(resp, conn) -> f (tlsHandleImpl conn) resp)
+
+-- | Takes a host name and port. A handle for interacting with the server
+-- will be returned in a callback. The commands will be protected with TLS.
+-- Make sure to use TLS data commands like 'nlstS' or 'retrS' if you want
+-- those to use TLS as well
+-- @
+--      withFTPS "ftps.server.com" 21 $ \h welcome -> do
+--          print welcome
+--          login h "username" "password"
+--          print =<< nlstS h []
+-- @
+withFTPS :: String -> Int -> (Handle -> FTPResponse -> IO a) -> IO a
+withFTPS host portNum = withTLSHandle host portNum
+
+-- TLS data connection
+
+-- | Send setup commands to the server and
+-- create a data TLS connection
+createTLSSendDataCommand
+    :: Handle
+    -> PortActivity
+    -> [FTPCommand]
+    -> IO Connection
+createTLSSendDataCommand ch pa cmds = do
+    sendCommands ch [Pbsz 0, Prot P]
+    socket <- createDataSocket pa ch
+    sendCommands ch cmds
+    acceptedSock <- acceptData socket pa
+    (S.SockAddrInet sPort sHost) <- S.getSocketName acceptedSock
+    let (h1, h2, h3, h4) = S.hostAddressToTuple sHost
+        hostName = intercalate "." $ (show . fromEnum) <$> [h1, h2, h3, h4]
+    h <- S.socketToHandle acceptedSock SIO.ReadWriteMode
+    connectTLS h hostName (fromEnum sPort)
+
+withTLSDataCommand
+    :: Handle
+    -> PortActivity
+    -> [FTPCommand]
+    -> (Handle -> IO a)
+    -> IO a
+withTLSDataCommand ch pa cmds f = do
+    x <- bracket
+        (createTLSSendDataCommand ch pa cmds)
+        connectionClose
+        (f . tlsHandleImpl)
+    resp <- getMultiLineResp ch
+    print $ "Recieved: " <> (show resp)
+    return x
+
+parse227 :: Parser (String, Int)
+parse227 = do
+    skipWhile (/= '(') *> char '('
+    [h1,h2,h3,h4,p1,p2] <- many1 digit `sepBy` char ','
+    let host = intercalate "." [h1,h2,h3,h4]
+        highBits = read p1
+        lowBits = read p2
+        portNum = (highBits `shift` 8) + lowBits
+    return (host, portNum)
+
+parse257 :: Parser String
+parse257 = do
+    char '"'
+    C.unpack <$> takeTill (== '"')
+
+-- Control commands
+
+login :: Handle -> String -> String -> IO FTPResponse
+login h user pass = last <$> sendCommands h [User user, Pass pass]
+
+pasv :: Handle -> IO (String, Int)
+pasv h = do
+    resp <- sendCommand h Pasv
+    let (Right (host, portNum)) = parseOnly parse227 (frMessage resp)
+    return (host, portNum)
+
+port :: Handle -> S.HostAddress -> S.PortNumber -> IO FTPResponse
+port h ha pn = sendCommand h (Port ha pn)
+
+acct :: Handle -> String -> IO FTPResponse
+acct h pass = sendCommand h (Acct pass)
+
+rename :: Handle -> String -> String -> IO FTPResponse
+rename h from to = do
+        res <- sendCommand h (Rnfr from)
+        case frStatus res of
+            Continue -> sendCommand h (Rnto to)
+            _ -> return res
+
+dele :: Handle -> String -> IO FTPResponse
+dele h file = sendCommand h (Dele file)
+
+cwd :: Handle -> String -> IO FTPResponse
+cwd h dir = do
+    sendCommand h $ if dir == ".."
+        then Cdup
+        else Cwd dir
+
+size :: Handle -> String -> IO Int
+size h file = do
+    resp <- sendCommand h (Size file)
+    return $ read $ C.unpack $ frMessage resp
+
+mkd :: Handle -> String -> IO String
+mkd h dir = do
+    resp <- sendCommand h (Mkd dir)
+    let (Right dir) = parseOnly parse257 (frMessage resp)
+    return dir
+
+rmd :: Handle -> String -> IO FTPResponse
+rmd h dir = sendCommand h (Rmd dir)
+
+pwd :: Handle -> IO String
+pwd h = do
+    resp <- sendCommand h Pwd
+    let (Right dir) = parseOnly parse257 (frMessage resp)
+    return dir
+
+quit :: Handle -> IO FTPResponse
+quit h = sendCommand h Quit
+
+-- TLS commands
+
+pbsz :: Handle -> Int -> IO FTPResponse
+pbsz h = sendCommand h . Pbsz
+
+prot :: Handle -> ProtType -> IO FTPResponse
+prot h = sendCommand h . Prot
+
+ccc :: Handle -> IO FTPResponse
+ccc h = sendCommand h Ccc
+
+auth :: Handle -> IO FTPResponse
+auth h = sendCommand h Auth
+
+-- Data commands
+
+sendType :: RTypeCode -> ByteString -> Handle -> IO ()
+sendType TA dat h = void $ mapM (sendCommandLine h) $ C.split '\n' dat
+sendType TI dat h = send h dat
+
+nlst :: Handle -> [String] -> IO ByteString
+nlst h args = withDataCommand h Passive [RType TA, Nlst args] getAllLineResp
+
+retr :: Handle -> String -> IO ByteString
+retr h path = withDataCommand h Passive [RType TI, Retr path] recvAll
+
+list :: Handle -> [String] -> IO ByteString
+list h args = withDataCommand h Passive [RType TA, List args] recvAll
+
+stor :: Handle -> String -> B.ByteString -> RTypeCode -> IO ()
+stor h loc dat rtype =
+    withDataCommand h Passive [RType rtype, Stor loc] $ sendType rtype dat
+
+-- TLS data commands
+
+nlstS :: Handle -> [String] -> IO ByteString
+nlstS h args = withTLSDataCommand h Passive [RType TA, Nlst args] getAllLineResp
+
+retrS :: Handle -> String -> IO ByteString
+retrS h path = withTLSDataCommand h Passive [RType TI, Retr path] recvAll
+
+listS :: Handle -> [String] -> IO ByteString
+listS h args = withTLSDataCommand h Passive [RType TA, List args] recvAll
+
+storS :: Handle -> String -> B.ByteString -> RTypeCode -> IO ()
+storS h loc dat rtype = do
+    withTLSDataCommand h Passive [RType rtype, Stor loc] $ sendType rtype dat
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+main :: IO ()
+main = putStrLn "Test suite not yet implemented"
