packages feed

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