summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Kademlia.hs8
-rw-r--r--src/Network/DHT/Routing.hs177
2 files changed, 97 insertions, 88 deletions
diff --git a/Kademlia.hs b/Kademlia.hs
index 63c2b494..4a811fa2 100644
--- a/Kademlia.hs
+++ b/Kademlia.hs
@@ -114,10 +114,10 @@ contramapIR f ir = InsertionReporter
114-- | All the IO operations neccessary to maintain a Kademlia routing table. 114-- | All the IO operations neccessary to maintain a Kademlia routing table.
115data TableStateIO nid ni = TableStateIO 115data TableStateIO nid ni = TableStateIO
116 { -- | Write the routing table. Typically 'writeTVar'. 116 { -- | Write the routing table. Typically 'writeTVar'.
117 tblWrite :: R.Table ni -> STM () 117 tblWrite :: R.BucketList ni -> STM ()
118 118
119 -- | Read the routing table. Typically 'readTVar'. 119 -- | Read the routing table. Typically 'readTVar'.
120 , tblRead :: STM (R.Table ni) 120 , tblRead :: STM (R.BucketList ni)
121 121
122 -- | Issue a ping to a remote node and report 'True' if the node 122 -- | Issue a ping to a remote node and report 'True' if the node
123 -- responded within an acceptable time and 'False' otherwise. 123 -- responded within an acceptable time and 'False' otherwise.
@@ -136,7 +136,7 @@ data TableStateIO nid ni = TableStateIO
136 , tblChanged :: RoutingTableChanged ni -> STM (IO ()) 136 , tblChanged :: RoutingTableChanged ni -> STM (IO ())
137 } 137 }
138 138
139vanillaIO :: TVar (Table ni) -> (ni -> IO Bool) -> TableStateIO nid ni 139vanillaIO :: TVar (BucketList ni) -> (ni -> IO Bool) -> TableStateIO nid ni
140vanillaIO var ping = TableStateIO 140vanillaIO var ping = TableStateIO
141 { tblRead = readTVar var 141 { tblRead = readTVar var
142 , tblWrite = writeTVar var 142 , tblWrite = writeTVar var
@@ -152,7 +152,7 @@ data Kademlia nid ni = Kademlia (InsertionReporter ni)
152 152
153{- 153{-
154kademlia :: FiniteBits nid => 154kademlia :: FiniteBits nid =>
155 TVar (Table nid nid) -> (nid -> IO Bool) -> Kademlia nid nid 155 TVar (BucketList nid nid) -> (nid -> IO Bool) -> Kademlia nid nid
156kademlia var ping = Kademlia quietInsertions 156kademlia var ping = Kademlia quietInsertions
157 (KademliaSpace id testIdBit) 157 (KademliaSpace id testIdBit)
158 (vanillaIO var ping) 158 (vanillaIO var ping)
diff --git a/src/Network/DHT/Routing.hs b/src/Network/DHT/Routing.hs
index 0004ac09..1821ca1c 100644
--- a/src/Network/DHT/Routing.hs
+++ b/src/Network/DHT/Routing.hs
@@ -25,8 +25,8 @@
25{-# OPTIONS_GHC -fno-warn-orphans #-} 25{-# OPTIONS_GHC -fno-warn-orphans #-}
26module Network.DHT.Routing 26module Network.DHT.Routing
27 {- 27 {-
28 ( -- * Table 28 ( -- * BucketList
29 Table 29 BucketList
30 , Info(..) 30 , Info(..)
31 31
32 -- * Attributes 32 -- * Attributes
@@ -86,8 +86,10 @@ import Text.PrettyPrint.HughesPJClass (pPrint,Pretty)
86import qualified Data.ByteString as BS 86import qualified Data.ByteString as BS
87import Data.Bits 87import Data.Bits
88import Data.Ord 88import Data.Ord
89 89import Data.Reflection
90import Network.Address 90import Network.Address
91import Data.Typeable
92import Data.Coerce
91 93
92-- | Last time the node was responding to our queries. 94-- | Last time the node was responding to our queries.
93-- 95--
@@ -167,6 +169,20 @@ bucketQ :: QueueMethods Identity ni (BucketQueue ni)
167bucketQ = seqQ 169bucketQ = seqQ
168 170
169 171
172newtype Compare a = Compare (a -> a -> Ordering)
173
174newtype Ordered s a = Ordered a
175
176-- | Hack to avoid UndecidableInstances
177newtype Shrink a = Shrink a
178
179instance Reifies s (Compare a) => Eq (Ordered s (Shrink a)) where
180 a == b = (compare a b == EQ)
181
182instance Reifies s (Compare a) => Ord (Ordered s (Shrink a)) where
183 compare a b = cmp (coerce a) (coerce b)
184 where Compare cmp = reflect (Proxy :: Proxy s)
185
170-- | Bucket is also limited in its length — thus it's called k-bucket. 186-- | Bucket is also limited in its length — thus it's called k-bucket.
171-- When bucket becomes full, we should split it in two lists by 187-- When bucket becomes full, we should split it in two lists by
172-- current span bit. Span bit is defined by depth in the routing 188-- current span bit. Span bit is defined by depth in the routing
@@ -332,14 +348,14 @@ split testNodeIdBit i b = (Bucket ns qs, Bucket ms rs)
332 348
333 349
334{----------------------------------------------------------------------- 350{-----------------------------------------------------------------------
335-- Table 351-- BucketList
336-----------------------------------------------------------------------} 352-----------------------------------------------------------------------}
337 353
338defaultBucketCount :: Int 354defaultBucketCount :: Int
339defaultBucketCount = 20 355defaultBucketCount = 20
340 356
341data Info ni nid = Info 357data Info ni nid = Info
342 { myBuckets :: Table ni 358 { myBuckets :: BucketList ni
343 , myNodeId :: nid 359 , myNodeId :: nid
344 , myAddress :: SockAddr 360 , myAddress :: SockAddr
345 } 361 }
@@ -366,22 +382,13 @@ deriving instance (Show ni, Show nid) => Show (Info ni nid)
366-- is always split into two new buckets covering the ranges @0..2 ^ 382-- is always split into two new buckets covering the ranges @0..2 ^
367-- 159@ and @2 ^ 159..2 ^ 160@. 383-- 159@ and @2 ^ 159..2 ^ 160@.
368-- 384--
369data Table ni 385data BucketList ni = BucketList !ni !Int ![Bucket ni]
370 -- most nearest bucket
371 = Tip ni Int (Bucket ni)
372
373 -- left biased tree branch
374 | Zero (Table ni) (Bucket ni)
375
376 -- right biased tree branch
377 | One (Bucket ni) (Table ni)
378 deriving Generic 386 deriving Generic
379 387
380mapTable f (One b t) = One (mapBucket f b) (mapTable f t) 388mapTable :: Ord ni => (a -> ni) -> BucketList a -> BucketList ni
381mapTable f (Zero t b) = Zero (mapTable f t) (mapBucket f b) 389mapTable f (BucketList self n bs) = BucketList (f self) n (map (mapBucket f) bs)
382mapTable f (Tip ni n b) = Tip (f ni) n (mapBucket f b)
383 390
384instance (Eq ni) => Eq (Table ni) where 391instance (Eq ni) => Eq (BucketList ni) where
385 (==) = (==) `on` Network.DHT.Routing.toList 392 (==) = (==) `on` Network.DHT.Routing.toList
386 393
387#if 0 394#if 0
@@ -392,19 +399,19 @@ instance Serialize NominalDiffTime where
392 399
393#endif 400#endif
394 401
395deriving instance (Show ni) => Show (Table ni) 402deriving instance (Show ni) => Show (BucketList ni)
396 403
397#if 0 404#if 0
398 405
399-- | Normally, routing table should be saved between invocations of 406-- | Normally, routing table should be saved between invocations of
400-- the client software. Note that you don't need to store /this/ 407-- the client software. Note that you don't need to store /this/
401-- 'NodeId' since it is already included in routing table. 408-- 'NodeId' since it is already included in routing table.
402instance (Eq ip, Serialize ip, Ord (NodeId), Serialize (NodeId), Serialize u) => Serialize (Table) 409instance (Eq ip, Serialize ip, Ord (NodeId), Serialize (NodeId), Serialize u) => Serialize (BucketList)
403 410
404#endif 411#endif
405 412
406-- | Shape of the table. 413-- | Shape of the table.
407instance Pretty (Table ni) where 414instance Pretty (BucketList ni) where
408 pPrint t 415 pPrint t
409 | bucketCount < 6 = hcat $ punctuate ", " $ L.map PP.int ss 416 | bucketCount < 6 = hcat $ punctuate ", " $ L.map PP.int ss
410 | otherwise = brackets $ 417 | otherwise = brackets $
@@ -415,8 +422,11 @@ instance Pretty (Table ni) where
415 ss = shape t 422 ss = shape t
416 423
417-- | Empty table with specified /spine/ node id. 424-- | Empty table with specified /spine/ node id.
418nullTable :: ni -> Int -> Table ni 425nullTable :: ni -> Int -> BucketList ni
419nullTable ni n = Tip ni (bucketCount (pred n)) (Bucket PSQ.empty (runIdentity $ emptyQueue bucketQ)) 426nullTable ni n = BucketList
427 ni
428 (bucketCount (pred n))
429 [Bucket PSQ.empty (runIdentity $ emptyQueue bucketQ)]
420 where 430 where
421 bucketCount x = max 0 (min 159 x) 431 bucketCount x = max 0 (min 159 x)
422 432
@@ -424,19 +434,19 @@ nullTable ni n = Tip ni (bucketCount (pred n)) (Bucket PSQ.empty (runIdentity $
424 434
425-- | Test if table is empty. In this case DHT should start 435-- | Test if table is empty. In this case DHT should start
426-- bootstrapping process until table becomes 'full'. 436-- bootstrapping process until table becomes 'full'.
427null :: Table -> Bool 437null :: BucketList -> Bool
428null (Tip _ _ b) = PSQ.null $ bktNodes b 438null (Tip _ _ b) = PSQ.null $ bktNodes b
429null _ = False 439null _ = False
430 440
431-- | Test if table have maximum number of nodes. No more nodes can be 441-- | Test if table have maximum number of nodes. No more nodes can be
432-- 'insert'ed, except old ones becomes bad. 442-- 'insert'ed, except old ones becomes bad.
433full :: Table -> Bool 443full :: BucketList -> Bool
434full (Tip _ n _) = n == 0 444full (Tip _ n _) = n == 0
435full (Zero t b) = PSQ.size (bktNodes b) == defaultBucketSize && full t 445full (Zero t b) = PSQ.size (bktNodes b) == defaultBucketSize && full t
436full (One b t) = PSQ.size (bktNodes b) == defaultBucketSize && full t 446full (One b t) = PSQ.size (bktNodes b) == defaultBucketSize && full t
437 447
438-- | Get the /spine/ node id. 448-- | Get the /spine/ node id.
439thisId :: Table -> NodeId 449thisId :: BucketList -> NodeId
440thisId (Tip nid _ _) = nid 450thisId (Tip nid _ _) = nid
441thisId (Zero table _) = thisId table 451thisId (Zero table _) = thisId table
442thisId (One _ table) = thisId table 452thisId (One _ table) = thisId table
@@ -448,58 +458,57 @@ type NodeCount = Int
448 458
449-- | Internally, routing table is similar to list of buckets or a 459-- | Internally, routing table is similar to list of buckets or a
450-- /matrix/ of nodes. This function returns the shape of the matrix. 460-- /matrix/ of nodes. This function returns the shape of the matrix.
451shape :: Table ni -> [Int] 461shape :: BucketList ni -> [Int]
452shape = map (PSQ.size . bktNodes) . toBucketList 462shape = map (PSQ.size . bktNodes) . buckets
453 463
454#if 0 464#if 0
455 465
456-- | Get number of nodes in the table. 466-- | Get number of nodes in the table.
457size :: Table -> NodeCount 467size :: BucketList -> NodeCount
458size = L.sum . shape 468size = L.sum . shape
459 469
460-- | Get number of buckets in the table. 470-- | Get number of buckets in the table.
461depth :: Table -> BucketCount 471depth :: BucketList -> BucketCount
462depth = L.length . shape 472depth = L.length . shape
463 473
464#endif 474#endif
465 475
466lookupBucket :: ( FiniteBits nid 476lookupBucket :: ( FiniteBits nid
467 , Ord nid 477 , Ord nid
468 ) => nid -> Table ni -> [Bucket ni] 478 ) => (ni -> nid) -> nid -> BucketList ni -> [Bucket ni]
469lookupBucket nid = go 0 [] 479lookupBucket nodeId nid (BucketList self _ bkts) = go 0 [] bkts
470 where 480 where
471 go i bs (Zero table bucket) 481 d = complement (nid `xor` nodeId self)
472 | testIdBit nid i = bucket : toBucketList table ++ bs 482
473 | otherwise = go (succ i) (bucket:bs) table 483 go i bs (bucket : buckets)
474 go i bs (One bucket table) 484 | testIdBit d i = bucket : buckets ++ bs
475 | testIdBit nid i = go (succ i) (bucket:bs) table 485 | otherwise = go (succ i) (bucket:bs) buckets
476 | otherwise = bucket : toBucketList table ++ bs 486 go _ bs [] = bs
477 go _ bs (Tip _ _ bucket) = bucket : bs
478 487
479 488
480compatibleNodeId :: forall ni nid. 489compatibleNodeId :: forall ni nid.
481 ( Serialize nid, FiniteBits nid) => 490 ( Serialize nid, FiniteBits nid) =>
482 Table ni -> IO nid 491 (ni -> nid) -> BucketList ni -> IO nid
483compatibleNodeId tbl = genBucketSample prefix br 492compatibleNodeId nodeId tbl = genBucketSample prefix br
484 where 493 where
485 br = bucketRange (L.length (shape tbl) - 1) True 494 br = bucketRange (L.length (shape tbl) - 1) True
486 nodeIdSize = finiteBitSize (undefined :: nid) `div` 8 495 nodeIdSize = finiteBitSize (undefined :: nid) `div` 8
487 bs = BS.pack $ take nodeIdSize $ tablePrefix tbl ++ repeat 0 496 bs = BS.pack $ take nodeIdSize $ tablePrefix (testIdBit . nodeId) tbl ++ repeat 0
488 prefix = either error id $ S.decode bs 497 prefix = either error id $ S.decode bs
489 498
490tablePrefix :: Table ni -> [Word8] 499tablePrefix :: (ni -> Word -> Bool) -> BucketList ni -> [Word8]
491tablePrefix = map (packByte . take 8 . (++repeat False)) 500tablePrefix testbit = map (packByte . take 8 . (++repeat False))
492 . chunksOf 8 501 . chunksOf 8
493 . tableBits 502 . tableBits testbit
494 where 503 where
495 packByte = foldl1' (.|.) . zipWith bitmask [7,6 .. 0] 504 packByte = foldl1' (.|.) . zipWith bitmask [7,6 .. 0]
496 bitmask ix True = bit ix 505 bitmask ix True = bit ix
497 bitmask _ _ = 0 506 bitmask _ _ = 0
498 507
499tableBits :: Table ni -> [Bool] 508tableBits :: (ni -> Word -> Bool) -> BucketList ni -> [Bool]
500tableBits (One _ tbl) = True : tableBits tbl 509tableBits testbit (BucketList self _ bkts) =
501tableBits (Zero tbl _) = False : tableBits tbl 510 zipWith const (map (testbit self) [0..])
502tableBits (Tip _ _ _) = [] 511 bkts
503 512
504chunksOf :: Int -> [e] -> [[e]] 513chunksOf :: Int -> [e] -> [[e]]
505chunksOf i ls = map (take i) (build (splitter ls)) where 514chunksOf i ls = map (take i) (build (splitter ls)) where
@@ -548,14 +557,14 @@ rank f nid = L.sortBy (comparing (distance nid . f))
548-- 'find_node' and 'get_peers' queries. 557-- 'find_node' and 'get_peers' queries.
549kclosest :: ( FiniteBits nid 558kclosest :: ( FiniteBits nid
550 , Ord nid 559 , Ord nid
551 ) => (ni -> nid) -> Int -> nid -> Table ni -> [ni] 560 ) => (ni -> nid) -> Int -> nid -> BucketList ni -> [ni]
552kclosest nodeId k nid tbl = take k $ rank nodeId nid (L.concat bucket) 561kclosest nodeId k nid tbl = take k $ rank nodeId nid (L.concat bucket)
553 ++ rank nodeId nid (L.concat everyone) 562 ++ rank nodeId nid (L.concat everyone)
554 where 563 where
555 (bucket,everyone) = 564 (bucket,everyone) =
556 L.splitAt 1 565 L.splitAt 1
557 . L.map (L.map PSQ.key . PSQ.toList . bktNodes) 566 . L.map (L.map PSQ.key . PSQ.toList . bktNodes)
558 . lookupBucket nid 567 . lookupBucket nodeId nid
559 $ tbl 568 $ tbl
560 569
561 570
@@ -567,10 +576,10 @@ kclosest nodeId k nid tbl = take k $ rank nodeId nid (L.concat bucket)
567splitTip :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) => 576splitTip :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) =>
568 Ord ni => 577 Ord ni =>
569 (ni -> Word -> Bool) 578 (ni -> Word -> Bool)
570 -> ni -> Int -> BitIx -> Bucket ni -> Table ni 579 -> ni -> Int -> BitIx -> Bucket ni -> [ Bucket ni ]
571splitTip testNodeBit ni n i bucket 580splitTip testNodeBit ni n i bucket
572 | testNodeBit ni i = (One zeros (Tip ni (pred n) ones)) 581 | testNodeBit ni i = [zeros , ones ]
573 | otherwise = (Zero (Tip ni (pred n) zeros) ones) 582 | otherwise = [ones , zeros ]
574 where 583 where
575 (ones, zeros) = split testNodeBit i bucket 584 (ones, zeros) = split testNodeBit i bucket
576 585
@@ -581,23 +590,25 @@ splitTip testNodeBit ni n i bucket
581-- paper. The rule requiring additional splits is in section 2.4. 590-- paper. The rule requiring additional splits is in section 2.4.
582modifyBucket 591modifyBucket
583 :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) => 592 :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) =>
584 forall ni nid xs. Ord ni => 593 forall ni nid xs. (Ord ni, Bits nid) =>
585 (nid -> Word -> Bool) 594 (nid -> Word -> Bool)
586 -> (ni -> Word -> Bool) 595 -> (ni -> nid)
587 -> nid -> (Bucket ni -> Maybe (xs, Bucket ni)) -> Table ni -> Maybe (xs,Table ni) 596 -> nid -> (Bucket ni -> Maybe (xs, Bucket ni)) -> BucketList ni -> Maybe (xs,BucketList ni)
588modifyBucket testIdBit testNodeBit nodeId f = go (0 :: BitIx) 597modifyBucket testIdBit nodeId nid f (BucketList self n bkts)
598 = second (BucketList self n) <$> go (0 :: BitIx) bkts
589 where 599 where
590 go :: BitIx -> Table ni -> Maybe (xs, Table ni) 600 d = nid `xor` nodeId self
591 go !i (Zero table bucket) 601
592 | testIdBit nodeId i = second (Zero table) <$> f bucket 602 go :: BitIx -> [Bucket ni] -> Maybe (xs, [Bucket ni])
593 | otherwise = second (`Zero` bucket) <$> go (succ i) table 603
594 go !i (One bucket table ) 604 go !i (bucket : buckets@(_:_))
595 | testIdBit nodeId i = second (One bucket) <$> go (succ i) table 605 | testIdBit d i = second (bucket :) <$> go (succ i) buckets
596 | otherwise = second (`One` table) <$> f bucket 606 | otherwise = second (: buckets) <$> f bucket
597 go !i (Tip nid n bucket) 607
598 | n == 0 = second (Tip nid n) <$> f bucket 608 go !i [bucket]
599 | otherwise = second (Tip nid n) <$> f bucket 609 | (n == 0) = second (: []) <$> f bucket
600 <|> go i (splitTip testNodeBit nid n i bucket) 610 | otherwise = second (: []) <$> f bucket
611 <|> go i (splitTip (testIdBit . nodeId) self n i bucket)
601 612
602 613
603-- | Triggering event for atomic table update 614-- | Triggering event for atomic table update
@@ -642,15 +653,15 @@ deriving instance ( Show ip
642-- may be added to a replacement queue and will be inserted if 653-- may be added to a replacement queue and will be inserted if
643-- one of the items in this list time out. 654-- one of the items in this list time out.
644-- 655--
645-- [ /tbl'/ ] The updated routing 'Table'. 656-- [ /tbl'/ ] The updated routing 'BucketList'.
646-- 657--
647updateForInbound :: Ord ni => 658updateForInbound :: (Ord ni, Bits nid) =>
648 KademliaSpace nid ni 659 KademliaSpace nid ni
649 -> Timestamp -> ni -> Table ni -> (Bool, [ni], Table ni) 660 -> Timestamp -> ni -> BucketList ni -> (Bool, [ni], BucketList ni)
650updateForInbound space tm ni tbl = 661updateForInbound space tm ni tbl =
651 maybe (False, [],tbl) (\(ps,tbl') -> (True, ps, tbl')) 662 maybe (False, [],tbl) (\(ps,tbl') -> (True, ps, tbl'))
652 $ modifyBucket (kademliaTestBit space) 663 $ modifyBucket (kademliaTestBit space)
653 (\ni -> kademliaTestBit space $ kademliaLocation space ni) 664 (kademliaLocation space)
654 (kademliaLocation space ni) 665 (kademliaLocation space ni)
655 (updateBucketForInbound tm ni) 666 (updateBucketForInbound tm ni)
656 tbl 667 tbl
@@ -659,16 +670,16 @@ updateForInbound space tm ni tbl =
659-- 670--
660-- Each (a,(tm,b)) in the returned list indicates that the node /a/ was deleted from the 671-- Each (a,(tm,b)) in the returned list indicates that the node /a/ was deleted from the
661-- routing table and the node /b/, with timestamp /tm/, has taken its place. 672-- routing table and the node /b/, with timestamp /tm/, has taken its place.
662updateForPingResult :: Ord ni => 673updateForPingResult :: (Ord ni, Bits nid) =>
663 KademliaSpace nid ni 674 KademliaSpace nid ni
664 -> ni -- ^ The pinged node. 675 -> ni -- ^ The pinged node.
665 -> Bool -- ^ True if we got a reply, False if it timed out. 676 -> Bool -- ^ True if we got a reply, False if it timed out.
666 -> Table ni -- ^ The routing table. 677 -> BucketList ni -- ^ The routing table.
667 -> ( [(ni,(Timestamp, ni))], Table ni ) 678 -> ( [(ni,(Timestamp, ni))], BucketList ni )
668updateForPingResult space ni got_reply tbl = 679updateForPingResult space ni got_reply tbl =
669 fromMaybe ([],tbl) 680 fromMaybe ([],tbl)
670 $ modifyBucket (kademliaTestBit space) 681 $ modifyBucket (kademliaTestBit space)
671 (\ni -> kademliaTestBit space $ kademliaLocation space ni) 682 (kademliaLocation space)
672 (kademliaLocation space ni) 683 (kademliaLocation space ni)
673 (updateBucketForPingResult ni got_reply) 684 (updateBucketForPingResult ni got_reply)
674 tbl 685 tbl
@@ -684,13 +695,11 @@ tableEntry :: NodeEntry ni -> TableEntry ni
684tableEntry (a :-> b) = (a, b) 695tableEntry (a :-> b) = (a, b)
685 696
686-- | Non-empty list of buckets. 697-- | Non-empty list of buckets.
687toBucketList :: Table ni -> [Bucket ni] 698buckets :: BucketList ni -> [Bucket ni]
688toBucketList (Tip _ _ b) = [b] 699buckets (BucketList _ _ bs) = bs
689toBucketList (Zero t b) = b : toBucketList t
690toBucketList (One b t) = b : toBucketList t
691 700
692toList :: Table ni -> [[TableEntry ni]] 701toList :: BucketList ni -> [[TableEntry ni]]
693toList = L.map (L.map tableEntry . PSQ.toList . bktNodes) . toBucketList 702toList = L.map (L.map tableEntry . PSQ.toList . bktNodes) . buckets
694 703
695data KademliaSpace nid ni = KademliaSpace 704data KademliaSpace nid ni = KademliaSpace
696 { -- | Given a node record (probably including IP address), yields a 705 { -- | Given a node record (probably including IP address), yields a