sip2/
message.rs

1use super::error::Error;
2use super::spec;
3use super::util;
4use log::{error, warn};
5use std::fmt;
6
7const PASSWORD_REDACTED: &str = "REDACTED";
8
9/// Fixed field with spec and value.
10///
11/// Since fixed fields have specific length requirements, a well-known
12/// spec::FixedField is required
13#[derive(PartialEq, Debug)]
14pub struct FixedField {
15    spec: &'static spec::FixedField,
16    value: String,
17}
18
19impl FixedField {
20    /// Create a new FixeField using the provided spec and value.
21    /// let mut ff = FixedField::new(&spec::FF_MAX_PRINT_WIDTH, "999").unwrap();
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use sip2::FixedField;
27    /// use sip2::spec;
28    /// assert!(FixedField::new(&spec::FF_MAX_PRINT_WIDTH, "999").is_ok());
29    /// assert!(FixedField::new(&spec::FF_MAX_PRINT_WIDTH, "999999").is_err());
30    /// ```
31    pub fn new(spec: &'static spec::FixedField, value: &str) -> Result<Self, Error> {
32        if value.len() == spec.length {
33            Ok(FixedField {
34                spec,
35                value: value.to_string(),
36            })
37        } else {
38            Err(Error::FixedFieldLengthError)
39        }
40    }
41
42    /// Ref to the FixedField spec that defines our structure.
43    pub fn spec(&self) -> &'static spec::FixedField {
44        self.spec
45    }
46
47    /// Value stored by this fixed field.
48    ///
49    /// The length of the value will match the length defined by the
50    /// Fixedfield spec.
51    pub fn value(&self) -> &str {
52        &self.value
53    }
54
55    /// Apply a value to this FixedField.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// use sip2::FixedField;
61    /// use sip2::spec;
62    /// let mut ff = FixedField::new(&spec::FF_MAX_PRINT_WIDTH, "999").unwrap();
63    /// ff.set_value("000").unwrap();
64    /// assert_eq!(ff.value(), "000");
65    ///
66    /// assert!(ff.set_value("too long").is_err());
67    /// ```
68    pub fn set_value(&mut self, value: &str) -> Result<(), Error> {
69        if value.len() == self.spec.length {
70            self.value = value.to_string();
71            Ok(())
72        } else {
73            Err(Error::FixedFieldLengthError)
74        }
75    }
76
77    /// Translate a FixedField into a string which can be inserted into
78    /// a SIP message.
79    ///
80    /// ```
81    /// use sip2::FixedField;
82    /// use sip2::spec;
83    /// let ff = FixedField::new(&spec::FF_MAX_PRINT_WIDTH, "999").unwrap();
84    /// assert_eq!(ff.to_sip(), "999");
85    /// ```
86    pub fn to_sip(&self) -> String {
87        util::sip_string(&self.value)
88    }
89}
90
91/// SIP Field with code and value.
92///
93/// To support passing field types that are not known at compile time,
94/// store the message code instead of a ref to a well-known spec::Field.
95#[derive(PartialEq, Debug)]
96pub struct Field {
97    /// 2-character code
98    // Note we could link to the static spec::Field here, like
99    // FixedField, instead of storing a copy of the code/label, but that
100    // won't work with fields which are unknown until runtime.
101    code: String,
102
103    /// Field value
104    value: String,
105}
106
107impl Field {
108    /// Create a new Field with the provided code and value.
109    ///
110    /// No limits are place to the length of the code or value, though
111    /// in practice, the code will be 2 characters in length.
112    pub fn new(code: &str, value: &str) -> Self {
113        Field {
114            code: code.to_string(),
115            value: value.to_string(),
116        }
117    }
118
119    /// Get the Field's value.
120    pub fn value(&self) -> &str {
121        &self.value
122    }
123
124    /// Apply a value to this Field.
125    pub fn set_value(&mut self, value: &str) {
126        self.value = value.to_string();
127    }
128
129    /// Get the Fields' code.
130    pub fn code(&self) -> &str {
131        &self.code
132    }
133
134    /// Create a partial SIP string from a field.
135    ///
136    /// String includes the trailing "|" delimiter.
137    ///
138    /// ```
139    /// use sip2::Field;
140    /// use sip2::spec;
141    /// let f = Field::new(spec::F_LOGIN_UID.code, "sip_username");
142    /// assert_eq!(f.to_sip(), "CNsip_username|");
143    /// ```
144    pub fn to_sip(&self) -> String {
145        self.code.to_string() + &util::sip_string(&self.value) + &String::from("|")
146    }
147}
148
149/// SIP message complete with message code, fixed fields, and fields.
150#[derive(PartialEq, Debug)]
151pub struct Message {
152    /// Link to the specification for this message type
153    spec: &'static spec::Message,
154
155    /// List of fixed fields
156    fixed_fields: Vec<FixedField>,
157
158    /// List of fields
159    fields: Vec<Field>,
160}
161
162impl Message {
163    pub fn new(
164        spec: &'static spec::Message,
165        fixed_fields: Vec<FixedField>,
166        fields: Vec<Field>,
167    ) -> Self {
168        let mut msg = Message {
169            spec,
170            fixed_fields,
171            fields,
172        };
173
174        // Sorting fields allows for consistent message layout,
175        // which is useful for debugging purposes.
176        msg.sort_fields();
177
178        msg
179    }
180
181    /// Creates a new message from a set of fixed field values.
182    ///
183    /// Returns an error if the fixed field values provided are not
184    /// the correct length for the specified message type.
185    pub fn from_ff_values(msg_code: &str, fixed_fields: &[&str]) -> Result<Message, Error> {
186        let msg_spec = match spec::Message::from_code(msg_code) {
187            Some(s) => s,
188            None => {
189                log::error!("Unknown message code: {msg_code}");
190                return Err(Error::UnknownMessageError);
191            }
192        };
193
194        let mut ff: Vec<FixedField> = Vec::new();
195
196        for (idx, ff_spec) in msg_spec.fixed_fields.iter().enumerate() {
197            if let Some(v) = fixed_fields.get(idx) {
198                ff.push(FixedField::new(ff_spec, v)?);
199            }
200        }
201
202        if ff.len() != msg_spec.fixed_fields.len() {
203            log::warn!(
204                "SIP message {} contains incorrect number of fixed fields",
205                msg_spec.code
206            );
207            return Err(Error::MessageFormatError);
208        }
209
210        Ok(Message {
211            spec: msg_spec,
212            fixed_fields: ff,
213            fields: Vec::new(),
214        })
215    }
216
217    /// Create a new message from a list of fixed field and field string values.
218    pub fn from_values(
219        msg_code: &str,
220        fixed_fields: &[&str],
221        fields: &[(&str, &str)],
222    ) -> Result<Message, Error> {
223        let mut msg = Message::from_ff_values(msg_code, fixed_fields)?;
224        for field in fields {
225            msg.add_field(field.0, field.1);
226        }
227        Ok(msg)
228    }
229
230    /// Keep fields sorted for consistent to_sip output.
231    fn sort_fields(&mut self) {
232        self.fields.sort_by(|a, b| a.code.cmp(&b.code));
233    }
234
235    /// Adds a Field to a message.
236    ///
237    /// ```
238    /// use sip2::{Message, Field};
239    /// use sip2::spec;
240    ///
241    /// let mut msg = Message::new(
242    ///     &spec::M_LOGIN,
243    ///     vec![],
244    ///     vec![],
245    /// );
246    ///
247    /// msg.add_field("ZZ", "ZZ is a value");
248    /// assert_eq!(msg.fields()[0].code(), "ZZ");
249    /// ```
250    pub fn add_field(&mut self, code: &str, value: &str) {
251        self.fields.push(Field::new(code, value));
252        self.sort_fields();
253    }
254
255    /// Adds a field to a SIP message if the provided value is not None.
256    pub fn maybe_add_field(&mut self, code: &str, value: Option<&str>) {
257        if let Some(v) = value {
258            self.fields.push(Field::new(code, v));
259            self.sort_fields();
260        }
261    }
262
263    /// Remove a field by its code.  If 'all' is true, remove all occurrences.
264    pub fn remove_field(&mut self, code: &str, all: bool) -> usize {
265        let mut count: usize = 0;
266
267        loop {
268            let pos = match self.fields.iter().position(|f| f.code().eq(code)) {
269                Some(p) => p,
270                None => return count, // got them all
271            };
272
273            self.fields.remove(pos);
274
275            count += 1;
276
277            if !all {
278                return count;
279            }
280        }
281    }
282
283    /// Return the first value with the specified field code.
284    pub fn get_field_value(&self, code: &str) -> Option<&str> {
285        if let Some(f) = self.fields().iter().find(|f| f.code() == code) {
286            Some(f.value.as_str())
287        } else {
288            None
289        }
290    }
291
292    /// Returns our message spec.
293    pub fn spec(&self) -> &'static spec::Message {
294        self.spec
295    }
296
297    /// Ref to our list of fields.
298    pub fn fields(&self) -> &Vec<Field> {
299        &self.fields
300    }
301
302    /// Mut ref to our list of fields
303    pub fn fields_mut(&mut self) -> &mut Vec<Field> {
304        &mut self.fields
305    }
306
307    /// Ref to our list of fixed fields
308    pub fn fixed_fields(&self) -> &Vec<FixedField> {
309        &self.fixed_fields
310    }
311
312    /// Mut ref to our list of fixed fields
313    pub fn fixed_fields_mut(&mut self) -> &mut Vec<FixedField> {
314        &mut self.fixed_fields
315    }
316
317    /// Create a SIP string of a message.
318    ///
319    /// ```
320    /// use sip2::{Message, Field, FixedField};
321    /// use sip2::spec;
322    ///
323    /// let msg = Message::new(
324    ///     &spec::M_LOGIN,
325    ///     vec![
326    ///         FixedField::new(&spec::FF_UID_ALGO, "0").unwrap(),
327    ///         FixedField::new(&spec::FF_PWD_ALGO, "0").unwrap(),
328    ///     ],
329    ///     vec![
330    ///         Field::new(spec::F_LOGIN_UID.code, "sip_username"),
331    ///         Field::new(spec::F_LOGIN_PWD.code, "sip_password"),
332    ///     ]
333    /// );
334    ///
335    /// assert_eq!(msg.to_sip(), "9300CNsip_username|COsip_password|");
336    /// ```
337    pub fn to_sip(&self) -> String {
338        let mut s = self.spec.code.to_string();
339
340        for ff in self.fixed_fields.iter() {
341            s.push_str(&ff.to_sip());
342        }
343
344        for f in self.fields.iter() {
345            s.push_str(&f.to_sip());
346        }
347
348        s
349    }
350
351    /// Same as to_sip() but replaces the patron password 'AD' value
352    /// with redacted text.
353    ///
354    /// Useful for logging.
355    pub fn to_sip_redacted(&self) -> String {
356        let mut s = self.spec.code.to_string();
357
358        for ff in self.fixed_fields.iter() {
359            s.push_str(&ff.to_sip());
360        }
361
362        for f in self.fields.iter() {
363            if f.code() == spec::F_PATRON_PWD.code {
364                s += f.code();
365                s += PASSWORD_REDACTED;
366                s += "|";
367            } else {
368                s.push_str(&f.to_sip());
369            }
370        }
371
372        s
373    }
374
375    /// Turns a SIP string into a Message
376    ///
377    /// Assumes the trailing message terminator character has been removed.
378    ///
379    /// Message types and Fixed Field types must be known in advance
380    /// (see sip2::spec), but Field's do not necessarily have to match
381    /// a known spec::Field.  Any value of 3 or more characters will be
382    /// treated as a valid field.
383    ///
384    /// ```
385    /// use sip2::{Message, Field, FixedField};
386    /// let sip_text = "9300CNsip_username|COsip_password|";
387    /// let msg = Message::from_sip(sip_text).unwrap();
388    /// assert_eq!(msg.spec().code, "93");
389    /// assert_eq!(msg.fields()[0].code(), "CN");
390    /// assert_eq!(msg.fields()[1].value(), "sip_password");
391    /// ```
392    pub fn from_sip(text: &str) -> Result<Message, Error> {
393        if text.len() < 2 {
394            log::warn!("SIP message is incomplete: {text}");
395            return Err(Error::MessageFormatError);
396        }
397
398        let msg_spec = match spec::Message::from_code(&text[0..2]) {
399            Some(m) => m,
400            None => {
401                // Message spec must match a known value.
402                error!("Unknown message type: {}", &text[0..2]);
403                return Err(Error::MessageFormatError);
404            }
405        };
406
407        let mut msg = Message {
408            spec: msg_spec,
409            fixed_fields: vec![],
410            fields: vec![],
411        };
412
413        // Remove the message code
414        let mut msg_text = &text[2..];
415
416        for ff_spec in msg_spec.fixed_fields.iter() {
417            if msg_text.len() < ff_spec.length {
418                // Fixed Fields must match known values.
419
420                warn!(
421                    "Message has invalid fixed field: {} : {}",
422                    ff_spec.label, msg_text
423                );
424                return Err(Error::MessageFormatError);
425            }
426
427            let value = &msg_text[0..ff_spec.length];
428            msg_text = &msg_text[ff_spec.length..];
429
430            // Length of the value string is confirmed above.
431            msg.fixed_fields
432                .push(FixedField::new(ff_spec, value).unwrap());
433        }
434
435        // Not all messages have fixed fields and/or fields
436        if msg_text.is_empty() {
437            return Ok(msg);
438        }
439
440        // Free-text fields are separated by "|" characters.
441        for part in msg_text.split('|') {
442            if part.len() > 1 {
443                let val = match part.len() > 2 {
444                    true => &part[2..],
445                    _ => "",
446                };
447                msg.fields.push(Field::new(&part[0..2], val));
448            }
449        }
450
451        Ok(msg)
452    }
453}
454
455/// Message display support for logging / debugging.
456impl fmt::Display for Message {
457    /// Format a message into a human-readable list of field labels
458    /// and values.
459    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
460        writeln!(f, "{} {}", self.spec.code, self.spec.label)?;
461
462        for ff in self.fixed_fields.iter() {
463            writeln!(f, "   {:.<35} {}", ff.spec.label, ff.value)?;
464        }
465
466        for field in self.fields.iter() {
467            if let Some(spec) = spec::Field::from_code(&field.code) {
468                writeln!(f, "{} {:.<35} {}", spec.code, spec.label, field.value)?;
469            } else {
470                writeln!(f, "{} {:.<35} {}", field.code, "custom", field.value)?;
471            }
472        }
473
474        write!(f, "")
475    }
476}