aboutsummaryrefslogtreecommitdiff
path: root/src/gnunet/service/dht/routingtable.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/gnunet/service/dht/routingtable.go')
-rw-r--r--src/gnunet/service/dht/routingtable.go305
1 files changed, 200 insertions, 105 deletions
diff --git a/src/gnunet/service/dht/routingtable.go b/src/gnunet/service/dht/routingtable.go
index 2933e6a..fe9f956 100644
--- a/src/gnunet/service/dht/routingtable.go
+++ b/src/gnunet/service/dht/routingtable.go
@@ -21,10 +21,11 @@ package dht
21import ( 21import (
22 "bytes" 22 "bytes"
23 "context" 23 "context"
24 "crypto/sha512"
25 "encoding/hex" 24 "encoding/hex"
25 "gnunet/config"
26 "gnunet/crypto"
27 "gnunet/service/dht/blocks"
26 "gnunet/util" 28 "gnunet/util"
27 "math/rand"
28 "sync" 29 "sync"
29 "time" 30 "time"
30 31
@@ -32,61 +33,63 @@ import (
32 "github.com/bfix/gospel/math" 33 "github.com/bfix/gospel/math"
33) 34)
34 35
35var ( 36// Routing table constants
36 // routing table hash function: defines number of
37 // buckets and size of peer addresses
38 rtHash = sha512.New
39)
40
41// Routing table contants (adjust with changing hash function)
42const ( 37const (
43 numBuckets = 512 // number of bits of hash function result 38 numK = 20 // number of entries per k-bucket
44 numK = 20 // number of entries per k-bucket
45 sizeAddr = 64 // size of peer address in bytes
46) 39)
47 40
48//====================================================================== 41//======================================================================
42// Peer address
49//====================================================================== 43//======================================================================
50 44
51// PeerAddress is the identifier for a peer in the DHT network. 45// PeerAddress is the identifier for a peer in the DHT network.
52// It is the SHA-512 hash of the PeerID (public Ed25519 key). 46// It is the SHA-512 hash of the PeerID (public Ed25519 key).
53type PeerAddress struct { 47type PeerAddress struct {
54 addr [sizeAddr]byte // hash value as bytes 48 Peer *util.PeerID // peer identifier
55 connected bool // is peer connected? 49 Key *crypto.HashCode // address key is a sha512 hash
56 lastSeen util.AbsoluteTime // time the peer was last seen 50 lastSeen util.AbsoluteTime // time the peer was last seen
57 lastUsed util.AbsoluteTime // time the peer was last used 51 lastUsed util.AbsoluteTime // time the peer was last used
58} 52}
59 53
60// NewPeerAddress returns the DHT address of a peer. 54// NewPeerAddress returns the DHT address of a peer.
61func NewPeerAddress(peer *util.PeerID) *PeerAddress { 55func NewPeerAddress(peer *util.PeerID) *PeerAddress {
62 r := new(PeerAddress) 56 return &PeerAddress{
63 h := rtHash() 57 Peer: peer,
64 h.Write(peer.Key) 58 Key: crypto.Hash(peer.Data),
65 copy(r.addr[:], h.Sum(nil)) 59 lastSeen: util.AbsoluteTimeNow(),
66 r.lastSeen = util.AbsoluteTimeNow() 60 lastUsed: util.AbsoluteTimeNow(),
67 r.lastUsed = util.AbsoluteTimeNow() 61 }
68 return r 62}
63
64// NewQueryAddress returns a wrapped peer address for a query key
65func NewQueryAddress(key *crypto.HashCode) *PeerAddress {
66 return &PeerAddress{
67 Peer: nil,
68 Key: crypto.NewHashCode(key.Bits),
69 lastSeen: util.AbsoluteTimeNow(),
70 lastUsed: util.AbsoluteTimeNow(),
71 }
69} 72}
70 73
71// String returns a human-readble representation of an address. 74// String returns a human-readble representation of an address.
72func (addr *PeerAddress) String() string { 75func (addr *PeerAddress) String() string {
73 return hex.EncodeToString(addr.addr[:]) 76 return hex.EncodeToString(addr.Key.Bits)
74} 77}
75 78
76// Equals returns true if two peer addresses are the same. 79// Equals returns true if two peer addresses are the same.
77func (addr *PeerAddress) Equals(p *PeerAddress) bool { 80func (addr *PeerAddress) Equals(p *PeerAddress) bool {
78 return bytes.Equal(addr.addr[:], p.addr[:]) 81 return bytes.Equal(addr.Key.Bits, p.Key.Bits)
79} 82}
80 83
81// Distance between two addresses: returns a distance value and a 84// Distance between two addresses: returns a distance value and a
82// bucket index (smaller index = less distant). 85// bucket index (smaller index = less distant).
83func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) { 86func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
84 var d PeerAddress 87 d := make([]byte, 64)
85 for i := range d.addr { 88 for i := range d {
86 d.addr[i] = addr.addr[i] ^ p.addr[i] 89 d[i] = addr.Key.Bits[i] ^ p.Key.Bits[i]
87 } 90 }
88 r := math.NewIntFromBytes(d.addr[:]) 91 r := math.NewIntFromBytes(d)
89 return r, numBuckets - r.BitLen() 92 return r, 512 - r.BitLen()
90} 93}
91 94
92//====================================================================== 95//======================================================================
@@ -98,23 +101,28 @@ func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
98// distance to the reference address, so smaller index means 101// distance to the reference address, so smaller index means
99// "nearer" to the reference address. 102// "nearer" to the reference address.
100type RoutingTable struct { 103type RoutingTable struct {
101 ref *PeerAddress // reference address for distance 104 sync.RWMutex
102 buckets []*Bucket // list of buckets 105
103 list map[*PeerAddress]struct{} // keep list of peers 106 ref *PeerAddress // reference address for distance
104 mtx sync.RWMutex // lock for write operations 107 buckets []*Bucket // list of buckets
105 l2nse float64 // log2 of estimated network size 108 list *util.Map[string, *PeerAddress] // keep list of peers
106 inProcess bool // flag if Process() is running 109 l2nse float64 // log2 of estimated network size
110 inProcess bool // flag if Process() is running
111 cfg *config.RoutingConfig // routing parameters
112 helloCache *util.Map[string, *blocks.HelloBlock] // HELLO block cache
107} 113}
108 114
109// NewRoutingTable creates a new routing table for the reference address. 115// NewRoutingTable creates a new routing table for the reference address.
110func NewRoutingTable(ref *PeerAddress) *RoutingTable { 116func NewRoutingTable(ref *PeerAddress, cfg *config.RoutingConfig) *RoutingTable {
111 // create routing table 117 // create routing table
112 rt := &RoutingTable{ 118 rt := &RoutingTable{
113 ref: ref, 119 ref: ref,
114 list: make(map[*PeerAddress]struct{}), 120 list: util.NewMap[string, *PeerAddress](),
115 buckets: make([]*Bucket, numBuckets), 121 buckets: make([]*Bucket, 512),
116 l2nse: 0., 122 l2nse: -1,
117 inProcess: false, 123 inProcess: false,
124 cfg: cfg,
125 helloCache: util.NewMap[string, *blocks.HelloBlock](),
118 } 126 }
119 // fill buckets 127 // fill buckets
120 for i := range rt.buckets { 128 for i := range rt.buckets {
@@ -130,41 +138,69 @@ func NewRoutingTable(ref *PeerAddress) *RoutingTable {
130// Add new peer address to routing table. 138// Add new peer address to routing table.
131// Returns true if the entry was added, false otherwise. 139// Returns true if the entry was added, false otherwise.
132func (rt *RoutingTable) Add(p *PeerAddress) bool { 140func (rt *RoutingTable) Add(p *PeerAddress) bool {
133 // ensure one write and no readers 141 k := p.String()
134 rt.lock(false) 142 logger.Printf(logger.DBG, "[RT] Add(%s)", k)
135 defer rt.unlock(false)
136 143
137 // check if peer is already known 144 // check if peer is already known
138 if _, ok := rt.list[p]; ok { 145 if px, ok := rt.list.Get(k); ok {
146 logger.Println(logger.DBG, "[RT] --> already known")
147 px.lastSeen = util.AbsoluteTimeNow()
139 return false 148 return false
140 } 149 }
141 150
142 // compute distance (bucket index) and insert address. 151 // compute distance (bucket index) and insert address.
143 _, idx := p.Distance(rt.ref) 152 _, idx := p.Distance(rt.ref)
144 if rt.buckets[idx].Add(p) { 153 if rt.buckets[idx].Add(p) {
145 rt.list[p] = struct{}{} 154 logger.Println(logger.DBG, "[RT] --> entry added")
155 p.lastUsed = util.AbsoluteTimeNow()
156 rt.list.Put(k, p)
146 return true 157 return true
147 } 158 }
148 // Full bucket: we did not add the address to the routing table. 159 // Full bucket: we did not add the address to the routing table.
160 logger.Println(logger.DBG, "[RT] --> bucket full -- discarded")
149 return false 161 return false
150} 162}
151 163
152// Remove peer address from routing table. 164// Remove peer address from routing table.
153// Returns true if the entry was removed, false otherwise. 165// Returns true if the entry was removed, false otherwise.
154func (rt *RoutingTable) Remove(p *PeerAddress) bool { 166func (rt *RoutingTable) Remove(p *PeerAddress) bool {
155 // ensure one write and no readers 167 k := p.String()
156 rt.lock(false) 168 logger.Printf(logger.DBG, "[RT] Remove(%s)", k)
157 defer rt.unlock(false)
158 169
159 // compute distance (bucket index) and remove entry from bucket 170 // compute distance (bucket index) and remove entry from bucket
171 rc := false
160 _, idx := p.Distance(rt.ref) 172 _, idx := p.Distance(rt.ref)
161 if rt.buckets[idx].Remove(p) { 173 if rt.buckets[idx].Remove(p) {
162 delete(rt.list, p) 174 logger.Println(logger.DBG, "[RT] --> entry removed from bucket and internal lists")
163 return true 175 rc = true
176 } else {
177 // remove from internal list
178 logger.Println(logger.DBG, "[RT] --> entry removed from internal lists only")
164 } 179 }
165 // remove from internal list 180 rt.list.Delete(k)
166 delete(rt.list, p) 181 // delete from HELLO cache
167 return false 182 rt.helloCache.Delete(p.Peer.String())
183 return rc
184}
185
186// Contains checks if a peer is available in the routing table
187func (rt *RoutingTable) Contains(p *PeerAddress) bool {
188 k := p.String()
189 logger.Printf(logger.DBG, "[RT] Contains(%s)?", k)
190
191 // check for peer in internal list
192 px, ok := rt.list.Get(k)
193 if !ok {
194 logger.Println(logger.DBG, "[RT] --> NOT found in current list:")
195 _ = rt.list.ProcessRange(func(key string, val *PeerAddress) error {
196 logger.Printf(logger.DBG, "[RT] * %s", val)
197 return nil
198 }, true)
199 } else {
200 logger.Println(logger.DBG, "[RT] --> found in current list")
201 px.lastSeen = util.AbsoluteTimeNow()
202 }
203 return ok
168} 204}
169 205
170//---------------------------------------------------------------------- 206//----------------------------------------------------------------------
@@ -186,51 +222,53 @@ func (rt *RoutingTable) Process(f func() error, readonly bool) error {
186// Routing functions 222// Routing functions
187//---------------------------------------------------------------------- 223//----------------------------------------------------------------------
188 224
189// SelectClosestPeer for a given peer address and bloomfilter. 225// SelectClosestPeer for a given peer address and peer filter.
190func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n *PeerAddress) { 226func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, pf *blocks.PeerFilter) (n *PeerAddress) {
191 // no writer allowed 227 // no writer allowed
192 rt.mtx.RLock() 228 rt.RLock()
193 defer rt.mtx.RUnlock() 229 defer rt.RUnlock()
194 230
195 // find closest address 231 // find closest peer in routing table
196 var dist *math.Int 232 var dist *math.Int
197 for _, b := range rt.buckets { 233 for _, b := range rt.buckets {
198 if k, d := b.SelectClosestPeer(p, bf); n == nil || (d != nil && d.Cmp(dist) < 0) { 234 if k, d := b.SelectClosestPeer(p, pf); n == nil || (d != nil && d.Cmp(dist) < 0) {
199 dist = d 235 dist = d
200 n = k 236 n = k
201 } 237 }
202 } 238 }
203 // mark peer as used 239 // mark peer as used
204 n.lastUsed = util.AbsoluteTimeNow() 240 if n != nil {
241 n.lastUsed = util.AbsoluteTimeNow()
242 }
205 return 243 return
206} 244}
207 245
208// SelectRandomPeer returns a random address from table (that is not 246// SelectRandomPeer returns a random address from table (that is not
209// included in the bloomfilter) 247// included in the bloomfilter)
210func (rt *RoutingTable) SelectRandomPeer(bf *PeerBloomFilter) *PeerAddress { 248func (rt *RoutingTable) SelectRandomPeer(pf *blocks.PeerFilter) (p *PeerAddress) {
211 // no writer allowed 249 // no writer allowed
212 rt.mtx.RLock() 250 rt.RLock()
213 defer rt.mtx.RUnlock() 251 defer rt.RUnlock()
214 252
215 // select random entry from list 253 // select random entry from list
216 if size := len(rt.list); size > 0 { 254 var ok bool
217 idx := rand.Intn(size) 255 for {
218 for k := range rt.list { 256 if _, p, ok = rt.list.GetRandom(); !ok {
219 if idx == 0 { 257 return nil
220 // mark peer as used 258 }
221 k.lastUsed = util.AbsoluteTimeNow() 259 if !pf.Contains(p.Peer) {
222 return k 260 break
223 }
224 idx--
225 } 261 }
226 } 262 }
227 return nil 263 // mark peer as used
264 p.lastUsed = util.AbsoluteTimeNow()
265 return
228} 266}
229 267
230// SelectPeer selects a neighbor depending on the number of hops parameter. 268// SelectPeer selects a neighbor depending on the number of hops parameter.
231// If hops < NSE this function MUST return SelectRandomPeer() and 269// If hops < NSE this function MUST return SelectRandomPeer() and
232// SelectClosestpeer() otherwise. 270// SelectClosestpeer() otherwise.
233func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf *PeerBloomFilter) *PeerAddress { 271func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf *blocks.PeerFilter) *PeerAddress {
234 if float64(hops) < rt.l2nse { 272 if float64(hops) < rt.l2nse {
235 return rt.SelectRandomPeer(bf) 273 return rt.SelectRandomPeer(bf)
236 } 274 }
@@ -238,9 +276,24 @@ func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf *PeerBloomFilter
238} 276}
239 277
240// IsClosestPeer returns true if p is the closest peer for k. Peers with a 278// IsClosestPeer returns true if p is the closest peer for k. Peers with a
241// positive test in the Bloom filter are not considered. 279// positive test in the Bloom filter are not considered. If p is nil, our
242func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter) bool { 280// reference address is used.
243 n := rt.SelectClosestPeer(k, bf) 281func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, pf *blocks.PeerFilter) bool {
282 // get closest peer in routing table
283 n := rt.SelectClosestPeer(k, pf)
284 // check SELF?
285 if p == nil {
286 // if no peer in routing table found
287 if n == nil {
288 // local peer is closest
289 return true
290 }
291 // check if local distance is smaller than for best peer in routing table
292 d0, _ := n.Distance(k)
293 d1, _ := rt.ref.Distance(k)
294 return d1.Cmp(d0) < 0
295 }
296 // check if p is closest peer
244 return n.Equals(p) 297 return n.Equals(p)
245} 298}
246 299
@@ -248,7 +301,7 @@ func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter) bo
248// The arguments are the desired replication level, the hop count of the message so far, 301// The arguments are the desired replication level, the hop count of the message so far,
249// and the base-2 logarithm of the current network size estimate (L2NSE) as provided by the 302// and the base-2 logarithm of the current network size estimate (L2NSE) as provided by the
250// underlay. The result is the non-negative number of next hops to select. 303// underlay. The result is the non-negative number of next hops to select.
251func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int { 304func (rt *RoutingTable) ComputeOutDegree(repl, hop uint16) int {
252 hf := float64(hop) 305 hf := float64(hop)
253 if hf > 4*rt.l2nse { 306 if hf > 4*rt.l2nse {
254 return 0 307 return 0
@@ -271,22 +324,62 @@ func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int {
271func (rt *RoutingTable) heartbeat(ctx context.Context) { 324func (rt *RoutingTable) heartbeat(ctx context.Context) {
272 325
273 // check for dead or expired peers 326 // check for dead or expired peers
274 timeout := util.NewRelativeTime(3 * time.Hour) 327 logger.Println(logger.DBG, "[dht] RT heartbeat...")
328 timeout := util.NewRelativeTime(time.Duration(rt.cfg.PeerTTL) * time.Second)
275 if err := rt.Process(func() error { 329 if err := rt.Process(func() error {
276 for addr := range rt.list { 330 return rt.list.ProcessRange(func(k string, p *PeerAddress) error {
277 if addr.connected {
278 continue
279 }
280 // check if we can/need to drop a peer 331 // check if we can/need to drop a peer
281 drop := timeout.Compare(addr.lastSeen.Elapsed()) < 0 332 drop := timeout.Compare(p.lastSeen.Elapsed()) < 0
282 if drop || timeout.Compare(addr.lastUsed.Elapsed()) < 0 { 333 if drop || timeout.Compare(p.lastUsed.Elapsed()) < 0 {
283 rt.Remove(addr) 334 logger.Printf(logger.DBG, "[RT] removing %v: %v, %v", p, p.lastSeen.Elapsed(), p.lastUsed.Elapsed())
335 rt.Remove(p)
284 } 336 }
285 } 337 return nil
286 return nil 338 }, false)
287 }, false); err != nil { 339 }, false); err != nil {
288 logger.Println(logger.ERROR, "[dht] RT heartbeat: "+err.Error()) 340 logger.Println(logger.ERROR, "[dht] RT heartbeat failed: "+err.Error())
289 } 341 }
342
343 // drop expired entries from the HELLO cache
344 _ = rt.helloCache.ProcessRange(func(key string, val *blocks.HelloBlock) error {
345 if val.Expires.Expired() {
346 rt.helloCache.Delete(key)
347 }
348 return nil
349 }, false)
350
351 // update the estimated network size
352 // rt.l2nse = ...
353}
354
355//----------------------------------------------------------------------
356
357func (rt *RoutingTable) BestHello(addr *PeerAddress, rf blocks.ResultFilter) (hb *blocks.HelloBlock, dist *math.Int) {
358 // iterate over cached HELLOs to find (best) match first
359 _ = rt.helloCache.ProcessRange(func(key string, val *blocks.HelloBlock) error {
360 // check if block is excluded by result filter
361 if !rf.Contains(val) {
362 // check for better match
363 p := NewPeerAddress(val.PeerID)
364 d, _ := addr.Distance(p)
365 if hb == nil || d.Cmp(dist) < 0 {
366 hb = val
367 dist = d
368 }
369 }
370 return nil
371 }, true)
372 return
373}
374
375// CacheHello adds a HELLO block to the list of cached entries.
376func (rt *RoutingTable) CacheHello(hb *blocks.HelloBlock) {
377 rt.helloCache.Put(hb.PeerID.String(), hb)
378}
379
380// GetHello returns a HELLO block for key k (if available)
381func (rt *RoutingTable) GetHello(k string) (*blocks.HelloBlock, bool) {
382 return rt.helloCache.Get(k)
290} 383}
291 384
292//---------------------------------------------------------------------- 385//----------------------------------------------------------------------
@@ -295,9 +388,9 @@ func (rt *RoutingTable) heartbeat(ctx context.Context) {
295func (rt *RoutingTable) lock(readonly bool) { 388func (rt *RoutingTable) lock(readonly bool) {
296 if !rt.inProcess { 389 if !rt.inProcess {
297 if readonly { 390 if readonly {
298 rt.mtx.RLock() 391 rt.RLock()
299 } else { 392 } else {
300 rt.mtx.Lock() 393 rt.Lock()
301 } 394 }
302 } 395 }
303} 396}
@@ -306,9 +399,9 @@ func (rt *RoutingTable) lock(readonly bool) {
306func (rt *RoutingTable) unlock(readonly bool) { 399func (rt *RoutingTable) unlock(readonly bool) {
307 if !rt.inProcess { 400 if !rt.inProcess {
308 if readonly { 401 if readonly {
309 rt.mtx.RUnlock() 402 rt.RUnlock()
310 } else { 403 } else {
311 rt.mtx.Unlock() 404 rt.Unlock()
312 } 405 }
313 } 406 }
314} 407}
@@ -319,8 +412,9 @@ func (rt *RoutingTable) unlock(readonly bool) {
319 412
320// Bucket holds peer entries with approx. same distance from node 413// Bucket holds peer entries with approx. same distance from node
321type Bucket struct { 414type Bucket struct {
322 list []*PeerAddress 415 sync.RWMutex
323 rwlock sync.RWMutex 416
417 list []*PeerAddress // list of peer addresses in bucket.
324} 418}
325 419
326// NewBucket creates a new entry list of given size 420// NewBucket creates a new entry list of given size
@@ -334,8 +428,8 @@ func NewBucket(n int) *Bucket {
334// Returns true if entry is added, false otherwise. 428// Returns true if entry is added, false otherwise.
335func (b *Bucket) Add(p *PeerAddress) bool { 429func (b *Bucket) Add(p *PeerAddress) bool {
336 // only one writer and no readers 430 // only one writer and no readers
337 b.rwlock.Lock() 431 b.Lock()
338 defer b.rwlock.Unlock() 432 defer b.Unlock()
339 433
340 // check for free space in bucket 434 // check for free space in bucket
341 if len(b.list) < numK { 435 if len(b.list) < numK {
@@ -343,6 +437,7 @@ func (b *Bucket) Add(p *PeerAddress) bool {
343 b.list = append(b.list, p) 437 b.list = append(b.list, p)
344 return true 438 return true
345 } 439 }
440 // full bucket: no further additions
346 return false 441 return false
347} 442}
348 443
@@ -350,8 +445,8 @@ func (b *Bucket) Add(p *PeerAddress) bool {
350// Returns true if entry is removed (found), false otherwise. 445// Returns true if entry is removed (found), false otherwise.
351func (b *Bucket) Remove(p *PeerAddress) bool { 446func (b *Bucket) Remove(p *PeerAddress) bool {
352 // only one writer and no readers 447 // only one writer and no readers
353 b.rwlock.Lock() 448 b.Lock()
354 defer b.rwlock.Unlock() 449 defer b.Unlock()
355 450
356 for i, pe := range b.list { 451 for i, pe := range b.list {
357 if pe.Equals(p) { 452 if pe.Equals(p) {
@@ -365,14 +460,14 @@ func (b *Bucket) Remove(p *PeerAddress) bool {
365 460
366// SelectClosestPeer returns the entry with minimal distance to the given 461// SelectClosestPeer returns the entry with minimal distance to the given
367// peer address; entries included in the bloom flter are ignored. 462// peer address; entries included in the bloom flter are ignored.
368func (b *Bucket) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n *PeerAddress, dist *math.Int) { 463func (b *Bucket) SelectClosestPeer(p *PeerAddress, pf *blocks.PeerFilter) (n *PeerAddress, dist *math.Int) {
369 // no writer allowed 464 // no writer allowed
370 b.rwlock.RLock() 465 b.RLock()
371 defer b.rwlock.RUnlock() 466 defer b.RUnlock()
372 467
373 for _, addr := range b.list { 468 for _, addr := range b.list {
374 // skip addresses in bloomfilter 469 // skip addresses in bloomfilter
375 if bf.Contains(addr) { 470 if pf.Contains(addr.Peer) {
376 continue 471 continue
377 } 472 }
378 // check for shorter distance 473 // check for shorter distance