diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2011, 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-enumerator.cabal b/lzma-enumerator.cabal
new file mode 100644
--- /dev/null
+++ b/lzma-enumerator.cabal
@@ -0,0 +1,67 @@
+name:                lzma-enumerator
+version:             0.1.1
+synopsis:            Enumerator 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-enumerator
+bug-reports:         http://github.com/alphaHeavy/lzma-enumerator/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
+    Codec.Compression.Lzma.Enumerator
+  build-depends:
+    base         >= 3      && < 5,
+    bindings-DSL >= 1.0    && < 1.1,
+    bytestring   >= 0.9.1  && < 0.10,
+    enumerator   >= 0.4.10 && < 0.5,
+    mtl          >= 2      && < 3
+  ghc-options:
+    -Wall
+  includes:
+    lzma.h
+  extra-libraries:
+    lzma
+
+test-suite lzma-test
+  default-language:
+    Haskell2010
+  hs-source-dirs:
+    tests
+  type:
+    exitcode-stdio-1.0
+  main-is:
+    Main.hs
+  ghc-options:
+    -threaded
+  build-depends:
+    lzma-enumerator,
+    base                       >= 3      && < 5,
+    bytestring                 >= 0.9.1  && < 0.10,
+    enumerator                 >= 0.4.10 && < 0.5,
+    test-framework             >= 0.4,
+    test-framework-hunit       >= 0.2.6,
+    test-framework-quickcheck2 >= 0.2.10,
+    HUnit                      >= 1.2.2.3,
+    QuickCheck                 >= 2.4.0.1
+
+source-repository head
+  type:     git
+  location: https://github.com/alphaHeavy/lzma-enumerator.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,95 @@
+{-|
+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 <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/Codec/Compression/Lzma/Enumerator.hs b/src/Codec/Compression/Lzma/Enumerator.hs
new file mode 100644
--- /dev/null
+++ b/src/Codec/Compression/Lzma/Enumerator.hs
@@ -0,0 +1,184 @@
+module Codec.Compression.Lzma.Enumerator
+  ( compress
+  , decompress
+  ) where
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Unsafe as B
+import Control.Monad (forM_, liftM)
+import Control.Monad.Trans
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import Foreign
+import Foreign.C.Types (CSize)
+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 :: Storable a => Ptr a -> Word8 -> IO ()
+memset ptr val = memset' ptr undefined where
+  memset' :: Storable a => Ptr a -> a -> IO ()
+  memset' _ a = 
+    forM_ [0..sizeOf 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
+
+-- | Compress a 'B.ByteString' 'E.Enumerator' into an xz container stream.
+compress :: MonadIO m
+         => Maybe Int -- ^ Compression level from [0..9], defaults to 6.
+         -> E.Enumeratee B.ByteString B.ByteString m b
+compress level step = do
+  streamPtr <- E.tryIO $ initStream "lzma_easy_encoder" (easyEncoder level)
+  codeEnum streamPtr step
+
+-- | Decompress a 'B.ByteString' 'E.Enumerator' from an lzma or xz container stream.
+decompress :: MonadIO m
+           => Maybe Word64 -- ^ Memory limit, in bytes.
+           -> E.Enumeratee B.ByteString B.ByteString m b
+decompress memlimit step = do
+  streamPtr <- E.tryIO $ initStream "lzma_auto_decoder" (autoDecoder memlimit)
+  codeEnum streamPtr step
+
+codeEnum :: MonadIO m
+         => Ptr C'lzma_stream
+         -> E.Enumeratee B.ByteString B.ByteString m b
+codeEnum streamPtr (E.Continue k) = do
+  availOut <- E.tryIO $ peekAvailOut streamPtr
+  chunks <- if availOut < bufferSize
+    -- always try to drain the output buffer before reading any data
+    then E.tryIO $ buildChunk streamPtr availOut
+    else do
+      chunk <- EL.head
+      E.tryIO $ case chunk of
+        -- pin the bytestring and make sure it's been fully
+        -- loaded into the native lzma_stream
+        Just chunk' -> do
+          chunks <- B.unsafeUseAsCStringLen chunk' $ \ (ptr, len) -> do
+            pokeNextIn streamPtr ptr
+            pokeAvailIn streamPtr $ fromIntegral len
+            buildChunks streamPtr c'LZMA_RUN c'LZMA_OK
+          -- sanity check, make sure it's actually been read
+          availIn <- peekAvailIn streamPtr
+          if availIn /= 0
+            then fail $ "c'lzma_stream'avail_in == " ++ show availIn ++ ", should be 0" 
+            else return chunks
+        -- once we're at the end of the enumerator flush the lzma_stream
+        Nothing -> do
+            pokeNextIn streamPtr nullPtr
+            pokeAvailIn streamPtr 0
+            buildChunks streamPtr c'LZMA_FINISH c'LZMA_OK
+  step <- lift $ E.runIteratee (k chunks)
+  codeEnum streamPtr step
+
+codeEnum streamPtr step = do
+  E.tryIO $ do
+    free =<< peekNextOut streamPtr
+    pokeNextOut streamPtr nullPtr
+    c'lzma_end streamPtr
+  return step
+
+buildChunks :: Ptr C'lzma_stream
+            -> C'lzma_action
+            -> C'lzma_ret
+            -> IO (E.Stream 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 (E.Stream 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 E.EOF
+
+  -- the normal case, we have some results..
+  | availOut < bufferSize = do
+      x <- getChunk streamPtr availOut
+      if availIn == 0 -- no more input, stop processing
+        then return $ E.Chunks [x]
+        else do
+          -- run lzma_code forward just far enough to read all the input buffer
+          xs <- buildChunks streamPtr action status
+          case xs of
+            E.Chunks xs' -> return $ E.Chunks (x:xs')
+            E.EOF        -> return $ E.Chunks [x]
+
+  -- 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 $ E.Chunks []
+
+buildChunk :: Ptr C'lzma_stream
+           -> CSize
+           -> IO (E.Stream B.ByteString)
+buildChunk streamPtr availOut =
+  liftM E.Chunks $ if availOut < bufferSize
+    then fmap (:[]) $ getChunk streamPtr availOut
+    else 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)
+
