mist_lib/
mist.rs

1// Copyright [2024] [Ryan Ruckley]
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Mist Event Module
16
17use serde::{Deserialize,Serialize};
18use serde_json::{Map,Value};
19
20/// Mist Device Up/Down Payload
21#[derive(Debug,Clone,Serialize,Deserialize)]
22pub struct MistDeviceUpDowns {
23    /// Access Point
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub ap      : Option<String>,
26    /// Access Point Name
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub ap_name : Option<String>,
29    /// Device Name
30    pub device_name : String,
31    /// Device Type
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub device_type : Option<String>,
34    /// Event Type
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub ev_type : Option<String>,
37    /// External IP Address
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub ext_ip  : Option<String>,
40    /// MAC Address
41    pub mac     : String,
42    /// Reason
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub reason  : Option<String>,
45    /// Organisation Id
46    pub org_id  : String,
47    /// Site Id
48    pub site_id : String,
49    /// Site Name
50    pub site_name : String,
51    /// Text
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub text    : Option<String>,
54    /// Timestamp
55    pub timestamp : i64,
56    /// Type
57    pub r#type    : String,
58}
59
60/// Mist Device Payload
61#[derive(Debug,Clone,Serialize,Deserialize)]
62pub struct MistDeviceEvents {
63    /// Access Point
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub ap      : Option<String>,
66    /// Access Point Name
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub ap_name : Option<String>,
69    /// Audit Id
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub audit_id    : Option<String>,
72    /// Radio Band
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub band        : Option<String>,
75    /// Radio Bandwidth
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub bandwidth   : Option<u16>,
78    /// Radio Channel
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub channel     : Option<u16>,
81    /// Device Name
82    pub device_name : String,
83    /// Device Type
84    pub device_type : String,
85    /// Event Type
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub ev_type : Option<String>,
88    /// MAC Address
89    pub mac     : String,
90    /// Organization Id
91    pub org_id  : String,
92    /// Radio Power Level
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub power   : Option<u16>,
95    /// PRE Bandwidth
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub pre_bandwidth   : Option<u16>,
98    /// PRE Channel
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub pre_channel     : Option<u16>,
101    /// PRE Power
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub pre_power       : Option<u16>,
104    /// PRE Usage
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub pre_usage       : Option<u16>,
107    /// Event Reason
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub reason  : Option<String>,
110    /// Site Id
111    pub site_id : String,
112    /// Site Name
113    pub site_name   : String,
114    /// Text (Details)
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub text    : Option<String>,
117    /// Event Timestamp
118    pub timestamp   : i64,
119    /// Type
120    pub r#type      : String,
121    /// Usage
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub usage       : Option<u16>,
124}
125
126/// Mist Alarm Details
127#[derive(Clone,Serialize,Deserialize,Debug)]
128pub struct MistAlarmDetails {
129    /// Action
130    pub action  : String,
131    /// Category
132    pub category : String,
133    /// Status
134    pub status  : String,
135    /// Symptom
136    pub symptom : String,
137}
138
139/// Mist Email Content
140#[derive(Clone,Serialize,Deserialize,Debug)]
141pub struct MistEmailContent {
142    /// Conntected Switch
143    #[serde(rename = "Connected switch")]
144    pub connected_switch:   String,
145    /// Event Reason
146    #[serde(rename = "Reason")]
147    pub reason: String,
148    /// Status
149    #[serde(rename = "Status")]
150    pub status: String,
151    /// Access Point
152    pub ap  : String,
153}
154
155/// Mist - Impacted Entity
156#[derive(Clone,Serialize,Deserialize,Debug)]
157pub struct MistImpactedEntity {
158    /// Connected Switch - MAC
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub connected_switch_mac    : Option<String>,
161    /// Connected Switch - Name
162    pub connected_switch_name   : String,
163    /// Entity MAC
164    pub entity_mac  : String,
165    /// Entity Name
166    pub entity_name : String,
167    /// Entity Type
168    pub entity_type : String,
169    /// Port Id
170    pub port_id : String,
171}
172
173/// Mist Alarm Payload
174#[derive(Clone,Serialize,Deserialize,Debug)]
175pub struct MistAlarm {
176    /// Alert Id
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub alert_id    : Option<String>,
179    /// Alert Category
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub category    : Option<String>,
182    /// Optional list of access point IDs
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub aps     : Option<Vec<String>>,
185    /// Count of alarms seen
186    pub count   : u32,
187    /// Optional Detail
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub detail  : Option<String>,
190    /// Optional Details object
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub details  : Option<MistAlarmDetails>,
193    /// Email Content
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub email_content : Option<MistEmailContent>,
196    /// Firmware Version (Optional)
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub fw_version  : Option<String>,
199    /// Group
200    pub group   : String,
201    /// Optional list of hostnames
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub hostnames : Option<Vec<String>>,
204    /// Alarm Id
205    pub id      : String,
206    /// Timestamp of last occurance
207    /// Impacted Entities
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub impacted_entities : Option<Vec<MistImpactedEntity>>,
210    /// Impacted Entity Count
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub impacted_entity_count   : Option<u16>,
213    /// Last Seen Timestamp
214    pub last_seen: String,
215    /// Model
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub model   : Option<String>,
218    /// Organisation Id
219    pub org_id  : String,
220    /// Organisation Name
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub org_name    : Option<String>,
223    /// Peer (Optional)
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub peer    :  Option<Map<String,Value>>,
226    /// Port Id
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub port_id     : Option<String>,
229    /// Port Ids
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub port_ids    : Option<Vec<String>>,
232    /// Optional reason if known
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub reasons : Option<Vec<String>>,
235    /// Severity: CRITICAL, MAJOR, MINOR, WARN, INFO
236    /// Root Cause (if known)
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub root_cause  : Option<String>,
239    /// Alarm Severity
240    pub severity: String,
241    /// Site Id
242    pub site_id : String,
243    /// Site Name
244    pub site_name: String,
245    /// Status
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub status  : Option<String>,
248    /// Suggestion
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub suggestion  : Option<String>,
251    /// Optional list of switches
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub switches : Option<Vec<String>>,
254    /// ISO Timestamp of this event
255    pub timestamp: f64,
256    /// Categorisation of alarm
257    pub r#type   : String,
258}
259
260/// Mist Audit Payload
261#[derive(Clone,Serialize,Deserialize,Debug)]
262pub struct MistAudits {
263    /// Admin Name
264    pub admin_name  : String,
265    /// After
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub after   : Option<String>,
268    /// Before
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub before  : Option<String>,
271    /// Identifier
272    pub id      : String,
273    /// Message
274    pub message : String,
275    /// Organization Id
276    pub org_id  : String,
277    /// Site Id
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub site_id : Option<String>,
280    /// Site Name
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub site_name   : Option<String>,
283    /// SRC IP Address
284    pub src_ip  : String,
285    /// Audit Timestamp
286    pub timestamp   : f64,
287    /// User Agent
288    pub user_agent  : String,
289    /// Webhook Id
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub webhook_id  : Option<String>,
292}
293
294/// Mist Client Session Payload
295#[derive(Clone,Serialize,Deserialize,Debug)]
296pub struct MistClientSessions {
297    /// Access Point Id
298    pub ap  : String,
299    /// Access Point Name
300    pub ap_name : String,
301    /// Frequency Band, e.g. 2.4GHz or 5Ghz etc.
302    pub band    : String,
303    /// Basic SSID (Service Set ID)
304    pub bssid   : String,
305    /// Client Family
306    pub client_family   : String,
307    /// Client Manufacturer
308    pub client_manufacture : String,
309    /// Client Model
310    pub client_model    : String,
311    /// Client OS
312    pub client_os   : String,
313    /// Connection
314    pub connect : i64,
315    /// Connection Float
316    pub connect_float : f64,
317    /// Disconnect
318    pub disconnect  : i64,
319    /// Disconnect Float
320    pub disconnect_float : f64,
321    /// Connection Duration
322    pub duration    : f64,
323    /// Client MAC
324    pub mac : String,
325    /// Next Access Point
326    pub next_ap : String,
327    /// Organization Id
328    pub org_id  : String,
329    /// Random MAC?
330    pub random_mac  : bool,
331    /// Recieved Signal Strength Indicator (RSSI)
332    pub rssi    : i32,
333    /// Site Id
334    pub site_id : String,
335    /// Site Name
336    pub site_name   : String,
337    /// Service Set Id (SSID)
338    pub ssid    : String,
339    /// Termination Reason
340    pub termination_reason : i32,
341    /// Connect Timestamp
342    pub timestamp   : i64,
343    /// Version
344    pub version : i32,
345    /// Wireless LAN Id
346    pub wlan_id : String,
347}
348
349/// Mist Client Join Payload
350#[derive(Clone,Serialize,Deserialize,Debug)]
351pub struct MistClientJoin {
352    /// Access Point ID
353    pub ap  : String,
354    /// Access Point Name
355    pub ap_name : String,
356    /// Radio Band
357    pub band    : String,
358    /// Basic Service Set Identifier (SSID)
359    pub bssid   : String,
360    /// Connect Count
361    pub connect : i64,
362    /// Connect Float
363    pub connect_float : f64,
364    /// Media Access Control (MAC) Address
365    pub mac     : String,
366    /// Organisaton Id
367    pub org_id  : String,
368    /// Random MAC
369    pub random_mac  : bool,
370    /// Recieved Signal Strength Identifier
371    pub rssi    : i32,
372    /// Site Identier
373    pub site_id : String,
374    /// Site Name
375    pub site_name   : String,
376    /// Service Set Identifier
377    pub ssid    : String,
378    /// ISO Timestamp
379    pub timestamp   : i64,
380    /// Payload version
381    pub version : i32,
382    /// Wireless LAN Id
383    pub wlan_id : String,
384}
385
386/// Mist Uber Payload
387#[derive(Debug,Clone,Serialize,Deserialize)]
388#[serde(tag = "topic")]
389pub enum MistMessage {
390    /// Alarm Container
391    #[serde(rename = "alarms")]
392    Alarms {
393        /// Vec of MistAlarm payloads
394        events         : Vec<MistAlarm>        
395    },
396    /// Audit Container
397    #[serde(rename = "audits")]
398    Audits {
399        /// Vec of MistAudit payloads
400        events         : Vec<MistAudits>       
401    },
402    /// ClientSession Container
403    #[serde(rename = "client-sessions")]
404    ClientSessions { 
405        /// Vec of MistClientSessions payloads
406        events    : Vec<MistClientSessions> 
407    },
408    /// Device Up/Down Container
409    #[serde(rename = "device-updowns")]
410    DeviceUpDowns { 
411        /// Vec of MistDeviceUpDowns payloads
412        events : Vec<MistDeviceUpDowns>
413    },
414    /// Device Events Container
415    #[serde(rename = "device-events")]
416    DeviceEvents { 
417        /// Vec of MistDeviceDevents payloads
418        events  : Vec<MistDeviceEvents> 
419    },
420    /// Client Join Container
421    #[serde(rename = "client-join")]
422    ClientJoin { 
423        /// Vec of MistClientJoin payloads
424        events : Vec<MistClientJoin> 
425    },
426}
427
428/// Legacy Mist schema
429#[derive(Serialize,Deserialize,Debug)]
430pub struct Mist {
431    /// Topic indicator
432    topic : String,
433    /// Vec of MistAlarm payloads
434    pub events : Vec<MistAlarm>,
435}
436
437impl Default for Mist {
438    fn default() -> Self {
439        Mist {
440            topic : "undefined".to_string(),
441            events : vec![],
442        }
443    }
444}
445
446impl MistAlarm {
447    /// Split an event by hostname
448    /// # Input
449    /// A normal MistAlarm event payload
450    /// # Output
451    /// Vector of MistAlarm payloads each with only a single hostname.
452    pub fn split<F>(event : &MistAlarm, get_ci : F) -> Option<Vec<MistAlarm>> 
453    where F : FnOnce(String) -> String {
454        // take a given MistEvent and return a vector split on hostnames
455        let mut output :Vec<MistAlarm> = vec![];
456        // Since hostname is optional
457        let mut match_count = 0;
458        match &event.hostnames {
459            Some(v) => {
460                // We have some hostnames, clone ourselves for each hostname
461                let cv = v.clone();
462                cv.iter().for_each(|e| {
463                    let mut new_event = event.clone();
464                    new_event.hostnames = Some(vec![e.to_string()]);
465                    new_event.id.push_str(format!(".{match_count}").as_ref());
466                    output.push(new_event);
467                    match_count += 1;
468                });
469                Some(output)
470            },
471            None => {
472                // No hostnames, try for impacted_entities instead
473                match &event.impacted_entities {
474                    Some(ie) => {
475                        ie.iter().for_each(|e| {
476                            let mut new_event = event.clone();
477                            new_event.hostnames = Some(vec![e.clone().entity_name]);
478                            output.push(new_event);
479                            match_count += 1;
480                        });
481                        Some(output)
482                    },
483                    None => {
484                        let mut new_event = event.clone();
485                        new_event.hostnames = Some(vec![get_ci(event.org_id.clone())]);
486                        output.push(new_event);
487                        Some(output)
488                    },
489                }
490            },
491        }
492    }
493}