diff options
-rw-r--r-- | src/Network/BitTorrent/Tracker/RPC/UDP.hs | 47 |
1 files changed, 37 insertions, 10 deletions
diff --git a/src/Network/BitTorrent/Tracker/RPC/UDP.hs b/src/Network/BitTorrent/Tracker/RPC/UDP.hs index 1ea47100..4fea7d17 100644 --- a/src/Network/BitTorrent/Tracker/RPC/UDP.hs +++ b/src/Network/BitTorrent/Tracker/RPC/UDP.hs | |||
@@ -226,7 +226,7 @@ initialConnectionId = ConnectionId 0x41727101980 | |||
226 | 226 | ||
227 | -- | Transaction Id is used within a UDP RPC. | 227 | -- | Transaction Id is used within a UDP RPC. |
228 | newtype TransactionId = TransactionId Word32 | 228 | newtype TransactionId = TransactionId Word32 |
229 | deriving (Eq, Ord, Serialize) | 229 | deriving (Eq, Ord, Enum, Bounded, Serialize) |
230 | 230 | ||
231 | instance Show TransactionId where | 231 | instance Show TransactionId where |
232 | showsPrec _ (TransactionId tid) = showString "0x" <> showHex tid | 232 | showsPrec _ (TransactionId tid) = showString "0x" <> showHex tid |
@@ -377,6 +377,35 @@ isExpired Connection {..} = do | |||
377 | -- Transactions | 377 | -- Transactions |
378 | -----------------------------------------------------------------------} | 378 | -----------------------------------------------------------------------} |
379 | 379 | ||
380 | -- | Sometimes 'genTransactionId' may return already used transaction | ||
381 | -- id. We use a good entropy source but the issue /still/ (with very | ||
382 | -- small probabality) may happen. If the collision happen then this | ||
383 | -- function tries to find nearest unused slot, otherwise pending | ||
384 | -- transactions table is full. | ||
385 | firstUnused :: SockAddr -> TransactionId -> PendingQueries -> TransactionId | ||
386 | firstUnused addr rid m = do | ||
387 | case M.splitLookup rid <$> M.lookup addr m of | ||
388 | Nothing -> rid | ||
389 | Just (_ , Nothing, _ ) -> rid | ||
390 | Just (lt, Just _ , gt) -> | ||
391 | case backwardHole (keys lt) rid <|> forwardHole rid (keys gt) of | ||
392 | Nothing -> error "firstUnused: table is full" -- impossible | ||
393 | Just tid -> tid | ||
394 | where | ||
395 | forwardHole a [] | ||
396 | | a == maxBound = Nothing | ||
397 | | otherwise = Just (succ a) | ||
398 | forwardHole a (b : xs) | ||
399 | | succ a == b = forwardHole b xs | ||
400 | | otherwise = Just (succ a) | ||
401 | |||
402 | backwardHole [] a | ||
403 | | a == minBound = Nothing | ||
404 | | otherwise = Just (pred a) | ||
405 | backwardHole (b : xs) a | ||
406 | | b == pred a = backwardHole xs b | ||
407 | | otherwise = Just (pred a) | ||
408 | |||
380 | register :: SockAddr -> TransactionId -> PendingResponse | 409 | register :: SockAddr -> TransactionId -> PendingResponse |
381 | -> PendingQueries -> PendingQueries | 410 | -> PendingQueries -> PendingQueries |
382 | register addr tid ares = M.alter insertId addr | 411 | register addr tid ares = M.alter insertId addr |
@@ -394,15 +423,13 @@ unregister addr tid = M.update deleteId addr | |||
394 | where | 423 | where |
395 | m' = M.delete tid m | 424 | m' = M.delete tid m |
396 | 425 | ||
397 | -- | Generate a new unused transaction id. | 426 | -- | Generate a new unused transaction id and register as pending. |
398 | allocTransaction :: Manager -> SockAddr -> MVar Response -> IO TransactionId | 427 | allocTransaction :: Manager -> SockAddr -> PendingResponse -> IO TransactionId |
399 | allocTransaction Manager {..} addr ares = modifyMVar pendingResps bindId | 428 | allocTransaction Manager {..} addr ares = |
400 | where | 429 | modifyMVar pendingResps $ \ m -> do |
401 | bindId m = do | 430 | rndId <- genTransactionId |
402 | tid <- genTransactionId | 431 | let tid = firstUnused addr rndId m |
403 | case M.lookup tid =<< M.lookup addr m of | 432 | return (register addr tid ares m, tid) |
404 | Just _ -> bindId m -- already used, retry | ||
405 | Nothing -> return (register addr tid ares m, tid) | ||
406 | 433 | ||
407 | -- | Wake up blocked thread and return response back. | 434 | -- | Wake up blocked thread and return response back. |
408 | commitTransaction :: Manager -> SockAddr -> TransactionId -> Response -> IO () | 435 | commitTransaction :: Manager -> SockAddr -> TransactionId -> Response -> IO () |