{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}
module Asn.Oid
( Oid(..)
, toShortText
, fromShortTextDot
, size
, index
, take
, isPrefixOf
) where
import Prelude hiding (take)
import Control.Monad.ST (runST)
import Data.Primitive (PrimArray)
import Data.Text.Short (ShortText)
import Data.Word (Word32)
import Data.ByteString.Short.Internal (ShortByteString(SBS))
import qualified Arithmetic.Nat as Nat
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Chunks as Chunks
import qualified Data.Bytes.Builder as Builder
import qualified Data.Bytes.Builder.Bounded as Bounded
import qualified Data.Primitive as Prim
import qualified Data.Primitive.Contiguous as C
import qualified Data.Text.Short as ST
import qualified Data.Text.Short.Unsafe as ST
import qualified Data.Bytes.Parser as Parser
import qualified Data.Bytes.Parser.Latin as Latin
newtype Oid = Oid { getOid :: PrimArray Word32 }
deriving newtype (Show)
deriving newtype (Semigroup)
deriving newtype (Monoid)
deriving newtype (Eq)
deriving newtype (Ord)
-- | Encode an OID. Encodes the empty OID as empty text even though
-- this is not a valid encoded OID.
toShortText :: Oid -> ShortText
toShortText (Oid arr) = case sz of
0 -> ST.empty
_ -> ba2st $ Chunks.concatU $ Builder.run 256
( Builder.word32Dec (Prim.indexPrimArray arr 0)
<>
C.foldMap
(\w -> Builder.fromBounded Nat.constant
(Bounded.append (Bounded.ascii '.') (Bounded.word32Dec w))
) (C.slice arr 1 (sz - 1))
)
where
!sz = Prim.sizeofPrimArray arr
-- | Decode an OID. Returns Nothing if the text is empty.
fromShortTextDot :: ShortText -> Maybe Oid
fromShortTextDot !str =
let !b = Bytes.fromShortByteString (ST.toShortByteString str)
!maxPossibleParts = div (Bytes.length b) 2 + 1
in Parser.parseBytesMaybe
( do w0 <- Latin.decWord32 ()
dst <- Parser.effect $ do
dst <- Prim.newPrimArray maxPossibleParts
Prim.writePrimArray dst 0 w0
pure dst
let go !ix = Parser.isEndOfInput >>= \case
True -> Parser.effect $ do
Prim.shrinkMutablePrimArray dst ix
dst' <- Prim.unsafeFreezePrimArray dst
pure (Oid dst')
False -> do
Latin.char () '.'
w <- Latin.decWord32 ()
Parser.effect (Prim.writePrimArray dst ix w)
go (ix + 1)
go 1
) b
size :: Oid -> Int
{-# inline size #-}
size (Oid ws) = Prim.sizeofPrimArray ws
index :: Oid -> Int -> Word32
{-# inline index #-}
index (Oid arr) = Prim.indexPrimArray arr
take :: Oid -> Int -> Oid
take (Oid !preArr) !len
| len >= Prim.sizeofPrimArray preArr = Oid preArr
| otherwise = runST $ do
dst <- Prim.newPrimArray len
Prim.copyPrimArray dst 0 preArr 0 len
Oid <$> Prim.unsafeFreezePrimArray dst
isPrefixOf :: Oid -> Oid -> Bool
isPrefixOf (Oid preArr) (Oid arr)
| preSize > theSize = False
| otherwise = go 0
where
go !i
| i >= preSize = True
| Prim.indexPrimArray preArr i /= Prim.indexPrimArray arr i = False
| otherwise = go (i + 1)
!preSize = Prim.sizeofPrimArray preArr
!theSize = Prim.sizeofPrimArray arr
ba2st :: Prim.ByteArray -> ShortText
{-# inline ba2st #-}
ba2st (Prim.ByteArray x) = ST.fromShortByteStringUnsafe (SBS x)