extfg_sigma/
lib.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3
4use bytes::{Bytes, BytesMut};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::util::*;
9
10#[macro_use]
11mod util;
12
13#[cfg(feature = "codec")]
14pub mod codec;
15
16#[derive(Debug, thiserror::Error, PartialEq, Clone)]
17pub enum Error {
18    #[error("{0}")]
19    Bounds(String),
20    #[error("Incorrect tag: {0}")]
21    IncorrectTag(String),
22    #[error("Incorrect field '{field_name}', should be {should_be}")]
23    IncorrectFieldData {
24        field_name: String,
25        should_be: String,
26    },
27    #[error("Missing field '{0}'")]
28    MissingField(String),
29    #[error("{0}")]
30    IncorrectData(String),
31}
32
33impl Error {
34    fn incorrect_field_data(field_name: &str, should_be: &str) -> Self {
35        Self::IncorrectFieldData {
36            field_name: field_name.into(),
37            should_be: should_be.into(),
38        }
39    }
40}
41
42fn validate_mti(s: &str) -> Result<(), Error> {
43    let b = s.as_bytes();
44    if b.len() != 4 {
45        return Err(Error::incorrect_field_data(
46            "MTI",
47            "4 digit number (string)",
48        ));
49    }
50    for x in b.iter() {
51        if !x.is_ascii_digit() {
52            return Err(Error::incorrect_field_data(
53                "MTI",
54                "4 digit number (string)",
55            ));
56        }
57    }
58    Ok(())
59}
60
61fn validate_source(s: &str) -> Result<(), Error> {
62    if s.len() != 1 {
63        return Err(Error::incorrect_field_data("SRC", "single ASCII char"));
64    }
65    Ok(())
66}
67
68fn validate_saf(s: &str) -> Result<(), Error> {
69    match s {
70        "Y" | "N" => Ok(()),
71        _ => Err(Error::incorrect_field_data("SAF", "char Y or N")),
72    }
73}
74
75#[derive(Debug, PartialEq, Clone)]
76pub enum IsoFieldData {
77    String(String),
78    Raw(Vec<u8>),
79}
80
81impl IsoFieldData {
82    pub fn to_string_lossy(self) -> String {
83        match self {
84            Self::String(v) => v,
85            Self::Raw(v) => String::from_utf8(v)
86                .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned()),
87        }
88    }
89
90    pub fn to_cow_str_lossy<'a, 'b: 'a>(&'b self) -> Cow<'a, str> {
91        match self {
92            Self::String(ref v) => Cow::Borrowed(v),
93            Self::Raw(ref v) => String::from_utf8_lossy(v),
94        }
95    }
96
97    pub fn as_bytes(&self) -> &[u8] {
98        match self {
99            IsoFieldData::String(x) => x.as_bytes(),
100            IsoFieldData::Raw(x) => x,
101        }
102    }
103
104    pub fn from_bytes(data: Bytes) -> Self {
105        let vec = data.to_vec();
106        String::from_utf8(vec).map_or_else(|err| Self::Raw(err.into_bytes()), Self::String)
107    }
108}
109
110impl From<String> for IsoFieldData {
111    fn from(v: String) -> Self {
112        Self::String(v)
113    }
114}
115
116impl From<&str> for IsoFieldData {
117    fn from(v: &str) -> Self {
118        Self::String(v.into())
119    }
120}
121
122impl From<Vec<u8>> for IsoFieldData {
123    fn from(v: Vec<u8>) -> Self {
124        Self::Raw(v)
125    }
126}
127
128impl From<&[u8]> for IsoFieldData {
129    fn from(v: &[u8]) -> Self {
130        Self::Raw(Vec::from(v))
131    }
132}
133
134impl<T: AsRef<[u8]> + ?Sized> PartialEq<T> for IsoFieldData {
135    fn eq(&self, other: &T) -> bool {
136        self.as_bytes() == other.as_ref()
137    }
138}
139
140#[derive(Debug, PartialEq, Clone)]
141pub struct SigmaRequest {
142    saf: String,
143    source: String,
144    mti: String,
145    pub auth_serno: u64,
146    pub tags: BTreeMap<u16, String>,
147    pub iso_fields: BTreeMap<u16, IsoFieldData>,
148    pub iso_subfields: BTreeMap<(u16, u8), IsoFieldData>,
149}
150
151impl SigmaRequest {
152    pub fn new(saf: &str, source: &str, mti: &str, auth_serno: u64) -> Result<Self, Error> {
153        validate_saf(saf)?;
154        validate_source(source)?;
155        validate_mti(mti)?;
156        Ok(Self {
157            saf: saf.into(),
158            source: source.into(),
159            mti: mti.into(),
160            auth_serno,
161            tags: Default::default(),
162            iso_fields: Default::default(),
163            iso_subfields: Default::default(),
164        })
165    }
166
167    pub fn from_json_value(mut data: Value) -> Result<SigmaRequest, Error> {
168        let data = data
169            .as_object_mut()
170            .ok_or_else(|| Error::IncorrectData("SigmaRequest JSON should be object".into()))?;
171        let mut req = Self::new("N", "X", "0100", 0)?;
172
173        macro_rules! fill_req_field {
174            ($fname:ident, $pname:literal, $comment:literal) => {
175                match data.remove($pname) {
176                    Some(x) => match x.as_str() {
177                        Some(v) => {
178                            req.$fname(v.to_string())?;
179                        }
180                        None => {
181                            return Err(Error::IncorrectFieldData {
182                                field_name: $pname.to_string(),
183                                should_be: $comment.to_string(),
184                            });
185                        }
186                    },
187                    None => {
188                        return Err(Error::MissingField($pname.to_string()));
189                    }
190                }
191            };
192        }
193
194        fill_req_field!(set_saf, "SAF", "String");
195        fill_req_field!(set_source, "SRC", "String");
196        fill_req_field!(set_mti, "MTI", "String");
197        // Authorization serno
198        match data.remove("Serno") {
199            Some(x) => {
200                if let Some(s) = x.as_str() {
201                    req.auth_serno = s.parse::<u64>().map_err(|_| Error::IncorrectFieldData {
202                        field_name: "Serno".into(),
203                        should_be: "integer".into(),
204                    })?;
205                } else if let Some(v) = x.as_u64() {
206                    req.auth_serno = v;
207                } else {
208                    return Err(Error::IncorrectFieldData {
209                        field_name: "Serno".into(),
210                        should_be: "u64 or String with integer".into(),
211                    });
212                }
213            }
214            None => {
215                req.auth_serno = util::gen_random_auth_serno();
216            }
217        }
218
219        for (name, field_data) in data.iter() {
220            let tag = Tag::from_str(name)?;
221            let content = if let Some(x) = field_data.as_str() {
222                x.into()
223            } else if let Some(x) = field_data.as_u64() {
224                format!("{}", x)
225            } else {
226                return Err(Error::IncorrectFieldData {
227                    field_name: name.clone(),
228                    should_be: "u64 or String with integer".into(),
229                });
230            };
231            match tag {
232                Tag::Regular(i) => {
233                    req.tags.insert(i, content);
234                }
235                Tag::Iso(i) => {
236                    req.iso_fields.insert(i, content.into());
237                }
238                Tag::IsoSubfield(i, si) => {
239                    req.iso_subfields.insert((i, si), content.into());
240                }
241            }
242        }
243
244        Ok(req)
245    }
246
247    pub fn encode(&self) -> Result<Bytes, Error> {
248        let mut buf = BytesMut::with_capacity(8192);
249        buf.extend_from_slice(b"00000");
250
251        buf.extend_from_slice(self.saf.as_bytes());
252        buf.extend_from_slice(self.source.as_bytes());
253        buf.extend_from_slice(self.mti.as_bytes());
254        if self.auth_serno > 9999999999 {
255            buf.extend_from_slice(&format!("{}", self.auth_serno).as_bytes()[0..10]);
256        } else {
257            buf.extend_from_slice(format!("{:010}", self.auth_serno).as_bytes());
258        }
259
260        for (k, v) in self.tags.iter() {
261            encode_field_to_buf(Tag::Regular(*k), v.as_bytes(), &mut buf)?;
262        }
263
264        for (k, v) in self.iso_fields.iter() {
265            encode_field_to_buf(Tag::Iso(*k), v.as_bytes(), &mut buf)?;
266        }
267
268        for ((k, k1), v) in self.iso_subfields.iter() {
269            encode_field_to_buf(Tag::IsoSubfield(*k, *k1), v.as_bytes(), &mut buf)?;
270        }
271
272        let msg_len = buf.len() - 5;
273        buf[0..5].copy_from_slice(format!("{:05}", msg_len).as_bytes());
274        Ok(buf.freeze())
275    }
276
277    pub fn decode(mut data: Bytes) -> Result<Self, Error> {
278        let mut req = Self::new("N", "X", "0100", 0)?;
279
280        let msg_len = parse_ascii_bytes_lossy!(
281            &bytes_split_to(&mut data, 5)?,
282            usize,
283            Error::incorrect_field_data("message length", "valid integer")
284        )?;
285        let mut data = bytes_split_to(&mut data, msg_len)?;
286
287        req.set_saf(String::from_utf8_lossy(&bytes_split_to(&mut data, 1)?).to_string())?;
288        req.set_source(String::from_utf8_lossy(&bytes_split_to(&mut data, 1)?).to_string())?;
289        req.set_mti(String::from_utf8_lossy(&bytes_split_to(&mut data, 4)?).to_string())?;
290        req.auth_serno = String::from_utf8_lossy(&bytes_split_to(&mut data, 10)?)
291            .trim()
292            .parse::<u64>()
293            .map_err(|_| Error::IncorrectFieldData {
294                field_name: "Serno".into(),
295                should_be: "u64".into(),
296            })?;
297
298        while !data.is_empty() {
299            let (tag, data_src) = decode_field_from_cursor(&mut data)?;
300
301            match tag {
302                Tag::Regular(i) => {
303                    req.tags
304                        .insert(i, String::from_utf8_lossy(&data_src).into_owned());
305                }
306                Tag::Iso(i) => {
307                    req.iso_fields.insert(i, IsoFieldData::from_bytes(data_src));
308                }
309                Tag::IsoSubfield(i, si) => {
310                    req.iso_subfields
311                        .insert((i, si), IsoFieldData::from_bytes(data_src));
312                }
313            }
314        }
315
316        Ok(req)
317    }
318
319    pub fn saf(&self) -> &str {
320        &self.saf
321    }
322
323    pub fn set_saf(&mut self, v: String) -> Result<(), Error> {
324        validate_saf(&v)?;
325        self.saf = v;
326        Ok(())
327    }
328
329    pub fn source(&self) -> &str {
330        &self.source
331    }
332
333    pub fn set_source(&mut self, v: String) -> Result<(), Error> {
334        validate_source(&v)?;
335        self.source = v;
336        Ok(())
337    }
338
339    pub fn mti(&self) -> &str {
340        &self.mti
341    }
342
343    pub fn set_mti(&mut self, v: String) -> Result<(), Error> {
344        validate_mti(&v)?;
345        self.mti = v;
346        Ok(())
347    }
348}
349
350#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
351pub struct FeeData {
352    pub reason: u16,
353    pub currency: u16,
354    pub amount: u64,
355}
356
357impl FeeData {
358    pub fn from_slice(data: &[u8]) -> Result<Self, Error> {
359        if data.len() >= 8 {
360            // "\x00\x32\x00\x00\x108116978300"
361            let reason = parse_ascii_bytes_lossy!(
362                &data[0..4],
363                u16,
364                Error::incorrect_field_data("FeeData.reason", "valid integer")
365            )?;
366            let currency = parse_ascii_bytes_lossy!(
367                &data[4..7],
368                u16,
369                Error::incorrect_field_data("FeeData.currency", "valid integer")
370            )?;
371            let amount = parse_ascii_bytes_lossy!(
372                &data[7..],
373                u64,
374                Error::incorrect_field_data("FeeData.amount", "valid integer")
375            )?;
376            Ok(Self {
377                reason,
378                currency,
379                amount,
380            })
381        } else {
382            Err(Error::IncorrectData(
383                "FeeData slice should be longer than 8 bytes".into(),
384            ))
385        }
386    }
387
388    pub fn encode(&self) -> Result<Bytes, Error> {
389        let mut buf = BytesMut::new();
390
391        if self.reason > 9999 {
392            return Err(Error::Bounds(
393                "FeeData.reason should be less or equal 9999".into(),
394            ));
395        }
396        buf.extend_from_slice(format!("{:<04}", self.reason).as_bytes());
397
398        if self.currency > 999 {
399            return Err(Error::Bounds(
400                "FeeData.reason should be less or equal 999".into(),
401            ));
402        }
403        buf.extend_from_slice(format!("{:<03}", self.currency).as_bytes());
404
405        buf.extend_from_slice(format!("{}", self.amount).as_bytes());
406
407        Ok(buf.freeze())
408    }
409}
410
411#[derive(Deserialize, Serialize, Debug, Clone)]
412pub struct SigmaResponse {
413    mti: String,
414    pub auth_serno: u64,
415    pub reason: u32,
416    #[serde(default, skip_serializing_if = "Vec::is_empty")]
417    pub fees: Vec<FeeData>,
418    #[serde(default, skip_serializing_if = "Option::is_none")]
419    pub adata: Option<String>,
420    #[serde(default, skip_serializing_if = "Option::is_none")]
421    pub supdata: Option<String>,
422    #[serde(default, skip_serializing_if = "Option::is_none")]
423    pub xri: Option<String>,
424}
425
426impl SigmaResponse {
427    pub fn new(mti: &str, auth_serno: u64, reason: u32) -> Result<Self, Error> {
428        validate_mti(mti)?;
429        Ok(Self {
430            mti: mti.into(),
431            auth_serno,
432            reason,
433            fees: Vec::new(),
434            adata: None,
435            supdata: None,
436            xri: None,
437        })
438    }
439
440    pub fn decode(mut data: Bytes) -> Result<Self, Error> {
441        let mut resp = Self::new("0100", 0, 0)?;
442
443        let msg_len = parse_ascii_bytes_lossy!(
444            &bytes_split_to(&mut data, 5)?,
445            usize,
446            Error::incorrect_field_data("message length", "valid integer")
447        )?;
448        let mut data = bytes_split_to(&mut data, msg_len)?;
449
450        resp.set_mti(String::from_utf8_lossy(&bytes_split_to(&mut data, 4)?).to_string())?;
451        resp.auth_serno = String::from_utf8_lossy(&bytes_split_to(&mut data, 10)?)
452            .trim()
453            .parse::<u64>()
454            .map_err(|_| Error::IncorrectFieldData {
455                field_name: "Serno".into(),
456                should_be: "u64".into(),
457            })?;
458
459        while !data.is_empty() {
460            /*
461             *  |
462             *  |  T  | \x00 | \x31 | \x00 | \x00 | \x04 |  8  |  1  |  0  |  0  |
463             *        |             |      |             |                       |
464             *        |__ tag id ___|      |tag data len |_______ data __________|
465             */
466            let (tag, data_src) = decode_field_from_cursor(&mut data)?;
467
468            match tag {
469                Tag::Regular(31) => {
470                    resp.reason = parse_ascii_bytes_lossy!(
471                        &data_src,
472                        u32,
473                        Error::incorrect_field_data("reason", "shloud be u32")
474                    )?;
475                }
476                Tag::Regular(32) => {
477                    resp.fees.push(FeeData::from_slice(&data_src)?);
478                }
479                Tag::Regular(33) => resp.xri = Some(String::from_utf8_lossy(&data_src).to_string()),
480                Tag::Regular(48) => {
481                    resp.adata = Some(String::from_utf8_lossy(&data_src).to_string());
482                }
483                Tag::Regular(50) => {
484                    resp.supdata = Some(String::from_utf8_lossy(&data_src).to_string());
485                }
486                _ => {}
487            }
488        }
489
490        Ok(resp)
491    }
492
493    pub fn mti(&self) -> &str {
494        &self.mti
495    }
496
497    pub fn set_mti(&mut self, v: String) -> Result<(), Error> {
498        validate_mti(&v)?;
499        self.mti = v;
500        Ok(())
501    }
502
503    pub fn encode(&self) -> Result<Bytes, Error> {
504        let mut buf = BytesMut::with_capacity(8192);
505        buf.extend_from_slice(b"00000");
506
507        buf.extend_from_slice(self.mti.as_bytes());
508        if self.auth_serno > 9999999999 {
509            buf.extend_from_slice(&format!("{}", self.auth_serno).as_bytes()[0..10]);
510        } else {
511            buf.extend_from_slice(format!("{:010}", self.auth_serno).as_bytes());
512        }
513        encode_field_to_buf(
514            Tag::Regular(31),
515            format!("{}", self.reason).as_bytes(),
516            &mut buf,
517        )?;
518        for i in &self.fees {
519            encode_field_to_buf(Tag::Regular(32), &i.encode()?, &mut buf)?;
520        }
521        if let Some(ref adata) = self.adata {
522            encode_field_to_buf(Tag::Regular(48), adata.as_bytes(), &mut buf)?;
523        }
524        if let Some(ref xri) = self.xri {
525            encode_field_to_buf(Tag::Regular(33), xri.as_bytes(), &mut buf)?;
526        }
527
528        let msg_len = buf.len() - 5;
529        buf[0..5].copy_from_slice(format!("{:05}", msg_len).as_bytes());
530        Ok(buf.freeze())
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    #[test]
539    fn ok() {
540        let payload = r#"{
541            "SAF": "Y",
542            "SRC": "M",
543            "MTI": "0200",
544            "Serno": 6007040979,
545            "T0000": 2371492071643,
546            "T0001": "C",
547            "T0002": 643,
548            "T0003": "000100000000",
549            "T0004": 978,
550            "T0005": "000300000000",
551            "T0006": "OPS6",
552            "T0007": 19,
553            "T0008": 643,
554            "T0009": 3102,
555            "T0010": 3104,
556            "T0011": 2,
557            "T0014": "IDDQD Bank",
558            "T0016": 74707182,
559            "T0018": "Y",
560            "T0022": "000000000010",
561            "i000": "0100",
562            "i002": "555544******1111",
563            "i003": "500000",
564            "i004": "000100000000",
565            "i006": "000100000000",
566            "i007": "0629151748",
567            "i011": "100250",
568            "i012": "181748",
569            "i013": "0629",
570            "i018": "0000",
571            "i022": "0000",
572            "i025": "02",
573            "i032": "010455",
574            "i037": "002595100250",
575            "i041": 990,
576            "i042": "DCZ1",
577            "i043": "IDDQD Bank.                         GE",
578            "i048": "USRDT|2595100250",
579            "i049": 643,
580            "i051": 643,
581            "i060": 3,
582            "i101": 91926242,
583            "i102": 2371492071643
584        }"#;
585
586        let r: SigmaRequest =
587            SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).unwrap();
588        assert_eq!(r.saf, "Y");
589        assert_eq!(r.source, "M");
590        assert_eq!(r.mti, "0200");
591        assert_eq!(r.auth_serno, 6007040979);
592        assert_eq!(r.tags.get(&0).unwrap(), "2371492071643");
593        assert_eq!(r.tags.get(&1).unwrap(), "C");
594        assert_eq!(r.tags.get(&2).unwrap(), "643");
595        assert_eq!(r.tags.get(&3).unwrap(), "000100000000");
596        assert_eq!(r.tags.get(&4).unwrap(), "978");
597        assert_eq!(r.tags.get(&5).unwrap(), "000300000000");
598        assert_eq!(r.tags.get(&6).unwrap(), "OPS6");
599        assert_eq!(r.tags.get(&7).unwrap(), "19");
600        assert_eq!(r.tags.get(&8).unwrap(), "643");
601        assert_eq!(r.tags.get(&9).unwrap(), "3102");
602        assert_eq!(r.tags.get(&10).unwrap(), "3104");
603        assert_eq!(r.tags.get(&11).unwrap(), "2");
604
605        if r.tags.get(&12).is_some() {
606            unreachable!();
607        }
608
609        if r.tags.get(&13).is_some() {
610            unreachable!();
611        }
612
613        assert_eq!(r.tags.get(&14).unwrap(), "IDDQD Bank");
614
615        if r.tags.get(&15).is_some() {
616            unreachable!();
617        }
618
619        assert_eq!(r.tags.get(&16).unwrap(), "74707182");
620        if r.tags.get(&17).is_some() {
621            unreachable!();
622        }
623        assert_eq!(r.tags.get(&18).unwrap(), "Y");
624        assert_eq!(r.tags.get(&22).unwrap(), "000000000010");
625
626        assert_eq!(r.iso_fields.get(&0).unwrap(), "0100");
627
628        if r.iso_fields.get(&1).is_some() {
629            unreachable!();
630        }
631
632        assert_eq!(r.iso_fields.get(&2).unwrap(), "555544******1111");
633        assert_eq!(r.iso_fields.get(&3).unwrap(), "500000");
634        assert_eq!(r.iso_fields.get(&4).unwrap(), "000100000000");
635        assert_eq!(r.iso_fields.get(&6).unwrap(), "000100000000");
636        assert_eq!(r.iso_fields.get(&7).unwrap(), "0629151748");
637        assert_eq!(r.iso_fields.get(&11).unwrap(), "100250");
638        assert_eq!(r.iso_fields.get(&12).unwrap(), "181748");
639        assert_eq!(r.iso_fields.get(&13).unwrap(), "0629");
640        assert_eq!(r.iso_fields.get(&18).unwrap(), "0000");
641        assert_eq!(r.iso_fields.get(&22).unwrap(), "0000");
642        assert_eq!(r.iso_fields.get(&25).unwrap(), "02");
643        assert_eq!(r.iso_fields.get(&32).unwrap(), "010455");
644        assert_eq!(r.iso_fields.get(&37).unwrap(), "002595100250");
645        assert_eq!(r.iso_fields.get(&41).unwrap(), "990");
646        assert_eq!(r.iso_fields.get(&42).unwrap(), "DCZ1");
647        assert_eq!(
648            r.iso_fields.get(&43).unwrap(),
649            "IDDQD Bank.                         GE"
650        );
651        assert_eq!(r.iso_fields.get(&48).unwrap(), "USRDT|2595100250");
652        assert_eq!(r.iso_fields.get(&49).unwrap(), "643");
653        assert_eq!(r.iso_fields.get(&51).unwrap(), "643");
654        assert_eq!(r.iso_fields.get(&60).unwrap(), "3");
655        assert_eq!(r.iso_fields.get(&101).unwrap(), "91926242");
656        assert_eq!(r.iso_fields.get(&102).unwrap(), "2371492071643");
657    }
658
659    #[test]
660    fn serno_as_string() {
661        let payload = r#"{
662            "SAF": "Y",
663            "SRC": "M",
664            "MTI": "0200",
665            "Serno": "0600704097",
666            "T0000": 2371492071643,
667            "T0001": "C",
668            "T0002": 643,
669            "T0003": "000100000000",
670            "T0004": 978,
671            "T0005": "000300000000",
672            "T0006": "OPS6",
673            "T0007": 19,
674            "T0008": 643,
675            "T0009": 3102,
676            "T0010": 3104,
677            "T0011": 2,
678            "T0014": "IDDQD Bank",
679            "T0016": 74707182,
680            "T0018": "Y",
681            "T0022": "000000000010",
682            "T0023": "X-Request-Id",
683            "i000": "0100",
684            "i002": "555544******1111",
685            "i003": "500000",
686            "i004": "000100000000",
687            "i006": "000100000000",
688            "i007": "0629151748",
689            "i011": "100250",
690            "i012": "181748",
691            "i013": "0629",
692            "i018": "0000",
693            "i022": "0000",
694            "i025": "02",
695            "i032": "010455",
696            "i037": "002595100250",
697            "i041": 990,
698            "i042": "DCZ1",
699            "i043": "IDDQD Bank.                         GE",
700            "i048": "USRDT|2595100250",
701            "i049": 643,
702            "i051": 643,
703            "i060": 3,
704            "i101": 91926242,
705            "i102": 2371492071643
706        }"#;
707
708        let r: SigmaRequest =
709            SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).unwrap();
710        assert_eq!(r.saf, "Y");
711        assert_eq!(r.source, "M");
712        assert_eq!(r.mti, "0200");
713        assert_eq!(r.auth_serno, 600704097);
714        assert_eq!(r.tags.get(&0).unwrap(), "2371492071643");
715        assert_eq!(r.tags.get(&1).unwrap(), "C");
716        assert_eq!(r.tags.get(&2).unwrap(), "643");
717        assert_eq!(r.tags.get(&3).unwrap(), "000100000000");
718        assert_eq!(r.tags.get(&4).unwrap(), "978");
719        assert_eq!(r.tags.get(&5).unwrap(), "000300000000");
720        assert_eq!(r.tags.get(&6).unwrap(), "OPS6");
721        assert_eq!(r.tags.get(&7).unwrap(), "19");
722        assert_eq!(r.tags.get(&8).unwrap(), "643");
723        assert_eq!(r.tags.get(&9).unwrap(), "3102");
724        assert_eq!(r.tags.get(&10).unwrap(), "3104");
725        assert_eq!(r.tags.get(&11).unwrap(), "2");
726
727        if r.tags.get(&12).is_some() {
728            unreachable!();
729        }
730
731        if r.tags.get(&13).is_some() {
732            unreachable!();
733        }
734
735        assert_eq!(r.tags.get(&14).unwrap(), "IDDQD Bank");
736
737        if r.tags.get(&15).is_some() {
738            unreachable!();
739        }
740
741        assert_eq!(r.tags.get(&16).unwrap(), "74707182");
742        if r.tags.get(&17).is_some() {
743            unreachable!();
744        }
745        assert_eq!(r.tags.get(&18).unwrap(), "Y");
746        assert_eq!(r.tags.get(&22).unwrap(), "000000000010");
747        assert_eq!(r.tags.get(&23).unwrap(), "X-Request-Id");
748
749        assert_eq!(r.iso_fields.get(&0).unwrap(), "0100");
750
751        if r.iso_fields.get(&1).is_some() {
752            unreachable!();
753        }
754
755        assert_eq!(r.iso_fields.get(&2).unwrap(), "555544******1111");
756        assert_eq!(r.iso_fields.get(&3).unwrap(), "500000");
757        assert_eq!(r.iso_fields.get(&4).unwrap(), "000100000000");
758        assert_eq!(r.iso_fields.get(&6).unwrap(), "000100000000");
759        assert_eq!(r.iso_fields.get(&7).unwrap(), "0629151748");
760        assert_eq!(r.iso_fields.get(&11).unwrap(), "100250");
761        assert_eq!(r.iso_fields.get(&12).unwrap(), "181748");
762        assert_eq!(r.iso_fields.get(&13).unwrap(), "0629");
763        assert_eq!(r.iso_fields.get(&18).unwrap(), "0000");
764        assert_eq!(r.iso_fields.get(&22).unwrap(), "0000");
765        assert_eq!(r.iso_fields.get(&25).unwrap(), "02");
766        assert_eq!(r.iso_fields.get(&32).unwrap(), "010455");
767        assert_eq!(r.iso_fields.get(&37).unwrap(), "002595100250");
768        assert_eq!(r.iso_fields.get(&41).unwrap(), "990");
769        assert_eq!(r.iso_fields.get(&42).unwrap(), "DCZ1");
770        assert_eq!(
771            r.iso_fields.get(&43).unwrap(),
772            "IDDQD Bank.                         GE"
773        );
774        assert_eq!(r.iso_fields.get(&48).unwrap(), "USRDT|2595100250");
775        assert_eq!(r.iso_fields.get(&49).unwrap(), "643");
776        assert_eq!(r.iso_fields.get(&51).unwrap(), "643");
777        assert_eq!(r.iso_fields.get(&60).unwrap(), "3");
778        assert_eq!(r.iso_fields.get(&101).unwrap(), "91926242");
779        assert_eq!(r.iso_fields.get(&102).unwrap(), "2371492071643");
780    }
781
782    #[test]
783    fn missing_saf() {
784        let payload = r#"{
785            "SRC": "M",
786            "MTI": "0200"
787        }"#;
788
789        if SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).is_ok() {
790            unreachable!("Should not return Ok if mandatory field is missing");
791        }
792    }
793
794    #[test]
795    fn invalid_saf() {
796        let payload = r#"{
797        	"SAF": 1234,
798            "SRC": "M",
799            "MTI": "0200"
800        }"#;
801
802        if SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).is_ok() {
803            unreachable!("Should not return Ok if the filed has invalid format");
804        }
805    }
806
807    #[test]
808    fn missing_source() {
809        let payload = r#"{
810        	"SAF": "N",
811            "MTI": "0200"
812        }"#;
813
814        if SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).is_ok() {
815            unreachable!("Should not return Ok if mandatory field is missing");
816        }
817    }
818
819    #[test]
820    fn invalid_source() {
821        let payload = r#"{
822        	"SAF": "N",
823            "SRC": 929292,
824            "MTI": "0200"
825        }"#;
826
827        if SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).is_ok() {
828            unreachable!("Should not return Ok if the filed has invalid format");
829        }
830    }
831
832    #[test]
833    fn missing_mti() {
834        let payload = r#"{
835        	"SAF": "N",
836        	"SRC": "O"
837        }"#;
838
839        if SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).is_ok() {
840            unreachable!("Should not return Ok if mandatory field is missing");
841        }
842    }
843
844    #[test]
845    fn invalid_mti() {
846        let payload = r#"{
847        	"SAF": "N",
848            "SRC": "O",
849            "MTI": 1200
850        }"#;
851
852        if SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).is_ok() {
853            unreachable!("Should not return Ok if the filed has invalid format");
854        }
855    }
856
857    #[test]
858    fn generating_auth_serno() {
859        let payload = r#"{
860                "SAF": "Y",
861                "SRC": "M",
862                "MTI": "0200",
863                "T0000": "02371492071643"
864            }"#;
865
866        let r: SigmaRequest =
867            SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).unwrap();
868        assert!(
869            r.auth_serno > 0,
870            "Should generate authorization serno if the field is missing"
871        );
872    }
873
874    #[test]
875    fn encode_generated_auth_serno() {
876        let payload = r#"{
877                "SAF": "Y",
878                "SRC": "M",
879                "MTI": "0201",
880                "Serno": 7877706965687192023
881            }"#;
882
883        let r: SigmaRequest =
884            SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).unwrap();
885        let serialized = r.encode().unwrap();
886        assert_eq!(
887            serialized,
888            b"00016YM02017877706965"[..],
889            "Original auth serno should be trimmed to 10 bytes"
890        );
891    }
892
893    #[test]
894    fn encode_sigma_request() {
895        let payload = r#"{
896                "SAF": "Y",
897                "SRC": "M",
898                "MTI": "0200",
899                "Serno": 6007040979,
900                "T0000": 2371492071643,
901                "T0001": "C",
902                "T0002": 643,
903                "T0003": "000100000000",
904                "T0004": 978,
905                "T0005": "000300000000",
906                "T0006": "OPS6",
907                "T0007": 19,
908                "T0008": 643,
909                "T0009": 3102,
910                "T0010": 3104,
911                "T0011": 2,
912                "T0014": "IDDQD Bank",
913                "T0016": 74707182,
914                "T0018": "Y",
915                "T0022": "000000000010",
916                "i000": "0100",
917                "i002": "555544******1111",
918                "i003": "500000",
919                "i004": "000100000000",
920                "i006": "000100000000",
921                "i007": "0629151748",
922                "i011": "100250",
923                "i012": "181748",
924                "i013": "0629",
925                "i018": "0000",
926                "i022": "0000",
927                "i025": "02",
928                "i032": "010455",
929                "i037": "002595100250",
930                "i041": 990,
931                "i042": "DCZ1",
932                "i043": "IDDQD Bank.                         GE",
933                "i048": "USRDT|2595100250",
934                "i049": 643,
935                "i051": 643,
936                "i060": 3,
937                "i101": 91926242,
938                "i102": 2371492071643
939            }"#;
940
941        let r: SigmaRequest =
942            SigmaRequest::from_json_value(serde_json::from_str(payload).unwrap()).unwrap();
943        let serialized = r.encode().unwrap();
944        assert_eq!(
945            serialized,
946            b"00536YM02006007040979T\x00\x00\x00\x00\x132371492071643T\x00\x01\x00\x00\x01CT\x00\x02\x00\x00\x03643T\x00\x03\x00\x00\x12000100000000T\x00\x04\x00\x00\x03978T\x00\x05\x00\x00\x12000300000000T\x00\x06\x00\x00\x04OPS6T\x00\x07\x00\x00\x0219T\x00\x08\x00\x00\x03643T\x00\t\x00\x00\x043102T\x00\x10\x00\x00\x043104T\x00\x11\x00\x00\x012T\x00\x14\x00\x00\x10IDDQD BankT\x00\x16\x00\x00\x0874707182T\x00\x18\x00\x00\x01YT\x00\x22\x00\x00\x12000000000010I\x00\x00\x00\x00\x040100I\x00\x02\x00\x00\x16555544******1111I\x00\x03\x00\x00\x06500000I\x00\x04\x00\x00\x12000100000000I\x00\x06\x00\x00\x12000100000000I\x00\x07\x00\x00\x100629151748I\x00\x11\x00\x00\x06100250I\x00\x12\x00\x00\x06181748I\x00\x13\x00\x00\x040629I\x00\x18\x00\x00\x040000I\x00\"\x00\x00\x040000I\x00%\x00\x00\x0202I\x002\x00\x00\x06010455I\x007\x00\x00\x12002595100250I\x00A\x00\x00\x03990I\x00B\x00\x00\x04DCZ1I\x00C\x00\x008IDDQD Bank.                         GEI\x00H\x00\x00\x16USRDT|2595100250I\x00I\x00\x00\x03643I\x00Q\x00\x00\x03643I\x00`\x00\x00\x013I\x01\x01\x00\x00\x0891926242I\x01\x02\x00\x00\x132371492071643"[..]
947        );
948    }
949
950    #[test]
951    fn decode_sigma_request() {
952        let src = Bytes::from_static(b"00545YM02006007040979T\x00\x00\x00\x00\x132371492071643T\x00\x01\x00\x00\x01CT\x00\x02\x00\x00\x03643T\x00\x03\x00\x00\x12000100000000T\x00\x04\x00\x00\x03978T\x00\x05\x00\x00\x12000300000000T\x00\x06\x00\x00\x04OPS6T\x00\x07\x00\x00\x0219T\x00\x08\x00\x00\x03643T\x00\t\x00\x00\x043102T\x00\x10\x00\x00\x043104T\x00\x11\x00\x00\x012T\x00\x14\x00\x00\x10IDDQD BankT\x00\x16\x00\x00\x0874707182T\x00\x18\x00\x00\x01YT\x00\x22\x00\x00\x12000000000010T\x00\x50\x00\x00\x03123I\x00\x00\x00\x00\x040100I\x00\x02\x00\x00\x16555544******1111I\x00\x03\x00\x00\x06500000I\x00\x04\x00\x00\x12000100000000I\x00\x06\x00\x00\x12000100000000I\x00\x07\x00\x00\x100629151748I\x00\x11\x00\x00\x06100250I\x00\x12\x00\x00\x06181748I\x00\x13\x00\x00\x040629I\x00\x18\x00\x00\x040000I\x00\"\x00\x00\x040000I\x00%\x00\x00\x0202I\x002\x00\x00\x06010455I\x007\x00\x00\x12002595100250I\x00A\x00\x00\x03990I\x00B\x00\x00\x04DCZ1I\x00C\x00\x008IDDQD Bank.                         GEI\x00H\x00\x00\x16USRDT|2595100250I\x00I\x00\x00\x03643I\x00Q\x00\x00\x03643I\x00`\x00\x00\x013I\x01\x01\x00\x00\x0891926242I\x01\x02\x00\x00\x132371492071643");
953        let json = r#"{
954                "SAF": "Y",
955                "SRC": "M",
956                "MTI": "0200",
957                "Serno": 6007040979,
958                "T0000": 2371492071643,
959                "T0001": "C",
960                "T0002": 643,
961                "T0003": "000100000000",
962                "T0004": 978,
963                "T0005": "000300000000",
964                "T0006": "OPS6",
965                "T0007": 19,
966                "T0008": 643,
967                "T0009": 3102,
968                "T0010": 3104,
969                "T0011": 2,
970                "T0014": "IDDQD Bank",
971                "T0016": 74707182,
972                "T0018": "Y",
973                "T0022": "000000000010",
974                "T0050": "123",
975                "i000": "0100",
976                "i002": "555544******1111",
977                "i003": "500000",
978                "i004": "000100000000",
979                "i006": "000100000000",
980                "i007": "0629151748",
981                "i011": "100250",
982                "i012": "181748",
983                "i013": "0629",
984                "i018": "0000",
985                "i022": "0000",
986                "i025": "02",
987                "i032": "010455",
988                "i037": "002595100250",
989                "i041": 990,
990                "i042": "DCZ1",
991                "i043": "IDDQD Bank.                         GE",
992                "i048": "USRDT|2595100250",
993                "i049": 643,
994                "i051": 643,
995                "i060": 3,
996                "i101": 91926242,
997                "i102": 2371492071643
998            }"#;
999
1000        let target: SigmaRequest =
1001            SigmaRequest::from_json_value(serde_json::from_str(json).unwrap()).unwrap();
1002
1003        let req = SigmaRequest::decode(src).unwrap();
1004
1005        assert_eq!(req, target);
1006    }
1007
1008    #[test]
1009    fn decode_sigma_response() {
1010        let s = Bytes::from_static(b"0002401104007040978T\x00\x31\x00\x00\x048495");
1011
1012        let resp = SigmaResponse::decode(s).unwrap();
1013        assert_eq!(resp.mti, "0110");
1014        assert_eq!(resp.auth_serno, 4007040978);
1015        assert_eq!(resp.reason, 8495);
1016
1017        let serialized = serde_json::to_string(&resp).unwrap();
1018        assert_eq!(
1019            serialized,
1020            r#"{"mti":"0110","auth_serno":4007040978,"reason":8495}"#
1021        );
1022    }
1023
1024    #[test]
1025    fn decode_sigma_response_xri() {
1026        let s = Bytes::from_static(
1027            b"0004201104007040978T\x00\x31\x00\x00\x048495T\x00\x33\x00\x00\x12X-Request-Id",
1028        );
1029
1030        let resp = SigmaResponse::decode(s).unwrap();
1031        assert_eq!(resp.mti, "0110");
1032        assert_eq!(resp.auth_serno, 4007040978);
1033        assert_eq!(resp.reason, 8495);
1034        assert_eq!(resp.xri, Some("X-Request-Id".to_string()));
1035
1036        let serialized = serde_json::to_string(&resp).unwrap();
1037        assert_eq!(
1038            serialized,
1039            r#"{"mti":"0110","auth_serno":4007040978,"reason":8495,"xri":"X-Request-Id"}"#
1040        );
1041    }
1042
1043    #[test]
1044    fn decode_sigma_response_incorrect_auth_serno() {
1045        let s = Bytes::from_static(b"000250110XYZ7040978T\x00\x31\x00\x00\x048100");
1046
1047        assert!(SigmaResponse::decode(s).is_err());
1048    }
1049
1050    #[test]
1051    fn decode_sigma_response_incorrect_reason() {
1052        let s = Bytes::from_static(b"0002501104007040978T\x00\x31\x00\x00\x04ABCD");
1053
1054        assert!(SigmaResponse::decode(s).is_err());
1055    }
1056
1057    #[test]
1058    fn decode_sigma_response_fee_data() {
1059        let s = Bytes::from_static(
1060            b"0004001104007040978T\x00\x31\x00\x00\x048100T\x00\x32\x00\x00\x108116978300",
1061        );
1062
1063        let resp = SigmaResponse::decode(s).unwrap();
1064        assert_eq!(resp.mti, "0110");
1065        assert_eq!(resp.auth_serno, 4007040978);
1066        assert_eq!(resp.reason, 8100);
1067
1068        let serialized = serde_json::to_string(&resp).unwrap();
1069        assert_eq!(
1070            serialized,
1071            r#"{"mti":"0110","auth_serno":4007040978,"reason":8100,"fees":[{"reason":8116,"currency":978,"amount":300}]}"#
1072        );
1073    }
1074
1075    #[test]
1076    fn decode_sigma_response_correct_short_auth_serno() {
1077        let s = Bytes::from_static(b"000240110123123    T\x00\x31\x00\x00\x048100");
1078
1079        let resp = SigmaResponse::decode(s).unwrap();
1080        assert_eq!(resp.mti, "0110");
1081        assert_eq!(resp.auth_serno, 123123);
1082        assert_eq!(resp.reason, 8100);
1083
1084        let serialized = serde_json::to_string(&resp).unwrap();
1085        assert_eq!(
1086            serialized,
1087            r#"{"mti":"0110","auth_serno":123123,"reason":8100}"#
1088        );
1089    }
1090
1091    #[test]
1092    fn decode_fee_data() {
1093        let data = b"8116978300";
1094
1095        let fee = FeeData::from_slice(data).unwrap();
1096        assert_eq!(fee.reason, 8116);
1097        assert_eq!(fee.currency, 978);
1098        assert_eq!(fee.amount, 300);
1099    }
1100
1101    #[test]
1102    fn decode_fee_data_large_amount() {
1103        let data = b"8116643123456789";
1104
1105        let fee = FeeData::from_slice(data).unwrap();
1106        assert_eq!(fee.reason, 8116);
1107        assert_eq!(fee.currency, 643);
1108        assert_eq!(fee.amount, 123456789);
1109    }
1110
1111    #[test]
1112    fn decode_sigma_response_fee_data_additional_data() {
1113        let s = Bytes::from_static(b"0015201104007040978T\x00\x31\x00\x00\x048100T\x00\x32\x00\x00\x1181166439000T\x00\x48\x00\x01\x05CJyuARCDBRibpKn+BSIVCgx0ZmE6FwAAAKoXmwIQnK4BGLcBIhEKDHRmcDoWAAAAxxX+ARik\nATCBu4PdBToICKqv7BQQgwVAnK4BSAI=");
1114
1115        let resp = SigmaResponse::decode(s).unwrap();
1116        assert_eq!(resp.mti, "0110");
1117        assert_eq!(resp.auth_serno, 4007040978);
1118        assert_eq!(resp.reason, 8100);
1119
1120        let serialized = serde_json::to_string(&resp).unwrap();
1121        assert_eq!(
1122            serialized,
1123            r#"{"mti":"0110","auth_serno":4007040978,"reason":8100,"fees":[{"reason":8116,"currency":643,"amount":9000}],"adata":"CJyuARCDBRibpKn+BSIVCgx0ZmE6FwAAAKoXmwIQnK4BGLcBIhEKDHRmcDoWAAAAxxX+ARik\nATCBu4PdBToICKqv7BQQgwVAnK4BSAI="}"#
1124        );
1125    }
1126
1127    #[test]
1128    fn decode_sigma_response_fee_data_additional_data_supplementary_data() {
1129        let s = Bytes::from_static(b"0016101104007040978T\x00\x31\x00\x00\x048100T\x00\x32\x00\x00\x1181166439000T\x00\x48\x00\x01\x05CJyuARCDBRibpKn+BSIVCgx0ZmE6FwAAAKoXmwIQnK4BGLcBIhEKDHRmcDoWAAAAxxX+ARik\nATCBu4PdBToICKqv7BQQgwVAnK4BSAI=T\x00\x50\x00\x00\x03123");
1130
1131        let resp = SigmaResponse::decode(s).unwrap();
1132        assert_eq!(resp.mti, "0110");
1133        assert_eq!(resp.auth_serno, 4007040978);
1134        assert_eq!(resp.reason, 8100);
1135        //"T0050": "123",
1136        let serialized = serde_json::to_string(&resp).unwrap();
1137        assert_eq!(
1138            serialized,
1139            r#"{"mti":"0110","auth_serno":4007040978,"reason":8100,"fees":[{"reason":8116,"currency":643,"amount":9000}],"adata":"CJyuARCDBRibpKn+BSIVCgx0ZmE6FwAAAKoXmwIQnK4BGLcBIhEKDHRmcDoWAAAAxxX+ARik\nATCBu4PdBToICKqv7BQQgwVAnK4BSAI=","supdata":"123"}"#
1140        );
1141    }
1142
1143    #[test]
1144    fn encode_fee_data() {
1145        let fee_data = FeeData {
1146            reason: 8123,
1147            currency: 643,
1148            amount: 1234567890,
1149        };
1150
1151        assert_eq!(fee_data.encode().unwrap()[..], b"81236431234567890"[..]);
1152    }
1153
1154    #[test]
1155    fn encode_fee_data_incorrect() {
1156        assert!(FeeData {
1157            reason: 10000,
1158            currency: 643,
1159            amount: 1234567890,
1160        }
1161        .encode()
1162        .is_err());
1163
1164        assert!(FeeData {
1165            reason: 8123,
1166            currency: 6430,
1167            amount: 1234567890,
1168        }
1169        .encode()
1170        .is_err());
1171    }
1172
1173    #[test]
1174    fn encode_sigma_response_fee_data_additional_data() {
1175        let src = r#"{"mti":"0110","auth_serno":4007040978,"reason":8100,"fees":[{"reason":8116,"currency":643,"amount":9000}],"adata":"CJyuARCDBRibpKn+BSIVCgx0ZmE6FwAAAKoXmwIQnK4BGLcBIhEKDHRmcDoWAAAAxxX+ARik\nATCBu4PdBToICKqv7BQQgwVAnK4BSAI="}"#;
1176        let response = serde_json::from_str::<SigmaResponse>(src).unwrap();
1177
1178        let target = b"0015201104007040978T\x00\x31\x00\x00\x048100T\x00\x32\x00\x00\x1181166439000T\x00\x48\x00\x01\x05CJyuARCDBRibpKn+BSIVCgx0ZmE6FwAAAKoXmwIQnK4BGLcBIhEKDHRmcDoWAAAAxxX+ARik\nATCBu4PdBToICKqv7BQQgwVAnK4BSAI=";
1179        assert_eq!(response.encode().unwrap()[..], target[..])
1180    }
1181
1182    #[test]
1183    fn validate_saf_field() {
1184        assert!(validate_saf("Y").is_ok());
1185        assert!(validate_saf("N").is_ok());
1186
1187        assert!(validate_saf("").is_err());
1188        assert!(validate_saf("YY").is_err());
1189        assert!(validate_saf("NN").is_err());
1190        assert!(validate_saf("A").is_err());
1191    }
1192
1193    #[test]
1194    fn validate_source_field() {
1195        assert!(validate_source("Y").is_ok());
1196        assert!(validate_source("N").is_ok());
1197
1198        assert!(validate_source("").is_err());
1199        assert!(validate_source("YY").is_err());
1200        assert!(validate_source("NN").is_err());
1201    }
1202
1203    #[test]
1204    fn validate_mti_field() {
1205        assert!(validate_mti("0120").is_ok());
1206
1207        assert!(validate_mti("").is_err());
1208        assert!(validate_mti("120").is_err());
1209        assert!(validate_mti("00120").is_err());
1210        assert!(validate_mti("O120").is_err());
1211    }
1212}