{-# OPTIONS_HADDOCK not-home #-} -- | -- Description : Peer version exchange -- -- Once a shared 'ClientProtocol.SessionKey' has been negotiated, the peers -- need to confirm that they have the same key. They do this with -- 'versionExchange'. module MagicWormhole.Internal.Versions ( versionExchange , Versions(..) , VersionsError(..) ) where import Protolude hiding (phase) import Data.Aeson (FromJSON, ToJSON, (.=), object, Value(..), (.:)) import Data.Aeson.Types (typeMismatch) import qualified Data.Aeson as Aeson import Data.String (String) import qualified MagicWormhole.Internal.ClientProtocol as ClientProtocol import qualified MagicWormhole.Internal.Messages as Messages -- NOTE: Versions -- ~~~~~~~~~~~~~~ -- -- Magic Wormhole Python implementation sends the following as the 'version' phase: -- {"app_versions": {}} -- -- The idea is that /some time in the future/ this will be used to indicate -- capabilities of peers. At present, it is unused, save as a confirmation -- that the SPAKE2 exchange worked. -- | Exchange version information with a Magic Wormhole peer. -- -- Can throw an 'Error' if something goes wrong. versionExchange :: ClientProtocol.Connection -- ^ A connection to a peer -> ClientProtocol.SessionKey -- ^ A shared session key. Obtain this via 'MagicWormhole.Internal.Pake.pakeExchange'. -> IO Versions -- ^ Shared version information versionExchange conn key = do (_, theirVersions) <- concurrently sendVersion (atomically receiveVersion) if theirVersions /= Versions then throwIO VersionMismatch else pure Versions where sendVersion = ClientProtocol.sendEncrypted conn key Messages.VersionPhase (ClientProtocol.PlainText (toS (Aeson.encode Versions))) receiveVersion = do (phase, ClientProtocol.PlainText plaintext) <- ClientProtocol.receiveEncrypted conn key unless (phase == Messages.VersionPhase) retry either (throwSTM . ParseError) pure $ Aeson.eitherDecode (toS plaintext) -- | Information about the versions supported by this Magic Wormhole client. -- -- There are no extant Magic Wormhole implementations that send any meaningful -- information in their versions message, so this is just a single-valued -- type. data Versions = Versions deriving (Eq, Show) instance ToJSON Versions where toJSON _ = object ["app_versions" .= object []] instance FromJSON Versions where parseJSON (Object v) = do -- Make sure there's an object in the "app_versions" key and abort if not. (Object _versions) <- v .: "app_versions" pure Versions parseJSON unknown = typeMismatch "Versions" unknown -- | An error occurred during 'versionExchange'. data VersionsError -- | We could not interpret the other side's version information = ParseError String -- | The other side sent us version information, but it does not match ours, -- so we cannot proceed. | VersionMismatch deriving (Eq, Show, Typeable) instance Exception VersionsError