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}