> module Foreign.Erlang.Network (
>   -- * Low-level communication with the Erlang Port-Mapper Daemon.
>     epmdGetNames
>   , epmdGetPort
>   , epmdGetPortR4
>   
>   , ErlRecv
>   , ErlSend
>   , erlConnect
>   , toNetwork
>   ) where
> import Control.Exception        (assert, bracketOnError)
> import Control.Monad            (liftM)
> import Data.Binary              (decode, encode)
> import Data.Binary.Get
> import Data.Binary.Put
> import Data.Bits                ((.|.))
> import Data.Char                (chr, ord)
> import Data.Digest.OpenSSL.MD5  (md5sum)
> import Data.List                (unfoldr)
> import Data.Word
> import Foreign.Erlang.Types
> import Network                  (PortID(..), connectTo, withSocketsDo)
> import Network.Socket           (PortNumber(..))
> import Numeric                  (readHex)
> import System.Directory         (getHomeDirectory)
> import System.FilePath          ((</>))
> import System.IO
> import System.IO.Unsafe         (unsafePerformIO)
> import System.Random            (randomIO)
> import qualified Data.ByteString.Char8      as C
> import qualified Data.ByteString.Lazy.Char8 as B
> erlangVersion = 5
> erlangProtocolVersion = 131
> passThrough = 'p'
> flagPublished          =  0x01
> flagAtomCache          =  0x02
> flagExtendedReferences =  0x04
> flagDistMonitor        =  0x08
> flagFunTags            =  0x10
> flagDistMonitorName    =  0x20
> flagHiddenAtomCache    =  0x40
> flagNewFunTags         =  0x80
> flagExtendedPidsPorts  = 0x100
> getUserCookie = do
>     home <- getHomeDirectory
>     readFile $ home </> ".erlang.cookie"
> toNetwork     :: Int -> Integer -> [Word8]
> toNetwork b n = reverse . take b $ unfoldr toNetwork' n ++ repeat 0
>   where
>     toNetwork' 0 = Nothing
>     toNetwork' n = let (b, a) = n `divMod` 256 in Just (fromIntegral a, b)
> ntohs n = let (b, a) = n `divMod` 256 in 256*a + b
> erlDigest                  :: String -> Word32 -> [Word8]
> erlDigest cookie challenge = let
>     [(n, _)] = readHex . md5sum . C.pack $ cookie ++ show challenge
>     in toNetwork 16 n
> packn, packN :: B.ByteString -> Put
> packn msg = putn (fromIntegral . B.length $ msg) >> putLazyByteString msg
> packN msg = putN (fromIntegral . B.length $ msg) >> putLazyByteString msg
> sendMessage :: (B.ByteString -> Put) -> (B.ByteString -> IO ()) -> B.ByteString -> IO ()
> sendMessage pack out = out . runPut . pack
> recvMessage            :: Int -> (Int -> IO B.ByteString) -> IO B.ByteString
> recvMessage hdrlen inf = (liftM (unpack hdrlen) $ inf hdrlen) >>= inf
>   where
>     unpack 2 = runGet getn
>     unpack 4 = runGet getN
> type ErlSend = (Maybe ErlType, Maybe ErlType) -> IO ()
> type ErlRecv = IO (Maybe ErlType, Maybe ErlType)
> erlSend :: (B.ByteString -> IO ()) -> ErlSend
> erlSend send (Nothing, _)    = send B.empty
> erlSend send (Just ctl, msg) = send . runPut $ do
>     tag passThrough
>     putMsg ctl
>     maybe (return ()) putMsg msg
>   where
>     putMsg msg = do
>       putC erlangProtocolVersion
>       putErl msg
> erlRecv      :: IO B.ByteString -> ErlRecv
> erlRecv recv = do
>     bytes <- recv
>     return . flip runGet bytes $ do
>       empty <- isEmpty
>       if empty
>         then return (Nothing, Nothing)
>         else do
>           pt <- getC
>           assert (chr pt == passThrough) $ return ()
>           ctl <- getMsg
>           empty <- isEmpty
>           if empty
>             then return (Just ctl, Nothing)
>             else case ctl of
>                    ErlTuple (ErlInt n:_) | n `elem` [2, 6] -> do
>                      msg <- getMsg
>                      return (Just ctl, Just msg)
>                    _ -> return (Just ctl, Nothing)
>   where
>     getMsg = do
>       ver <- getC
>       assert (ver == erlangProtocolVersion) $ getErl
> erlConnect           :: String -> String -> IO (ErlSend, ErlRecv)
> erlConnect self node = withSocketsDo $ do
>     port <- epmdGetPort node
>     let port' = PortNumber (PortNum . fromIntegral . ntohs $ port)
>     bracketOnError
>       (connectTo epmdHost port' >>= \h -> hSetBuffering h NoBuffering >> return h)
>       hClose $ \h -> do
>         let out = sendMessage packn (B.hPut h)
>         let inf = recvMessage 2 (B.hGet h)
>         handshake out inf self
>         let out' = sendMessage packN (\s -> B.hPut h s >> hFlush h)
>         let inf' = recvMessage 4 (B.hGet h)
>         return (erlSend out', erlRecv inf')
> handshake              :: (B.ByteString -> IO ()) -> IO B.ByteString -> String -> IO ()
> handshake out inf self = do
>     cookie <- getUserCookie
>     sendName
>     recvStatus
>     challenge <- recvChallenge
>     let reply = erlDigest cookie challenge
>     challenge' <- liftM fromIntegral (randomIO :: IO Int)
>     challengeReply reply challenge'
>     recvChallengeAck cookie challenge'
>   where
>     sendName = out . runPut $ do
>         tag 'n'
>         putn erlangVersion
>         putN $ flagExtendedReferences .|. flagExtendedPidsPorts
>         putA self
>     recvStatus = do
>         msg <- inf
>         assert ("sok" == B.unpack msg) $ return ()
>     recvChallenge = do
>         msg <- inf
>         return . flip runGet msg $ do
>             tag <- getC
>             version <- getn
>             flags <- getN
>             challenge <- getWord32be
>             return challenge
>     challengeReply reply challenge = out . runPut $ do
>         tag 'r'
>         putWord32be challenge
>         puta reply
>     recvChallengeAck cookie challenge = do
>         let digest = erlDigest cookie challenge
>         msg <- inf
>         let reply = take 16 . drop 1 . map (fromIntegral . ord) . B.unpack $ msg
>         assert (digest == reply) $ return ()
> epmdHost = "127.0.0.1"
> epmdPort = Service "epmd"
> withEpmd = withSocketsDo . bracketOnError
>     (connectTo epmdHost epmdPort >>= \h -> hSetBuffering h NoBuffering >> return h)
>     hClose
> epmdSend     :: String -> IO B.ByteString
> epmdSend msg = withEpmd $ \hdl -> do
>     let out = runPut $ putn (length msg) >> putA msg
>     B.hPut hdl out
>     hFlush hdl
>     B.hGetContents hdl
> -- | Return the names and addresses of all registered Erlang nodes.
> epmdGetNames :: IO [String]
> epmdGetNames = do
>     reply <- epmdSend "n"
>     let txt = runGet (getN >> liftM B.unpack getRemainingLazyByteString) reply
>     return . lines $ txt
> -- | Return the port address of a named Erlang node.
> epmdGetPort      :: String -> IO Int
> epmdGetPort name = do
>     reply <- epmdSend $ 'p' : name
>     return $ runGet getn reply
> -- | Returns (port, nodeType, protocol, vsnMax, vsnMin, name, extra)
> epmdGetPortR4      :: String -> IO (Int, Int, Int, Int, Int, String, String)
> epmdGetPortR4 name = do
>     reply <- epmdSend $ 'z' : name
>     return $ flip runGet reply $ do
>         getn
>         port <- getn
>         nodeType <- getC
>         protocol <- getC
>         vsnMax <- getn
>         vsnMin <- getn
>         name <- getn >>= getA
>         extra <- liftM B.unpack getRemainingLazyByteString
>         return (port, nodeType, protocol, vsnMax, vsnMin, name, extra)