{-# LANGUAGE OverloadedStrings #-}
module NetSpider.RPL.ContikiNGSpec (main,spec) where

import Control.Monad.Logger (runWriterLoggingT, LogLevel(LevelWarn))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Maybe (fromJust)
import Data.Text (Text)
import NetSpider.Found (FoundNode(..), FoundLink(..), LinkState(..))
import NetSpider.Timestamp (Timestamp, fromEpochMillisecond)
import System.IO (withFile, IOMode(ReadMode))
import System.Log.FastLogger (fromLogStr)
import Test.Hspec

import NetSpider.RPL.ContikiNG (parseFile, pCoojaLogHead', pSyslogHead, parseFileHandleM)
import NetSpider.RPL.FindingID (idFromText)
import qualified NetSpider.RPL.DIO as DIO
import qualified NetSpider.RPL.DAO as DAO

main :: IO ()
main = hspec spec

daoEntry :: Timestamp -> Text -> Maybe Word -> [(Text, Word)] -> DAO.FoundNodeDAO
daoEntry ts src mroute_num dests =
  FoundNode
  { subjectNode = fromJust $ idFromText src,
    foundAt = ts,
    nodeAttributes = DAO.DAONode mroute_num,
    neighborLinks = map toLink dests
  }
  where
    toLink (dest, lifetime) =
      FoundLink
      { targetNode = fromJust $ idFromText dest,
        linkState = LinkToTarget,
        linkAttributes = DAO.DAOLink lifetime
      }

spec :: Spec
spec = do
  describe "parseFile" $ do
    specify "cooja log" $ do
      let exp_dio =
            FoundNode { subjectNode = fromJust $ idFromText "dio://[fd00::201:1:1:1]",
                        foundAt = fromEpochMillisecond 60382,
                        neighborLinks = exp_dio_links,
                        nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 15 }
                      }
          exp_dio_links =
            [ FoundLink { targetNode = fromJust $ idFromText "dio://[fd00::202:2:2:2]",
                          linkState = LinkUnused,
                          linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                         DIO.neighborRank = 299,
                                                         DIO.metric = Just 141
                                                       }
                        }
            ]
          exp_daos = [ daoEntry (fromEpochMillisecond 60382)
                       "dao://[fd00::201:1:1:1]" (Just 2)
                       [("dao://[fd00::202:2:2:2]", 1740)]
                     ]
      (got_dios, got_daos) <- parseFile pCoojaLogHead' "test/data/cooja.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` exp_daos
    specify "syslog root log" $ do
      let exp_timestamp = fromEpochMillisecond 1546968337000
          exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::212:1199:eebb:62c4]",
              foundAt = exp_timestamp,
              nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 14 },
              neighborLinks =
                [ FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:bbcc:4fdf]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 262,
                                                   DIO.metric = Just 128
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:bbcc:d52d]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 263,
                                                   DIO.metric = Just 128
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:bbcc:5e88]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 128
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:eebb:62fe]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 129
                                                 }
                  }
                ]
            }
          exp_daos = [ daoEntry exp_timestamp
                       "dao://[fd00::212:1199:eebb:62c4]" (Just 5)
                       [ ("dao://[fd00::212:1199:bbcc:d52d]", 1080),
                         ("dao://[fd00::212:1199:bbcc:4fdf]", 1260),
                         ("dao://[fd00::212:1199:eebb:62fe]", 1440),
                         ("dao://[fd00::212:1199:bbcc:5e88]", 1140)
                       ]
                     ]
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_root.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` exp_daos
    specify "syslog nonroot" $ do
      let exp_timestamp = fromEpochMillisecond 1547558149000
          exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::212:eeaa:0077:2f9c]",
              foundAt = exp_timestamp,
              nodeAttributes = DIO.DIONode { DIO.rank = 423, DIO.dioInterval = 16 },
              neighborLinks =
                [ FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:632a]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 198
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33ff:a874]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 177
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:63d0]",
                    linkState = LinkToTarget,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.PreferredParent,
                                                   DIO.neighborRank = 272,
                                                   DIO.metric = Just 151
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:9977:13ba]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 283,
                                                   DIO.metric = Just 152
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:6350]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 400,
                                                   DIO.metric = Just 171
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:e88:db36]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 432,
                                                   DIO.metric = Just 166
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:9977:b4]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                                   DIO.neighborRank = 584,
                                                   DIO.metric = Just 65535
                                                 }
                  }
                ]
            }
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_nonroot.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` []
    specify "syslog sr tables" $ do
      let exp_ts_jan = fromEpochMillisecond 1548843376000
          exp_ts_feb = fromEpochMillisecond 1549373162000
          exp_dio1 =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::222:5566:cc99:62c4]",
              foundAt = exp_ts_jan,
              nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 15 },
              neighborLinks = []
            }
          exp_dio2 =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::222:5566:cc99:62c4]",
              foundAt = exp_ts_feb,
              nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 18 },
              neighborLinks = exp_dio_links2
            }
          exp_dio_links2 =
            [ FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:4fdf]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 256,
                                               DIO.metric = Just 128
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:d52d]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 396,
                                               DIO.metric = Just 128
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:cc99:62fe]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 266,
                                               DIO.metric = Just 137
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:401e]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 384,
                                               DIO.metric = Just 128
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:5e88]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 406,
                                               DIO.metric = Just 128
                                             }
              }
            ]
          exp_daos = [ daoEntry exp_ts_feb
                       "dao://[fd00::222:5566:cc99:62c4]" (Just 6)
                       [ ("dao://[fd00::222:5566:ddee:4fdf]", 1140),
                         ("dao://[fd00::222:5566:cc99:62fe]", 1380)
                       ],
                       daoEntry exp_ts_feb
                       "dao://[fd00::222:5566:ddee:4fdf]" Nothing
                       [ ("dao://[fd00::222:5566:ddee:d52d]", 1380),
                         ("dao://[fd00::222:5566:ddee:401e]", 1740),
                         ("dao://[fd00::222:5566:ddee:5e88]", 1740)
                       ]
                    ]
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_sr_tables.log"
      got_dios `shouldBe` [exp_dio1, exp_dio2]
      got_daos `shouldMatchList` exp_daos
    specify "syslog_inf_rank" $ do
      let exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:bcc:1008]",
              foundAt = fromEpochMillisecond 1551104666000,
              nodeAttributes = DIO.DIONode { DIO.rank = 65535, DIO.dioInterval = 12 },
              neighborLinks = exp_links
            }
          exp_links =
            [ FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:bcc:100a]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                               DIO.neighborRank = 492,
                                               DIO.metric = Just 601
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:9221:d51a]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                               DIO.neighborRank = 422,
                                               DIO.metric = Just 601
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:bcc:d5e8]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                               DIO.neighborRank = 65535,
                                               DIO.metric = Just 133
                                             }
              }
            ]
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_inf_rank.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldMatchList` []
    specify "syslog_interleaving" $ do
      let exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::212:eeaa:0077:2f9c]",
              foundAt = fromEpochMillisecond 1547558152000,
              nodeAttributes = DIO.DIONode { DIO.rank = 332, DIO.dioInterval = 14 },
              neighborLinks = exp_links
            }
          exp_links =
            [ FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:63d0]",
                linkState = LinkToTarget,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.PreferredParent,
                                               DIO.neighborRank = 188,
                                               DIO.metric = Just 144
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:9977:13ba]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 263,
                                               DIO.metric = Just 152
                                             }
              }
            ]
          pHead = pSyslogHead 2019 Nothing
      ((got_dios, got_daos), got_logs) <- withFile "test/data/syslog_interleaving.log" ReadMode
                                          (runWriterLoggingT . parseFileHandleM pHead)
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` []
      -- Because the log file contains interleaving lines inside DIO
      -- block, it should emit a warning message.
      length got_logs `shouldBe` 1
      let exp_msg = "Jan 15 13:15:49 hostname hoge[3392]: this is a log not related to ContikiNG."
          (_, _, got_level, got_msg_str) = got_logs !! 0
          got_msg = fromLogStr got_msg_str
      got_level `shouldBe` LevelWarn
      got_msg `shouldSatisfy` BS.isInfixOf exp_msg
      got_msg `shouldSatisfy` BS.isInfixOf "DIO"

# LANGUAGE OverloadedStrings #
 Because the log file contains interleaving lines inside DIO
 block, it should emit a warning message.
module NetSpider.RPL.ContikiNGSpec (main,spec) where

import Control.Monad.Logger (runWriterLoggingT, LogLevel(LevelWarn))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Maybe (fromJust)
import Data.Text (Text)
import NetSpider.Found (FoundNode(..), FoundLink(..), LinkState(..))
import NetSpider.Timestamp (Timestamp, fromEpochMillisecond)
import System.IO (withFile, IOMode(ReadMode))
import System.Log.FastLogger (fromLogStr)
import Test.Hspec

import NetSpider.RPL.ContikiNG (parseFile, pCoojaLogHead', pSyslogHead, parseFileHandleM)
import NetSpider.RPL.FindingID (idFromText)
import qualified NetSpider.RPL.DIO as DIO
import qualified NetSpider.RPL.DAO as DAO

main :: IO ()
main = hspec spec

daoEntry :: Timestamp -> Text -> Maybe Word -> [(Text, Word)] -> DAO.FoundNodeDAO
daoEntry ts src mroute_num dests =
  FoundNode
  { subjectNode = fromJust $ idFromText src,
    foundAt = ts,
    nodeAttributes = DAO.DAONode mroute_num,
    neighborLinks = map toLink dests
  }
  where
    toLink (dest, lifetime) =
      FoundLink
      { targetNode = fromJust $ idFromText dest,
        linkState = LinkToTarget,
        linkAttributes = DAO.DAOLink lifetime
      }

spec :: Spec
spec = do
  describe "parseFile" $ do
    specify "cooja log" $ do
      let exp_dio =
            FoundNode { subjectNode = fromJust $ idFromText "dio://[fd00::201:1:1:1]",
                        foundAt = fromEpochMillisecond 60382,
                        neighborLinks = exp_dio_links,
                        nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 15 }
                      }
          exp_dio_links =
            [ FoundLink { targetNode = fromJust $ idFromText "dio://[fd00::202:2:2:2]",
                          linkState = LinkUnused,
                          linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                         DIO.neighborRank = 299,
                                                         DIO.metric = Just 141
                                                       }
                        }
            ]
          exp_daos = [ daoEntry (fromEpochMillisecond 60382)
                       "dao://[fd00::201:1:1:1]" (Just 2)
                       [("dao://[fd00::202:2:2:2]", 1740)]
                     ]
      (got_dios, got_daos) <- parseFile pCoojaLogHead' "test/data/cooja.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` exp_daos
    specify "syslog root log" $ do
      let exp_timestamp = fromEpochMillisecond 1546968337000
          exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::212:1199:eebb:62c4]",
              foundAt = exp_timestamp,
              nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 14 },
              neighborLinks =
                [ FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:bbcc:4fdf]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 262,
                                                   DIO.metric = Just 128
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:bbcc:d52d]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 263,
                                                   DIO.metric = Just 128
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:bbcc:5e88]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 128
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:1199:eebb:62fe]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 129
                                                 }
                  }
                ]
            }
          exp_daos = [ daoEntry exp_timestamp
                       "dao://[fd00::212:1199:eebb:62c4]" (Just 5)
                       [ ("dao://[fd00::212:1199:bbcc:d52d]", 1080),
                         ("dao://[fd00::212:1199:bbcc:4fdf]", 1260),
                         ("dao://[fd00::212:1199:eebb:62fe]", 1440),
                         ("dao://[fd00::212:1199:bbcc:5e88]", 1140)
                       ]
                     ]
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_root.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` exp_daos
    specify "syslog nonroot" $ do
      let exp_timestamp = fromEpochMillisecond 1547558149000
          exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::212:eeaa:0077:2f9c]",
              foundAt = exp_timestamp,
              nodeAttributes = DIO.DIONode { DIO.rank = 423, DIO.dioInterval = 16 },
              neighborLinks =
                [ FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:632a]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 198
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33ff:a874]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 256,
                                                   DIO.metric = Just 177
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:63d0]",
                    linkState = LinkToTarget,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.PreferredParent,
                                                   DIO.neighborRank = 272,
                                                   DIO.metric = Just 151
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:9977:13ba]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 283,
                                                   DIO.metric = Just 152
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:6350]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 400,
                                                   DIO.metric = Just 171
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:e88:db36]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                                   DIO.neighborRank = 432,
                                                   DIO.metric = Just 166
                                                 }
                  },
                  FoundLink
                  { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:9977:b4]",
                    linkState = LinkUnused,
                    linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                                   DIO.neighborRank = 584,
                                                   DIO.metric = Just 65535
                                                 }
                  }
                ]
            }
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_nonroot.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` []
    specify "syslog sr tables" $ do
      let exp_ts_jan = fromEpochMillisecond 1548843376000
          exp_ts_feb = fromEpochMillisecond 1549373162000
          exp_dio1 =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::222:5566:cc99:62c4]",
              foundAt = exp_ts_jan,
              nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 15 },
              neighborLinks = []
            }
          exp_dio2 =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::222:5566:cc99:62c4]",
              foundAt = exp_ts_feb,
              nodeAttributes = DIO.DIONode { DIO.rank = 128, DIO.dioInterval = 18 },
              neighborLinks = exp_dio_links2
            }
          exp_dio_links2 =
            [ FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:4fdf]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 256,
                                               DIO.metric = Just 128
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:d52d]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 396,
                                               DIO.metric = Just 128
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:cc99:62fe]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 266,
                                               DIO.metric = Just 137
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:401e]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 384,
                                               DIO.metric = Just 128
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::222:5566:ddee:5e88]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 406,
                                               DIO.metric = Just 128
                                             }
              }
            ]
          exp_daos = [ daoEntry exp_ts_feb
                       "dao://[fd00::222:5566:cc99:62c4]" (Just 6)
                       [ ("dao://[fd00::222:5566:ddee:4fdf]", 1140),
                         ("dao://[fd00::222:5566:cc99:62fe]", 1380)
                       ],
                       daoEntry exp_ts_feb
                       "dao://[fd00::222:5566:ddee:4fdf]" Nothing
                       [ ("dao://[fd00::222:5566:ddee:d52d]", 1380),
                         ("dao://[fd00::222:5566:ddee:401e]", 1740),
                         ("dao://[fd00::222:5566:ddee:5e88]", 1740)
                       ]
                    ]
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_sr_tables.log"
      got_dios `shouldBe` [exp_dio1, exp_dio2]
      got_daos `shouldMatchList` exp_daos
    specify "syslog_inf_rank" $ do
      let exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:bcc:1008]",
              foundAt = fromEpochMillisecond 1551104666000,
              nodeAttributes = DIO.DIONode { DIO.rank = 65535, DIO.dioInterval = 12 },
              neighborLinks = exp_links
            }
          exp_links =
            [ FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:bcc:100a]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                               DIO.neighborRank = 492,
                                               DIO.metric = Just 601
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:9221:d51a]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                               DIO.neighborRank = 422,
                                               DIO.metric = Just 601
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::aaa:bbbb:bcc:d5e8]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.OtherNeighbor,
                                               DIO.neighborRank = 65535,
                                               DIO.metric = Just 133
                                             }
              }
            ]
          pHead = pSyslogHead 2019 Nothing
      (got_dios, got_daos) <- parseFile pHead "test/data/syslog_inf_rank.log"
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldMatchList` []
    specify "syslog_interleaving" $ do
      let exp_dio =
            FoundNode
            { subjectNode = fromJust $ idFromText "dio://[fd00::212:eeaa:0077:2f9c]",
              foundAt = fromEpochMillisecond 1547558152000,
              nodeAttributes = DIO.DIONode { DIO.rank = 332, DIO.dioInterval = 14 },
              neighborLinks = exp_links
            }
          exp_links =
            [ FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:33cc:63d0]",
                linkState = LinkToTarget,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.PreferredParent,
                                               DIO.neighborRank = 188,
                                               DIO.metric = Just 144
                                             }
              },
              FoundLink
              { targetNode = fromJust $ idFromText "dio://[fd00::212:eeaa:9977:13ba]",
                linkState = LinkUnused,
                linkAttributes = DIO.DIOLink { DIO.neighborType = DIO.ParentCandidate,
                                               DIO.neighborRank = 263,
                                               DIO.metric = Just 152
                                             }
              }
            ]
          pHead = pSyslogHead 2019 Nothing
      ((got_dios, got_daos), got_logs) <- withFile "test/data/syslog_interleaving.log" ReadMode
                                          (runWriterLoggingT . parseFileHandleM pHead)
      got_dios `shouldBe` [exp_dio]
      got_daos `shouldBe` []
      length got_logs `shouldBe` 1
      let exp_msg = "Jan 15 13:15:49 hostname hoge[3392]: this is a log not related to ContikiNG."
          (_, _, got_level, got_msg_str) = got_logs !! 0
          got_msg = fromLogStr got_msg_str
      got_level `shouldBe` LevelWarn
      got_msg `shouldSatisfy` BS.isInfixOf exp_msg
      got_msg `shouldSatisfy` BS.isInfixOf "DIO"
