Die OUI-Datenbank in Haskell parsen
In diesem Beitrag zeigen wir eine Methode und stellen ein Haskell-Modul zur Verfügung, das sowohl die IEEE OUI-Liste als auch die IEEE IAB-Liste parsen kann.
Unser Code parst die Datenbanken zwar nur in eine Objektform und fügt sie nicht in einen Baum ein, der schnelle MAC-Adressen-Suche nach Hersteller ermöglicht, aber er basiert auf Attoparsec und bietet gute Performance und hohe Flexibilität für Änderungen.
Um oui.txt zu parsen, rufe einfach parseOUIFile "oui.txt" auf. Beachte, dass zwar eine Either-Fehlermeldung für Parsing-Fehler bereitgestellt wird, die aktuelle Version jedoch keine Either-basierten Fehler für Datei-IO-Fehler liefert.
OUIParser.hs
{-# LANGUAGE OverloadedStrings #-}
module OUIParser where
{-
Parser für die IEEE OUI / IAB-Liste
Download unter
https://standards.ieee.org/develop/regauth/oui/oui.txt
https://standards.ieee.org/develop/regauth/iab/iab.txt
Copyright (c) 2014 Uli Koehler
Lizenziert unter der Apache License v2.0
Version 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)
-- Leerzeichen überspringen, aber nicht \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
-- Führende Zeile: leer
skipSpaceNoNewline >> endOfLine
-- OUI-Header
skipSpaceNoNewline >> string "OUI/MA-L"
skipSpaceNoNewline >> string "Organization"
endOfLine
-- Company-ID-Header
skipSpaceNoNewline >> string "company_id"
skipSpaceNoNewline >> string "Organization"
skipSpaceNoNewline >> endOfLine
-- Adress-Header
skipSpaceNoNewline >> string "Address" >> endOfLine
---- Zwei Leerzeilen
skipSpaceNoNewline >> endOfLine
skipSpaceNoNewline >> endOfLine
-- Nichts zurückgeben
return ()
iabHeaderParser :: Parser ()
iabHeaderParser = do
-- Führende Zeile: leer
skipSpaceNoNewline >> endOfLine
-- OUI-Header
skipSpaceNoNewline >> string "OUI"
skipSpaceNoNewline >> string "Organization"
endOfLine
-- Company-ID-Header
skipSpaceNoNewline >> string "IAB Range"
skipSpaceNoNewline >> string "Organization"
skipSpaceNoNewline >> endOfLine
-- Adress-Header
skipSpaceNoNewline >> string "Address" >> endOfLine
---- Zwei Leerzeilen
skipSpaceNoNewline >> endOfLine
skipSpaceNoNewline >> endOfLine
-- Nichts zurückgeben
return ()
-- Eine Zeile konsumieren, den nicht-leeren abgeschnittenen Teil zurückgeben
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)
-- Parser für die erste OUI-Datensatzzeile
ouiLineParser :: Parser (OUIPrefix, ByteString)
ouiLineParser = do
skipSpace
ouiPrefix <- ouiPrefixParser
skipSpace
string "(hex)"
-- Firmenname
companyName <- stripLineParser1
return (ouiPrefix, companyName)
-- Parst die Zeile mit der Company-ID
-- Ignoriert den Firmennamen, da er derselbe ist wie
-- in der Company-ID-Zeile (und PRIVATE-Reservierungen enthalten ihn nicht in dieser Zeile)
companyLineParser :: Parser ByteString
companyLineParser = do
skipSpace
companyId <- takeWhile1 isHexDigit_w8
skipSpace
string "(base 16)"
-- Firma überspringen
skipWhile (not . isEndOfLine)
endOfLine
return companyId
iabLineParser :: Parser IABRange
iabLineParser = do
skipSpace
iabRange <- iabRangeParser
skipSpace
string "(base 16)"
-- Firma überspringen
skipWhile (not . isEndOfLine)
endOfLine
return iabRange
iabRecordParser :: Parser IABInfo
iabRecordParser = do
(ouiPrefix, companyName) <- ouiLineParser
iabRange <- iabLineParser
companyAddress <- many $ addressLineParser
-- Leerzeile
endOfLine
return $ IABInfo ouiPrefix iabRange companyName $ B.unlines companyAddress
ouiRecordParser :: Parser OUIInfo
ouiRecordParser = do
(ouiPrefix, companyName) <- ouiLineParser
companyId <- companyLineParser
companyAddress <- many $ addressLineParser
-- Leerzeile
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