diff --git a/ip.cabal b/ip.cabal
--- a/ip.cabal
+++ b/ip.cabal
@@ -1,15 +1,15 @@
-name:                ip
-version:             0.9.3
-synopsis:            Library for IP and MAC addresses
-homepage:            https://github.com/andrewthad/haskell-ip#readme
-license:             BSD3
-license-file:        LICENSE
-author:              Andrew Martin
-maintainer:          andrew.thaddeus@gmail.com
-copyright:           2016 Andrew Martin
-category:            web
-build-type:          Simple
-cabal-version:       >=1.10
+name: ip
+version: 1.0.0
+synopsis: Library for IP and MAC addresses
+homepage: https://github.com/andrewthad/haskell-ip#readme
+license: BSD3
+license-file: LICENSE
+author: Andrew Martin
+maintainer: andrew.thaddeus@gmail.com
+copyright: 2016 Andrew Martin
+category: web
+build-type: Simple
+cabal-version: >=1.10
 description:
   The `ip` package provides types and functions for dealing with
   IPv4 addresses, CIDR blocks, and MAC addresses. We provide instances
@@ -20,9 +20,7 @@
   typeclasses of its own. Neither does it prefix functions with the name
   of the type that they work on. Instead, functions of the same name are
   exported by several different modules, and it is expected that end users
-  disambiguate by importing these modules qualified. For example,
-  `Data.IPv4.ByteString.Char8` and `Data.IPv4.Text` have nearly identical
-  export lists.
+  disambiguate by importing these modules qualified.
   .
   The only module intended to be imported unqualified is `Net.Types`. The
   types in this package should not conflict with the types in
@@ -32,47 +30,35 @@
   .
   * `yesod-ip`: Provides orphan instances needed to work with yesod and
     persistent. Also, provides a `yesod-form` helper.
-  .
-  * `impure-containers`: Provides a trie that can be used for looking
-    up which subnet an IP address belongs in.
 
 library
-  hs-source-dirs:      src
+  hs-source-dirs: src
   exposed-modules:
     Net.Mac
-    Net.Mac.Text
-    Net.Mac.ByteString.Char8
     Net.IPv4
     Net.IPv4.Range
-    Net.IPv4.Range.Text
-    Net.IPv4.String
-    Net.IPv4.Text
-    Net.IPv4.ByteString.Char8
     Net.IPv6
-    Net.IPv6.Text
     Net.IP
     Net.Types
-    Net.Internal
-    Data.Word.Synthetic
-    Data.Word.Synthetic.Word48
+  other-modules:
     Data.Word.Synthetic.Word12
     Data.Text.Builder.Fixed
     Data.Text.Builder.Variable
     Data.Text.Builder.Common.Internal
     Data.ByteString.Builder.Fixed
   build-depends:
-      base        >= 4.8  && < 5
+      base >= 4.8  && < 5
     , attoparsec
-    , aeson       >= 0.9  && < 1.3
-    , hashable    >= 1.2  && < 1.3
-    , text        >= 1.2  && < 1.3
-    , bytestring  >= 0.10 && < 0.11
-    , vector      >= 0.11 && < 0.13
+    , aeson >= 0.9  && < 1.3
+    , hashable >= 1.2  && < 1.3
+    , text >= 1.2  && < 1.3
+    , bytestring >= 0.10 && < 0.11
+    , vector >= 0.11 && < 0.13
     , primitive
   -- if impl(ghcjs)
   --   build-depends: ghcjs-base >= 0.2 && < 0.3
-  ghc-options:         -Wall -O2
-  default-language:    Haskell2010
+  ghc-options: -Wall -O2
+  default-language: Haskell2010
 
 test-suite test
   type:                exitcode-stdio-1.0
@@ -90,12 +76,11 @@
     , test-framework-hunit
     , attoparsec
   other-modules:
-    ArbitraryInstances
     Naive
     IPv4Text1
     IPv4Text2
     IPv4ByteString1
-    IPv4TextVariableBuilder
+    -- IPv4TextVariableBuilder
   ghc-options:         -Wall -O2
   default-language:    Haskell2010
 
@@ -111,7 +96,7 @@
   default-language:    Haskell2010
 
 benchmark criterion
-  type:                exitcode-stdio-1.0
+  type: exitcode-stdio-1.0
   build-depends:
       base
     , ip
@@ -127,12 +112,12 @@
     IPv4DecodeText2
     IPv4ByteString1
     IPv4TextVariableBuilder
-  ghc-options:         -Wall -O2
-  default-language:    Haskell2010
-  hs-source-dirs:      test
-  main-is:             Bench.hs
+  ghc-options: -Wall -O2
+  default-language: Haskell2010
+  hs-source-dirs: test
+  main-is: Bench.hs
 
 source-repository head
-  type:     git
+  type: git
   location: https://github.com/andrewthad/haskell-ip
 
diff --git a/src/Data/ByteString/Builder/Fixed.hs b/src/Data/ByteString/Builder/Fixed.hs
--- a/src/Data/ByteString/Builder/Fixed.hs
+++ b/src/Data/ByteString/Builder/Fixed.hs
@@ -25,7 +25,7 @@
 import Control.Monad.ST
 import Data.Monoid
 import Data.Word
-import Data.Word.Synthetic (Word12)
+import Data.Word.Synthetic.Word12 (Word12)
 import Data.Bits
 import Data.Char (ord)
 import Text.Printf
diff --git a/src/Data/Text/Builder/Common/Internal.hs b/src/Data/Text/Builder/Common/Internal.hs
--- a/src/Data/Text/Builder/Common/Internal.hs
+++ b/src/Data/Text/Builder/Common/Internal.hs
@@ -8,7 +8,6 @@
 import Text.Printf
 import Debug.Trace
 import Data.Char (ord)
-import Data.Word.Synthetic (Word12)
 import Data.Vector (Vector)
 import Data.Foldable (fold)
 import qualified Data.Vector as Vector
diff --git a/src/Data/Text/Builder/Fixed.hs b/src/Data/Text/Builder/Fixed.hs
--- a/src/Data/Text/Builder/Fixed.hs
+++ b/src/Data/Text/Builder/Fixed.hs
@@ -28,7 +28,7 @@
 import Text.Printf
 import Debug.Trace
 import Data.Char (ord)
-import Data.Word.Synthetic (Word12)
+import Data.Word.Synthetic.Word12 (Word12)
 import Data.Vector (Vector)
 import Data.Foldable (fold)
 import Data.Text (Text)
diff --git a/src/Data/Word/Synthetic.hs b/src/Data/Word/Synthetic.hs
deleted file mode 100644
--- a/src/Data/Word/Synthetic.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module Data.Word.Synthetic
-  ( Word48
-  , Word12
-  ) where
-
-import Data.Word.Synthetic.Word48 (Word48)
-import Data.Word.Synthetic.Word12 (Word12)
-
diff --git a/src/Data/Word/Synthetic/Word48.hs b/src/Data/Word/Synthetic/Word48.hs
deleted file mode 100644
--- a/src/Data/Word/Synthetic/Word48.hs
+++ /dev/null
@@ -1,252 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module Data.Word.Synthetic.Word48 where
-
-import Data.Bits
-import Data.Word
-import Data.Int
-import Data.Ratio
-import GHC.Arr
-import GHC.Enum
-import Data.Primitive.Types
-import Data.Hashable (Hashable)
-import qualified Data.Vector.Unboxed as UVector
-import qualified Data.Vector.Generic as GVector
-import qualified Data.Vector.Unboxed.Mutable as MUVector
-import qualified Data.Vector.Generic.Mutable as MGVector
-
-newtype Word48 = W48 Word64
-  deriving (Eq,Ord,Hashable)
-
-narrow48Word :: Word64 -> Word64
-narrow48Word = (.&.) 0xFFFFFFFFFFFF
-{-# INLINE narrow48Word #-}
-
-popCnt48 :: Word64 -> Int
-popCnt48 = popCount . narrow48Word
-{-# INLINE popCnt48 #-}
-
-word48UpperWord16 :: Word48 -> Word16
-word48UpperWord16 (W48 w) = fromIntegral (unsafeShiftR w 32)
-
-word48LowerWord32 :: Word48 -> Word32
-word48LowerWord32 (W48 w) = fromIntegral w
-
-word48FromUpperLower :: Word16 -> Word32 -> Word48
-word48FromUpperLower a b =
-  W48 (unsafeShiftL (fromIntegral a) 32 .|. fromIntegral b)
-
-instance Show Word48 where
-  showsPrec p (W48 x) = showsPrec p x
-
-instance Num Word48 where
-  (W48 x) + (W48 y) = W48 (narrow48Word (x + y))
-  (W48 x) - (W48 y) = W48 (narrow48Word (x - y))
-  (W48 x) * (W48 y) = W48 (narrow48Word (x * y))
-  negate (W48 x) = W48 (negate (fromIntegral (negate (fromIntegral x :: Int64))))
-  abs x = x
-  signum 0 = 0
-  signum _ = 1
-  fromInteger i = W48 (narrow48Word (fromInteger i))
-
-instance Real Word48 where
-  toRational (W48 x) = toInteger x % 1
-
-instance Enum Word48 where
-  succ x
-    | x /= maxBound = x + 1
-    | otherwise = succError "Word48"
-  pred x
-    | x /= minBound  = x - 1
-    | otherwise      = predError "Word48"
-  toEnum i
-    | i >= 0 && i <= fromIntegral (maxBound :: Word48)
-                     = W48 (fromIntegral i)
-    | otherwise      = toEnumError "Word48" i (minBound::Word48, maxBound::Word48)
-  -- Causes a bounds check to occur twice
-  fromEnum (W48 x)
-    | x <= fromIntegral (maxBound :: Int) = fromIntegral x
-    | otherwise = fromEnumError "Word48" x
-  enumFrom           = boundedEnumFrom
-  enumFromThen       = boundedEnumFromThen
-
-instance Integral Word48 where
-  quot (W48 x) (W48 y) = W48 (x `quot` y)
-  rem (W48 x) (W48 y) = W48 (x `rem` y)
-  div (W48 x) (W48 y) = W48 (x `quot` y)
-  mod (W48 x) (W48 y) = W48 (x `rem` y)
-  quotRem (W48 x) (W48 y) = (W48 (x `quot` y), W48 (x `rem` y))
-  divMod (W48 x) (W48 y) = (W48 (x `quot` y), W48 (x `rem` y))
-  toInteger (W48 x) = toInteger x
-
-instance Bounded Word48 where
-  minBound = 0
-  maxBound = 0xFFFFFFFFFFFF
-
-instance Ix Word48 where
-  range (m,n)         = [m..n]
-  unsafeIndex (m,_) i = fromIntegral (i - m)
-  inRange (m,n) i     = m <= i && i <= n
-
-instance Read Word48 where
-  readsPrec p s = [(fromIntegral (x::Word64), r) | (x, r) <- readsPrec p s]
-
-instance Bits Word48 where
-    {-# INLINE shift #-}
-    {-# INLINE bit #-}
-    {-# INLINE testBit #-}
-
-    (W48 x) .&.   (W48 y)  = W48 (x .&. y)
-    (W48 x) .|.   (W48 y)  = W48 (x .|.  y)
-    (W48 x) `xor` (W48 y)  = W48 (x `xor` y)
-    complement (W48 x) = W48 (x `xor` mb) where !(W48 mb) = maxBound
-    (W48 x) `shift` i
-        | i >= 0 = W48 (narrow48Word (x `shiftL` i))
-        | otherwise = W48 (x `shiftR` negate i)
-    (W48 x) `shiftL` i = W48 (narrow48Word (x `shiftL` i))
-    (W48 x) `unsafeShiftL` i =
-        W48 (narrow48Word (x `unsafeShiftL` i))
-    (W48 x) `shiftR`       i = W48 (x `shiftR` i)
-    (W48 x) `unsafeShiftR` i = W48 (x `unsafeShiftR` i)
-    (W48 x) `rotate`       i
-        | i' == 0 = W48 x
-        | otherwise = W48 (narrow48Word ((x `unsafeShiftL` i') .|.
-                                            (x `unsafeShiftR` (48 - i'))))
-      where
-        !i' = i `mod` 48
-    bitSizeMaybe i = Just (finiteBitSize i)
-    bitSize = finiteBitSize
-    isSigned _ = False
-    popCount (W48 x) = popCnt48 x
-    bit = bitDefault
-    testBit = testBitDefault
-
-instance FiniteBits Word48 where
-    finiteBitSize _ = 48
-    countLeadingZeros (W48 x) = countLeadingZeros x - 16
-    countTrailingZeros (W48 x) = countTrailingZeros x
-
-data instance MUVector.MVector s Word48
-  = MV_Word48
-      {-# UNPACK #-} !Int
-      !(MUVector.MVector s Word16)
-      !(MUVector.MVector s Word32)
-
-data instance UVector.Vector Word48
-  = V_Word48
-      {-# UNPACK #-} !Int
-      !(UVector.Vector Word16)
-      !(UVector.Vector Word32)
-
-instance UVector.Unbox Word48
-
-instance MGVector.MVector MUVector.MVector Word48 where
-  {-# INLINE basicLength  #-}
-  basicLength (MV_Word48 n_ as bs) = n_
-  {-# INLINE basicUnsafeSlice  #-}
-  basicUnsafeSlice i_ m_ (MV_Word48 n_ as bs)
-      = MV_Word48 m_
-          (MGVector.basicUnsafeSlice i_ m_ as)
-          (MGVector.basicUnsafeSlice i_ m_ bs)
-  {-# INLINE basicOverlaps  #-}
-  basicOverlaps (MV_Word48 n_1 as1 bs1) (MV_Word48 n_2 as2 bs2)
-      = MGVector.basicOverlaps as1 as2
-        || MGVector.basicOverlaps bs1 bs2
-  {-# INLINE basicUnsafeNew  #-}
-  basicUnsafeNew n_
-      = do
-          as <- MGVector.basicUnsafeNew n_
-          bs <- MGVector.basicUnsafeNew n_
-          return $ MV_Word48 n_ as bs
-  {-# INLINE basicInitialize  #-}
-  basicInitialize (MV_Word48 _ as bs)
-      = do
-          MGVector.basicInitialize as
-          MGVector.basicInitialize bs
-  {-# INLINE basicUnsafeReplicate  #-}
-  basicUnsafeReplicate n_ w
-      = do
-          let upper = word48UpperWord16 w
-              lower = word48LowerWord32 w
-          as <- MGVector.basicUnsafeReplicate n_ upper
-          bs <- MGVector.basicUnsafeReplicate n_ lower
-          return $ MV_Word48 n_ as bs
-  {-# INLINE basicUnsafeRead  #-}
-  basicUnsafeRead (MV_Word48 n_ as bs) i_
-      = do
-          a <- MGVector.basicUnsafeRead as i_
-          b <- MGVector.basicUnsafeRead bs i_
-          return (word48FromUpperLower a b)
-  {-# INLINE basicUnsafeWrite  #-}
-  basicUnsafeWrite (MV_Word48 n_ as bs) i_ w
-      = do
-          let upper = word48UpperWord16 w
-              lower = word48LowerWord32 w
-          MGVector.basicUnsafeWrite as i_ upper
-          MGVector.basicUnsafeWrite bs i_ lower
-  {-# INLINE basicClear  #-}
-  basicClear (MV_Word48 n_ as bs)
-      = do
-          MGVector.basicClear as
-          MGVector.basicClear bs
-  {-# INLINE basicSet  #-}
-  basicSet (MV_Word48 n_ as bs) w
-      = do
-          let upper = word48UpperWord16 w
-              lower = word48LowerWord32 w
-          MGVector.basicSet as upper
-          MGVector.basicSet bs lower
-  {-# INLINE basicUnsafeCopy  #-}
-  basicUnsafeCopy (MV_Word48 n_1 as1 bs1) (MV_Word48 n_2 as2 bs2)
-      = do
-          MGVector.basicUnsafeCopy as1 as2
-          MGVector.basicUnsafeCopy bs1 bs2
-  {-# INLINE basicUnsafeMove  #-}
-  basicUnsafeMove (MV_Word48 n_1 as1 bs1) (MV_Word48 n_2 as2 bs2)
-      = do
-          MGVector.basicUnsafeMove as1 as2
-          MGVector.basicUnsafeMove bs1 bs2
-  {-# INLINE basicUnsafeGrow  #-}
-  basicUnsafeGrow (MV_Word48 n_ as bs) m_
-      = do
-          as' <- MGVector.basicUnsafeGrow as m_
-          bs' <- MGVector.basicUnsafeGrow bs m_
-          return $ MV_Word48 (m_+n_) as' bs'
-
-instance GVector.Vector UVector.Vector Word48 where
-  {-# INLINE basicUnsafeFreeze  #-}
-  basicUnsafeFreeze (MV_Word48 n_ as bs)
-      = do
-          as' <- GVector.basicUnsafeFreeze as
-          bs' <- GVector.basicUnsafeFreeze bs
-          return $ V_Word48 n_ as' bs'
-  {-# INLINE basicUnsafeThaw  #-}
-  basicUnsafeThaw (V_Word48 n_ as bs)
-      = do
-          as' <- GVector.basicUnsafeThaw as
-          bs' <- GVector.basicUnsafeThaw bs
-          return $ MV_Word48 n_ as' bs'
-  {-# INLINE basicLength  #-}
-  basicLength (V_Word48 n_ as bs) = n_
-  {-# INLINE basicUnsafeSlice  #-}
-  basicUnsafeSlice i_ m_ (V_Word48 n_ as bs)
-      = V_Word48 m_ (GVector.basicUnsafeSlice i_ m_ as)
-                 (GVector.basicUnsafeSlice i_ m_ bs)
-  {-# INLINE basicUnsafeIndexM  #-}
-  basicUnsafeIndexM (V_Word48 n_ as bs) i_
-      = do
-          a <- GVector.basicUnsafeIndexM as i_
-          b <- GVector.basicUnsafeIndexM bs i_
-          return (word48FromUpperLower a b)
-  {-# INLINE basicUnsafeCopy  #-}
-  basicUnsafeCopy (MV_Word48 n_1 as1 bs1) (V_Word48 n_2 as2 bs2)
-      = do
-          GVector.basicUnsafeCopy as1 as2
-          GVector.basicUnsafeCopy bs1 bs2
-  {-# INLINE elemseq  #-}
-  elemseq _ = seq
-
-
diff --git a/src/Net/IP.hs b/src/Net/IP.hs
--- a/src/Net/IP.hs
+++ b/src/Net/IP.hs
@@ -1,7 +1,29 @@
-module Net.IP where
+module Net.IP
+  ( -- * Pattern Matching
+    case_
+  , ipv4
+  , ipv6
+    -- * Construction
+  , fromIPv4
+  , fromIPv6
+    -- * Textual Conversion
+    -- ** Text
+  , encode
+  , decode
+    -- * Types
+  , IP(..)
+  ) where
 
-import Net.Types
 import Data.Bits
+import Net.IPv6 (IPv6(..))
+import Net.IPv4 (IPv4(..))
+import Text.Read (Read(..),Lexeme(Ident),lexP,parens)
+import Text.ParserCombinators.ReadPrec (prec,step,(+++))
+import Data.Aeson (FromJSON(..),ToJSON(..))
+import Data.Text (Text)
+import qualified Net.IPv4 as IPv4
+import qualified Net.IPv6 as IPv6
+import qualified Data.Aeson as Aeson
 
 case_ :: (IPv4 -> a) -> (IPv6 -> a) -> IP -> a
 case_ f g (IP addr@(IPv6 w1 w2)) = if w1 == 0 && (0xFFFFFFFF00000000 .&. w2 == 0x0000FFFF00000000)
@@ -16,4 +38,52 @@
 --   an IPv4-mapped IPv6 address, return the address.
 ipv6 :: IP -> Maybe IPv6
 ipv6 = case_ (const Nothing) Just
+
+fromIPv4 :: IPv4 -> IP
+fromIPv4 (IPv4 w) = IP (IPv6 0 (0x0000FFFF00000000 .|. fromIntegral w))
+
+fromIPv6 :: IPv6 -> IP
+fromIPv6 = IP
+
+encode :: IP -> Text
+encode = case_ IPv4.encode IPv6.encode
+
+decode :: Text -> Maybe IP
+decode t = case IPv4.decode t of
+  Nothing -> case IPv6.decode t of
+    Nothing -> Nothing
+    Just v6 -> Just (fromIPv6 v6)
+  Just v4 -> Just (fromIPv4 v4)
+
+-- | A 32-bit 'IPv4' address or a 128-bit 'IPv6' address. Internally, this
+--   is just represented as an 'IPv6' address. The functions provided
+--   in @Net.IP@ help simulate constructing and pattern matching on values
+--   of this type. All functions and typeclass methods that convert
+--   'IP' values to text will display it as an 'IPv4' address if possible.
+newtype IP = IP { getIP :: IPv6 }
+  deriving (Eq,Ord)
+
+instance Show IP where
+  showsPrec p = case_
+    (\x -> showParen (p > 10) $ showString "fromIPv4 " . showsPrec 11 x)
+    (\x -> showParen (p > 10) $ showString "fromIPv6 " . showsPrec 11 x)
+
+instance Read IP where
+  readPrec = parens $
+    ( prec 10 $ do
+        Ident "fromIPv4 " <- lexP
+        fmap fromIPv4 (step readPrec)
+    ) +++
+    ( prec 10 $ do
+        Ident "fromIPv6 " <- lexP
+        fmap fromIPv6 (step readPrec)
+    )
+
+instance ToJSON IP where
+  toJSON = Aeson.String . encode
+
+instance FromJSON IP where
+  parseJSON = Aeson.withText "IP" $ \t -> case decode t of
+    Nothing -> fail "Could not parse IP address"
+    Just addr -> return addr
 
diff --git a/src/Net/IPv4.hs b/src/Net/IPv4.hs
--- a/src/Net/IPv4.hs
+++ b/src/Net/IPv4.hs
@@ -1,3 +1,12 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE CPP #-}
+
+{-# OPTIONS_GHC -Wall #-}
 {-| An IPv4 data type
 
     This module provides the IPv4 data type and functions for working
@@ -24,51 +33,98 @@
   , private
   , reserved
   , public
+    -- * Textual Conversion
+    -- ** Text
+  , encode
+  , decode
+  , builder
+  , reader
+  , parser
+  , print
+    -- ** UTF-8 ByteString
+  , encodeUtf8
+  , decodeUtf8
+  , builderUtf8
+  , parserUtf8
+    -- ** String
+    -- $string
+  , encodeString
+  , decodeString
+    -- * Types
+  , IPv4(..)
   ) where
 
-import Prelude hiding (any)
-import Net.Types (IPv4(..))
-import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement)
+import Prelude hiding (any,print)
+import Data.Bits ((.&.),(.|.),shiftR,shiftL,unsafeShiftR)
 import Data.Word
-import Data.Int
 import Data.Hashable
 import Data.Aeson (FromJSON(..),ToJSON(..))
 import GHC.Generics (Generic)
-import Net.Internal (attoparsecParseJSON,rightToMaybe)
 import Control.Monad
 import Data.Text.Internal (Text(..))
-import Data.Coerce (coerce)
-import Unsafe.Coerce (unsafeCoerce)
 import Data.ByteString (ByteString)
-import Data.Vector.Generic.Mutable      (MVector(..))
-import Control.Monad.Primitive          (PrimMonad,PrimState)
-import qualified Net.Internal           as Internal
-import qualified Data.Text.Lazy         as LText
-import qualified Data.Text.IO           as Text
-import qualified Data.ByteString        as ByteString
+import Data.Vector.Generic.Mutable (MVector(..))
+import Foreign.Ptr (Ptr,plusPtr)
+import Foreign.Storable (poke)
+import Data.Monoid ((<>))
+import Data.Text.Encoding (decodeUtf8')
+import Foreign.Storable (Storable)
+import Data.Bits (Bits,FiniteBits)
+import Data.Primitive.Types (Prim)
+import Control.Monad.ST (ST,runST)
+import Text.Printf (printf)
+import Text.Read (Read(..),Lexeme(Ident),lexP,parens)
+import Text.ParserCombinators.ReadPrec (prec,step)
+import qualified Data.Text.Read as TextRead
+import qualified Data.ByteString.Builder as Builder
+import qualified Data.Text.IO as Text
 import qualified Data.ByteString.Unsafe as ByteString
+import qualified Data.Text.Read as Text (Reader)
+import qualified Data.Text.Lazy.Builder as TBuilder
+import qualified Data.Attoparsec.Text as AT
+import qualified Data.Attoparsec.ByteString.Char8 as AB
+import qualified Data.ByteString.Char8 as BC8
+import qualified Data.Text as Text
+import qualified Data.ByteString.Internal as I
+import qualified Data.ByteString.Unsafe as BSU
+import qualified Data.Vector.Generic as GVector
+import qualified Data.Vector.Generic.Mutable as MGVector
+import qualified Data.Vector.Unboxed as UVector
+import qualified Data.Vector.Primitive as PVector
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
+import qualified Data.ByteString.Builder as BB
+import qualified Data.Text.Array as TArray
 
+#if MIN_VERSION_aeson(1,0,0) 
+import Data.Aeson (ToJSONKey(..),FromJSONKey(..),
+  ToJSONKeyFunction(..),FromJSONKeyFunction(..))
+#endif
+
 -- $setup
 --
 -- These are here to get doctest's property checking to work
 --
 -- >>> import Test.QuickCheck (Arbitrary(..))
+-- >>> import qualified Prelude as P
 -- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }
 --
 
 -- | Create an 'IPv4' address from four octets. The first argument
 --   is the most significant octet. The last argument is the least
---   significant.
---
---   Since the 'Show' and 'Read' instances for 'IPv4' are not generally
---   usefully, this function is the recommened way to create 'IPv4' addresses.
---   For example:
+--   significant. Since IP addresses are commonly written using dot-decimal
+--   notation, this is the recommended way to create an IP address.
+--   Additionally, it is used for the 'Show' and 'Read' instances
+--   of 'IPv4' to help keep things readable in GHCi.
 --
---   >>> fromOctets 192 168 1 1
---   IPv4 {getIPv4 = 3232235777}
+--   >>> let ip = fromOctets 192 168 1 1
+--   >>> ip
+--   fromOctets 192 168 1 1
+--   >>> getIPv4 ip
+--   3232235777
 --
 fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4
-fromOctets a b c d = IPv4 $ Internal.fromOctets'
+fromOctets a b c d = fromOctets'
   (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)
 
 -- | Convert an 'IPv4' address into a quadruple of octets. The first
@@ -99,9 +155,9 @@
 -- @10.0.0.0/8@, @172.16.0.0/12@, and @192.168.0.0/16@.
 private :: IPv4 -> Bool
 private (IPv4 w) =
-     Internal.mask8  .&. w == Internal.p24
-  || Internal.mask12 .&. w == Internal.p20
-  || Internal.mask16 .&. w == Internal.p16
+     mask8  .&. w == p24
+  || mask12 .&. w == p20
+  || mask16 .&. w == p16
 
 -- | Checks to see if the 'IPv4' address belongs to a reserved
 -- network. This includes the three private networks that 'private'
@@ -109,48 +165,406 @@
 -- on the public Internet.
 reserved :: IPv4 -> Bool
 reserved =
-  let a = Internal.fromOctets' 0 0 0 0
-      b = Internal.fromOctets' 100 64 0 0
-      c = Internal.fromOctets' 127 0 0 0
-      d = Internal.fromOctets' 169 254 0 0
-      e = Internal.fromOctets' 192 0 0 0
-      f = Internal.fromOctets' 192 0 2 0
-      g = Internal.fromOctets' 192 88 99 0
-      h = Internal.fromOctets' 198 18 0 0
-      i = Internal.fromOctets' 198 51 100 0
-      j = Internal.fromOctets' 203 0 113 0
-      k = Internal.fromOctets' 224 0 0 0
-      l = Internal.fromOctets' 240 0 0 0
-      m = Internal.fromOctets' 255 255 255 255
-  in \(IPv4 w) -> Internal.mask8  .&. w == Internal.p24
-               || Internal.mask12 .&. w == Internal.p20
-               || Internal.mask16 .&. w == Internal.p16
-               || Internal.mask8  .&. w == a
-               || Internal.mask10 .&. w == b
-               || Internal.mask16 .&. w == d
-               || Internal.mask24 .&. w == e
-               || Internal.mask24 .&. w == g
-               || Internal.mask15 .&. w == h
-               || Internal.mask24 .&. w == i
-               || Internal.mask24 .&. w == j
-               || Internal.mask4  .&. w == k
-               || Internal.mask4  .&. w == l
-               || Internal.mask32 .&. w == m
+  let a = getIPv4 $ fromOctets' 0 0 0 0
+      b = getIPv4 $ fromOctets' 100 64 0 0
+      c = getIPv4 $ fromOctets' 127 0 0 0
+      d = getIPv4 $ fromOctets' 169 254 0 0
+      e = getIPv4 $ fromOctets' 192 0 0 0
+      f = getIPv4 $ fromOctets' 192 0 2 0
+      g = getIPv4 $ fromOctets' 192 88 99 0
+      h = getIPv4 $ fromOctets' 198 18 0 0
+      i = getIPv4 $ fromOctets' 198 51 100 0
+      j = getIPv4 $ fromOctets' 203 0 113 0
+      k = getIPv4 $ fromOctets' 224 0 0 0
+      l = getIPv4 $ fromOctets' 240 0 0 0
+      m = getIPv4 $ fromOctets' 255 255 255 255
+  in \(IPv4 w) -> mask8  .&. w == p24
+               || mask12 .&. w == p20
+               || mask16 .&. w == p16
+               || mask8  .&. w == a
+               || mask10 .&. w == b
+               || mask8  .&. w == c
+               || mask16 .&. w == d
+               || mask24 .&. w == e
+               || mask24 .&. w == f
+               || mask24 .&. w == g
+               || mask15 .&. w == h
+               || mask24 .&. w == i
+               || mask24 .&. w == j
+               || mask4  .&. w == k
+               || mask4  .&. w == l
+               || mask32 .&. w == m
 
+mask8,mask4,mask12,mask16,mask10,mask24,mask32,mask15 :: Word32
+mask4  = 0xF0000000
+mask8  = 0xFF000000
+mask10 = 0xFFC00000
+mask12 = 0xFFF00000
+mask15 = 0xFFFE0000
+mask16 = 0xFFFF0000
+mask24 = 0xFFFFFF00
+mask32 = 0xFFFFFFFF
+
 -- | Checks to see if the 'IPv4' address is publicly routable.
 --
 -- prop> public x == not (reserved x)
 public :: IPv4 -> Bool
 public = not . reserved
 
--- | $internal
--- Everything below here is not part of the stable API. Many of these
--- functions must live here because they are needed for the 'ToJSON' and
--- 'FromJSON' instances. Hopefully, at some point, these can be removed
--- from this module.
+encode :: IPv4 -> Text
+encode = toDotDecimalText
 
--- This only exists for doctests. Do not use it.
--- prAddr :: IPv4 -> IO ()
--- prAddr (IPv4 addr) = Text.putStrLn (Internal.toDotDecimalText addr)
+decode :: Text -> Maybe IPv4
+decode = decodeIPv4TextMaybe
 
+builder :: IPv4 -> TBuilder.Builder
+builder = toDotDecimalBuilder
+
+reader :: Text.Reader IPv4
+reader = decodeIPv4TextReader
+
+parser :: AT.Parser IPv4
+parser = dotDecimalParser
+
+-- | This exists mostly for testing purposes.
+print :: IPv4 -> IO ()
+print = Text.putStrLn . encode
+
+encodeUtf8 :: IPv4 -> ByteString
+encodeUtf8 = toBSPreAllocated
+
+toBSPreAllocated :: IPv4 -> ByteString
+toBSPreAllocated (IPv4 !w) = I.unsafeCreateUptoN 15 (\ptr1 ->
+  do len1 <- writeWord ptr1 w1
+     let ptr2 = ptr1 `plusPtr` len1
+     poke ptr2 dot
+     len2 <- writeWord (ptr2 `plusPtr` 1) w2
+     let ptr3 = ptr2 `plusPtr` len2 `plusPtr` 1
+     poke ptr3 dot
+     len3 <- writeWord (ptr3 `plusPtr` 1) w3
+     let ptr4 = ptr3 `plusPtr` len3 `plusPtr` 1
+     poke ptr4 dot
+     len4 <- writeWord (ptr4 `plusPtr` 1) w4
+     return (3 + len1 + len2 + len3 + len4))
+  where w1 = fromIntegral $ shiftR w 24
+        w2 = fromIntegral $ shiftR w 16
+        w3 = fromIntegral $ shiftR w 8
+        w4 = fromIntegral w
+        dot = 46 :: Word8
+        writeWord :: Ptr Word8 -> Word8 -> IO Int
+        writeWord !ptr !word
+          | word >= 100 = do
+              let int = fromIntegral word
+                  indx = int + int + int
+                  get3 = fromIntegral . BSU.unsafeIndex threeDigits
+              poke ptr (get3 indx)
+              poke (ptr `plusPtr` 1) (get3 (indx + 1))
+              poke (ptr `plusPtr` 2) (get3 (indx + 2))
+              return 3
+          | word >= 10 = do
+              let int = fromIntegral word
+                  indx = int + int
+                  get2 = fromIntegral . BSU.unsafeIndex twoDigits
+              poke ptr (get2 indx)
+              poke (ptr `plusPtr` 1) (get2 (indx + 1))
+              return 2
+          | otherwise = do
+              poke ptr (word + 48)
+              return 1
+
+-- This should be rewritten to not go through text
+-- as an intermediary.
+decodeUtf8 :: ByteString -> Maybe IPv4
+decodeUtf8 = decode <=< rightToMaybe . decodeUtf8'
+
+builderUtf8 :: IPv4 -> Builder.Builder
+builderUtf8 = Builder.byteString . encodeUtf8
+
+parserUtf8 :: AB.Parser IPv4
+parserUtf8 = fromOctets'
+  <$> (AB.decimal >>= limitSize)
+  <*  AB.char '.'
+  <*> (AB.decimal >>= limitSize)
+  <*  AB.char '.'
+  <*> (AB.decimal >>= limitSize)
+  <*  AB.char '.'
+  <*> (AB.decimal >>= limitSize)
+  where
+  limitSize i =
+    if i > 255
+      then fail "All octets in an ip address must be between 0 and 255"
+      else return i
+
+{- $string
+ 
+    These functions exist for the convenience of those who need a
+    'String' representation of an 'IPv4' address. Using them
+    is discouraged unless the end user is working with a library
+    that can only use 'String' to deal with textual data (such as
+    @pandoc@, @hxr@, or @network@).
+
+-}
+
+encodeString :: IPv4 -> String
+encodeString = Text.unpack . encode
+
+decodeString :: String -> Maybe IPv4
+decodeString = decode . Text.pack
+
+
+-- | A 32-bit Internet Protocol version 4 address.
+newtype IPv4 = IPv4 { getIPv4 :: Word32 }
+  deriving (Eq,Ord,Enum,Bounded,Hashable,Generic,Prim,Bits,FiniteBits,Storable)
+
+instance Show IPv4 where
+  showsPrec p addr = showParen (p > 10)
+    $ showString "fromOctets "
+    . showsPrec 11 a
+    . showChar ' '
+    . showsPrec 11 b
+    . showChar ' '
+    . showsPrec 11 c
+    . showChar ' '
+    . showsPrec 11 d
+    where
+    (a,b,c,d) = toOctets addr
+
+instance Read IPv4 where
+  readPrec = parens $ prec 10 $ do
+    Ident "fromOctets" <- lexP
+    a <- step readPrec
+    b <- step readPrec
+    c <- step readPrec
+    d <- step readPrec
+    return (fromOctets a b c d)
+    
+
+newtype instance UVector.MVector s IPv4 = MV_IPv4 (PVector.MVector s IPv4)
+newtype instance UVector.Vector IPv4 = V_IPv4 (PVector.Vector IPv4)
+
+instance UVector.Unbox IPv4
+
+instance MGVector.MVector UVector.MVector IPv4 where
+  {-# INLINE basicLength #-}
+  {-# INLINE basicUnsafeSlice #-}
+  {-# INLINE basicOverlaps #-}
+  {-# INLINE basicUnsafeNew #-}
+  {-# INLINE basicInitialize #-}
+  {-# INLINE basicUnsafeReplicate #-}
+  {-# INLINE basicUnsafeRead #-}
+  {-# INLINE basicUnsafeWrite #-}
+  {-# INLINE basicClear #-}
+  {-# INLINE basicSet #-}
+  {-# INLINE basicUnsafeCopy #-}
+  {-# INLINE basicUnsafeGrow #-}
+  basicLength (MV_IPv4 v) = MGVector.basicLength v
+  basicUnsafeSlice i n (MV_IPv4 v) = MV_IPv4 $ MGVector.basicUnsafeSlice i n v
+  basicOverlaps (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicOverlaps v1 v2
+  basicUnsafeNew n = MV_IPv4 `liftM` MGVector.basicUnsafeNew n
+  basicInitialize (MV_IPv4 v) = MGVector.basicInitialize v
+  basicUnsafeReplicate n x = MV_IPv4 `liftM` MGVector.basicUnsafeReplicate n x
+  basicUnsafeRead (MV_IPv4 v) i = MGVector.basicUnsafeRead v i
+  basicUnsafeWrite (MV_IPv4 v) i x = MGVector.basicUnsafeWrite v i x
+  basicClear (MV_IPv4 v) = MGVector.basicClear v
+  basicSet (MV_IPv4 v) x = MGVector.basicSet v x
+  basicUnsafeCopy (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeCopy v1 v2
+  basicUnsafeMove (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeMove v1 v2
+  basicUnsafeGrow (MV_IPv4 v) n = MV_IPv4 `liftM` MGVector.basicUnsafeGrow v n
+
+instance GVector.Vector UVector.Vector IPv4 where
+  {-# INLINE basicUnsafeFreeze #-}
+  {-# INLINE basicUnsafeThaw #-}
+  {-# INLINE basicLength #-}
+  {-# INLINE basicUnsafeSlice #-}
+  {-# INLINE basicUnsafeIndexM #-}
+  {-# INLINE elemseq #-}
+  basicUnsafeFreeze (MV_IPv4 v) = V_IPv4 `liftM` GVector.basicUnsafeFreeze v
+  basicUnsafeThaw (V_IPv4 v) = MV_IPv4 `liftM` GVector.basicUnsafeThaw v
+  basicLength (V_IPv4 v) = GVector.basicLength v
+  basicUnsafeSlice i n (V_IPv4 v) = V_IPv4 $ GVector.basicUnsafeSlice i n v
+  basicUnsafeIndexM (V_IPv4 v) i = GVector.basicUnsafeIndexM v i
+  basicUnsafeCopy (MV_IPv4 mv) (V_IPv4 v) = GVector.basicUnsafeCopy mv v
+  elemseq _ = seq
+
+instance ToJSON IPv4 where
+  toJSON = Aeson.String . encode
+
+instance FromJSON IPv4 where
+  parseJSON = Aeson.withText "IPv4" aesonParser
+
+instance ToJSONKey IPv4 where
+  toJSONKey = ToJSONKeyText
+    encode
+    (\ip -> Aeson.unsafeToEncoding $ BB.char7 '"' <> builderUtf8 ip <> BB.char7 '"')
+
+#if MIN_VERSION_aeson(1,0,0) 
+instance FromJSONKey IPv4 where
+  fromJSONKey = FromJSONKeyTextParser aesonParser
+#endif
+
+aesonParser :: Text -> Aeson.Parser IPv4
+aesonParser t = case decode t of
+  Nothing -> fail "Could not parse IPv4 address"
+  Just ip -> return ip
+
+
+------------------------------------
+-- Internal functions, not exported
+------------------------------------
+
+decodeIPv4TextMaybe :: Text -> Maybe IPv4
+decodeIPv4TextMaybe t = case decodeIPv4TextReader t of
+  Left _ -> Nothing
+  Right (w,t') -> if Text.null t'
+    then Just w
+    else Nothing
+
+decodeIPv4TextReader :: TextRead.Reader IPv4
+decodeIPv4TextReader t1' = do
+  (a,t2) <- TextRead.decimal t1'
+  t2' <- stripDecimal t2
+  (b,t3) <- TextRead.decimal t2'
+  t3' <- stripDecimal t3
+  (c,t4) <- TextRead.decimal t3'
+  t4' <- stripDecimal t4
+  (d,t5) <- TextRead.decimal t4'
+  if a > 255 || b > 255 || c > 255 || d > 255
+    then Left ipOctetSizeErrorMsg
+    else Right (fromOctets' a b c d,t5)
+
+stripDecimal :: Text -> Either String Text
+stripDecimal t = case Text.uncons t of
+  Nothing -> Left "expected a dot but input ended instead"
+  Just (c,tnext) -> if c == '.'
+    then Right tnext
+    else Left "expected a dot but found a different character"
+
+-- | This is sort of a misnomer. It takes Word to make
+--   dotDecimalParser perform better. This is mostly
+--   for internal use. The arguments must all fit
+--   in a Word8.
+fromOctets' :: Word -> Word -> Word -> Word -> IPv4
+fromOctets' a b c d = IPv4 $ fromIntegral
+    ( shiftL a 24
+  .|. shiftL b 16
+  .|. shiftL c 8
+  .|. d
+    )
+
+p24 :: Word32
+p24 = getIPv4 (fromOctets' 10 0 0 0)
+
+p20 :: Word32
+p20 = getIPv4 (fromOctets' 172 16 0 0)
+
+p16 :: Word32
+p16 = getIPv4 (fromOctets' 192 168 0 0)
+
+-- | This does not do an endOfInput check because it is
+-- reused in the range parser implementation.
+dotDecimalParser :: AT.Parser IPv4
+dotDecimalParser = fromOctets'
+  <$> (AT.decimal >>= limitSize)
+  <*  AT.char '.'
+  <*> (AT.decimal >>= limitSize)
+  <*  AT.char '.'
+  <*> (AT.decimal >>= limitSize)
+  <*  AT.char '.'
+  <*> (AT.decimal >>= limitSize)
+  where
+  limitSize i =
+    if i > 255
+      then fail ipOctetSizeErrorMsg
+      else return i
+
+ipOctetSizeErrorMsg :: String
+ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"
+
+toDotDecimalText :: IPv4 -> Text
+toDotDecimalText = toTextPreAllocated
+
+toDotDecimalBuilder :: IPv4 -> TBuilder.Builder
+toDotDecimalBuilder = TBuilder.fromText . toTextPreAllocated
+
+-- | I think that this function can be improved. Right now, it
+--   always allocates enough space for a fifteen-character text
+--   rendering of an IP address. I think that it should be possible
+--   to do more of the math upfront and allocate less space.
+toTextPreAllocated :: IPv4 -> Text
+toTextPreAllocated (IPv4 w) =
+  let w1 = 255 .&. unsafeShiftR (fromIntegral w) 24
+      w2 = 255 .&. unsafeShiftR (fromIntegral w) 16
+      w3 = 255 .&. unsafeShiftR (fromIntegral w) 8
+      w4 = 255 .&. fromIntegral w
+   in toTextPreallocatedPartTwo w1 w2 w3 w4
+
+toTextPreallocatedPartTwo :: Word -> Word -> Word -> Word -> Text
+toTextPreallocatedPartTwo !w1 !w2 !w3 !w4 =
+#ifdef ghcjs_HOST_OS
+  let dotStr = "."
+   in Text.pack $ concat
+        [ show w1
+        , "."
+        , show w2
+        , "."
+        , show w3
+        , "."
+        , show w4
+        ]
+#else
+  let dot = 46
+      (arr,len) = runST $ do
+        marr <- TArray.new 15
+        i1 <- putAndCount 0 w1 marr
+        let n1 = i1
+            n1' = i1 + 1
+        TArray.unsafeWrite marr n1 dot
+        i2 <- putAndCount n1' w2 marr
+        let n2 = i2 + n1'
+            n2' = n2 + 1
+        TArray.unsafeWrite marr n2 dot
+        i3 <- putAndCount n2' w3 marr
+        let n3 = i3 + n2'
+            n3' = n3 + 1
+        TArray.unsafeWrite marr n3 dot
+        i4 <- putAndCount n3' w4 marr
+        theArr <- TArray.unsafeFreeze marr
+        return (theArr,i4 + n3')
+   in Text arr 0 len
+#endif
+
+twoDigits :: ByteString
+twoDigits = foldMap (BC8.pack . printf "%02d") $ enumFromTo (0 :: Int) 99
+{-# NOINLINE twoDigits #-}
+
+threeDigits :: ByteString
+threeDigits = foldMap (BC8.pack . printf "%03d") $ enumFromTo (0 :: Int) 999
+{-# NOINLINE threeDigits #-}
+
+i2w :: Integral a => a -> Word16
+i2w v = zero + fromIntegral v
+
+zero :: Word16
+zero = 48
+
+putAndCount :: Int -> Word -> TArray.MArray s -> ST s Int
+putAndCount pos w marr
+  | w < 10 = TArray.unsafeWrite marr pos (i2w w) >> return 1
+  | w < 100 = write2 pos w >> return 2
+  | otherwise = write3 pos w >> return 3
+  where
+  write2 off i0 = do
+    let i = fromIntegral i0; j = i + i
+    TArray.unsafeWrite marr off $ get2 j
+    TArray.unsafeWrite marr (off + 1) $ get2 (j + 1)
+  write3 off i0 = do
+    let i = fromIntegral i0; j = i + i + i
+    TArray.unsafeWrite marr off $ get3 j
+    TArray.unsafeWrite marr (off + 1) $ get3 (j + 1)
+    TArray.unsafeWrite marr (off + 2) $ get3 (j + 2)
+  get2 = fromIntegral . ByteString.unsafeIndex twoDigits
+  get3 = fromIntegral . ByteString.unsafeIndex threeDigits
+
+rightToMaybe :: Either a b -> Maybe b
+rightToMaybe = either (const Nothing) Just
 
diff --git a/src/Net/IPv4/ByteString/Char8.hs b/src/Net/IPv4/ByteString/Char8.hs
deleted file mode 100644
--- a/src/Net/IPv4/ByteString/Char8.hs
+++ /dev/null
@@ -1,120 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-module Net.IPv4.ByteString.Char8
-  ( encode
-  , decode
-  , builder
-  , parser
-  ) where
-
-import Net.Types (IPv4(..))
-import Net.IPv4
-import Control.Monad
-import Data.Coerce (coerce)
-import qualified Net.Internal as Internal
-import qualified Net.IPv4.Text as IPv4Text
-import qualified Data.Attoparsec.ByteString.Char8 as AB
-import qualified Data.ByteString.Builder as Builder
-import Net.Internal (rightToMaybe)
-import Data.Text.Encoding (encodeUtf8, decodeUtf8')
-import Data.ByteString (ByteString,replicate)
-import Data.Attoparsec.ByteString.Char8 (Parser)
-import Data.ByteString.Builder (Builder)
-import Data.ByteString.Char8 as BC8
-
-import Data.ByteString.Internal as I
-import Data.Bits
-import Foreign.Ptr
-import Foreign.Storable
-import Data.Word
-import Data.ByteString.Unsafe as BSU
-import Data.Monoid
-
--- | This should be rewritten to not create 'Text' as an
---   intermediate step.
-encode :: IPv4 -> ByteString
-encode = toBSPreAllocated
-
-toBSPreAllocated :: IPv4 -> ByteString
-toBSPreAllocated (IPv4 !w) = I.unsafeCreateUptoN 15 (\ptr1 ->
-  do len1 <- writeWord ptr1 w1
-     let ptr2 = ptr1 `plusPtr` len1
-     poke ptr2 dot
-     len2 <- writeWord (ptr2 `plusPtr` 1) w2
-     let ptr3 = ptr2 `plusPtr` len2 `plusPtr` 1
-     poke ptr3 dot
-     len3 <- writeWord (ptr3 `plusPtr` 1) w3
-     let ptr4 = ptr3 `plusPtr` len3 `plusPtr` 1
-     poke ptr4 dot
-     len4 <- writeWord (ptr4 `plusPtr` 1) w4
-     return (3 + len1 + len2 + len3 + len4))
-  where w1 = fromIntegral $ shiftR w 24
-        w2 = fromIntegral $ shiftR w 16
-        w3 = fromIntegral $ shiftR w 8
-        w4 = fromIntegral w
-        dot = 46 :: Word8
-        writeWord :: Ptr Word8 -> Word8 -> IO Int
-        writeWord !ptr !word
-          | word >= 100 = do
-              let int = fromIntegral word
-                  indx = int + int + int
-                  get3 = fromIntegral . BSU.unsafeIndex threeDigits
-              poke ptr (get3 indx)
-              poke (ptr `plusPtr` 1) (get3 (indx + 1))
-              poke (ptr `plusPtr` 2) (get3 (indx + 2))
-              return 3
-          | word >= 10 = do
-              let int = fromIntegral word
-                  indx = int + int
-                  get2 = fromIntegral . BSU.unsafeIndex twoDigits
-              poke ptr (get2 indx)
-              poke (ptr `plusPtr` 1) (get2 (indx + 1))
-              return 2
-          | otherwise = do
-              poke ptr (word + 48)
-              return 1
-
-twoDigits :: ByteString
-twoDigits = BC8.pack
-  "0001020304050607080910111213141516171819\
-  \2021222324252627282930313233343536373839\
-  \4041424344454647484950515253545556575859\
-  \6061626364656667686970717273747576777879\
-  \8081828384858687888990919293949596979899"
-
-threeDigits :: ByteString
-threeDigits =
-  Data.ByteString.replicate 300 0 <> BC8.pack
-  "100101102103104105106107108109110111112\
-  \113114115116117118119120121122123124125\
-  \126127128129130131132133134135136137138\
-  \139140141142143144145146147148149150151\
-  \152153154155156157158159160161162163164\
-  \165166167168169170171172173174175176177\
-  \178179180181182183184185186187188189190\
-  \191192193194195196197198199200201202203\
-  \204205206207208209210211212213214215216\
-  \217218219220221222223224225226227228229\
-  \230231232233234235236237238239240241242\
-  \243244245246247248249250251252253254255"
-
--- | This should also be rewritten
-decode :: ByteString -> Maybe IPv4
-decode = IPv4Text.decode <=< rightToMaybe . decodeUtf8'
-
-builder :: IPv4 -> Builder
-builder = Builder.byteString . encode
-
-parser :: Parser IPv4
-parser = coerce $ Internal.fromOctets'
-  <$> (AB.decimal >>= limitSize)
-  <*  AB.char '.'
-  <*> (AB.decimal >>= limitSize)
-  <*  AB.char '.'
-  <*> (AB.decimal >>= limitSize)
-  <*  AB.char '.'
-  <*> (AB.decimal >>= limitSize)
-  where
-  limitSize i =
-    if i > 255
-      then fail "All octets in an ip address must be between 0 and 255"
-      else return i
diff --git a/src/Net/IPv4/Range.hs b/src/Net/IPv4/Range.hs
--- a/src/Net/IPv4/Range.hs
+++ b/src/Net/IPv4/Range.hs
@@ -1,3 +1,11 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE TypeFamilies #-}
+
+{-# OPTIONS_GHC -Wall #-}
 module Net.IPv4.Range
   ( -- * Range functions
     normalize
@@ -12,24 +20,45 @@
   , private24
   , private20
   , private16
-    -- * Internal Functions
-    -- $internal
-  , prRange
+    -- * Textual Conversion
+    -- ** Text
+  , encode
+  , decode
+  , builder
+  , parser
+  , print
+    -- * Types
+  , IPv4Range(..)
   ) where
 
-import Net.Types (IPv4(..),IPv4Range(..))
-import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement)
-import Data.Coerce (coerce)
+import Prelude hiding (print)
+import Net.IPv4 (IPv4(..))
+import Data.Bits ((.&.),(.|.),shiftR,complement,shift)
 import Control.Monad
-import qualified Net.Internal as Internal
-import qualified Net.IPv4     as IPv4
+import Data.Text (Text)
+import Data.Word (Word8,Word32,Word64)
+import Data.Hashable (Hashable)
+import Data.Aeson (FromJSON(..),ToJSON(..))
+import GHC.Generics (Generic)
+import Data.Monoid ((<>))
+import qualified Net.IPv4 as IPv4
 import qualified Data.Text.IO as Text
+import qualified Data.Attoparsec.Text as AT
+import qualified Data.Text.Lazy.Builder as TBuilder
+import qualified Data.Text.Lazy.Builder.Int as TBI
+import qualified Data.Vector.Generic as GVector
+import qualified Data.Vector.Generic.Mutable as MGVector
+import qualified Data.Vector.Unboxed.Mutable as MUVector
+import qualified Data.Vector.Unboxed as UVector
+import qualified Data.Aeson as Aeson
+import qualified Data.Text.Lazy as LText
 
 -- $setup
 --
 -- These are here to get doctest's property checking to work.
 --
--- >>> import qualified Net.IPv4.Text as I
+-- >>> import qualified Prelude as P
+-- >>> import qualified Net.IPv4 as I
 -- >>> import Net.IPv4 (fromOctets)
 -- >>> import Test.QuickCheck (Arbitrary(..))
 -- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }
@@ -50,7 +79,7 @@
 -- For example, you might test elements in a list for membership like this:
 --
 -- >>> let r = IPv4Range (fromOctets 10 10 10 6) 31
--- >>> mapM_ (print . contains r) (take 5 $ iterate succ $ fromOctets 10 10 10 5)
+-- >>> mapM_ (P.print . contains r) (take 5 $ iterate succ $ fromOctets 10 10 10 5)
 -- False
 -- True
 -- True
@@ -62,10 +91,13 @@
 -- They are reused as the list is iterated.
 contains :: IPv4Range -> IPv4 -> Bool
 contains (IPv4Range (IPv4 wsubnet) len) =
-  let theMask = Internal.mask len
+  let theMask = mask len
       wsubnetNormalized = wsubnet .&. theMask
    in \(IPv4 w) -> (w .&. theMask) == wsubnetNormalized
 
+mask :: Word8 -> Word32
+mask = complement . shiftR 0xffffffff . fromIntegral
+
 -- | This is provided to mirror the interface provided by @Data.Set@. It
 -- behaves just like 'contains' but with flipped arguments.
 --
@@ -85,7 +117,7 @@
 -- prop> lowerInclusive r == ipv4RangeBase (normalize r)
 lowerInclusive :: IPv4Range -> IPv4
 lowerInclusive (IPv4Range (IPv4 w) len) =
-  IPv4 (w .&. Internal.mask len)
+  IPv4 (w .&. mask len)
 
 upperInclusive :: IPv4Range -> IPv4
 upperInclusive (IPv4Range (IPv4 w) len) =
@@ -93,6 +125,27 @@
       theMask = complement theInvertedMask
    in IPv4 ((w .&. theMask) .|. theInvertedMask)
 
+-- Given the size of the mask, return the total number of ips in the subnet. This
+-- only works for IPv4 addresses because an IPv6 subnet can have up to 2^128
+-- addresses. Not exported.
+countAddrs :: Word8 -> Word64
+countAddrs w =
+  let amountToShift = if w > 32
+        then 0
+        else 32 - fromIntegral w
+   in shift 1 amountToShift
+
+wordSuccessors :: Word64 -> IPv4 -> [IPv4]
+wordSuccessors !w (IPv4 !a) = if w > 0
+  then IPv4 a : wordSuccessors (w - 1) (IPv4 (a + 1))
+  else []
+
+wordSuccessorsM :: MonadPlus m => Word64 -> IPv4 -> m IPv4
+wordSuccessorsM = go where
+  go !w (IPv4 !a) = if w > 0
+    then mplus (return (IPv4 a)) (go (w - 1) (IPv4 (a + 1)))
+    else mzero
+
 -- | Convert an 'IPv4Range' into a list of the 'IPv4' addresses that
 --   are in it.
 -- >>> let r = IPv4Range (fromOctets 192 168 1 8) 30
@@ -103,34 +156,34 @@
 -- 192.168.1.11
 
 toList :: IPv4Range -> [IPv4]
-toList (IPv4Range (IPv4 ip) len) = 
-  let totalAddrs = Internal.countAddrs len
-   in coerce (Internal.wordSuccessors totalAddrs ip)
+toList (IPv4Range ip len) = 
+  let totalAddrs = countAddrs len
+   in wordSuccessors totalAddrs ip
 
 toGenerator :: MonadPlus m => IPv4Range -> m IPv4
-toGenerator (IPv4Range (IPv4 ip) len) =  
-  let totalAddrs = Internal.countAddrs len
-   in Internal.wordSuccessorsM IPv4 totalAddrs ip
+toGenerator (IPv4Range ip len) =  
+  let totalAddrs = countAddrs len
+   in wordSuccessorsM totalAddrs ip
 
 -- | The RFC1918 24-bit block. Subnet mask: @10.0.0.0/8@
 private24 :: IPv4Range
-private24 = IPv4Range (IPv4 Internal.p24) 8
+private24 = IPv4Range (IPv4.fromOctets 10 0 0 0) 8
 
 -- | The RFC1918 20-bit block. Subnet mask: @172.16.0.0/12@
 private20 :: IPv4Range
-private20  = IPv4Range (IPv4 Internal.p20) 12
+private20  = IPv4Range (IPv4.fromOctets 172 16 0 0) 12
 
 -- | The RFC1918 16-bit block. Subnet mask: @192.168.0.0/16@
 private16 :: IPv4Range
-private16 = IPv4Range (IPv4 Internal.p16) 16
+private16 = IPv4Range (IPv4.fromOctets 192 168 0 0) 16
 
 -- | Normalize an 'IPv4Range'. The first result of this is that the
 -- 'IPv4' inside the 'IPv4Range' is changed so that the insignificant
 -- bits are zeroed out. For example:
 --
--- >>> prRange $ normalize $ IPv4Range (fromOctets 192 168 1 19) 24
+-- >>> print $ normalize $ IPv4Range (fromOctets 192 168 1 19) 24
 -- 192.168.1.0/24
--- >>> prRange $ normalize $ IPv4Range (fromOctets 192 168 1 163) 28
+-- >>> print $ normalize $ IPv4Range (fromOctets 192 168 1 163) 28
 -- 192.168.1.160/28
 --
 -- The second effect of this is that the mask length is lowered to
@@ -145,12 +198,177 @@
 normalize :: IPv4Range -> IPv4Range
 normalize (IPv4Range (IPv4 w) len) =
   let len' = min len 32
-      w' = w .&. Internal.mask len'
+      w' = w .&. mask len'
    in IPv4Range (IPv4 w') len'
 
+encode :: IPv4Range -> Text
+encode = rangeToDotDecimalText
 
--- | This only exists for doctests. Do not use it.
-prRange :: IPv4Range -> IO ()
-prRange (IPv4Range (IPv4 addr) range) =
-  Text.putStrLn (Internal.rangeToDotDecimalText addr range)
+decode :: Text -> Maybe IPv4Range
+decode = rightToMaybe . AT.parseOnly (parser <* AT.endOfInput)
+
+builder :: IPv4Range -> TBuilder.Builder
+builder = rangeToDotDecimalBuilder
+
+parser :: AT.Parser IPv4Range
+parser = do
+  ip <- IPv4.parser
+  _ <- AT.char '/'
+  theMask <- AT.decimal >>= limitSize
+  return (normalize (IPv4Range ip theMask))
+  where
+  limitSize i =
+    if i > 32
+      then fail "An IP range length must be between 0 and 32"
+      else return i
+
+-- | This exists mostly for testing purposes.
+print :: IPv4Range -> IO ()
+print = Text.putStrLn . encode
+
+rightToMaybe :: Either a b -> Maybe b
+rightToMaybe = either (const Nothing) Just
+
+-- | The length should be between 0 and 32. These bounds are inclusive.
+--   This expectation is not in any way enforced by this library because
+--   it does not cause errors. A mask length greater than 32 will be
+--   treated as if it were 32.
+data IPv4Range = IPv4Range
+  { ipv4RangeBase   :: {-# UNPACK #-} !IPv4
+  , ipv4RangeLength :: {-# UNPACK #-} !Word8
+  } deriving (Eq,Ord,Show,Read,Generic)
+
+
+instance Hashable IPv4Range
+
+instance ToJSON IPv4Range where
+  toJSON = Aeson.String . encode
+
+instance FromJSON IPv4Range where
+  parseJSON (Aeson.String t) = case decode t of
+    Nothing -> fail "Could not decode IPv4 range"
+    Just res -> return res
+  parseJSON _ = mzero
+
+data instance MUVector.MVector s IPv4Range = MV_IPv4Range
+  !(MUVector.MVector s IPv4)
+  !(MUVector.MVector s Word8)
+data instance UVector.Vector IPv4Range = V_IPv4Range
+  !(UVector.Vector IPv4)
+  !(UVector.Vector Word8)
+
+instance UVector.Unbox IPv4Range
+instance MGVector.MVector MUVector.MVector IPv4Range where
+  {-# INLINE basicLength  #-}
+  basicLength (MV_IPv4Range as _) = MGVector.basicLength as
+  {-# INLINE basicUnsafeSlice  #-}
+  basicUnsafeSlice i_ m_ (MV_IPv4Range as bs)
+      = MV_IPv4Range (MGVector.basicUnsafeSlice i_ m_ as)
+                     (MGVector.basicUnsafeSlice i_ m_ bs)
+  {-# INLINE basicOverlaps  #-}
+  basicOverlaps (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2)
+      = MGVector.basicOverlaps as1 as2
+        || MGVector.basicOverlaps bs1 bs2
+  {-# INLINE basicUnsafeNew  #-}
+  basicUnsafeNew n_
+      = do
+          as <- MGVector.basicUnsafeNew n_
+          bs <- MGVector.basicUnsafeNew n_
+          return $ MV_IPv4Range as bs
+  {-# INLINE basicInitialize  #-}
+  basicInitialize (MV_IPv4Range as bs)
+      = do
+          MGVector.basicInitialize as
+          MGVector.basicInitialize bs
+  {-# INLINE basicUnsafeReplicate  #-}
+  basicUnsafeReplicate n_ (IPv4Range a b)
+      = do
+          as <- MGVector.basicUnsafeReplicate n_ a
+          bs <- MGVector.basicUnsafeReplicate n_ b
+          return (MV_IPv4Range as bs)
+  {-# INLINE basicUnsafeRead  #-}
+  basicUnsafeRead (MV_IPv4Range as bs) i_
+      = do
+          a <- MGVector.basicUnsafeRead as i_
+          b <- MGVector.basicUnsafeRead bs i_
+          return (IPv4Range a b)
+  {-# INLINE basicUnsafeWrite  #-}
+  basicUnsafeWrite (MV_IPv4Range as bs) i_ (IPv4Range a b)
+      = do
+          MGVector.basicUnsafeWrite as i_ a
+          MGVector.basicUnsafeWrite bs i_ b
+  {-# INLINE basicClear  #-}
+  basicClear (MV_IPv4Range as bs)
+      = do
+          MGVector.basicClear as
+          MGVector.basicClear bs
+  {-# INLINE basicSet  #-}
+  basicSet (MV_IPv4Range as bs) (IPv4Range a b)
+      = do
+          MGVector.basicSet as a
+          MGVector.basicSet bs b
+  {-# INLINE basicUnsafeCopy  #-}
+  basicUnsafeCopy (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2)
+      = do
+          MGVector.basicUnsafeCopy as1 as2
+          MGVector.basicUnsafeCopy bs1 bs2
+  {-# INLINE basicUnsafeMove  #-}
+  basicUnsafeMove (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2)
+      = do
+          MGVector.basicUnsafeMove as1 as2
+          MGVector.basicUnsafeMove bs1 bs2
+  {-# INLINE basicUnsafeGrow  #-}
+  basicUnsafeGrow (MV_IPv4Range as bs) m_
+      = do
+          as' <- MGVector.basicUnsafeGrow as m_
+          bs' <- MGVector.basicUnsafeGrow bs m_
+          return $ MV_IPv4Range as' bs'
+
+instance GVector.Vector UVector.Vector IPv4Range where
+  {-# INLINE basicUnsafeFreeze  #-}
+  basicUnsafeFreeze (MV_IPv4Range as bs)
+      = do
+          as' <- GVector.basicUnsafeFreeze as
+          bs' <- GVector.basicUnsafeFreeze bs
+          return $ V_IPv4Range as' bs'
+  {-# INLINE basicUnsafeThaw  #-}
+  basicUnsafeThaw (V_IPv4Range as bs)
+      = do
+          as' <- GVector.basicUnsafeThaw as
+          bs' <- GVector.basicUnsafeThaw bs
+          return $ MV_IPv4Range as' bs'
+  {-# INLINE basicLength  #-}
+  basicLength (V_IPv4Range as _) = GVector.basicLength as
+  {-# INLINE basicUnsafeSlice  #-}
+  basicUnsafeSlice i_ m_ (V_IPv4Range as bs)
+      = V_IPv4Range (GVector.basicUnsafeSlice i_ m_ as)
+                    (GVector.basicUnsafeSlice i_ m_ bs)
+  {-# INLINE basicUnsafeIndexM  #-}
+  basicUnsafeIndexM (V_IPv4Range as bs) i_
+      = do
+          a <- GVector.basicUnsafeIndexM as i_
+          b <- GVector.basicUnsafeIndexM bs i_
+          return (IPv4Range a b)
+  {-# INLINE basicUnsafeCopy  #-}
+  basicUnsafeCopy (MV_IPv4Range as1 bs1) (V_IPv4Range as2 bs2)
+      = do
+          GVector.basicUnsafeCopy as1 as2
+          GVector.basicUnsafeCopy bs1 bs2
+  {-# INLINE elemseq  #-}
+  elemseq _ (IPv4Range a b)
+      = GVector.elemseq (undefined :: UVector.Vector a) a
+        . GVector.elemseq (undefined :: UVector.Vector b) b
+
+-----------------
+-- Internal Stuff
+-----------------
+
+rangeToDotDecimalText :: IPv4Range -> Text
+rangeToDotDecimalText = LText.toStrict . TBuilder.toLazyText . rangeToDotDecimalBuilder
+
+rangeToDotDecimalBuilder :: IPv4Range -> TBuilder.Builder
+rangeToDotDecimalBuilder (IPv4Range addr len) =
+     IPv4.builder addr
+  <> TBuilder.singleton '/'
+  <> TBI.decimal len
 
diff --git a/src/Net/IPv4/Range/Text.hs b/src/Net/IPv4/Range/Text.hs
deleted file mode 100644
--- a/src/Net/IPv4/Range/Text.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-module Net.IPv4.Range.Text
-  ( encode
-  , decode
-  , decodeEither
-  , builder
-  , parser
-  , print
-  ) where
-
-import Prelude hiding (print)
-import Net.Types (IPv4Range(..),IPv4(..))
-import Data.Text (Text)
-import Data.Word (Word8, Word32)
-import qualified Data.Text.IO           as Text
-import qualified Data.Attoparsec.Text   as AT
-import qualified Data.Text.Lazy.Builder as TBuilder
-import qualified Net.Internal           as Internal
-
-encode :: IPv4Range -> Text
-encode (IPv4Range (IPv4 w) r) = Internal.rangeToDotDecimalText w r
-
-decodeEither :: Text -> Either String IPv4Range
-decodeEither = Internal.rangeFromDotDecimalText' mkIPv4Range
-
-decode :: Text -> Maybe IPv4Range
-decode = Internal.rightToMaybe . decodeEither
-
-builder :: IPv4Range -> TBuilder.Builder
-builder (IPv4Range (IPv4 w) r) = Internal.rangeToDotDecimalBuilder w r
-
-parser :: AT.Parser IPv4Range
-parser = Internal.dotDecimalRangeParser mkIPv4Range
-
--- | This exists mostly for testing purposes.
-print :: IPv4Range -> IO ()
-print = Text.putStrLn . encode
-
--- internal function
-mkIPv4Range :: Word32 -> Word8 -> IPv4Range
-mkIPv4Range w = IPv4Range (IPv4 w)
-{-# INLINE mkIPv4Range #-}
-
diff --git a/src/Net/IPv4/String.hs b/src/Net/IPv4/String.hs
deleted file mode 100644
--- a/src/Net/IPv4/String.hs
+++ /dev/null
@@ -1,25 +0,0 @@
--- | This module exists for the convenience of those who need a
--- 'String' representation of an 'IPv4' address. Using this module
--- is discouraged unless the end user is working with a library
--- that can only use 'String' to deal with textual data (such as
--- @pandoc@, @hxr@, or @network@).
---
-module Net.IPv4.String
-  ( encode
-  , decode
-  , decodeEither
-  ) where
-
-import Net.Types (IPv4(..))
-import qualified Data.Text as Text
-import qualified Net.IPv4.Text as N
-
-encode :: IPv4 -> String
-encode = Text.unpack . N.encode
-
-decode :: String -> Maybe IPv4
-decode = N.decode . Text.pack
-
-decodeEither :: String -> Either String IPv4
-decodeEither = N.decodeEither . Text.pack
-
diff --git a/src/Net/IPv4/Text.hs b/src/Net/IPv4/Text.hs
deleted file mode 100644
--- a/src/Net/IPv4/Text.hs
+++ /dev/null
@@ -1,43 +0,0 @@
-module Net.IPv4.Text
-  ( encode
-  , decode
-  , decodeEither
-  , builder
-  , reader
-  , parser
-  , print
-  ) where
-
-import Prelude hiding (print)
-import Net.Types (IPv4(..))
-import Net.IPv4
-import Data.Text (Text)
-import Data.Coerce (coerce)
-import qualified Data.Text.Read         as Text (Reader)
-import qualified Data.Text.IO           as Text
-import qualified Data.Attoparsec.Text   as AT
-import qualified Data.Text.Lazy.Builder as TBuilder
-import qualified Net.Internal           as Internal
-
-encode :: IPv4 -> Text
-encode = Internal.toDotDecimalText . getIPv4
-
-decodeEither :: Text -> Either String IPv4
-decodeEither = coerce . Internal.decodeIPv4TextEither
-
-decode :: Text -> Maybe IPv4
-decode = Internal.rightToMaybe . decodeEither
-
-builder :: IPv4 -> TBuilder.Builder
-builder = Internal.toDotDecimalBuilder . getIPv4
-
-reader :: Text.Reader IPv4
-reader = coerce Internal.decodeIPv4TextReader
-
-parser :: AT.Parser IPv4
-parser = coerce Internal.dotDecimalParser
-
--- | This exists mostly for testing purposes.
-print :: IPv4 -> IO ()
-print = Text.putStrLn . encode
-
diff --git a/src/Net/IPv6.hs b/src/Net/IPv6.hs
--- a/src/Net/IPv6.hs
+++ b/src/Net/IPv6.hs
@@ -1,12 +1,113 @@
 {-# LANGUAGE BangPatterns #-}
 
-module Net.IPv6 where
+{-# OPTIONS_GHC -Wall #-}
 
-import qualified Net.Internal as Internal
+module Net.IPv6
+  ( -- * Types
+    IPv6(..)
+    -- * Convert
+  , fromOctets
+  , fromWord16s
+  , toWord16s
+    -- * Special IP Addresses
+  , any
+  , loopback
+    -- * Textual Conversion
+    -- ** Text
+  , encode
+  , decode
+  , parser
+  ) where
+
+import Prelude hiding (any)
 import Data.Bits
+import Data.List (intercalate, group)
 import Data.Word
-import Net.Types
+import Data.Char (chr)
+import Control.Applicative
+import Data.Text (Text)
+import Text.Read (Read(..),Lexeme(Ident),lexP,parens)
+import Text.ParserCombinators.ReadPrec (prec,step)
+import qualified Data.Text as Text
+import qualified Data.Attoparsec.Text as Atto
+import qualified Data.Aeson as Aeson
+import qualified Data.Attoparsec.Text as AT
+import Numeric (showHex)
 
+-- $setup
+--
+-- These are here to get doctest work.
+--
+-- >>> import qualified Prelude as P
+-- >>> import qualified Data.Text.IO as T
+--
+
+-- | A 128-bit Internet Protocol version 6 address.
+data IPv6 = IPv6
+  { ipv6A :: {-# UNPACK #-} !Word64
+  , ipv6B :: {-# UNPACK #-} !Word64
+  } deriving (Eq,Ord)
+
+instance Show IPv6 where
+  showsPrec p addr = showParen (p > 10)
+    $ showString "fromWord16s "
+    . showHexWord16 a
+    . showChar ' '
+    . showHexWord16 b
+    . showChar ' '
+    . showHexWord16 c
+    . showChar ' '
+    . showHexWord16 d
+    . showChar ' '
+    . showHexWord16 e
+    . showChar ' '
+    . showHexWord16 f
+    . showChar ' '
+    . showHexWord16 g
+    . showChar ' '
+    . showHexWord16 h
+    . showChar ' '
+    where
+    (a,b,c,d,e,f,g,h) = toWord16s addr
+
+showHexWord16 :: Word16 -> ShowS
+showHexWord16 w =
+    showString "0x"
+  . showChar (nibbleToHex (unsafeShiftR (fromIntegral w) 12))
+  . showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 8) .&. 0xF))
+  . showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 4) .&. 0xF))
+  . showChar (nibbleToHex ((fromIntegral w) .&. 0xF))
+
+-- invariant: argument must be less than 16
+nibbleToHex :: Word -> Char
+nibbleToHex w
+  | w < 10 = chr (fromIntegral (w + 48))
+  | otherwise = chr (fromIntegral (w + 87))
+
+instance Read IPv6 where
+  readPrec = parens $ prec 10 $ do
+    Ident "fromWord16s" <- lexP
+    a <- step readPrec
+    b <- step readPrec
+    c <- step readPrec
+    d <- step readPrec
+    e <- step readPrec
+    f <- step readPrec
+    g <- step readPrec
+    h <- step readPrec
+    return (fromWord16s a b c d e f g h)
+
+instance Aeson.ToJSON IPv6 where
+  toJSON = Aeson.String . encode
+
+instance Aeson.FromJSON IPv6 where
+  parseJSON = Aeson.withText "IPv6" $ \t -> case decode t of
+    Nothing -> fail "invalid IPv6 address"
+    Just i -> return i
+        
+rightToMaybe :: Either a b -> Maybe b
+rightToMaybe = either (const Nothing) Just
+
 fromOctets ::
      Word8 -> Word8 -> Word8 -> Word8
   -> Word8 -> Word8 -> Word8 -> Word8
@@ -14,19 +115,30 @@
   -> Word8 -> Word8 -> Word8 -> Word8
   -> IPv6
 fromOctets a b c d e f g h i j k l m n o p =
-  let !(w1,w2) = Internal.fromOctetsV6
+  let !(w1,w2) = fromOctetsV6
         (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)
         (fromIntegral e) (fromIntegral f) (fromIntegral g) (fromIntegral h)
         (fromIntegral i) (fromIntegral j) (fromIntegral k) (fromIntegral l)
         (fromIntegral m) (fromIntegral n) (fromIntegral o) (fromIntegral p)
    in IPv6 w1 w2
 
+-- | Create an 'IPv6' address from the eight 16-bit fragments that make
+--   it up. This closely resembles the standard IPv6 notation, so
+--   is used for the 'Show' instance. Note that this lacks the formatting
+--   feature for suppress zeroes in an 'IPv6' address, but it should be
+--   readable enough for hacking in GHCi.
+--
+--   >>> let ip = fromWord16s 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1
+--   >>> ip
+--   fromWord16s 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001
+--   >>> T.putStrLn (encode ip)
+--   3124::dead:cafe:ff:fe00:1
 fromWord16s ::
      Word16 -> Word16 -> Word16 -> Word16
   -> Word16 -> Word16 -> Word16 -> Word16
   -> IPv6
 fromWord16s a b c d e f g h =
-  let !(w1,w2) = Internal.fromWord16sV6
+  let !(w1,w2) = fromWord16sV6
         (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)
         (fromIntegral e) (fromIntegral f) (fromIntegral g) (fromIntegral h)
    in IPv6 w1 w2
@@ -42,3 +154,176 @@
   , fromIntegral (unsafeShiftR b 16)
   , fromIntegral b
   )
+
+loopback :: IPv6
+loopback = IPv6 0 1
+
+any :: IPv6
+any = IPv6 0 0
+
+-- | Encodes the IP, using zero-compression on the leftmost-longest string of
+-- zeroes in the address.
+encode :: IPv6 -> Text
+encode ip = toText [w1, w2, w3, w4, w5, w6, w7, w8]
+  where
+  (w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip
+  toText ws = Text.pack $ intercalate ":" $ expand 0 longestZ grouped
+    where
+    expand _ 8 _ = ["::"]
+    expand _ _ [] = []
+    expand i longest ((x, len):wsNext)
+        -- zero-compressed group:
+        | x == 0 && len == longest =
+            -- first and last need an extra colon since there's nothing
+            -- to concat against
+            (if i == 0 || (i+len) == 8 then ":" else "")
+            : expand (i+len) 0 wsNext
+        -- normal group:
+        | otherwise = replicate len (showHex x "") ++ expand (i+len) longest wsNext
+    longestZ = maximum . (0:) . map snd . filter ((==0) . fst) $ grouped
+    grouped = map (\x -> (head x, length x)) (group ws)
+
+decode :: Text -> Maybe IPv6
+decode t = rightToMaybe (AT.parseOnly (parser <* AT.endOfInput) t)
+
+parser :: Atto.Parser IPv6
+parser = do
+  s <- start
+  case toIPv6 s of
+    Nothing -> fail "Wrong number of octets in IPv6 address"
+    Just ip -> return ip
+  where
+  msg = "All chunks in a formatted IPv6 address must be between 0x0000 and 0xFFFF"
+  colonMsg = "Cannot use double colon for omitting zeroes more than once in an IPv6 address"
+  start = do
+    c <- Atto.peekChar'
+    if c == ':'
+      then go (-1) 0 []
+      else Atto.hexadecimal >>= \w -> go (-1) 1 [w]
+    -- r <- fmap Just Atto.hexadecimal <|> (Nothing <$ Atto.char ':')
+    -- case r of
+    --   Just !w -> go (-1) 1 [w]
+    --   Nothing -> go 0 0 []
+  go !colonIndex !currentIndex !ws = do
+    r <- do
+      m <- Atto.peekChar
+      case m of
+        Nothing -> return ResDone
+        Just c -> if c == ':'
+          then do
+            _ <- Atto.anyChar -- should be a colon
+            if colonIndex == currentIndex
+              then fmap ResWord Atto.hexadecimal <|> pure ResDone
+              else do
+                d <- Atto.peekChar'
+                if d == ':'
+                  then return ResColon
+                  else fmap ResWord Atto.hexadecimal
+          else return ResDone
+    case r of
+      ResDone -> pure (S colonIndex currentIndex ws)
+      ResColon -> if alreadySet colonIndex
+        then fail colonMsg
+        else go currentIndex currentIndex ws
+      ResWord w -> restrictTo16 msg w >> go colonIndex (currentIndex + 1) (w : ws)
+
+toIPv6 :: S -> Maybe IPv6
+toIPv6 (S colonIndex total input) = case compare total 8 of
+  EQ -> if colonIndex == (-1)
+    then go 0 0 input
+    else Nothing
+  GT -> Nothing
+  LT -> go 0 0 input
+  where
+  revColonIndex = total - colonIndex
+  spacesToSkip = 8 - total
+  go :: Int -> Word64 -> [Word64] -> Maybe IPv6
+  go !ix !acc ws = if ix > 3
+    then fmap (flip IPv6 acc) (go2 ix 0 ws)
+    else case ws of
+      w : wsNext -> if ix == revColonIndex
+        then go (ix + spacesToSkip) acc (w : wsNext)
+        else go (ix + 1) (acc .|. unsafeShiftL w (times16 ix)) wsNext
+      [] -> if ix == revColonIndex
+        then Just $ IPv6 0 acc
+        else Nothing -- Not enough word16s in list
+  go2 :: Int -> Word64 -> [Word64] -> Maybe Word64
+  go2 !ix !acc ws = case ws of
+    w : wsNext -> if ix == revColonIndex
+      then go2 (ix + spacesToSkip) acc (w : wsNext)
+      else go2 (ix + 1) (acc .|. unsafeShiftL w (times16 ix - 64)) wsNext
+    [] -> if ix == revColonIndex || ix > 7
+      then Just acc
+      else Nothing -- Not enough word16s in list
+
+times16 :: Int -> Int
+times16 a = unsafeShiftL a 4
+
+alreadySet :: Int -> Bool
+alreadySet i = i /= (-1)
+
+restrictTo16 :: String -> Word64 -> Atto.Parser ()
+restrictTo16 msg w = if w > 65535
+  then fail msg
+  else return ()
+
+-- | This is an internal data type used as the result
+--   after parsing an ipv6 address. The first field
+--   indicates the index at which a double colon occurs.
+--   The second is the length of the third.
+--   The third is a reversed list of the 16s
+--   that comprise the ipv6 address.
+data S = S
+  { _sDoubleColon :: {-# UNPACK #-} !Int
+  , _sTotal :: {-# UNPACK #-} !Int
+  , _sRevWords :: ![Word64]
+  } deriving (Show,Read)
+
+data Res
+  = ResWord {-# UNPACK #-} !Word64
+  | ResColon
+  | ResDone
+
+fromOctetsV6 ::
+     Word64 -> Word64 -> Word64 -> Word64
+  -> Word64 -> Word64 -> Word64 -> Word64
+  -> Word64 -> Word64 -> Word64 -> Word64
+  -> Word64 -> Word64 -> Word64 -> Word64
+  -> (Word64,Word64)
+fromOctetsV6 a b c d e f g h i j k l m n o p =
+  ( fromOctetsWord64 a b c d e f g h
+  , fromOctetsWord64 i j k l m n o p
+  )
+
+fromWord16sV6 ::
+     Word64 -> Word64 -> Word64 -> Word64
+  -> Word64 -> Word64 -> Word64 -> Word64
+  -> (Word64,Word64)
+fromWord16sV6 a b c d e f g h =
+  ( fromWord16Word64 a b c d
+  , fromWord16Word64 e f g h
+  )
+
+fromOctetsWord64 ::
+     Word64 -> Word64 -> Word64 -> Word64
+  -> Word64 -> Word64 -> Word64 -> Word64
+  -> Word64
+fromOctetsWord64 a b c d e f g h = fromIntegral
+    ( shiftL a 56
+  .|. shiftL b 48
+  .|. shiftL c 40
+  .|. shiftL d 32
+  .|. shiftL e 24
+  .|. shiftL f 16
+  .|. shiftL g 8
+  .|. h
+    )
+
+fromWord16Word64 :: Word64 -> Word64 -> Word64 -> Word64 -> Word64
+fromWord16Word64 a b c d = fromIntegral
+    ( unsafeShiftL a 48
+  .|. unsafeShiftL b 32
+  .|. unsafeShiftL c 16
+  .|. d
+    )
+
diff --git a/src/Net/IPv6/Text.hs b/src/Net/IPv6/Text.hs
deleted file mode 100644
--- a/src/Net/IPv6/Text.hs
+++ /dev/null
@@ -1,141 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-
-module Net.IPv6.Text
-  ( encode
-  , parser
-  ) where
-
-import Prelude hiding (print)
-import Net.Types (IPv6(..))
-import Net.IPv6 (toWord16s)
-import Data.Bits
-import Data.List (intercalate, group)
-import Data.Word
-import Control.Applicative
-import Data.Text (Text)
-import qualified Data.Text as Text
-import qualified Data.Attoparsec.Text as Atto
-import Numeric (showHex)
-
--- | Encodes the IP, using zero-compression on the leftmost-longest string of
--- zeroes in the address.
-encode :: IPv6 -> Text
-encode ip = toText [w1, w2, w3, w4, w5, w6, w7, w8]
-  where
-  (w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip
-  toText ws = Text.pack $ intercalate ":" $ expand 0 longestZ grouped
-    where
-    expand _ 8 _ = ["::"]
-    expand _ _ [] = []
-    expand i longest ((x, len):wsNext)
-        -- zero-compressed group:
-        | x == 0 && len == longest =
-            -- first and last need an extra colon since there's nothing
-            -- to concat against
-            (if i == 0 || (i+len) == 8 then ":" else "")
-            : expand (i+len) 0 wsNext
-        -- normal group:
-        | otherwise = replicate len (showHex x "") ++ expand (i+len) longest wsNext
-    longestZ = maximum . (0:) . map snd . filter ((==0) . fst) $ grouped
-    grouped = map (\x -> (head x, length x)) (group ws)
-
-        
-parser :: Atto.Parser IPv6
-parser = do
-  s <- start
-  case toIPv6 s of
-    Nothing -> fail "Wrong number of octets in IPv6 address"
-    Just ip -> return ip
-  where
-  msg = "All chunks in a formatted IPv6 address must be between 0x0000 and 0xFFFF"
-  colonMsg = "Cannot use double colon for omitting zeroes more than once in an IPv6 address"
-  start = do
-    c <- Atto.peekChar'
-    if c == ':'
-      then go (-1) 0 []
-      else Atto.hexadecimal >>= \w -> go (-1) 1 [w]
-    -- r <- fmap Just Atto.hexadecimal <|> (Nothing <$ Atto.char ':')
-    -- case r of
-    --   Just !w -> go (-1) 1 [w]
-    --   Nothing -> go 0 0 []
-  go !colonIndex !currentIndex !ws = do
-    r <- do
-      m <- Atto.peekChar
-      case m of
-        Nothing -> return ResDone
-        Just c -> if c == ':'
-          then do
-            _ <- Atto.anyChar -- should be a colon
-            if colonIndex == currentIndex
-              then fmap ResWord Atto.hexadecimal <|> pure ResDone
-              else do
-                d <- Atto.peekChar'
-                if d == ':'
-                  then return ResColon
-                  else fmap ResWord Atto.hexadecimal
-          else return ResDone
-    case r of
-      ResDone -> pure (S colonIndex currentIndex ws)
-      ResColon -> if alreadySet colonIndex
-        then fail colonMsg
-        else go currentIndex currentIndex ws
-      ResWord w -> restrictTo16 msg w >> go colonIndex (currentIndex + 1) (w : ws)
-
-toIPv6 :: S -> Maybe IPv6
-toIPv6 (S colonIndex total input) = case compare total 8 of
-  EQ -> if colonIndex == (-1)
-    then go 0 0 input
-    else Nothing
-  GT -> Nothing
-  LT -> go 0 0 input
-  where
-  revColonIndex = total - colonIndex
-  spacesToSkip = 8 - total
-  go :: Int -> Word64 -> [Word64] -> Maybe IPv6
-  go !ix !acc ws = if ix > 3
-    then fmap (flip IPv6 acc) (go2 ix 0 ws)
-    else case ws of
-      w : wsNext -> if ix == revColonIndex
-        then go (ix + spacesToSkip) acc (w : wsNext)
-        else go (ix + 1) (acc .|. unsafeShiftL w (times16 ix)) wsNext
-      [] -> if ix == revColonIndex
-        then Just $ IPv6 0 acc
-        else Nothing -- Not enough word16s in list
-  go2 :: Int -> Word64 -> [Word64] -> Maybe Word64
-  go2 !ix !acc ws = case ws of
-    w : wsNext -> if ix == revColonIndex
-      then go2 (ix + spacesToSkip) acc (w : wsNext)
-      else go2 (ix + 1) (acc .|. unsafeShiftL w (times16 ix - 64)) wsNext
-    [] -> if ix == revColonIndex || ix > 7
-      then Just acc
-      else Nothing -- Not enough word16s in list
-
-times16 :: Int -> Int
-times16 a = unsafeShiftL a 4
-
-alreadySet :: Int -> Bool
-alreadySet i = i /= (-1)
-
-restrictTo16 :: String -> Word64 -> Atto.Parser ()
-restrictTo16 msg w = if w > 65535
-  then fail msg
-  else return ()
-
--- | This is an internal data type used as the result
---   after parsing an ipv6 address. The first field
---   indicates the index at which a double colon occurs.
---   The second is the length of the third.
---   The third is a reversed list of the 16s
---   that comprise the ipv6 address.
-data S = S
-  { _sDoubleColon :: {-# UNPACK #-} !Int
-  , _sTotal :: {-# UNPACK #-} !Int
-  , _sRevWords :: ![Word64]
-  } deriving (Show,Read)
-
-data Res
-  = ResWord {-# UNPACK #-} !Word64
-  | ResColon
-  | ResDone
-
-
diff --git a/src/Net/Internal.hs b/src/Net/Internal.hs
deleted file mode 100644
--- a/src/Net/Internal.hs
+++ /dev/null
@@ -1,465 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE CPP #-}
-
-module Net.Internal where
-
-import Data.Monoid ((<>))
-import Data.Word
-import Data.Bits ((.&.),(.|.),shiftR,shiftL,shift,complement,unsafeShiftR,unsafeShiftL)
-import Control.Monad.ST
-import Data.Text.Internal (Text(..))
-import Data.ByteString (ByteString)
-import Data.Text.Lazy.Builder.Int (decimal)
-import Control.Monad
-import Text.Printf (printf)
-import Data.Char (chr,ord)
-import Data.Word.Synthetic (Word48)
-import qualified Data.Text              as Text
-import qualified Data.Text.Lazy         as LText
-import qualified Data.Attoparsec.Text   as AT
-import qualified Data.Aeson.Types       as Aeson
-import qualified Data.Text.Array        as TArray
-import qualified Data.ByteString.Char8  as BC8
-import qualified Data.ByteString        as ByteString
-import qualified Data.ByteString.Unsafe as ByteString
-import qualified Data.Text.Lazy.Builder as TBuilder
-import qualified Data.Text.Read         as TextRead
-import qualified Data.Text.Lazy.Builder.Int as TBuilder
-
--- | Taken from @Data.ByteString.Internal@. The same warnings
---   apply here.
-c2w :: Char -> Word8
-c2w = fromIntegral . ord
-{-# INLINE c2w #-}
-
-eitherToAesonParser :: Either String a -> Aeson.Parser a
-eitherToAesonParser x = case x of
-  Left err -> fail err
-  Right a -> return a
-
-attoparsecParseJSON :: AT.Parser a -> Aeson.Value -> Aeson.Parser a
-attoparsecParseJSON p v =
-  case v of
-    Aeson.String t ->
-      case AT.parseOnly p t of
-        Left err  -> fail err
-        Right res -> return res
-    _ -> fail "expected a String"
-
-stripDecimal :: Text -> Either String Text
-stripDecimal t = case Text.uncons t of
-  Nothing -> Left "expected a dot but input ended instead"
-  Just (c,tnext) -> if c == '.'
-    then Right tnext
-    else Left "expected a dot but found a different character"
-{-# INLINE stripDecimal #-}
-
-decodeIPv4TextReader :: TextRead.Reader Word32
-decodeIPv4TextReader t1' = do
-  (a,t2) <- TextRead.decimal t1'
-  t2' <- stripDecimal t2
-  (b,t3) <- TextRead.decimal t2'
-  t3' <- stripDecimal t3
-  (c,t4) <- TextRead.decimal t3'
-  t4' <- stripDecimal t4
-  (d,t5) <- TextRead.decimal t4'
-  if a > 255 || b > 255 || c > 255 || d > 255
-    then Left ipOctetSizeErrorMsg
-    else Right (fromOctets' a b c d,t5)
-{-# INLINE decodeIPv4TextReader #-}
-
-decodeIPv4TextEither :: Text -> Either String Word32
-decodeIPv4TextEither t = case decodeIPv4TextReader t of
-  Left err -> Left err
-  Right (w,t') -> if Text.null t'
-    then Right w
-    else Left "expected end of text but it continued instead"
-
-ipOctetSizeErrorMsg :: String
-ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"
-
-rightToMaybe :: Either a b -> Maybe b
-rightToMaybe = either (const Nothing) Just
-
-toDotDecimalText :: Word32 -> Text
-toDotDecimalText = toTextPreAllocated
-{-# INLINE toDotDecimalText #-}
-
-toDotDecimalBuilder :: Word32 -> TBuilder.Builder
-toDotDecimalBuilder = TBuilder.fromText . toTextPreAllocated
-{-# INLINE toDotDecimalBuilder #-}
-
-rangeToDotDecimalText :: Word32 -> Word8 -> Text
-rangeToDotDecimalText addr len =
-  LText.toStrict (TBuilder.toLazyText (rangeToDotDecimalBuilder addr len))
-
-rangeToDotDecimalBuilder :: Word32 -> Word8 -> TBuilder.Builder
-rangeToDotDecimalBuilder addr len =
-  toDotDecimalBuilder addr
-  <> TBuilder.singleton '/'
-  <> decimal len
-
--- | I think that this function can be improved. Right now, it
---   always allocates enough space for a fifteen-character text
---   rendering of an IP address. I think that it should be possible
---   to do more of the math upfront and allocate less space.
-toTextPreAllocated :: Word32 -> Text
-toTextPreAllocated w =
-  let w1 = 255 .&. unsafeShiftR (fromIntegral w) 24
-      w2 = 255 .&. unsafeShiftR (fromIntegral w) 16
-      w3 = 255 .&. unsafeShiftR (fromIntegral w) 8
-      w4 = 255 .&. fromIntegral w
-   in toTextPreallocatedPartTwo w1 w2 w3 w4
-
-toTextPreallocatedPartTwo :: Word -> Word -> Word -> Word -> Text
-toTextPreallocatedPartTwo w1 w2 w3 w4 =
-#ifdef ghcjs_HOST_OS
-  let dotStr = "."
-   in Text.pack $ concat
-        [ show w1
-        , "."
-        , show w2
-        , "."
-        , show w3
-        , "."
-        , show w4
-        ]
-#else
-  let dot = 46
-      (arr,len) = runST $ do
-        marr <- TArray.new 15
-        i1 <- putAndCount 0 w1 marr
-        let n1 = i1
-            n1' = i1 + 1
-        TArray.unsafeWrite marr n1 dot
-        i2 <- putAndCount n1' w2 marr
-        let n2 = i2 + n1'
-            n2' = n2 + 1
-        TArray.unsafeWrite marr n2 dot
-        i3 <- putAndCount n2' w3 marr
-        let n3 = i3 + n2'
-            n3' = n3 + 1
-        TArray.unsafeWrite marr n3 dot
-        i4 <- putAndCount n3' w4 marr
-        theArr <- TArray.unsafeFreeze marr
-        return (theArr,i4 + n3')
-   in Text arr 0 len
-#endif
-
-putAndCount :: Int -> Word -> TArray.MArray s -> ST s Int
-putAndCount pos w marr
-  | w < 10 = TArray.unsafeWrite marr pos (i2w w) >> return 1
-  | w < 100 = write2 pos w >> return 2
-  | otherwise = write3 pos w >> return 3
-  where
-  write2 off i0 = do
-    let i = fromIntegral i0; j = i + i
-    TArray.unsafeWrite marr off $ get2 j
-    TArray.unsafeWrite marr (off + 1) $ get2 (j + 1)
-  write3 off i0 = do
-    let i = fromIntegral i0; j = i + i + i
-    TArray.unsafeWrite marr off $ get3 j
-    TArray.unsafeWrite marr (off + 1) $ get3 (j + 1)
-    TArray.unsafeWrite marr (off + 2) $ get3 (j + 2)
-  get2 = fromIntegral . ByteString.unsafeIndex twoDigits
-  get3 = fromIntegral . ByteString.unsafeIndex threeDigitsWord8
-
-putMac :: ByteString -> Int -> Word64 -> TArray.MArray s -> ST s ()
-putMac hexPairs pos w' marr = do
-  let w = fromIntegral w'
-      i = w + w
-  TArray.unsafeWrite marr pos $ fromIntegral $ ByteString.unsafeIndex hexPairs i
-  TArray.unsafeWrite marr (pos + 1) $ fromIntegral $ ByteString.unsafeIndex hexPairs (i + 1)
-{-# INLINE putMac #-}
-
-word48AsOctets :: Word48 -> (Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> a) -> a
-word48AsOctets w f =
-  let w1 = fromIntegral $ unsafeShiftR w 40
-      w2 = fromIntegral $ unsafeShiftR w 32
-      w3 = fromIntegral $ unsafeShiftR w 24
-      w4 = fromIntegral $ unsafeShiftR w 16
-      w5 = fromIntegral $ unsafeShiftR w 8
-      w6 = fromIntegral w
-  in f w1 w2 w3 w4 w5 w6
-{-# INLINE word48AsOctets #-}
-
-macToTextDefault :: Word48 -> Text
-macToTextDefault = macToTextPreAllocated 58 False
-
-macToTextPreAllocated :: Word8 -> Bool -> Word48 -> Text
-macToTextPreAllocated separator' isUpperCase w =
-  let w1 = 255 .&. unsafeShiftR (fromIntegral w) 40
-      w2 = 255 .&. unsafeShiftR (fromIntegral w) 32
-      w3 = 255 .&. unsafeShiftR (fromIntegral w) 24
-      w4 = 255 .&. unsafeShiftR (fromIntegral w) 16
-      w5 = 255 .&. unsafeShiftR (fromIntegral w) 8
-      w6 = 255 .&. fromIntegral w
-  in macToTextPartTwo separator' isUpperCase w1 w2 w3 w4 w5 w6
-{-# INLINE macToTextPreAllocated #-}
-
-macToTextPartTwo :: Word8 -> Bool -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Text
-macToTextPartTwo separator' isUpperCase w1 w2 w3 w4 w5 w6 =
-#ifdef ghcjs_HOST_OS
-  Text.pack $ concat
-    [ toHex w1
-    , separatorStr
-    , toHex w2
-    , separatorStr
-    , toHex w3
-    , separatorStr
-    , toHex w4
-    , separatorStr
-    , toHex w5
-    , separatorStr
-    , toHex w6
-    ]
-  where
-  toHex :: Word64 -> String
-  toHex = if isUpperCase then printf "%02X" else printf "%02x"
-  separatorStr = [chr (fromEnum separator')]
-#else
-  let hexPairs = if isUpperCase then twoHexDigits else twoHexDigitsLower
-      separator = fromIntegral separator' :: Word16
-      arr = runST $ do
-        marr <- TArray.new 17
-        putMac hexPairs 0 w1 marr
-        TArray.unsafeWrite marr 2 separator
-        putMac hexPairs 3 w2 marr
-        TArray.unsafeWrite marr 5 separator
-        putMac hexPairs 6 w3 marr
-        TArray.unsafeWrite marr 8 separator
-        putMac hexPairs 9 w4 marr
-        TArray.unsafeWrite marr 11 separator
-        putMac hexPairs 12 w5 marr
-        TArray.unsafeWrite marr 14 separator
-        putMac hexPairs 15 w6 marr
-        TArray.unsafeFreeze marr
-  in Text arr 0 17
-#endif
-{-# INLINE macToTextPartTwo #-}
-
-
-zero :: Word16
-zero = 48
-{-# INLINE zero #-}
-
-i2w :: Integral a => a -> Word16
-i2w v = zero + fromIntegral v
-{-# INLINE i2w #-}
-
-
-fromDotDecimalText' :: Text -> Either String Word32
-fromDotDecimalText' t =
-  AT.parseOnly (dotDecimalParser <* AT.endOfInput) t
-
-fromDotDecimalText :: Text -> Maybe Word32
-fromDotDecimalText = rightToMaybe . fromDotDecimalText'
-
-rangeFromDotDecimalText' :: (Word32 -> Word8 -> a) -> Text -> Either String a
-rangeFromDotDecimalText' f t =
-  AT.parseOnly (dotDecimalRangeParser f <* AT.endOfInput) t
-{-# INLINE rangeFromDotDecimalText' #-}
-
-rangeFromDotDecimalText :: (Word32 -> Word8 -> a) -> Text -> Maybe a
-rangeFromDotDecimalText f = rightToMaybe . rangeFromDotDecimalText' f
-
-dotDecimalRangeParser :: (Word32 -> Word8 -> a) -> AT.Parser a
-dotDecimalRangeParser f = f
-  <$> dotDecimalParser
-  <*  AT.char '/'
-  <*> (AT.decimal >>= limitSize)
-  where
-  limitSize i =
-    if i > 32
-      then fail "An IP range length must be between 0 and 32"
-      else return i
-
--- | This does not do an endOfInput check because it is
--- reused in the range parser implementation.
-dotDecimalParser :: AT.Parser Word32
-dotDecimalParser = fromOctets'
-  <$> (AT.decimal >>= limitSize)
-  <*  AT.char '.'
-  <*> (AT.decimal >>= limitSize)
-  <*  AT.char '.'
-  <*> (AT.decimal >>= limitSize)
-  <*  AT.char '.'
-  <*> (AT.decimal >>= limitSize)
-  where
-  limitSize i =
-    if i > 255
-      then fail ipOctetSizeErrorMsg
-      else return i
-
--- | This is sort of a misnomer. It takes Word32 to make
---   dotDecimalParser probably perform better. This is mostly
---   for internal use.
---
---   At some point, it would be worth revisiting the decision
---   to use 'Word32' here. Using 'Word' would probably give
---   better performance on a 64-bit processor.
-fromOctets' :: Word32 -> Word32 -> Word32 -> Word32 -> Word32
-fromOctets' a b c d =
-    ( shiftL a 24
-  .|. shiftL b 16
-  .|. shiftL c 8
-  .|. d
-    )
-
-fromOctetsV6 ::
-     Word64 -> Word64 -> Word64 -> Word64
-  -> Word64 -> Word64 -> Word64 -> Word64
-  -> Word64 -> Word64 -> Word64 -> Word64
-  -> Word64 -> Word64 -> Word64 -> Word64
-  -> (Word64,Word64)
-fromOctetsV6 a b c d e f g h i j k l m n o p =
-  ( fromOctetsWord64 a b c d e f g h
-  , fromOctetsWord64 i j k l m n o p
-  )
-
-fromWord16sV6 ::
-     Word64 -> Word64 -> Word64 -> Word64
-  -> Word64 -> Word64 -> Word64 -> Word64
-  -> (Word64,Word64)
-fromWord16sV6 a b c d e f g h =
-  ( fromWord16Word64 a b c d
-  , fromWord16Word64 e f g h
-  )
-
-fromWord16Word64 :: Word64 -> Word64 -> Word64 -> Word64 -> Word64
-fromWord16Word64 a b c d = fromIntegral
-    ( unsafeShiftL a 48
-  .|. unsafeShiftL b 32
-  .|. unsafeShiftL c 16
-  .|. d
-    )
-
--- | All the words given as argument should be
---   range restricted from 0 to 255. This is not
---   checked.
-fromOctetsWord64 ::
-     Word64 -> Word64 -> Word64 -> Word64
-  -> Word64 -> Word64 -> Word64 -> Word64
-  -> Word64
-fromOctetsWord64 a b c d e f g h = fromIntegral
-    ( shiftL a 56
-  .|. shiftL b 48
-  .|. shiftL c 40
-  .|. shiftL d 32
-  .|. shiftL e 24
-  .|. shiftL f 16
-  .|. shiftL g 8
-  .|. h
-    )
-
--- | Given the size of the mask, return the
---   total number of ips in the subnet. This
---   only works for IPv4 addresses because
---   an IPv6 subnet can have up to 2^128
---   addresses.
-countAddrs :: Word8 -> Word64
-countAddrs w =
-  let amountToShift = if w > 32
-        then 0
-        else 32 - fromIntegral w
-   in shift 1 amountToShift
-
-wordSuccessors :: Word64 -> Word32 -> [Word32]
-wordSuccessors !w !a = if w > 0
-  then a : wordSuccessors (w - 1) (a + 1)
-  else []
-
-wordSuccessorsM :: MonadPlus m => (Word32 -> a) -> Word64 -> Word32 -> m a
-wordSuccessorsM f = go where
-  go !w !a = if w > 0
-    then mplus (return (f a)) (go (w - 1) (a + 1))
-    else mzero
-{-# INLINE wordSuccessorsM #-}
-
-mask :: Word8 -> Word32
-mask = complement . shiftR 0xffffffff . fromIntegral
-
-p24 :: Word32
-p24 = fromOctets' 10 0 0 0
-
-p20 :: Word32
-p20 = fromOctets' 172 16 0 0
-
-p16 :: Word32
-p16 = fromOctets' 192 168 0 0
-
-mask8,mask4,mask12,mask20,mask28,mask16,mask10,mask24,mask32,mask15 :: Word32
-mask4  = 0xF0000000
-mask8  = 0xFF000000
-mask10 = 0xFFC00000
-mask12 = 0xFFF00000
-mask15 = 0xFFFE0000
-mask16 = 0xFFFF0000
-mask20 = 0xFFFFF000
-mask24 = 0xFFFFFF00
-mask28 = 0xFFFFFFF0
-mask32 = 0xFFFFFFFF
-
--- r1,r2,r3,r4,r5,r6 :: Word32
--- r1 = fromOctets' 0 0 0 0
-
-macTextParser :: Maybe Char -> (Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> a) -> AT.Parser a
-macTextParser separator f = f
-  <$> (AT.hexadecimal >>= limitSize)
-  <*  parseSeparator
-  <*> (AT.hexadecimal >>= limitSize)
-  <*  parseSeparator
-  <*> (AT.hexadecimal >>= limitSize)
-  <*  parseSeparator
-  <*> (AT.hexadecimal >>= limitSize)
-  <*  parseSeparator
-  <*> (AT.hexadecimal >>= limitSize)
-  <*  parseSeparator
-  <*> (AT.hexadecimal >>= limitSize)
-  where
-  parseSeparator = case separator of
-    Just c -> AT.char c
-    Nothing -> return 'x' -- character is unused
-  limitSize i =
-    if i > 255
-      then fail "All octets in a mac address must be between 00 and FF"
-      else return i
-
--- Unchecked invariant: each of these Word64s must be smaller
--- than 256.
-unsafeWord48FromOctets :: Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word48
-unsafeWord48FromOctets a b c d e f =
-    fromIntegral
-  $ unsafeShiftL a 40
-  .|. unsafeShiftL b 32
-  .|. unsafeShiftL c 24
-  .|. unsafeShiftL d 16
-  .|. unsafeShiftL e 8
-  .|. f
-{-# INLINE unsafeWord48FromOctets #-}
-
-macFromText :: Maybe Char -> (Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> a) -> Text -> Maybe a
-macFromText separator f = rightToMaybe . macFromText' separator f
-{-# INLINE macFromText #-}
-
-macFromText' :: Maybe Char -> (Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> a) -> Text -> Either String a
-macFromText' separator f =
-  AT.parseOnly (macTextParser separator f <* AT.endOfInput)
-{-# INLINE macFromText' #-}
-
-twoDigits :: ByteString
-twoDigits = foldMap (BC8.pack . printf "%02d") $ enumFromTo (0 :: Int) 99
-{-# NOINLINE twoDigits #-}
-
-threeDigitsWord8 :: ByteString
-threeDigitsWord8 = foldMap (BC8.pack . printf "%03d") $ enumFromTo (0 :: Int) 255
-{-# NOINLINE threeDigitsWord8 #-}
-
-twoHexDigits :: ByteString
-twoHexDigits = foldMap (BC8.pack . printf "%02X") $ enumFromTo (0 :: Int) 255
-{-# NOINLINE twoHexDigits #-}
-
-twoHexDigitsLower :: ByteString
-twoHexDigitsLower = foldMap (BC8.pack . printf "%02x") $ enumFromTo (0 :: Int) 255
-{-# NOINLINE twoHexDigitsLower #-}
-
diff --git a/src/Net/Mac.hs b/src/Net/Mac.hs
--- a/src/Net/Mac.hs
+++ b/src/Net/Mac.hs
@@ -1,18 +1,72 @@
- {-# LANGUAGE DeriveGeneric #-}
- {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE UnboxedTuples #-}
 
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{-# OPTIONS_GHC -Wall #-}
 module Net.Mac
-  ( fromOctets
+  ( -- * Convert
+    fromOctets
   , toOctets
+    -- * Textual Conversion
+    -- ** Text
+  , encode
+  , encodeWith
+  , decode
+  , decodeWith
+  , decodeEither
+  , decodeEitherWith
+  , builder
+  , parser
+  , parserWith
+    -- ** UTF-8 ByteString
+  , encodeUtf8
+  , encodeWithUtf8
+  , decodeUtf8
+  , decodeWithUtf8
+  , builderUtf8
+  , parserUtf8
+  , parserWithUtf8
+  , parserLenientUtf8
+    -- * Types
+  , Mac(..)
+  , MacCodec(..)
+  , MacGrouping(..)
   ) where
 
-import Net.Types (Mac(..))
 import Data.Word
-import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement,unsafeShiftR)
-import qualified Net.Internal as Internal
+import Data.Bits ((.|.),unsafeShiftL,unsafeShiftR)
+import Data.Text (Text)
+import Data.Word (Word8)
+import Data.Word.Synthetic.Word12 (Word12)
+import Data.Monoid
+import Data.ByteString (ByteString)
+import Data.Aeson (FromJSON(..),ToJSON(..))
+import Data.Hashable (Hashable)
+import GHC.Generics (Generic)
+import Data.Char (ord)
+import qualified Data.ByteString.Builder as BB
+import qualified Data.Attoparsec.ByteString as ABW
+import qualified Data.Attoparsec.Text as AT
+import qualified Data.Attoparsec.ByteString as AB
+import qualified Data.Text.Lazy.Builder as TBuilder
+import qualified Data.Text.Builder.Fixed as TFB
+import qualified Data.ByteString.Builder.Fixed as BFB
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
 
+#if MIN_VERSION_aeson(1,0,0) 
+import Data.Aeson (ToJSONKey(..),FromJSONKey(..),
+  ToJSONKeyFunction(..),FromJSONKeyFunction(..))
+#endif
+
 fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Mac
-fromOctets a b c d e f = Mac $ Internal.unsafeWord48FromOctets
+fromOctets a b c d e f = Mac $ unsafeWord48FromOctets
   (fromIntegral a) (fromIntegral b) (fromIntegral c)
   (fromIntegral d) (fromIntegral e) (fromIntegral f)
 {-# INLINE fromOctets #-}
@@ -26,4 +80,462 @@
   , fromIntegral $ unsafeShiftR w 8
   , fromIntegral w
   )
+
+rightToMaybe :: Either a b -> Maybe b
+rightToMaybe = either (const Nothing) Just
+
+c2w :: Char -> Word8
+c2w = fromIntegral . ord
+
+encode :: Mac -> Text
+encode = encodeWith defCodec -- Internal.macToTextDefault w
+
+encodeWith :: MacCodec -> Mac -> Text
+encodeWith (MacCodec g u) m = case g of
+  MacGroupingNoSeparator -> case u of
+    True -> TFB.run (fixedBuilderNoSeparator TFB.word8HexFixedUpper) m
+    False -> TFB.run (fixedBuilderNoSeparator TFB.word8HexFixedLower) m
+  MacGroupingPairs c -> case u of
+    True -> TFB.run (fixedBuilderPairs TFB.word8HexFixedUpper) (Pair c m)
+    False -> TFB.run (fixedBuilderPairs TFB.word8HexFixedLower) (Pair c m)
+    -- withCasedBuilder u $ \bw8 -> TFB.run (fixedBuilderPairs bw8) (Pair c m)
+  MacGroupingTriples c -> case u of
+    True -> TFB.run (fixedBuilderTriples TFB.word12HexFixedUpper) (Pair c m)
+    False -> TFB.run (fixedBuilderTriples TFB.word12HexFixedLower) (Pair c m)
+  MacGroupingQuadruples c -> case u of
+    True -> TFB.run (fixedBuilderQuadruples TFB.word8HexFixedUpper) (Pair c m)
+    False -> TFB.run (fixedBuilderQuadruples TFB.word8HexFixedLower) (Pair c m)
+
+decodeEitherWith :: MacCodec -> Text -> Either String Mac
+decodeEitherWith codec t = AT.parseOnly (parserWith codec <* AT.endOfInput) t
+
+decodeEither :: Text -> Either String Mac
+decodeEither = decodeEitherWith defCodec
+
+decode :: Text -> Maybe Mac
+decode = decodeWith defCodec
+
+decodeWith :: MacCodec -> Text -> Maybe Mac
+decodeWith codec t = rightToMaybe (AT.parseOnly (parserWith codec <* AT.endOfInput) t)
+
+builder :: Mac -> TBuilder.Builder
+builder = TBuilder.fromText . encode
+
+parser :: AT.Parser Mac
+parser = parserWith defCodec
+
+parserWith :: MacCodec -> AT.Parser Mac
+parserWith (MacCodec g _) = case g of
+  MacGroupingQuadruples c -> parserQuadruples c
+  MacGroupingTriples c -> parserTriples c
+  MacGroupingPairs c -> parserPairs c
+  MacGroupingNoSeparator -> parserNoSeparator
+
+defCodec :: MacCodec
+defCodec = MacCodec (MacGroupingPairs ':') False
+
+parserQuadruples :: Char -> AT.Parser Mac
+parserQuadruples s = fromOctets
+  <$> parseTwoHex <*> parseTwoHex <* AT.char s
+  <*> parseTwoHex <*> parseTwoHex <* AT.char s
+  <*> parseTwoHex <*> parseTwoHex
+
+parserPairs :: Char -> AT.Parser Mac
+parserPairs s = fromOctets
+  <$> parseTwoHex <* AT.char s
+  <*> parseTwoHex <* AT.char s
+  <*> parseTwoHex <* AT.char s
+  <*> parseTwoHex <* AT.char s
+  <*> parseTwoHex <* AT.char s
+  <*> parseTwoHex
+
+parserTriples :: Char -> AT.Parser Mac
+parserTriples s = do
+  a1 <- parseOneHex
+  a2 <- parseOneHex
+  a3 <- parseOneHex
+  _ <- AT.char s
+  a4 <- parseOneHex
+  a5 <- parseOneHex
+  a6 <- parseOneHex
+  _ <- AT.char s
+  a7 <- parseOneHex
+  a8 <- parseOneHex
+  a9 <- parseOneHex
+  _ <- AT.char s
+  a10 <- parseOneHex
+  a11 <- parseOneHex
+  a12 <- parseOneHex
+  return $ fromOctets
+    (unsafeShiftL a1 4 + a2)
+    (unsafeShiftL a3 4 + a4)
+    (unsafeShiftL a5 4 + a6)
+    (unsafeShiftL a7 4 + a8)
+    (unsafeShiftL a9 4 + a10)
+    (unsafeShiftL a11 4 + a12)
+
+parserNoSeparator :: AT.Parser Mac
+parserNoSeparator = fromOctets
+  <$> parseTwoHex
+  <*> parseTwoHex
+  <*> parseTwoHex
+  <*> parseTwoHex
+  <*> parseTwoHex
+  <*> parseTwoHex
+
+parseTwoHex :: AT.Parser Word8
+parseTwoHex = do
+  a <- AT.anyChar >>= parseCharHex
+  b <- AT.anyChar >>= parseCharHex
+  return (unsafeShiftL a 4 + b)
+
+tryParseCharHex :: AT.Parser Word8 -> Char -> AT.Parser Word8
+tryParseCharHex a c
+  | w >= 48 && w <= 57 = return (w - 48)
+  | w >= 65 && w <= 70 = return (w - 55)
+  | w >= 97 && w <= 102 = return (w - 87)
+  | otherwise = a
+  where w = c2w c
+
+parseOneHex :: AT.Parser Word8
+parseOneHex = AT.anyChar >>= parseCharHex
+
+parseCharHex :: Char -> AT.Parser Word8
+parseCharHex = tryParseCharHex (fail "invalid hexadecimal character")
+
+data Pair = Pair
+  { pairSep :: {-# UNPACK #-} !Char
+  , pairMac :: {-# UNPACK #-} !Mac
+  }
+
+fixedBuilderTriples :: TFB.Builder Word12 -> TFB.Builder Pair
+fixedBuilderTriples tripBuilder =
+     TFB.contramapBuilder (word12At 36 . pairMac) tripBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word12At 24 . pairMac) tripBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word12At 12 . pairMac) tripBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word12At 0 . pairMac) tripBuilder
+{-# INLINE fixedBuilderTriples #-}
+
+fixedBuilderNoSeparator :: TFB.Builder Word8 -> TFB.Builder Mac
+fixedBuilderNoSeparator hexBuilder =
+     TFB.contramapBuilder (word8At 40) hexBuilder
+  <> TFB.contramapBuilder (word8At 32) hexBuilder
+  <> TFB.contramapBuilder (word8At 24) hexBuilder
+  <> TFB.contramapBuilder (word8At 16) hexBuilder
+  <> TFB.contramapBuilder (word8At 8) hexBuilder
+  <> TFB.contramapBuilder (word8At 0) hexBuilder
+{-# INLINE fixedBuilderNoSeparator #-}
+
+fixedBuilderQuadruples :: TFB.Builder Word8 -> TFB.Builder Pair
+fixedBuilderQuadruples pairBuilder =
+     TFB.contramapBuilder (word8At 40 . pairMac) pairBuilder
+  <> TFB.contramapBuilder (word8At 32 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 24 . pairMac) pairBuilder
+  <> TFB.contramapBuilder (word8At 16 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 8 . pairMac) pairBuilder
+  <> TFB.contramapBuilder (word8At 0 . pairMac) pairBuilder
+{-# INLINE fixedBuilderQuadruples #-}
+
+fixedBuilderPairs :: TFB.Builder Word8 -> TFB.Builder Pair
+fixedBuilderPairs pairBuilder =
+     TFB.contramapBuilder (word8At 40 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 32 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 24 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 16 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 8 . pairMac) pairBuilder
+  <> TFB.contramapBuilder pairSep TFB.charBmp
+  <> TFB.contramapBuilder (word8At 0 . pairMac) pairBuilder
+{-# INLINE fixedBuilderPairs #-}
+
+word8At :: Int -> Mac -> Word8
+word8At i (Mac w) = fromIntegral (unsafeShiftR w i)
+{-# INLINE word8At #-}
+
+word12At :: Int -> Mac -> Word12
+word12At i (Mac w) = fromIntegral (unsafeShiftR w i)
+{-# INLINE word12At #-}
+
+encodeUtf8 :: Mac -> ByteString
+encodeUtf8 = encodeWithUtf8 defCodec
+
+-- | Lenient decoding of MAC address that accepts lowercase, uppercase,
+--   and any kind separator.
+decodeUtf8 :: ByteString -> Maybe Mac
+decodeUtf8 = decodeLenientUtf8
+
+decodeWithUtf8 :: MacCodec -> ByteString -> Maybe Mac
+decodeWithUtf8 codec bs = rightToMaybe (AB.parseOnly (parserWithUtf8 codec <* AB.endOfInput) bs)
+
+decodeLenientUtf8 :: ByteString -> Maybe Mac
+decodeLenientUtf8 bs = rightToMaybe (AB.parseOnly (parserLenientUtf8 <* AB.endOfInput) bs)
+
+-- | Make a bytestring builder from a 'Mac' address
+--   using a colon as the separator.
+builderUtf8 :: Mac -> BB.Builder
+builderUtf8 = BB.byteString . encodeUtf8
+
+-- | Parser for a 'Mac' address using with a colon as the
+--   separator (i.e. @FA:43:B2:C0:0F:99@).
+parserUtf8 :: AB.Parser Mac
+parserUtf8 = parserWithUtf8 defCodec
+
+-- | Parser for a 'Mac' address using the provided settings.
+parserWithUtf8 :: MacCodec -> AB.Parser Mac
+parserWithUtf8 (MacCodec g _) = case g of
+  MacGroupingPairs s -> parserPairsUtf8 (c2w s)
+  MacGroupingTriples s -> parserTriplesUtf8 (c2w s)
+  MacGroupingQuadruples s -> parserQuadruplesUtf8 (c2w s)
+  MacGroupingNoSeparator -> parserNoSeparatorUtf8
+
+parserLenientUtf8 :: AB.Parser Mac
+parserLenientUtf8 = do
+  a1 <- parseOneHexUtf8
+  a2 <- parseOneHexLenientUtf8
+  a3 <- parseOneHexLenientUtf8
+  a4 <- parseOneHexLenientUtf8
+  a5 <- parseOneHexLenientUtf8
+  a6 <- parseOneHexLenientUtf8
+  a7 <- parseOneHexLenientUtf8
+  a8 <- parseOneHexLenientUtf8
+  a9 <- parseOneHexLenientUtf8
+  a10 <- parseOneHexLenientUtf8
+  a11 <- parseOneHexLenientUtf8
+  a12 <- parseOneHexLenientUtf8
+  return $ fromOctets
+    (unsafeShiftL a1 4 + a2)
+    (unsafeShiftL a3 4 + a4)
+    (unsafeShiftL a5 4 + a6)
+    (unsafeShiftL a7 4 + a8)
+    (unsafeShiftL a9 4 + a10)
+    (unsafeShiftL a11 4 + a12)
+
+
+parserNoSeparatorUtf8 :: AB.Parser Mac
+parserNoSeparatorUtf8 = fromOctets
+  <$> parseTwoHexUtf8
+  <*> parseTwoHexUtf8
+  <*> parseTwoHexUtf8
+  <*> parseTwoHexUtf8
+  <*> parseTwoHexUtf8
+  <*> parseTwoHexUtf8
+
+parserPairsUtf8 :: Word8 -> AB.Parser Mac
+parserPairsUtf8 s = fromOctets
+  <$> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8
+
+parserTriplesUtf8 :: Word8 -> AB.Parser Mac
+parserTriplesUtf8 s = do
+  a1 <- parseOneHexUtf8
+  a2 <- parseOneHexUtf8
+  a3 <- parseOneHexUtf8
+  _ <- ABW.word8 s
+  a4 <- parseOneHexUtf8
+  a5 <- parseOneHexUtf8
+  a6 <- parseOneHexUtf8
+  _ <- ABW.word8 s
+  a7 <- parseOneHexUtf8
+  a8 <- parseOneHexUtf8
+  a9 <- parseOneHexUtf8
+  _ <- ABW.word8 s
+  a10 <- parseOneHexUtf8
+  a11 <- parseOneHexUtf8
+  a12 <- parseOneHexUtf8
+  return $ fromOctets
+    (unsafeShiftL a1 4 + a2)
+    (unsafeShiftL a3 4 + a4)
+    (unsafeShiftL a5 4 + a6)
+    (unsafeShiftL a7 4 + a8)
+    (unsafeShiftL a9 4 + a10)
+    (unsafeShiftL a11 4 + a12)
+
+parserQuadruplesUtf8 :: Word8 -> AB.Parser Mac
+parserQuadruplesUtf8 s  = fromOctets
+  <$> parseTwoHexUtf8 <*> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8 <*> parseTwoHexUtf8 <* ABW.word8 s
+  <*> parseTwoHexUtf8 <*> parseTwoHexUtf8
+
+parseOneHexUtf8 :: AB.Parser Word8
+parseOneHexUtf8 = ABW.anyWord8 >>= parseWord8Hex
+
+-- | Parse a single hexidecimal character. This will skip
+--   at most one character to do this.
+parseOneHexLenientUtf8 :: AB.Parser Word8
+parseOneHexLenientUtf8 = do
+  a <- ABW.anyWord8
+  flip tryParseWord8Hex a $ do
+    b <- ABW.anyWord8
+    tryParseWord8Hex (fail "invalid hexadecimal character") b
+
+parseTwoHexUtf8 :: AB.Parser Word8
+parseTwoHexUtf8 = do
+  a <- ABW.anyWord8 >>= parseWord8Hex
+  b <- ABW.anyWord8 >>= parseWord8Hex
+  return (unsafeShiftL a 4 + b)
+
+-- | Kind of a confusing type signature. The Word8 that stands
+--   alone represented an ascii-encoded value. The others actually
+--   describes the numbers that would be decoded from this value.
+tryParseWord8Hex :: AB.Parser Word8 -> Word8 -> AB.Parser Word8
+tryParseWord8Hex a w
+  | w >= 48 && w <= 57 = return (w - 48)
+  | w >= 65 && w <= 70 = return (w - 55)
+  | w >= 97 && w <= 102 = return (w - 87)
+  | otherwise = a
+
+parseWord8Hex :: Word8 -> AB.Parser Word8
+parseWord8Hex = tryParseWord8Hex (fail "invalid hexadecimal character")
+
+encodeWithUtf8 :: MacCodec -> Mac -> ByteString
+encodeWithUtf8 (MacCodec g u) m = case g of
+  MacGroupingNoSeparator -> case u of
+    True -> BFB.run (fixedBuilderNoSeparatorUtf8 BFB.word8HexFixedUpper) m
+    False -> BFB.run (fixedBuilderNoSeparatorUtf8 BFB.word8HexFixedLower) m
+  MacGroupingPairs c -> case u of
+    True -> BFB.run (fixedBuilderPairsUtf8 BFB.word8HexFixedUpper) (PairUtf8 (c2w c) m)
+    False -> BFB.run (fixedBuilderPairsUtf8 BFB.word8HexFixedLower) (PairUtf8 (c2w c) m)
+    -- withCasedBuilder u $ \bw8 -> BFB.run (fixedBuilderPairs bw8) (Pair c m)
+  MacGroupingTriples c -> case u of
+    True -> BFB.run (fixedBuilderTriplesUtf8 BFB.word12HexFixedUpper) (PairUtf8 (c2w c) m)
+    False -> BFB.run (fixedBuilderTriplesUtf8 BFB.word12HexFixedLower) (PairUtf8 (c2w c) m)
+  MacGroupingQuadruples c -> case u of
+    True -> BFB.run (fixedBuilderQuadruplesUtf8 BFB.word8HexFixedUpper) (PairUtf8 (c2w c) m)
+    False -> BFB.run (fixedBuilderQuadruplesUtf8 BFB.word8HexFixedLower) (PairUtf8 (c2w c) m)
+
+data PairUtf8 = PairUtf8
+  { pairSepUtf8 :: {-# UNPACK #-} !Word8
+  , pairMacUtf8 :: {-# UNPACK #-} !Mac
+  }
+
+fixedBuilderTriplesUtf8 :: BFB.Builder Word12 -> BFB.Builder PairUtf8
+fixedBuilderTriplesUtf8 tripBuilder =
+     BFB.contramapBuilder (word12AtUtf8 36 . pairMacUtf8) tripBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word12AtUtf8 24 . pairMacUtf8) tripBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word12AtUtf8 12 . pairMacUtf8) tripBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word12AtUtf8 0 . pairMacUtf8) tripBuilder
+{-# INLINE fixedBuilderTriplesUtf8 #-}
+
+fixedBuilderQuadruplesUtf8 :: BFB.Builder Word8 -> BFB.Builder PairUtf8
+fixedBuilderQuadruplesUtf8 pairBuilder =
+     BFB.contramapBuilder (word8AtUtf8 40 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 32 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 24 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 16 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 8 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 0 . pairMacUtf8) pairBuilder
+{-# INLINE fixedBuilderQuadruplesUtf8 #-}
+
+fixedBuilderPairsUtf8 :: BFB.Builder Word8 -> BFB.Builder PairUtf8
+fixedBuilderPairsUtf8 pairBuilder =
+     BFB.contramapBuilder (word8AtUtf8 40 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 32 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 24 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 16 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 8 . pairMacUtf8) pairBuilder
+  <> BFB.contramapBuilder pairSepUtf8 BFB.word8
+  <> BFB.contramapBuilder (word8AtUtf8 0 . pairMacUtf8) pairBuilder
+{-# INLINE fixedBuilderPairsUtf8 #-}
+
+fixedBuilderNoSeparatorUtf8 :: BFB.Builder Word8 -> BFB.Builder Mac
+fixedBuilderNoSeparatorUtf8 hexBuilder =
+     BFB.contramapBuilder (word8AtUtf8 40) hexBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 32) hexBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 24) hexBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 16) hexBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 8) hexBuilder
+  <> BFB.contramapBuilder (word8AtUtf8 0) hexBuilder
+{-# INLINE fixedBuilderNoSeparatorUtf8 #-}
+
+word8AtUtf8 :: Int -> Mac -> Word8
+word8AtUtf8 i (Mac w) = fromIntegral (unsafeShiftR w i)
+{-# INLINE word8AtUtf8 #-}
+
+word12AtUtf8 :: Int -> Mac -> Word12
+word12AtUtf8 i (Mac w) = fromIntegral (unsafeShiftR w i)
+{-# INLINE word12AtUtf8 #-}
+
+-- | A 48-bit MAC address. Do not use the data constructor for this
+--   type. It is not considered part of the stable API, and it
+--   allows you to construct invalid MAC addresses.
+newtype Mac = Mac { getMac :: Word64 }
+  deriving (Eq,Ord,Show,Read,Generic)
+
+data MacCodec = MacCodec
+  { macCodecGrouping :: !MacGrouping
+  , macCodecUpperCase :: !Bool
+  } deriving (Eq,Ord,Show,Read,Generic)
+
+-- | The format expected by the mac address parser. The 'Word8' taken
+--   by some of these constructors is the ascii value of the character
+--   to be used as the separator. This is typically a colon, a hyphen, or
+--   a space character. All decoding functions are case insensitive.
+data MacGrouping
+  = MacGroupingPairs !Char -- ^ Two-character groups, @FA:2B:40:09:8C:11@
+  | MacGroupingTriples !Char -- ^ Three-character groups, @24B-F0A-025-829@
+  | MacGroupingQuadruples !Char -- ^ Four-character groups, @A220.0745.CAC7@
+  | MacGroupingNoSeparator -- ^ No separator, @24AF4B5B0780@
+  deriving (Eq,Ord,Show,Read,Generic)
+
+instance Hashable Mac
+
+instance ToJSON Mac where
+  toJSON = Aeson.String . encode
+
+#if MIN_VERSION_aeson(1,0,0) 
+instance ToJSONKey Mac where
+  toJSONKey = ToJSONKeyText
+    encode
+    (\m -> Aeson.unsafeToEncoding $ BB.char7 '"' <> builderUtf8 m <> BB.char7 '"')
+
+instance FromJSONKey Mac where
+  fromJSONKey = FromJSONKeyTextParser $ \t -> case decode t of
+    Nothing -> fail "invalid mac address"
+    Just mac -> return mac
+#endif
+
+instance FromJSON Mac where
+  parseJSON = attoparsecParseJSON parser
+
+attoparsecParseJSON :: AT.Parser a -> Aeson.Value -> Aeson.Parser a
+attoparsecParseJSON p v =
+  case v of
+    Aeson.String t ->
+      case AT.parseOnly p t of
+        Left err  -> fail err
+        Right res -> return res
+    _ -> fail "expected a String"
+
+-- Unchecked invariant: each of these Word64s must be smaller
+-- than 256.
+unsafeWord48FromOctets :: Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64
+unsafeWord48FromOctets a b c d e f =
+    fromIntegral
+  $ unsafeShiftL a 40
+  .|. unsafeShiftL b 32
+  .|. unsafeShiftL c 24
+  .|. unsafeShiftL d 16
+  .|. unsafeShiftL e 8
+  .|. f
 
diff --git a/src/Net/Mac/ByteString/Char8.hs b/src/Net/Mac/ByteString/Char8.hs
deleted file mode 100644
--- a/src/Net/Mac/ByteString/Char8.hs
+++ /dev/null
@@ -1,254 +0,0 @@
-module Net.Mac.ByteString.Char8
-  ( encode
-  , encodeWith
-  , decode
-  , decodeWith
-  , decodeLenient
-  , builder
-  , parser
-  , parserWith
-  , parserLenient
-  ) where
-
-import Net.Types (Mac(..),MacCodec(..),MacGrouping(..))
-import Net.Mac (fromOctets)
-import Data.ByteString (ByteString)
-import Data.Attoparsec.ByteString.Char8 (Parser)
-import Data.ByteString.Lazy.Builder (Builder)
-import Net.Internal (rightToMaybe,c2w)
-import Data.Text.Encoding (encodeUtf8, decodeUtf8')
-import Data.Word (Word8)
-import Data.Word.Synthetic (Word12)
-import Data.Bits (unsafeShiftL,unsafeShiftR)
-import Control.Monad
-import Data.Monoid
-import qualified Data.ByteString.Builder.Fixed as FB
-import qualified Data.ByteString.Builder as Builder
-import qualified Data.Attoparsec.ByteString as ABW
-import qualified Data.Attoparsec.ByteString.Char8 as AB
-
-encode :: Mac -> ByteString
-encode = encodeWith defCodec
-
-decode :: ByteString -> Maybe Mac
-decode = decodeWith defCodec
-
-decodeWith :: MacCodec -> ByteString -> Maybe Mac
-decodeWith codec bs = rightToMaybe (AB.parseOnly (parserWith codec <* AB.endOfInput) bs)
-
-decodeLenient :: ByteString -> Maybe Mac
-decodeLenient bs = rightToMaybe (AB.parseOnly (parserLenient <* AB.endOfInput) bs)
-
--- | Make a bytestring builder from a 'Mac' address
---   using a colon as the separator.
-builder :: Mac -> Builder
-builder = Builder.byteString . encode
-
--- | Parser for a 'Mac' address using with a colon as the
---   separator (i.e. @FA:43:B2:C0:0F:99@).
-parser :: Parser Mac
-parser = parserWith defCodec
-
--- | Parser for a 'Mac' address using the provided settings.
-parserWith :: MacCodec -> Parser Mac
-parserWith (MacCodec g _) = case g of
-  MacGroupingPairs s -> parserPairs (c2w s)
-  MacGroupingTriples s -> parserTriples (c2w s)
-  MacGroupingQuadruples s -> parserQuadruples (c2w s)
-  MacGroupingNoSeparator -> parserNoSeparator
-
-parserLenient :: Parser Mac
-parserLenient = do
-  a1 <- parseOneHex
-  a2 <- parseOneHexLenient
-  a3 <- parseOneHexLenient
-  a4 <- parseOneHexLenient
-  a5 <- parseOneHexLenient
-  a6 <- parseOneHexLenient
-  a7 <- parseOneHexLenient
-  a8 <- parseOneHexLenient
-  a9 <- parseOneHexLenient
-  a10 <- parseOneHexLenient
-  a11 <- parseOneHexLenient
-  a12 <- parseOneHexLenient
-  return $ fromOctets
-    (unsafeShiftL a1 4 + a2)
-    (unsafeShiftL a3 4 + a4)
-    (unsafeShiftL a5 4 + a6)
-    (unsafeShiftL a7 4 + a8)
-    (unsafeShiftL a9 4 + a10)
-    (unsafeShiftL a11 4 + a12)
-
-
-parserNoSeparator :: Parser Mac
-parserNoSeparator = fromOctets
-  <$> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-
-parserPairs :: Word8 -> Parser Mac
-parserPairs s = fromOctets
-  <$> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex
-
-parserTriples :: Word8 -> Parser Mac
-parserTriples s = do
-  a1 <- parseOneHex
-  a2 <- parseOneHex
-  a3 <- parseOneHex
-  _ <- ABW.word8 s
-  a4 <- parseOneHex
-  a5 <- parseOneHex
-  a6 <- parseOneHex
-  _ <- ABW.word8 s
-  a7 <- parseOneHex
-  a8 <- parseOneHex
-  a9 <- parseOneHex
-  _ <- ABW.word8 s
-  a10 <- parseOneHex
-  a11 <- parseOneHex
-  a12 <- parseOneHex
-  return $ fromOctets
-    (unsafeShiftL a1 4 + a2)
-    (unsafeShiftL a3 4 + a4)
-    (unsafeShiftL a5 4 + a6)
-    (unsafeShiftL a7 4 + a8)
-    (unsafeShiftL a9 4 + a10)
-    (unsafeShiftL a11 4 + a12)
-
-parserQuadruples :: Word8 -> Parser Mac
-parserQuadruples s  = fromOctets
-  <$> parseTwoHex <*> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex <*> parseTwoHex <* ABW.word8 s
-  <*> parseTwoHex <*> parseTwoHex
-
-parseOneHex :: Parser Word8
-parseOneHex = ABW.anyWord8 >>= parseWord8Hex
-
--- | Parse a single hexidecimal character. This will skip
---   at most one character to do this.
-parseOneHexLenient :: Parser Word8
-parseOneHexLenient = do
-  a <- ABW.anyWord8
-  flip tryParseWord8Hex a $ do
-    b <- ABW.anyWord8
-    tryParseWord8Hex (fail "invalid hexadecimal character") b
-
-parseTwoHex :: Parser Word8
-parseTwoHex = do
-  a <- ABW.anyWord8 >>= parseWord8Hex
-  b <- ABW.anyWord8 >>= parseWord8Hex
-  return (unsafeShiftL a 4 + b)
-
--- | Kind of a confusing type signature. The Word8 that stands
---   alone represented an ascii-encoded value. The others actually
---   describes the numbers that would be decoded from this value.
-tryParseWord8Hex :: Parser Word8 -> Word8 -> Parser Word8
-tryParseWord8Hex a w
-  | w >= 48 && w <= 57 = return (w - 48)
-  | w >= 65 && w <= 70 = return (w - 55)
-  | w >= 97 && w <= 102 = return (w - 87)
-  | otherwise = a
-
-parseWord8Hex :: Word8 -> Parser Word8
-parseWord8Hex = tryParseWord8Hex (fail "invalid hexadecimal character")
-
-defCodec :: MacCodec
-defCodec = MacCodec (MacGroupingPairs ':') False
-
-encodeWith :: MacCodec -> Mac -> ByteString
-encodeWith (MacCodec g u) m = case g of
-  MacGroupingNoSeparator -> case u of
-    True -> FB.run (fixedBuilderNoSeparator FB.word8HexFixedUpper) m
-    False -> FB.run (fixedBuilderNoSeparator FB.word8HexFixedLower) m
-  MacGroupingPairs c -> case u of
-    True -> FB.run (fixedBuilderPairs FB.word8HexFixedUpper) (Pair (c2w c) m)
-    False -> FB.run (fixedBuilderPairs FB.word8HexFixedLower) (Pair (c2w c) m)
-    -- withCasedBuilder u $ \bw8 -> FB.run (fixedBuilderPairs bw8) (Pair c m)
-  MacGroupingTriples c -> case u of
-    True -> FB.run (fixedBuilderTriples FB.word12HexFixedUpper) (Pair (c2w c) m)
-    False -> FB.run (fixedBuilderTriples FB.word12HexFixedLower) (Pair (c2w c) m)
-  MacGroupingQuadruples c -> case u of
-    True -> FB.run (fixedBuilderQuadruples FB.word8HexFixedUpper) (Pair (c2w c) m)
-    False -> FB.run (fixedBuilderQuadruples FB.word8HexFixedLower) (Pair (c2w c) m)
-
-withCasedBuilder :: Bool -> (FB.Builder Word8 -> a) -> a
-withCasedBuilder x f = case x of
-  True -> f FB.word8HexFixedUpper
-  False -> f FB.word8HexFixedLower
-{-# INLINE withCasedBuilder #-}
-
-withCasedBuilderTriple :: Bool -> (FB.Builder Word12 -> a) -> a
-withCasedBuilderTriple x f = case x of
-  True -> f FB.word12HexFixedUpper
-  False -> f FB.word12HexFixedLower
-{-# INLINE withCasedBuilderTriple #-}
-
-data Pair = Pair
-  { pairSep :: {-# UNPACK #-} !Word8
-  , pairMac :: {-# UNPACK #-} !Mac
-  }
-
-fixedBuilderTriples :: FB.Builder Word12 -> FB.Builder Pair
-fixedBuilderTriples tripBuilder =
-     FB.contramapBuilder (word12At 36 . pairMac) tripBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word12At 24 . pairMac) tripBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word12At 12 . pairMac) tripBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word12At 0 . pairMac) tripBuilder
-{-# INLINE fixedBuilderTriples #-}
-
-fixedBuilderQuadruples :: FB.Builder Word8 -> FB.Builder Pair
-fixedBuilderQuadruples pairBuilder =
-     FB.contramapBuilder (word8At 40 . pairMac) pairBuilder
-  <> FB.contramapBuilder (word8At 32 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 24 . pairMac) pairBuilder
-  <> FB.contramapBuilder (word8At 16 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 8 . pairMac) pairBuilder
-  <> FB.contramapBuilder (word8At 0 . pairMac) pairBuilder
-{-# INLINE fixedBuilderQuadruples #-}
-
-fixedBuilderPairs :: FB.Builder Word8 -> FB.Builder Pair
-fixedBuilderPairs pairBuilder =
-     FB.contramapBuilder (word8At 40 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 32 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 24 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 16 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 8 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.word8
-  <> FB.contramapBuilder (word8At 0 . pairMac) pairBuilder
-{-# INLINE fixedBuilderPairs #-}
-
-fixedBuilderNoSeparator :: FB.Builder Word8 -> FB.Builder Mac
-fixedBuilderNoSeparator hexBuilder =
-     FB.contramapBuilder (word8At 40) hexBuilder
-  <> FB.contramapBuilder (word8At 32) hexBuilder
-  <> FB.contramapBuilder (word8At 24) hexBuilder
-  <> FB.contramapBuilder (word8At 16) hexBuilder
-  <> FB.contramapBuilder (word8At 8) hexBuilder
-  <> FB.contramapBuilder (word8At 0) hexBuilder
-{-# INLINE fixedBuilderNoSeparator #-}
-
-word8At :: Int -> Mac -> Word8
-word8At i (Mac w) = fromIntegral (unsafeShiftR w i)
-{-# INLINE word8At #-}
-
-word12At :: Int -> Mac -> Word12
-word12At i (Mac w) = fromIntegral (unsafeShiftR w i)
-{-# INLINE word12At #-}
-
diff --git a/src/Net/Mac/Text.hs b/src/Net/Mac/Text.hs
deleted file mode 100644
--- a/src/Net/Mac/Text.hs
+++ /dev/null
@@ -1,221 +0,0 @@
-module Net.Mac.Text
-  ( encode
-  , encodeWith
-  , decode
-  , decodeWith
-  , decodeEither
-  , decodeEitherWith
-  , builder
-  , parser
-  , parserWith
-  ) where
-
-import Net.Types (Mac(..),MacCodec(..),MacGrouping(..))
-import Net.Mac (fromOctets)
--- import Net.Mac (fromOctetsNoCast)
-import Data.Text (Text)
-import Data.Word (Word8)
-import Data.Word.Synthetic (Word12)
-import Data.Char (chr)
-import Data.Attoparsec.Text (Parser)
-import Net.Internal (rightToMaybe,c2w)
-import Data.Bits (unsafeShiftL,unsafeShiftR)
-import Data.Monoid
-import qualified Net.Internal as Internal
-import qualified Data.Attoparsec.Text as AT
-import qualified Data.Text.Lazy.Builder as TBuilder
-import qualified Data.Text.Builder.Fixed as FB
-
-encode :: Mac -> Text
-encode = encodeWith defCodec -- Internal.macToTextDefault w
-
-encodeWith :: MacCodec -> Mac -> Text
-encodeWith (MacCodec g u) m = case g of
-  MacGroupingNoSeparator -> case u of
-    True -> FB.run (fixedBuilderNoSeparator FB.word8HexFixedUpper) m
-    False -> FB.run (fixedBuilderNoSeparator FB.word8HexFixedLower) m
-  MacGroupingPairs c -> case u of
-    True -> FB.run (fixedBuilderPairs FB.word8HexFixedUpper) (Pair c m)
-    False -> FB.run (fixedBuilderPairs FB.word8HexFixedLower) (Pair c m)
-    -- withCasedBuilder u $ \bw8 -> FB.run (fixedBuilderPairs bw8) (Pair c m)
-  MacGroupingTriples c -> case u of
-    True -> FB.run (fixedBuilderTriples FB.word12HexFixedUpper) (Pair c m)
-    False -> FB.run (fixedBuilderTriples FB.word12HexFixedLower) (Pair c m)
-  MacGroupingQuadruples c -> case u of
-    True -> FB.run (fixedBuilderQuadruples FB.word8HexFixedUpper) (Pair c m)
-    False -> FB.run (fixedBuilderQuadruples FB.word8HexFixedLower) (Pair c m)
-
-withCasedBuilder :: Bool -> (FB.Builder Word8 -> a) -> a
-withCasedBuilder x f = case x of
-  True -> f FB.word8HexFixedUpper
-  False -> f FB.word8HexFixedLower
-{-# INLINE withCasedBuilder #-}
-
-
-decodeEitherWith :: MacCodec -> Text -> Either String Mac
-decodeEitherWith codec t = AT.parseOnly (parserWith codec <* AT.endOfInput) t
-
-decodeEither :: Text -> Either String Mac
-decodeEither = decodeEitherWith defCodec
-
-decode :: Text -> Maybe Mac
-decode = decodeWith defCodec
-
-decodeWith :: MacCodec -> Text -> Maybe Mac
-decodeWith codec t = rightToMaybe (AT.parseOnly (parserWith codec <* AT.endOfInput) t)
-
-builder :: Mac -> TBuilder.Builder
-builder = TBuilder.fromText . encode
-
-parser :: AT.Parser Mac
-parser = parserWith defCodec
-
-parserWith :: MacCodec -> AT.Parser Mac
-parserWith (MacCodec g _) = case g of
-  MacGroupingQuadruples c -> parserQuadruples c
-  MacGroupingTriples c -> parserTriples c
-  MacGroupingPairs c -> parserPairs c
-  MacGroupingNoSeparator -> parserNoSeparator
-
-
-defCodec :: MacCodec
-defCodec = MacCodec (MacGroupingPairs ':') False
-
-w8ToChar :: Word8 -> Char
-w8ToChar = chr . fromIntegral
-
-parserQuadruples :: Char -> Parser Mac
-parserQuadruples s = fromOctets
-  <$> parseTwoHex <*> parseTwoHex <* AT.char s
-  <*> parseTwoHex <*> parseTwoHex <* AT.char s
-  <*> parseTwoHex <*> parseTwoHex
-
-parserPairs :: Char -> Parser Mac
-parserPairs s = fromOctets
-  <$> parseTwoHex <* AT.char s
-  <*> parseTwoHex <* AT.char s
-  <*> parseTwoHex <* AT.char s
-  <*> parseTwoHex <* AT.char s
-  <*> parseTwoHex <* AT.char s
-  <*> parseTwoHex
-
-parserTriples :: Char -> Parser Mac
-parserTriples s = do
-  a1 <- parseOneHex
-  a2 <- parseOneHex
-  a3 <- parseOneHex
-  _ <- AT.char s
-  a4 <- parseOneHex
-  a5 <- parseOneHex
-  a6 <- parseOneHex
-  _ <- AT.char s
-  a7 <- parseOneHex
-  a8 <- parseOneHex
-  a9 <- parseOneHex
-  _ <- AT.char s
-  a10 <- parseOneHex
-  a11 <- parseOneHex
-  a12 <- parseOneHex
-  return $ fromOctets
-    (unsafeShiftL a1 4 + a2)
-    (unsafeShiftL a3 4 + a4)
-    (unsafeShiftL a5 4 + a6)
-    (unsafeShiftL a7 4 + a8)
-    (unsafeShiftL a9 4 + a10)
-    (unsafeShiftL a11 4 + a12)
-
-parserNoSeparator :: Parser Mac
-parserNoSeparator = fromOctets
-  <$> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-  <*> parseTwoHex
-
-parseTwoHex :: Parser Word8
-parseTwoHex = do
-  a <- AT.anyChar >>= parseCharHex
-  b <- AT.anyChar >>= parseCharHex
-  return (unsafeShiftL a 4 + b)
-
-tryParseCharHex :: Parser Word8 -> Char -> Parser Word8
-tryParseCharHex a c
-  | w >= 48 && w <= 57 = return (w - 48)
-  | w >= 65 && w <= 70 = return (w - 55)
-  | w >= 97 && w <= 102 = return (w - 87)
-  | otherwise = a
-  where w = c2w c
-
-parseOneHex :: Parser Word8
-parseOneHex = AT.anyChar >>= parseCharHex
-
-parseCharHex :: Char -> Parser Word8
-parseCharHex = tryParseCharHex (fail "invalid hexadecimal character")
-
-withCasedBuilderTriple :: Bool -> (FB.Builder Word12 -> a) -> a
-withCasedBuilderTriple x f = case x of
-  True -> f FB.word12HexFixedUpper
-  False -> f FB.word12HexFixedLower
-{-# INLINE withCasedBuilderTriple #-}
-
-data Pair = Pair
-  { pairSep :: {-# UNPACK #-} !Char
-  , pairMac :: {-# UNPACK #-} !Mac
-  }
-
-fixedBuilderTriples :: FB.Builder Word12 -> FB.Builder Pair
-fixedBuilderTriples tripBuilder =
-     FB.contramapBuilder (word12At 36 . pairMac) tripBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word12At 24 . pairMac) tripBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word12At 12 . pairMac) tripBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word12At 0 . pairMac) tripBuilder
-{-# INLINE fixedBuilderTriples #-}
-
-fixedBuilderNoSeparator :: FB.Builder Word8 -> FB.Builder Mac
-fixedBuilderNoSeparator hexBuilder =
-     FB.contramapBuilder (word8At 40) hexBuilder
-  <> FB.contramapBuilder (word8At 32) hexBuilder
-  <> FB.contramapBuilder (word8At 24) hexBuilder
-  <> FB.contramapBuilder (word8At 16) hexBuilder
-  <> FB.contramapBuilder (word8At 8) hexBuilder
-  <> FB.contramapBuilder (word8At 0) hexBuilder
-{-# INLINE fixedBuilderNoSeparator #-}
-
-fixedBuilderQuadruples :: FB.Builder Word8 -> FB.Builder Pair
-fixedBuilderQuadruples pairBuilder =
-     FB.contramapBuilder (word8At 40 . pairMac) pairBuilder
-  <> FB.contramapBuilder (word8At 32 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 24 . pairMac) pairBuilder
-  <> FB.contramapBuilder (word8At 16 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 8 . pairMac) pairBuilder
-  <> FB.contramapBuilder (word8At 0 . pairMac) pairBuilder
-{-# INLINE fixedBuilderQuadruples #-}
-
-fixedBuilderPairs :: FB.Builder Word8 -> FB.Builder Pair
-fixedBuilderPairs pairBuilder =
-     FB.contramapBuilder (word8At 40 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 32 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 24 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 16 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 8 . pairMac) pairBuilder
-  <> FB.contramapBuilder pairSep FB.charBmp
-  <> FB.contramapBuilder (word8At 0 . pairMac) pairBuilder
-{-# INLINE fixedBuilderPairs #-}
-
-word8At :: Int -> Mac -> Word8
-word8At i (Mac w) = fromIntegral (unsafeShiftR w i)
-{-# INLINE word8At #-}
-
-word12At :: Int -> Mac -> Word12
-word12At i (Mac w) = fromIntegral (unsafeShiftR w i)
-{-# INLINE word12At #-}
diff --git a/src/Net/Types.hs b/src/Net/Types.hs
--- a/src/Net/Types.hs
+++ b/src/Net/Types.hs
@@ -1,12 +1,4 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE InstanceSigs #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE UnboxedTuples #-}
-
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# OPTIONS_GHC -Wall -Werror #-}
 
 module Net.Types
   ( IPv4(..)
@@ -18,310 +10,9 @@
   , MacGrouping(..)
   ) where
 
-import qualified Net.Internal         as Internal
-import qualified Data.Aeson           as Aeson
-import qualified Data.Aeson.Types     as Aeson
-import qualified Data.Attoparsec.Text as AT
--- import qualified Data.Vector.Generic            as G
--- import qualified Data.Vector.Generic.Mutable    as M
-import qualified Data.Vector.Generic            as GVector
-import qualified Data.Vector.Unboxed            as UVector
-import qualified Data.Vector.Primitive          as PVector
-import qualified Data.Vector.Generic.Mutable    as MGVector
-import qualified Data.Vector.Unboxed.Mutable    as MUVector
-import qualified Data.Vector.Primitive.Mutable  as MPVector
-import qualified Data.Text.Encoding as TE
-import qualified Data.ByteString.Builder as BB
-import Foreign.Storable (Storable(..))
-import Data.Word.Synthetic (Word48)
-import Data.Primitive.Types (Prim)
-import Data.Bits (Bits,FiniteBits,(.|.),unsafeShiftL)
-import Data.Coerce (coerce)
-import Control.Monad
-import Data.Word
-import Data.Int
-import Data.Hashable
-import Data.Aeson (FromJSON(..),ToJSON(..))
-import GHC.Generics (Generic)
-import Data.Monoid
-
-#if MIN_VERSION_aeson(1,0,0) 
-import qualified Data.Aeson.Encoding as Aeson
-import Data.Aeson (ToJSONKey(..),FromJSONKey(..),
-  ToJSONKeyFunction(..),FromJSONKeyFunction(..))
-#endif
-
--- | A 32-bit Internet Protocol version 4 address.
-newtype IPv4 = IPv4 { getIPv4 :: Word32 }
-  deriving (Eq,Ord,Show,Read,Enum,Bounded,Hashable,Generic,Prim,Bits,FiniteBits,Storable)
-
--- | A 128-bit Internet Protocol version 6 address.
-data IPv6 = IPv6
-  { ipv6A :: {-# UNPACK #-} !Word64
-  , ipv6B :: {-# UNPACK #-} !Word64
-  } deriving (Eq,Ord,Show,Read)
-
--- | A 32-bit 'IPv4' address or a 128-bit 'IPv6' address. Internally, this
---   is just represented as an 'IPv6' address. The functions provided
---   in @Net.IP@ help simulate pattern matching on it.
-newtype IP = IP { getIP :: IPv6 }
-  deriving (Eq,Ord,Show,Read)
-
--- | The length should be between 0 and 32. These bounds are inclusive.
---   This expectation is not in any way enforced by this library because
---   it does not cause errors. A mask length greater than 32 will be
---   treated as if it were 32.
-data IPv4Range = IPv4Range
-  { ipv4RangeBase   :: {-# UNPACK #-} !IPv4
-  , ipv4RangeLength :: {-# UNPACK #-} !Word8
-  } deriving (Eq,Ord,Show,Read,Generic)
-
--- | A 48-bit MAC address.
-newtype Mac = Mac { getMac :: Word48 }
-  deriving (Eq,Ord,Show,Read,Generic)
-
--- data MacEncoding = MacEncoding
---   { macEncodingSeparator :: {-# UNPACK #-} !Word8 -- ^ ASCII value of the separator
---   , macEncodingUpperCase :: {-# UNPACK #-} !Bool
---   } deriving (Eq,Ord,Show,Read,Generic)
-
-data MacCodec = MacCodec
-  { macCodecGrouping :: !MacGrouping
-  , macCodecUpperCase :: !Bool
-  } deriving (Eq,Ord,Show,Read,Generic)
-
--- | The format expected by the mac address parser. The 'Word8' taken
---   by some of these constructors is the ascii value of the character
---   to be used as the separator. This is typically a colon, a hyphen, or
---   a space character. All decoding functions are case insensitive.
-data MacGrouping
-  = MacGroupingPairs !Char -- ^ Two-character groups, @FA:2B:40:09:8C:11@
-  | MacGroupingTriples !Char -- ^ Three-character groups, @24B-F0A-025-829@
-  | MacGroupingQuadruples !Char -- ^ Four-character groups, @A220.0745.CAC7@
-  | MacGroupingNoSeparator -- ^ No separator, @24AF4B5B0780@
-  deriving (Eq,Ord,Show,Read,Generic)
-
-instance Hashable Mac
-
-instance ToJSON Mac where
-  toJSON (Mac w) = Aeson.String (Internal.macToTextDefault w)
-
-#if MIN_VERSION_aeson(1,0,0) 
-instance ToJSONKey Mac where
-  toJSONKey = ToJSONKeyText
-    (\(Mac w) -> Internal.macToTextDefault w)
-    -- The bytestring encoding currently goes through text. This is suboptimal.
-    (\(Mac w) -> Aeson.unsafeToEncoding $ BB.char7 '"' <> BB.byteString (TE.encodeUtf8 $ Internal.macToTextDefault w) <> BB.char7 '"')
-
-instance FromJSONKey Mac where
-  fromJSONKey = FromJSONKeyTextParser $ \t -> 
-    case Internal.macFromText (Just ':') macFromOctets' t of
-      Nothing -> fail "invalid mac address"
-      Just mac -> return mac
-
-instance ToJSONKey IPv4 where
-  toJSONKey = ToJSONKeyText
-    (\(IPv4 w) -> Internal.toDotDecimalText w)
-    (\(IPv4 w) -> Aeson.unsafeToEncoding $ BB.char7 '"' <> (BB.byteString $ TE.encodeUtf8 $ Internal.toDotDecimalText w) <> BB.char7 '"')
-
-instance FromJSONKey IPv4 where
-  fromJSONKey = FromJSONKeyTextParser
-    (Internal.eitherToAesonParser . coerce Internal.decodeIPv4TextEither)
-#endif
-
-instance FromJSON Mac where
-  parseJSON = Internal.attoparsecParseJSON
-    (Internal.macTextParser (Just ':') macFromOctets' <* AT.endOfInput)
-
--- Unchecked invariant: each of these Word64s must be smaller
--- than 256.
-macFromOctets' :: Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Mac
-macFromOctets' a b c d e f = 
-  Mac (Internal.unsafeWord48FromOctets a b c d e f)
-{-# INLINE macFromOctets' #-}
-
-instance Hashable IPv4Range
-
-instance ToJSON IPv4 where
-  toJSON (IPv4 addr) = Aeson.String (Internal.toDotDecimalText addr)
-
-instance FromJSON IPv4 where
-  parseJSON = Aeson.withText "IPv4" (Internal.eitherToAesonParser . coerce Internal.decodeIPv4TextEither)
-
-instance ToJSON IPv4Range where
-  toJSON (IPv4Range (IPv4 addr) range) = Aeson.String (Internal.rangeToDotDecimalText addr range)
-
-instance FromJSON IPv4Range where
-  parseJSON (Aeson.String t) =
-    case Internal.rangeFromDotDecimalText' mkIPv4Range t of
-      Left err  -> fail err
-      Right res -> return res
-  parseJSON _ = mzero
-
-mkIPv4Range :: Word32 -> Word8 -> IPv4Range
-mkIPv4Range w len = IPv4Range (IPv4 w) len
-{-# INLINE mkIPv4Range #-}
-
-newtype instance UVector.MVector s IPv4 = MV_IPv4 (PVector.MVector s IPv4)
-newtype instance UVector.Vector IPv4 = V_IPv4 (PVector.Vector IPv4)
-
-instance UVector.Unbox IPv4
-
-instance MGVector.MVector UVector.MVector IPv4 where
-  {-# INLINE basicLength #-}
-  {-# INLINE basicUnsafeSlice #-}
-  {-# INLINE basicOverlaps #-}
-  {-# INLINE basicUnsafeNew #-}
-  {-# INLINE basicInitialize #-}
-  {-# INLINE basicUnsafeReplicate #-}
-  {-# INLINE basicUnsafeRead #-}
-  {-# INLINE basicUnsafeWrite #-}
-  {-# INLINE basicClear #-}
-  {-# INLINE basicSet #-}
-  {-# INLINE basicUnsafeCopy #-}
-  {-# INLINE basicUnsafeGrow #-}
-  basicLength (MV_IPv4 v) = MGVector.basicLength v
-  basicUnsafeSlice i n (MV_IPv4 v) = MV_IPv4 $ MGVector.basicUnsafeSlice i n v
-  basicOverlaps (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicOverlaps v1 v2
-  basicUnsafeNew n = MV_IPv4 `liftM` MGVector.basicUnsafeNew n
-  basicInitialize (MV_IPv4 v) = MGVector.basicInitialize v
-  basicUnsafeReplicate n x = MV_IPv4 `liftM` MGVector.basicUnsafeReplicate n x
-  basicUnsafeRead (MV_IPv4 v) i = MGVector.basicUnsafeRead v i
-  basicUnsafeWrite (MV_IPv4 v) i x = MGVector.basicUnsafeWrite v i x
-  basicClear (MV_IPv4 v) = MGVector.basicClear v
-  basicSet (MV_IPv4 v) x = MGVector.basicSet v x
-  basicUnsafeCopy (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeCopy v1 v2
-  basicUnsafeMove (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeMove v1 v2
-  basicUnsafeGrow (MV_IPv4 v) n = MV_IPv4 `liftM` MGVector.basicUnsafeGrow v n
-
-instance GVector.Vector UVector.Vector IPv4 where
-  {-# INLINE basicUnsafeFreeze #-}
-  {-# INLINE basicUnsafeThaw #-}
-  {-# INLINE basicLength #-}
-  {-# INLINE basicUnsafeSlice #-}
-  {-# INLINE basicUnsafeIndexM #-}
-  {-# INLINE elemseq #-}
-  basicUnsafeFreeze (MV_IPv4 v) = V_IPv4 `liftM` GVector.basicUnsafeFreeze v
-  basicUnsafeThaw (V_IPv4 v) = MV_IPv4 `liftM` GVector.basicUnsafeThaw v
-  basicLength (V_IPv4 v) = GVector.basicLength v
-  basicUnsafeSlice i n (V_IPv4 v) = V_IPv4 $ GVector.basicUnsafeSlice i n v
-  basicUnsafeIndexM (V_IPv4 v) i = GVector.basicUnsafeIndexM v i
-  basicUnsafeCopy (MV_IPv4 mv) (V_IPv4 v) = GVector.basicUnsafeCopy mv v
-  elemseq _ = seq
-
-data instance MUVector.MVector s IPv4Range
-    = MV_IPv4Range {-# UNPACK #-} !Int !(MUVector.MVector s IPv4)
-                                       !(MUVector.MVector s Word8)
-data instance UVector.Vector IPv4Range
-    = V_IPv4Range {-# UNPACK #-} !Int !(UVector.Vector IPv4)
-                                      !(UVector.Vector Word8)
-instance UVector.Unbox IPv4Range
-instance MGVector.MVector MUVector.MVector IPv4Range where
-  {-# INLINE basicLength  #-}
-  basicLength (MV_IPv4Range n_ as bs) = n_
-  {-# INLINE basicUnsafeSlice  #-}
-  basicUnsafeSlice i_ m_ (MV_IPv4Range n_ as bs)
-      = MV_IPv4Range m_ (MGVector.basicUnsafeSlice i_ m_ as)
-                        (MGVector.basicUnsafeSlice i_ m_ bs)
-  {-# INLINE basicOverlaps  #-}
-  basicOverlaps (MV_IPv4Range n_1 as1 bs1) (MV_IPv4Range n_2 as2 bs2)
-      = MGVector.basicOverlaps as1 as2
-        || MGVector.basicOverlaps bs1 bs2
-  {-# INLINE basicUnsafeNew  #-}
-  basicUnsafeNew n_
-      = do
-          as <- MGVector.basicUnsafeNew n_
-          bs <- MGVector.basicUnsafeNew n_
-          return $ MV_IPv4Range n_ as bs
-  {-# INLINE basicInitialize  #-}
-  basicInitialize (MV_IPv4Range _ as bs)
-      = do
-          MGVector.basicInitialize as
-          MGVector.basicInitialize bs
-  {-# INLINE basicUnsafeReplicate  #-}
-  basicUnsafeReplicate n_ (IPv4Range a b)
-      = do
-          as <- MGVector.basicUnsafeReplicate n_ a
-          bs <- MGVector.basicUnsafeReplicate n_ b
-          return $ MV_IPv4Range n_ as bs
-  {-# INLINE basicUnsafeRead  #-}
-  basicUnsafeRead (MV_IPv4Range n_ as bs) i_
-      = do
-          a <- MGVector.basicUnsafeRead as i_
-          b <- MGVector.basicUnsafeRead bs i_
-          return (IPv4Range a b)
-  {-# INLINE basicUnsafeWrite  #-}
-  basicUnsafeWrite (MV_IPv4Range n_ as bs) i_ (IPv4Range a b)
-      = do
-          MGVector.basicUnsafeWrite as i_ a
-          MGVector.basicUnsafeWrite bs i_ b
-  {-# INLINE basicClear  #-}
-  basicClear (MV_IPv4Range n_ as bs)
-      = do
-          MGVector.basicClear as
-          MGVector.basicClear bs
-  {-# INLINE basicSet  #-}
-  basicSet (MV_IPv4Range n_ as bs) (IPv4Range a b)
-      = do
-          MGVector.basicSet as a
-          MGVector.basicSet bs b
-  {-# INLINE basicUnsafeCopy  #-}
-  basicUnsafeCopy (MV_IPv4Range n_1 as1 bs1) (MV_IPv4Range n_2 as2
-                                                               bs2)
-      = do
-          MGVector.basicUnsafeCopy as1 as2
-          MGVector.basicUnsafeCopy bs1 bs2
-  {-# INLINE basicUnsafeMove  #-}
-  basicUnsafeMove (MV_IPv4Range n_1 as1 bs1) (MV_IPv4Range n_2 as2
-                                                               bs2)
-      = do
-          MGVector.basicUnsafeMove as1 as2
-          MGVector.basicUnsafeMove bs1 bs2
-  {-# INLINE basicUnsafeGrow  #-}
-  basicUnsafeGrow (MV_IPv4Range n_ as bs) m_
-      = do
-          as' <- MGVector.basicUnsafeGrow as m_
-          bs' <- MGVector.basicUnsafeGrow bs m_
-          return $ MV_IPv4Range (m_+n_) as' bs'
-instance GVector.Vector UVector.Vector IPv4Range where
-  {-# INLINE basicUnsafeFreeze  #-}
-  basicUnsafeFreeze (MV_IPv4Range n_ as bs)
-      = do
-          as' <- GVector.basicUnsafeFreeze as
-          bs' <- GVector.basicUnsafeFreeze bs
-          return $ V_IPv4Range n_ as' bs'
-  {-# INLINE basicUnsafeThaw  #-}
-  basicUnsafeThaw (V_IPv4Range n_ as bs)
-      = do
-          as' <- GVector.basicUnsafeThaw as
-          bs' <- GVector.basicUnsafeThaw bs
-          return $ MV_IPv4Range n_ as' bs'
-  {-# INLINE basicLength  #-}
-  basicLength (V_IPv4Range n_ as bs) = n_
-  {-# INLINE basicUnsafeSlice  #-}
-  basicUnsafeSlice i_ m_ (V_IPv4Range n_ as bs)
-      = V_IPv4Range m_ (GVector.basicUnsafeSlice i_ m_ as)
-                       (GVector.basicUnsafeSlice i_ m_ bs)
-  {-# INLINE basicUnsafeIndexM  #-}
-  basicUnsafeIndexM (V_IPv4Range n_ as bs) i_
-      = do
-          a <- GVector.basicUnsafeIndexM as i_
-          b <- GVector.basicUnsafeIndexM bs i_
-          return (IPv4Range a b)
-  {-# INLINE basicUnsafeCopy  #-}
-  basicUnsafeCopy (MV_IPv4Range n_1 as1 bs1) (V_IPv4Range n_2 as2
-                                                              bs2)
-      = do
-          GVector.basicUnsafeCopy as1 as2
-          GVector.basicUnsafeCopy bs1 bs2
-  {-# INLINE elemseq  #-}
-  elemseq _ (IPv4Range a b)
-      = GVector.elemseq (undefined :: UVector.Vector a) a
-        . GVector.elemseq (undefined :: UVector.Vector b) b
-
-
-newtype instance MUVector.MVector s Mac
-    = MV_Mac (MUVector.MVector s Word48)
+import Net.IPv6 (IPv6(..))
+import Net.IPv4 (IPv4(..))
+import Net.IPv4.Range (IPv4Range(..))
+import Net.IP (IP(..))
+import Net.Mac (Mac(..),MacCodec(..),MacGrouping(..))
 
-newtype instance UVector.Vector Mac
-    = V_Mac (UVector.Vector Word48)
diff --git a/test/ArbitraryInstances.hs b/test/ArbitraryInstances.hs
deleted file mode 100644
--- a/test/ArbitraryInstances.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-{-# LANGUAGE StandaloneDeriving         #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module ArbitraryInstances where
-
--- Orphan instances that are needed to make QuickCheck work.
-
-import Net.Types (IPv4(..),IPv4Range(..),Mac(..),MacGrouping(..),MacCodec(..))
-import Test.QuickCheck (Arbitrary(..),oneof,Gen,elements)
-import Data.Word
-import Data.Word.Synthetic
-
-deriving instance Arbitrary IPv4
-deriving instance Arbitrary Mac
-
--- This instance can generate masks that exceed the recommended
--- length of 32.
-instance Arbitrary IPv4Range where
-  arbitrary = fmap fromTuple arbitrary
-    where fromTuple (a,b) = IPv4Range a b
-
-instance Arbitrary Word48 where
-  arbitrary = fromIntegral <$> (arbitrary :: Gen Word64)
-
-instance Arbitrary MacCodec where
-  arbitrary = MacCodec <$> arbitrary <*> arbitrary
-
-instance Arbitrary MacGrouping where
-  arbitrary = oneof
-    [ MacGroupingPairs <$> arbitraryMacSeparator
-    , MacGroupingTriples <$> arbitraryMacSeparator
-    , MacGroupingQuadruples <$> arbitraryMacSeparator
-    , pure MacGroupingNoSeparator
-    ]
-
-arbitraryMacSeparator :: Gen Char
-arbitraryMacSeparator = elements [':','-','.','_']
-
diff --git a/test/Bench.hs b/test/Bench.hs
--- a/test/Bench.hs
+++ b/test/Bench.hs
@@ -9,17 +9,15 @@
 import Data.Word
 import Data.ByteString (ByteString)
 import Control.Monad.ST
-import qualified Data.Text                as Text
-import qualified Data.ByteString.Char8    as BC8
-import qualified Data.ByteString          as ByteString
-import qualified Data.ByteString.Unsafe   as ByteString
-import qualified Data.Text.Lazy           as LText
-import qualified Data.Text.Lazy.Builder   as TBuilder
-import qualified Data.Text.Array          as TArray
-import qualified Net.IPv4.Text            as IPv4_Text
-import qualified Net.Mac.Text             as MacText
-import qualified Net.Mac.ByteString.Char8 as MacByteString
-import qualified Net.Mac                  as Mac
+import qualified Data.Text as Text
+import qualified Data.ByteString.Char8 as BC8
+import qualified Data.ByteString as ByteString
+import qualified Data.ByteString.Unsafe as ByteString
+import qualified Data.Text.Lazy as LText
+import qualified Data.Text.Lazy.Builder as TBuilder
+import qualified Data.Text.Array as TArray
+import qualified Net.Mac as Mac
+import qualified Net.IPv4 as IPv4
 
 import qualified Naive
 import qualified IPv4Text1
@@ -27,9 +25,7 @@
 import qualified IPv4ByteString1
 import qualified IPv4DecodeText1
 import qualified IPv4DecodeText2
-import qualified IPv4TextVariableBuilder
-
-import qualified Net.IPv4.ByteString.Char8 as NIPBS
+-- import qualified IPv4TextVariableBuilder
 
 main :: IO ()
 main = do
@@ -38,24 +34,24 @@
       mac = Mac.fromOctets 0xFA 0xBB 0x43 0xA1 0x22 0x09
   defaultMain
     [ bgroup "Mac to Text"
-      [ bench "Current Implementation, pairs" $ whnf MacText.encode mac
+      [ bench "Current Implementation, pairs" $ whnf Mac.encode mac
       , bench "Current Implementation, no separator"
-          $ whnf (MacText.encodeWith (MacCodec MacGroupingNoSeparator True)) mac
+          $ whnf (Mac.encodeWith (MacCodec MacGroupingNoSeparator True)) mac
       , bench "Current Implementation, quads"
-          $ whnf (MacText.encodeWith (MacCodec (MacGroupingQuadruples '-') True)) mac
+          $ whnf (Mac.encodeWith (MacCodec (MacGroupingQuadruples '-') True)) mac
       , bench "Current Implementation, triples"
-          $ whnf (MacText.encodeWith (MacCodec (MacGroupingQuadruples '.') False)) mac
+          $ whnf (Mac.encodeWith (MacCodec (MacGroupingQuadruples '.') False)) mac
       ]
     , bgroup "Mac to ByteString"
-      [ bench "Current Implementation, pairs" $ whnf MacByteString.encode mac
+      [ bench "Current Implementation, pairs" $ whnf Mac.encodeUtf8 mac
       , bench "Current Implementation, no separator"
-          $ whnf (MacByteString.encodeWith (MacCodec MacGroupingNoSeparator True)) mac
+          $ whnf (Mac.encodeWithUtf8 (MacCodec MacGroupingNoSeparator True)) mac
       ]
     , bgroup "IPv4 to Text"
       [ bench "Naive" $ whnf Naive.encodeText ipAddr
       , bench "Text Builder" $ whnf IPv4Text2.encode ipAddr
       , bench "Preallocated" $ whnf IPv4Text1.encode ipAddr
-      , bench "Variable Builder" $ whnf IPv4TextVariableBuilder.encode ipAddr
+      -- , bench "Variable Builder" $ whnf IPv4TextVariableBuilder.encode ipAddr
       ]
     , bgroup "IPv4 from Text"
       [ bench "Naive" $ whnf Naive.decodeText ipText
@@ -65,6 +61,6 @@
     , bgroup "IPv4 to ByteString"
       [ bench "Naive" $ whnf Naive.encodeByteString ipAddr
       , bench "Preallocated: No Lookup Tables" $ whnf IPv4ByteString1.encode ipAddr
-      , bench "Preallocated" $ whnf NIPBS.encode ipAddr
+      , bench "Preallocated" $ whnf IPv4.encodeUtf8 ipAddr
       ]
     ]
diff --git a/test/Doctests.hs b/test/Doctests.hs
--- a/test/Doctests.hs
+++ b/test/Doctests.hs
@@ -3,5 +3,6 @@
 main :: IO ()
 main = doctest
   [ "src/Net/IPv4.hs"
+  , "src/Net/IPv6.hs"
   , "src/Net/IPv4/Range.hs"
   ]
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -1,39 +1,35 @@
+{-# LANGUAGE StandaloneDeriving         #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
+
 module Main (main) where
 
 import Naive
-import Data.List                            (intercalate)
-import Test.QuickCheck                      (Gen, Arbitrary(..), choose)
-import Test.Framework                       (defaultMain, testGroup, Test)
+import Test.Framework (defaultMain, testGroup, Test)
 import Test.Framework.Providers.QuickCheck2 (testProperty)
-import Test.HUnit                           (Assertion,(@?=))
-import Numeric                              (showHex)
-import Test.QuickCheck.Property             (failed,succeeded,Result(..))
-import Data.Word
+import Test.QuickCheck (Arbitrary(..),oneof,Gen,elements)
+import Test.HUnit (Assertion,(@?=))
+import Numeric (showHex)
+import Test.QuickCheck.Property (failed,succeeded,Result(..))
 import Data.Bifunctor
 import qualified Test.Framework.Providers.HUnit as PH
 
-import Net.Types (IPv4(..),IPv4Range(..),Mac(..),IPv6(..))
+import Net.Types (IPv4(..),IPv4Range(..),Mac(..),IPv6(..),MacGrouping(..),MacCodec(..))
 import qualified Data.Text as Text
 import qualified Data.ByteString.Char8 as BC8
 import qualified Net.IPv4 as IPv4
 import qualified Net.IPv6 as IPv6
 import qualified Net.IPv4.Range as IPv4Range
-import qualified Net.IPv4.Text as IPv4Text
-import qualified Net.IPv6.Text as IPv6Text
-import qualified Net.IPv4.ByteString.Char8 as IPv4ByteString
 import qualified Net.Mac as Mac
-import qualified Net.Mac.Text as MacText
-import qualified Net.Mac.ByteString.Char8 as MacByteString
 
 import qualified Data.Attoparsec.Text as AT
 import qualified Data.Attoparsec.ByteString as AB
 
-import ArbitraryInstances ()
-import qualified Naive
 import qualified IPv4Text1
 import qualified IPv4Text2
 import qualified IPv4ByteString1
-import qualified IPv4TextVariableBuilder
+-- import qualified IPv4TextVariableBuilder
 
 main :: IO ()
 main = defaultMain tests
@@ -43,17 +39,17 @@
   [ testGroup "Encoding and Decoding"
     [ testGroup "Currently used IPv4 encode/decode" $
       [ testProperty "Isomorphism"
-          $ propEncodeDecodeIso IPv4Text.encode IPv4Text.decode
+          $ propEncodeDecodeIso IPv4.encode IPv4.decode
       , PH.testCase "Decode an IP" testIPv4Decode
       ] ++ testDecodeFailures
     , testGroup "Currently used MAC Text encode/decode"
       [ testProperty "Isomorphism"
-          $ propEncodeDecodeIsoSettings MacText.encodeWith MacText.decodeWith
+          $ propEncodeDecodeIsoSettings Mac.encodeWith Mac.decodeWith
       , PH.testCase "Encode a MAC Address" testMacEncode
       ]
     , testGroup "Currently used MAC ByteString encode/decode"
       [ testProperty "Isomorphism"
-          $ propEncodeDecodeIsoSettings MacByteString.encodeWith MacByteString.decodeWith
+          $ propEncodeDecodeIsoSettings Mac.encodeWithUtf8 Mac.decodeWithUtf8
       , PH.testCase "Lenient Decoding" testLenientMacByteStringParser
       ]
     , testGroup "Naive IPv4 encode/decode"
@@ -64,10 +60,10 @@
       [ testProperty "Identical to Naive"
           $ propMatching IPv4Text2.encode Naive.encodeText
       ]
-    , testGroup "Variable Text Builder IPv4 Text encode/decode"
-      [ testProperty "Identical to Naive"
-          $ propMatching IPv4TextVariableBuilder.encode Naive.encodeText
-      ]
+    -- , testGroup "Variable Text Builder IPv4 Text encode/decode"
+    --   [ testProperty "Identical to Naive"
+    --       $ propMatching IPv4TextVariableBuilder.encode Naive.encodeText
+    --   ]
     , testGroup "Raw byte array IPv4 Text encode/decode"
       [ testProperty "Identical to Naive"
           $ propMatching IPv4Text1.encode Naive.encodeText
@@ -78,7 +74,7 @@
       ]
     , testGroup "Raw byte array (with lookup table) IPv4 ByteString encode/decode"
       [ testProperty "Identical to Naive"
-          $ propMatching IPv4ByteString.encode Naive.encodeByteString
+          $ propMatching IPv4.encodeUtf8 Naive.encodeByteString
       ]
     , testGroup "IPv4 encode/decode"
       [ PH.testCase "Parser Test Cases" testIPv4Parser
@@ -132,7 +128,7 @@
 propRangeSelf r = IPv4Range.member (ipv4RangeBase r) r == True
 
 testIPv4Decode :: Assertion
-testIPv4Decode = IPv4Text.decode (Text.pack "124.222.255.0")
+testIPv4Decode = IPv4.decode (Text.pack "124.222.255.0")
              @?= Just (IPv4.fromOctets 124 222 255 0)
 
 testLenientMacByteStringParser :: Assertion
@@ -144,7 +140,7 @@
   where
   go a b c d e f str =
     Just (HexMac (Mac.fromOctets a b c d e f))
-    @?= fmap HexMac (MacByteString.decodeLenient (BC8.pack str))
+    @?= fmap HexMac (Mac.decodeUtf8 (BC8.pack str))
 
 testIPv4Parser :: Assertion
 testIPv4Parser = do
@@ -154,7 +150,7 @@
   go a b c d str =
     Right (IPv4.fromOctets a b c d)
     @?= (AB.parseOnly
-          (IPv4ByteString.parser <* AT.endOfInput)
+          (IPv4.parserUtf8 <* AT.endOfInput)
           (BC8.pack str)
         )
 
@@ -184,7 +180,7 @@
     Right (HexIPv6 (IPv6.fromWord16s a b c d e f g h))
     @?= fmap HexIPv6
       (AT.parseOnly
-        (IPv6Text.parser <* AT.endOfInput)
+        (IPv6.parser <* AT.endOfInput)
         (Text.pack str)
       )
 
@@ -198,7 +194,7 @@
     Left ()
     @?= bimap (\_ -> ()) HexIPv6
       (AT.parseOnly
-        (IPv6Text.parser <* AT.endOfInput)
+        (IPv6.parser <* AT.endOfInput)
         (Text.pack str)
       )
 
@@ -234,8 +230,8 @@
 
    where
    roundTripsTo s sExpected =
-     case AT.parseOnly (IPv6Text.parser <* AT.endOfInput) (Text.pack s) of
-        Right result -> IPv6Text.encode result @?= Text.pack sExpected
+     case AT.parseOnly (IPv6.parser <* AT.endOfInput) (Text.pack s) of
+        Right result -> IPv6.encode result @?= Text.pack sExpected
         Left failMsg -> fail failMsg -- parse shouldn't fail here
 
 textBadIPv4 :: [String]
@@ -252,10 +248,10 @@
 
 testDecodeFailures :: [Test]
 testDecodeFailures = flip map textBadIPv4 $ \str ->
-  PH.testCase ("Should fail to decode [" ++ str ++ "]") $ IPv4Text.decode (Text.pack str) @?= Nothing
+  PH.testCase ("Should fail to decode [" ++ str ++ "]") $ IPv4.decode (Text.pack str) @?= Nothing
 
 testMacEncode :: Assertion
-testMacEncode = MacText.encode (Mac.fromOctets 0xFF 0x00 0xAB 0x12 0x99 0x0F)
+testMacEncode = Mac.encode (Mac.fromOctets 0xFF 0x00 0xAB 0x12 0x99 0x0F)
             @?= Text.pack "ff:00:ab:12:99:0f"
 
 failure :: String -> Result
@@ -264,7 +260,7 @@
   , theException = Nothing
   }
 
-newtype HexMac = HexMac { getHexMac :: Mac }
+newtype HexMac = HexMac Mac
   deriving (Eq)
 
 instance Show HexMac where
@@ -278,7 +274,7 @@
         . showHex f
 
 
-newtype HexIPv6 = HexIPv6 { getHexIPv6 :: IPv6 }
+newtype HexIPv6 = HexIPv6 IPv6
   deriving (Eq)
 
 instance Show HexIPv6 where
@@ -293,9 +289,34 @@
         . showHex g . showChar ':'
         . showHex h
 
-newtype Hex a = Hex { getHex :: a }
 
--- instance (Integral a, Show a) => Show (Hex a) where
---   show = showHex . getHex
+deriving instance Arbitrary IPv4
+instance Arbitrary Mac where
+  arbitrary = Mac.fromOctets
+    <$> arbitrary
+    <*> arbitrary
+    <*> arbitrary
+    <*> arbitrary
+    <*> arbitrary
+    <*> arbitrary
 
+-- This instance can generate masks that exceed the recommended
+-- length of 32.
+instance Arbitrary IPv4Range where
+  arbitrary = fmap fromTuple arbitrary
+    where fromTuple (a,b) = IPv4Range a b
+
+instance Arbitrary MacCodec where
+  arbitrary = MacCodec <$> arbitrary <*> arbitrary
+
+instance Arbitrary MacGrouping where
+  arbitrary = oneof
+    [ MacGroupingPairs <$> arbitraryMacSeparator
+    , MacGroupingTriples <$> arbitraryMacSeparator
+    , MacGroupingQuadruples <$> arbitraryMacSeparator
+    , pure MacGroupingNoSeparator
+    ]
+
+arbitraryMacSeparator :: Gen Char
+arbitraryMacSeparator = elements [':','-','.','_']
 
