packages feed

language-asn (empty) → 0.1.0.0

raw patch · 8 files changed

+1148/−0 lines, 8 filesdep +basedep +bytestringdep +contravariant

Dependencies added: base, bytestring, contravariant, hashable, pretty, primitive, text, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,12 @@+# Changelog++`language-asn` uses [PVP Versioning][1].+The changelog is available [on GitHub][2].++0.0.0+=====++* Initially created.++[1]: https://pvp.haskell.org+[2]: https://github.com/chessai/language-asn/releases
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2019, chessai+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++* Neither the name of the copyright holder nor the names of its+  contributors may be used to endorse or promote products derived from+  this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ language-asn.cabal view
@@ -0,0 +1,52 @@+cabal-version: 2.2+name:+  language-asn+version:+  0.1.0.0+synopsis:+  ASN.1 encoding and decoding+description:+  ASN.1 language implementation. ASN.1 is used tightly with SNMP.+homepage:+  https://github.com/chessai/language-asn.git+license:+  BSD-3-Clause+license-file:+  LICENSE+author:+  Andrew Martin+maintainer:+  chessai1996@gmail.com+copyright:+  2019 (c) Andrew Martin+category:+  Language+build-type:+  Simple+extra-source-files:+  CHANGELOG.md++source-repository head+  type: git+  location: https://github.com/chessai/language-asn++library+  exposed-modules:+    Language.Asn.Encoding+    Language.Asn.Decoding+    Language.Asn.ObjectIdentifier+    Language.Asn.Types+    Language.Asn.Types.Internal+  build-depends:+      base >=4.9 && <4.13+    , bytestring >= 0.10 && < 0.11+    , contravariant >= 1.3 && < 1.6+    , vector >= 0.11 && < 0.13+    , primitive >= 0.6.4 && < 7+    , pretty >= 1.1 && < 1.2+    , text >= 1.2 && < 1.3+    , hashable >= 1.2 && < 1.3+  hs-source-dirs:+    src+  default-language:+    Haskell2010
+ src/Language/Asn/Decoding.hs view
@@ -0,0 +1,339 @@+{-# language ExistentialQuantification #-}+{-# language GADTs #-}+{-# language OverloadedStrings #-}+{-# language GeneralizedNewtypeDeriving #-}+{-# language BangPatterns #-}+{-# language MagicHash #-}++module Language.Asn.Decoding+  ( ber+  , sequence+  , sequenceOf+  , required+  , optional+  , defaulted+  , utf8String+  , integer+  , integerRanged+  , int32+  , int+  , word32+  , word64+  , null+  , null'+  , octetString+  , octetStringWord8+  , octetStringWord32+  , objectIdentifier+  , choice+  , option+  , tag+  , mapFailable+  ) where++import Prelude hiding (sequence,null)+import Language.Asn.Types.Internal+import Data.ByteString (ByteString)+import Data.Bits (Bits(..))+import Control.Monad hiding (sequence)+import Data.Maybe (fromMaybe, listToMaybe)+import Data.Text (Text)+import Data.Word (Word8, Word32, Word64)+import Data.Int (Int32)+import Data.Functor.Identity (Identity(..))+import Text.Printf (printf)+import qualified GHC.Exts as E+import qualified Data.Text.Encoding as TE+import qualified Data.List as List+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Unsafe as BSU++ber :: AsnDecoding a -> ByteString -> Either String a+ber d bs = requireNoLeftovers =<< decodeBerInternal d Nothing bs++sequence :: FieldDecoding a -> AsnDecoding a+sequence = AsnDecodingSequence++sequenceOf :: AsnDecoding a -> AsnDecoding [a]+sequenceOf = AsnDecodingSequenceOf id++required :: FieldName -> AsnDecoding a -> FieldDecoding a+required name d = FieldDecoding (liftAp (FieldDecodingRequired name d))++optional :: FieldName -> AsnDecoding a -> FieldDecoding (Maybe a)+optional name d = FieldDecoding (liftAp (FieldDecodingOptional name d id))++defaulted :: Show a => FieldName -> AsnDecoding a -> a -> FieldDecoding a+defaulted name d defVal = FieldDecoding (liftAp (FieldDecodingDefault name d defVal show))++choice :: [OptionDecoding a] -> AsnDecoding a+choice = AsnDecodingChoice++option :: OptionName -> AsnDecoding a -> OptionDecoding a+option = OptionDecoding++tag :: TagClass -> Int -> Explicitness -> AsnDecoding a -> AsnDecoding a+tag c n e = AsnDecodingRetag (TagAndExplicitness (Tag c n) e)++utf8String :: AsnDecoding Text+utf8String = AsnDecodingUniversal $ UniverseDecodingTextualString Utf8String id (Subtypes []) (Subtypes [])++octetString :: AsnDecoding ByteString+octetString = AsnDecodingUniversal $ UniverseDecodingOctetString id (Subtypes [])++octetStringWord32 :: AsnDecoding Word32+octetStringWord32 = mapFailable+  ( \bs -> if ByteString.length bs == 4+      then Right $ (fromIntegral :: Word -> Word32)+         $ unsafeShiftL 24 (fromIntegral (BSU.unsafeIndex bs 0))+         + unsafeShiftL 16 (fromIntegral (BSU.unsafeIndex bs 1))+         + unsafeShiftL 8 (fromIntegral (BSU.unsafeIndex bs 2))+         + fromIntegral (BSU.unsafeIndex bs 3)+      else Left "octetStringWord32 expects the octet string to have exactly 4 bytes"+  ) octetString++octetStringWord8 :: AsnDecoding Word8+octetStringWord8 = mapFailable+  ( \bs -> if ByteString.length bs == 1+      then Right (BSU.unsafeIndex bs 0)+      else Left "octetStringWord8 expects the octet string to have exactly 1 byte"+  ) octetString++integer :: AsnDecoding Integer+integer = AsnDecodingUniversal (UniverseDecodingInteger id (Subtypes []))++-- This could be improved by making sure that integer in question is actually+-- in the provided bounds.+int :: AsnDecoding Int+int = AsnDecodingUniversal (UniverseDecodingInteger fromIntegral (Subtypes []))++-- This could be improved by making sure that integer in question is actually+-- in the provided bounds.+int32 :: AsnDecoding Int32+int32 = AsnDecodingUniversal (UniverseDecodingInteger fromIntegral (Subtypes []))++-- This could be improved by making sure that integer in question is actually+-- in the provided bounds.+word32 :: AsnDecoding Word32+word32 = AsnDecodingUniversal (UniverseDecodingInteger fromIntegral (Subtypes []))++-- This could be improved by making sure that integer in question is actually+-- in the provided bounds.+word64 :: AsnDecoding Word64+word64 = AsnDecodingUniversal (UniverseDecodingInteger fromIntegral (Subtypes []))++null :: AsnDecoding ()+null = AsnDecodingUniversal (UniverseDecodingNull ())++null' :: a -> AsnDecoding a+null' = AsnDecodingUniversal . UniverseDecodingNull++objectIdentifier :: AsnDecoding ObjectIdentifier+objectIdentifier = AsnDecodingUniversal (UniverseDecodingObjectIdentifier id (Subtypes []))++integerRanged :: Integer -> Integer -> AsnDecoding Integer+integerRanged lo hi = AsnDecodingUniversal+  (UniverseDecodingInteger id (Subtypes [SubtypeValueRange lo hi]))++mapFailable :: (a -> Either String b) -> AsnDecoding a -> AsnDecoding b+mapFailable f d = AsnDecodingConversion d f++repeatUntilEmpty :: Monad m => (ByteString -> m (a,ByteString)) -> ByteString -> m [a]+repeatUntilEmpty f = go where+  go bs1 = do+    (a,bs2) <- f bs1+    if ByteString.null bs2+      then return [a]+      else fmap (a:) (go bs2)++decodeBerInternal :: AsnDecoding a -> Maybe Tag -> ByteString -> Either String (a,ByteString)+decodeBerInternal x overrideTag bs1 = case x of+  AsnDecodingUniversal u -> do+    (bsContent,bsRemainder) <- takeTagAndLength Primitive (Tag Universal (universeDecodingTagNumber u))+    a <- decodeUniversal u bsContent+    return (a,bsRemainder)+  AsnDecodingRetag (TagAndExplicitness newTag expl) nextDecoding -> case expl of+    Implicit -> decodeBerInternal nextDecoding (Just $ fromMaybe newTag overrideTag) bs1+    Explicit -> do+      (bsContent,bsRemainder) <- takeTagAndLength Constructed newTag+      a <- requireNoLeftovers =<< decodeBerInternal nextDecoding Nothing bsContent+      return (a,bsRemainder)+  AsnDecodingSequence (FieldDecoding fieldDecoding) -> do+    (bsContent,bsRemainder) <- takeTagAndLength Constructed sequenceTag+    a <- requireNoLeftovers =<< getDecodePart (runAp decodeField fieldDecoding) bsContent+    return (a,bsRemainder)+  AsnDecodingSequenceOf f nextDecoding -> do+    (bsContent,bsRemainder) <- takeTagAndLength Constructed sequenceTag+    cs <- repeatUntilEmpty (decodeBerInternal nextDecoding Nothing) bsContent+    return (f cs,bsRemainder)+  AsnDecodingConversion nextDecoding conv -> do+    (b,bs2) <- decodeBerInternal nextDecoding overrideTag bs1+    a <- conv b+    return (a,bs2)+  -- Note: overrideTag is currently ignored in this case. Per+  -- ASN.1 rules, you cannot put an IMPLICIT tag over a CHOICE,+  -- which is the only thing that could cause this to happen.+  -- Still, I don't like that it's being ignored.+  AsnDecodingChoice opts -> case ByteString.uncons bs1 of+    Nothing -> Left "while trying to decode Choice tag, ran out of input"+    Just (b,bs2) -> do+      let possibilities = nextExpectedTags =<< map (\(OptionDecoding _ d) -> d) opts+          theTagNumberPrefix = b .&. 31+      (theTagNumber,_) <- decipherTagNumber theTagNumberPrefix bs2+      let mmatched = listToMaybe $ List.filter+            (\((Tag tc tn,cstn),_) -> b .&. 192 == tagClassBit tc && b .&. 32 == constructionBit cstn && tn == theTagNumber)+            possibilities+      case mmatched of+        Nothing -> Left $ concat +          [ "while trying to decode Choice tag, the tag did not match any of the expected tags: found "+          , show theTagNumber+          , " but expected one of ["+          , List.intercalate "," $ flip map possibilities $ \((Tag tc tn, cstn),_) -> concat+              [ show tc+              , " "+              , show cstn+              , " "+              , show tn+              ]+          , "]"+          ]+        Just (_,Wrapper chosenDecoder conv2) -> do+          (c,bs3) <- decodeBerInternal chosenDecoder Nothing bs1+          r <- conv2 c+          Right (r,bs3)+  where+  takeTagAndLength :: Construction -> Tag -> Either String (ByteString,ByteString)+  takeTagAndLength construction t1 = do+    let tagToUse = fromMaybe t1 overrideTag+    bs2 <- expectTag construction tagToUse bs1+    (len,bs3) <- takeLength bs2+    splitOrFail len bs3++data Wrapper a = forall b. Wrapper (AsnDecoding b) (b -> Either String a)++idWrapper :: AsnDecoding a -> Wrapper a+idWrapper d = Wrapper d Right++nextExpectedTags :: AsnDecoding a -> [((Tag,Construction), Wrapper a)]+nextExpectedTags x = case x of+  AsnDecodingUniversal u -> [((Tag Universal (universeDecodingTagNumber u), Primitive),idWrapper x)]+  AsnDecodingSequenceOf _ _ -> [((sequenceTag, Constructed),idWrapper x)]+  AsnDecodingSequence _ -> [((sequenceTag, Constructed),idWrapper x)]+  AsnDecodingChoice opts -> join (map (\(OptionDecoding _ theDec) -> nextExpectedTags theDec) opts)+  AsnDecodingRetag (TagAndExplicitness newTag expl) nextDecoding -> case expl of+    Explicit -> [((newTag,Constructed),idWrapper x)]+    Implicit -> map (\((_,c),Wrapper theDec theConv) -> ((newTag,c), Wrapper (AsnDecodingRetag (TagAndExplicitness newTag expl) theDec) theConv)) (nextExpectedTags nextDecoding)+  AsnDecodingConversion nextDecoding conv ->+    map (\((t,c),Wrapper theDec theConv) -> ((t,c),Wrapper theDec (theConv >=> conv))) (nextExpectedTags nextDecoding)++decodeField :: FieldDecodingPart a -> DecodePart a+decodeField x = case x of+  FieldDecodingRequired _ d -> DecodePart (decodeBerInternal d Nothing)+  FieldDecodingOptional _ d conv1 -> handlePossiblyMissingField d conv1+  FieldDecodingDefault _ d a _ -> handlePossiblyMissingField d (fromMaybe a)++handlePossiblyMissingField :: AsnDecoding b -> (Maybe b -> a) -> DecodePart a+handlePossiblyMissingField d conv1 = DecodePart $ \bs1 -> case ByteString.uncons bs1 of+  Nothing -> Right (conv1 Nothing, bs1)+  Just (b,bs2) -> do+    let possibilities = nextExpectedTags d+        theTagNumberPrefix = b .&. 31+    (theTagNumber,_) <- decipherTagNumber theTagNumberPrefix bs2+    let mmatched = listToMaybe $ List.filter+          (\((Tag tc tn,cstn),_) -> b .&. 192 == tagClassBit tc && b .&. 32 == constructionBit cstn && tn == theTagNumber) possibilities+    case mmatched of+      Nothing -> Right (conv1 Nothing, bs1)+      Just (_,Wrapper chosenDecoder conv2) -> do+        (c,bs3) <- decodeBerInternal chosenDecoder Nothing bs1+        r <- conv2 c+        Right (conv1 (Just r),bs3)++-- The first argument is the full tag byte+decipherTagNumber :: Word8 -> ByteString -> Either String (Int,ByteString)+decipherTagNumber w bs = do+  let tn = w .&. 31+  if tn < 31+    then Right (fromIntegral tn,bs)+    else error "decipherTagNumber: handle tags greater than 30"+++requireNoLeftovers :: (a,ByteString) -> Either String a+requireNoLeftovers (a,bs) = if ByteString.null bs then Right a else Left "expected not to have leftovers, but there were leftovers"++expectTag :: Construction -> Tag -> ByteString -> Either String ByteString+expectTag construction (Tag tc tn) bs = case ByteString.uncons bs of+  Nothing -> Left "expected a byte for the Tag but the ByteString was empty"+  Just (b,bsNext) -> do+    let expectedTagBits = tagClassBit tc+        actualTagBits = b .&. 192+    when (actualTagBits /= expectedTagBits)+      $ Left $ "while parsing the tag, the tag class bits did not match: "+            ++ "found " ++ printf "%08b" actualTagBits ++ " but expected "+            ++ printf "%08b" expectedTagBits ++ " (" ++ show tc ++ ")"+    when (b .&. 32 /= constructionBit construction) $ Left "while parsing the tag, the construction bit did not match"+    if tn < 31+      then do+        when (b .&. 31 /= fromIntegral tn) $ Left "while parsing the tag, the tag number did not match what was expected"+        Right bsNext+      else error "expectTag: handle tag numbers higher than 30"++splitOrFail :: Int -> ByteString -> Either String (ByteString,ByteString)+splitOrFail i bs = if i > ByteString.length bs+  then Left "tried to take a fixed number of bytes as specified by the encoding, but bytestring ended"+  else Right (ByteString.splitAt i bs)++takeLength :: ByteString -> Either String (Int,ByteString)+takeLength bs1 = case ByteString.uncons bs1 of+  Nothing -> Left "while trying to decode the length, expected an initial octet but the bytestring ended"+  Just (b,bs2) -> if b < 128+    then Right (fromIntegral b, bs2)+    else let bytesToTake = fromIntegral (127 .&. b) in+      if ByteString.length bs2 < bytesToTake+        then Left "while decoding multi-byte length, ran out of bytes"+        else let (bs3,bs4) = ByteString.splitAt bytesToTake bs2 in Right (parseLength bs3, bs4)+++parseLength :: ByteString -> Int+parseLength = ByteString.foldl' (\i w8 -> i * 256 + fromIntegral w8) 0++-- This currently does the wrong thing for negative numbers+parseInteger :: ByteString -> Integer+parseInteger = ByteString.foldl' (\i w8 -> i * 256 + fromIntegral w8) 0++decodeUniversal :: UniverseDecoding a -> ByteString -> Either String a+decodeUniversal x bs = case x of+  UniverseDecodingInteger f _ -> Right (f (parseInteger bs))+  UniverseDecodingTextualString _ f _ _ -> case TE.decodeUtf8' bs of+    Left _ -> Left "while decoding string primitive, found that UTF8-encoding was not used"+    Right t -> Right (f t)+  UniverseDecodingObjectIdentifier f _ -> fmap f (stepOidAll bs)+  UniverseDecodingOctetString f _ -> Right (f bs)+  UniverseDecodingNull a -> Right a++stepOidAll :: ByteString -> Either String ObjectIdentifier+stepOidAll bs1 = case ByteString.uncons bs1 of+  Nothing -> Left "while decoding OID, found no bytes, the OID should have at least one octet"+  Just (b,bs2) ->+    let (w1,w2) = quotRem b 40+        Identity nums = repeatUntilEmpty (Identity . stepOid 0) bs2+     in Right (ObjectIdentifier (E.fromList $ fromIntegral w1 : fromIntegral w2 : nums))++stepOid :: Word -> ByteString -> (Word,ByteString)+stepOid !i bs1 = case ByteString.uncons bs1 of+  Nothing -> (i,bs1)+  Just (w,bs2) ->+    let !acc = i * 128 + fromIntegral (w .&. 127) in+    if w .&. 128 == 128+      then stepOid acc bs2+      else (acc,bs2)++universeDecodingTagNumber :: UniverseDecoding a -> Int+universeDecodingTagNumber x = case x of+  UniverseDecodingInteger _ _ -> 2+  UniverseDecodingTextualString typ _ _ _ -> tagNumStringType typ+  UniverseDecodingObjectIdentifier _ _ -> 6+  UniverseDecodingOctetString _ _ -> 4+  UniverseDecodingNull _ -> 5+++
+ src/Language/Asn/Encoding.hs view
@@ -0,0 +1,376 @@+{-# language BangPatterns #-}+{-# language CPP #-}+{-# language ExistentialQuantification #-}+{-# language GADTs #-}+{-# language GeneralizedNewtypeDeriving #-}+{-# language LambdaCase #-}+{-# language MagicHash #-}+{-# language OverloadedStrings #-}++module Language.Asn.Encoding+  ( -- * Run Encoding+    der+  , toDefinitionString+    -- * Build Encoding+    -- ** Constructed+  , sequence+  , sequenceOf+  , choice+  , tag+  , implicitTag+    -- ** Fields+  , required+  , optional+  , defaulted+  , option+    -- ** Primitive+  , integer+  , integerRanged+  , int32+  , int+  , word32+  , word64+  , word+  , octetString+  , octetStringWord8+  , octetStringWord32+  , utf8String+  , null+  , null'+  , objectIdentifier+  -- Remove anything exported below this +  , int64Log256+  , encodeLength+  ) where++import Prelude hiding (sequence,null)+import Language.Asn.Types.Internal+import Data.ByteString (ByteString)+import Data.ByteString.Builder (Builder)+import Data.Text (Text)+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid+#endif+import Data.Word (Word8, Word32, Word64)+import Data.Int (Int32, Int64)+import Data.Bits (Bits(..), FiniteBits(..))+import Data.Primitive (PrimArray,Prim)+import GHC.Int (Int(..))+import Data.Foldable hiding (null)+import qualified Data.Text.Encoding as TE+import qualified Text.PrettyPrint as PP+import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString.Builder as Builder+import qualified Data.List as List+import qualified Data.Primitive as PM+import qualified Data.ByteString as ByteString++-- Note that DER encoding is a subset of BER encoding. If you+-- need to encode with BER, just use this function.+der :: AsnEncoding a -> a -> LB.ByteString+der e = encodeTaggedByteString . encodeBerInternal e++tagClassPrefix :: TagClass -> String+tagClassPrefix = \case+  Universal -> "UNIVERSAL "+  Private -> "PRIVATE "+  Application -> "APPLICATION "+  ContextSpecific -> ""++toDefinitionString :: AsnEncoding a -> String+toDefinitionString = PP.render . go where+  go :: forall b. AsnEncoding b -> PP.Doc+  go (EncUniversalValue u) = prettyPrintUniversalValue u+  go (EncRetag (TagAndExplicitness theTag expl) e) =+    PP.text (prettyPrintTag theTag ++ " " ++ ppExplicitness expl ++ " ") <> go e+  go (EncChoice (Choice _ allCtors getValAndEnc)) = (PP.$+$)+    "CHOICE"+    ( PP.nest 2 $ PP.vcat $ map (ppValEnc . getValAndEnc) allCtors)+  go (EncSequence fields) = (PP.$+$)+    "SEQUENCE"+    ( PP.nest 2 $ PP.vcat $ map ppField fields)+  go (EncSequenceOf _ e) = PP.text "SEQUENCE OF" PP.<+> go e+  ppField :: forall b. Field b -> PP.Doc+  ppField = \case+    FieldRequired (FieldName name) _ e -> PP.text (name ++ " ") <> go e+    FieldOptional (FieldName name) _ e -> PP.text (name ++ " OPTIONAL ") <> go e+    FieldDefaulted (FieldName name) _ defVal showVal _ e ->+      PP.text (name ++ " DEFAULT " ++ showVal defVal ++ " ") <> go e+  ppValEnc :: ValueAndEncoding -> PP.Doc+  ppValEnc (ValueAndEncoding _ (OptionName name) _ enc) = PP.text (name ++ " ") <> go enc+  ppExplicitness :: Explicitness -> String+  ppExplicitness = \case+    Implicit -> "IMPLICIT"+    Explicit -> "EXPLICIT"++prettyPrintTag :: Tag -> String+prettyPrintTag (Tag c n) = "[" ++ tagClassPrefix c ++ show n ++ "]"++prettyPrintUniversalValue :: UniversalValue x -> PP.Doc+prettyPrintUniversalValue = \case+  UniversalValueBoolean _ _ -> PP.text "BOOLEAN"+  UniversalValueInteger _ ss -> PP.text $ "INTEGER" ++ strSubtypes show ss+  UniversalValueNull -> PP.text "NULL"+  UniversalValueOctetString _ _ -> PP.text "OCTET STRING"+  UniversalValueObjectIdentifier _ _ -> PP.text "OBJECT IDENTIFIER"+  UniversalValueTextualString typ _ _ _ -> PP.text (strStringType typ)++strStringType :: StringType -> String+strStringType = \case+  Utf8String -> "UTF8String"+  NumericString -> "NumericString"+  PrintableString -> "PrintableString"+  TeletexString -> "TeletexString"+  VideotexString -> "VideotexString"+  IA5String -> "IA5String"+  GraphicString -> "GraphicString"+  VisibleString -> "VisibleString"+  GeneralString -> "GeneralString"+  UniversalString -> "UniversalString"+  CharacterString -> "CHARACTER STRING"+  BmpString -> "BMPString"++strSubtypes :: (a -> String) -> Subtypes a -> String+strSubtypes f (Subtypes ss)+  | length ss == 0 = ""+  | otherwise = " (" ++ List.intercalate " | " (map (strSubtype f) ss) ++ ")"++strSubtype :: (a -> String) -> Subtype a -> String+strSubtype f = \case+  SubtypeSingleValue a -> f a+  SubtypeValueRange lo hi -> f lo ++ " .. " ++ f hi++makeTag :: TagClass -> Int -> Tag+makeTag = Tag++sequence :: [Field a] -> AsnEncoding a+sequence = EncSequence++sequenceOf :: Foldable f => AsnEncoding a -> AsnEncoding (f a)+sequenceOf = EncSequenceOf toList++choice :: [a] -> (a -> ValueAndEncoding) -> AsnEncoding a+choice xs f = EncChoice (Choice id xs f)++option :: Int -> OptionName -> b -> AsnEncoding b -> ValueAndEncoding+option = ValueAndEncoding++tag :: TagClass -> Int -> Explicitness -> AsnEncoding a -> AsnEncoding a+tag c n e = EncRetag (TagAndExplicitness (Tag c n) e)++implicitTag :: Tag -> AsnEncoding a -> AsnEncoding a+implicitTag t = EncRetag (TagAndExplicitness t Implicit)++required :: FieldName -> (a -> b) -> AsnEncoding b -> Field a+required = FieldRequired++optional :: FieldName -> (a -> Maybe b) -> AsnEncoding b -> Field a+optional = FieldOptional++defaulted :: (Eq b, Show b) => FieldName -> (a -> b) -> AsnEncoding b -> b -> Field a+defaulted name getVal enc defVal = FieldDefaulted name getVal defVal show (==) enc++objectIdentifier :: AsnEncoding ObjectIdentifier+objectIdentifier = EncUniversalValue (UniversalValueObjectIdentifier id mempty)++null :: AsnEncoding ()+null = null'++-- | Anything can be encoded as @NULL@ by simply discarding it. Typically,+--   encoding a type with more than one inhabitant as @NULL@ is a mistake,+--   so the more restrictive 'null' is to be preferred.+null' :: AsnEncoding a+null' = EncUniversalValue UniversalValueNull++integer :: AsnEncoding Integer+integer = EncUniversalValue (UniversalValueInteger id mempty)++integerRanged :: Integer -> Integer -> AsnEncoding Integer+integerRanged lo hi = EncUniversalValue+  (UniversalValueInteger id (Subtypes [SubtypeValueRange lo hi]))++word32 :: AsnEncoding Word32+word32 = EncUniversalValue (UniversalValueInteger fromIntegral (Subtypes [SubtypeValueRange 0 4294967295]))++word64 :: AsnEncoding Word64+word64 = EncUniversalValue (UniversalValueInteger fromIntegral (Subtypes [SubtypeValueRange 0 18446744073709551615]))++-- TODO: add a size subtype to this+octetStringWord32 :: AsnEncoding Word32+octetStringWord32 = EncUniversalValue (UniversalValueOctetString (LB.toStrict . Builder.toLazyByteString . Builder.word32BE) mempty)++octetStringWord8 :: AsnEncoding Word8+octetStringWord8 = EncUniversalValue +  ( UniversalValueOctetString +    ByteString.singleton+    mempty+  )++int32 :: AsnEncoding Int32+int32 = EncUniversalValue (UniversalValueInteger fromIntegral (Subtypes [SubtypeValueRange (-2147483648) 2147483647]))++word :: AsnEncoding Word+word = EncUniversalValue (UniversalValueInteger fromIntegral (Subtypes [SubtypeValueRange 0 (fromIntegral (maxBound :: Word))]))++int :: AsnEncoding Int+int = EncUniversalValue (UniversalValueInteger fromIntegral (Subtypes [SubtypeValueRange (fromIntegral (minBound :: Int)) (fromIntegral (maxBound :: Int))]))++octetString :: AsnEncoding ByteString+octetString = EncUniversalValue (UniversalValueOctetString id mempty)++utf8String :: AsnEncoding Text+utf8String = EncUniversalValue (UniversalValueTextualString Utf8String id mempty mempty)++universalValueTag :: UniversalValue a -> Int+universalValueTag = \case+  UniversalValueOctetString _ _ -> 4+  UniversalValueBoolean _ _ -> 1+  UniversalValueInteger _ _ -> 2+  UniversalValueNull -> 5+  UniversalValueObjectIdentifier _ _ -> 6+  UniversalValueTextualString typ _ _ _ -> tagNumStringType typ++-- For DER, which is what is actually targetted by this file,+-- I think that this is always Primitive.+univsersalValueConstruction :: UniversalValue a -> Construction+univsersalValueConstruction = \case+  UniversalValueOctetString _ _ -> Primitive+  UniversalValueBoolean _ _ -> Primitive+  UniversalValueInteger _ _ -> Primitive+  UniversalValueNull -> Primitive+  UniversalValueTextualString _ _ _ _ -> Primitive+  UniversalValueObjectIdentifier _ _ -> Primitive++-- | The ByteString that accompanies the tag does not+--   include its own length.+encodeBerInternal :: AsnEncoding a -> a -> TaggedByteString+encodeBerInternal x a = case x of+  EncRetag (TagAndExplicitness outerTag explicitness) e ->+    let TaggedByteString construction innerTag lbs = encodeBerInternal e a+     in case explicitness of+          Implicit -> TaggedByteString construction outerTag lbs+          Explicit -> TaggedByteString Constructed outerTag (encodeTaggedByteString (TaggedByteString construction innerTag lbs))+  EncUniversalValue p -> TaggedByteString (univsersalValueConstruction p) (makeTag Universal (universalValueTag p)) (encodePrimitiveBer p a)+  EncChoice (Choice conv _ f) -> case f (conv a) of+    ValueAndEncoding _ _ b enc2 -> encodeBerInternal enc2 b+  EncSequence fields -> TaggedByteString Constructed sequenceTag (foldMap (encodeField a) fields)+  -- It's kind of weird that sequence and sequence-of share the same tag,+  -- but hey, that's how the committee designed it.+  EncSequenceOf listify e -> TaggedByteString Constructed sequenceTag+    (foldMap (encodeTaggedByteString . encodeBerInternal e) (listify a))++-- Factor out some of the encoding stuff here into another function+encodeField :: a -> Field a -> LB.ByteString+encodeField a = \case+  FieldRequired _ func enc -> encodeTaggedByteString (encodeBerInternal enc (func a))+  FieldDefaulted _ func defVal _ eqVal enc ->+    let val = func a+     in if eqVal defVal val+          then mempty+          else encodeTaggedByteString (encodeBerInternal enc val)+  FieldOptional _ mfunc enc -> case mfunc a of+    Nothing -> mempty+    Just v -> encodeTaggedByteString (encodeBerInternal enc v)++encodeTaggedByteString :: TaggedByteString -> LB.ByteString+encodeTaggedByteString (TaggedByteString construction theTag lbs) =+  encodeTag construction theTag <> encodeLength (LB.length lbs) <> lbs++encodeTag :: Construction -> Tag -> LB.ByteString+encodeTag c (Tag tclass tnum)+  | tnum < 31 = LB.singleton (firstThreeBits .|. intToWord8 tnum)+  | otherwise = error "tag number above 30: write this"+  where+  !firstThreeBits = constructionBit c .|. tagClassBit tclass++encodeLength :: Int64 -> LB.ByteString+encodeLength x+  | x < 128 = LB.singleton (int64ToWord8 x)+  | otherwise =+      let totalOctets = fromIntegral (int64Log256 x + 1) :: Word8+       in LB.singleton (128 .|. totalOctets)+          <> lengthBE (fromIntegral x)++int64Log256 :: Int64 -> Int+int64Log256 x = unsafeShiftR (int64Log2 x) 3++int64Log2 :: Int64 -> Int+int64Log2 x = finiteBitSize x - 1 - countLeadingZeros x++int64ToWord8 :: Int64 -> Word8+int64ToWord8 = fromIntegral+{-# INLINE int64ToWord8 #-}++intToWord8 :: Int -> Word8+intToWord8 = fromIntegral+{-# INLINE intToWord8 #-}++encodePrimitiveBer :: UniversalValue a -> a -> LB.ByteString+encodePrimitiveBer p x = case p of+  UniversalValueTextualString typ f _ _ -> LB.fromStrict (encodeText typ (f x))+  UniversalValueOctetString f _ -> LB.fromStrict (f x)+  UniversalValueObjectIdentifier f _ -> oidBE (f x)+  UniversalValueBoolean f _ -> case f x of+    True -> LB.singleton 1+    False -> LB.singleton 0+  UniversalValueInteger f _ -> integerBE (f x)+  UniversalValueNull -> LB.empty++encodeText :: StringType -> Text -> ByteString+encodeText x t = case x of+  Utf8String -> TE.encodeUtf8 t+  _ -> error "encodeText: handle more ASN.1 string types"++lengthBE :: Int64 -> LB.ByteString+lengthBE i = if i > 0+  then Builder.toLazyByteString (goPos i)+  else error "lengthBE: handle the negative case"+  where+  goPos :: Int64 -> Builder+  goPos n1 = if n1 == 0+    then mempty+    else let (!n2,!byteVal) = quotRem n1 256+          in goPos n2 <> Builder.word8 (fromIntegral byteVal)++integerBE :: Integer -> LB.ByteString+integerBE i+  | i < 128 && i > (-129) = Builder.toLazyByteString (Builder.int8 (fromIntegral i))+  | otherwise = if i > 0+      then let lb = Builder.toLazyByteString (goPos i)+            in if LB.head lb > 127 then LB.cons 0 lb else lb+      else error "integerBE: handle the negative case"+  where+  goPos :: Integer -> Builder+  goPos n1 = if n1 == 0+    then mempty+    else let (!n2,!byteVal) = quotRem n1 256+          in goPos n2 <> Builder.word8 (fromIntegral byteVal)++oidBE :: ObjectIdentifier -> LB.ByteString+oidBE (ObjectIdentifier nums1)+  | sz > 2 =+      let !n1 = PM.indexPrimArray nums1 0+          !n2 = PM.indexPrimArray nums1 1+          -- !nums2 = Vector.unsafeDrop 2 nums1+          !firstOctet = fromIntegral n1 * 40 + fromIntegral n2 :: Word8+       in Builder.toLazyByteString (Builder.word8 firstOctet <> foldMapPrimArrayFromTo 2 sz multiByteBase127Encoding nums1)+  | otherwise = error "oidBE: OID with less than 3 identifiers"+  where+  sz = PM.sizeofPrimArray nums1++foldMapPrimArrayFromTo :: forall a b. (Prim a, Monoid b) => Int -> Int -> (a -> b) -> PrimArray a -> b+foldMapPrimArrayFromTo !start !end f !arr = go start where+  go !i+    | end > i = mappend (f (PM.indexPrimArray arr i)) (go (i+1))+    | otherwise = mempty++-- This function works fine on any integral type.+multiByteBase127Encoding :: Word -> Builder+multiByteBase127Encoding i0 =+  let (!i1,!byteVal) = quotRem i0 128+   in go i1 <> Builder.word8 (fromIntegral byteVal)+  where+  go n1 = if n1 == 0+    then mempty+    else let (!n2,!byteVal) = quotRem n1 128+          in go n2 <> Builder.word8 (128 .|. fromIntegral byteVal)
+ src/Language/Asn/ObjectIdentifier.hs view
@@ -0,0 +1,73 @@+{-# language BangPatterns #-}++module Language.Asn.ObjectIdentifier+  ( fromList+  , suffixSingleton+  , appendSuffix+  , isPrefixOf+  , stripPrefix+  , encodeString+  , encodeByteString+  , encodeText+  )+  where++import Control.Monad.ST (runST)+import Language.Asn.Types+import Data.Maybe (isJust)+import Data.Text (Text)+import Data.ByteString (ByteString)+import Data.Word (Word32)+import Data.Primitive (PrimArray(..))+import qualified Data.Text as Text+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as BC8+import qualified Data.Vector.Primitive as PV+import qualified Data.List as List+import qualified Data.Primitive as PM+import qualified GHC.Exts as E++fromList :: [Word] -> ObjectIdentifier+fromList = ObjectIdentifier . E.fromList++suffixSingleton :: Word -> ObjectIdentifierSuffix+suffixSingleton = ObjectIdentifierSuffix . singletonPrimArray++appendSuffix :: ObjectIdentifier -> ObjectIdentifierSuffix -> ObjectIdentifier+appendSuffix (ObjectIdentifier a) (ObjectIdentifierSuffix b) = ObjectIdentifier (mappend a b)++isPrefixOf :: ObjectIdentifier -> ObjectIdentifier -> Bool+isPrefixOf a b = isJust (stripPrefix a b)++stripPrefix :: ObjectIdentifier -> ObjectIdentifier -> Maybe ObjectIdentifierSuffix+stripPrefix (ObjectIdentifier a) (ObjectIdentifier b) =+  let lenA = PM.sizeofPrimArray a+   in if (lenA <= PM.sizeofPrimArray b) && (upgradeArray a == PV.take lenA (upgradeArray b))+        then Just (ObjectIdentifierSuffix (unsafeDropPrimArray lenA b))+        else Nothing++upgradeArray :: PM.Prim a => PrimArray a -> PV.Vector a+upgradeArray b@(PrimArray a) = PV.Vector 0 (PM.sizeofPrimArray b) (PM.ByteArray a)++singletonPrimArray :: PM.Prim a => a -> PrimArray a+singletonPrimArray a = runST $ do+  m <- PM.newPrimArray 1+  PM.writePrimArray m 0 a+  PM.unsafeFreezePrimArray m++unsafeDropPrimArray :: PM.Prim a => Int -> PrimArray a -> PrimArray a+unsafeDropPrimArray !n !xs = runST $ do+  m <- PM.newPrimArray (PM.sizeofPrimArray xs - n)+  PM.copyPrimArray m 0 xs n (PM.sizeofPrimArray xs - n)+  PM.unsafeFreezePrimArray m++encodeString :: ObjectIdentifier -> String+encodeString = List.intercalate "." . map show . E.toList . getObjectIdentifier++encodeByteString :: ObjectIdentifier -> ByteString+encodeByteString = BC8.pack . encodeString++encodeText :: ObjectIdentifier -> Text+encodeText = Text.pack . encodeString++
+ src/Language/Asn/Types.hs view
@@ -0,0 +1,10 @@+module Language.Asn.Types+  ( AsnEncoding+  , AsnDecoding+  , ObjectIdentifier(..)+  , ObjectIdentifierSuffix(..)+  , TagClass(..)+  , Explicitness(..)+  ) where++import Language.Asn.Types.Internal
+ src/Language/Asn/Types/Internal.hs view
@@ -0,0 +1,257 @@+{-# language BangPatterns #-}+{-# language CPP #-}+{-# language DeriveFunctor #-}+{-# language DeriveGeneric #-}+{-# language ExistentialQuantification #-}+{-# language GADTs #-}+{-# language GeneralizedNewtypeDeriving #-}+{-# language LambdaCase #-}+{-# language MagicHash #-}+{-# language OverloadedStrings #-}+{-# language RankNTypes #-}+{-# language StandaloneDeriving #-}++module Language.Asn.Types.Internal where++import Prelude hiding (sequence,null)+import Data.String (IsString)+import Data.ByteString (ByteString)+import Data.Text (Text)+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid (Monoid)+#endif+import Data.Semigroup (Semigroup)+import Data.Word+import Data.Primitive (PrimArray)+import GHC.Int (Int(..))+import Data.Hashable (Hashable(..))+import GHC.Generics (Generic)+import Data.Functor.Contravariant (Contravariant(..))+import qualified Data.ByteString.Lazy as LB+import qualified GHC.Exts as E++data AsnEncoding a+  = EncSequence [Field a]+  | forall b. EncSequenceOf (a -> [b]) (AsnEncoding b)+  | EncChoice (Choice a)+  | EncRetag TagAndExplicitness (AsnEncoding a)+  | EncUniversalValue (UniversalValue a)++instance Contravariant AsnEncoding where+  contramap f = \case+    EncRetag te y -> EncRetag te (contramap f y)+    EncUniversalValue u -> EncUniversalValue (contramap f u)+    EncSequence xs -> EncSequence (map (contramap f) xs)+    EncChoice c -> EncChoice (contramap f c)+    EncSequenceOf conv enc -> EncSequenceOf (conv . f) enc++data UniversalValue a+  = UniversalValueBoolean (a -> Bool) (Subtypes Bool)+  | UniversalValueInteger (a -> Integer) (Subtypes Integer)+  | UniversalValueNull+  | UniversalValueOctetString (a -> ByteString) (Subtypes ByteString)+  | UniversalValueTextualString StringType (a -> Text) (Subtypes Text) (Subtypes Char)+  | UniversalValueObjectIdentifier (a -> ObjectIdentifier) (Subtypes ObjectIdentifier)++instance Contravariant UniversalValue where+  contramap f = \case+    UniversalValueBoolean conv s -> UniversalValueBoolean (conv . f) s+    UniversalValueInteger conv s -> UniversalValueInteger (conv . f) s+    UniversalValueObjectIdentifier conv s -> UniversalValueObjectIdentifier (conv . f) s+    UniversalValueOctetString conv s -> UniversalValueOctetString (conv . f) s+    UniversalValueTextualString typ conv s1 s2 -> UniversalValueTextualString typ (conv . f) s1 s2+    UniversalValueNull -> UniversalValueNull++newtype Subtypes a = Subtypes { getSubtypes :: [Subtype a] }+  deriving (Semigroup,Monoid)++-- | Note: we deviate slightly from the actual definition of an object+-- identifier. Technically, each number of an OID should be allowed to+-- be an integer of unlimited size. However, we are intentionally unfaithful+-- to this specification because in practice, there are no OIDs that use+-- integers above a 32-bit word, so we just use the machine's native word+-- size.+newtype ObjectIdentifier = ObjectIdentifier+  { getObjectIdentifier :: PrimArray Word+  } deriving (Eq,Ord,Show,Generic)++instance Hashable ObjectIdentifier where+  hash (ObjectIdentifier v) = hash (E.toList v)+  hashWithSalt s (ObjectIdentifier v) = hashWithSalt s (E.toList v)++newtype ObjectIdentifierSuffix = ObjectIdentifierSuffix+  { getObjectIdentifierSuffix :: PrimArray Word+  } deriving (Eq,Ord,Show,Generic)++instance Hashable ObjectIdentifierSuffix where+  hash (ObjectIdentifierSuffix v) = hash (E.toList v)+  hashWithSalt s (ObjectIdentifierSuffix v) = hashWithSalt s (E.toList v)++data Subtype a+  = SubtypeSingleValue a -- This also acts as PermittedAlphabet+  | SubtypeValueRange a a++data StringType+  = Utf8String+  | NumericString+  | PrintableString+  | TeletexString+  | VideotexString+  | IA5String+  | GraphicString+  | VisibleString+  | GeneralString+  | UniversalString+  | CharacterString+  | BmpString++data Explicitness = Explicit | Implicit+data TagAndExplicitness = TagAndExplicitness Tag Explicitness++data Choice a = forall b. Choice (a -> b) [b] (b -> ValueAndEncoding)++instance Contravariant Choice where+  contramap f (Choice conv bs bToValEnc) =+    Choice (conv . f) bs bToValEnc++data ValueAndEncoding = forall b. ValueAndEncoding Int OptionName b (AsnEncoding b)+data Field a+  = forall b. FieldRequired FieldName (a -> b) (AsnEncoding b)+  | forall b. FieldOptional FieldName (a -> Maybe b) (AsnEncoding b)+  | forall b. FieldDefaulted FieldName (a -> b) b (b -> String) (b -> b -> Bool) (AsnEncoding b)++instance Contravariant Field where+  contramap f = \case+    FieldRequired name g enc -> FieldRequired name (g . f) enc+    FieldOptional name g enc -> FieldOptional name (g . f) enc+    FieldDefaulted name g b1 b2 b3 enc -> FieldDefaulted name (g . f) b1 b2 b3 enc++data TaggedByteString = TaggedByteString !Construction !Tag !LB.ByteString+data TaggedStrictByteString = TaggedStrictByteString !Construction !Tag !ByteString+data Construction = Constructed | Primitive+  deriving (Show,Eq)++newtype FieldName = FieldName { getFieldName :: String }+  deriving (IsString)+newtype OptionName = OptionName { getOptionName :: String }+  deriving (IsString)++data TagClass+  = Universal+  | Application+  | Private+  | ContextSpecific+  deriving (Show,Eq)++data Tag = Tag+  { tagClass :: TagClass+  , tagNumber :: Int+  } deriving (Show,Eq)++fromIntegerTagAndExplicitness :: Integer -> TagAndExplicitness+fromIntegerTagAndExplicitness n = TagAndExplicitness+  (Tag ContextSpecific (fromIntegral n))+  Explicit++fromIntegerTag :: Integer -> Tag+fromIntegerTag n = Tag ContextSpecific (fromIntegral n)++------------------------------+-- Stuff specific to decoding+------------------------------++data AsnDecoding a+  = AsnDecodingUniversal (UniverseDecoding a)+  | forall b. AsnDecodingSequenceOf ([b] -> a) (AsnDecoding b)+  | forall b. AsnDecodingConversion (AsnDecoding b) (b -> Either String a)+  | AsnDecodingRetag TagAndExplicitness (AsnDecoding a)+  | AsnDecodingSequence (FieldDecoding a)+  | AsnDecodingChoice [OptionDecoding a]++deriving instance Functor AsnDecoding++data Ap f a where+  Pure :: a -> Ap f a+  Ap :: f a -> Ap f (a -> b) -> Ap f b++instance Functor (Ap f) where+  fmap f (Pure a)   = Pure (f a)+  fmap f (Ap x y)   = Ap x ((f .) <$> y)++instance Applicative (Ap f) where+  pure = Pure+  Pure f <*> y = fmap f y+  Ap x y <*> z = Ap x (flip <$> y <*> z)++data OptionDecoding a = OptionDecoding OptionName (AsnDecoding a)+  deriving (Functor)++newtype FieldDecoding a = FieldDecoding (Ap FieldDecodingPart a)+  deriving (Functor,Applicative)++data FieldDecodingPart a+  = FieldDecodingRequired FieldName (AsnDecoding a)+  | FieldDecodingDefault FieldName (AsnDecoding a) a (a -> String)+  | forall b. FieldDecodingOptional FieldName (AsnDecoding b) (Maybe b -> a)++data UniverseDecoding a+  = UniverseDecodingInteger (Integer -> a) (Subtypes Integer)+  | UniverseDecodingTextualString StringType (Text -> a) (Subtypes Text) (Subtypes Char)+  | UniverseDecodingOctetString (ByteString -> a) (Subtypes ByteString)+  | UniverseDecodingObjectIdentifier (ObjectIdentifier -> a) (Subtypes ObjectIdentifier)+  | UniverseDecodingNull a+  deriving (Functor)++newtype DecodePart a = DecodePart { getDecodePart :: ByteString -> Either String (a,ByteString) }+  deriving (Functor)++instance Applicative DecodePart where+  pure a = DecodePart (\bs -> Right (a,bs))+  DecodePart f <*> DecodePart g = DecodePart $ \bs1 -> do+    (h,bs2) <- f bs1+    (a,bs3) <- g bs2+    return (h a, bs3)++runAp :: Applicative g => (forall x. f x -> g x) -> Ap f a -> g a+runAp _ (Pure x) = pure x+runAp u (Ap f x) = flip id <$> u f <*> runAp u x++liftAp :: f a -> Ap f a+liftAp x = Ap x (Pure id)+{-# INLINE liftAp #-}++--------------------------+-- Functions common to encoding and decoding+--------------------------++-- Bit six is 1 when a value is constructed.+constructionBit :: Construction -> Word8+constructionBit = \case+  Constructed -> 32+  Primitive -> 0++-- Controls upper two bits in the octet+tagClassBit :: TagClass -> Word8+tagClassBit = \case+  Universal -> 0+  Application -> 64+  ContextSpecific -> 128+  Private -> 192++sequenceTag :: Tag+sequenceTag = Tag Universal 16++tagNumStringType :: StringType -> Int+tagNumStringType = \case+  Utf8String -> 12+  NumericString -> 18+  PrintableString -> 19+  TeletexString -> 20+  VideotexString -> 21+  IA5String -> 22+  GraphicString -> 25+  VisibleString -> 26+  GeneralString -> 27+  UniversalString -> 28+  CharacterString -> 29+  BmpString -> 30