irc_bot/core/
state.rs

1use super::config;
2use super::irc_msgs::OwningMsgPrefix;
3use super::BotCommand;
4use super::ErrorKind;
5use super::MsgPrefix;
6use super::Result;
7use super::Server;
8use super::ServerId;
9use super::State;
10use rand::StdRng;
11use std::borrow::Cow;
12use std::path::Path;
13use std::sync::MutexGuard;
14use std::sync::RwLockReadGuard;
15
16impl State {
17    pub fn nick(&self, server_id: ServerId) -> Result<String> {
18        self.read_msg_prefix(server_id)?
19            .parse()
20            .nick
21            .ok_or(ErrorKind::NicknameUnknown.into())
22            .map(ToOwned::to_owned)
23    }
24
25    pub fn module_data_path(&self) -> Result<&Path> {
26        Ok(self.module_data_path.as_ref())
27    }
28
29    pub fn command(&self, name: &str) -> Result<Option<&BotCommand>> {
30        Ok(self.commands.get(name))
31    }
32
33    pub fn command_names(&self) -> Result<Vec<Cow<'static, str>>> {
34        Ok(self.commands.keys().cloned().collect())
35    }
36
37    pub fn have_admin(
38        &self,
39        MsgPrefix {
40            nick: nick_1,
41            user: user_1,
42            host: host_1,
43        }: MsgPrefix,
44    ) -> Result<bool> {
45        Ok(self.config.admins.iter().any(
46            |&config::Admin {
47                 nick: ref nick_2,
48                 user: ref user_2,
49                 host: ref host_2,
50             }| {
51                check_admin_cred(nick_1, nick_2) && check_admin_cred(user_1, user_2)
52                    && check_admin_cred(host_1, host_2)
53            },
54        ))
55    }
56
57    // TODO: This is server-specific.
58    pub(super) fn read_msg_prefix(
59        &self,
60        _server_id: ServerId,
61    ) -> Result<RwLockReadGuard<OwningMsgPrefix>> {
62        self.msg_prefix
63            .read()
64            .map_err(|_| ErrorKind::LockPoisoned("stored message prefix".into()).into())
65    }
66
67    pub(super) fn read_server(
68        &self,
69        server_id: ServerId,
70    ) -> Result<Option<RwLockReadGuard<Server>>> {
71        match self.servers.get(&server_id) {
72            Some(lock) => match lock.read() {
73                Ok(guard) => Ok(Some(guard)),
74                Err(_) => Err(ErrorKind::LockPoisoned(
75                    format!("server {}", server_id.uuid.hyphenated()).into(),
76                ).into()),
77            },
78            None => Ok(None),
79        }
80    }
81
82    /// Allows access to a random number generator that's stored centrally, to avoid the cost of
83    /// repeatedly initializing one.
84    pub fn rng(&self) -> Result<MutexGuard<StdRng>> {
85        self.rng.lock().map_err(|_| {
86            ErrorKind::LockPoisoned("the central random number generator".into()).into()
87        })
88    }
89
90    /// Returns a string identifying the server for debug purposes.
91    ///
92    /// TODO: This should return something less allocate-y.
93    pub(super) fn server_socket_addr_dbg_string(&self, server_id: ServerId) -> String {
94        let uuid = server_id.uuid.hyphenated();
95
96        match self.read_server(server_id) {
97            Ok(Some(s)) => s.socket_addr_string.clone(),
98            Ok(None) => format!("<unknown server {} (not found)>", uuid),
99            Err(e) => format!("<unknown server {} ({})>", uuid, e),
100        }
101    }
102}
103
104/// Check a field of a (nick, user, host) triple representing some user (the "candidate") against
105/// the corresponding field of a like triple representing an authorized administrator of the bot
106/// (the "control"). Returns whether the given candidate field matches the control.
107fn check_admin_cred(candidate: Option<&str>, control: &Option<String>) -> bool {
108    match (candidate, control) {
109        (Some(cdt), &Some(ref ctl)) => {
110            // If a field is set in both candidate and control, the values must be equal.
111            cdt == ctl
112        }
113        (_, &None) => {
114            // All candidates match against a field that is unset in the control record.
115            true
116        }
117        (None, &Some(_)) => {
118            // A candidate does not match if it lacks a field that is set in the control record.
119            false
120        }
121    }
122}