mfmts-1.2.0.0: cid/MultiFormats/CID/Component/Version.hs
-- | Module : MultiFormats.CID.Component.Version
-- Description : Implements extraction versioning from CID data.
-- Copyright : Zoey McBride (c) 2026
-- License : AGPL-3.0-or-later
-- Maintainer : zoeymcbride@mailbox.org
-- Stability : experimental
module MultiFormats.CID.Component.Version
( Version (CIDv0, CIDv1),
-- * Extractions
extractVersion0,
extractVersion1,
-- * Helper functions
isTextCIDv0,
isStringCIDv0,
)
where
import Control.DeepSeq (NFData (..))
import Data.Text (Text)
import Data.Text qualified as Text
import Data.XCodec.BinaryTranscoder (BinaryTranscoder)
import Data.XCodec.BinaryTranscoder qualified as BXC
import MultiFormats.CID.Constants
import MultiFormats.CID.Errors
import MultiFormats.CID.Extractor (Extraction, Extractor (extractor))
-- | Implements available CID versions.
data Version = CIDv0 | CIDv1 deriving (Show, Eq, Enum)
-- | Evaluates Version into strict normal form.
instance NFData Version where
rnf _ = ()
-- | Extracts and validates the CID versioning information from bytes.
instance (BinaryTranscoder t) => Extractor Version t where
extractor cidbytes =
case BXC.uncons cidbytes of
Nothing -> Left (InvalidData EmptyCID)
Just (magic, _)
-- Only CIDv0 will start with 0x12, and it must be followed w/ a
-- special byte; if it doesn't have both, the encoding is invalid.
| magic == v0FirstByte -> extractVersion0 cidbytes
-- Without the magic bytes, CIDvX > CIDv0 is done in VarInts, so we get
-- the Version information from the first varint (0x1 for CIDv1).
| otherwise -> extractVersion1 cidbytes
-- | Performs extraction for CIDv1.
extractVersion1 :: (BinaryTranscoder t) => Extraction Version t
extractVersion1 bytes =
extractor bytes >>= \(varint1, rest) ->
if invalidCIDv1 varint1
then Left (InvalidEncoding ExpectedCIDv1)
else Right (CIDv1, rest)
where
invalidCIDv1 v1 = v1 /= v1FirstVarInt
-- | Performs extraction for CIDv0.
extractVersion0 :: (BinaryTranscoder t) => Extraction Version t
extractVersion0 ciddata =
let (lead, rest) = BXC.splitOffset 2 ciddata
total = BXC.totalOctets ciddata
in case BXC.unpackOctets lead of
[b1, b2]
| invalidCIDv0 b1 b2 total -> Left (InvalidEncoding MalformedCIDv0)
| otherwise -> Right (CIDv0, rest)
_Not2 -> Left (InvalidEncoding MalformedCIDv0)
where
-- Checks the conditions where a CIDv0 is invalidated.
{-# INLINE invalidCIDv0 #-}
invalidCIDv0 b1 b2 total =
b1 /= v0FirstByte || b2 /= v0SecondByte || total /= v0TotalBytes
-- | /O(1)/ Checks if Text qualifes for CIDv0 decoding.
isTextCIDv0 :: Text -> Bool
isTextCIDv0 text =
let start = Text.unpack $ Text.take 2 text
len = Text.length text
in start == v0EncodingPrefix && len == v0EncodingLength
-- | /O(n)/ Checks if a String qualifes for CIDv0 decoding.
isStringCIDv0 :: String -> Bool
isStringCIDv0 str =
let start = take 2 str
len = length str
in start == v0EncodingPrefix && len == v0EncodingLength