Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
module Tahoe.Client where
import Control.Exception (SomeException (SomeException))
import Control.Monad.IO.Class (MonadIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64 as Base64
import qualified Data.Set as Set
import qualified Data.Text as T
import Network.HTTP.Client (Manager, ManagerSettings (managerModifyRequest), Request (requestHeaders))
import Network.URI (URI (URI, uriAuthority, uriFragment, uriPath, uriScheme), URIAuth (URIAuth, uriPort, uriRegName, uriUserInfo))
import Servant.Client (BaseUrl (BaseUrl, baseUrlHost, baseUrlPath, baseUrlPort, baseUrlScheme))
import Tahoe.Announcement (StorageServerAnnouncement, greatBlackSwampURIs)
import TahoeLAFS.Storage.API (CBORSet (CBORSet), ShareNumber (ShareNumber))
import Text.Read (readMaybe)
-- | There was a problem while trying to look up a server from its announcement.
data LookupError
= -- | The server's announced URI was unparseable.
URIParseError StorageServerAnnouncement
| -- | The port integer in the server's URI was unparseable.
PortParseError String
| -- | The structure of the server's URI was unparseable.
AnnouncementStructureUnmatched
deriving (Eq, Ord, Show)
data StorageClient = StorageClient
{ storageClientManager :: Manager
, storageClientBaseUrl :: BaseUrl
}
makeGBSManager :: MonadIO m => URI -> m (Either LookupError Manager)
makeGBSManager
URI
{ uriScheme = "pb:"
, uriAuthority = Just URIAuth{uriUserInfo = tubid, uriRegName = host, uriPort = (':' : port)}
, uriPath = ('/' : swissnum)
, uriFragment = "" -- It's a fURL, not a NURL, so there's no fragment.
} =
case readMaybe port of
Nothing -> pure . Left . PortParseError $ port
Just realPort -> do
manager <- newGBSManager tubid swissnum
wrapGreatBlackSwamp
-- | Make a manager suitable for use with a Great Black Swamp server.
newGBSManager ::
MonadIO m =>
[Char] ->
String ->
m Manager
newGBSManager tubid swissnum =
newTlsManagerWith $
managerSettingsForService
(T.pack . init $ tubid)
(T.pack swissnum)
-- pure . Right $ wrapGreatBlackSwamp manager host realPort
-- makeServer _ = pure . Left $ AnnouncementStructureUnmatched
-- Parameterize readImmutableShare and getImmutableShareNumbers to
-- wrapGreatBlackSwamp, then use it for both mutables and immutables. Then
-- use the same download function for both!
{- | Create a StorageClient that will speak Great Black Swamp using the given
manager to the server at the given host/port.
-}
wrapGreatBlackSwamp :: [Char] -> Int -> Manager -> StorageServer
wrapGreatBlackSwamp host realPort manager =
StorageClient{..}
where
baseUrl = https host realPort
env = mkClientEnv manager baseUrl
toBase32 = T.unpack . T.toLower . encodeBase32Unpadded
storageServerID = undefined
storageServerWrite = undefined
storageServerRead storageIndex shareNum = do
let clientm = readImmutableShare (toBase32 storageIndex) (ShareNumber $ fromIntegral shareNum) Nothing
res <- runClientM clientm env
case res of
Left err -> throwIO err
Right bs -> pure bs
storageServerGetBuckets storageIndex = do
let clientm = getImmutableShareNumbers (toBase32 storageIndex)
print' "Going to get share numbers"
r <- try $ runClientM clientm env
case r of
Left (err :: SomeException) -> do
pure mempty
Right res -> do
case res of
Left err -> throwIO err
Right (CBORSet s) -> pure $ Set.map (\(ShareNumber i) -> fromIntegral i) s -- XXX fromIntegral aaaaaaaa!!
-- | Make an HTTPS URL.
https :: String -> Int -> BaseUrl
https host port =
BaseUrl
{ baseUrlScheme = Https
, baseUrlHost = host
, baseUrlPort = port
, baseUrlPath = ""
}
{- | Make an HTTPS manager for the given SPKI hash and swissnum.
The SPKI hash is _not_ used to authenticate the server! See
https://whetstone.private.storage/privatestorage/tahoe-great-black-swamp/-/issues/27
-}
managerSettingsForService :: T.Text -> T.Text -> ManagerSettings
managerSettingsForService _ swissnum =
(mkManagerSettings tlsSettings sockSettings){managerModifyRequest = pure . authorize}
where
tlsSettings = TLSSettingsSimple True True True
sockSettings = Nothing
swissnumBytes = encodeUtf8 swissnum
swissnumBase64 = Base64.encode swissnumBytes
headerCompleteBytes = B.concat ["Tahoe-LAFS ", swissnumBase64]
authorize req =
req
{ requestHeaders =
( "Authorization"
, headerCompleteBytes
) :
requestHeaders req
}
{- | Interpret the location in an announcement as a Tahoe-LAFS fURL pointed at
a Great Black Swamp server.
-}
announcementToStorageServer :: MonadIO m => StorageServerAnnouncement -> m (Either LookupError StorageServer)
announcementToStorageServer ann =
case greatBlackSwampURIs ann of
Nothing -> pure . Left . URIParseError $ ann
Just uri -> makeServer uri