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 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 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 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 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}