diff --git a/src/Tahoe/Download.hs b/src/Tahoe/Download.hs
index 91f851e9552e642e22f95ff7302117b99db3f8cb..55d94812799652b049f842e20ddb2ec960849430 100644
--- a/src/Tahoe/Download.hs
+++ b/src/Tahoe/Download.hs
@@ -1,4 +1,4 @@
-module Tahoe.Download (DownloadError (..), download) where
+module Tahoe.Download (LookupServer, DownloadError (..), discoverOnce, download) where
 
 import Data.Binary (decodeOrFail)
 import qualified Data.ByteString.Lazy as LB
@@ -23,11 +23,17 @@ data DownloadError
     | -- | Across all of the configured servers, fewer than the required
       -- number of shares were found. XXX Could split this into the different
       -- cases - did not locate enough shares, did not download enough shares,
-      -- did not decode enough shares, did not verify enough shares
+      -- did not verify enough shares
       NotEnoughShares
         { notEnoughSharesNeeded :: Int
         , notEnoughSharesFound :: Int
         }
+    | -- | Across all of the shares that we could download, fewer than the
+      -- required number could actually be decoded.
+      NotEnoughDecodedShares
+        { notEnoughDecodedSharesNeeded :: Int
+        , notEnoughDecodedSharesFound :: Int
+        }
     | ShareDecodingFailed
     | -- | An attempt was made to download a share but no servers were given for
       -- the download.
@@ -43,6 +49,12 @@ data DiscoverError
     | StorageServerUnreachable
     deriving (Eq, Ord, Show)
 
+-- TODO The result might need to be in IO in case the URL indicates a
+-- Tor-based route to the server.  In this case we might need to launch a Tor
+-- daemon or connect to a running Tor daemon or at least set up a new Tor
+-- circuit.  All of which require I/O.  But we can always refactor later!
+type LookupServer = URL -> Maybe StorageServer
+
 {- | Recover the application data associated with a given capability from the
  given servers, if possible.
 -}
@@ -53,20 +65,20 @@ download ::
     -- | The read capability for the application data.
     Reader ->
     -- | Get functions for interacting with a server given its URL.
-    (URL -> IO (Maybe StorageServer)) ->
+    LookupServer ->
     -- | Either a description of how the recovery failed or the recovered
     -- application data.
     IO (Either DownloadError LB.ByteString)
-download servers cap@Reader{readKey, verifier = Verifier{..}} openServer =
+download servers cap@Reader{readKey, verifier = Verifier{..}} lookupServer =
     case Map.toList servers of
         [] -> pure . Left $ NoConfiguredServers
         serverList -> do
             -- Ask each server for all shares it has.
-            discovered <- rights <$> mapM discoverOnce serverList :: IO [(StorageServer, Set.Set ShareNum)]
+            discovered <- rights <$> mapM (discoverOnce lookupServer storageIndex) serverList :: IO [(StorageServer, Set.Set ShareNum)]
             if null discovered
                 then pure $ Left NoReachableServers
                 else
-                    if (fromIntegral required >=) . countDistinctShares $ discovered
+                    if (fromIntegral required >) . countDistinctShares $ discovered
                         then pure $ Left NotEnoughShares{notEnoughSharesNeeded = fromIntegral required, notEnoughSharesFound = countDistinctShares discovered}
                         else do
                             -- XXX up to here is probably "locateShares".  now we move in to "fetchShares".
@@ -79,29 +91,32 @@ download servers cap@Reader{readKey, verifier = Verifier{..}} openServer =
                                 fewerShares = filter (isRight . snd) $ (\(sharenum, Right bs) -> (sharenum, decodeOrFail bs)) <$> someShares
                                 onlyDecoded = (\(sharenum, Right (_, _, share)) -> (fromIntegral sharenum, share)) <$> fewerShares
                             if length onlyDecoded < fromIntegral required
-                                then pure $ Left NotEnoughShares{notEnoughSharesNeeded = fromIntegral required, notEnoughSharesFound = length onlyDecoded}
+                                then pure $ Left NotEnoughDecodedShares{notEnoughDecodedSharesNeeded = fromIntegral required, notEnoughDecodedSharesFound = length onlyDecoded}
                                 else do
                                     ciphertext <- Tahoe.CHK.decode cap onlyDecoded
                                     case ciphertext of
                                         Nothing -> pure $ Left ShareDecodingFailed
                                         Just ct ->
                                             pure . Right $ Tahoe.CHK.Encrypt.decrypt readKey ct
-  where
-    -- Figure the total number of distinct shares reported by all of the servers we
-    -- asked.
-    countDistinctShares = Set.size . foldl' Set.union mempty . map snd
 
-    -- Ask one server which shares it has related to the storage index in
-    -- question.
-    discoverOnce (_sid, sann) = do
-        case storageServerAnnouncementFURL sann of
-            Nothing -> pure $ Left StorageServerLocationUnknown
-            Just url -> do
-                server <- openServer url
-                case server of
-                    Nothing -> pure $ Left StorageServerUnreachable
-                    Just ss@StorageServer{storageServerGetBuckets} ->
-                        Right . (ss,) <$> storageServerGetBuckets storageIndex
+{- | Figure the total number of distinct shares reported by all of the servers
+ we asked.
+-}
+countDistinctShares :: Ord b => [(a, Set.Set b)] -> Int
+countDistinctShares = Set.size . foldl' Set.union mempty . map snd
+
+-- Ask one server which shares it has related to the storage index in
+-- question.
+discoverOnce :: LookupServer -> StorageIndex -> (StorageServerID, StorageServerAnnouncement) -> IO (Either DiscoverError (StorageServer, Set.Set ShareNum))
+discoverOnce lookupServer storageIndex (_sid, sann) = do
+    case storageServerAnnouncementFURL sann of
+        Nothing -> pure $ Left StorageServerLocationUnknown
+        Just url -> do
+            let server = lookupServer url
+            case server of
+                Nothing -> pure $ Left StorageServerUnreachable
+                Just ss@StorageServer{storageServerGetBuckets} ->
+                    Right . (ss,) <$> storageServerGetBuckets storageIndex
 
 {- | Invert the mapping implied by the list of two tuples so that the servers
  that claim to have a certain share can easily be retrieved.
@@ -125,4 +140,9 @@ downloadShare ::
     -- download.
     IO (ShareNum, Either DownloadError LB.ByteString)
 downloadShare _ shareNum [] = pure (shareNum, Left NoServers)
-downloadShare _ shareNum _ = pure (shareNum, Right "")
+downloadShare storageIndex shareNum (s : _) = do
+    -- TODO: There might be more servers.  We could try them if this fails.
+    -- On the other hand, we might get bytes but we don't verify them here so
+    -- we might also need retry logic up a level or two from here.
+    shareBytes <- storageServerRead s storageIndex shareNum
+    pure (shareNum, Right $ LB.fromStrict shareBytes)
diff --git a/test/Spec.hs b/test/Spec.hs
index fac9ffe160b82ca222b37a3fe42a9a5bfa0840ab..0c4afeaf07810e2ac11f73864103d5079996e16d 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -10,6 +10,7 @@ import Data.ByteString.Base32 (encodeBase32Unpadded)
 import qualified Data.ByteString.Lazy as BL
 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 Data.Word (Word16)
 import Generators (genParameters)
@@ -23,7 +24,7 @@ import qualified Tahoe.CHK.Encrypt
 import Tahoe.CHK.Server (StorageServer (..), StorageServerAnnouncement (..), StorageServerID)
 import Tahoe.CHK.Types (Parameters (..))
 import Tahoe.CHK.Upload (getConvergentKey)
-import Tahoe.Download (DownloadError (..), download)
+import Tahoe.Download (DownloadError (..), LookupServer, download)
 import Tahoe.Server (memoryStorageServer)
 import Test.Tasty (TestTree, defaultMain, testGroup)
 import Test.Tasty.HUnit (assertEqual, testCase)
@@ -89,8 +90,8 @@ tests =
             -- Make the server reachable.
             let openServer furl =
                     if furl == "somewhere"
-                        then pure . pure $ server
-                        else pure Nothing
+                        then pure server
+                        else Nothing
 
             -- Try to download the cap which requires three shares to reconstruct.
             result <- liftIO $ download anns cap openServer
@@ -136,7 +137,7 @@ tests =
 
             -- Make the server reachable.
             let openServer furl =
-                    pure $ case furl of
+                    case furl of
                         "somewhere" -> pure somewhere
                         "elsewhere" -> pure elsewhere
                         _ -> Nothing
@@ -188,19 +189,27 @@ tests =
                 -- Distribute the shares.
                 liftIO $ placeShares cap (Binary.encode <$> shares) perServerShareCount servers 0
 
+                let serverMap = Map.fromList $ zip (Set.toList serverIDs') servers
+                    lookupServer = someServers serverMap
+                    serverAnnouncements = Map.fromSet makeAnn serverIDs'
+
+                -- discovered <- liftIO $ mapM (discoverOnce lookupServer . storageIndex . verifier $ cap) (Map.toList serverAnnouncements)
+                -- liftIO $ print (rights discovered)
+
                 -- Recover the plaintext from the servers.
-                result <- liftIO $ download (Map.fromSet makeAnn serverIDs') cap (someServers $ Map.fromList $ zip (encodeUtf8 <$> Set.toList serverIDs') servers)
+                result <- liftIO $ download serverAnnouncements cap lookupServer
                 diff (Right plaintext) (==) result
         ]
   where
     -- A server lookup function that always fails.
-    noServers _ = pure Nothing
+    noServers _ = Nothing
 
     -- A server lookup function that finds servers already present in a Map.
-    someServers servers = pure . flip Map.lookup servers . parseURL
+    someServers :: Map.Map StorageServerID StorageServer -> LookupServer
+    someServers servers = flip Map.lookup servers . parseURL
       where
         -- Exactly match the nonsense makeAnn spits out
-        parseURL = B.take 2 . B.drop 5 . encodeUtf8
+        parseURL = T.take 2 . T.drop 5
 
     --- PHILOSOFY
     -- We wish that share numbers were an opaque type instead of a
@@ -218,7 +227,7 @@ tests =
         -- write the right number of shares to this server
         zipWithM_
             (\shnum share -> storageServerWrite s (storageIndex . verifier $ cap) shnum 0 share)
-            [fromIntegral n ..]
+            [fromIntegral sharesSoFar ..]
             (BL.toStrict <$> take n shares)
         -- recurse to write the rest
         placeShares cap (drop n shares) ns ss (sharesSoFar + n)