packages feed

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