diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/TNET.hs b/TNET.hs
new file mode 100644
--- /dev/null
+++ b/TNET.hs
@@ -0,0 +1,296 @@
+{-# LANGUAGE OverloadedStrings, FlexibleInstances, TypeSynonymInstances,
+             OverlappingInstances #-}
+-- | 'TNET' is a library that implements the TNET serialization protocol
+-- to be used for PGI
+-- (<http://code.google.com/p/polyweb/source/browse/doc/PGI.txt>)
+-- applications. The TNET protocol
+-- (<http://tnetstrings.org>) is designed to be simple to implement in
+-- any language, please look at the README for the changes to the
+-- original tnetstrings spec.
+module TNET
+  (
+  -- * TNET Parser
+    tnetParser
+  -- * Classes
+  , TNET(..)
+  -- * Types
+  , TValue
+  -- * TNET serialization functions
+  , decode
+  , encode
+  -- * Helpers for defining TNET datatypes
+  , dict
+  , (.=)
+  , (.:)
+  ) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as B8
+import qualified Data.ByteString.UTF8 as U
+
+import Data.Attoparsec as P
+import Data.Attoparsec.Char8 (char8, endOfLine)
+import qualified Data.Attoparsec.Char8 as P8
+
+import Control.Applicative
+import Data.Word (Word8)
+import Data.Char
+import Control.Arrow (second)
+
+-- | A 'TValue' represents a raw TNET object. TNET values
+-- are one of the following types:
+--
+--  * a string of bytes
+--
+--  * a UTF-8 encoded string
+--
+--  * an integer
+--
+--  * a floating point number
+--
+--  * a boolean
+--
+--  * null
+--
+--  * a dictionary type
+--
+--  * a list of TValues
+data TValue = TBytes ByteString
+            | TString String
+            | TInteger Integer
+            | TFloat Double
+            | TBool Bool
+            | TNull
+            | TDict [(String, TValue)]
+            | TList [TValue]
+            deriving (Eq, Show)
+
+-- | Used to create a TNET dictionary from
+-- TNET values. Meant to be used with the
+-- '.=' operator as in the following example:
+--
+-- > myDict = dict [ "a" .= 5
+-- >               , "is_dict" .= True
+-- >               ]
+dict :: [(String, TValue)] -> TValue
+dict = TDict
+
+(.=) :: (TNET a) => String -> a -> (String, TValue)
+key .= val = (key, toTNET val)
+
+-- | Helper function to extract TNET values from
+-- a TNET dictionary. Meant to be used as in the
+-- following example:
+--
+-- > data Person = Person {
+-- >                 name :: String
+-- >               , age  :: Integer
+-- >               }
+-- > personFromDict :: TValue -> Maybe Person
+-- > personFromDict tdict = do
+-- >   name <- tdict .: "name"
+-- >   age  <- tdict .: "age"
+-- >   return $ Person name age
+(.:) :: TNET a => TValue -> String -> Maybe a
+(TDict xs) .: key = let lkup = lookup key xs in
+                    case lkup of
+                      Nothing -> fromTNET TNull
+                      Just v  -> fromTNET v
+_ .: _            = Nothing
+
+-- | The 'TNET' typeclass represents types that
+-- can be encoded and decoded in the TNET format.
+-- An example instance:
+--
+-- > data Person = Person {
+-- >                 name :: String
+-- >               , age  :: Integer
+-- >               }
+-- > instance TNET Person where
+-- >   toTNET (Person n a) = dict [ "name" .= n
+-- >                              , "age"  .= a
+-- >                              ]
+-- >   fromTNET tval = do
+-- >     n <- tval .: "name"
+-- >     a <- tval .: "age"
+-- >     return $ Person n a
+class TNET a where
+  toTNET :: a -> TValue
+  fromTNET :: TValue -> Maybe a
+
+instance TNET () where
+  toTNET = const TNull
+  fromTNET TNull = Just ()
+  fromTNET _     = Nothing
+
+instance TNET TValue where
+  toTNET = id
+  fromTNET = Just
+
+instance TNET Integer where
+  toTNET = TInteger
+  fromTNET (TInteger n) = Just n
+  fromTNET _            = Nothing
+
+instance TNET Double where
+  toTNET = TFloat
+  fromTNET (TFloat n) = Just n
+  fromTNET _          = Nothing
+
+instance TNET Bool where
+  toTNET = TBool
+  fromTNET (TBool b) = Just b
+  fromTNET _         = Nothing
+
+instance TNET a => TNET [(String, a)] where
+  toTNET = TDict . map (second toTNET)
+  fromTNET (TDict d) = mapM (f . second fromTNET) d
+                       where f (s, Just x) = Just (s, x)
+                             f _           = Nothing
+  fromTNET _         = Nothing
+
+instance TNET String where
+  toTNET = TString
+  fromTNET (TString s) = Just s
+  fromTNET _           = Nothing
+
+instance TNET Char where
+  toTNET = TString . (:[])
+  fromTNET (TString [c]) = Just c
+  fromTNET _             = Nothing
+
+instance TNET ByteString where
+  toTNET = TBytes
+  fromTNET (TBytes b) = Just b
+  fromTNET _          = Nothing
+
+instance TNET a => TNET (Maybe a) where
+  toTNET = maybe TNull toTNET
+  fromTNET TNull = Just Nothing
+  fromTNET x     = case fromTNET x of
+                     Just val -> Just $ Just val
+                     Nothing  -> Nothing
+
+instance TNET a => TNET [a] where
+  toTNET = TList . map toTNET
+  fromTNET (TList xs) = mapM fromTNET xs
+  fromTNET _          = Nothing
+
+dataFromTValue :: TValue -> ByteString
+dataFromTValue (TBytes bs)   = bs
+dataFromTValue (TString s)   = U.fromString s
+dataFromTValue (TBool True)  = "true"
+dataFromTValue (TBool False) = "false"
+dataFromTValue TNull         = ""
+dataFromTValue (TInteger n)  = (B8.pack . show) n
+dataFromTValue (TFloat n)    = (B8.pack . show) n
+dataFromTValue (TList l)     = B8.concat $ map dumpTNET l
+dataFromTValue (TDict m)     = B8.concat $ map dumpPair m
+
+typeFromTValue :: TValue -> ByteString
+typeFromTValue (TBytes _)   = ","
+typeFromTValue (TString _)  = "$"
+typeFromTValue (TBool _)    = "!"
+typeFromTValue TNull        = "~"
+typeFromTValue (TInteger _) = "#"
+typeFromTValue (TFloat _)   = "^"
+typeFromTValue (TList _)    = "]"
+typeFromTValue (TDict _)    = "}"
+
+dumpPair :: (String, TValue) -> ByteString
+dumpPair (key, value) =
+  dumpTNET (TString key) `B8.append` dumpTNET value
+
+dumpTNET :: TValue -> ByteString
+dumpTNET tval = let t_data = dataFromTValue tval
+                    t_size = B8.pack . show $ B8.length t_data
+                    t_type = typeFromTValue tval
+                in B8.concat [t_size, ":", t_data, t_type]
+
+-- | Encode a Haskell value into the TNET format.
+-- Some examples:
+--
+-- >>> encode 5
+-- "1:5#"
+--
+-- >>> encode "Hello"
+-- "5:Hello$"
+--
+-- >>> encode (-12.3)
+-- "5:-12.3^"
+--
+-- >>> encode ()
+-- "0:~"
+encode :: TNET a => a -> ByteString
+encode = dumpTNET . toTNET
+
+-- | Decode a TNET format bytestring into a Haskell
+-- value. An explicit type annotation may be needed
+-- if the type of the decoded value can not be
+-- determined:
+--
+-- >>> decode "0:~" :: Maybe ()
+-- Just ()
+--
+-- >>> decode "0:~" :: Maybe (Maybe String)
+-- Just Nothing
+--
+-- >>> decode "1:5#" :: Maybe Integer
+-- Just 5
+--
+-- > let x = decode "4:true!" in
+-- > case x of
+-- >   Just True  -> putStrLn "got true!"
+-- >   Just False -> putStrLn "got false!"
+-- >   Nothing    -> putStrLn "error decoding"
+decode :: TNET a => ByteString -> Maybe a
+decode s = case parse tnetParser s of
+             Done _ v -> fromTNET v
+             _        -> Nothing
+
+tnetParser :: Parser TValue
+tnetParser = do
+  size <- P8.decimal <* char8 ':'
+  t_data <- P.take size
+  t_type <- P8.satisfy $ P8.inClass ",$#^!~}]?"
+  case t_type of
+    ',' -> return $ TBytes t_data
+    '$' -> return $ TString $ U.toString t_data
+    '~' -> if size == 0 then return TNull
+                        else empty
+    '!' -> case parseOnly p_bool t_data of
+             Left err -> fail "failed to parse bool"
+             Right b -> return b
+    '?' -> case parseOnly p_smallbool t_data of
+             Left err -> fail "failed to parse bool"
+             Right b -> return b
+    '#' -> case parseOnly (P8.signed P8.decimal) t_data of
+             Left err -> fail "failed to parse integer"
+             Right n -> return $ TInteger n
+    '^' -> case parseOnly (P8.signed P8.double) t_data of
+             Left err -> fail "failed to parse float"
+             Right n -> return $ TFloat n
+    '}' -> case parseOnly (many p_pair) t_data of
+             Left err -> fail "failed to parse dict"
+             Right m -> return $ TDict m
+    ']' -> case parseOnly (many tnetParser) t_data of
+             Left err -> fail "failed to parse list"
+             Right m -> return $ TList m
+    _   -> empty
+
+p_bool :: Parser TValue
+p_bool = (return (TBool True) <* P8.string "true")
+     <|> return (TBool False)
+
+p_smallbool :: Parser TValue
+p_smallbool = (return (TBool True) <* P8.string "t")
+     <|> return (TBool False)
+
+p_pair :: Parser (String, TValue)
+p_pair = do
+  key <- tnetParser
+  value <- tnetParser
+  case key of
+    TString s -> return (s, value)
+    TBytes  s -> return (U.toString s, value)
+    _         -> fail "key must be string"
diff --git a/tnet.cabal b/tnet.cabal
new file mode 100644
--- /dev/null
+++ b/tnet.cabal
@@ -0,0 +1,17 @@
+Name:          tnet
+Version:       0.0.1
+Synopsis:      Library for encoding/decoding TNET strings for PGI
+Description:
+  Provides an API to encode/decode Haskell datatypes into the
+  TNET protocol.
+Author:        Harry Terkelsen
+Maintainer:    het32@cornell.edu
+Cabal-Version: >= 1.2
+Build-Type:    Simple
+Category:      Web
+License:       OtherLicense
+License-File:  LICENSE
+library
+  Exposed-Modules: TNET
+  Build-Depends:   base >= 4.0 && < 5, attoparsec >= 0.10, bytestring >= 0.9,
+                   utf8-string >= 0.3
