packages feed

ntrip-client-0.1.2: src/Network/Ntrip/Client.hs

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

module Network.Ntrip.Client where

import           BasicPrelude                     hiding (intercalate, tail)
import           Control.Concurrent.Async
import           Control.Lens
import           Data.Attoparsec.ByteString
import           Data.Attoparsec.ByteString.Char8 (decimal, endOfLine, isEndOfLine)
import           Data.ByteString
import           Data.ByteString.Base64
import           Data.CaseInsensitive
import           Data.Conduit
import           Data.Conduit.Attoparsec
import           Data.Conduit.Network
import           Network.HTTP.Types
import           URI.ByteString

tcpClient :: ByteString -> Int -> Source IO ByteString -> Sink ByteString IO () -> IO ()
tcpClient host port source sink =
  runTCPClient (clientSettings port host) $ \ad ->
    void $ concurrently (source $$ appSink ad) (appSource ad $$ sink)

sourceNtrip :: ByteString -> ByteString -> ByteString -> Source IO ByteString -> Source IO ByteString
sourceNtrip path user password source = do
  yield $ intercalate "\r\n"
    [ methodGet <> " " <> path <> " HTTP/1.0"
    , original hUserAgent <> ": NTRIP ntrip-client/0.0"
    , original hAuthorization <> ": " <> encode (user <> ":" <> password)
    , ""
    , ""
    ]
  source

parseNtrip :: Parser (ByteString, Status)
parseNtrip = do
  format  <- parseFormat
  code    <- parseCode
  message <- parseMessage
  return (format, Status code message)
  where
    takeSpace      = takeWhile1 (== 32)
    takeInClass    = takeWhile1 . inClass
    takeEndOfLine1 = takeWhile1 (not . isEndOfLine)
    parseFormat    = takeInClass "a-zA-Z0-9/." <* takeSpace
    parseCode      = decimal <* takeSpace
    parseMessage   = takeEndOfLine1 <* endOfLine

sinkNtrip :: Sink ByteString IO () -> Sink ByteString IO ()
sinkNtrip sink = do
  (format, status) <- sinkParser parseNtrip
  unless (format == "ICY") $ throwIO $ userError "Unsupported format"
  unless (status == status200) $ throwIO $ userError "Bad status"
  sink

runNtrip :: ByteString -> Source IO ByteString -> Sink ByteString IO () -> IO ()
runNtrip url source sink = do
  uri  <- either (throwIO . userError . textToString . show) return $ parseURI strictURIParserOptions url
  auth <- maybe (throwIO $ userError "Bad authority") return $ uri ^. authorityL
  ui   <- maybe (throwIO $ userError "Bad user info") return $ auth ^. authorityUserInfoL
  tcpClient
    (hostBS $ auth ^. authorityHostL)
    (fromMaybe 2101 $ portNumber <$> auth ^. authorityPortL)
    (sourceNtrip
      (tail $ uri ^. pathL)
      (ui ^. uiUsernameL)
      (ui ^. uiPasswordL)
      source)
    (sinkNtrip sink)