packages feed

ebml-0.1.0.0: src/Codec/EBML/Element.hs

-- | EBML core data decoder, see: https://matroska-org.github.io/libebml/specs.html
module Codec.EBML.Element where

import Data.Binary.Get (Get, getWord8)
import Data.Bits (Bits (shift, testBit, (.|.)), (.&.))
import Data.ByteString (ByteString)
import Data.Int (Int64)
import Data.Text (Text)
import Data.Word (Word32, Word64)

-- | EBML document structure, including the Header and Body Root.
newtype EBMLDocument = EBMLDocument [EBMLElement]

-- | EBML element id.
newtype EBMLID = EBMLID Word32
    deriving (Show)
    deriving newtype (Num, Eq, Ord)

-- | EBML element header.
data EBMLElementHeader = EBMLElementHeader
    { eid :: EBMLID
    , size :: Maybe Word64
    -- ^ size is Nothing for unknown-sized element.
    }
    deriving (Eq, Show)

-- | EBML element.
data EBMLElement = EBMLElement
    { header :: EBMLElementHeader
    , value :: EBMLValue
    }
    deriving (Eq, Show)

-- | EBML element value.
data EBMLValue
    = EBMLRoot [EBMLElement]
    | EBMLSignedInteger Int64
    | EBMLUnsignedInteger Word64
    | EBMLFloat Double
    | EBMLText Text
    | EBMLDate Text
    | EBMLBinary ByteString
    deriving (Eq, Show)

getElementHeader :: Get EBMLElementHeader
getElementHeader = EBMLElementHeader <$> getElementID <*> getMaybeDataSize

getElementID :: Get EBMLID
getElementID =
    EBMLID <$> do
        b1 <- getWord8
        let w1 = fromIntegral b1
        if
                | b1 `testBit` 7 -> getVar 0 w1
                | b1 `testBit` 6 -> getVar 1 w1
                | b1 `testBit` 5 -> getVar 2 w1
                | b1 `testBit` 4 -> getVar 3 w1
                | otherwise -> fail ("Invalid width: " <> show b1)

getMaybeDataSize :: Get (Maybe Word64)
getMaybeDataSize = do
    sz <- getDataSize
    pure $
        -- TODO: better check for unknown-sized for different VINT_DATA size.
        -- though, it seems like this is the common value.
        if sz == 0xFFFFFFFFFFFFFF
            then Nothing
            else Just sz

getDataSize :: Get Word64
getDataSize = do
    b1 <- getWord8
    if
            | b1 `testBit` 7 -> getVar 0 (fromIntegral (b1 .&. 127))
            | b1 `testBit` 6 -> getVar 1 (fromIntegral (b1 .&. 63))
            | b1 `testBit` 5 -> getVar 2 (fromIntegral (b1 .&. 31))
            | b1 `testBit` 4 -> getVar 3 (fromIntegral (b1 .&. 15))
            | b1 `testBit` 3 -> getVar 4 (fromIntegral (b1 .&. 7))
            | b1 `testBit` 2 -> getVar 5 (fromIntegral (b1 .&. 3))
            | b1 `testBit` 1 -> getVar 6 (fromIntegral (b1 .&. 1))
            | b1 `testBit` 0 -> getVar 7 0
            | otherwise -> pure 0

getVar :: (Num a, Bits a) => Int -> a -> Get a
getVar 0 acc = pure acc
getVar n acc = do
    b <- getWord8
    getVar (n - 1) ((acc `shift` 8) .|. fromIntegral b)