diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Alpha Heavy Industries
+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 names of the copyright owners nor the names of the
+  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/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/lzma-conduit.cabal b/lzma-conduit.cabal
new file mode 100644
--- /dev/null
+++ b/lzma-conduit.cabal
@@ -0,0 +1,45 @@
+name:                lzma-conduit
+version:             0.1.2
+synopsis:            Conduit interface for lzma/xz compression.
+description:
+  High level bindings to xz-utils.
+  .
+license:             BSD3
+license-file:        LICENSE
+author:              Nathan Howell <nhowell@alphaheavy.com>
+maintainer:          Nathan Howell <nhowell@alphaheavy.com>
+homepage:            http://github.com/alphaHeavy/lzma-conduit
+bug-reports:         http://github.com/alphaHeavy/lzma-conduit/issues
+category:            Codec, Compression, Enumerator
+
+build-type:          Simple
+cabal-version:       >= 1.10
+
+extra-source-files:
+  tests/Main.hs
+
+library
+  default-language:
+    Haskell2010
+  hs-source-dirs:
+    src
+  exposed-modules:
+    Bindings.Lzma
+    Data.Conduit.Lzma
+  build-depends:
+    base         >= 3      && < 5,
+    bindings-DSL >= 1.0    && < 1.1,
+    bytestring   >= 0.9.1  && < 0.10,
+    conduit      >= 0.2.2  && < 0.3,
+    transformers == 0.2.*
+  ghc-options:
+    -Wall
+  includes:
+    lzma.h
+  extra-libraries:
+    lzma
+
+source-repository head
+  type:     git
+  location: https://github.com/alphaHeavy/lzma-conduit.git
+
diff --git a/src/Bindings/Lzma.hsc b/src/Bindings/Lzma.hsc
new file mode 100644
--- /dev/null
+++ b/src/Bindings/Lzma.hsc
@@ -0,0 +1,97 @@
+{-|
+For a high-level description of the C API, refer to the README file
+included in the liblinear archive, available for download at
+<http://www.csie.ntu.edu.tw/~cjlin/liblinear/>.
+-}
+
+#include <stdio.h>
+#include <string.h>
+#include <bindings.dsl.h>
+#include <lzma.h>
+
+module Bindings.Lzma where
+#strict_import
+
+-- lzma_ret
+#integral_t lzma_ret
+#num LZMA_OK
+#num LZMA_STREAM_END
+#num LZMA_NO_CHECK
+#num LZMA_UNSUPPORTED_CHECK
+#num LZMA_GET_CHECK
+#num LZMA_MEM_ERROR
+#num LZMA_MEMLIMIT_ERROR
+#num LZMA_FORMAT_ERROR
+#num LZMA_OPTIONS_ERROR
+#num LZMA_DATA_ERROR
+#num LZMA_BUF_ERROR
+#num LZMA_PROG_ERROR
+
+-- lzma_action
+#integral_t lzma_action
+#num LZMA_RUN
+#num LZMA_SYNC_FLUSH
+#num LZMA_FULL_FLUSH
+#num LZMA_FINISH
+
+-- lzma_check
+#integral_t lzma_check
+#num LZMA_CHECK_NONE
+#num LZMA_CHECK_CRC32
+#num LZMA_CHECK_CRC64
+#num LZMA_CHECK_SHA256
+
+#num LZMA_PRESET_DEFAULT
+#num LZMA_PRESET_LEVEL_MASK
+#num LZMA_PRESET_EXTREME
+
+-- lzma_stream
+#starttype lzma_stream
+#field next_in , Ptr CUChar
+#field avail_in , CSize
+#field total_in , CULong
+#field next_out , Ptr CUChar
+#field avail_out , CSize
+#field total_out , CULong
+#stoptype
+
+-- figure out what to put here?
+-- #cinline LZMA_STREAM_INIT , IO <lzma_stream>
+
+-- base.h
+#ccall lzma_code , Ptr <lzma_stream> -> <lzma_action> -> IO <lzma_ret>
+#ccall lzma_end , Ptr <lzma_stream> -> IO ()
+#ccall lzma_memusage , Ptr <lzma_stream> -> IO CULong
+#ccall lzma_memlimit_get , Ptr <lzma_stream> -> IO CULong
+#ccall lzma_memlimit_set , Ptr <lzma_stream> -> CULong -> IO <lzma_ret>
+
+-- container.h
+#ccall lzma_easy_encoder_memusage , CInt -> IO CULong
+#ccall lzma_easy_decoder_memusage , CInt -> IO CULong
+#ccall lzma_easy_encoder , Ptr <lzma_stream> -> CInt -> <lzma_check> -> IO <lzma_ret>
+#ccall lzma_auto_decoder , Ptr <lzma_stream> -> CULong -> CUInt -> IO <lzma_ret>
+
+pokeNextIn :: Ptr C'lzma_stream -> Ptr a -> IO ()
+pokeNextIn = #poke lzma_stream, next_in
+
+pokeAvailIn :: Ptr C'lzma_stream -> CSize -> IO ()
+pokeAvailIn = #poke lzma_stream, avail_in
+
+pokeNextOut :: Ptr C'lzma_stream -> Ptr a -> IO ()
+pokeNextOut = #poke lzma_stream, next_out
+
+pokeAvailOut :: Ptr C'lzma_stream -> CSize -> IO ()
+pokeAvailOut = #poke lzma_stream, avail_out
+
+peekNextIn :: Ptr C'lzma_stream -> IO (Ptr a)
+peekNextIn = #peek lzma_stream, next_in
+
+peekAvailIn :: Ptr C'lzma_stream -> IO CSize
+peekAvailIn = #peek lzma_stream, avail_in
+
+peekNextOut :: Ptr C'lzma_stream -> IO (Ptr a)
+peekNextOut = #peek lzma_stream, next_out
+
+peekAvailOut :: Ptr C'lzma_stream -> IO CSize
+peekAvailOut = #peek lzma_stream, avail_out
+
diff --git a/src/Data/Conduit/Lzma.hs b/src/Data/Conduit/Lzma.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Conduit/Lzma.hs
@@ -0,0 +1,183 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Data.Conduit.Lzma (compress, decompress) where
+
+import Control.Monad (forM_, liftM2)
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad.Trans.Resource
+import Data.ByteString (ByteString)
+import Data.Conduit
+import Foreign
+import Foreign.C.Types (CSize)
+import System.IO.Unsafe (unsafeInterleaveIO)
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Unsafe as B
+
+import Bindings.Lzma
+
+prettyRet :: C'lzma_ret -> String
+prettyRet r
+  | r == c'LZMA_OK                = "Operation completed successfully"
+  | r == c'LZMA_STREAM_END        = "End of stream was reached"
+  | r == c'LZMA_NO_CHECK          = "Input stream has no integrity check"
+  | r == c'LZMA_UNSUPPORTED_CHECK = "Cannot calculate the integrity check"
+  | r == c'LZMA_GET_CHECK         = "Integrity check type is now available"
+  | r == c'LZMA_MEM_ERROR         = "Cannot allocate memory"
+  | r == c'LZMA_MEMLIMIT_ERROR    = "Memory usage limit was reached"
+  | r == c'LZMA_FORMAT_ERROR      = "File format not recognized"
+  | r == c'LZMA_OPTIONS_ERROR     = "Invalid or unsupported options"
+  | r == c'LZMA_DATA_ERROR        = "Data is corrupt"
+  | r == c'LZMA_BUF_ERROR         = "No progress is possible"
+  | r == c'LZMA_PROG_ERROR        = "Programming error"
+  | otherwise                     = "Unknown LZMA error: "++show r
+
+bufferSize :: Num a => a
+bufferSize = 4096
+
+memset :: forall a . Storable a => Ptr a -> Word8 -> IO ()
+memset ptr val =
+  forM_ [0..sizeOf (undefined :: a) - 1] $ \ i ->
+    pokeByteOff ptr i val
+
+initStream
+  :: String
+  -> (Ptr C'lzma_stream -> IO C'lzma_ret)
+  -> IO (Ptr C'lzma_stream)
+initStream name fun = do
+  buffer <- mallocBytes bufferSize
+  streamPtr <- malloc
+  memset streamPtr 0
+  poke streamPtr C'lzma_stream
+    { c'lzma_stream'next_in   = nullPtr
+    , c'lzma_stream'avail_in  = 0
+    , c'lzma_stream'total_in  = 0
+    , c'lzma_stream'next_out  = buffer
+    , c'lzma_stream'avail_out = bufferSize
+    , c'lzma_stream'total_out = 0 }
+  ret <- fun streamPtr
+  if ret == c'LZMA_OK
+    then return streamPtr
+    else fail $ name ++ " failed: " ++ prettyRet ret
+
+easyEncoder :: Maybe Int -> Ptr C'lzma_stream -> IO C'lzma_ret
+easyEncoder level ptr = c'lzma_easy_encoder ptr (maybe c'LZMA_PRESET_DEFAULT fromIntegral level) c'LZMA_CHECK_CRC64
+
+autoDecoder :: Maybe Word64 -> Ptr C'lzma_stream -> IO C'lzma_ret
+autoDecoder memlimit ptr = c'lzma_auto_decoder ptr (maybe maxBound fromIntegral memlimit) 0
+
+-- | Decompress a 'ByteString' from a lzma or xz container stream.
+decompress
+  :: ResourceIO m
+  => Maybe Word64 -- ^ Memory limit, in bytes.
+  -> Conduit ByteString m ByteString
+decompress memlimit = Conduit{conduitPush = initPush, conduitClose = return []} where
+  initPush input = do
+    (_, streamPtr) <- withIO
+      (initStream "lzma_auto_decoder" (autoDecoder memlimit))
+      (\ ptr -> c'lzma_end ptr >> free ptr)
+    lzmaPush streamPtr input
+
+-- | Compress a 'ByteString' into a xz container stream.
+compress
+  :: ResourceIO m
+  => Maybe Int -- ^ Compression level from [0..9], defaults to 6.
+  -> Conduit ByteString m ByteString
+compress level = Conduit{conduitPush = initPush, conduitClose = return []} where
+  initPush input = do
+    (_, streamPtr) <- withIO
+      (initStream "lzma_easy_encoder" (easyEncoder level))
+      (\ ptr -> c'lzma_end ptr >> free ptr)
+    lzmaPush streamPtr input
+
+lzmaConduit
+  :: ResourceIO m
+  => Ptr C'lzma_stream
+  -> Conduit ByteString m ByteString
+lzmaConduit = liftM2 Conduit lzmaPush lzmaClose
+
+lzmaPush
+  :: ResourceIO m
+  => Ptr C'lzma_stream
+  -> ByteString
+  -> ResourceT m (ConduitResult ByteString m ByteString)
+lzmaPush streamPtr xs = do
+  chunks <- liftIO $ codeEnum streamPtr xs
+  return $ Producing (lzmaConduit streamPtr) chunks
+
+lzmaClose
+  :: Control.Monad.IO.Class.MonadIO m
+  => Ptr C'lzma_stream
+  -> m [ByteString]
+lzmaClose streamPtr = liftIO $
+  buildChunks streamPtr c'LZMA_FINISH c'LZMA_OK
+
+codeEnum
+  :: Ptr C'lzma_stream
+  -> ByteString
+  -> IO [ByteString]
+codeEnum streamPtr chunk =
+  B.unsafeUseAsCStringLen chunk $ \ (ptr, len) -> do
+    pokeNextIn streamPtr ptr
+    pokeAvailIn streamPtr $ fromIntegral len
+    buildChunks streamPtr c'LZMA_RUN c'LZMA_OK
+
+buildChunks
+  :: Ptr C'lzma_stream
+  -> C'lzma_action
+  -> C'lzma_ret
+  -> IO [B.ByteString]
+buildChunks streamPtr action status = do
+  availIn <- peekAvailIn streamPtr
+  availOut <- peekAvailOut streamPtr
+  codeStep streamPtr action status availIn availOut
+
+codeStep
+  :: Ptr C'lzma_stream
+  -> C'lzma_action
+  -> C'lzma_ret
+  -> CSize
+  -> CSize
+  -> IO [B.ByteString]
+codeStep streamPtr action status availIn availOut
+  -- the inner enumerator has finished and we're done flushing the coder
+  | availOut == bufferSize && status == c'LZMA_STREAM_END =
+      return []
+
+  -- the normal case, we have some results..
+  | availOut < bufferSize = do
+      x <- getChunk streamPtr availOut
+      if availIn == 0 -- no more input, stop processing
+        then return [x]
+        else do
+          -- run lzma_code forward just far enough to read all the input buffer
+          xs <- unsafeInterleaveIO $ buildChunks streamPtr action status
+          return $! x:xs
+
+  -- the input buffer points into a pinned bytestring, so we need to make sure it's been
+  -- fully loaded (availIn == 0) before returning
+  | availIn > 0 || action == c'LZMA_FINISH = do
+      ret <- c'lzma_code streamPtr action
+      if ret == c'LZMA_OK || ret == c'LZMA_STREAM_END
+        then buildChunks streamPtr action ret
+        else fail $ "lzma_code failed: " ++ prettyRet ret
+
+  -- nothing to do here
+  | otherwise = return []
+
+getChunk
+  :: Ptr C'lzma_stream
+  -> CSize
+  -> IO B.ByteString
+getChunk streamPtr availOut
+  | availOut < bufferSize = do
+      nextOut <- peekNextOut streamPtr
+      let avail = bufferSize - fromIntegral availOut
+          baseBuffer = nextOut `plusPtr` (-avail)
+      bs <- B.packCStringLen (baseBuffer, avail)
+      pokeAvailOut streamPtr bufferSize
+      -- B.pack* copies the buffer, so reuse it
+      pokeNextOut streamPtr baseBuffer
+      return bs
+  | otherwise = return B.empty
+
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,87 @@
+import Test.Framework (defaultMain, testGroup)
+import Test.Framework.Providers.HUnit
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+import Test.QuickCheck
+import Test.QuickCheck.Monadic
+
+import qualified Data.ByteString as B
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import Data.List
+import Data.Word
+import Codec.Compression.Lzma.Enumerator
+
+main = defaultMain tests
+
+tests =
+  [ testGroup "Compress" compressTests
+  , testGroup "Decompress" decompressTests
+  , testGroup "Chained" chainedTests
+  ]
+
+compressTests =
+  [ testProperty "compressAndDiscard" prop_compressAndDiscard
+  , testProperty "compressAndCheckLength" prop_compressAndCheckLength
+  ]
+
+decompressTests =
+  [ testProperty "decompressRandom" prop_decompressRandom
+  , testProperty "decompressCorrupt" prop_decompressCorrupt
+  ]
+
+chainedTests =
+  [ testProperty "chain" prop_chain
+  , testProperty "compressThenDecompress" prop_compressThenDecompress
+  ]
+
+someString :: Gen B.ByteString
+someString = do
+  val <- listOf $ elements [0..255::Word8] 
+  return $ B.pack val
+
+someBigString :: Gen B.ByteString
+someBigString = resize (8*1024) someString
+
+dropAll :: Monad m => E.Iteratee a m ()
+dropAll = EL.dropWhile (const True)
+
+prop_compressAndDiscard :: Property
+prop_compressAndDiscard = monadicIO $ forAllM someBigString $ \ str -> do
+  run $ E.run_ $ E.enumList 2 [str] E.$$ E.joinI (compress Nothing E.$$ dropAll)
+
+prop_compressAndCheckLength :: Property
+prop_compressAndCheckLength = monadicIO $ forAllM someBigString $ \ str -> do
+  len <- run $ E.run_ $ E.enumList 2 [str] E.$$ E.joinI (compress Nothing E.$$ EL.fold (\ acc el -> acc + B.length el) 0)
+  assert (len - 32 > B.length str `div` 2) -- random strings don't compress very well
+
+prop_chain :: Property
+prop_chain = monadicIO $ forAllM someBigString $ \ str -> do
+  str' <- run $ E.run_ $ E.enumList 2 [str] E.$$ E.joinI (compress Nothing E.$$ E.joinI (decompress Nothing E.$$ EL.consume))
+  return $ str == B.concat str'
+
+prop_compressThenDecompress :: Property
+prop_compressThenDecompress = monadicIO $ forAllM someBigString $ \ str -> do
+  blob <- run $ E.run_ $ E.enumList 2 [str] E.$$ E.joinI (compress Nothing E.$$ EL.consume)
+  let blob' = B.concat blob
+  randIdx <- pick $ elements [0..B.length blob'-1]
+  let resplit = let (x,y) = B.splitAt randIdx blob' in [x,y]
+  str' <- run $ E.run_ $ E.enumList 2 resplit E.$$ E.joinI (decompress Nothing E.$$ EL.consume)
+  return $ str == B.concat str'
+
+prop_decompressRandom :: Property
+prop_decompressRandom = monadicIO $ forAllM someBigString $ \ str -> do
+  header <- run $ E.run_ $ E.enumList 2 [] E.$$ E.joinI (compress Nothing E.$$ EL.consume)
+  let blob = header ++ [str]
+  run $ E.run_ $ E.enumList 2 blob E.$$ E.joinI (decompress Nothing E.$$ dropAll)
+
+prop_decompressCorrupt :: Property
+prop_decompressCorrupt = expectFailure $ monadicIO $ forAllM someBigString $ \ str -> do
+  header <- run $ E.run_ $ E.enumList 2 [] E.$$ E.joinI (compress Nothing E.$$ EL.consume)
+  let header' = B.concat header
+  randVal <- pick $ elements [0..255::Word8]
+  randIdx <- pick $ elements [0..B.length header'-1]
+  let (left, right) = B.splitAt randIdx header'
+      updated = left `B.append` (randVal `B.cons` B.tail right)
+      blob = [updated, str]
+  run $ E.run_ $ E.enumList 2 blob E.$$ E.joinI (decompress Nothing E.$$ dropAll)
+
