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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//! Types for deserializing a response from `/api/v1/activitymaps/query`

use std::{cmp, fmt, vec, slice};
use std::collections::HashSet;

#[cfg(feature = "petgraph")]
use petgraph::Graph;

#[cfg(feature = "petgraph")]
use std::collections::HashMap;

use serde_json;

use Oid;

/// A successful response to a single topology API request.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Response {
    /// Non-fatal errors encountered during the construction of the map.
    /// Items in this list indicate that the returned topology may be incomplete.
    pub warnings: Vec<Error>,
    /// The absolute UTC packet time at which data starts for the response, in milliseconds
    /// since epoch.
    pub from: u64,
    /// The absolute UTC packet time at which data ends for the response, in milliseconds
    /// since epoch.
    pub until: u64,
    /// The collection of edges which matched the activity map query.
    pub edges: Vec<Edge>,
}

impl Response {
    /// Computes the set of nodes in the response.
    pub fn nodes(&self) -> HashSet<Oid> {
        let mut oids = HashSet::new();
        for edge in &self.edges {
            oids.insert(edge.from.clone());
            oids.insert(edge.to.clone());
        }

        oids
    }

    /// Gets a slice iterator over the edges
    pub fn iter<'a>(&'a self) -> slice::Iter<'a, Edge> {
        self.edges.iter()
    }

    /// Checks that there are no warnings which indicate an incomplete response
    /// from the appliance.
    pub fn is_complete(&self) -> bool {
        self.warnings.is_empty()
    }
}

impl Default for Response {
    fn default() -> Self {
        Response {
            warnings: vec![],
            from: 0,
            until: 0,
            edges: vec![],
        }
    }
}

impl IntoIterator for Response {
    type Item = Edge;
    type IntoIter = vec::IntoIter<Edge>;

    fn into_iter(self) -> Self::IntoIter {
        self.edges.into_iter()
    }
}

impl<'a> IntoIterator for &'a Response {
    type Item = &'a Edge;
    type IntoIter = slice::Iter<'a, Edge>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

#[cfg(feature = "petgraph")]
impl From<Response> for Graph<Oid, Edge> {
    fn from(val: Response) -> Self {
        let mut graph = Graph::new();
        let nodes = val.nodes();
        let mut node_idx = HashMap::with_capacity(nodes.len());
        for node in nodes {
            node_idx.insert(node.clone(), graph.add_node(node));
        }

        for edge in val.edges {
            graph.add_edge(
                *node_idx.get(&edge.from).unwrap(),
                *node_idx.get(&edge.to).unwrap(),
                edge
            );
        }

        graph
    }
}

/// An error or warning returned by the API.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Error {
    /// A human-friendly message describing the error that occurred.
    pub message: String,

    /// A machine-friendly string that identifies the type of error.
    #[serde(rename = "type")]
    pub error_type: String,

    /// A bag of properties that can be used in conjunction with the error type
    /// to construct a richer error message.
    #[serde(default)]
    pub(crate) properties: Option<serde_json::Value>,
}

impl Error {
    /// Create a new error with no properties.
    pub fn new<M: Into<String>, E: Into<String>>(message: M, error_type: E) -> Self {
        Error {
            message: message.into(),
            error_type: error_type.into(),
            properties: None,
        }
    }
}

/// A connection between two devices in an activity map API response.
///
/// An edge goes from client to server regardless of the direction it was
/// traversed during the walk.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Edge {
    /// The "client" device in the edge. This may be the device on which
    /// the step *ended* if a server-to-client relationship was specified.
    pub from: Oid,

    /// The "server" device in the edge. This may be the device on which
    /// the step *started* if a server-to-client relationship was specified.
    pub to: Oid,

    /// The "importance" of the edge; larger numbers are more important.
    pub weight: usize,

    /// Additional data about the edge which was asked for in the request.
    #[serde(default)]
    pub annotations: EdgeAnnotations,
}

impl Edge {
    /// Returns true if the edge contains the specified object ID.
    pub fn contains(&self, oid: &Oid) -> bool {
        self.from == *oid || self.to == *oid
    }

    pub fn to_tuple(&self) -> (Oid, Oid) {
        (self.from.clone(), self.to.clone())
    }
}

/// Additional data about the edge which can be asked for in the request.
/// Properties should have a value of `Some` when their key was present
/// in the request, though the contents may themselves be empty.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct EdgeAnnotations {
    /// The list of walk/step pairs which traversed this edge during map
    /// construction.
    pub appearances: Option<Vec<Appearance>>,

    /// The per-protocol contributions to the edge's weight.
    pub protocols: Option<Vec<ProtocolAnnotation>>,
}

/// A walk index and step index into the request.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct Appearance {
    /// The index of the walk that contributed to this appearance.
    pub walk: u16,
    /// The index of the step in the walk that contributed to this appearance.
    pub step: u16,
}

impl Appearance {
    /// Create a new `Appearance` for a specific walk and step.
    pub fn new(walk: u16, step: u16) -> Self {
        Appearance {
            walk: walk,
            step: step,
        }
    }
}

impl PartialOrd for Appearance {
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
        let walk = self.walk.cmp(&other.walk);
        if walk == cmp::Ordering::Equal {
            Some(self.step.cmp(&other.step))
        } else {
            Some(walk)
        }
    }
}

/// An annotation connecting a protocol to the weight that it added to an
/// edge.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProtocolAnnotation {
    /// The amount of weight on the protocol. The units depend on the
    /// request configuration.
    pub weight: u32,
    /// The protocol which contributed the specified weight.
    pub protocol: ProtocolStack,
}

/// The stack of protocols for this part of the edge.
///
/// This will look as follows:
///
/// ```text
/// [L3Protocol, L4Protocol, L7Protocol+]
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProtocolStack(Vec<String>);

impl ProtocolStack {
    /// Get the protocols in the stack in ascending OSI stack level.
    pub fn protocols(&self) -> &[String] {
        &self.0
    }
}

impl fmt::Display for ProtocolStack {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let val = &self.0;
        let len = val.len();

        if val.is_empty() {
            write!(f, "OTHER")
        } else if len > 1 && val[len - 1] == "OTHER" {
            write!(f, "{}", val[len - 2])
        } else {
            write!(f, "{}", val[len - 1])
        }
    }
}

impl From<Vec<&'static str>> for ProtocolStack {
    fn from(vals: Vec<&'static str>) -> Self {
        ProtocolStack(vals.into_iter().map(String::from).collect())
    }
}

#[cfg(test)]
mod tests {
    use super::{Appearance, ProtocolStack};

    #[test]
    fn protocol_fmt_http() {
        assert_eq!(
            "HTTP",
            &format!("{}", ProtocolStack::from(vec!["IPv4", "TCP", "HTTP"]))
        );
        assert_eq!(
            "HTTP",
            &format!("{}", ProtocolStack::from(vec!["IPv6", "TCP", "HTTP"]))
        );
    }

    #[test]
    fn protocol_fmt_unknowns() {
        assert_eq!(
            "TCP",
            &format!("{}", ProtocolStack::from(vec!["IPv4", "TCP", "OTHER"]))
        );
        assert_eq!("OTHER", &format!("{}", ProtocolStack::from(vec!["OTHER"])));
    }

    /// Appearances are sorted by walk, then by step.
    #[test]
    fn order_appearances() {
        let mut items = vec![
            Appearance::new(1, 2),
            Appearance::new(0, 2),
            Appearance::new(1, 0),
            Appearance::new(0, 1),
        ];

        items.sort();

        assert_eq!(items, vec![
            Appearance::new(0, 1),
            Appearance::new(0, 2),
            Appearance::new(1, 0),
            Appearance::new(1, 2),
        ]);
    }
}