asif 5.0.0 → 5.0.1
raw patch · 6 files changed
+394/−249 lines, 6 filesdep −iproutePVP ok
version bump matches the API change (PVP)
Dependencies removed: iproute
API changes (from Hackage documentation)
+ Arbor.File.Format.Asif.Format.Decoder: getBool :: Get Bool
+ Arbor.File.Format.Asif.Format.Decoder: getIpv4 :: Get IpAddress
+ Arbor.File.Format.Asif.Format.Decoder: getIpv4Block :: Get (IpBlock Canonical)
+ Arbor.File.Format.Asif.Format.Decoder: getIpv6 :: Get IpAddress
+ Arbor.File.Format.Asif.Format.Decoder: getIpv6Block :: Get (IpBlock Canonical)
+ Arbor.File.Format.Asif.Format.Decoder: getTimeMicros :: Get UTCTime
+ Arbor.File.Format.Asif.Format.Decoder: getTimeMillis :: Get UTCTime
+ Arbor.File.Format.Asif.Format.Decoder: getWord32x4 :: Get (Word32, Word32, Word32, Word32)
+ Arbor.File.Format.Asif.Write: fixedLengthAsciiSegment :: MonadResource m => (a -> Text) -> Text -> Word -> FoldM m a [Segment Handle]
+ Arbor.File.Format.Asif.Write: lookupSegment :: (MonadResource m, Ord b, Eq i, Num i, Bounded i) => Text -> (a -> Maybe b) -> Whatever Format -> (i -> Builder) -> FoldM m b [Segment Handle] -> FoldM m a [Segment Handle]
+ Arbor.File.Format.Asif.Write: word16LookupSegment :: (MonadResource m, Ord b) => Text -> (a -> Maybe b) -> FoldM m b [Segment Handle] -> FoldM m a [Segment Handle]
+ Arbor.File.Format.Asif.Write: word32LookupSegment :: (MonadResource m, Ord b) => Text -> (a -> Maybe b) -> FoldM m b [Segment Handle] -> FoldM m a [Segment Handle]
+ Arbor.File.Format.Asif.Write: word64LookupSegment :: (MonadResource m, Ord b) => Text -> (a -> Maybe b) -> FoldM m b [Segment Handle] -> FoldM m a [Segment Handle]
Files
- app/App/Dump.hs +141/−109
- asif.cabal +72/−110
- src/Arbor/File/Format/Asif/Format/Decoder.hs +48/−0
- src/Arbor/File/Format/Asif/Format/SegmentValue.hs +8/−27
- src/Arbor/File/Format/Asif/Write.hs +93/−3
- test/Arbor/File/Format/Asif/WriteSpec.hs +32/−0
app/App/Dump.hs view
@@ -9,27 +9,26 @@ ) where import Arbor.File.Format.Asif.Data.Ip+import Arbor.File.Format.Asif.Format.Decoder as D import Arbor.File.Format.Asif.Segment import Arbor.File.Format.Asif.Whatever import Control.Lens import Control.Monad-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Resource (MonadResource)-import Data.Char (isPrint)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Resource (MonadResource)+import Data.Char (isPrint) import Data.Generics.Product.Any import Data.List-import Data.Monoid ((<>))-import Data.Text (Text)-import Data.Thyme.Clock-import Data.Thyme.Clock.POSIX (POSIXTime)-import Data.Thyme.Format (formatTime)+import Data.Monoid ((<>))+import Data.Text (Text)+import Data.Thyme.Format (formatTime) import Data.Thyme.Time.Core import Data.Word import HaskellWorks.Data.Bits.BitShow import HaskellWorks.Data.Bits.BitWise-import Numeric (showHex)-import System.IO (Handle)-import System.Locale (defaultTimeLocale, iso8601DateFormat)+import Numeric (showHex)+import System.IO (Handle)+import System.Locale (defaultTimeLocale, iso8601DateFormat) import qualified Arbor.File.Format.Asif.ByteString.Lazy as LBS import qualified Arbor.File.Format.Asif.Format as F@@ -40,6 +39,8 @@ import qualified Data.ByteString.Lazy.Char8 as LBSC import qualified Data.Text as T import qualified Data.Text.Encoding as T+import qualified HaskellWorks.Data.Network.Ip.Ipv4 as IP4+import qualified HaskellWorks.Data.Network.Ip.Ipv6 as IP6 import qualified System.IO as IO {-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}@@ -48,14 +49,6 @@ showTime :: FormatTime t => t -> String showTime = formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S %Z")) -getWord32x4 :: G.Get (Word32, Word32, Word32, Word32)-getWord32x4 = do- a <- G.getWord32be- b <- G.getWord32be- c <- G.getWord32be- d <- G.getWord32be- return (a, b, c, d)- word64ToList :: Int -> Word64 -> [Word32] -> [Word32] word64ToList _ 0 = id word64ToList o w = (ip:) . word64ToList o (w .&. comp b)@@ -66,94 +59,133 @@ dumpSegment :: MonadResource m => Handle -> Int -> Text -> Segment LBS.ByteString -> m () dumpSegment hOut i filename segment = do- if T.null filename- then liftIO $ IO.hPutStrLn hOut $ "==== [" <> show i <> "] ===="- else liftIO $ IO.hPutStrLn hOut $ "==== [" <> show i <> "] " <> T.unpack filename <> " ===="+ if T.null filename+ then liftIO $ IO.hPutStrLn hOut $ "==== [" <> show i <> "] ===="+ else liftIO $ IO.hPutStrLn hOut $ "==== [" <> show i <> "] " <> T.unpack filename <> " ====" - case segment ^. the @"meta" . the @"format" of- Just (Known F.StringZ) -> do- when (LBS.length (segment ^. the @"payload") > 0) $- forM_ (init (LBS.split 0 (segment ^. the @"payload"))) $ \bs ->- liftIO $ IO.hPutStrLn hOut $ T.unpack (T.decodeUtf8 (LBS.toStrict bs))- Just (Known (F.Repeat n F.Char)) ->- forM_ (LBS.chunkBy (fromIntegral n) (segment ^. the @"payload")) $ \bs ->- liftIO $ IO.hPutStrLn hOut $ T.unpack (T.decodeUtf8 (LBS.toStrict bs))- Just (Known F.TimeMillis64LE) ->- forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getInt64le (LBS.take 8 (bs <> LBS.replicate 8 0))- let t :: POSIXTime = (w * 1000) ^. from microseconds- liftIO $ IO.hPutStrLn hOut $ showTime (posixSecondsToUTCTime t) <> " (" <> show w <> " ms)"- Just (Known F.TimeMicros64LE) ->- forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getInt64le (LBS.take 8 (bs <> LBS.replicate 8 0))- let t :: POSIXTime = w ^. from microseconds- liftIO $ IO.hPutStrLn hOut $ showTime (posixSecondsToUTCTime t) <> " (" <> show w <> " µs)"- Just (Known F.Ipv4) ->- forM_ (LBS.chunkBy 4 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getWord32le (LBS.take 8 (bs <> LBS.replicate 4 0))- let ipString = w & word32ToIpv4 & ipv4ToString- liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (16 - length ipString) ' ' <> "(" <> show w <> ")"- Just (Known F.Ipv6) ->- forM_ (LBS.chunkBy 16 (segment ^. the @"payload")) $ \bs -> do- let (a, b, c, d) = G.runGet getWord32x4 (LBS.take 16 (bs <> LBS.replicate 16 0))- if a == 0 && b == 0 && c == 0xFFFF- then do- let ipString = d & word32ToIpv4 & ipv4ToString- liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (64 - length ipString) ' ' <> "(" <> show d <> ")"- else do- let ipString = word32x4ToIpv6 (a, b, c, d) & ipv6ToString- liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (64 - length ipString) ' ' <> "(" <> show (a, b, c, d) <> ")"- Just (Known F.Int64LE) ->- forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getInt64le (LBS.take 8 (bs <> LBS.replicate 8 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Int32LE) ->- forM_ (LBS.chunkBy 4 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getInt32le (LBS.take 4 (bs <> LBS.replicate 4 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Int16LE) ->- forM_ (LBS.chunkBy 2 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getInt16le (LBS.take 2 (bs <> LBS.replicate 2 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Int8) ->- forM_ (LBS.chunkBy 1 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getInt8 (LBS.take 1 (bs <> LBS.replicate 1 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Word64LE) ->- forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getWord64le (LBS.take 8 (bs <> LBS.replicate 8 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Word32LE) ->- forM_ (LBS.chunkBy 4 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getWord32le (LBS.take 4 (bs <> LBS.replicate 4 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Word16LE) ->- forM_ (LBS.chunkBy 2 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getWord16le (LBS.take 2 (bs <> LBS.replicate 2 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Word8) ->- forM_ (LBS.chunkBy 1 (segment ^. the @"payload")) $ \bs -> do- let w = G.runGet G.getWord8 (LBS.take 1 (bs <> LBS.replicate 1 0))- liftIO $ IO.hPrint hOut w- Just (Known F.Text) ->- liftIO $ LBSC.hPutStrLn hOut (segment ^. the @"payload")- Just (Known F.BitString) ->- liftIO $ IO.hPutStrLn hOut (bitShow (segment ^. the @"payload"))- Just (Known F.Bitmap) ->- forM_ (zip [0..] (G.runGet G.getWord64le <$> LBS.chunkBy 8 (segment ^. the @"payload"))) $ \(idx, w64) ->- forM_ (word64ToList idx w64 []) $ \w32 -> do- let ipString = w32 & word32ToIpv4 & ipv4ToString- liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (16 - length ipString) ' ' <> "(" <> show w32 <> ")"- Just (Known F.Bool) ->- forM_ (LBS.chunkBy 1 (segment ^. the @"payload")) $ \bs -> do- let w :: Bool = G.runGet G.get (LBS.take 1 (bs <> LBS.replicate 1 0))- liftIO $ IO.hPrint hOut w- _ ->- forM_ (zip (LBS.chunkBy 16 (segment ^. the @"payload")) [0 :: Int, 16..]) $ \(bs, j) -> do- let bytes = mconcat (intersperse " " (reverse . take 2 . reverse . ('0':) . flip showHex "" <$> LBS.unpack bs))- liftIO $ IO.hPutStr hOut $ reverse $ take 8 $ reverse $ ("0000000" ++) $ showHex j ""- liftIO $ IO.hPutStr hOut " "- liftIO $ IO.hPutStr hOut $ bytes <> replicate (47 - length bytes) ' '- liftIO $ IO.hPutStr hOut " "- liftIO $ IO.hPutStr hOut $ (\c -> if isPrint c then c else '.') <$> LBSC.unpack bs- liftIO $ IO.hPutStrLn hOut ""+ case segment ^. the @"meta" . the @"format" of+ Just (Known F.StringZ) -> do+ when (LBS.length (segment ^. the @"payload") > 0) $+ forM_ (init (LBS.split 0 (segment ^. the @"payload"))) $ \bs ->+ liftIO $ IO.hPutStrLn hOut $ T.unpack (T.decodeUtf8 (LBS.toStrict bs))++ Just (Known (F.Repeat n F.Char)) ->+ forM_ (LBS.chunkBy (fromIntegral n) (segment ^. the @"payload")) $ \bs ->+ liftIO $ IO.hPutStrLn hOut $ T.unpack (T.decodeUtf8 (LBS.toStrict bs))++ Just (Known F.TimeMillis64LE) ->+ forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do+ let t = G.runGet D.getTimeMillis (LBS.take 8 (bs <> LBS.replicate 8 0))+ let ms = t & utcTimeToPOSIXSeconds & toMicroseconds & (`div` 1000)+ liftIO $ IO.hPutStrLn hOut $ showTime t <> " (" <> show ms <> " ms)"++ Just (Known F.TimeMicros64LE) ->+ forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do+ let t = G.runGet D.getTimeMicros (LBS.take 8 (bs <> LBS.replicate 8 0))+ let micros = t & utcTimeToPOSIXSeconds & toMicroseconds+ liftIO $ IO.hPutStrLn hOut $ showTime t <> " (" <> show micros <> " µs)"++ Just (Known F.Ipv4) ->+ forM_ (LBS.chunkBy 4 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet D.getIpv4 (LBS.take 4 (bs <> LBS.replicate 4 0))+ let ipString = w & ipv4ToString+ liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (16 - length ipString) ' ' <> "(" <> show w <> ")"++ Just (Known F.Ipv6) ->+ forM_ (LBS.chunkBy 16 (segment ^. the @"payload")) $ \bs -> do+ let ip6@(IP6.IpAddress (a,b,c,d)) = G.runGet D.getIpv6 (LBS.take 16 (bs <> LBS.replicate 16 0))+ let ipString = ipv6toStringCollapseV4 ip6+ let raw = case isIpv4 ip6 of+ Just (IP4.IpAddress w32) -> "(" <> show w32 <> ")"+ Nothing -> "(" <> show (a, b, c, d) <> ")"+ liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (64 - length ipString) ' ' <> raw++ Just (Known F.Ipv4Block) ->+ forM_ (LBS.chunkBy 5 (segment ^. the @"payload")) $ \bs -> do+ let block@(IP4.IpBlock ip mask) = G.runGet D.getIpv4Block (LBS.take 5 (bs <> LBS.replicate 5 0))+ let IP4.IpAddress ipw = ip+ let IP4.IpNetMask maskw = mask+ let ipString = block & ipv4CidrToString+ liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (64 - length ipString) ' ' <> "(" <> show (ipw, maskw) <> ")"++ Just (Known F.Ipv6Block) ->+ forM_ (LBS.chunkBy 17 (segment ^. the @"payload")) $ \bs -> do+ let block@(IP6.IpBlock ip mask) = G.runGet D.getIpv6Block (LBS.take 17 (bs <> LBS.replicate 17 0))+ let IP6.IpAddress ipw = ip+ let IP6.IpNetMask maskw = mask+ let ipString = block & ipv6CidrToStringCollapseV4+ liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (64 - length ipString) ' ' <> "(" <> show (ipw, maskw) <> ")"++ Just (Known F.Int64LE) ->+ forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getInt64le (LBS.take 8 (bs <> LBS.replicate 8 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Int32LE) ->+ forM_ (LBS.chunkBy 4 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getInt32le (LBS.take 4 (bs <> LBS.replicate 4 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Int16LE) ->+ forM_ (LBS.chunkBy 2 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getInt16le (LBS.take 2 (bs <> LBS.replicate 2 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Int8) ->+ forM_ (LBS.chunkBy 1 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getInt8 (LBS.take 1 (bs <> LBS.replicate 1 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Word64LE) ->+ forM_ (LBS.chunkBy 8 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getWord64le (LBS.take 8 (bs <> LBS.replicate 8 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Word32LE) ->+ forM_ (LBS.chunkBy 4 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getWord32le (LBS.take 4 (bs <> LBS.replicate 4 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Word16LE) ->+ forM_ (LBS.chunkBy 2 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getWord16le (LBS.take 2 (bs <> LBS.replicate 2 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Word8) ->+ forM_ (LBS.chunkBy 1 (segment ^. the @"payload")) $ \bs -> do+ let w = G.runGet G.getWord8 (LBS.take 1 (bs <> LBS.replicate 1 0))+ liftIO $ IO.hPrint hOut w++ Just (Known F.Text) ->+ liftIO $ LBSC.hPutStrLn hOut (segment ^. the @"payload")++ Just (Known F.BitString) ->+ liftIO $ IO.hPutStrLn hOut (bitShow (segment ^. the @"payload"))++ Just (Known F.Bitmap) ->+ forM_ (zip [0..] (G.runGet G.getWord64le <$> LBS.chunkBy 8 (segment ^. the @"payload"))) $ \(idx, w64) ->+ forM_ (word64ToList idx w64 []) $ \w32 -> do+ let ipString = w32 & word32ToIpv4 & ipv4ToString+ liftIO $ IO.hPutStrLn hOut $ ipString <> replicate (16 - length ipString) ' ' <> "(" <> show w32 <> ")"++ Just (Known F.Bool) ->+ forM_ (LBS.chunkBy 1 (segment ^. the @"payload")) $ \bs -> do+ let w :: Bool = G.runGet G.get (LBS.take 1 (bs <> LBS.replicate 1 0))+ liftIO $ IO.hPrint hOut w++ -- WARNING: Don't put a true catch-all (_ -> ...) case here+ -- it means we miss this function when new formats get added.+ Just (Known F.Binary) -> catchAll+ Just (Known F.Char) -> catchAll+ Just (Known (F.Repeat _ _)) -> catchAll+ Just (Unknown _) -> catchAll+ Nothing -> catchAll+ where+ catchAll = forM_ (zip (LBS.chunkBy 16 (segment ^. the @"payload")) [0 :: Int, 16..]) $ \(bs, j) -> do+ let bytes = mconcat (intersperse " " (reverse . take 2 . reverse . ('0':) . flip showHex "" <$> LBS.unpack bs))+ liftIO $ IO.hPutStr hOut $ reverse $ take 8 $ reverse $ ("0000000" ++) $ showHex j ""+ liftIO $ IO.hPutStr hOut " "+ liftIO $ IO.hPutStr hOut $ bytes <> replicate (47 - length bytes) ' '+ liftIO $ IO.hPutStr hOut " "+ liftIO $ IO.hPutStr hOut $ (\c -> if isPrint c then c else '.') <$> LBSC.unpack bs+ liftIO $ IO.hPutStrLn hOut ""
asif.cabal view
@@ -1,5 +1,6 @@+cabal-version: 2.2 name: asif-version: 5.0.0+version: 5.0.1 description: Library for creating and querying segmented feeds. Please see <https://github.com/packetloop/asif#readme> synopsis: Library for creating and querying segmented feeds category: Services@@ -11,7 +12,6 @@ license: MIT license-file: LICENSE build-type: Simple-cabal-version: >= 1.10 extra-source-files: ChangeLog.md README.md@@ -20,36 +20,9 @@ type: git location: https://github.com/packetloop/asif -library- exposed-modules:- Arbor.File.Format.Asif- Arbor.File.Format.Asif.ByIndex- Arbor.File.Format.Asif.ByteString- Arbor.File.Format.Asif.ByteString.Builder- Arbor.File.Format.Asif.ByteString.Lazy- Arbor.File.Format.Asif.Data.Ip- Arbor.File.Format.Asif.Data.Read- Arbor.File.Format.Asif.Extract- Arbor.File.Format.Asif.Format- Arbor.File.Format.Asif.Format.Text- Arbor.File.Format.Asif.Get- Arbor.File.Format.Asif.IO- Arbor.File.Format.Asif.Lookup- Arbor.File.Format.Asif.Segment- Arbor.File.Format.Asif.Type- Arbor.File.Format.Asif.Whatever- Arbor.File.Format.Asif.Write- other-modules:- Arbor.File.Format.Asif.Format.SegmentValue- Arbor.File.Format.Asif.Format.Type- Arbor.File.Format.Asif.List- Arbor.File.Format.Asif.Maybe- Arbor.File.Format.Asif.Text- Paths_asif- hs-source-dirs:- src- default-extensions: BangPatterns FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving MultiParamTypeClasses OverloadedStrings TupleSections- ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints+common common-deps+ default-language: Haskell2010+ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -msse4.2 build-depends: attoparsec , base >=4.7 && <5@@ -66,7 +39,6 @@ , generic-lens , hw-bits , hw-ip- , iproute , lens , network , old-locale@@ -77,107 +49,97 @@ , thyme , transformers , vector+++library+ import: common-deps+ exposed-modules:+ Arbor.File.Format.Asif+ Arbor.File.Format.Asif.ByIndex+ Arbor.File.Format.Asif.ByteString+ Arbor.File.Format.Asif.ByteString.Builder+ Arbor.File.Format.Asif.ByteString.Lazy+ Arbor.File.Format.Asif.Data.Ip+ Arbor.File.Format.Asif.Data.Read+ Arbor.File.Format.Asif.Extract+ Arbor.File.Format.Asif.Format+ Arbor.File.Format.Asif.Format.Text+ Arbor.File.Format.Asif.Get+ Arbor.File.Format.Asif.IO+ Arbor.File.Format.Asif.Lookup+ Arbor.File.Format.Asif.Segment+ Arbor.File.Format.Asif.Type+ Arbor.File.Format.Asif.Whatever+ Arbor.File.Format.Asif.Write+ Arbor.File.Format.Asif.Format.Decoder+ other-modules:+ Arbor.File.Format.Asif.Format.SegmentValue+ Arbor.File.Format.Asif.Format.Type+ Arbor.File.Format.Asif.List+ Arbor.File.Format.Asif.Maybe+ Arbor.File.Format.Asif.Text+ Paths_asif+ autogen-modules:+ Paths_asif+ hs-source-dirs:+ src+ default-extensions: BangPatterns FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving MultiParamTypeClasses OverloadedStrings TupleSections+ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints default-language: Haskell2010 executable asif+ import: common-deps main-is: Main.hs other-modules:- App.Commands- App.Commands.Dump- App.Commands.DumpBitmap- App.Commands.DumpOnly- App.Commands.EncodeFiles- App.Commands.ExtractFiles- App.Commands.ExtractSegments- App.Commands.Ls- App.Commands.Options.Type- App.Dump- App.IO- Paths_asif+ App.Commands+ App.Commands.Dump+ App.Commands.DumpBitmap+ App.Commands.DumpOnly+ App.Commands.EncodeFiles+ App.Commands.ExtractFiles+ App.Commands.ExtractSegments+ App.Commands.Ls+ App.Commands.Options.Type+ App.Dump+ App.IO+ Paths_asif+ autogen-modules:+ Paths_asif hs-source-dirs:- app+ app default-extensions: BangPatterns FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving MultiParamTypeClasses OverloadedStrings TupleSections ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -threaded -rtsopts -O2 -msse4.2 build-depends: asif- , attoparsec- , base >=4.7 && <5- , binary- , bytestring- , conduit- , conduit-combinators- , conduit-extra- , containers- , cpu , directory- , either- , exceptions- , foldl- , generic-lens- , hw-bits- , hw-ip- , iproute- , lens- , network- , old-locale , optparse-applicative- , profunctors- , resourcet- , temporary-resourcet- , text- , thyme- , transformers- , vector default-language: Haskell2010 test-suite asif-test+ import: common-deps type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:- Arbor.File.Format.Asif.ByteString.BuilderSpec- Arbor.File.Format.Asif.Data.IpSpec- Arbor.File.Format.Asif.ExtractSpec- Arbor.File.Format.Asif.Format.SegmentValueSpec- Arbor.File.Format.Asif.Format.TextSpec- Arbor.File.Format.Asif.WriteSpec- Arbor.File.Format.AsifSpec- Arbor.TestUtils- Gen.Feed- TestApp- Paths_asif+ Arbor.File.Format.Asif.ByteString.BuilderSpec+ Arbor.File.Format.Asif.Data.IpSpec+ Arbor.File.Format.Asif.ExtractSpec+ Arbor.File.Format.Asif.Format.SegmentValueSpec+ Arbor.File.Format.Asif.Format.TextSpec+ Arbor.File.Format.Asif.WriteSpec+ Arbor.File.Format.AsifSpec+ Arbor.TestUtils+ Gen.Feed+ TestApp+ Paths_asif+ autogen-modules:+ Paths_asif hs-source-dirs:- test+ test default-extensions: BangPatterns FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving MultiParamTypeClasses OverloadedStrings TupleSections ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: asif- , attoparsec- , base >=4.7 && <5- , binary- , bytestring- , conduit- , conduit-combinators- , conduit-extra- , containers- , cpu- , either- , exceptions- , foldl- , generic-lens , hedgehog , hspec- , hw-bits , hw-hspec-hedgehog- , hw-ip- , iproute- , lens- , network- , old-locale- , profunctors- , resourcet- , temporary-resourcet- , text- , thyme- , transformers- , vector default-language: Haskell2010
+ src/Arbor/File/Format/Asif/Format/Decoder.hs view
@@ -0,0 +1,48 @@+module Arbor.File.Format.Asif.Format.Decoder where++import Arbor.File.Format.Asif.Data.Ip+import Control.Lens+import Data.Thyme.Time.Core+import Data.Word (Word32)+import HaskellWorks.Data.Network.Ip.Validity (Canonical)++import qualified Data.Binary.Get as G+import qualified HaskellWorks.Data.Network.Ip.Ipv4 as IP4+import qualified HaskellWorks.Data.Network.Ip.Ipv6 as IP6++getWord32x4 :: G.Get (Word32, Word32, Word32, Word32)+getWord32x4 = do+ a <- G.getWord32be+ b <- G.getWord32be+ c <- G.getWord32be+ d <- G.getWord32be+ return (a, b, c, d)++getBool :: G.Get Bool+getBool = G.getWord8 <&> (/= 0)++getIpv4 :: G.Get IP4.IpAddress+getIpv4 = G.getWord32le <&> word32ToIpv4++getIpv6 :: G.Get IP6.IpAddress+getIpv6 = getWord32x4 <&> word32x4ToIpv6++getIpv4Block :: G.Get (IP4.IpBlock Canonical)+getIpv4Block =+ let toIpBlock w32 w8 = IP4.IpBlock (IP4.IpAddress w32) (IP4.IpNetMask w8)+ in toIpBlock <$> G.getWord32le <*> G.getWord8++getIpv6Block :: G.Get (IP6.IpBlock Canonical)+getIpv6Block =+ let toIpBlock w128 w8 = IP6.IpBlock (IP6.IpAddress w128) (IP6.IpNetMask w8)+ in toIpBlock <$> getWord32x4 <*> G.getWord8++getTimeMillis :: G.Get UTCTime+getTimeMillis =+ let toTime ms = (ms * 1000) ^. from microseconds & posixSecondsToUTCTime+ in G.getInt64le <&> toTime++getTimeMicros :: G.Get UTCTime+getTimeMicros =+ let toTime ms = ms ^. from microseconds & posixSecondsToUTCTime+ in G.getInt64le <&> toTime
src/Arbor/File/Format/Asif/Format/SegmentValue.hs view
@@ -7,7 +7,7 @@ where import qualified Arbor.File.Format.Asif.ByteString.Lazy as LBS-import Arbor.File.Format.Asif.Data.Ip+import Arbor.File.Format.Asif.Format.Decoder as D import qualified Arbor.File.Format.Asif.Format.Type as F import Arbor.File.Format.Asif.List as L import Arbor.File.Format.Asif.Segment@@ -53,20 +53,6 @@ | SUnknown T.Text LBS.ByteString deriving (Show, Eq, Generic) -getWord32x4 :: G.Get (Word32, Word32, Word32, Word32)-getWord32x4 = do- a <- G.getWord32be- b <- G.getWord32be- c <- G.getWord32be- d <- G.getWord32be- return (a, b, c, d)--getWord40 :: G.Get (Word32, Word8)-getWord40 = (,) <$> G.getWord32le <*> G.getWord8--getWord136 :: G.Get ((Word32, Word32, Word32, Word32), Word8)-getWord136 = (,) <$> getWord32x4 <*> G.getWord8- getValues :: Int64 -> G.Get a -> LBSC.ByteString -> [a] getValues n f bs = let getValue bs' = G.runGet f (LBS.take n (bs' <> LBS.replicate n 0))@@ -81,33 +67,28 @@ else init (LBS.split 0 bs) <&> SString F.Bool -> whenNonEmpty bs $- let toBool = (/=) 0- in bs & getValues 1 G.getWord8 <&> (SBool . toBool)+ bs & getValues 1 D.getBool <&> SBool F.Char -> whenNonEmpty bs $ LBSC.unpack bs <&> SChar F.TimeMillis64LE -> whenNonEmpty bs $- let toTime ms = (ms * 1000) ^. from microseconds & posixSecondsToUTCTime- in bs & getValues 8 G.getInt64le <&> (STime . toTime)+ bs & getValues 8 D.getTimeMillis <&> STime F.TimeMicros64LE -> whenNonEmpty bs $- let toTime ms = ms ^. from microseconds & posixSecondsToUTCTime- in bs & getValues 8 G.getInt64le <&> (STime . toTime)+ bs & getValues 8 D.getTimeMicros <&> STime F.Ipv4 -> whenNonEmpty bs $- bs & getValues 4 G.getWord32le <&> (SIpv4 . word32ToIpv4)+ bs & getValues 4 D.getIpv4 <&> SIpv4 F.Ipv6 -> whenNonEmpty bs $- bs & getValues 16 getWord32x4 <&> (SIpv6 . word32x4ToIpv6)+ bs & getValues 16 D.getIpv6 <&> SIpv6 F.Ipv4Block -> whenNonEmpty bs $- let toIpBlock (w32,w8) = IP4.IpBlock (IP4.IpAddress w32) (IP4.IpNetMask w8)- in bs & getValues 5 getWord40 <&> (SIpv4Block . toIpBlock)+ bs & getValues 5 D.getIpv4Block <&> SIpv4Block F.Ipv6Block -> whenNonEmpty bs $- let toIpBlock (w128,w8) = IP6.IpBlock (IP6.IpAddress w128) (IP6.IpNetMask w8)- in bs & getValues 17 getWord136 <&> (SIpv6Block . toIpBlock)+ bs & getValues 17 D.getIpv6Block <&> SIpv6Block F.Int64LE -> whenNonEmpty bs $ bs & getValues 8 G.getInt64le <&> SInt64
src/Arbor/File/Format/Asif/Write.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-}--+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeApplications #-} -- | -- Module: Arbor.File.Format.Asif.Write --@@ -19,6 +19,7 @@ -- $segments , lazyByteStringSegment , nullTerminatedStringSegment+ , fixedLengthAsciiSegment , textSegment , asciiSegment , boolSegment@@ -36,6 +37,12 @@ , ipv6BlockSegment , utcTimeMicrosSegment + -- * Lookup segments+ , lookupSegment+ , word16LookupSegment+ , word32LookupSegment+ , word64LookupSegment+ -- * Utility functions -- $helper , genericInitial@@ -72,6 +79,8 @@ import qualified Data.Text.Lazy.Encoding as TE import qualified HaskellWorks.Data.Network.Ip.Ipv4 as IP4 import qualified HaskellWorks.Data.Network.Ip.Ipv6 as IP6+import qualified Data.Map.Strict as Map+import qualified Data.Foldable as Foldable import qualified Data.Thyme.Clock.POSIX as TY import qualified Data.Thyme.Time.Core as TY@@ -131,7 +140,6 @@ segments <- lift $ foldM fld foldable segmentsC asifType mTimestamp segments ----- -- $segments -- -- Use these to build 'FoldM's for the types you want to encode.@@ -164,6 +172,12 @@ asciiSegment :: MonadResource m => (a -> Char) -> T.Text -> FoldM m a [Segment Handle] asciiSegment = genericFold BB.char8 (Known F.Char) +fixedLengthAsciiSegment :: MonadResource m => (a -> T.Text) -> T.Text -> Word -> FoldM m a [Segment Handle]+fixedLengthAsciiSegment f name len =+ genericFold (Foldable.foldMap BB.char8 . T.unpack) (Known (F.Repeat len F.Char)) (ensureLength . f) name+ where+ ensureLength = T.take (fromIntegral len) . T.justifyLeft 2 ' '+ ----- -- | Build a segment of 'Bool's, encoded as 'Word8's, where False == 0, and True == 1@@ -243,6 +257,82 @@ where fromTime :: TY.UTCTime -> Int64 fromTime = view (TY.posixTime . TY.microseconds)++-- | Creates a lookup segment where index keys are 'Word16'+-- Missing values are represented by 'maxBound :: Word16'+--+-- @+-- word16LookupSegment name f = lookupSegment name f (Known F.Word16LE) BB.word16LE+-- @+word16LookupSegment :: (MonadResource m, Ord b)+ => T.Text+ -> (a -> Maybe b)+ -> FoldM m b [Segment Handle]+ -> FoldM m a [Segment Handle]+word16LookupSegment name f = lookupSegment name f (Known F.Word16LE) BB.word16LE++-- | Creates a lookup segment where index keys are 'Word32'+-- Missing values are represented by 'maxBound :: Word32'+--+-- @+-- word32LookupSegment name f = lookupSegment name f (Known F.Word32LE) BB.word32LE+-- @+word32LookupSegment :: (MonadResource m, Ord b)+ => T.Text+ -> (a -> Maybe b)+ -> FoldM m b [Segment Handle]+ -> FoldM m a [Segment Handle]+word32LookupSegment name f = lookupSegment name f (Known F.Word32LE) BB.word32LE++-- | Creates a lookup segment where index keys are 'Word64'+-- Missing values are represented by 'maxBound :: Word64'+--+-- @+-- word64LookupSegment name f = lookupSegment name f (Known F.Word64LE) BB.word64LE+-- @+word64LookupSegment :: (MonadResource m, Ord b)+ => T.Text+ -> (a -> Maybe b)+ -> FoldM m b [Segment Handle]+ -> FoldM m a [Segment Handle]+word64LookupSegment name f = lookupSegment name f (Known F.Word64LE) BB.word64LE++-- | Creates a lookup segment for every input into a value in an "inner" dictionary segment.+-- Missing values are represented as 'maxBound' for the key type.+lookupSegment :: (MonadResource m, Ord b, Eq i, Num i, Bounded i)+ => T.Text -- ^ Lookup segment name+ -> (a -> Maybe b) -- ^ Extract "dictionary" value+ -> Whatever F.Format -- ^ Format of lookup segment+ -> (i -> BB.Builder) -- ^ Write a lookup value+ -> FoldM m b [Segment Handle] -- ^ A fold that represents a dictionary segment+ -> FoldM m a [Segment Handle]+lookupSegment name f fmt enc (FoldM rstep rinit rextract) =+ FoldM lstep linit lextract+ where+ linit = do+ (_, _, h) <- openTempFile Nothing (T.unpack name)+ rx <- rinit+ pure (h, Map.empty, 0, rx)++ lstep (h, m, c, rx) a =+ case f a of+ Nothing -> do+ liftIO $ BB.hPutBuilder h $ enc maxBound+ pure (h, m, c, rx)+ Just b -> do+ let (v, c', m') = updateMap b c m+ liftIO $ BB.hPutBuilder h $ enc v+ -- only push value to the dictionary fold if the map has been updated+ -- so that the dictionary segment would only have unique values+ rx' <- if c' == c then pure rx else rstep rx b+ pure (h, m', c', rx')++ lextract (h, _, _, rx) = do+ rres <- rextract rx+ pure $ [ segment h $ metaFilename name <> metaFormat fmt] <> rres++ updateMap k c m =+ maybe (c, c+1, Map.insert k c m) (, c, m) (Map.lookup k m) -------------------------------------------------------------------------------
test/Arbor/File/Format/Asif/WriteSpec.hs view
@@ -4,10 +4,13 @@ import Arbor.File.Format.Asif.Format import Arbor.File.Format.Asif.Segment import Arbor.File.Format.Asif.Write+import Control.Lens import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Class import Control.Monad.Trans.Resource import Data.Int+import Data.List (elemIndex, nub)+import Data.Maybe (catMaybes, fromMaybe) import Data.Semigroup ((<>)) import Data.Word import HaskellWorks.Data.Network.Ip.Validity (Canonical)@@ -30,7 +33,9 @@ import qualified System.IO as IO +import Debug.Trace + {-# ANN module ("HLint: ignore Redundant do" :: String) #-} spec :: Spec@@ -253,6 +258,33 @@ [names, times, types, seg] <- forAll $ pure (segmentValues <$> segments) [SText . T.encodeUtf8 . T.concat $ T.fromStrict <$> lst] === seg++ it "should write and read back a lookup segment" $ require $ property $ do+ let pairGen = (,) <$> genIpv4 <*> G.maybe (G.element ["US", "KZ", "AU"])+ pairs <- forAll $ G.list (R.linear 1 100) pairGen++ let countriesFold = nullTerminatedStringSegment id "countries"+ let countriesLookup = word16LookupSegment "ip-to-countries" snd countriesFold++ let resFold = ipv4Segment fst "ip" <> countriesLookup++ content <- asifContent "ipct" Nothing resFold pairs++ let Right segments = extractSegments (AP.string "seg:ipct") content++ [names, times, types, ips, lkp, dict] <- forAll $ pure (segmentValues <$> segments)++ let expectedVals = pairs ^.. each . _2 . _Just & nub+ let expectedLkpVals = pairs ^.. each . _2 . to (\x -> x >>= flip elemIndex expectedVals) . to (fromMaybe maxBound)++ expectedDict <- forAll . pure $+ expectedVals <&> (SString . T.encodeUtf8 . T.fromStrict)++ expectedLkp <- forAll . pure $+ expectedLkpVals <&> (SWord16 . fromIntegral)++ dict === expectedDict+ lkp === expectedLkp genTriple :: MonadGen m => m (Int64, Word16, T.Text)