content: {-# LANGUAGE DuplicateRecordFields #-}
  {-# LANGUAGE NamedFieldPuns #-}
  {-# LANGUAGE OverloadedLists #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE TypeApplications #-}
  {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
  
  module CoreTests.SOCKSSettings where
  
  import Network.Socket (SockAddr (..), tupleToHostAddress)
  import Simplex.Messaging.Client
  import Simplex.Messaging.Encoding.String
  import Simplex.Messaging.Protocol (ErrorType)
  import Simplex.Messaging.Transport.Client
  import Test.Hspec hiding (fit, it)
  import Util
  
  socksSettingsTests :: Spec
  socksSettingsTests = do
    describe "hostMode and requiredHostMode settings" testHostMode
    describe "socksMode setting, independent of hostMode setting" testSocksMode
    describe "socks proxy address encoding" testSocksProxyEncoding
  
  testPublicHost :: TransportHost
  testPublicHost = "smp.example.com"
  
  testOnionHost :: TransportHost
  testOnionHost = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst.onion"
  
  testHostMode :: Spec
  testHostMode = do
    describe "requiredHostMode = False (default)" $ do
      it "without socks proxy, should choose onion host only with HMOnion" $ do
        chooseTransportHost @ErrorType defaultNetworkConfig [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
        chooseHost HMOnionViaSocks Nothing [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
        chooseHost HMOnion Nothing [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
        chooseHost HMPublic Nothing [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
      it "with socks proxy, should choose onion host with HMOnionViaSocks (default) and HMOnion" $ do
        chooseTransportHost @ErrorType defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
        chooseHost HMOnionViaSocks (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
        chooseHost HMOnion (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
        chooseHost HMPublic (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
      it "should choose any available host, if preferred not available" $ do
        chooseHost HMOnionViaSocks Nothing [testOnionHost] `shouldBe` Right testOnionHost
        chooseHost HMOnion Nothing [testPublicHost] `shouldBe` Right testPublicHost
        chooseHost HMPublic Nothing [testOnionHost] `shouldBe` Right testOnionHost
        chooseHost HMOnionViaSocks (Just defaultSocksProxyWithAuth) [testPublicHost] `shouldBe` Right testPublicHost
        chooseHost HMOnion (Just defaultSocksProxyWithAuth) [testPublicHost] `shouldBe` Right testPublicHost
        chooseHost HMPublic (Just defaultSocksProxyWithAuth) [testOnionHost] `shouldBe` Right testOnionHost
    describe "requiredHostMode = True" $ do
      it "should fail, if preferred host not available" $ do
        testOnionHost `incompatible` (HMOnionViaSocks, Nothing)
        testPublicHost `incompatible` (HMOnion, Nothing)
        testOnionHost `incompatible` (HMPublic, Nothing)
        testPublicHost `incompatible` (HMOnionViaSocks, Just defaultSocksProxyWithAuth)
        testPublicHost `incompatible` (HMOnion, Just defaultSocksProxyWithAuth)
        testOnionHost `incompatible` (HMPublic, Just defaultSocksProxyWithAuth)
      it "should choose preferred host, if available" $ do
        testPublicHost `compatible` (HMOnionViaSocks, Nothing)
        testOnionHost `compatible` (HMOnion, Nothing)
        testPublicHost `compatible` (HMPublic, Nothing)
        testOnionHost `compatible` (HMOnionViaSocks, Just defaultSocksProxyWithAuth)
        testOnionHost `compatible` (HMOnion, Just defaultSocksProxyWithAuth)
        testPublicHost `compatible` (HMPublic, Just defaultSocksProxyWithAuth)
    where
      chooseHost = chooseHostCfg defaultNetworkConfig
      host `incompatible` (hostMode, socksProxy) =
        chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host] `shouldBe` Left PCEIncompatibleHost
      host `compatible` (hostMode, socksProxy) = do
        chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host] `shouldBe` Right host
        chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host, testPublicHost, testOnionHost] `shouldBe` Right host
      chooseHostCfg cfg hostMode socksProxy =
        chooseTransportHost @ErrorType cfg {hostMode, socksProxy}
  
  testSocksMode :: Spec
  testSocksMode = do
    it "should not use SOCKS proxy if not specified" $ do
      transportSocksCfg defaultNetworkConfig testPublicHost `shouldBe` Nothing
      transportSocksCfg defaultNetworkConfig testOnionHost `shouldBe` Nothing
      transportSocks Nothing SMAlways testPublicHost `shouldBe` Nothing
      transportSocks Nothing SMAlways testOnionHost `shouldBe` Nothing
      transportSocks Nothing SMOnion testPublicHost `shouldBe` Nothing
      transportSocks Nothing SMOnion testOnionHost `shouldBe` Nothing
    it "should always use SOCKS proxy if specified and (socksMode = SMAlways or (socksMode = SMOnion and onion host))" $ do
      transportSocksCfg defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} testPublicHost `shouldBe` Just defaultSocksProxy
      transportSocksCfg defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} testOnionHost `shouldBe` Just defaultSocksProxy
      transportSocks (Just defaultSocksProxyWithAuth) SMAlways testPublicHost `shouldBe` Just defaultSocksProxy
      transportSocks (Just defaultSocksProxyWithAuth) SMAlways testOnionHost `shouldBe` Just defaultSocksProxy
      transportSocks (Just defaultSocksProxyWithAuth) SMOnion testPublicHost `shouldBe` Nothing
      transportSocks (Just defaultSocksProxyWithAuth) SMOnion testOnionHost `shouldBe` Just defaultSocksProxy
    where
      transportSocks proxy socksMode = transportSocksCfg defaultNetworkConfig {socksProxy = proxy, socksMode}
      transportSocksCfg cfg host =
        let TransportClientConfig {socksProxy} = transportClientConfig cfg NRMInteractive host False Nothing
         in socksProxy
  
  testSocksProxyEncoding :: Spec
  testSocksProxyEncoding = do
    it "should decode SOCKS proxy with isolate-by-auth mode" $ do
      let authIsolate proxy = Right $ SocksProxyWithAuth SocksIsolateByAuth proxy
      strDecode "" `shouldBe` authIsolate defaultSocksProxy
      strDecode ":9050" `shouldBe` authIsolate defaultSocksProxy
      strDecode ":8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 8080 $ tupleToHostAddress defaultSocksHost)
      strDecode "127.0.0.1" `shouldBe` authIsolate defaultSocksProxy
      strDecode "1.1.1.1" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress (1, 1, 1, 1))
      strDecode "::1" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)
      strDecode "[fd12:3456:789a:1::1]" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0xfd123456, 0x789a0001, 0, 1) 0)
      strDecode "127.0.0.1:9050" `shouldBe` authIsolate defaultSocksProxy
      strDecode "127.0.0.1:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 8080 $ tupleToHostAddress defaultSocksHost)
      strDecode "[::1]:9050" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)
      strDecode "[::1]:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 8080 0 (0, 0, 0, 1) 0)
      strDecode "[fd12:3456:789a:1::1]:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
      strEncode (SocksProxyWithAuth SocksIsolateByAuth defaultSocksProxy) `shouldBe` "127.0.0.1:9050"
      strEncode (SocksProxyWithAuth SocksIsolateByAuth (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)) `shouldBe` "[::1]:9050"
      strEncode (SocksProxyWithAuth SocksIsolateByAuth (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)) `shouldBe` "[fd12:3456:789a:1::1]:8080"
    it "should decode SOCKS proxy without credentials" $ do
      let authNull proxy = Right $ SocksProxyWithAuth SocksAuthNull proxy
      strDecode "@" `shouldBe` authNull defaultSocksProxy
      strDecode "@:9050" `shouldBe` authNull defaultSocksProxy
      strDecode "@127.0.0.1" `shouldBe` authNull defaultSocksProxy
      strDecode "@1.1.1.1" `shouldBe` authNull (SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress (1, 1, 1, 1))
      strDecode "@127.0.0.1:9050" `shouldBe` authNull defaultSocksProxy
      strDecode "@[fd12:3456:789a:1::1]:8080" `shouldBe` authNull (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
      strEncode (SocksProxyWithAuth SocksAuthNull defaultSocksProxy) `shouldBe` "@127.0.0.1:9050"
      strEncode (SocksProxyWithAuth SocksAuthNull (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)) `shouldBe` "@[::1]:9050"
      strEncode (SocksProxyWithAuth SocksAuthNull (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)) `shouldBe` "@[fd12:3456:789a:1::1]:8080"
    it "should decode SOCKS proxy with credentials" $ do
      let auth = SocksAuthUsername {username = "user", password = "pass"}
          authUser proxy = Right $ SocksProxyWithAuth auth proxy
      strDecode "user:pass@" `shouldBe` authUser defaultSocksProxy
      strDecode "user:pass@:9050" `shouldBe` authUser defaultSocksProxy
      strDecode "user:pass@127.0.0.1" `shouldBe` authUser defaultSocksProxy
      strDecode "user:pass@127.0.0.1:9050" `shouldBe` authUser defaultSocksProxy
      strDecode "user:pass@fd12:3456:789a:1::1" `shouldBe` authUser (SocksProxy $ SockAddrInet6 9050 0 (0xfd123456, 0x789a0001, 0, 1) 0)
      strDecode "user:pass@[fd12:3456:789a:1::1]:8080" `shouldBe` authUser (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
      strEncode (SocksProxyWithAuth auth defaultSocksProxy) `shouldBe` "user:pass@127.0.0.1:9050"
      strEncode (SocksProxyWithAuth auth (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)) `shouldBe` "user:pass@[::1]:9050"
      strEncode (SocksProxyWithAuth auth (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)) `shouldBe` "user:pass@[fd12:3456:789a:1::1]:8080"
  