packages feed

network-transport-quic-0.1.0: src/Network/Transport/QUIC/Internal/QUICAddr.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Network.Transport.QUIC.Internal.QUICAddr (
    EndPointId (..),
    QUICAddr (..),
    encodeQUICAddr,
    decodeQUICAddr,
) where

import Data.Binary (Binary)
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString.Char8 qualified as BSC (unpack)
import Data.Word (Word32)
import Network.Socket (HostName, ServiceName)
import Network.Transport (EndPointAddress (EndPointAddress))

{- | Represents the unique ID of an endpoint within a transport.

This is used by endpoints to identify remote endpoints, even though
the remote endpoints are all backed by the same QUIC address.
-}
newtype EndPointId = EndPointId Word32
    deriving newtype (Eq, Show, Ord, Read, Bounded, Enum, Real, Integral, Num, Binary)

-- A QUICAddr represents the unique address an `endpoint` has, which involves
-- pointing to the transport (HostName, ServiceName) and then specific
-- endpoint spawned by that transport (EndpointId)
data QUICAddr = QUICAddr
    { quicBindHost :: !HostName
    , quicBindPort :: !ServiceName
    , quicEndpointId :: !EndPointId
    }
    deriving (Eq, Ord, Show)

-- | Encode a 'QUICAddr' to 'EndPointAddress'
encodeQUICAddr :: QUICAddr -> EndPointAddress
encodeQUICAddr (QUICAddr host port ix) =
    EndPointAddress
        (BS8.pack $ host <> ":" <> port <> ":" <> show ix)

-- | Decode end point address
decodeQUICAddr ::
    EndPointAddress ->
    Either String QUICAddr
decodeQUICAddr (EndPointAddress bs) =
    case splitMaxFromEnd (== ':') 2 $ BSC.unpack bs of
        [host, port, endPointIdStr] ->
            case reads endPointIdStr of
                [(endPointId, "")] -> Right $ QUICAddr host port endPointId
                _ -> Left $ "Undecodeable 'EndPointAddress': " <> show bs
        _ ->
            Left $ "Undecodeable 'EndPointAddress': " <> show bs

{- | @spltiMaxFromEnd p n xs@ splits list @xs@ at elements matching @p@,
returning at most @p@ segments -- counting from the /end/

> splitMaxFromEnd (== ':') 2 "ab:cd:ef:gh" == ["ab:cd", "ef", "gh"]
-}
splitMaxFromEnd :: (a -> Bool) -> Int -> [a] -> [[a]]
splitMaxFromEnd p = \n -> go [[]] n . reverse
  where
    -- go :: [[a]] -> Int -> [a] -> [[a]]
    go accs _ [] = accs
    go ([] : accs) 0 xs = reverse xs : accs
    go (acc : accs) n (x : xs) =
        if p x
            then go ([] : acc : accs) (n - 1) xs
            else go ((x : acc) : accs) n xs
    go _ _ _ = error "Bug in splitMaxFromEnd"