diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
--- a/CHANGELOG.markdown
+++ b/CHANGELOG.markdown
@@ -1,3 +1,17 @@
+1.0.2
+-----
+* Add fromChar
+* Add IsString UTF8 instance
+* Fixup documentation and tests
+
+1.0.1.1
+-----
+* Build correctly on GHC-7.0 (#14)
+
+1.0.1
+-----
+* Improve the performance of Data.ByteString.Lazy.UTF8.fromString. (Thanks, ndmitchell)
+
 1
 -----
 * Remove out all the old utf8 IO support. GHC supports utf8 now.
diff --git a/Codec/Binary/UTF8/Generic.hs b/Codec/Binary/UTF8/Generic.hs
--- a/Codec/Binary/UTF8/Generic.hs
+++ b/Codec/Binary/UTF8/Generic.hs
@@ -94,7 +94,7 @@
 fromString xs = pack (encode xs)
 
 -- | Convert a UTF8 encoded bytestring into a Haskell string.
--- Invalid characters are replaced with '\xFFFD'.
+-- Invalid characters are replaced with @\'\\0xFFFD\'@.
 {-# SPECIALIZE toString :: B.ByteString -> String #-}
 {-# SPECIALIZE toString :: L.ByteString -> String #-}
 {-# SPECIALIZE toString :: [Word8] -> String #-}
@@ -109,7 +109,7 @@
 -- Returns 'Nothing' if there are no more bytes in the byte string.
 -- Otherwise, it returns a decoded character and the number of
 -- bytes used in its representation.
--- Errors are replaced by character '\0xFFFD'.
+-- Errors are replaced by character @\'\\0xFFFD\'@.
 
 -- XXX: Should we combine sequences of errors into a single replacement
 -- character?
@@ -195,7 +195,7 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 {-# SPECIALIZE span :: (Char -> Bool) -> B.ByteString -> (B.ByteString,B.ByteString) #-}
 {-# SPECIALIZE span :: (Char -> Bool) -> L.ByteString -> (L.ByteString,L.ByteString) #-}
 {-# SPECIALIZE span :: (Char -> Bool) -> [Word8] -> ([Word8],[Word8])    #-}
@@ -208,13 +208,13 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that do not satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 {-# INLINE break #-}
 break :: UTF8Bytes b s => (Char -> Bool) -> b -> (b,b)
 break p bs = span (not . p) bs
 
 -- | Get the first character of a byte string, if any.
--- Malformed characters are replaced by '\0xFFFD'.
+-- Malformed characters are replaced by @\'\\0xFFFD\'@.
 {-# INLINE uncons #-}
 uncons :: UTF8Bytes b s => b -> Maybe (Char,b)
 uncons bs = do (c,n) <- decode bs
@@ -252,9 +252,9 @@
                       Nothing -> n
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
--- See also 'lines\''.
+-- See also 'lines''.
 {-# SPECIALIZE lines :: B.ByteString -> [B.ByteString] #-}
 {-# SPECIALIZE lines :: L.ByteString -> [L.ByteString] #-}
 {-# SPECIALIZE lines :: [Word8]      -> [[Word8]]       #-}
@@ -266,7 +266,7 @@
              Nothing -> [bs]
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
 -- This function preserves the terminators.
 -- See also 'lines'.
diff --git a/Codec/Binary/UTF8/String.hs b/Codec/Binary/UTF8/String.hs
--- a/Codec/Binary/UTF8/String.hs
+++ b/Codec/Binary/UTF8/String.hs
@@ -12,7 +12,7 @@
 -- Stability   :  experimental
 -- Portability :  portable
 --
--- Support for encoding UTF8 Strings to and from @[Word8]@
+-- Support for encoding UTF8 Strings to and from @['Word8']@
 --
 
 module Codec.Binary.UTF8.String (
@@ -37,15 +37,15 @@
 encodeString xs = map (toEnum . fromEnum) (encode xs)
 
 -- | Decode a string using 'decode' using a 'String' as input.
--- | This is not safe but it is necessary if UTF-8 encoded text
--- | has been loaded into a 'String' prior to being decoded.
+-- This is not safe but it is necessary if UTF-8 encoded text
+-- has been loaded into a 'String' prior to being decoded.
 decodeString :: String -> String
 decodeString xs = decode (map (toEnum . fromEnum) xs)
 
 replacement_character :: Char
 replacement_character = '\xfffd'
 
--- | Encode a single Haskell Char to a list of Word8 values, in UTF8 format.
+-- | Encode a single Haskell 'Char' to a list of 'Word8' values, in UTF8 format.
 encodeChar :: Char -> [Word8]
 encodeChar = map fromIntegral . go . ord
  where
@@ -67,12 +67,12 @@
                         ]
 
 
--- | Encode a Haskell String to a list of Word8 values, in UTF8 format.
+-- | Encode a Haskell 'String' to a list of 'Word8' values, in UTF8 format.
 encode :: String -> [Word8]
 encode = concatMap encodeChar
 
 --
--- | Decode a UTF8 string packed into a list of Word8 values, directly to String
+-- | Decode a UTF8 string packed into a list of 'Word8' values, directly to 'String'
 --
 decode :: [Word8] -> String
 decode [    ] = ""
diff --git a/Data/ByteString/Lazy/UTF8.hs b/Data/ByteString/Lazy/UTF8.hs
--- a/Data/ByteString/Lazy/UTF8.hs
+++ b/Data/ByteString/Lazy/UTF8.hs
@@ -38,18 +38,70 @@
 import Data.Bits
 import Data.Word
 import Data.Int
+import Foreign.Storable
+import Foreign.Ptr
+import Foreign.ForeignPtr
+import Data.Char        (ord)
+import Control.Exception        (assert)
 import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Lazy.Internal as B
+import qualified Data.ByteString.Internal as S
 import Prelude hiding (take,drop,splitAt,span,break,foldr,foldl,length,lines)
 
-import Codec.Binary.UTF8.String(encode)
 import Codec.Binary.UTF8.Generic (buncons)
 
+#if MIN_VERSION_base(4,4,0)
+import System.IO.Unsafe (unsafeDupablePerformIO)
+#else
+import GHC.IO (unsafeDupablePerformIO)
+#endif
+
+---------------------------------------------------------------------
+-- ENCODING
+
 -- | Converts a Haskell string into a UTF8 encoded bytestring.
 fromString :: String -> B.ByteString
-fromString xs = B.pack (encode xs)
+fromString []  = B.empty
+fromString xs0 = packChunks 32 xs0
+  where
+    packChunks n xs = case packUptoLenBytes n xs of
+        (bs, [] ) -> B.chunk bs B.Empty
+        (bs, xs') -> B.Chunk bs (packChunks (min (n * 2) B.smallChunkSize) xs')
 
+    packUptoLenBytes :: Int -> String -> (S.ByteString, String)
+    packUptoLenBytes len xs = unsafeCreateUptoN' len $ \ptr -> do
+        (end, xs') <- go ptr (ptr `plusPtr` (len-4)) xs
+        return (end `minusPtr` ptr, xs')
+
+    -- end is the last position at which you can write a whole 4 byte sequence safely
+    go :: Ptr Word8 -> Ptr Word8 -> String -> IO (Ptr Word8, String)
+    go !ptr !end xs | ptr > end = return (ptr, xs)
+    go !ptr !_   [] = return (ptr, [])
+    go !ptr !end (x:xs)
+        | x <= '\x7f' = poke ptr (S.c2w x) >> go (plusPtr ptr 1) end xs
+        | otherwise = case ord x of
+            oc | oc <= 0x7ff -> do
+                    poke ptr $ fromIntegral $ 0xc0 + (oc `shiftR` 6)
+                    pokeElemOff ptr 1 $ fromIntegral $ 0x80 + oc .&. 0x3f
+                    go (plusPtr ptr 2) end xs
+               | oc <= 0xffff -> do
+                    poke ptr $ fromIntegral $ 0xe0 + (oc `shiftR` 12)
+                    pokeElemOff ptr 1 $ fromIntegral $ 0x80 + ((oc `shiftR` 6) .&. 0x3f)
+                    pokeElemOff ptr 2 $ fromIntegral $ 0x80 + oc .&. 0x3f
+                    go (plusPtr ptr 3) end xs
+               | otherwise -> do
+                    poke ptr $ fromIntegral $ 0xf0 + (oc `shiftR` 18)
+                    pokeElemOff ptr 1 $ fromIntegral $ 0x80 + ((oc `shiftR` 12) .&. 0x3f)
+                    pokeElemOff ptr 2 $ fromIntegral $ 0x80 + ((oc `shiftR` 6) .&. 0x3f)
+                    pokeElemOff ptr 3 $ fromIntegral $ 0x80 + oc .&. 0x3f
+                    go (plusPtr ptr 4) end xs
+
+
+---------------------------------------------------------------------
+-- DECODING
+
 -- | Convert a UTF8 encoded bytestring into a Haskell string.
--- Invalid characters are replaced with '\xFFFD'.
+-- Invalid characters are replaced with @\'\\0xFFFD\'@.
 toString :: B.ByteString -> String
 toString bs = foldr (:) [] bs
 
@@ -61,7 +113,7 @@
 -- Returns 'Nothing' if there are no more bytes in the byte string.
 -- Otherwise, it returns a decoded character and the number of
 -- bytes used in its representation.
--- Errors are replaced by character '\0xFFFD'.
+-- Errors are replaced by character @\'\\0xFFFD\'@.
 
 -- XXX: Should we combine sequences of errors into a single replacement
 -- character?
@@ -156,7 +208,7 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 span :: (Char -> Bool) -> B.ByteString -> (B.ByteString, B.ByteString)
 span p bs = loop 0 bs
   where loop a cs = case decode cs of
@@ -166,12 +218,12 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that do not satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 break :: (Char -> Bool) -> B.ByteString -> (B.ByteString, B.ByteString)
 break p bs = span (not . p) bs
 
 -- | Get the first character of a byte string, if any.
--- Malformed characters are replaced by '\0xFFFD'.
+-- Malformed characters are replaced by @\'\\0xFFFD\'@.
 uncons :: B.ByteString -> Maybe (Char,B.ByteString)
 uncons bs = do (c,n) <- decode bs
                return (c, B.drop n bs)
@@ -199,9 +251,9 @@
                       Nothing -> n
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
--- See also 'lines\''.
+-- See also 'lines''.
 lines :: B.ByteString -> [B.ByteString]
 lines bs | B.null bs  = []
 lines bs = case B.elemIndex 10 bs of
@@ -210,7 +262,7 @@
              Nothing -> [bs]
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
 -- This function preserves the terminators.
 -- See also 'lines'.
@@ -221,3 +273,25 @@
                         in xs : lines' ys
               Nothing -> [bs]
 
+
+---------------------------------------------------------------------
+-- COPIED FROM BYTESTRING
+-- These functions are copied verbatum from Data.ByteString.Internal
+-- I suspect their lack of export is an oversight
+
+unsafeCreateUptoN' :: Int -> (Ptr Word8 -> IO (Int, a)) -> (S.ByteString, a)
+unsafeCreateUptoN' l f = unsafeDupablePerformIO (createUptoN' l f)
+{-# INLINE unsafeCreateUptoN' #-}
+
+-- | Create ByteString of up to size @l@ and use action @f@ to fill it's contents which returns its true size.
+createUptoN' :: Int -> (Ptr Word8 -> IO (Int, a)) -> IO (S.ByteString, a)
+createUptoN' l f = do
+    fp <- S.mallocByteString l
+    (l', res) <- withForeignPtr fp $ \p -> f p
+#if MIN_VERSION_bytestring(0,11,0)
+    let bs = S.BS fp l'
+#else
+    let bs = S.PS fp 0 l'
+#endif
+    assert (l' <= l) $ return (bs, res)
+{-# INLINE createUptoN' #-}
diff --git a/Data/ByteString/UTF8.hs b/Data/ByteString/UTF8.hs
--- a/Data/ByteString/UTF8.hs
+++ b/Data/ByteString/UTF8.hs
@@ -26,6 +26,7 @@
   , drop
   , span
   , break
+  , fromChar
   , fromString
   , toString
   , foldl
@@ -43,12 +44,16 @@
 import Codec.Binary.UTF8.String(encode)
 import Codec.Binary.UTF8.Generic (buncons)
 
+-- | Converts a Haskell char into a UTF8 encoded bytestring.
+fromChar :: Char -> B.ByteString
+fromChar x = fromString [x]
+
 -- | Converts a Haskell string into a UTF8 encoded bytestring.
 fromString :: String -> B.ByteString
 fromString xs = B.pack (encode xs)
 
 -- | Convert a UTF8 encoded bytestring into a Haskell string.
--- Invalid characters are replaced with '\xFFFD'.
+-- Invalid characters are replaced with @\'\\0xFFFD\'@.
 toString :: B.ByteString -> String
 toString bs = foldr (:) [] bs
 
@@ -60,7 +65,7 @@
 -- Returns 'Nothing' if there are no more bytes in the byte string.
 -- Otherwise, it returns a decoded character and the number of
 -- bytes used in its representation.
--- Errors are replaced by character '\0xFFFD'.
+-- Errors are replaced by character @\'\\0xFFFD\'@.
 
 -- XXX: Should we combine sequences of errors into a single replacement
 -- character?
@@ -146,7 +151,7 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 span :: (Char -> Bool) -> B.ByteString -> (B.ByteString, B.ByteString)
 span p bs = loop 0 bs
   where loop a cs = case decode cs of
@@ -156,12 +161,12 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that do not satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 break :: (Char -> Bool) -> B.ByteString -> (B.ByteString, B.ByteString)
 break p bs = span (not . p) bs
 
 -- | Get the first character of a byte string, if any.
--- Malformed characters are replaced by '\0xFFFD'.
+-- Malformed characters are replaced by @\'\\0xFFFD\'@.
 uncons :: B.ByteString -> Maybe (Char,B.ByteString)
 uncons bs = do (c,n) <- decode bs
                return (c, B.drop n bs)
@@ -189,9 +194,9 @@
                       Nothing -> n
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
--- See also 'lines\''.
+-- See also 'lines''.
 lines :: B.ByteString -> [B.ByteString]
 lines bs | B.null bs  = []
 lines bs = case B.elemIndex 10 bs of
@@ -200,7 +205,7 @@
              Nothing -> [bs]
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
 -- This function preserves the terminators.
 -- See also 'lines'.
diff --git a/Data/String/UTF8.hs b/Data/String/UTF8.hs
--- a/Data/String/UTF8.hs
+++ b/Data/String/UTF8.hs
@@ -48,6 +48,7 @@
                       ,foldl,foldr,length,lines,splitAt)
 import qualified Codec.Binary.UTF8.Generic as G
 import Codec.Binary.UTF8.Generic (UTF8Bytes)
+import qualified Data.String as S
 
 -- | The type of strings that are represented using the UTF8 encoding.
 -- The parameter is the type of the container for the representation.
@@ -56,6 +57,9 @@
 instance UTF8Bytes string index => Show (UTF8 string) where
   show x = show (toString x)
 
+instance UTF8Bytes string index => S.IsString (UTF8 string) where
+  fromString = fromString
+
 fromRep :: string -> UTF8 string
 fromRep = Str
 
@@ -68,7 +72,7 @@
 fromString xs = Str (G.fromString xs)
 
 -- | Convert a UTF8 encoded string into a Haskell string.
--- Invalid characters are replaced by 'replacement_char'.
+-- Invalid characters are replaced by 'G.replacement_char'.
 -- Complexity: linear.
 toString :: UTF8Bytes string index => UTF8 string -> String
 toString (Str xs) = G.toString xs
@@ -79,7 +83,6 @@
 
 -- | Split after a given number of characters.
 -- Negative values are treated as if they are 0.
--- See also 'bytesSplitAt'.
 splitAt :: UTF8Bytes string index
         => index -> UTF8 string -> (UTF8 string, UTF8 string)
 splitAt x (Str bs)  = case G.splitAt x bs of
@@ -116,7 +119,7 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as '\0xFFFD' to the predicate.
+-- Invalid characters are passed as @\'\\0xFFFD\'@ to the predicate.
 span :: UTF8Bytes string index
      => (Char -> Bool) -> UTF8 string -> (UTF8 string, UTF8 string)
 span p (Str bs) = case G.span p bs of
@@ -125,14 +128,14 @@
 -- | Split a string into two parts:  the first is the longest prefix
 -- that contains only characters that do not satisfy the predicate; the second
 -- part is the rest of the string.
--- Invalid characters are passed as 'replacement_char' to the predicate.
+-- Invalid characters are passed as 'G.replacement_char' to the predicate.
 break :: UTF8Bytes string index
       => (Char -> Bool) -> UTF8 string -> (UTF8 string, UTF8 string)
 break p (Str bs)  = case G.break p bs of
                       (s1,s2) -> (Str s1, Str s2)
 
 -- | Get the first character of a byte string, if any.
--- Invalid characters are replaced by 'replacement_char'.
+-- Invalid characters are replaced by 'G.replacement_char'.
 uncons :: UTF8Bytes string index
        => UTF8 string -> Maybe (Char, UTF8 string)
 uncons (Str x)  = do (c,y) <- G.uncons x
@@ -141,7 +144,7 @@
 -- | Extract the first character for the underlying representation,
 -- if one is available.  It also returns the number of bytes used
 -- in the representation of the character.
--- See also 'uncons', 'dropBytes'.
+-- See also 'uncons'.
 decode :: UTF8Bytes string index => UTF8 string -> Maybe (Char, index)
 decode (Str x)  = G.decode x
 
@@ -161,14 +164,14 @@
 length (Str b) = G.length b
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
--- See also 'lines\''.
+-- See also 'lines''.
 lines :: UTF8Bytes string index => UTF8 string -> [UTF8 string]
 lines (Str b) = map Str (G.lines b)   -- XXX: unnecessary map
 
 -- | Split a string into a list of lines.
--- Lines are terminated by '\n' or the end of the string.
+-- Lines are terminated by @\'\\n\'@ or the end of the string.
 -- Empty lines may not be terminated by the end of the string.
 -- This function preserves the terminators.
 -- See also 'lines'.
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,208 @@
+import Codec.Binary.UTF8.String
+import Test.HUnit (Test (TestCase, TestList, TestLabel), assertEqual, errors, failures, runTestTT)
+import System.Exit (exitFailure)
+import Control.Monad (when)
+
+main :: IO ()
+main = do counts <- runTestTT tests
+          when (errors counts > 0 || failures counts > 0) exitFailure
+
+tests :: Test
+tests = TestList [test_1, test_2, test_3, test_4, test_5, test_6]
+
+test_1 :: Test
+test_1 = TestLabel "1 Some correct UTF-8 text" $
+  TestCase $ assertEqual "kosme, " "\x03ba\x1f79\x03c3\x03bc\x03b5 "
+    (decode [0xce,0xba,0xe1,0xbd,0xb9,0xcf,0x83,0xce,0xbc,0xce,0xb5,0x20])
+
+test_2 :: Test
+test_2 = TestLabel "2 Boundary condition test cases" $
+  TestList [test_2_1, test_2_2, test_2_3]
+
+test_2_1 :: Test
+test_2_1 = TestLabel "2.1 First possible sequence of a certain length" $
+  TestList $ map TestCase $
+  [ assertEqual "2.1.1, " "\0\0" (decode [0, 0])
+  , assertEqual "2.1.2, " "\x80\0" (decode [0xc2, 0x80, 0])
+  , assertEqual "2.1.3, " "\x800\0" (decode [0xe0, 0xa0, 0x80, 0])
+  , assertEqual "2.1.4, " "\x10000\0" (decode [0xf0, 0x90, 0x80, 0x80, 0])
+  , assertEqual "2.1.5, " "\xfffd\0" (decode [0xf8, 0x88, 0x80, 0x80, 0x80, 0])
+  , assertEqual "2.1.6, " "\xfffd\0" (decode [0xfc,0x84,0x80,0x80,0x80,0x80,0])
+  ]
+
+test_2_2 :: Test
+test_2_2 = TestLabel "2.2 Last possible sequence of a certain length" $
+  TestList $ map TestCase $
+  [ assertEqual "2.2.1, " "\x7f\0" (decode [0x7f, 0])
+  , assertEqual "2.2.2, " "\x7ff\0" (decode [0xdf, 0xbf, 0])
+  , assertEqual "2.2.3, " "\xfffd\0" (decode [0xef, 0xbf, 0xbf, 0])
+  , assertEqual "2.2.4, " "\xfffd\0" (decode [0xf7, 0xbf, 0xbf, 0xbf, 0])
+  , assertEqual "2.2.5, " "\xfffd\0" (decode [0xfb, 0xbf, 0xbf, 0xbf, 0xbf, 0])
+  , assertEqual "2.2.6, " "\xfffd\0" (decode [0xfd,0xbf,0xbf,0xbf,0xbf,0xbf,0])
+  ]
+
+test_2_3 :: Test
+test_2_3 = TestLabel "2.3 Other boundary conditions" $
+  TestList $ map TestCase $
+  [ assertEqual "2.3.1, " "\xd7ff\0" (decode [0xed, 0x9f, 0xbf, 0])
+  , assertEqual "2.3.2, " "\xe000\0" (decode [0xee, 0x80, 0x80, 0])
+  , assertEqual "2.3.3, " "\xfffd\0" (decode [0xef, 0xbf, 0xbd, 0])
+  , assertEqual "2.3.4, " "\x10ffff\0" (decode [0xf4, 0x8f, 0xbf, 0xbf, 0])
+  , assertEqual "2.3.5, " "\xfffd\0" (decode [0xf4, 0x90, 0x80, 0x80, 0])
+  ]
+
+test_3 :: Test
+test_3 = TestLabel "3 Malformed sequences" $
+  TestList [test_3_1, test_3_2, test_3_3, test_3_4, test_3_5]
+
+test_3_1 :: Test
+test_3_1 = TestLabel "3.1 Unexpected continuation bytes" $
+  TestList $ map TestCase $
+  [ assertEqual "3.1.1, " "\xfffd\0" (decode [0x80, 0])
+  , assertEqual "3.1.2, " "\xfffd\0" (decode [0xbf, 0])
+  , assertEqual "3.1.3, " "\xfffd\xfffd\0" (decode [0x80, 0xbf, 0])
+  , assertEqual "3.1.4, " "\xfffd\xfffd\xfffd\0" (decode [0x80, 0xbf, 0x80, 0])
+  , assertEqual "3.1.5, " "\xfffd\xfffd\xfffd\xfffd\0"
+                          (decode [0x80, 0xbf, 0x80, 0xbf, 0])
+  , assertEqual "3.1.6, " "\xfffd\xfffd\xfffd\xfffd\xfffd\0"
+                          (decode [0x80, 0xbf, 0x80, 0xbf, 0x80, 0])
+  , assertEqual "3.1.7, " "\xfffd\xfffd\xfffd\xfffd\xfffd\xfffd\0"
+                          (decode [0x80, 0xbf, 0x80, 0xbf, 0x80, 0xbf, 0])
+  , assertEqual "3.1.8, " "\xfffd\xfffd\xfffd\xfffd\xfffd\xfffd\xfffd\0"
+                          (decode [0x80, 0xbf, 0x80, 0xbf, 0x80, 0xbf, 0x80, 0])
+  , assertEqual "3.1.9, " (replicate 64 '\xfffd') (decode [0x80..0xbf])
+  ]
+
+test_3_2 :: Test
+test_3_2 = TestLabel "3.2 Lonely start characters" $
+  TestList $ map TestCase $
+  [ assertEqual "3.2.1, " (concat (replicate 32 "\xfffd "))
+                          (decode (concat [[x,0x20] | x <- [0xc0..0xdf]]))
+  , assertEqual "3.2.2, " (concat (replicate 16 "\xfffd "))
+                          (decode (concat [[x,0x20] | x <- [0xe0..0xef]]))
+  , assertEqual "3.2.3, " (concat (replicate 8 "\xfffd "))
+                          (decode (concat [[x,0x20] | x <- [0xf0..0xf7]]))
+  , assertEqual "3.2.4, " "\xfffd \xfffd \xfffd \xfffd "
+                          (decode (concat [[x,0x20] | x <- [0xf8..0xfb]]))
+  , assertEqual "3.2.5, " "\xfffd \xfffd " (decode [0xfc, 0x20, 0xfd, 0x20])
+  ]
+
+test_3_3 :: Test
+test_3_3 = TestLabel "3.3 Sequences with last continuation byte missing" $
+  TestList $ map TestCase $
+  [ assertEqual "3.3.1, " "\xfffd " (decode [0xc0, 0x20])
+  , assertEqual "3.3.2, " "\xfffd " (decode [0xe0, 0x80, 0x20])
+  , assertEqual "3.3.3, " "\xfffd " (decode [0xf0, 0x80, 0x80, 0x20])
+  , assertEqual "3.3.4, " "\xfffd " (decode [0xf8, 0x80, 0x80, 0x80, 0x20])
+  , assertEqual "3.3.5, " "\xfffd " (decode [0xfc, 0x80, 0x80, 0x80,0x80,0x20])
+  , assertEqual "3.3.6, " "\xfffd " (decode [0xdf, 0x20])
+  , assertEqual "3.3.7, " "\xfffd " (decode [0xef, 0xbf, 0x20])
+  , assertEqual "3.3.8, " "\xfffd " (decode [0xf7, 0xbf, 0xbf, 0x20])
+  , assertEqual "3.3.9, " "\xfffd " (decode [0xfb, 0xbf, 0xbf, 0xbf, 0x20])
+  , assertEqual "3.3.10, " "\xfffd " (decode [0xfd, 0xbf, 0xbf, 0xbf,0xbf,0x20])
+  ]
+
+test_3_4 :: Test
+test_3_4 = TestLabel "3.4 Concatenation of incomplete sequences" $
+  TestCase $ assertEqual "3.4, "
+  (replicate 10 '\xfffd')
+  (decode [0xc0, 0xe0, 0x80, 0xf0, 0x80, 0x80, 0xf8, 0x80, 0x80, 0x80,
+   0xfc, 0x80, 0x80, 0x80,0x80, 0xdf, 0xef, 0xbf, 0xf7, 0xbf, 0xbf,
+   0xfb, 0xbf, 0xbf, 0xbf, 0xfd, 0xbf, 0xbf, 0xbf,0xbf])
+
+test_3_5 :: Test
+test_3_5 = TestLabel "3.5 Impossible bytes" $
+  TestList $ map TestCase $
+  [ assertEqual "3.5.1, " "\xfffd " (decode [0xfe, 0x20])
+  , assertEqual "3.5.2, " "\xfffd " (decode [0xff, 0x20])
+  , assertEqual "3.5.3, " "\xfffd\xfffd\xfffd\xfffd "
+                          (decode [0xfe, 0xfe, 0xff, 0xff, 0x20])
+  ]
+
+test_4 :: Test
+test_4 = TestLabel "4 Overlong sequences" $
+  TestList [test_4_1, test_4_2, test_4_3]
+
+test_4_1 :: Test
+test_4_1 = TestLabel "4.1" $ TestList $ map TestCase $
+  [ assertEqual "4.1.1, " "\xfffd " (decode [0xc0, 0xaf, 0x20])
+  , assertEqual "4.1.2, " "\xfffd " (decode [0xe0, 0x80, 0xaf, 0x20])
+  , assertEqual "4.1.3, " "\xfffd " (decode [0xf0, 0x80, 0x80, 0xaf, 0x20])
+  , assertEqual "4.1.4, " "\xfffd " (decode [0xf8, 0x80, 0x80,0x80,0xaf, 0x20])
+  , assertEqual "4.1.5, " "\xfffd " (decode[0xfc,0x80,0x80,0x80,0x80,0xaf,0x20])
+  ]
+
+test_4_2 :: Test
+test_4_2 = TestLabel "4.2 Maximum overlong sequences" $
+  TestList $ map TestCase $
+  [ assertEqual "4.2.1, " "\xfffd " (decode [0xc1, 0xbf, 0x20])
+  , assertEqual "4.2.2, " "\xfffd " (decode [0xe0, 0x9f, 0xbf, 0x20])
+  , assertEqual "4.2.3, " "\xfffd " (decode [0xf0, 0x8f, 0xbf, 0xbf, 0x20])
+  , assertEqual "4.2.4, " "\xfffd " (decode [0xf8, 0x87, 0xbf, 0xbf,0xbf,0x20])
+  , assertEqual "4.2.5, " "\xfffd "(decode[0xfc,0x83,0xbf,0xbf,0xbf,0xbf,0x20])
+  ]
+
+test_4_3 :: Test
+test_4_3 = TestLabel "4.2 Overlong NUL" $
+  TestList $ map TestCase $
+  [ assertEqual "4.3.1, " "\xfffd " (decode [0xc0, 0x80, 0x20])
+  , assertEqual "4.3.2, " "\xfffd " (decode [0xe0, 0x80, 0x80, 0x20])
+  , assertEqual "4.3.3, " "\xfffd " (decode [0xf0, 0x80, 0x80, 0x80, 0x20])
+  , assertEqual "4.3.4, " "\xfffd " (decode [0xf8, 0x80, 0x80, 0x80,0x80,0x20])
+  , assertEqual "4.3.5, " "\xfffd "(decode[0xfc,0x80,0x80,0x80,0x80,0x80,0x20])
+  ]
+
+test_5 :: Test
+test_5 = TestLabel "Illegal code positions" $
+  TestList [test_5_1, test_5_2, test_5_3]
+
+test_5_1 :: Test
+test_5_1 = TestLabel "5.1 Single UTF-16 surrogates" $
+  TestList $ map TestCase $
+  [ assertEqual "5.1.1, " "\xfffd " (decode [0xed,0xa0,0x80,0x20])
+  , assertEqual "5.1.2, " "\xfffd " (decode [0xed,0xad,0xbf,0x20])
+  , assertEqual "5.1.3, " "\xfffd " (decode [0xed,0xae,0x80,0x20])
+  , assertEqual "5.1.4, " "\xfffd " (decode [0xed,0xaf,0xbf,0x20])
+  , assertEqual "5.1.5, " "\xfffd " (decode [0xed,0xb0,0x80,0x20])
+  , assertEqual "5.1.6, " "\xfffd " (decode [0xed,0xbe,0x80,0x20])
+  , assertEqual "5.1.7, " "\xfffd " (decode [0xed,0xbf,0xbf,0x20])
+  ]
+
+test_5_2 :: Test
+test_5_2 = TestLabel "5.2 Paired UTF-16 surrogates" $
+  TestList $ map TestCase $
+  [ assertEqual "5.2.1, " res (decode [0xed,0xa0,0x80,0xed,0xb0,0x80,0x20])
+  , assertEqual "5.2.2, " res (decode [0xed,0xa0,0x80,0xed,0xbf,0xbf,0x20])
+  , assertEqual "5.2.3, " res (decode [0xed,0xad,0xbf,0xed,0xb0,0x80,0x20])
+  , assertEqual "5.2.4, " res (decode [0xed,0xad,0xbf,0xed,0xbf,0xbf,0x20])
+  , assertEqual "5.2.5, " res (decode [0xed,0xae,0x80,0xed,0xb0,0x80,0x20])
+  , assertEqual "5.2.6, " res (decode [0xed,0xae,0x80,0xed,0xbf,0xbf,0x20])
+  , assertEqual "5.2.7, " res (decode [0xed,0xaf,0xbf,0xed,0xb0,0x80,0x20])
+  , assertEqual "5.2.8, " res (decode [0xed,0xaf,0xbf,0xed,0xbf,0xbf,0x20])
+  ]
+  where res = "\xfffd\xfffd "
+
+test_5_3 :: Test
+test_5_3 = TestLabel "5.3 Other illegal code positions" $
+  TestList $ map TestCase $
+  [ assertEqual "5.3.1, " "\xfffd " (decode [0xef, 0xbf, 0xbe, 0x20])
+  , assertEqual "5.3.2, " "\xfffd " (decode [0xef, 0xbf, 0xbf, 0x20])
+  ]
+
+test_6 :: Test
+test_6 = TestLabel "Encode then decode" $
+  TestList $ map TestCase $
+  [ assertEqual "6.1" encodeDecodeTest []
+  ]
+
+--
+-- test decode . encode == id for the class of chars we know that to be true of
+--
+encodeDecodeTest :: [Char]
+encodeDecodeTest = filter (\x -> [x] /= decode (encode [x])) legal_codepoints ++
+                   filter (\x -> ['\xfffd'] /= decode (encode [x])) illegal_codepoints
+  where
+    legal_codepoints = ['\0'..'\xd7ff'] ++ ['\xe000'..'\xfffd'] ++ ['\x10000'..'\x10ffff']
+    illegal_codepoints = '\xffff' : '\xfffe' : ['\xd800'..'\xdfff']
+
+
diff --git a/utf8-string.cabal b/utf8-string.cabal
--- a/utf8-string.cabal
+++ b/utf8-string.cabal
@@ -1,10 +1,11 @@
 Name:               utf8-string
-Version:            1
+Version:            1.0.2
 Author:             Eric Mertens
 Maintainer:         emertens@galois.com
 License:            BSD3
 License-file:       LICENSE
-Homepage:           http://github.com/glguy/utf8-string/
+Homepage:           https://github.com/glguy/utf8-string/
+Bug-Reports:        https://github.com/glguy/utf8-string/issues
 Synopsis:           Support for reading and writing UTF8 Strings
 Description:        A UTF8 layer for Strings. The utf8-string
                     package provides operations for encoding UTF8
@@ -12,17 +13,30 @@
                     writing UTF8 without truncation.
 Category:           Codec
 Build-type:         Simple
-cabal-version:      >= 1.2
+cabal-version:      >= 1.10
 Extra-Source-Files: CHANGELOG.markdown
+Tested-With:        GHC==7.0.4, GHC==7.4.2, GHC==7.6.3, GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.1
 
+source-repository head
+  type:               git
+  location:           https://github.com/glguy/utf8-string
+
 library
   Ghc-options:        -W -O2
 
-  build-depends: base >= 4.3 && < 4.9, bytestring >= 0.9
+  build-depends:      base >= 4.3 && < 5, bytestring >= 0.9
 
-  Extensions:         CPP
   Exposed-modules:    Codec.Binary.UTF8.String
                       Codec.Binary.UTF8.Generic
                       Data.String.UTF8
                       Data.ByteString.UTF8
                       Data.ByteString.Lazy.UTF8
+
+  default-language:   Haskell2010
+
+test-suite unit-tests
+  type:               exitcode-stdio-1.0
+  hs-source-dirs:     tests
+  main-is:            Tests.hs
+  build-depends:      base, HUnit >= 1.3 && < 1.7, utf8-string
+  default-language:   Haskell2010
