From fb1c168555efcdebbcff6f581b7c73b4ba873d7f Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Fri, 21 Apr 2023 14:57:08 -0400
Subject: [PATCH] start of attempts to adapt the gbs interface to what we know
 how to work with

quite possibly both of these interfaces are wrong
may want to ponder them for a moment before proceeding
---
 gbs-downloader.cabal  |  6 +++
 src/Tahoe/Download.hs | 89 +++++++++++++++++++++++++++++++++++++++++++
 test/Spec.hs          | 22 +++++++++++
 3 files changed, 117 insertions(+)

diff --git a/gbs-downloader.cabal b/gbs-downloader.cabal
index 8d37700..5b126bd 100644
--- a/gbs-downloader.cabal
+++ b/gbs-downloader.cabal
@@ -86,9 +86,15 @@ library
   -- Other library packages from which modules are imported.
   build-depends:
     , base                     ^>=4.14.3.0
+    , base32
     , binary
     , bytestring
     , containers
+    , exceptions
+    , http-client
+    , modern-uri
+    , servant-client
+    , servant-client-core
     , tahoe-chk
     , tahoe-great-black-swamp
     , text
diff --git a/src/Tahoe/Download.hs b/src/Tahoe/Download.hs
index 35d8718..bbc274e 100644
--- a/src/Tahoe/Download.hs
+++ b/src/Tahoe/Download.hs
@@ -1,19 +1,46 @@
+{-# LANGUAGE ViewPatterns #-}
+
 {- | A high-level interface to downloading share data as bytes from storage
  servers.
 -}
 module Tahoe.Download (LookupServer, DownloadError (..), discoverShares, download) where
+k with)
+
+module Tahoe.Download (
+    LookupServer,
+    DownloadError (..),
+    discoverOnce,
+    download,
+    gbsURLToStorageServer,
+) where
 
+import Control.Exception (throwIO)
+import Control.Monad.Catch (MonadCatch, catch)
+import Control.Monad.IO.Class (MonadIO (liftIO))
 import Data.Binary (decodeOrFail)
 import qualified Data.ByteString.Lazy as LB
 import Data.Either (isRight, rights)
 import Data.List (foldl')
+import Data.List.NonEmpty (NonEmpty ((:|)))
 import qualified Data.Map.Strict as Map
 import qualified Data.Set as Set
+import qualified Data.Text as T
+import Data.Text.Encoding (decodeUtf8)
+import Network.HTTP.Client (ManagerSettings, defaultManagerSettings, newManager)
+import Servant.Client (Scheme (Https), mkClientEnv, runClientM)
+import Servant.Client.Core (BaseUrl (BaseUrl, baseUrlHost, baseUrlPath, baseUrlPort, baseUrlScheme))
 import qualified Tahoe.CHK
 import Tahoe.CHK.Capability (Reader (..), Verifier (..))
 import qualified Tahoe.CHK.Encrypt
 import Tahoe.CHK.Server (StorageServer (..), StorageServerAnnouncement (..), StorageServerID, URL)
 import Tahoe.CHK.Types (ShareNum, StorageIndex)
+import TahoeLAFS.Storage.API (ShareNumber (ShareNumber))
+import TahoeLAFS.Storage.Client (readImmutableShares)
+import Text.URI (Authority (Authority, authHost, authPort, authUserInfo), ParseException (ParseException), RText, URI (..), UserInfo (UserInfo, uiPassword, uiUsername), mkURI, unRText)
+
+-- | A view pattern for matching on the text of an RTest value.
+restrictedText :: RText l -> String
+restrictedText (T.unpack . unRText -> t) = t
 
 {- | An unrecoverable problem arose while attempting to download and/or read
  some application data.
@@ -51,6 +78,8 @@ data DownloadError
 data DiscoverError
     = -- | An announcement did not include a location for a connection attempt.
       StorageServerLocationUnknown
+    | -- | An announcement included a location we could not interpret.
+      StorageServerLocationUnsupported
     | StorageServerUnreachable
     deriving (Eq, Ord, Show)
 
@@ -152,3 +181,63 @@ downloadShare storageIndex shareNum (s : _) = do
     -- we might also need retry logic up a level or two from here.
     shareBytes <- storageServerRead s storageIndex shareNum
     pure (shareNum, Right $ LB.fromStrict shareBytes)
+
+gbsURLToStorageServer :: (MonadIO m, MonadCatch m) => T.Text -> m (Maybe StorageServer)
+gbsURLToStorageServer url =
+    catch (mkURI url >>= makeServer) (\ParseException{} -> pure Nothing)
+
+makeServer :: MonadIO m => URI -> m (Maybe StorageServer)
+makeServer
+    URI
+        { uriScheme = Just (restrictedText -> "pb")
+        , uriAuthority =
+            Right
+                ( Authority
+                        { authUserInfo =
+                            Just
+                                ( UserInfo
+                                        { uiUsername = tubid
+                                        , uiPassword = Nothing
+                                        }
+                                    )
+                        , authHost = host
+                        , authPort = Just port
+                        }
+                    )
+        , uriPath = Just (False, swissnum :| [])
+        , uriQuery = []
+        , uriFragment = Just (restrictedText -> "v=1")
+        } =
+        do
+            manager <- liftIO $ newManager (managerSettingsForService (unRText tubid) (unRText swissnum))
+
+            let baseUrl = https (T.unpack $ unRText host) (fromIntegral port)
+                env = mkClientEnv manager baseUrl
+
+                storageServerID = undefined
+
+                storageServerWrite = undefined
+
+                storageServerRead storageIndex shareNum = do
+                    let clientm = readImmutableShares (T.unpack $ decodeUtf8 storageIndex) (ShareNumber $ fromIntegral shareNum) Nothing
+                    res <- runClientM clientm env
+                    case res of
+                        Left err -> throwIO err
+                        Right bs -> pure bs
+
+                storageServerGetBuckets = undefined
+
+            pure $ Just StorageServer{..}
+makeServer _ = pure Nothing
+
+https :: String -> Int -> BaseUrl
+https host port =
+    BaseUrl
+        { baseUrlScheme = Https
+        , baseUrlHost = host
+        , baseUrlPort = port
+        , baseUrlPath = "/"
+        }
+
+managerSettingsForService :: T.Text -> T.Text -> ManagerSettings
+managerSettingsForService _ _ = defaultManagerSettings
diff --git a/test/Spec.hs b/test/Spec.hs
index f254790..7751682 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -196,6 +196,28 @@ tests =
                 -- Recover the plaintext from the servers.
                 result <- liftIO $ download serverAnnouncements cap lookupServer
                 diff (Right plaintext) (==) result
+        , testCase "immutable upload/download to using Great Black Swamp" $ do
+            pure ()
+            -- Consider moving these tests to another module, they're pretty
+            -- different and there's quite a handful of them.
+            --
+            -- SUCCESS CASE
+            -- Make an in-memory GBS backend
+            -- Encode some data to shares
+            -- Store the shares on the backend
+            -- Wrap an HTTPS server around the backend
+            -- Get its FURL
+            -- Make an announcement for it
+            -- Give the announcement and the cap to the download function
+            -- Assert we recovered the plaintext
+            --
+            -- ERROR CASES
+            -- Server address unresolveable
+            -- Server address unconnectable
+            -- Server times out
+            -- Server presents incorrect TLS certificate
+            -- Server returns error response to our request
+            -- Server returns tampered share data
         ]
   where
     -- A server lookup function that always fails.
-- 
GitLab