diff --git a/Data/Vector/Storable/ByteString.hs b/Data/Vector/Storable/ByteString.hs
new file mode 100644
--- /dev/null
+++ b/Data/Vector/Storable/ByteString.hs
@@ -0,0 +1,36 @@
+-- | Convert between @ByteString@ and @Vector.Storable@
+-- without copying.
+module Data.Vector.Storable.ByteString
+    ( -- | See also the caveats mentioned in the package's
+      -- top-level documentation.
+      byteStringToVector
+    , vectorToByteString
+    ) where
+
+import qualified Data.ByteString          as BS
+import qualified Data.ByteString.Internal as BS
+import qualified Data.Vector.Storable     as V
+
+import Foreign.Storable
+import Foreign.ForeignPtr
+
+sizeOfElem :: (Storable a) => V.Vector a -> Int
+sizeOfElem vec = sizeOf (undefined `asTypeOf` V.head vec)
+
+-- | Convert a @'BS.ByteString'@ to a @'V.Vector'@.
+--
+-- This function can produce @'Vector'@s which do not obey
+-- architectural alignment requirements.  On @x86@ this should
+-- not be an issue.
+byteStringToVector :: (Storable a) => BS.ByteString -> V.Vector a
+byteStringToVector bs = vec where
+    vec = V.unsafeFromForeignPtr (castForeignPtr fptr) (scale off) (scale len)
+    (fptr, off, len) = BS.toForeignPtr bs
+    scale = (`div` sizeOfElem vec)
+
+-- | Convert a @'V.Vector'@ to a @'BS.ByteString'@.
+vectorToByteString :: (Storable a) => V.Vector a -> BS.ByteString
+vectorToByteString vec
+  = BS.fromForeignPtr (castForeignPtr fptr) (scale off) (scale len) where
+    (fptr, off, len) = V.unsafeToForeignPtr vec
+    scale = (* sizeOfElem vec)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) Keegan McAllister 2011
+
+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 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 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 b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,10 @@
+spool provides conversion between ByteString and Vector.Storable without
+copying the underlying data.
+
+Documentation is hosted at http://hackage.haskell.org/package/spool
+
+To build the documentation yourself, run
+
+  $ cabal configure && cabal haddock --hyperlink-source
+
+This will produce HTML documentation under dist/doc/html/spool.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+#! /usr/bin/runhaskell
+
+import Distribution.Simple
+main = defaultMain
diff --git a/spool.cabal b/spool.cabal
new file mode 100644
--- /dev/null
+++ b/spool.cabal
@@ -0,0 +1,38 @@
+name:                spool
+version:             0.1
+license:             BSD3
+license-file:        LICENSE
+synopsis:            Convert between ByteString and Vector.Storable without copying
+category:            Data Structures
+author:              Keegan McAllister <mcallister.keegan@gmail.com>
+maintainer:          Keegan McAllister <mcallister.keegan@gmail.com>
+build-type:          Simple
+cabal-version:       >=1.6
+description:
+  This library allows conversion between the types from @Data.ByteString@
+  (package @bytestring@) and @Data.Vector.Storable@ (package @vector@) without
+  copying the underlying data.  This is useful, for example, when @ByteString@
+  IO produces or consumes vectors of numbers in native byte order.
+  .
+  This trick relies on the fact that @ByteString@ and @Vector@ use their
+  respective @ForeignPtr@s in compatible ways.  It works with
+  @bytestring-0.9.1.10@ and @vector-0.9@ on GHC 7.0.  It may break with future
+  releases of these packages.  Depending on this library should be seen as a
+  way to document and standardize an existing hack, and not as an absolute
+  guarantee of correct behavior.
+
+extra-source-files:
+    test/test.hs
+  , README
+
+library
+  exposed-modules:  Data.Vector.Storable.ByteString
+  ghc-options:      -Wall
+  build-depends:
+      base       >= 3 && < 5
+    , bytestring >= 0.9
+    , vector     >= 0.7
+
+source-repository head
+    type:     git
+    location: git://github.com/kmcallister/spool.git
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE
+    ScopedTypeVariables #-}
+
+module Main(main) where
+
+import qualified Data.ByteString      as B
+import qualified Data.Vector.Storable as V
+
+import Data.Word
+import System.ByteOrder   -- package 'byteorder'
+import Test.QuickCheck
+import Foreign.Storable
+
+import Data.Vector.Storable.ByteString
+
+
+arbList :: (Bounded a, Integral a) => Gen [a]
+arbList = choose (0, 16384) >>= flip vectorOf arbitraryBoundedIntegral
+
+instance Arbitrary B.ByteString where
+    arbitrary = fmap B.pack arbList
+
+instance (Bounded a, Integral a, Storable a) => Arbitrary (V.Vector a) where
+    arbitrary = fmap V.fromList arbList
+
+
+-- Reference implementations
+
+fromBE, fromHost :: (Integral a) => [Word8] -> a
+fromBE = foldl (\n x -> n*256 + fromIntegral x) 0
+fromHost = case byteOrder of
+    LittleEndian -> fromBE . reverse
+    BigEndian    -> fromBE
+
+toLE, toHost :: (Integral a) => Int -> a -> [Word8]
+toLE sz = take sz . map (fromIntegral . (`mod` 256)) . iterate (`div` 256)
+toHost = case byteOrder of
+    LittleEndian -> toLE
+    BigEndian    -> \sz -> reverse . toLE sz
+
+data Rep a = Rep Int ([Word8] -> a) (a -> [Word8])
+
+rep8 :: Rep Word8
+rep8 = Rep 1 (\[x] -> x) return
+
+repN :: forall a. (Storable a, Integral a) => Rep a
+repN = let n = sizeOf (undefined :: a) in Rep n fromHost (toHost n)
+
+ref_byteStringToVector :: (Storable a) => Rep a -> B.ByteString -> V.Vector a
+ref_byteStringToVector (Rep n f _) = V.fromList . go where
+    go bs = case B.splitAt n bs of
+        (x, xs) | B.length x == n -> f (B.unpack x) : go xs
+        _ -> []
+
+ref_vectorToByteString :: (Storable a) => Rep a -> V.Vector a -> B.ByteString
+ref_vectorToByteString (Rep _ _ f) = B.pack . concatMap f . V.toList
+
+
+-- Properties
+
+mkProp_inv      _ x = byteStringToVector (vectorToByteString x) == x
+mkProp_inv_ref  r x = ref_byteStringToVector r (ref_vectorToByteString r x) == x
+
+mkProp_inv_ref1 r x = ref_byteStringToVector r (vectorToByteString x) == x
+mkProp_inv_ref2 r x = byteStringToVector (ref_vectorToByteString r x) == x
+
+mkProp_eq_BV r x = ref_byteStringToVector r x == byteStringToVector x
+mkProp_eq_VB r x = ref_vectorToByteString r x == vectorToByteString x
+
+
+-- Test runner
+
+runFor :: (Integral a, Bounded a, Storable a) => Rep a -> IO ()
+runFor r = do
+    mapM_ (quickCheck . ($r))
+        [mkProp_inv, mkProp_inv_ref, mkProp_inv_ref1,
+         mkProp_inv_ref2, mkProp_eq_VB]
+    quickCheck (mkProp_eq_BV r)
+
+main :: IO ()
+main = do
+    runFor rep8
+    runFor (repN :: Rep Word16)
+    runFor (repN :: Rep Word32)
+    runFor (repN :: Rep Word64)
