在 Haskell 中解析 OUI 数据库
在这篇文章中,我们将展示一种方法并提供一个能够解析 IEEE OUI 列表和 IEEE IAB 列表的 Haskell 模块。
虽然我们的代码只将数据库解析为对象形式,并没有将它们插入到能够快速 MAC 地址 -> 供应商查找的树中,但它基于 Attoparsec,提供了良好的性能和更改的高灵活性。
要解析 oui.txt,只需调用 parseOUIFile "oui.txt"。注意虽然为解析错误提供了 Either 错误消息,但当前版本不为文件 IO 错误提供基于 Either 的错误。
OUIParser.hs
{-# LANGUAGE OverloadedStrings #-}
module OUIParser where
{-
IEEE OUI / IAB 列表的解析器
下载地址
https://standards.ieee.org/develop/regauth/oui/oui.txt
https://standards.ieee.org/develop/regauth/iab/iab.txt
版权所有 (c) 2014 Uli Koehler
在 Apache License v2.0 下许可
版本 1.0
-}
import Prelude hiding (takeWhile)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.ByteString.Char8 (ByteString)
import Data.Attoparsec.Char8 (skipSpace, endOfLine, isEndOfLine, isDigit_w8, hexadecimal, char, isSpace_w8)
import Data.Attoparsec.ByteString.Lazy
import GHC.Word (Word8)
import Control.Applicative
import Data.Char (isHexDigit)
import Control.Monad
import qualified Data.ByteString.Internal as BI (c2w, w2c)
import Debug.Trace
isHexDigit_w8 = isHexDigit . BI.w2c
skipWhile1 :: (Word8 -> Bool) -> Parser ()
skipWhile1 p = skip p *> skipWhile p
type OUIPrefix = (Int, Int, Int)
type IABRange = (Int, Int)
type CompanyId = ByteString
data OUIInfo = OUIInfo {
ouiPrefix :: OUIPrefix,
ouiCompanyId :: CompanyId,
ouiCompanyName :: ByteString,
ouiCompanyAddress :: ByteString
} deriving (Show)
data IABInfo = IABInfo {
iabPrefix :: OUIPrefix,
iabRange :: IABRange,
iabCompanyName :: ByteString,
iabCompanyAddress :: ByteString
} deriving (Show)
-- 跳过空格,但不跳过 \n
skipSpaceNoNewline = skipWhile (\x -> isSpace_w8 x && not (isEndOfLine x))
skipSpaceNoNewline1 = skipWhile1 (\x -> isSpace_w8 x && not (isEndOfLine x))
parseOUIFile :: FilePath -> IO (Either String [OUIInfo])
parseOUIFile filename = do
fileContent <- LB.readFile filename
return $ eitherResult $ parse ouiFileParser fileContent
parseIABFile :: FilePath -> IO (Either String [IABInfo])
parseIABFile filename = do
fileContent <- LB.readFile filename
return $ eitherResult $ parse iabFileParser fileContent
ouiFileParser :: Parser [OUIInfo]
ouiFileParser = do
ouiHeaderParser
result <- many ouiRecordParser
return result
iabFileParser :: Parser [IABInfo]
iabFileParser = do
iabHeaderParser
result <- many iabRecordParser
return result
ouiHeaderParser :: Parser ()
ouiHeaderParser = do
-- 前导行:空
skipSpaceNoNewline >> endOfLine
-- OUI 头
skipSpaceNoNewline >> string "OUI/MA-L"
skipSpaceNoNewline >> string "Organization"
endOfLine
-- 公司 ID 头
skipSpaceNoNewline >> string "company_id"
skipSpaceNoNewline >> string "Organization"
skipSpaceNoNewline >> endOfLine
-- 地址头
skipSpaceNoNewline >> string "Address" >> endOfLine
---- 两个空白行
skipSpaceNoNewline >> endOfLine
skipSpaceNoNewline >> endOfLine
-- 不返回任何内容
return ()
iabHeaderParser :: Parser ()
iabHeaderParser = do
-- 前导行:空
skipSpaceNoNewline >> endOfLine
-- OUI 头
skipSpaceNoNewline >> string "OUI"
skipSpaceNoNewline >> string "Organization"
endOfLine
-- 公司 ID 头
skipSpaceNoNewline >> string "IAB Range"
skipSpaceNoNewline >> string "Organization"
skipSpaceNoNewline >> endOfLine
-- 地址头
skipSpaceNoNewline >> string "Address" >> endOfLine
---- 两个空白行
skipSpaceNoNewline >> endOfLine
skipSpaceNoNewline >> endOfLine
-- 不返回任何内容
return ()
-- 消耗一行,返回非空的剥离部分
stripLineParser1 :: Parser ByteString
stripLineParser1 = skipSpaceNoNewline *> takeWhile1 (not . isEndOfLine) <* endOfLine
addressLineParser :: Parser ByteString
addressLineParser = skipSpaceNoNewline1 *> takeWhile (not . isEndOfLine) <* endOfLine
ouiPrefixParser :: Parser OUIPrefix
ouiPrefixParser = do
part1 <- hexadecimal
char '-'
part2 <- hexadecimal
char '-'
part3 <- hexadecimal
return $ (part1, part2, part3)
iabRangeParser :: Parser IABRange
iabRangeParser = do
part1 <- hexadecimal
char '-'
part2 <- hexadecimal
return (part1, part2)
-- 第一个 OUI 记录行的解析器
ouiLineParser :: Parser (OUIPrefix, ByteString)
ouiLineParser = do
skipSpace
ouiPrefix <- ouiPrefixParser
skipSpace
string "(hex)"
-- 公司名称
companyName <- stripLineParser1
return (ouiPrefix, companyName)
-- 解析包含公司 ID 的行
-- 忽略公司名称,因为它与
-- 公司 ID 行中的相同(并且 PRIVATE 保留在此行中不包含它)
companyLineParser :: Parser ByteString
companyLineParser = do
skipSpace
companyId <- takeWhile1 isHexDigit_w8
skipSpace
string "(base 16)"
-- 跳过公司
skipWhile (not . isEndOfLine)
endOfLine
return companyId
iabLineParser :: Parser IABRange
iabLineParser = do
skipSpace
iabRange <- iabRangeParser
skipSpace
string "(base 16)"
-- 跳过公司
skipWhile (not . isEndOfLine)
endOfLine
return iabRange
iabRecordParser :: Parser IABInfo
iabRecordParser = do
(ouiPrefix, companyName) <- ouiLineParser
iabRange <- iabLineParser
companyAddress <- many $ addressLineParser
-- 空行
endOfLine
return $ IABInfo ouiPrefix iabRange companyName $ B.unlines companyAddress
ouiRecordParser :: Parser OUIInfo
ouiRecordParser = do
(ouiPrefix, companyName) <- ouiLineParser
companyId <- companyLineParser
companyAddress <- many $ addressLineParser
-- 空行
endOfLine
return $ OUIInfo ouiPrefix companyId companyName $ B.unlines companyAddressCheck out similar posts by category:
Haskell
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow