net-spider-rpl 0.2.2.0 → 0.2.3.0
raw patch · 3 files changed
+119/−71 lines, 3 filesdep +conduitdep +conduit-parsedep +mtlPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: conduit, conduit-parse, mtl, safe-exceptions
API changes (from Hackage documentation)
- NetSpider.RPL.ContikiNG: instance GHC.Classes.Eq NetSpider.RPL.ContikiNG.Heading
- NetSpider.RPL.ContikiNG: instance GHC.Classes.Eq NetSpider.RPL.ContikiNG.ParseError
- NetSpider.RPL.ContikiNG: instance GHC.Classes.Ord NetSpider.RPL.ContikiNG.Heading
- NetSpider.RPL.ContikiNG: instance GHC.Classes.Ord NetSpider.RPL.ContikiNG.ParseError
- NetSpider.RPL.ContikiNG: instance GHC.Exception.Type.Exception NetSpider.RPL.ContikiNG.ParseError
- NetSpider.RPL.ContikiNG: instance GHC.Show.Show NetSpider.RPL.ContikiNG.Heading
- NetSpider.RPL.ContikiNG: instance GHC.Show.Show NetSpider.RPL.ContikiNG.ParseError
+ NetSpider.RPL.ContikiNG: parseStream :: MonadThrow m => Parser Timestamp -> ConduitT Line (Either FoundNodeDIO FoundNodeDAO) m ()
+ NetSpider.RPL.ContikiNG: parserFoundNodeDAO :: Monad m => Parser Timestamp -> ConduitParser Line m [FoundNodeDAO]
+ NetSpider.RPL.ContikiNG: parserFoundNodeDIO :: Monad m => Parser Timestamp -> ConduitParser Line m FoundNodeDIO
+ NetSpider.RPL.ContikiNG: type Line = Text
Files
- ChangeLog.md +7/−0
- net-spider-rpl.cabal +7/−3
- src/NetSpider/RPL/ContikiNG.hs +105/−68
ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for net-spider-rpl +## 0.2.3.0 -- 2019-09-24++### ContikiNG module++* Add `parseStream`, `parserFoundNodeDIO`, `parserFoundNodeDAO` functions.++ ## 0.2.2.0 -- 2019-09-23 ### RPL module
net-spider-rpl.cabal view
@@ -1,5 +1,5 @@ name: net-spider-rpl-version: 0.2.2.0+version: 0.2.3.0 author: Toshio Ito <debug.ito@gmail.com> maintainer: Toshio Ito <debug.ito@gmail.com> license: BSD3@@ -22,7 +22,7 @@ default-language: Haskell2010 hs-source-dirs: src ghc-options: -Wall -fno-warn-unused-imports- default-extensions: StrictData+ default-extensions: StrictData, OverloadedStrings other-extensions: OverloadedStrings, DeriveGeneric exposed-modules: NetSpider.RPL, NetSpider.RPL.FindingID,@@ -38,7 +38,11 @@ text >=1.2.3.1 && <1.3, ip >=1.3.0 && <1.5, hashable >=1.2.7.0 && <1.3,- time >=1.8.0.2 && <1.9+ time >=1.8.0.2 && <1.9,+ conduit-parse >=0.2.1.0 && <0.3,+ conduit >=1.3.1 && <1.4,+ mtl >=2.2.2 && <2.3,+ safe-exceptions >=0.1.7.0 && <0.2 -- executable net-spider-rpl -- default-language: Haskell2010
src/NetSpider/RPL/ContikiNG.hs view
@@ -12,7 +12,13 @@ ( -- * Parser functions parseFile, parseFileHandle,+ parseStream, -- * Parser components+ -- ** Parsers for line stream+ parserFoundNodeDIO,+ parserFoundNodeDAO,+ Line,+ -- ** Parser for text Parser, pCoojaLogHead, pCoojaLogHead',@@ -20,23 +26,33 @@ ) where import Control.Applicative ((<|>), (<$>), (<*>), (*>), (<*), many, optional)-import Control.Exception (Exception, throwIO)+import Control.Exception.Safe (MonadThrow) import Control.Monad (void)+import Control.Monad.Except (throwError)+import Control.Monad.IO.Class (liftIO, MonadIO)+import Data.Bifunctor (Bifunctor(first)) import Data.Bits (shift) import Data.Char (isDigit, isHexDigit, isSpace)+import Data.Conduit (ConduitT, mapOutput, yield, runConduit, (.|))+import qualified Data.Conduit.List as CL+import Data.Conduit.Parser (ConduitParser)+import qualified Data.Conduit.Parser as CP+import Data.Either (partitionEithers) import Data.Int (Int64) import Data.List (sortOn, reverse) import Data.Maybe (listToMaybe) import Data.Monoid ((<>))-import Data.Text (pack)+import Data.Text (Text, pack, unpack)+import qualified Data.Text.IO as TIO import qualified Data.Time as Time+import Data.Void (absurd) import Data.Word (Word16) import GHC.Exts (groupWith) import Net.IPv6 (IPv6) import qualified Net.IPv6 as IPv6 import NetSpider.Found (FoundNode(..), FoundLink(..), LinkState(LinkToTarget)) import NetSpider.Timestamp (Timestamp, fromEpochMillisecond, fromLocalTime, fromZonedTime)-import System.IO (withFile, IOMode(ReadMode), hGetLine, hIsEOF, Handle)+import System.IO (withFile, IOMode(ReadMode), hGetLine, hIsEOF, Handle, stderr) import qualified Text.ParserCombinators.ReadP as P import Text.Read (readEither) @@ -49,11 +65,6 @@ type Parser = P.ReadP -data ParseError = ParseError String- deriving (Show,Eq,Ord)--instance Exception ParseError- runParser :: Parser a -> String -> Maybe a runParser p input = extract $ sortPairs $ P.readP_to_S p input where@@ -97,53 +108,83 @@ parseFileHandle :: Parser Timestamp -- ^ Parser for log prefix -> Handle -- ^ File handle to read -> IO ([FoundNodeDIO], [FoundNodeDAO])-parseFileHandle pTimestamp handle = go ([], [])+parseFileHandle pTimestamp handle =+ fmap partitionEithers $ runConduit (the_source .| parseStream pTimestamp .| CL.consume) where- go (acc_dio, acc_dao) = do- mentry <- parseOneEntry pTimestamp $ tryGetLine handle- case mentry of- Nothing -> return (reverse acc_dio, reverse acc_dao)- Just (PEDIO fl) -> go (fl : acc_dio, acc_dao)- Just (PEDAO srs) -> go (acc_dio, srs ++ acc_dao)- Just PEMisc -> go (acc_dio, acc_dao)- tryGetLine h = do- eof <- hIsEOF h+ the_source = do+ eof <- liftIO $ hIsEOF handle if eof- then return Nothing- else Just <$> hGetLine h+ then return ()+ else do+ yield =<< (liftIO $ TIO.hGetLine handle)+ the_source data ParseEntry = PEDIO FoundNodeDIO | PEDAO [FoundNodeDAO]- | PEMisc+ | PELine (Maybe Line) deriving (Show,Eq) --- | Return 'Nothing' when it finishes reading.-parseOneEntry :: Parser Timestamp -> IO (Maybe String) -> IO (Maybe ParseEntry)-parseOneEntry pTimestamp getL = impl+-- | Same as 'parseFile' but as an conduit.+--+-- @since 0.2.3.0+parseStream :: (MonadThrow m)+ => Parser Timestamp -- ^ Parser for log prefix+ -> ConduitT Line (Either FoundNodeDIO FoundNodeDAO) m ()+parseStream pTimestamp = go where- impl = do- mline <- getL- case mline of- Nothing -> return Nothing- Just line ->- case runParser ((,) <$> pTimestamp <*> (pLogHead *> pHeading)) line of- Nothing -> return $ Just PEMisc- Just (ts, HDIO addr node) -> proceedDIO ts addr node- Just (ts, HDAO route_num) -> proceedDAO ts route_num+ go = do+ got <- mapOutput absurd $ CP.runConduitParser merged_parser+ case got of+ PEDIO dio -> yield (Left dio) >> go+ PEDAO daos -> mapM_ (yield . Right) daos >> go+ PELine Nothing -> return ()+ PELine (Just _) -> go+ merged_parser = (PEDIO <$> parserFoundNodeDIO pTimestamp)+ <|> (PEDAO <$> parserFoundNodeDAO pTimestamp)+ <|> (PELine <$> awaitM)++-- | One line text.+--+-- @since 0.2.3.0+type Line = Text++-- | Parse stream of log lines for a 'FoundNodeDIO'.+--+-- @since 0.2.3.0+parserFoundNodeDIO :: Monad m+ => Parser Timestamp -- ^ Text parser for log head.+ -> ConduitParser Line m FoundNodeDIO+parserFoundNodeDIO pTimestamp = do+ line <- CP.await+ case runParser pDIOHead $ unpack line of+ Nothing -> throwError $ CP.Unexpected ("Not a log line head of local findings about DIO.")+ Just (ts, (self_addr, dio_node)) -> proceedDIO ts self_addr dio_node+ where+ pDIOHead = (,) <$> pTimestamp <*> (pLogHead *> pDIONode) withPrefix p = pTimestamp *> pLogHead *> p proceedDIO ts addr node = do- mlinks <- readUntil getL (withPrefix pDIONeighbor) (withPrefix pDIONeighborEnd)- case mlinks of- Nothing -> return Nothing- Just links -> return $ Just $ PEDIO $ makeFoundNodeDIO ts addr node $ map (setAddressPrefix addr) links+ links <- readUntilCP (withPrefix pDIONeighbor) (withPrefix pDIONeighborEnd)+ return $ makeFoundNodeDIO ts addr node $ map (first $ setNonLocalPrefix addr) links++-- | Parse stream of log lines for a 'FoundNodeDAO'.+--+-- @since 0.2.3.0+parserFoundNodeDAO :: Monad m+ => Parser Timestamp -- ^ Text parser for log head.+ -> ConduitParser Line m [FoundNodeDAO]+parserFoundNodeDAO pTimestamp = do+ line <- CP.await+ case runParser pDAOHead $ unpack line of+ Nothing -> throwError $ CP.Unexpected ("Not a log line head of local findings about DAO.")+ Just (ts, r) -> proceedDAO ts r+ where+ withPrefix p = pTimestamp *> pLogHead *> p+ pDAOHead = (,) <$> pTimestamp <*> (pLogHead *> pDAOLogHeader) proceedDAO ts route_num = do- mlinks <- readUntil getL (withPrefix pDAOLink) (withPrefix pDAOLinkEnd)- case mlinks of- Nothing -> return Nothing- Just links -> do- root_address <- maybe (throwIO $ ParseError "No root address found in SR log") return- $ getRootAddress links- return $ Just $ PEDAO $ map (makeDAONodeFromTuple root_address route_num ts) $ groupDAOLinks links+ links <- readUntilCP (withPrefix pDAOLink) (withPrefix pDAOLinkEnd)+ root_address <- maybe (throwError $ CP.Unexpected "No root address found in SR log") return+ $ getRootAddress links+ return $ map (makeDAONodeFromTuple root_address route_num ts) $ groupDAOLinks links getRootAddress :: [(IPv6, Maybe (IPv6, Word))] -> Maybe IPv6 getRootAddress links = fmap fst $ listToMaybe $ filter isRootEntry links where@@ -162,24 +203,29 @@ makeFoundNodeDAO ts (if parent_addr == root_addr then Just route_num else Nothing) parent_addr children- setAddressPrefix self_addr (neighbor_addr, ll) = (modified_addr, ll)- where- modified_addr = if isLinkLocal neighbor_addr- then setPrefix (getPrefix self_addr) neighbor_addr- else neighbor_addr -readUntil :: IO (Maybe String) -> Parser a -> Parser end -> IO (Maybe [a])-readUntil getL pBody pEnd = go []+setNonLocalPrefix :: IPv6 -> IPv6 -> IPv6+setNonLocalPrefix prefix_addr orig_addr =+ if isLinkLocal orig_addr+ then setPrefix (getPrefix prefix_addr) orig_addr+ else orig_addr++awaitM :: Monad m => ConduitParser i m (Maybe i)+awaitM = do+ mnext <- CP.peek+ case mnext of+ Nothing -> return Nothing+ Just _ -> fmap Just $ CP.await++readUntilCP :: Monad m => Parser a -> Parser end -> ConduitParser Line m [a]+readUntilCP pBody pEnd = go [] where go acc = do- mline <- getL- case mline of- Nothing -> return Nothing- Just line ->- case runParser ((Left <$> pEnd) <|> (Right <$> pBody)) line of- Nothing -> throwIO $ ParseError ("Parse error at line: " <> line)- Just (Left _) -> return $ Just $ reverse acc- Just (Right body) -> go (body : acc)+ line <- CP.await+ case runParser ((Left <$> pEnd) <|> (Right <$> pBody)) $ unpack line of+ Nothing -> throwError $ CP.Unexpected ("Parse error at line: " <> line)+ Just (Left _) -> return $ reverse acc+ Just (Right body) -> go (body : acc) makeFoundNodeDIO :: Timestamp -> IPv6 -> DIO.DIONode -> [(IPv6, DIO.DIOLink)] -> FoundNodeDIO makeFoundNodeDIO ts self_addr node_attr neighbors =@@ -208,15 +254,6 @@ linkState = LinkToTarget, linkAttributes = DAO.DAOLink lifetime }---data Heading = HDIO IPv6 DIO.DIONode- | HDAO Word- deriving (Show,Eq,Ord)--pHeading :: Parser Heading-pHeading = (fmap (uncurry HDIO) $ pDIONode)- <|> (HDAO <$> pDAOLogHeader) isAddressChar :: Char -> Bool isAddressChar c = isHexDigit c || c == ':'