diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Pavel Yakovlev (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Pavel Yakovlev nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
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/mmtf.cabal b/mmtf.cabal
new file mode 100644
--- /dev/null
+++ b/mmtf.cabal
@@ -0,0 +1,47 @@
+name:                mmtf
+version:             0.1.0.0
+synopsis:            Macromolecular Transmission Format implementation
+description:         Haskell implementation of MMTF biological structure format.
+homepage:            https://github.com/zmactep/mmtf#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Pavel Yakovlev
+maintainer:          pavel@yakovlev.me
+copyright:           (c) 2017, Pavel Yakovlev
+category:            Bioinformatics
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Bio.MMTF
+  other-modules:       Bio.MMTF.Type
+                     , Bio.MMTF.MessagePack
+                     , Bio.MMTF.Decode
+                     , Bio.MMTF.Decode.Codec
+                     , Bio.MMTF.Decode.MessagePack
+  build-depends:       base >= 4.8 && < 5
+                     , data-msgpack >= 0.0.9 && < 0.1
+                     , text >= 1.2.2.1 && < 1.3
+                     , bytestring >= 0.10.8.1 && < 0.11
+                     , binary >= 0.8.3.0 && < 0.9
+                     , containers >= 0.5.7.1 && < 0.6
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+test-suite mmtf-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:       base
+                     , mmtf
+                     , QuickCheck >= 2.9.2 && < 2.10
+                     , hspec >= 2.4.1 && < 2.5
+                     , bytestring
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/zmactep/mmtf
diff --git a/src/Bio/MMTF.hs b/src/Bio/MMTF.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MMTF.hs
@@ -0,0 +1,14 @@
+module Bio.MMTF
+  ( module T
+  , decode
+  ) where
+
+import           Bio.MMTF.MessagePack ()
+import           Bio.MMTF.Type        as T
+
+import           Data.ByteString.Lazy (ByteString)
+import           Data.MessagePack     (unpack)
+
+-- |Decodes a 'ByteString' to 'MMTF'
+decode :: Monad m => ByteString -> m MMTF
+decode = unpack
diff --git a/src/Bio/MMTF/Decode.hs b/src/Bio/MMTF/Decode.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MMTF/Decode.hs
@@ -0,0 +1,107 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Bio.MMTF.Decode where
+
+import           Bio.MMTF.Decode.Codec
+import           Bio.MMTF.Decode.MessagePack
+import           Bio.MMTF.Type
+
+import           Control.Monad               ((>=>))
+import           Data.Map.Strict             (Map)
+import           Data.MessagePack            (Object)
+import           Data.Text                   (Text)
+
+-- |Parses format data from ObjectMap
+formatData :: Monad m => Map Text Object -> m FormatData
+formatData mp = do v <- atP mp "mmtfVersion" asStr
+                   p <- atP mp "mmtfProducer" asStr
+                   pure $ FormatData v p
+
+-- |Parses model data from ObjectMap
+modelData :: Monad m => Map Text Object -> m ModelData
+modelData mp = atP mp "chainsPerModel" asIntList >>= return . ModelData
+
+-- |Parses chain data from ObjectMap
+chainData :: Monad m => Map Text Object -> m ChainData
+chainData mp = do gpc <- atP mp "groupsPerChain" asIntList
+                  cil <- codec5 . parseBinary <$> atP mp "chainIdList" asBinary
+                  cnl <- (codec5 . parseBinary <$>) <$> atPM mp "chainNameList" asBinary
+                  pure $ ChainData gpc cil cnl
+
+-- |Parses atom data from ObjectMap
+atomData :: Monad m => Map Text Object -> m AtomData
+atomData mp = do ail' <- (codec8 . parseBinary <$>) <$> atPM mp "atomIdList" asBinary
+                 all' <- (codec6 . parseBinary <$>) <$> atPM mp "altLocList" asBinary
+                 bfl' <- (codec10 . parseBinary <$>) <$> atPM mp "bFactorList" asBinary
+                 xcl' <- codec10 . parseBinary <$> atP mp "xCoordList" asBinary
+                 ycl' <- codec10 . parseBinary <$> atP mp "yCoordList" asBinary
+                 zcl' <- codec10 . parseBinary <$> atP mp "zCoordList" asBinary
+                 ol' <-  (codec9 . parseBinary <$>) <$> atPM mp "occupancyList" asBinary
+                 pure $ AtomData ail' all' bfl' xcl' ycl' zcl' ol'
+
+-- |Parses group data from ObjectMap
+groupData :: Monad m => Map Text Object -> m GroupData
+groupData mp = do gl' <- atP mp "groupList" asObjectList >>= sequence . map (transformObjectMap >=> groupType)
+                  gtl' <- codec4 . parseBinary <$> atP mp "groupTypeList" asBinary
+                  gil' <- codec8 . parseBinary <$> atP mp "groupIdList" asBinary
+                  ssl' <- (map ssDec . codec2 . parseBinary <$>) <$> atPM mp "secStructList" asBinary
+                  icl' <- (codec6 . parseBinary <$>) <$> atPM mp "insCodeList" asBinary
+                  sil' <- (codec8 . parseBinary <$>) <$> atPM mp "sequenceIndexList" asBinary
+                  pure $ GroupData gl' gtl' gil' ssl' icl' sil'
+
+-- |Parses group type from ObjectMap
+groupType :: Monad m => Map Text Object -> m GroupType
+groupType mp = do fcl' <- atP mp "formalChargeList" asIntList
+                  anl' <- atP mp "atomNameList" asStrList
+                  el'  <- atP mp "elementList" asStrList
+                  bal' <- atP mp "bondAtomList" asIntList
+                  bol' <- atP mp "bondOrderList" asIntList
+                  gn'  <- atP mp "groupName" asStr
+                  slc' <- atP mp "singleLetterCode" asChar
+                  cct' <- atP mp "chemCompType" asStr
+                  pure $ GroupType fcl' anl' el' bal' bol' gn' slc' cct'
+
+-- |Parses structure data from ObjectMap
+structureData :: Monad m => Map Text Object -> m StructureData
+structureData mp = do ttl' <- atPM mp "title" asStr
+                      sid' <- atPM mp "structureId" asStr
+                      dd'  <- atPM mp "depositionDate" asStr
+                      rd'  <- atPM mp "releaseDate" asStr
+                      nb'  <- atP mp "numBonds" asInt
+                      na'  <- atP mp "numAtoms" asInt
+                      ng'  <- atP mp "numGroups" asInt
+                      nc'  <- atP mp "numChains" asInt
+                      nm'  <- atP mp "numModels" asInt
+                      sg'  <- atPM mp "spaceGroup" asStr
+                      uc'  <- (>>= ucDec) <$> atPM mp "unitCell" asFloatList
+                      nol' <- ((>>= asFloatList) <$>) <$> atPM mp "ncsOperatorList" asObjectList
+                      bal' <- (>>= sequence . map (transformObjectMap >=> bioAssembly)) <$> atPM mp "bioAssemblyList" asObjectList
+                      el'  <- (>>= sequence . map (transformObjectMap >=> entity)) <$> atPM mp "entityList" asObjectList
+                      res' <- atPM mp "resolution" asFloat
+                      rf'  <- atPM mp "rFree" asFloat
+                      rw'  <- atPM mp "rWork" asFloat
+                      em'  <- atPM mp "experimentalMethods" asStrList
+                      btl' <- (codec4 . parseBinary <$>) <$> atPM mp "bondAtomList" asBinary
+                      bol' <- (codec2 . parseBinary <$>) <$> atPM mp "bondOrderList" asBinary
+                      pure $ StructureData ttl' sid' dd' rd' nb' na' ng' nc' nm' sg' uc' nol'
+                                           bal' el' res' rf' rw' em' btl' bol'
+
+-- |Parses bio assembly data from ObjectMap
+bioAssembly :: Monad m => Map Text Object -> m Assembly
+bioAssembly mp = do nme' <- atP mp "name" asStr
+                    tlt' <- atP mp "transformList" asObjectList >>= sequence . map (transformObjectMap >=> transform)
+                    pure $ Assembly tlt' nme'
+
+-- |Parses transform data from ObjectMap
+transform :: Monad m => Map Text Object -> m Transform
+transform mp = do cil' <- atP mp "chainIndexList" asIntList
+                  mtx' <- atP mp "matrix" asFloatList
+                  pure $ Transform cil' mtx'
+
+-- |Parses entity data from ObjectMap
+entity :: Monad m => Map Text Object -> m Entity
+entity mp = do cil' <- atP mp "chainIndexList" asIntList
+               dsc' <- atP mp "description" asStr
+               tpe' <- atP mp "type" asStr
+               sqc' <- atP mp "sequence" asStr
+               pure $ Entity cil' dsc' tpe' sqc'
diff --git a/src/Bio/MMTF/Decode/Codec.hs b/src/Bio/MMTF/Decode/Codec.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MMTF/Decode/Codec.hs
@@ -0,0 +1,148 @@
+module Bio.MMTF.Decode.Codec where
+
+import           Data.Binary          (Binary, decode)
+import           Data.ByteString.Lazy (ByteString)
+import qualified Data.ByteString.Lazy as B (length, null, splitAt, unpack)
+import           Data.Char            (chr)
+import           Data.Int             (Int16, Int32, Int8)
+import           Data.List            (mapAccumL)
+import           Data.Text            (Text)
+import qualified Data.Text            as T (pack)
+
+import           Bio.MMTF.Type
+
+codecCommon :: Binary a => (ByteString -> a) -> Int -> ByteString -> [a]
+codecCommon  f th bs | B.null bs         = []
+                     | B.length bs < ith = error "Wrong number of bytes in bytestring"
+                     | otherwise         = let (start, rest) = B.splitAt ith bs
+                                           in f start : codecCommon f th rest
+  where ith = fromIntegral th
+
+data BinaryData = BD { binaryCodec  :: Int32
+                     , binaryLength :: Int32
+                     , binaryParam  :: Int32
+                     , binaryData   :: ByteString
+                     }
+
+-- |Parse useless header for binary data
+parseBinary :: ByteString -> BinaryData
+parseBinary bs = let (cdc, rest1) = B.splitAt 4 bs
+                     (lnh, rest2) = B.splitAt 4 rest1
+                     (prm, rest)  = B.splitAt 4 rest2
+                 in  BD (decode cdc) (decode lnh) (decode prm) rest
+
+-- |Interpret bytes as array of 32-bit floating-point numbers.
+codec1 :: BinaryData -> [Float]
+codec1 = codecCommon decode 4 . binaryData
+
+-- |Interpret bytes as array of 8-bit signed integers.
+codec2 :: BinaryData -> [Int8]
+codec2 = codecCommon decode 1 . binaryData
+
+-- |Interpret bytes as array of 16-bit signed integers.
+codec3 :: BinaryData -> [Int16]
+codec3 = codecCommon decode 2 . binaryData
+
+-- |Interpret bytes as array of 32-bit signed integers.
+codec4 :: BinaryData -> [Int32]
+codec4 = codecCommon decode 4 . binaryData
+
+-- |Interpret bytes as array of 8-bit unsigned integers, then iteratively
+-- consume length many bytes to form a string array.
+codec5 :: BinaryData -> [Text]
+codec5 bd = codecCommon decodeBytes (fromIntegral $ binaryParam bd) (binaryData bd)
+  where decodeBytes :: ByteString -> Text
+        decodeBytes bs = T.pack $ chr <$> filter (/=0) (fromIntegral <$> B.unpack bs)
+
+-- |Interpret bytes as array of 32-bit signed integers, then run-length
+-- decode into array of characters.
+codec6 :: BinaryData -> [Char]
+codec6 = map (chr . fromIntegral) . codec7
+
+-- |Interpret bytes as array of 32-bit signed integers, then run-length
+-- decode into array of 32-bit signed integers.
+codec7 :: BinaryData -> [Int32]
+codec7 = runLengthDec . codec4
+
+-- |Interpret bytes as array of 32-bit signed integers, then run-length
+-- decode into array of 32-bit signed integers, then delta decode into
+-- array of 32-bit signed integers.
+codec8 :: BinaryData -> [Int32]
+codec8 = deltaDec . codec7
+
+-- |Interpret bytes as array of 32-bit signed integers, then run-length
+-- decode into array of 32-bit signed integers, then integer decode into
+-- array of 32-bit floating-point numbers using the divisor parameter.
+codec9 :: BinaryData -> [Float]
+codec9 bd = integerDec (binaryParam bd) $ codec7 bd
+
+-- |Interpret bytes as array of 16-bit signed integers, then unpack into
+-- array of 32-bit integers, then delta decode into array of 32-bit
+-- integers, then integer decode into array of 32-bit floating-point
+-- numbers using the divisor parameter.
+codec10 :: BinaryData -> [Float]
+codec10 bd = integerDec (binaryParam bd) $ map fromIntegral $ deltaDec $ codec3 bd
+
+-- |Interpret bytes as array of 16-bit signed integers, then integer
+-- decode into array of 32-bit floating-point numbers using the divisor parameter.
+codec11 :: BinaryData -> [Float]
+codec11 bd = integerDec (binaryParam bd) $ map fromIntegral $ codec3 bd
+
+-- |Interpret bytes as array of 16-bit signed integers, then unpack into
+-- array of 32-bit signed integers, then integer decode into array
+-- of 32-bit floating-point numbers using the divisor parameter.
+codec12 :: BinaryData -> [Float]
+codec12 bd = integerDec (binaryParam bd) $ recIndexDec $ codec3 bd
+
+-- |Interpret array of bytes as array of 8-bit signed integers, then
+-- unpack into array of 32-bit signed integers, then integer decode into
+-- array of 32-bit floating-point numbers using the divisor parameter.
+codec13 :: BinaryData -> [Float]
+codec13 bd = integerDec (binaryParam bd) $ recIndexDec $ codec2 bd
+
+-- |Interpret bytes as array of 16-bit signed integers, then unpack
+-- into array of 32-bit signed integers.
+codec14 :: BinaryData -> [Int32]
+codec14 bd = recIndexDec $ codec3 bd
+
+-- |Interpret bytes as array of 8-bit signed integers, then unpack
+-- into array of 32-bit signed integers.
+codec15 :: BinaryData -> [Int32]
+codec15 bd = recIndexDec $ codec2 bd
+
+-- Decodings
+
+runLengthDec :: Integral a => [a] -> [a]
+runLengthDec [] = []
+runLengthDec [_] = error "List must have even length for run-length encoding"
+runLengthDec (x:l:xs) = (replicate (fromIntegral l) x) ++ runLengthDec xs
+
+deltaDec :: Num a => [a] -> [a]
+deltaDec = snd . mapAccumL (\x y -> (x+y,x+y)) 0
+
+recIndexDec :: (Integral a, Bounded a, Eq a) => [a] -> [Int32]
+recIndexDec [] = []
+recIndexDec xs = recIndexDecAcc 0 xs
+  where recIndexDecAcc :: (Integral a, Bounded a) => Int32 -> [a] -> [Int32]
+        recIndexDecAcc acc []     | acc /= 0  = [acc]
+                                  | otherwise = []
+        recIndexDecAcc acc (x:ys) | x > minBound && x < maxBound = fromIntegral x + acc : recIndexDecAcc 0 ys
+                                  | otherwise                    = recIndexDecAcc (fromIntegral x + acc) ys
+
+integerDec :: Integral a => a -> [a] -> [Float]
+integerDec divisor = map (\x -> fromIntegral x / fromIntegral divisor)
+
+ssDec :: Int8 -> SecondaryStructure
+ssDec n | n == 0    = PiHelix
+        | n == 1    = Bend
+        | n == 2    = AlphaHelix
+        | n == 3    = Extended
+        | n == 4    = ThreeTenHelix
+        | n == 5    = Bridge
+        | n == 6    = Turn
+        | n == 7    = Coil
+        | otherwise = Undefined
+
+ucDec :: Monad m => [Float] -> m UnitCell
+ucDec [a,b,c,d,e,f] = pure $ UnitCell a b c d e f
+ucDec _  = fail "Wrong list format for unit cell"
diff --git a/src/Bio/MMTF/Decode/MessagePack.hs b/src/Bio/MMTF/Decode/MessagePack.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MMTF/Decode/MessagePack.hs
@@ -0,0 +1,61 @@
+module Bio.MMTF.Decode.MessagePack where
+
+import           Data.ByteString.Lazy  (ByteString, fromStrict)
+import           Data.Map.Strict       (Map, fromList)
+import qualified Data.Map.Strict       as M (lookup)
+import           Data.MessagePack
+import           Data.Text             (Text)
+import qualified Data.Text             as T (unpack)
+
+transformObjectMap :: Monad m => Object -> m (Map Text Object)
+transformObjectMap (ObjectMap kv) = let mkPair :: Monad m => (Object, Object) -> m (Text, Object)
+                                        mkPair ((ObjectStr txt), v) = pure (txt, v)
+                                        mkPair _ = fail "Non-string key"
+                                    in  fromList <$> sequence (map mkPair kv)
+transformObjectMap _ = fail "Wrong MessagePack MMTF format"
+
+atP :: Monad m => Map Text Object -> Text -> (Object -> m a) -> m a
+atP m k conv =
+  case M.lookup k m of
+    Just x  -> conv x
+    Nothing -> fail $ "Required field '" ++ uk ++ "' was not found"
+  where uk = T.unpack k
+
+atPM :: Monad m => Map Text Object -> Text -> (Object -> m a) -> m (Maybe a)
+atPM m k conv = traverse conv $ M.lookup k m
+
+asStr :: Monad m => Object -> m Text
+asStr (ObjectStr s) = pure s
+asStr _             = fail "Not a string data"
+
+asChar :: Monad m => Object -> m Char
+asChar = (head . T.unpack <$>) . asStr
+
+asInt :: (Monad m, Integral a) => Object -> m a
+asInt (ObjectInt i)  = pure (fromIntegral i)
+asInt (ObjectWord w) = pure (fromIntegral w)
+asInt _              = fail "Not an int data"
+
+asFloat :: Monad m => Object -> m Float
+asFloat (ObjectFloat f) = pure f
+asFloat _               = fail "Not a float data"
+
+asIntList :: (Monad m, Integral a) => Object -> m [a]
+asIntList (ObjectArray l) = sequence $ map asInt l
+asIntList _               = fail "Not an array of ints data"
+
+asStrList :: Monad m => Object -> m [Text]
+asStrList (ObjectArray l) = sequence $ map asStr l
+asStrList _               = fail "Not an array of string data"
+
+asFloatList :: Monad m => Object -> m [Float]
+asFloatList (ObjectArray l) = sequence $ map asFloat l
+asFloatList _               = fail "Not an array of float data"
+
+asObjectList :: Monad m => Object -> m [Object]
+asObjectList (ObjectArray l) = pure l
+asObjectList _               = fail "Not an array data"
+
+asBinary :: Monad m => Object -> m ByteString
+asBinary (ObjectBin bs) = pure (fromStrict bs)
+asBinary _              = fail "Not a binary data"
diff --git a/src/Bio/MMTF/MessagePack.hs b/src/Bio/MMTF/MessagePack.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MMTF/MessagePack.hs
@@ -0,0 +1,20 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Bio.MMTF.MessagePack where
+
+import           Bio.MMTF.Decode
+import           Bio.MMTF.Decode.MessagePack
+import           Bio.MMTF.Type
+
+import           Data.MessagePack            (MessagePack (..))
+
+instance MessagePack MMTF where
+  toObject = undefined -- TODO: add MMTF encoding
+  fromObject obj = do mp <- transformObjectMap obj
+                      f <- formatData mp
+                      s <- structureData mp
+                      m <- modelData mp
+                      c <- chainData mp
+                      g <- groupData mp
+                      a <- atomData mp
+                      pure $ MMTF f s m c g a
diff --git a/src/Bio/MMTF/Type.hs b/src/Bio/MMTF/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/Bio/MMTF/Type.hs
@@ -0,0 +1,117 @@
+module Bio.MMTF.Type where
+
+import           Data.Int  (Int32, Int8)
+import           Data.Text (Text)
+
+data UnitCell = UnitCell { ucA     :: !Float
+                         , ucB     :: !Float
+                         , ucC     :: !Float
+                         , ucAlpha :: !Float
+                         , ucBeta  :: !Float
+                         , ucGamma :: !Float
+                         }
+  deriving (Show, Eq)
+
+data Transform = Transform { chainIndexList :: ![Int32]
+                           , matrix         :: ![Float]
+                           }
+  deriving (Show, Eq)
+
+data Assembly = Assembly { transformList :: ![Transform]
+                         , assemblyName  :: !Text
+                         }
+  deriving (Show, Eq)
+
+data Entity = Entity { entityChainIndexList :: ![Int32]
+                     , entityDescription    :: !Text
+                     , entityType           :: !Text
+                     , entitySequence       :: !Text
+                     }
+  deriving (Show, Eq)
+
+data GroupType = GroupType { gtFormalChargeList :: ![Int32]
+                           , gtAtomNameList     :: ![Text]
+                           , gtElementList      :: ![Text]
+                           , gtBondAtomList     :: ![Int32]
+                           , gtBondOrderList    :: ![Int32]
+                           , gtGroupName        :: !Text
+                           , gtSingleLetterCode :: !Char
+                           , gtChemCompType     :: !Text
+                           }
+  deriving (Show, Eq)
+
+data SecondaryStructure = PiHelix       -- 0
+                        | Bend          -- 1
+                        | AlphaHelix    -- 2
+                        | Extended      -- 3
+                        | ThreeTenHelix -- 4
+                        | Bridge        -- 5
+                        | Turn          -- 6
+                        | Coil          -- 7
+                        | Undefined     -- -1
+  deriving (Show, Eq)
+
+data FormatData = FormatData { mmtfVersion  :: !Text
+                             , mmtfProducer :: !Text
+                             }
+  deriving (Show, Eq)
+
+data StructureData = StructureData { title               :: !(Maybe Text)
+                                   , structureId         :: !(Maybe Text)
+                                   , depositionDate      :: !(Maybe Text)
+                                   , releaseDate         :: !(Maybe Text)
+                                   , numBonds            :: !Int32
+                                   , numAtoms            :: !Int32
+                                   , numGroups           :: !Int32
+                                   , numChains           :: !Int32
+                                   , numModels           :: !Int32
+                                   , spaceGroup          :: !(Maybe Text)
+                                   , unitCell            :: !(Maybe UnitCell)
+                                   , ncsOperatorList     :: !(Maybe [[Float]])
+                                   , bioAssemblyList     :: !(Maybe [Assembly])
+                                   , entityList          :: !(Maybe [Entity])
+                                   , resolution          :: !(Maybe Float)
+                                   , rFree               :: !(Maybe Float)
+                                   , rWork               :: !(Maybe Float)
+                                   , experimentalMethods :: !(Maybe [Text])
+                                   , bondAtomList        :: !(Maybe [Int32]) -- binary (type 4)
+                                   , bondOrderList       :: !(Maybe [Int8])  -- binary (type 2)
+                                   }
+  deriving (Show, Eq)
+
+data ModelData = ModelData { chainsPerModel :: ![Int32] }
+  deriving (Show, Eq)
+
+data ChainData = ChainData { groupsPerChain :: ![Int32]
+                           , chainIdList    :: ![Text] -- binary (type 5, length 4)
+                           , chainNameList  :: !(Maybe [Text]) -- binary (type 5, length 4)
+                           }
+  deriving (Show, Eq)
+
+data GroupData = GroupData { groupList         :: ![GroupType]
+                           , groupTypeList     :: ![Int32] -- binary (type 4)
+                           , groupIdList       :: ![Int32] -- binary (type 8)
+                           , secStructList     :: !(Maybe [SecondaryStructure]) -- binary (type 2)
+                           , insCodeList       :: !(Maybe [Char]) -- binary (type 6)
+                           , sequenceIndexList :: !(Maybe [Int32]) -- binary (type 8)
+                           }
+  deriving (Show, Eq)
+
+data AtomData = AtomData { atomIdList    :: !(Maybe [Int32]) -- binary (type 8)
+                         , altLocList    :: !(Maybe [Char]) -- binary (type 6)
+                         , bFactorList   :: !(Maybe [Float]) -- binary (type 10)
+                         , xCoordList    :: ![Float] -- binary (type 10)
+                         , yCoordList    :: ![Float] -- binary (type 10)
+                         , zCoordList    :: ![Float] -- binary (type 10)
+                         , occupancyList :: !(Maybe [Float]) -- binary (type 9)
+                         }
+  deriving (Show, Eq)
+
+data MMTF = MMTF { format    :: !FormatData
+                 , structure :: !StructureData
+                 , model     :: !ModelData
+                 , chain     :: !ChainData
+                 , group     :: !GroupData
+                 , atom      :: !AtomData
+                 }
+  deriving (Show, Eq)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import           Bio.MMTF
+import qualified Data.ByteString.Lazy as B
+import           Test.Hspec
+
+main :: IO ()
+main = hspec $
+  describe "MMTF" $
+    it "should parse 1FSD" $ do
+      contents <- B.readFile "resource/1FSD.mmtf"
+      m <- decode contents
+      let sid = (structureId . structure) m
+      sid `shouldBe` Just "1FSD"
