1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use super::RouterStore;
use bytes::Bytes;
use futures::{future::err, Future};
use interledger_packet::{ErrorCode, RejectBuilder};
use interledger_service::*;
use std::str;

/// The router implements the IncomingService trait and uses the routing table
/// to determine the `to` (or "next hop") Account for the given request.
///
/// Note that the router does **not**:
///   - apply exchange rates or fees to the Prepare packet
///   - adjust account balances
///   - reduce the Prepare packet's expiry
#[derive(Clone)]
pub struct Router<S, T> {
    next: S,
    store: T,
}

impl<S, T> Router<S, T>
where
    S: OutgoingService<T::Account>,
    T: RouterStore,
{
    pub fn new(next: S, store: T) -> Self {
        Router { next, store }
    }
}

impl<S, T> IncomingService<T::Account> for Router<S, T>
where
    S: OutgoingService<T::Account> + Clone + Send + 'static,
    T: RouterStore,
{
    type Future = BoxedIlpFuture;

    fn handle_request(&mut self, request: IncomingRequest<T::Account>) -> Self::Future {
        let destination = Bytes::from(request.prepare.destination());
        let mut next_hop: Option<<T::Account as Account>::AccountId> = None;
        let routing_table = self.store.routing_table();

        // Check if we have a direct path for that account or if we need to scan through the routing table
        if let Some(account_id) = routing_table.get(&destination) {
            debug!(
                "Found direct route for address: \"{}\". Account: {}",
                str::from_utf8(&destination[..]).unwrap_or("<not utf8>"),
                account_id
            );
            next_hop = Some(*account_id);
        } else {
            let mut max_prefix_len = 0;
            for route in self.store.routing_table() {
                // Check if the route prefix matches or is empty (meaning it's a catch-all address)
                if (route.0.is_empty() || destination.starts_with(&route.0[..]))
                    && route.0.len() >= max_prefix_len
                {
                    next_hop = Some(route.1);
                    max_prefix_len = route.0.len();
                    debug!(
                        "Found matching route for address: \"{}\". Prefix: \"{}\", account: {}",
                        str::from_utf8(&destination[..]).unwrap_or("<not utf8>"),
                        str::from_utf8(&route.0[..]).unwrap_or("<not utf8>"),
                        route.1,
                    );
                }
            }
        }

        if let Some(account_id) = next_hop {
            let mut next = self.next.clone();
            Box::new(
                self.store
                    .get_accounts(vec![account_id])
                    .map_err(move |_| {
                        error!("No record found for account: {}", account_id);
                        RejectBuilder {
                            code: ErrorCode::F02_UNREACHABLE,
                            message: &[],
                            triggered_by: &[],
                            data: &[],
                        }
                        .build()
                    })
                    .and_then(move |mut accounts| {
                        let request = request.into_outgoing(accounts.remove(0));
                        next.send_request(request)
                    }),
            )
        } else {
            debug!("No route found for request: {:?}", request);
            Box::new(err(RejectBuilder {
                code: ErrorCode::F02_UNREACHABLE,
                message: &[],
                triggered_by: &[],
                data: &[],
            }
            .build()))
        }
    }
}