tahoe-chk-0.1.0.2: src/Tahoe/CHK/Server.hs
{-# LANGUAGE RecordWildCards #-}
module Tahoe.CHK.Server where
import Data.Aeson (
FromJSON (..),
ToJSON (..),
object,
withObject,
(.:),
(.:?),
(.=),
)
import qualified Data.ByteString as B
import Data.ByteString.Base32 (decodeBase32Unpadded, encodeBase32Unpadded)
import Data.List (sortOn)
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Tahoe.CHK.Crypto (sha1, sha256)
import Tahoe.CHK.Types (Offset, ShareNum, StorageIndex)
-- Where can a server be found
type URL = T.Text
-- The unique identifier for a particular storage server, conventionally the
-- lowercase base32 encoding of some public key controlled by the server.
type StorageServerID = T.Text
-- | An announcement from a storage server about its storage service.
data StorageServerAnnouncement = StorageServerAnnouncement
{ storageServerAnnouncementFURL :: Maybe URL
, storageServerAnnouncementNick :: Maybe T.Text
, storageServerAnnouncementPermutationSeed :: Maybe B.ByteString
}
deriving (Eq, Ord, Show)
-- A server that can have some data uploaded to it.
data StorageServer = StorageServer
{ storageServerID :: StorageServerID
, -- TODO Strict byte strings here are unfortunate. They will force whole
-- chunks of data into memory at once.
storageServerWrite :: StorageIndex -> ShareNum -> Offset -> B.ByteString -> IO ()
, storageServerRead :: StorageIndex -> ShareNum -> IO B.ByteString
, storageServerGetBuckets :: StorageIndex -> IO (Set.Set ShareNum)
}
instance Eq StorageServer where
a == b = storageServerID a == storageServerID b
instance Ord StorageServer where
a <= b = storageServerID a <= storageServerID b
instance Show StorageServer where
show ss = show $ storageServerID ss
type ShareMap = Map.Map ShareNum (Set.Set StorageServer)
instance FromJSON StorageServerAnnouncement where
parseJSON = withObject "StorageServerAnnouncement" $ \ann -> do
v <- ann .: "ann"
storageServerAnnouncementFURL <- v .:? "anonymous-storage-FURL"
storageServerAnnouncementNick <- v .:? "nickname"
permutationSeed <- v .:? "permutation-seed-base32"
let storageServerAnnouncementPermutationSeed =
case permutationSeed of
Nothing -> Nothing
Just txt -> case decodeBase32Unpadded . encodeUtf8 $ txt of
Left _ -> Nothing
Right ps -> Just ps
pure StorageServerAnnouncement{..}
instance ToJSON StorageServerAnnouncement where
toJSON StorageServerAnnouncement{..} =
object
[ "ann"
.= object
[ "anonymous-storage-FURL" .= storageServerAnnouncementFURL
, "nickname" .= storageServerAnnouncementNick
, "permutation-seed-base32"
.= (encodeBase32Unpadded <$> storageServerAnnouncementPermutationSeed)
]
]
{- | Find the preferred order of servers for an object with the given index.
This is like allmydata.storage_client.StorageFarmBroker.get_servers_for_psi
-}
preferredServers :: StorageIndex -> Map.Map T.Text StorageServerAnnouncement -> [(StorageServerID, StorageServerAnnouncement)]
preferredServers storageIndex = sortOn permutedServerHash . Map.toList
where
permutedServerHash =
-- allmydata.util.hashutil.permute_server_hash
sha1 . (storageIndex <>) . uncurry storageServerPermutationSeed
{- | Compute a sort key for a storage server given its identifier and storage
service announcement.
This is like pieces of allmydata.storage_client._parse_announcement
-}
storageServerPermutationSeed :: StorageServerID -> StorageServerAnnouncement -> B.ByteString
storageServerPermutationSeed serverId ann =
case storageServerAnnouncementPermutationSeed ann of
Just bs -> bs
Nothing ->
case decodeBase32Unpadded . encodeUtf8 . T.drop 3 $ serverId of
Right bs -> bs
Left _ -> sha256 . encodeUtf8 $ serverId