code wiki / (root) / nx_iso20022_lib.nx

nx_iso20022_lib.nx source

↩ module page · 163 lines · 6398 B

1// nx_iso20022_lib.nx -- F989: ISO 20022 payment-message parse + integrity validation. 2// 3// 2026 is the inflection: Fedwire migrated to ISO 20022 in July 2025 and Swift ends MT coexistence in 4// November. A pain.001 (customer credit-transfer initiation) carries its OWN integrity fields -- NbOfTxs 5// (the declared transaction count) and CtrlSum (the declared total). The sovereign property here is that 6// these are CHECKED against the actual message content, never trusted: a corrupted or TAMPERED payment 7// file -- one where an amount was altered but the control sum was not -- is REJECTED by construction. 8// Money is integer minor units; a control sum is compared to the exact cent, no float, no tolerance. 9// 10// FAIL-CLOSED: a missing required field (NbOfTxs / CtrlSum) is a rejection, not a default -- a payment 11// message you cannot validate is a payment message you do not process. 12// 13// SCOPE (declared, honest): this parses element text and validates the two internal-integrity invariants. 14// Full XSD schema validation, namespace resolution, and variable-fraction-digit currencies (this assumes 15// 2 fraction digits, correct for USD/EUR/most) are DECLARED follow-ons, not silently handled. 16// DRY: composes nx_matter_lib (sys_*/mt_*). A validated message feeds nx_ledger as transfers. license_tier: ORIGINAL LIB. 17 18import "nx_matter_lib.nx" 19 20const ISO_OK: i64 = 0 21const ISO_NO_FIELD: i64 = 0 - 1 // a required field is absent (malformed) 22const ISO_TX_MISMATCH: i64 = 0 - 2 // NbOfTxs != actual CdtTrfTxInf count 23const ISO_NO_CTRL: i64 = 0 - 3 // CtrlSum absent 24const ISO_SUM_MISMATCH: i64 = 0 - 4 // CtrlSum != actual sum of amounts (tamper/corruption) 25 26func iso_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 27 28// substring search from `from`; returns index or -1. Mismatch jumps the cursor past the run (no break). 29func iso_find(hay: *u8, hl: i64, needle: *u8, nl: i64, from: i64) -> i64 { 30 if nl == 0 { return 0 - 1 } 31 var i: i64 = from 32 while i + nl <= hl { 33 var j: i64 = 0 34 while j < nl { 35 if hay[i + j] == needle[j] { j = j + 1 } else { j = nl + 1 } 36 } 37 if j == nl { return i } 38 i = i + 1 39 } 40 return 0 - 1 41} 42 43// build "<tag>" -> out, returns len 44func iso_open(tag: *u8, out: *u8) -> i64 { 45 out[0] = 60 as u8 46 var o: i64 = 1 47 var i: i64 = 0 48 while tag[i] != (0 as u8) { out[o] = tag[i]; o = o + 1; i = i + 1 } 49 out[o] = 62 as u8 50 o = o + 1 51 out[o] = 0 as u8 52 return o 53} 54// build "</tag>" -> out, returns len 55func iso_close(tag: *u8, out: *u8) -> i64 { 56 out[0] = 60 as u8 57 out[1] = 47 as u8 58 var o: i64 = 2 59 var i: i64 = 0 60 while tag[i] != (0 as u8) { out[o] = tag[i]; o = o + 1; i = i + 1 } 61 out[o] = 62 as u8 62 o = o + 1 63 out[o] = 0 as u8 64 return o 65} 66 67// extract the text content of the FIRST <tag>...</tag> into out; returns length, -1 if not found. 68func iso_element(xml: *u8, xl: i64, tag: *u8, out: *u8) -> i64 { 69 let ot: *u8 = sys_mmap(128) 70 let ct: *u8 = sys_mmap(128) 71 let ol: i64 = iso_open(tag, ot) 72 let cl: i64 = iso_close(tag, ct) 73 let os: i64 = iso_find(xml, xl, ot, ol, 0) 74 if os < 0 { return 0 - 1 } 75 let cs: i64 = os + ol 76 let ce: i64 = iso_find(xml, xl, ct, cl, cs) 77 if ce < 0 { return 0 - 1 } 78 var t: i64 = 0 79 var p: i64 = cs 80 while p < ce { out[t] = xml[p]; t = t + 1; p = p + 1 } 81 out[t] = 0 as u8 82 return t 83} 84 85// count occurrences of the opening <tag>. 86func iso_count(xml: *u8, xl: i64, tag: *u8) -> i64 { 87 let ot: *u8 = sys_mmap(128) 88 let ol: i64 = iso_open(tag, ot) 89 var c: i64 = 0 90 var from: i64 = 0 91 var go: i64 = 1 92 while go == 1 { 93 let p: i64 = iso_find(xml, xl, ot, ol, from) 94 if p < 0 { go = 0 } else { c = c + 1; from = p + ol } 95 } 96 return c 97} 98 99// parse a decimal amount string "1234.56" -> 123456 minor units (2 fraction digits assumed). 100func iso_amount_to_cents(s: *u8) -> i64 { 101 var slen: i64 = 0 102 var dot: i64 = 0 - 1 103 while s[slen] != (0 as u8) { if s[slen] == (46 as u8) { if dot < 0 { dot = slen } } slen = slen + 1 } 104 var wend: i64 = slen 105 if dot >= 0 { wend = dot } 106 var whole: i64 = 0 107 var p: i64 = 0 108 while p < wend { whole = whole * 10 + (s[p] - 48); p = p + 1 } 109 var cents: i64 = 0 110 var fd: i64 = 0 111 if dot >= 0 { 112 var q: i64 = dot + 1 113 while q < slen { if fd < 2 { cents = cents * 10 + (s[q] - 48); fd = fd + 1 } q = q + 1 } 114 } 115 while fd < 2 { cents = cents * 10; fd = fd + 1 } 116 return whole * 100 + cents 117} 118 119// sum every <tag>...</tag> amount (in minor units) across the message. 120func iso_sum_amounts(xml: *u8, xl: i64, tag: *u8) -> i64 { 121 let ot: *u8 = sys_mmap(128) 122 let ct: *u8 = sys_mmap(128) 123 let ol: i64 = iso_open(tag, ot) 124 let cl: i64 = iso_close(tag, ct) 125 let val: *u8 = sys_mmap(64) 126 var sum: i64 = 0 127 var from: i64 = 0 128 var go: i64 = 1 129 while go == 1 { 130 let os: i64 = iso_find(xml, xl, ot, ol, from) 131 if os < 0 { go = 0 } else { 132 let cs: i64 = os + ol 133 let ce: i64 = iso_find(xml, xl, ct, cl, cs) 134 if ce < 0 { go = 0 } else { 135 var t: i64 = 0 136 var q: i64 = cs 137 while q < ce { if t < 63 { val[t] = xml[q]; t = t + 1 } q = q + 1 } 138 val[t] = 0 as u8 139 sum = sum + iso_amount_to_cents(val) 140 from = ce + cl 141 } 142 } 143 } 144 return sum 145} 146 147// ★VALIDATE the message's own integrity. 0 = valid; negative code = the specific failure. 148// NbOfTxs must equal the actual CdtTrfTxInf count; CtrlSum must equal the actual sum of InstdAmt. 149func iso_validate(xml: *u8, xl: i64) -> i64 { 150 let nb: *u8 = sys_mmap(32) 151 if iso_element(xml, xl, "NbOfTxs" as *u8, nb) < 0 { return ISO_NO_FIELD } 152 var declared_n: i64 = 0 153 var i: i64 = 0 154 while nb[i] != (0 as u8) { declared_n = declared_n * 10 + (nb[i] - 48); i = i + 1 } 155 let actual_n: i64 = iso_count(xml, xl, "CdtTrfTxInf" as *u8) 156 if declared_n != actual_n { return ISO_TX_MISMATCH } 157 let cs: *u8 = sys_mmap(32) 158 if iso_element(xml, xl, "CtrlSum" as *u8, cs) < 0 { return ISO_NO_CTRL } 159 let declared_sum: i64 = iso_amount_to_cents(cs) 160 let actual_sum: i64 = iso_sum_amounts(xml, xl, "InstdAmt" as *u8) 161 if declared_sum != actual_sum { return ISO_SUM_MISMATCH } 162 return ISO_OK 163}