aboutsummaryrefslogblamecommitdiff
path: root/src/builder.rs
blob: cc42fa8d2ca4e82f6f985c4c795c720333af6fd4 (plain) (tree)











































































































































































                                                                                                         
// Copyright 2019 Hristo Venev
//
// See COPYING.

use crate::{config, model, proto};
use std::{error, fmt};
use std::collections::hash_map;


#[derive(Debug)]
pub struct ConfigError {
    pub url: String,
    pub peer: model::Key,
    pub important: bool,
    err: &'static str,
}

impl ConfigError {
    fn new(err: &'static str, s: &config::Source, p: &proto::Peer, important: bool) -> Self {
        ConfigError {
            url: s.url.clone(),
            peer: p.public_key.clone(),
            important,
            err,
        }
    }
}

impl error::Error for ConfigError {}
impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{} [{}] from [{}]: {}",
            if self.important {
                "Invalid peer"
            } else {
                "Misconfigured peer"
            },
            self.peer,
            self.url,
            self.err
        )
    }
}

pub struct ConfigBuilder<'a> {
    c: model::Config,
    err: Vec<ConfigError>,
    public_key: model::Key,
    pc: &'a config::PeerConfig,
}

impl<'a> ConfigBuilder<'a> {
    #[inline]
    pub fn new(public_key: model::Key, pc: &'a config::PeerConfig) -> Self {
        ConfigBuilder {
            c: model::Config::default(),
            err: vec![],
            public_key,
            pc,
        }
    }

    #[inline]
    pub fn build(self) -> (model::Config, Vec<ConfigError>) {
        (self.c, self.err)
    }

    #[inline]
    pub fn add_server(
        &mut self,
        s: &config::Source,
        p: &proto::Server,
    ) {
        if p.peer.public_key == self.public_key {
            return;
        }

        let pc = self.pc;
        let ent = insert_peer(&mut self.c, &mut self.err, s, &p.peer, |ent| {
            ent.psk = s.psk.clone();
            ent.endpoint = Some(p.endpoint.clone());
            ent.keepalive = pc.fix_keepalive(p.keepalive);
        });

        add_peer(&mut self.err, ent, s, &p.peer)
    }

    #[inline]
    pub fn add_road_warrior(
        &mut self,
        s: &config::Source,
        p: &proto::RoadWarrior,
    ) {
        if p.peer.public_key == self.public_key {
            self.err.push(ConfigError::new("The local peer cannot be a road warrior", s, &p.peer, true));
            return;
        }

        let ent = if p.base == self.public_key {
            insert_peer(&mut self.c, &mut self.err, s, &p.peer, |_| {})
        } else {
            match self.c.peers.get_mut(&p.base) {
                Some(ent) => ent,
                None => {
                    self.err.push(ConfigError::new("Unknown base peer", s, &p.peer, true));
                    return;
                }
            }
        };
        add_peer(&mut self.err, ent, s, &p.peer)
    }
}

#[inline]
fn insert_peer<'b>(
    c: &'b mut model::Config,
    err: &mut Vec<ConfigError>,
    s: &config::Source,
    p: &proto::Peer,
    update: impl for<'c> FnOnce(&'c mut model::Peer) -> (),
) -> &'b mut model::Peer {
    match c.peers.entry(p.public_key.clone()) {
        hash_map::Entry::Occupied(ent) => {
            err.push(ConfigError::new("Duplicate public key", s, p, true));
            ent.into_mut()
        }
        hash_map::Entry::Vacant(ent) => {
            let ent = ent.insert(model::Peer {
                endpoint: None,
                psk: None,
                keepalive: 0,
                ipv4: vec![],
                ipv6: vec![],
            });
            update(ent);
            ent
        }
    }
}

fn add_peer(err: &mut Vec<ConfigError>, ent: &mut model::Peer, s: &config::Source, p: &proto::Peer) {
    let mut added = false;
    let mut removed = false;

    for i in p.ipv4.iter() {
        if s.ipv4.contains(i) {
            ent.ipv4.push(*i);
            added = true;
        } else {
            removed = true;
        }
    }
    for i in p.ipv6.iter() {
        if s.ipv6.contains(i) {
            ent.ipv6.push(*i);
            added = true;
        } else {
            removed = true;
        }
    }

    if removed {
        let msg = if added {
            "Some IPs removed"
        } else {
            "All IPs removed"
        };
        err.push(ConfigError::new(msg, s, p, !added));
    }
}