diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+
+  Quiver combinators for bytestring streaming
+  ===========================================
+
+    Copyright © 2015 Patryk Zadarnowski «pat@jantar.org».
+    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. ➂ The name of any
+  author may not be used to endorse or promote products derived from this
+  software without their specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED “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 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,3 @@
+#! /usr/bin/env runhaskell
+import Distribution.Simple
+main = defaultMain
diff --git a/quiver-bytestring.cabal b/quiver-bytestring.cabal
new file mode 100644
--- /dev/null
+++ b/quiver-bytestring.cabal
@@ -0,0 +1,43 @@
+name:           quiver-bytestring
+version:        0.0.0.1
+synopsis:       Quiver combinators for bytestring streaming
+homepage:       https://github.com/zadarnowski/quiver-bytestring
+category:       Control
+stability:      alpha
+
+author:         Patryk Zadarnowski
+maintainer:     Patryk Zadarnowski <pat@jantar.org>
+
+copyright:      Copyright (c) 2015 Patryk Zadarnowski
+
+description:
+
+    This library provides a set of combinators for efficient
+    streaming of bytestring data in the Quiver framework.
+
+cabal-version:  >= 1.18
+build-type:     Simple
+license:        BSD3
+license-file:   LICENSE
+
+source-repository head
+  type:         git
+  location:     https://github.com/zadarnowski/quiver-bytestring.git
+
+source-repository this
+  type:         git
+  location:     https://github.com/zadarnowski/quiver-bytestring.git
+  tag:          0.0.0.1
+
+library
+  hs-source-dirs:   src
+  default-language: Haskell2010
+  ghc-options:      -Wall -fno-warn-unused-do-bind -fno-warn-missing-signatures
+
+  exposed-modules:
+    Control.Quiver.ByteString
+
+  build-depends:
+    base                    >= 4.8 && < 5,
+    bytestring              >= 0.10.6.0,
+    quiver                  >= 0.0.0.6
diff --git a/src/Control/Quiver/ByteString.lhs b/src/Control/Quiver/ByteString.lhs
new file mode 100644
--- /dev/null
+++ b/src/Control/Quiver/ByteString.lhs
@@ -0,0 +1,100 @@
+> -- | Module:    Control.Quiver.ByteString
+> -- Description: Chunked streams of lazy bytestrings
+> -- Copyright:   © 2015 Patryk Zadarnowski <pat@jantar.org>
+> -- License:     BSD3
+> -- Maintainer:  pat@jantar.org
+> -- Stability:   experimental
+> -- Portability: portable
+> --
+> -- When binary data is streamed through a Quiver processor, it's often
+> -- partitioned arbitrarily into /packets/ with no semantic significance,
+> -- beyond facilitation of processing in constant space. This is similar
+> -- to the chunking mechanism within lazy bytestrings, and, accordingly,
+> -- it's often convenient to unify the two mechanisms, by converting
+> -- between processors over lazy bytestrings and processors over strict
+> -- chunks of these bytestrings. This module provides efficient
+> -- implementations of these conversions.
+
+> {-# LANGUAGE RankNTypes #-}
+
+> module Control.Quiver.ByteString (
+>   toChunks, fromChunks
+> ) where
+
+> import Data.ByteString (ByteString)
+> import Data.Int
+> import Data.Functor
+> import Control.Quiver
+
+> import qualified Data.ByteString as ByteString
+> import qualified Data.ByteString.Lazy as Lazy
+
+> -- | A processors that converts a stream of lazy bytestrings
+> --   into a stream of their chunks.
+
+> toChunks :: Functor f => SP Lazy.ByteString ByteString f [ByteString]
+> toChunks = snd <$> (qpure_ Lazy.toChunks >->> qconcat_)
+
+> -- | A processors that converts a stream of strict bytestring chunks
+> --   into a stream of lazy bytestrings with the specified minimum
+> --   and maximum size, without copying any data.
+
+> fromChunks :: Functor f => Int64 -> Int64 -> SP ByteString Lazy.ByteString f [ByteString]
+> fromChunks m n
+>   | (m > n || n <= 0) = error ("fromChunks: invalid string size range: [" ++ show m ++ ".." ++ show n ++ "]")
+>   | (m <= 0) = fromMaxChunks n
+>   | (n >= fromIntegral (maxBound::Int)) = fromMinChunks m
+>   | (m == n) = fromExactChunks n
+>   | otherwise = loop0
+>  where
+>   loop0 = loop1 m n []
+>   loop1 rm rn cs = consume () (loop2 rm rn cs) (deliver (reverse cs))
+>   loop2 rm rn cs c = loop3 rm rn cs c (fromIntegral (ByteString.length c))
+>   loop3 rm rn cs c cl
+>     | (cl <=  0) = loop1 rm rn cs -- skip empty chunks
+>     | (cl <  rm) = loop1 (rm - cl) (rn - cl) (c:cs)
+>     | (cl <= rn) = let xs = reverse (c:cs)
+>                    in produce (Lazy.fromChunks xs) (const loop0) (deliver xs)
+>     | otherwise  = let (c1, c2) = ByteString.splitAt (fromIntegral rn) c
+>                    in produce (Lazy.fromChunks (reverse (c1:cs))) (const $ loop3 m n [] c2 (cl - rn)) (deliver (reverse (c:cs)))
+
+> fromMaxChunks :: Functor f => Int64 -> SP ByteString Lazy.ByteString f [ByteString]
+> fromMaxChunks n
+>   | (n <= 0) = error ("fromChunks: invalid maximum chunk size: " ++ show n)
+>   | (n >= fromIntegral (maxBound::Int)) = qpure_ Lazy.fromStrict $> []
+>   | otherwise = loop0
+>  where
+>   loop0 = consume () loop1 (deliver [])
+>   loop1 c = loop2 c (fromIntegral (ByteString.length c))
+>   loop2 c cl
+>     | (cl <= n) = produce (Lazy.fromStrict c) (const loop0) (deliver [c])
+>     | otherwise = let (c1, c2) = ByteString.splitAt n' c
+>                   in produce (Lazy.fromStrict c1) (const $ loop2 c2 (cl - n)) (deliver [c])
+>   n' = fromIntegral n
+
+> fromMinChunks :: Functor f => Int64 -> SP ByteString Lazy.ByteString f [ByteString]
+> fromMinChunks n
+>   | (n > 0) = loop0 n []
+>   | otherwise = qpure_ Lazy.fromStrict $> []
+>  where
+>   loop0 r cs = consume () (loop1 r cs) (deliver (reverse cs))
+>   loop1 r cs c = loop2 r cs c (fromIntegral (ByteString.length c))
+>   loop2 r cs c cl
+>     | (cl <= 0) = loop0 r cs -- skip empty chunks
+>     | (cl <  r) = loop0 (r - cl) (c:cs)
+>     | otherwise = let xs = reverse (c:cs) in produce (Lazy.fromChunks xs) (const $ loop0 n []) (deliver xs)
+
+> fromExactChunks :: Int64 -> SP ByteString Lazy.ByteString f [ByteString]
+> fromExactChunks n
+>   | (n > 0) = loop0 n []
+>   | otherwise = error ("Pipes.fromChunks: invalid chunk size: " ++ show n)
+>  where
+>   loop0 r cs = consume () (loop1 r cs) (deliver (reverse cs))
+>   loop1 r cs c = loop2 r cs c (fromIntegral (ByteString.length c))
+>   loop2 r cs c cl
+>     | (cl <= 0) = loop0 r cs -- skip empty chunks
+>     | otherwise = case compare cl r of
+>                     LT -> loop0 (r - cl) (c:cs)
+>                     EQ -> let xs = reverse (c:cs) in produce (Lazy.fromChunks xs) (const $ loop0 0 []) (deliver xs)
+>                     GT -> let (c1, c2) = ByteString.splitAt (fromIntegral r) c
+>                           in produce (Lazy.fromChunks (reverse (c1:cs))) (const $ loop2 n [] c2 (cl - r)) (deliver (reverse (c:cs)))
