packages feed

mvclient-0.2: other/LogTranslate.hs

{-
    Takes saved hex dump files from Wireshark's "Follow UDP Stream", then
    attempts to guess where they break up into packets, and converts to a
    readable format with message names.

    This is the primary tool I've used to reverse engineer the protocol.
-}
module Main where

import Control.Monad

import Network.Metaverse.Circuit
import System.Environment
import Data.Word

import qualified Data.ByteString as B

dumpToBytes :: String -> (Bool, [Word8])
dumpToBytes str = (receiving,
                   map (read . ("0x" ++)) $ drop 1 $ words $ take 59 str1)
    where receiving   = (head str == ' ')
          str1        = if receiving then drop 4 str else str
          dropEnd n s = take (length s - n) s

-- Chunk lines to packets... There are hueristics used here to guess
-- packet boundaries, since Wireshark does not save them well.  They are:
--
-- (1) The transition between sending lines and receiving lines always
--     marks a packet boundary.
--
-- (2) If the length of this line is less than 7 bytes, it can NOT be a new
--     packet, *and* it must be the end of the old packet.
-- 
-- (3) If the line contains a first byte with zeros for its lower 4 bits,
--     the sequence number in bytes 1-5 is within 20 of the last seen sequence,
--     a there is a zero at position 6, then we decide this looks like a
--     packet header, and break it there.
--
-- (4) If the line is less than a full 16 bytes, we know it's the end of
--     a packet.

chunk :: Int -> Int -> [(Bool, [Word8])] -> [(Bool, [Word8])]
chunk inseq outseq []  = []
chunk inseq outseq lns = chunk' [] (fst (head lns)) lns
  where
    chunk' :: [Word8] -> Bool -> [(Bool, [Word8])] -> [(Bool, [Word8])]
    chunk' cur oldrecv ((recv, bytes) : tail)
        | recv /= oldrecv   = (oldrecv, cur) : chunk nin nout ((recv, bytes) : tail)
        | length bytes < 8  = (oldrecv, cur ++ bytes) : chunk nin nout tail
        | cur /= []
            && ((bytes !! 0) `mod` 16 == 0)
            && abs (seqdecode bytes 1 - lastseq) < 2
            && ((bytes !! 5) == 0)
                            = (oldrecv, cur) : chunk nin nout ((recv, bytes) : tail)
        | length bytes < 16 = (oldrecv, cur ++ bytes) : chunk nin nout tail
        | otherwise         = chunk' (cur ++ bytes) recv tail
        where nin       | oldrecv        = seqdecode seqsource 1
                        | otherwise      = inseq
              nout      | oldrecv        = outseq
                        | otherwise      = seqdecode seqsource 1
              seqsource | cur == [] = bytes
                        | otherwise = cur
              lastseq   | oldrecv   = inseq
                        | otherwise = outseq
    seqdecode bs i = fromIntegral (bs !! i) * 16777216
                   + fromIntegral (bs !! (i+1)) * 65536
                   + fromIntegral (bs !! (i+2)) * 256
                   + fromIntegral (bs !! (i+3))

main = do
    [ logfile ] <- getArgs
    txt         <- readFile logfile
    let lns = map dumpToBytes (filter (not . null) $ lines txt)
    mapM_ print (take 40 lns)
    let packets = map (\(r,p) -> (r, deserialize (B.pack p))) (chunk 1 1 lns)
    mapM_ print (take 20 packets)
    forM_ packets $ \(r,p) -> appendFile (logfile ++ ".out") $
        if r then "<<< " ++ show p ++ "\n" else ">>> " ++ show p ++ "\n"