module Data.ASN1.Raw
(
ASN1Class(..)
, ASN1Tag
, ASN1Length(..)
, ASN1Header(..)
, ASN1Err(..)
, ASN1Event(..)
, iterateFile
, iterateByteString
, enumReadBytes
, enumWriteBytes
, toBytes
, getHeader
, putHeader
) where
import Data.Enumerator hiding (head, length, map)
import qualified Data.Enumerator as E
import qualified Data.Enumerator.List as EL
import Data.Enumerator.Binary (enumFile)
import Data.Attoparsec.Enumerator
import Data.Attoparsec
import qualified Data.Attoparsec as A
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.ASN1.Internal
import Control.Exception
import Data.Typeable
import Data.Word
import Data.Bits
import Control.Monad
import Control.Monad.Identity
import Control.Applicative ((<|>), (<$>))
data ASN1Class =
Universal
| Application
| Context
| Private
deriving (Show,Eq,Ord,Enum)
type ASN1Tag = Int
data ASN1Length =
LenShort Int
| LenLong Int Int
| LenIndefinite
deriving (Show,Eq)
data ASN1Header = ASN1Header !ASN1Class !ASN1Tag !Bool !ASN1Length
deriving (Show,Eq)
data ASN1Event =
Header ASN1Header
| Primitive !ByteString
| ConstructionBegin
| ConstructionEnd
deriving (Show,Eq)
data ASN1Err =
ASN1LengthDecodingLongContainsZero
| ASN1WritingUnexpectedConstructionEnd
| ASN1WritingUnexpectedInputEOF
| ASN1PolicyFailed String String
| ASN1NotImplemented String
| ASN1Multiple [ASN1Err]
| ASN1Misc String
| ASN1ParsingPartial
| ASN1ParsingFail String
deriving (Typeable, Show, Eq)
instance Exception ASN1Err
iterateFile :: FilePath -> Iteratee ASN1Event IO a -> IO (Either SomeException a)
iterateFile path p = run (enumFile path $$ joinI (enumReadBytes $$ p))
iterateByteString :: Monad m => L.ByteString -> Iteratee ASN1Event m a -> m (Either SomeException a)
iterateByteString bs p = run (enumList 1 (L.toChunks bs) $$ joinI (enumReadBytes $$ p))
data ParseState =
PSPrimitive Int
| PSConstructing Int
| PSConstructingEOC
enumReadBytes :: Monad m => Enumeratee ByteString ASN1Event m a
enumReadBytes = checkDone $ \k -> k (Chunks []) >>== loop [0] []
where
loop !cs !ps = checkDone (go cs ps)
go (n:[]) [] k = iterDesc >>= eofCheck k
(\(c,e,nps) -> case nps of
PSPrimitive _ -> k (Chunks [e]) >>== loop [c+n] [nps]
_ -> k (Chunks [e, ConstructionBegin]) >>== loop [0, c+n] [nps]
)
go (n:cs) (PSPrimitive i:pss) k =
iterPrim i >>= (\e -> k (Chunks [e]) >>== loop (n+i:cs) pss)
go (n:m:cs) fps@(PSConstructing i:pss) k
| n == i = k (Chunks [ConstructionEnd]) >>== loop (n+m:cs) pss
| otherwise = iterDesc >>= eofCheck k
(\(c, e, nps) -> case nps of
PSPrimitive _ -> k (Chunks [e]) >>== loop (n+c:m:cs) (nps:fps)
_ -> k (Chunks [e, ConstructionBegin]) >>== loop (0:n+c:m:cs) (nps:fps)
)
go (n:m:cs) fps@(PSConstructingEOC:pss) k =
iterDesc >>= eofCheck k
(\(c, e, nps) -> case e of
(Header (ASN1Header _ 0 _ _)) -> k (Chunks [ConstructionEnd]) >>== loop (c+n+m:cs) pss
_ -> k (Chunks [e]) >>== loop (n+c:m:cs) (nps:fps)
)
go _ _ k = k (Chunks []) >>== return
eofCheck k _ Nothing = k (Chunks []) >>== return
eofCheck _ f (Just x) = f $! x
iterDesc :: Monad m => Iteratee ByteString m (Maybe (Int, ASN1Event, ParseState))
iterDesc = iterParser ((endOfInput >> return Nothing) <|> fmap Just parseHeaderEvent)
iterPrim i = iterParser (fmap (Primitive) (A.take i))
parseHeaderEvent :: Parser (Int, ASN1Event, ParseState)
parseHeaderEvent = do
(lbytes, asn1header@(ASN1Header _ _ pc len)) <- parseHeader
let ps = if pc
then case len of
LenIndefinite -> PSConstructingEOC
LenLong _ i -> PSConstructing i
LenShort i -> PSConstructing i
else case len of
LenIndefinite -> error "cannot do indefinite primitive"
LenLong _ i -> PSPrimitive i
LenShort i -> PSPrimitive i
return (lbytes, Header asn1header, ps)
parseHeader :: Parser (Int, ASN1Header)
parseHeader = do
(cl,pc,t1) <- parseFirstWord <$> anyWord8
(tagbytes, tag) <- if t1 == 0x1f then getTagLong else return (0, t1)
(lenbytes, len) <- getLength
return (1+tagbytes+lenbytes, ASN1Header cl tag pc len)
getHeader :: ByteString -> Either ASN1Err ASN1Header
getHeader l = case parse parseHeader l of
(Fail _ _ _) -> Left (ASN1ParsingFail "header")
(Partial _) -> Left (ASN1ParsingPartial)
Done b r -> if B.null b then Right (snd r) else Left ASN1ParsingPartial
parseFirstWord :: Word8 -> (ASN1Class, Bool, ASN1Tag)
parseFirstWord w = (cl,pc,t1)
where
cl = toEnum $ fromIntegral $ (w `shiftR` 6)
pc = testBit w 5
t1 = fromIntegral (w .&. 0x1f)
getTagLong :: Parser (Int, ASN1Tag)
getTagLong = do
t <- fromIntegral <$> anyWord8
when (t == 0x80) $ error "not canonical encoding of tag"
if testBit t 7
then getNext 1 (clearBit t 7)
else return (1, t)
where getNext !blen !n = do
t <- fromIntegral <$> anyWord8
if testBit t 7
then getNext (blen + 1) (n `shiftL` 7 + clearBit t 7)
else return (blen + 1, n `shiftL` 7 + t)
getLength :: Parser (Int, ASN1Length)
getLength = do
l1 <- fromIntegral <$> anyWord8
if testBit l1 7
then case clearBit l1 7 of
0 -> return (1, LenIndefinite)
len -> do
lw <- A.take len
return (1+len, LenLong len $ uintbs lw)
else
return (1, LenShort l1)
where
uintbs = B.foldl (\acc n -> (acc `shiftL` 8) + fromIntegral n) 0
putHeader :: ASN1Header -> ByteString
putHeader (ASN1Header cl tag pc len) = B.append tgBytes lenBytes
where cli = shiftL (fromIntegral $ fromEnum cl) 6
pcval = shiftL (if pc then 0x1 else 0x0) 5
tag0 = if tag < 0x1f then fromIntegral tag else 0x1f
word1 = cli .|. pcval .|. tag0
tgBytes = if tag < 0x1f then B.singleton word1
else B.cons word1 $ putVarEncodingIntegral tag
lenBytes = B.pack $ putLength len
putLength :: ASN1Length -> [Word8]
putLength (LenShort i)
| i < 0 || i > 0x7f = error "putLength: short length is not between 0x0 and 0x80"
| otherwise = [fromIntegral i]
putLength (LenLong _ i)
| i < 0 = error "putLength: long length is negative"
| otherwise = lenbytes : lw
where
lw = bytesOfUInt $ fromIntegral i
lenbytes = fromIntegral (length lw .|. 0x80)
putLength (LenIndefinite) = [0x80]
enumWriteBytes :: Monad m => Enumeratee ASN1Event ByteString m a
enumWriteBytes = checkDone $ \k -> k (Chunks []) >>== loop []
where
putEoc = putHeader $ ASN1Header Universal 0 False (LenShort 0)
loop eocs = checkDone $ go eocs
go eocs k = EL.head >>= \x -> case x of
Nothing ->
if eocs == [] then k (Chunks []) >>== return else E.throwError ASN1WritingUnexpectedInputEOF
Just (Header hdr@(ASN1Header _ _ True len)) ->
k (Chunks [putHeader hdr]) >>== loop ((len == LenIndefinite) : eocs)
Just (Header hdr) -> k (Chunks [putHeader hdr]) >>== loop eocs
Just (Primitive p) -> k (Chunks [p]) >>== loop eocs
Just ConstructionBegin -> k (Chunks []) >>== loop eocs
Just ConstructionEnd -> case eocs of
[] -> E.throwError ASN1WritingUnexpectedConstructionEnd
True : tl -> k (Chunks [putEoc]) >>== loop tl
False : tl -> k (Chunks []) >>== loop tl
toBytes :: [ASN1Event] -> L.ByteString
toBytes evs = case runIdentity (run (enumList 8 evs $$ joinI (enumWriteBytes $$ EL.consume))) of
Left err -> error $ show err
Right l -> L.fromChunks l