Skip to main content

dig_dht/
service.rs

1//! [`DhtService`] — the public handle that ties the routing table, provider store, transport, and
2//! iterative lookup into the four operations a DIG Node needs:
3//!
4//! - [`bootstrap`](DhtService::bootstrap) — seed the routing table from known peers (the dig-gossip
5//!   pool / relay introducer) + populate it with a self-lookup.
6//! - [`find_providers`](DhtService::find_providers) — "who holds this content?" → the provider
7//!   records (the node then fetches over the L7 peer RPC).
8//! - [`announce_provider`](DhtService::announce_provider) — "I hold this content" → PUT a provider
9//!   record at the `k` nodes closest to the content key (and locally), and remember to republish it.
10//! - [`find_node`](DhtService::find_node) — the `k` peers closest to a `peer_id` (routing primitive).
11//!
12//! Plus maintenance ([`republish`](DhtService::republish), [`refresh_buckets`](DhtService::refresh_buckets),
13//! [`gc`](DhtService::gc)) and the **serving side** ([`handle_request`](DhtService::handle_request))
14//! that answers inbound DHT RPCs from other nodes.
15//!
16//! ## Serving vs. querying
17//!
18//! A node is both a client and a server of the DHT. [`handle_request`](DhtService::handle_request)
19//! is the server: given an inbound [`DhtRequest`], it reads/writes the local routing table +
20//! provider store and returns the [`DhtResponse`]. The `find_*` / `announce_*` methods are the
21//! client: they run iterative lookups over the [`DhtTransport`]. A dig-node wires `handle_request`
22//! to inbound DHT streams and gives the service a transport that dials outbound.
23
24use std::sync::Arc;
25use std::time::{SystemTime, UNIX_EPOCH};
26
27use tokio::sync::Mutex;
28
29use dig_nat::PeerId;
30
31use crate::config::DhtConfig;
32use crate::content::ContentId;
33use crate::error::DhtError;
34use crate::key::Key;
35use crate::lookup::{iterative_find, QueryOutcome};
36use crate::provider_store::{ProviderStore, PutOutcome};
37use crate::record::{CandidateAddr, ProviderRecord};
38use crate::routing::{Contact, InsertOutcome, RoutingTable};
39use crate::transport::DhtTransport;
40use crate::wire::{DhtRequest, DhtResponse};
41
42/// A peer to bootstrap the routing table from — its `peer_id` and at least one candidate address.
43/// These come from the node's existing discovery (the dig-gossip peer pool / the relay introducer);
44/// the DHT crate takes them as input and never hard-depends on a live relay itself.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct BootstrapPeer {
47    /// The bootstrap peer's identity.
48    pub peer_id: PeerId,
49    /// Candidate addresses to reach it.
50    pub addresses: Vec<CandidateAddr>,
51}
52
53impl BootstrapPeer {
54    /// A bootstrap peer with a single direct address.
55    pub fn direct(peer_id: PeerId, host: impl Into<String>, port: u16) -> Self {
56        BootstrapPeer {
57            peer_id,
58            addresses: vec![CandidateAddr::direct(host, port)],
59        }
60    }
61
62    fn to_contact(&self) -> Contact {
63        Contact::new(&self.peer_id, self.addresses.clone())
64    }
65}
66
67/// The DHT service for one node. Cloneable-by-`Arc` internally; wrap in `Arc` to share between the
68/// serving task (inbound RPC) and querying callers.
69pub struct DhtService {
70    local_id: PeerId,
71    /// This node's own candidate addresses — put into provider records it announces so finders can
72    /// reach it.
73    local_addresses: Vec<CandidateAddr>,
74    config: DhtConfig,
75    routing: Arc<Mutex<RoutingTable>>,
76    providers: Arc<Mutex<ProviderStore>>,
77    transport: Arc<dyn DhtTransport>,
78}
79
80impl DhtService {
81    /// Create a service for the node identified by `local_id`, advertising `local_addresses` in the
82    /// provider records it announces, driving RPC over `transport`.
83    pub fn new(
84        local_id: PeerId,
85        local_addresses: Vec<CandidateAddr>,
86        config: DhtConfig,
87        transport: Arc<dyn DhtTransport>,
88    ) -> Self {
89        let routing = RoutingTable::new(&local_id, config.k);
90        let providers = ProviderStore::with_limits(config.provider_store_limits);
91        DhtService {
92            local_id,
93            local_addresses,
94            config,
95            routing: Arc::new(Mutex::new(routing)),
96            providers: Arc::new(Mutex::new(providers)),
97            transport,
98        }
99    }
100
101    /// This node's id.
102    pub fn local_id(&self) -> &PeerId {
103        &self.local_id
104    }
105
106    /// This node's own [`Contact`] (its id + advertised addresses) — the authenticated caller
107    /// identity supplied to the transport as the RPC `from`.
108    fn local_contact(&self) -> Contact {
109        Contact::new(&self.local_id, self.local_addresses.clone())
110    }
111
112    // ---- Bootstrap ---------------------------------------------------------------------------
113
114    /// Seed the routing table from `peers` and populate it by looking up this node's own id (the
115    /// canonical Kademlia bootstrap: a self-lookup fills the buckets around us). Returns the number
116    /// of distinct peers now known.
117    ///
118    /// Safe to call repeatedly (on reconnect / when new bootstrap peers arrive) — it merges, never
119    /// resets.
120    pub async fn bootstrap(&self, peers: &[BootstrapPeer]) -> Result<usize, DhtError> {
121        {
122            let mut rt = self.routing.lock().await;
123            for p in peers {
124                let _ = rt.insert(p.to_contact());
125            }
126        }
127        // Self-lookup: find the nodes closest to us to fill our buckets.
128        let self_key = Key::from_peer_id(&self.local_id);
129        let seeds: Vec<Contact> = peers.iter().map(|p| p.to_contact()).collect();
130        let result = self.run_lookup(self_key, seeds, false).await;
131        self.absorb_contacts(&result.closest).await;
132        Ok(self.routing.lock().await.len())
133    }
134
135    // ---- Client operations -------------------------------------------------------------------
136
137    /// Find the `k` peers closest to `peer_id` (the routing primitive). Runs an iterative
138    /// `find_node` lookup and returns the converged closest contacts.
139    pub async fn find_node(&self, peer_id: &PeerId) -> Result<Vec<Contact>, DhtError> {
140        let target = Key::from_peer_id(peer_id);
141        let seeds = self.seed_contacts(&target).await;
142        if seeds.is_empty() {
143            return Err(DhtError::NoPeers);
144        }
145        let result = self.run_lookup(target, seeds, false).await;
146        self.absorb_contacts(&result.closest).await;
147        Ok(result.closest)
148    }
149
150    /// Find the providers of `content` — the peers holding it. Runs an iterative `find_providers`
151    /// lookup toward the content key, returning every live provider record collected (deduped by
152    /// provider). The node then connects to those providers over dig-nat and fetches via the L7 peer
153    /// RPC.
154    ///
155    /// Returns an empty vec (not an error) when the content simply has no known providers; returns
156    /// [`DhtError::NoPeers`] only when there is no one to ask (empty routing table + no bootstrap).
157    pub async fn find_providers(
158        &self,
159        content: &ContentId,
160    ) -> Result<Vec<ProviderRecord>, DhtError> {
161        let target = content.to_key();
162
163        // Local short-circuit: if we already hold providers for this key, include them.
164        let now = now_secs();
165        let mut local = self.providers.lock().await.get(&target.to_hex(), now);
166
167        let seeds = self.seed_contacts(&target).await;
168        if seeds.is_empty() {
169            // No peers to ask — return whatever we hold locally (possibly empty).
170            return Ok(local);
171        }
172        let result = self.run_lookup(target, seeds, true).await;
173        self.absorb_contacts(&result.closest).await;
174
175        // Merge local + discovered, dedup by provider, drop expired. Discovered records come
176        // straight off the wire from other peers' responses, bypassing `ProviderRecord::new`'s
177        // address cap — capped here before handing them back to our caller (SPEC §5.5, §14).
178        let mut discovered = result.providers;
179        for r in &mut discovered {
180            crate::record::sort_and_cap_addresses(&mut r.addresses);
181        }
182        local.extend(discovered);
183        let now = now_secs();
184        let mut seen = std::collections::HashSet::new();
185        local.retain(|r| !r.is_expired(now) && seen.insert(r.provider_peer_id.clone()));
186        Ok(local)
187    }
188
189    /// Announce that THIS node holds `content`: build a provider record (this node's `peer_id` +
190    /// addresses, expiring at `now + provider_ttl`), store it locally, remember to republish it, and
191    /// PUT it at the `k` nodes closest to the content key. Returns how many peers accepted the PUT.
192    ///
193    /// Called when the node's inventory gains content (a new capsule/root/resource it now serves).
194    pub async fn announce_provider(&self, content: &ContentId) -> Result<usize, DhtError> {
195        let target = content.to_key();
196        let record = self.build_local_record(&target);
197
198        // Store locally + remember for republish.
199        {
200            let mut ps = self.providers.lock().await;
201            ps.put(record.clone());
202            ps.mark_announced(target.to_hex());
203        }
204
205        // PUT at the k closest peers we can find.
206        let seeds = self.seed_contacts(&target).await;
207        if seeds.is_empty() {
208            // No peers yet — the local record stands; republish will re-attempt once bootstrapped.
209            return Ok(0);
210        }
211        let result = self.run_lookup(target, seeds, false).await;
212        self.absorb_contacts(&result.closest).await;
213        Ok(self.put_record_at(&result.closest, &record).await)
214    }
215
216    /// Stop announcing `content` (the node no longer holds it). The record ages out of the DHT via
217    /// TTL; we just stop republishing it. Returns whether it was being announced.
218    pub async fn withdraw_provider(&self, content: &ContentId) -> bool {
219        let key = content.to_key().to_hex();
220        self.providers.lock().await.unmark_announced(&key)
221    }
222
223    // ---- Maintenance -------------------------------------------------------------------------
224
225    /// Republish every content key this node still announces — re-runs the announce PUT so provider
226    /// records never expire while the node is online. Call on the [`DhtConfig::republish_interval`].
227    /// Returns the number of content keys republished.
228    pub async fn republish(&self) -> usize {
229        let keys = self.providers.lock().await.local_announcements();
230        let count = keys.len();
231        for hex in keys {
232            let Some(bytes) = hex64_to_bytes(&hex) else {
233                continue;
234            };
235            let target = Key::from_bytes(bytes);
236            let record = self.build_local_record(&target);
237            self.providers.lock().await.put(record.clone());
238            let seeds = self.seed_contacts(&target).await;
239            if !seeds.is_empty() {
240                let result = self.run_lookup(target, seeds, false).await;
241                self.absorb_contacts(&result.closest).await;
242                self.put_record_at(&result.closest, &record).await;
243            }
244        }
245        count
246    }
247
248    /// Refresh populated buckets by looking up a random key in each — keeps the routing table fresh
249    /// as peers churn. Call on the [`DhtConfig::refresh_interval`]. Returns the number of buckets
250    /// refreshed.
251    pub async fn refresh_buckets(&self) -> usize {
252        let indices = self.routing.lock().await.non_empty_bucket_indices();
253        let count = indices.len();
254        for idx in indices {
255            let target = self.random_key_in_bucket(idx);
256            let seeds = self.seed_contacts(&target).await;
257            if !seeds.is_empty() {
258                let result = self.run_lookup(target, seeds, false).await;
259                self.absorb_contacts(&result.closest).await;
260            }
261        }
262        count
263    }
264
265    /// Drop expired provider records. Call periodically (piggy-backs on republish/refresh). Returns
266    /// the number of records removed.
267    pub async fn gc(&self) -> usize {
268        self.providers.lock().await.gc(now_secs())
269    }
270
271    /// Ping a peer for liveness; on failure, evict it from the routing table. Used by the
272    /// ping-and-replace maintenance when a bucket is full. Returns whether the peer is alive.
273    pub async fn ping(&self, peer: &Contact) -> bool {
274        let nonce = rand::random::<u64>();
275        let from = self.local_contact();
276        match self
277            .transport
278            .rpc(&from, peer, &DhtRequest::Ping { nonce })
279            .await
280        {
281            Ok(DhtResponse::Pong { nonce: got }) if got == nonce => true,
282            _ => {
283                self.routing.lock().await.remove(&peer.peer_id);
284                false
285            }
286        }
287    }
288
289    // ---- Serving side (inbound RPC) ----------------------------------------------------------
290
291    /// Answer an inbound DHT request from another node, without a known caller identity. Prefer
292    /// [`handle_request_from`](Self::handle_request_from) on an authenticated transport (it lets the
293    /// responder learn the caller and populate its routing table bidirectionally, the way Kademlia
294    /// tables fill).
295    pub async fn handle_request(&self, request: DhtRequest) -> DhtResponse {
296        self.handle_request_from(None, request).await
297    }
298
299    /// Answer an inbound DHT request, folding the **authenticated caller** into the routing table.
300    ///
301    /// This is the server half — a dig-node wires it to inbound DHT streams, passing the caller's
302    /// mTLS-verified [`Contact`] as `caller`. Learning the caller from every inbound RPC is how a
303    /// Kademlia node discovers peers *without* an explicit announce: a node that talks to you becomes
304    /// a candidate in your table. The caller MUST come from the authenticated transport (the mTLS
305    /// `peer_id`), never from the request body — identity is not self-asserted.
306    ///
307    /// It reads/writes only local state (routing table + provider store) and never makes outbound
308    /// RPCs, so it cannot recurse or block on the network.
309    pub async fn handle_request_from(
310        &self,
311        caller: Option<Contact>,
312        request: DhtRequest,
313    ) -> DhtResponse {
314        // The authenticated caller's peer_id (if any), kept for the AddProvider self-announce check
315        // below — taken BEFORE the caller Contact is (conditionally) moved into the routing table.
316        let caller_peer_id = caller.as_ref().map(|c| c.peer_id.clone());
317
318        // Learn the (authenticated) caller — every inbound RPC is evidence the caller is alive.
319        // Cap its address list at the boundary (SPEC §5.5, §14): a `Contact` decoded off the wire
320        // bypasses `Contact::new`'s cap entirely (its fields are public), so an uncapped caller
321        // address list would otherwise be folded straight into our routing table and later re-served
322        // to every peer that queries us.
323        if let Some(mut c) = caller {
324            if c.peer_id != self.local_id.to_hex() {
325                crate::record::sort_and_cap_addresses(&mut c.addresses);
326                let _ = self.routing.lock().await.insert(c);
327            }
328        }
329        match request {
330            DhtRequest::Ping { nonce } => DhtResponse::Pong { nonce },
331            DhtRequest::FindNode { target } => {
332                let Some(key) = parse_key(&target) else {
333                    return DhtResponse::Error {
334                        code: 2,
335                        message: "bad target key".into(),
336                    };
337                };
338                let nodes = self.routing.lock().await.closest(&key);
339                DhtResponse::Nodes { nodes }
340            }
341            DhtRequest::FindProviders { content_key } => {
342                let Some(key) = parse_key(&content_key) else {
343                    return DhtResponse::Error {
344                        code: 2,
345                        message: "bad content key".into(),
346                    };
347                };
348                let now = now_secs();
349                let providers = self.providers.lock().await.get(&key.to_hex(), now);
350                let closer = self.routing.lock().await.closest(&key);
351                DhtResponse::Providers { providers, closer }
352            }
353            DhtRequest::AddProvider { mut record } => {
354                // Self-announce check (SPEC §6.4, §14): when the caller identity is known (an
355                // authenticated transport), the record's provider_peer_id MUST be the caller itself.
356                // ProviderRecord carries no signature, so without this check any authenticated caller
357                // could announce an arbitrary THIRD-PARTY peer_id as a provider of arbitrary content
358                // at attacker-chosen addresses — provider-set poisoning. A caller we cannot identify
359                // (`handle_request`, no transport-supplied identity) cannot be checked and is let
360                // through unchanged — that path already deviates from the mTLS-authenticated model.
361                if let Some(caller_id) = &caller_peer_id {
362                    if *caller_id != record.provider_peer_id {
363                        return DhtResponse::Error {
364                            code: 4,
365                            message:
366                                "add_provider: provider_peer_id must match the authenticated caller"
367                                    .into(),
368                        };
369                    }
370                }
371
372                // Cap the address list at the boundary BEFORE anything else (SPEC §5.5, §14): a
373                // `ProviderRecord` decoded off the wire bypasses `ProviderRecord::new`'s cap (its
374                // fields are public), so an attacker could otherwise pack thousands of addresses into
375                // one record within the 256 KiB frame limit — stored, folded into routing, and
376                // re-served (cloned) to every querying peer.
377                crate::record::sort_and_cap_addresses(&mut record.addresses);
378
379                // Clamp the inbound expires_at to our own TTL ceiling BEFORE admission/storage
380                // (SPEC §6.2, §14): an inbound record is never trusted to self-report its expiry.
381                // Without this, a record naming expires_at = u64::MAX would never GC (is_expired
382                // is `now >= expires_at`), making the memory it occupies permanent.
383                let clamp_ceiling = now_secs().saturating_add(self.config.provider_ttl_secs());
384                record.expires_at = record.expires_at.min(clamp_ceiling);
385
386                // Admission-controlled: put() enforces the per-key + global caps (SPEC §6.3, §14)
387                // so a flood of add_provider from one peer cannot grow the store without bound.
388                match self.providers.lock().await.put(record.clone()) {
389                    PutOutcome::Accepted => {
390                        // Fold the provider into our routing table (its addresses let us reach it).
391                        if let Some(pid) = record.provider_peer_id() {
392                            let contact = Contact::new(&pid, record.addresses.clone());
393                            let _ = self.routing.lock().await.insert(contact);
394                        }
395                        DhtResponse::AddProviderOk
396                    }
397                    PutOutcome::RejectedOverCapacity => DhtResponse::Error {
398                        code: 3,
399                        message: "provider store over capacity".into(),
400                    },
401                }
402            }
403        }
404    }
405
406    // ---- Internals ---------------------------------------------------------------------------
407
408    /// Build a provider record for content key `target` naming THIS node, expiring at
409    /// `now + provider_ttl`.
410    fn build_local_record(&self, target: &Key) -> ProviderRecord {
411        let expires_at = now_secs().saturating_add(self.config.provider_ttl_secs());
412        ProviderRecord::new(
413            target,
414            &self.local_id,
415            self.local_addresses.clone(),
416            expires_at,
417        )
418    }
419
420    /// The seed set for a lookup toward `target`: the closest contacts we currently know.
421    async fn seed_contacts(&self, target: &Key) -> Vec<Contact> {
422        self.routing.lock().await.closest(target)
423    }
424
425    /// Run an iterative lookup toward `target` from `seeds`, querying peers over the transport. Each
426    /// peer is asked `find_providers` (which also returns closer contacts), so ONE query kind serves
427    /// both node- and provider-lookups; `stop_on_providers` controls early exit.
428    async fn run_lookup(
429        &self,
430        target: Key,
431        seeds: Vec<Contact>,
432        stop_on_providers: bool,
433    ) -> crate::lookup::LookupResult {
434        let transport = self.transport.clone();
435        let content_key = target.to_hex();
436        let from = self.local_contact();
437        let query = move |contact: Contact| {
438            let transport = transport.clone();
439            let content_key = content_key.clone();
440            let from = from.clone();
441            async move {
442                let req = DhtRequest::FindProviders { content_key };
443                match transport.rpc(&from, &contact, &req).await {
444                    Ok(DhtResponse::Providers { providers, closer }) => {
445                        Ok(QueryOutcome { closer, providers })
446                    }
447                    Ok(DhtResponse::Nodes { nodes }) => Ok(QueryOutcome {
448                        closer: nodes,
449                        providers: vec![],
450                    }),
451                    _ => Err(()),
452                }
453            }
454        };
455        iterative_find(
456            target,
457            seeds,
458            self.config.k,
459            self.config.alpha,
460            stop_on_providers,
461            query,
462        )
463        .await
464    }
465
466    /// Fold discovered contacts back into the routing table (skipping ourselves). Applies the LRS
467    /// insert policy; a full bucket's [`InsertOutcome::Full`] is left for the ping-and-replace
468    /// maintenance (we do not ping inline to keep lookups fast).
469    ///
470    /// `contacts` come straight off the wire (a peer's `find_node`/`find_providers` response) and
471    /// so bypass [`Contact::new`]'s address cap (its fields are public) — this is another
472    /// untrusted-input boundary (SPEC §5.5, §14), capped here before insertion.
473    async fn absorb_contacts(&self, contacts: &[Contact]) {
474        let mut rt = self.routing.lock().await;
475        for c in contacts {
476            let mut c = c.clone();
477            crate::record::sort_and_cap_addresses(&mut c.addresses);
478            match rt.insert(c) {
479                InsertOutcome::Inserted => {}
480                InsertOutcome::Full { .. } => {
481                    // Bucket full — leave for ping-and-replace; do not block the lookup on a ping.
482                }
483            }
484        }
485    }
486
487    /// PUT `record` at each of `peers` via `add_provider`, counting acceptances. A peer that errors
488    /// is skipped (best-effort replication — the record survives at the peers that accepted + locally).
489    async fn put_record_at(&self, peers: &[Contact], record: &ProviderRecord) -> usize {
490        let req = DhtRequest::AddProvider {
491            record: record.clone(),
492        };
493        let from = self.local_contact();
494        let mut accepted = 0;
495        for p in peers {
496            if p.peer_id == self.local_id.to_hex() {
497                continue; // already stored locally
498            }
499            if let Ok(DhtResponse::AddProviderOk) = self.transport.rpc(&from, p, &req).await {
500                accepted += 1;
501            }
502        }
503        accepted
504    }
505
506    /// A random key whose distance from this node falls in bucket `idx` (so a refresh lookup targets
507    /// that bucket's region). Sets the bit at position `255 - idx` and randomizes the lower bits.
508    fn random_key_in_bucket(&self, idx: usize) -> Key {
509        let local = *self.local_id.as_bytes();
510        let mut distance = [0u8; 32];
511        let bit = 255 - idx; // MSB-set position for this bucket
512        let byte = bit / 8;
513        let bit_in_byte = 7 - (bit % 8);
514        distance[byte] = 1 << bit_in_byte;
515        // Randomize lower-significant bits so successive refreshes vary the target.
516        for b in distance.iter_mut().skip(byte + 1) {
517            *b = rand::random::<u8>();
518        }
519        let mut target = [0u8; 32];
520        for i in 0..32 {
521            target[i] = local[i] ^ distance[i];
522        }
523        Key::from_bytes(target)
524    }
525
526    /// The contacts currently in this node's routing table closest to `target` (diagnostic /
527    /// introspection — the peers this node knows without any network round-trip).
528    pub async fn known_closest(&self, target: &Key) -> Vec<Contact> {
529        self.routing.lock().await.closest(target)
530    }
531
532    /// The number of peers currently in this node's routing table (diagnostic / metrics).
533    pub async fn routing_len(&self) -> usize {
534        self.routing.lock().await.len()
535    }
536}
537
538/// Current wall-clock Unix seconds (saturating to 0 before the epoch), for provider TTLs.
539fn now_secs() -> u64 {
540    SystemTime::now()
541        .duration_since(UNIX_EPOCH)
542        .unwrap_or_default()
543        .as_secs()
544}
545
546/// Parse a 64-hex string into a [`Key`] (used on the serving side for wire targets).
547fn parse_key(hex: &str) -> Option<Key> {
548    hex64_to_bytes(hex).map(Key::from_bytes)
549}
550
551/// Decode a 64-char hex string to 32 bytes.
552fn hex64_to_bytes(hex: &str) -> Option<[u8; 32]> {
553    if hex.len() != 64 {
554        return None;
555    }
556    let mut out = [0u8; 32];
557    let bytes = hex.as_bytes();
558    for (i, chunk) in bytes.chunks(2).enumerate() {
559        let hi = (chunk[0] as char).to_digit(16)?;
560        let lo = (chunk[1] as char).to_digit(16)?;
561        out[i] = ((hi << 4) | lo) as u8;
562    }
563    Some(out)
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569
570    fn key_hex_round_trips() {
571        // sanity for the local hex helper
572    }
573
574    #[test]
575    fn hex64_round_trip() {
576        let bytes = [0xABu8; 32];
577        let hex = Key::from_bytes(bytes).to_hex();
578        assert_eq!(hex64_to_bytes(&hex).unwrap(), bytes);
579        assert!(hex64_to_bytes("short").is_none());
580        assert!(hex64_to_bytes(&"zz".repeat(32)).is_none());
581        key_hex_round_trips();
582    }
583
584    #[test]
585    fn parse_key_rejects_bad_hex() {
586        assert!(parse_key("nothex").is_none());
587        assert!(parse_key(&"00".repeat(32)).is_some());
588    }
589}