top page > computer > web > rfc > x_690 > 8_1_common > coding > decode.html
更新日:
文責: 重城良国

X.690: ASN.1のBER方式の共通部分のデコード関数

(工事中 85%)

ここで扱うファイル

タグのデコード

タグの1バイト目のデコード

タグの1バイト目を解析する。8,7ビット目がタグのクラスを、6ビット目はデータのクラスを表す。残りの5から1ビットが0x1fであれば、タグの番号は続くバイト列で決まる。そうでなければ、この部分がタグの番号を示す。

decodeTag1 :: Analyzer BS.ByteString
	(TagClass, DataClass, Maybe Word8)
decodeTag1 = flip fmap token $ \w -> let
	tc = case w `shiftR` 6 of
		0 -> Universal
		1 -> Application
		2 -> ContextSpecific
		3 -> Private
		_ -> error "never occur"
	dc = if testBit w 5
		then Constructed
		else Primitive
	tn = case w .&. 0x1f of
		0x1f -> Nothing
		n	| n < 0x1f -> Just n
			| otherwise ->
				error "never occur" in
	(tc, dc, tn)

試してみる。

% ghci testDecodeAsn1Common.hs
*Main> runAnalyzer decodeTag1 cert
Just ((Universal,Constructed,Just 16), "\130\ETXN0\130\STX
...
\155\186\ACK\172")

タグの残りのバイトをデコード

8ビット目が1であれば後続のバイトが存在し、0であればそこで終了。読み込んできた値を保持しておき、次のバイトがあれば保持してあった値を7ビット左シフトしたうえで、そのバイトの値を足していく。

decodeTagR :: Integer -> Analyzer BS.ByteString Integer
decodeTagR n = token >>= \w -> if testBit w 7
	then decodeTagR $
		n `shiftL` 7 .|. fromIntegral (w .&. 0x7f)
	else return $ n `shiftL` 7 .|. fromIntegral w

1バイト目のデコードの結果の長さのフィールドがJust [番号]であれば、その[番号]がそのままタグの番号となり、Nothingならば残りのバイトを読み込む。

decodeTag :: Analyzer BS.ByteString Asn1Tag
decodeTag = decodeTag1 >>= \(tc, dc, mtn) -> maybe
	(Asn1Tag tc dc <$> decodeTagR 0)
	(return . Asn1Tag tc dc . fromIntegral)
	mtn

試してみる。

% ghci testDecodeAsn1Common.hs
*Main> runAnalyzer decodeTag cert
Just ((Universal,Constructed,16), "\130\ETXN0\130\STX
...
\155\186\ACK\172")

タグ番号とデータ形式のエラーチェック

以下のような制約がある。

これらの制約をdecodeTagRに織り込む。

最上位バイトの制約

decodeTagR :: ...
decodeTagR 0 = token >>= \w -> do
	when (w == 0x80) $ fail
		"Redundant byte for tag number"
	if testBit w 7
		then decodeTagR $ fromIntegral (w .&. 0x7f)
		else return $ fromIntegral w
decodeTagR n = token >>= if testBit w 7
	...

30以下の場合の制約

decodeTagR0 :: Analyzer BS.ByteString Integer
decodeTagR0 = decodeTagR 0 >>= \n -> do
	when (n <= 30) $ fail
		"Use single byte for tag number 0 - 30"
	return n
decodeTag :: ...
decodeTag = decodeTag1 >>= \(tc, dc, mtn) -> maybe
	(Asn1Tag tc dc <$> decodeTagR0)
	(return . Asn1Tag tc dc . fromIntegral)
	mtn

変更後のモジュール

DecodeAsn1Common.hs

エラー用のテストデータを用意する。

under30Error, redundantTagNumberError :: BS.ByteString
under30Error = "\x1f\x1e"
redundantTagNumberError = "\x1f\x80\x7f"

試してみる。

*Main> runAnalyzer decodeTag redundantTagNumberError
Left "Redundant byte for tag number"
*Main> runAnalyzer decodeTag under30Error
Left "Use single byte for tag number 0 - 30"

データの長さのデコード

長さを指定しない場合にNothingを返すために返すデータはMaybe Integerとする。

データの長さの1バイト目のデコード

以下の3通りに場合分けする。

よって返す値はMaybe (Either Int Int)で良い。

decodeLength1 :: Analyzer ByteString
	(Maybe (Either Word8 Word8))
decodeLength1 = flip fmap token $ \w -> if not (testBit w 7)
	then Just $ Right w
	else let ln = w .&. 0x7f in
		if ln == 0 then Nothing else Just $ Left ln

HaskellのData.Bitsライブラリではビットを0ビットから数え始めることに注意。

データの長さの残りのバイトのデコード

与えられた数だけのバイトを取り、8ビットずつ左シフトしながらつなげていく。

decodeLengthR ::
	Integer -> Int -> Analyzer ByteString Integer
decodeLengthR ln 0 = return ln
decodeLengthR ln n = token >>= \w -> decodeLengthR
	(ln `shiftL` 8 .|. fromIntegral w)
	(n - 1)

データの長さのデコード

データの長さを指定しない場合はNothingを指定する場合はIntegerを返す。また、Primitiveなデータでデータの長さを指定しない場合はエラーとする。

decodeLength :: Analyzer ByteString (Maybe Integer)
decodeLength = decodeLength1 >>= \ln1 -> case ln1 of
	Just (Right ln) -> return . Just $ fromIntegral ln
	Just (Left n) -> Just <$> decodeLengthR 0 n
	_ -> return Nothing

データ部分のデコード

再帰フラグ

再帰的にデコードするかどうかを決めるためのフラグが必要。このフラグ自体が再帰的に表現されている必要がある。

data RecFlag = NoRec | Rec1 [RecFlag] | RecAll deriving Show

NoRecは再帰的にデコードしないことを示し、RecAllは最深部まで再帰的にデコードすることを示す。Rec1はまずは一番上のレベルでデコードし次のレベルもデコードしたうえで、それぞれに対して再帰的にデコードするかどうかを再度指定する。

データ部分のデコード

長さを指定した場合は、今のところは、生のバイト列を取り出す。長さを指定しない形式の場合には、再帰的に読み込まないとデータの終わりを検出できない。

decodeCnt :: DataClass -> RecFlag ->
	Maybe Integer -> Analyzer ByteString Asn1Data
decodeCnt Constructed RecAll (Just l) = tokens l >>= \d ->
	case runAnalyzer (listAll $ decode1 RecAll) d of
		Right (r, "") -> return $ Asn1DataAsn1 r
		Left e -> fail e
		_ -> error "never occur"
decodeCnt Constructed (Rec1 f) (Just l) = tokens l >>= \d ->
	case runAnalyzer (listMap decode1 f) d of
		Right (r, "") -> return $ Asn1DataAsn1 r
		Left e -> fail e
		_ -> error "never occur"
decodeCnt _ _ (Just ln) = Asn1DataRaw <$> tokens ln
decodeCnt Constructed (Rec1 rfs) _ = Asn1DataAsn1 <$>
	mapWhileM (/= endOfContents) decode1 rfs
decodeCnt Constructed rf _ = Asn1DataAsn1 <$>
	loopWhileM (/= endOfContents) (decode1 rf)
decodeCnt _ _ _ = fail "Primitive needs length"
endOfContents :: Asn1
endOfContents = Asn1
	(Asn1Tag Universal Primitive 0) (Asn1DataRaw "")
loopWhileM :: Monad m => (a -> Bool) -> m a -> m [a]
loopWhileM p m = m >>= \x -> if p x
	then (x :) `liftM` loopWhileM p m
	else return [x]
mapWhileM :: Monad m =>
	(b -> Bool) -> (a -> m b) -> [a] -> m [b]
mapWhileM _ _ [] = return []
mapWhileM p m (x : xs) = m x >>= \y -> if p y
	then (y :) `liftM` mapWhileM p m xs
	else return [y]

全体のデコード

decode1 :: RecFlag -> Analyzer BS.ByteString Asn1
decode1 rf = decodeTag >>= \t@(Asn1Tag _ dc _) ->
	Asn1 t <$> (decodeLength >>= decodeCnt dc rf)

データ型へもどる 「存在型を使う」へ (工事中 0%)

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics