diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Dmitry Dzhus
+
+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 Dmitry Dzhus 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/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/sext.cabal b/sext.cabal
new file mode 100644
--- /dev/null
+++ b/sext.cabal
@@ -0,0 +1,57 @@
+name:                sext
+version:             0.1.0.0
+synopsis:            Lists, Texts and ByteStrings with type-encoded length
+homepage:            http://github.com/dzhus/sext/
+
+description:         Sext (/s/tatic t/ext/) provides type-level safety for
+                     basic operations on string-like types (finite
+                     lists of elements). Use it when you need static
+                     guarantee on lengths of strings produced in your
+                     code.
+
+license:             BSD3
+license-file:        LICENSE
+author:              Dmitry Dzhus
+maintainer:          dima@dzhus.org
+category:            Data, Text, Type System
+stability:           Experimental
+
+build-type:          Simple
+cabal-version:       >=1.10
+tested-with:         GHC == 7.8.3
+
+source-repository head
+  type:     git
+  location: http://github.com/dzhus/sext
+
+flag text
+  description: Build interface for Text
+  default: True
+
+flag bytestring
+  description: Build interface for ByteString
+  default: True
+
+library
+  exposed-modules:
+    Data.Sext,
+    Data.Sext.Class,
+    Data.Sext.TH
+
+  build-depends:
+    base             >=4.7 && <4.8,
+    template-haskell
+
+  if flag(bytestring)
+    cpp-options: -DWITH_BS
+    build-depends:
+      bytestring     >=0.10 && <0.11
+
+  if flag(text)
+    cpp-options: -DWITH_TEXT
+    build-depends:
+      text           >=1.1 && <1.2
+
+  ghc-options: -Wall
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Data/Sext.hs b/src/Data/Sext.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Sext.hs
@@ -0,0 +1,219 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+
+{-|
+
+Sext (/s/tatic t/ext/) provides type-level safety for basic operations
+on string-like types (finite lists of elements). Use it when you need
+static guarantee on lengths of strings produced in your code.
+
+An example application would be a network exchange protocol built of
+packets with fixed-width fields:
+
+> mkPacket :: String -> Sext 32 String
+> mkPacket inp =
+>   -- 5-character version signature
+>   $(sext "PKT10") `append`
+>   -- 25-character payload
+>   payload `append`
+>   -- 2-character payload checksum
+>   checksum
+>   where
+>     payload = createLeft ' ' inp
+>     checksum :: Sext 2 String
+>     checksum = createLeft ' ' $
+>                show $ length payload `mod` 100
+>
+> message :: Sext 64 String
+> message = mkPacket "Hello" `append` mkPacket "world"
+
+Sext combinators are defined for members of 'Sextable' class. The
+package includes 'Sextable' instances for several common types.
+
+This module is meant to be imported qualifed, e.g.
+
+> import qualified Data.Sext as S
+
+-}
+
+module Data.Sext
+       (
+         -- * Constructing Sexts
+         --
+         -- | See also 'C.unsafeCreate'
+         createLeft
+       , createRight
+       , sext
+       , create
+       , replicate
+
+         -- * Working with Sexts
+       , append
+       , take
+       , drop
+       , map
+       , padLeft
+       , padRight
+
+       , length
+
+         -- * Sextable class
+       , Sextable(C.unsafeCreate, C.unwrap)
+       )
+
+where
+
+import           Prelude as P hiding (drop, length, map, replicate, take)
+import qualified Prelude as P hiding (length)
+
+import           GHC.TypeLits
+
+import           Data.Proxy
+import           Data.Sext.Class (Elem, Sext, Sextable)
+import qualified Data.Sext.Class as C
+import           Data.Sext.TH
+
+
+-- | Safely create a Sext, possibly altering the source to match
+-- target length. If target length is less than that of the source,
+-- the source gets truncated. If target length is greater, the source
+-- is padded using the provided basic element. Elements on the left
+-- are preferred.
+--
+-- >>> createLeft ' ' "foobarbaz" :: Sext 6 String
+-- "foobar"
+-- >>> createLeft '#' "foobarbaz" :: Sext 12 String
+-- "foobarbaz###"
+createLeft :: forall a i.
+              (Sextable a, KnownNat i) =>
+              Elem a -> a -> Sext i a
+createLeft e s =
+  C.unsafeCreate $
+  C.take (C.length s) $
+  C.append s $
+  C.replicate (t - C.length s) e
+  where
+    t = fromIntegral $ natVal (Proxy :: Proxy i)
+
+
+-- | Just like 'createLeft', except that elements on the right are preferred.
+--
+-- >>> createRight '@' "foobarbaz" :: Sext 6 String
+-- "barbaz"
+-- >>> createRight '!' "foobarbaz" :: Sext 12 String
+-- "!!!foobarbaz"
+createRight :: forall a i.
+               (Sextable a, KnownNat i) =>
+               Elem a -> a -> Sext i a
+createRight e s =
+  C.unsafeCreate $
+  C.drop (C.length s - t) $
+  C.append (C.replicate (t - C.length s) e) s
+  where
+    t = fromIntegral $ natVal (Proxy :: Proxy i)
+
+
+-- | Attempt to safely create a Sext if it matches target length.
+--
+-- >>> create "foobar" :: Maybe (Sext 6 String)
+-- Just "foobar"
+-- >>> create "barbaz" :: Maybe (Sext 8 String)
+-- Nothing
+--
+-- This is safer than 'C.unsafeCreate' and unlike with 'createLeft' /
+-- 'createRight' the source value is left unchanged. However, this
+-- implies a further run-time check for Nothing values.
+create :: forall a i.
+          (Sextable a, KnownNat i) =>
+          a -> P.Maybe (Sext i a)
+create s =
+  if C.length s == t
+  then Just $ C.unsafeCreate s
+  else Nothing
+  where
+    t = fromIntegral $ natVal (Proxy :: Proxy i)
+
+
+-- | Append two Sexts together.
+--
+-- >>> append "foo" "bar" :: Sext 6 String
+-- "foobar"
+append :: forall a m n.
+          (Sextable a) => Sext m a -> Sext n a -> Sext (m + n) a
+append a b = C.unsafeCreate $ C.append (C.unwrap a) (C.unwrap b)
+
+
+-- | Construct a new Sext from a basic element.
+--
+-- >>> replicate '=' :: Sext 10 String
+-- "=========="
+replicate :: forall a i.
+             (Sextable a, KnownNat i) => Elem a -> Sext i a
+replicate e =
+  C.unsafeCreate $ C.replicate t e
+  where
+    t = fromIntegral $ natVal (Proxy :: Proxy i)
+
+
+map :: Sextable a =>
+       (Elem a -> Elem a) -> Sext m a -> Sext m a
+map f s =
+  C.unsafeCreate $ C.map f $ C.unwrap s
+
+
+-- | Reduce Sext length, preferring elements on the left.
+--
+-- >>> take "Foobar" :: Sext 3 String
+-- "Foo"
+take :: forall a m n.
+        (Sextable a, KnownNat m, KnownNat n, n <= m) =>
+        Sext m a -> Sext n a
+take s =
+  C.unsafeCreate $ C.take t $ C.unwrap s
+  where
+    t = fromIntegral $ natVal (Proxy :: Proxy n)
+
+
+-- | Reduce Sext length, preferring elements on the right.
+--
+-- >>> drop "Foobar" :: Sext 2 String
+-- "ar"
+drop :: forall a m n.
+        (Sextable a, KnownNat m, KnownNat n, n <= m) =>
+        Sext m a -> Sext n a
+drop s =
+  C.unsafeCreate $ C.drop (C.length s' - t) s'
+  where
+    s' = C.unwrap s
+    t = fromIntegral $ natVal (Proxy :: Proxy n)
+
+
+-- | Obtain value-level length.
+length :: forall a m.
+          KnownNat m => Sext m a -> P.Int
+length _ = P.fromIntegral P.$ natVal (Proxy :: Proxy m)
+
+
+-- | Fill a Sext with extra elements up to target length, padding
+-- original elements to the left.
+padLeft :: forall a m n.
+           (Sextable a, KnownNat m, KnownNat (n - m),
+            n ~ (n - m + m), m <= n) =>
+           Elem a -> Sext m a -> Sext n a
+padLeft pad = append (replicate pad)
+
+
+-- | Like 'padLeft', but original elements are padded to the right.
+padRight :: forall a m n.
+           (Sextable a, KnownNat m, KnownNat (n - m),
+            n ~ (m + (n - m)), m <= n) =>
+           Elem a -> Sext m a -> Sext n a
+padRight pad = P.flip append (replicate pad)
diff --git a/src/Data/Sext/Class.hs b/src/Data/Sext/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Sext/Class.hs
@@ -0,0 +1,122 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeFamilies #-}
+
+{-|
+
+Use this only if you need to make some type Sextable.
+
+-}
+
+module Data.Sext.Class
+       ( Sextable(..)
+       )
+
+where
+
+import           Prelude
+import qualified Prelude as P
+
+#ifdef WITH_BS
+import qualified Data.ByteString as B
+import           GHC.Word
+#endif
+
+#ifdef WITH_TEXT
+import qualified Data.Text as T
+#endif
+
+import           GHC.TypeLits
+
+
+-- | Class of types which can be assigned a type-level length.
+class Sextable a where
+  -- | Data family which wraps values of the underlying type giving
+  -- them a type-level length. @Sext 6 t@ means a value of type @t@ of
+  -- length 6.
+  data Sext (i :: Nat) a
+
+  -- | Basic element type. For @Sextable [a]@, this is @a@.
+  type Elem a
+
+  -- | Simply wrap a value in a Sext as is, assuming any length.
+  --
+  -- For example, an expression like
+  --
+  -- > unsafeCreate "somestring" :: Sext 50 String
+  --
+  -- will typecheck, although the stored length information will not
+  -- match actual string size. This may result in wrong behaviour of
+  -- all functions defined for Sext.
+  --
+  -- Use it only when you know what you're doing.
+  --
+  -- When implementing new Sextable instances, code this to simply
+  -- apply the constructor of 'Sext'.
+  unsafeCreate :: a -> Sext i a
+
+  -- | Forget type-level length, obtaining the underlying value.
+  unwrap :: Sext i a -> a
+
+  length :: a -> Int
+  append :: a -> a -> a
+  replicate :: Int -> Elem a -> a
+  map :: (Elem a -> Elem a) -> a -> a
+  take :: Int -> a -> a
+  drop :: Int -> a -> a
+
+
+instance (Show a, Sextable a) => Show (Sext i a) where
+  show s = show $ unwrap s
+
+
+instance Sextable [a] where
+  type Elem [a] = a
+
+  data Sext i [a] = List [a]
+
+  unsafeCreate = List
+  unwrap (List l) = l
+
+  length = P.length
+  append = (P.++)
+  replicate = P.replicate
+  map = P.map
+  take = P.take
+  drop = P.drop
+
+
+#ifdef WITH_TEXT
+instance Sextable T.Text where
+  type Elem T.Text = Char
+
+  data Sext i T.Text = Text T.Text
+
+  unsafeCreate = Text
+  unwrap (Text t) = t
+
+  length = T.length
+  append = T.append
+  replicate = \n c -> T.replicate n (T.singleton c)
+  map = T.map
+  take = T.take
+  drop = T.drop
+#endif
+
+
+#ifdef WITH_BS
+instance Sextable B.ByteString where
+  type Elem B.ByteString = Word8
+
+  data Sext i B.ByteString = ByteString B.ByteString
+
+  unsafeCreate = ByteString
+  unwrap (ByteString t) = t
+
+  length = B.length
+  append = B.append
+  replicate = B.replicate
+  map = B.map
+  take = B.take
+  drop = B.drop
+#endif
diff --git a/src/Data/Sext/TH.hs b/src/Data/Sext/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Sext/TH.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+{-|
+
+Template Haskell helpers for Sext.
+
+-}
+
+module Data.Sext.TH
+       ( sext
+       )
+
+where
+
+import           Prelude
+import qualified Prelude as P (length)
+
+import           Data.Sext.Class
+import           Data.String
+
+import           Language.Haskell.TH
+
+
+-- | A type with IsString instance to allow string literals in 'sext'
+-- argument without quoting.
+newtype LitS = LitS String deriving IsString
+
+
+-- | Type-safe Sext constructor macro for string literals.
+--
+-- Example:
+--
+-- > $(sext "Foobar")
+--
+-- compiles to
+--
+-- > unsafeCreate "Foobar" :: forall a. (IsString a, Sextable a) => Sext 6 a
+--
+-- where 6 is the string length obtained at compile time.
+sext :: LitS -> Q Exp
+sext (LitS s) =
+  do
+    at <- newName "a"
+    return $ SigE (AppE (VarE 'unsafeCreate) (LitE $ StringL s))
+                (ForallT
+                 [PlainTV at]
+                 [ ClassP ''IsString [VarT at]
+                 , ClassP ''Sextable [VarT at]] $
+                 (AppT
+                  (AppT
+                   (ConT ''Sext)
+                   (LitT $ NumTyLit (fromIntegral $ P.length s)))
+                  (VarT at)))
