#![allow(deprecated)] /* * Copyright 2009 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use num::{self, bigint::ToBigUint, BigUint}; use std::rc::Rc; use crate::{ common::{DecoderRXingResult, ECIStringBuilder, Result}, pdf417::PDF417RXingResultMetadata, Exceptions, }; /** *

This class contains the methods for decoding the PDF417 codewords.

* * @author SITA Lab (kevin.osullivan@sita.aero) * @author Guenther Grau */ #[derive(Clone, Copy, PartialEq, Eq)] enum Mode { Alpha, Lower, Mixed, Punct, AlphaShift, PunctShift, } const TEXT_COMPACTION_MODE_LATCH: u32 = 900; const BYTE_COMPACTION_MODE_LATCH: u32 = 901; const NUMERIC_COMPACTION_MODE_LATCH: u32 = 902; const BYTE_COMPACTION_MODE_LATCH_6: u32 = 924; const ECI_USER_DEFINED: u32 = 925; const ECI_GENERAL_PURPOSE: u32 = 926; const ECI_CHARSET: u32 = 927; const BEGIN_MACRO_PDF417_CONTROL_BLOCK: u32 = 928; const BEGIN_MACRO_PDF417_OPTIONAL_FIELD: u32 = 923; const MACRO_PDF417_TERMINATOR: u32 = 922; const MODE_SHIFT_TO_BYTE_COMPACTION_MODE: u32 = 913; const MAX_NUMERIC_CODEWORDS: usize = 15; const MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: u32 = 0; const MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT: u32 = 1; const MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP: u32 = 2; const MACRO_PDF417_OPTIONAL_FIELD_SENDER: u32 = 3; const MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE: u32 = 4; const MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE: u32 = 5; const MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM: u32 = 6; const PRE_TEXT_COMPACTION_MODE_LATCH: u32 = TEXT_COMPACTION_MODE_LATCH - 1; const PL: u32 = 25; const LL: u32 = 27; const AS: u32 = 27; const ML: u32 = 28; const AL: u32 = 28; const PS: u32 = 29; const PAL: u32 = 29; const PUNCT_CHARS: [char; 29] = [ ';', '<', '>', '@', '[', '\\', ']', '_', '`', '~', '!', '\r', '\t', ',', ':', '\n', '-', '.', '$', '/', '"', '|', '*', '(', ')', '?', '{', '}', '\'', ]; const MIXED_CHARS: [char; 25] = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&', '\r', '\t', ',', ':', '#', '-', '.', '$', '/', '+', '%', '*', '=', '^', ]; use once_cell::sync::Lazy; /** * Table containing values for the exponent of 900. * This is used in the numeric compaction decode algorithm. */ static EXP900: Lazy> = Lazy::new(|| { const EXP_LEN: usize = 16; let mut exp900 = Vec::with_capacity(EXP_LEN); //[0;16]; exp900.push(ToBigUint::to_biguint(&1).unwrap_or_default()); let nineHundred = ToBigUint::to_biguint(&900).unwrap_or_default(); exp900.push(nineHundred); let mut i = 2; while i < EXP_LEN { exp900.push(&exp900[i - 1] * 900_u32); i += 1; } exp900 }); const NUMBER_OF_SEQUENCE_CODEWORDS: usize = 2; pub fn decode(codewords: &[u32], ecLevel: &str) -> Result { let mut result = ECIStringBuilder::with_capacity(codewords.len() * 2); let mut codeIndex = textCompaction(codewords, 1, &mut result)?; let mut resultMetadata = PDF417RXingResultMetadata::default(); while codeIndex < codewords[0] as usize { let code = codewords[codeIndex]; codeIndex += 1; match code { TEXT_COMPACTION_MODE_LATCH => { codeIndex = textCompaction(codewords, codeIndex, &mut result)? } BYTE_COMPACTION_MODE_LATCH | BYTE_COMPACTION_MODE_LATCH_6 => { codeIndex = byteCompaction(code, codewords, codeIndex, &mut result)? } MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { result.append_char(char::from_u32(codewords[codeIndex]).ok_or(Exceptions::parse)?); codeIndex += 1; } NUMERIC_COMPACTION_MODE_LATCH => { codeIndex = numericCompaction(codewords, codeIndex, &mut result)? } ECI_CHARSET => { result.appendECI(codewords[codeIndex])?; codeIndex += 1; } ECI_GENERAL_PURPOSE => // Can't do anything with generic ECI; skip its 2 characters { codeIndex += 2 } ECI_USER_DEFINED => // Can't do anything with user ECI; skip its 1 character { codeIndex += 1 } BEGIN_MACRO_PDF417_CONTROL_BLOCK => { codeIndex = decodeMacroBlock(codewords, codeIndex, &mut resultMetadata)? } BEGIN_MACRO_PDF417_OPTIONAL_FIELD | MACRO_PDF417_TERMINATOR => // Should not see these outside a macro block { return Err(Exceptions::format) } _ => { // Default to text compaction. During testing numerous barcodes // appeared to be missing the starting Mode:: In these cases defaulting // to text compaction seems to work. codeIndex -= 1; codeIndex = textCompaction(codewords, codeIndex, &mut result)?; } } } result = result.build_result(); if result.is_empty() && resultMetadata.getFileId().is_empty() { return Err(Exceptions::format); } let mut decoderRXingResult = DecoderRXingResult::new( Vec::new(), result.to_string(), Vec::new(), ecLevel.to_owned(), ); decoderRXingResult.setOther(Some(Rc::new(resultMetadata))); Ok(decoderRXingResult) } pub fn decodeMacroBlock( codewords: &[u32], codeIndex: usize, resultMetadata: &mut PDF417RXingResultMetadata, ) -> Result { let mut codeIndex = codeIndex; if codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0] as usize { // we must have at least two bytes left for the segment index return Err(Exceptions::format); } let mut segmentIndexArray = [0; NUMBER_OF_SEQUENCE_CODEWORDS]; for seq in segmentIndexArray .iter_mut() .take(NUMBER_OF_SEQUENCE_CODEWORDS) { *seq = codewords[codeIndex]; codeIndex += 1; } let segmentIndexString = decodeBase900toBase10(&segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS)?; if segmentIndexString.is_empty() { resultMetadata.setSegmentIndex(0); } else if let Ok(parsed_int) = segmentIndexString.parse::() { resultMetadata.setSegmentIndex(parsed_int); } else { // too large; bad input? return Err(Exceptions::format); } // Decoding the fileId codewords as 0-899 numbers, each 0-filled to width 3. This follows the spec // (See ISO/IEC 15438:2015 Annex H.6) and preserves all info, but some generators (e.g. TEC-IT) write // the fileId using text compaction, so in those cases the fileId will appear mangled. let mut fileId = String::new(); while codeIndex < codewords[0] as usize && codeIndex < codewords.len() && codewords[codeIndex] != MACRO_PDF417_TERMINATOR && codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD { fileId.push_str(&format!("{:0>3}", codewords[codeIndex])/*String.format("%03d", codewords[codeIndex])*/); codeIndex += 1; } if fileId.chars().count() == 0 { // at least one fileId codeword is required (Annex H.2) return Err(Exceptions::format); } resultMetadata.setFileId(fileId); let mut optionalFieldsStart = -1_isize; if codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD { optionalFieldsStart = codeIndex as isize + 1; } while codeIndex < codewords[0] as usize { match codewords[codeIndex] { BEGIN_MACRO_PDF417_OPTIONAL_FIELD => { codeIndex += 1; match codewords[codeIndex] { MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME => { let mut fileName = ECIStringBuilder::new(); codeIndex = textCompaction(codewords, codeIndex + 1, &mut fileName)?; fileName = fileName.build_result(); resultMetadata.setFileName(fileName.to_string()); } MACRO_PDF417_OPTIONAL_FIELD_SENDER => { let mut sender = ECIStringBuilder::new(); codeIndex = textCompaction(codewords, codeIndex + 1, &mut sender)?; sender = sender.build_result(); resultMetadata.setSender(sender.to_string()); } MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE => { let mut addressee = ECIStringBuilder::new(); codeIndex = textCompaction(codewords, codeIndex + 1, &mut addressee)?; addressee = addressee.build_result(); resultMetadata.setAddressee(addressee.to_string()); } MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT => { let mut segmentCount = ECIStringBuilder::new(); codeIndex = numericCompaction(codewords, codeIndex + 1, &mut segmentCount)?; segmentCount = segmentCount.build_result(); let Ok(parsed_segment_count) = segmentCount.to_string().parse() else { return Err(Exceptions::format); }; resultMetadata.setSegmentCount(parsed_segment_count); } MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP => { let mut timestamp = ECIStringBuilder::new(); codeIndex = numericCompaction(codewords, codeIndex + 1, &mut timestamp)?; timestamp = timestamp.build_result(); let Ok(parsed_timestamp) = timestamp.to_string().parse() else { return Err(Exceptions::format); }; resultMetadata.setTimestamp(parsed_timestamp); } MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM => { let mut checksum = ECIStringBuilder::new(); codeIndex = numericCompaction(codewords, codeIndex + 1, &mut checksum)?; checksum = checksum.build_result(); let Ok(parsed_checksum ) = checksum.to_string().parse() else { return Err(Exceptions::format); }; resultMetadata.setChecksum(parsed_checksum); } MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE => { let mut fileSize = ECIStringBuilder::new(); codeIndex = numericCompaction(codewords, codeIndex + 1, &mut fileSize)?; fileSize = fileSize.build_result(); let Ok(parsed_file_size)= fileSize.to_string().parse() else { return Err(Exceptions::format); }; resultMetadata.setFileSize(parsed_file_size); } _ => return Err(Exceptions::format), } } MACRO_PDF417_TERMINATOR => { codeIndex += 1; resultMetadata.setLastSegment(true); } _ => return Err(Exceptions::format), } } // copy optional fields to additional options if optionalFieldsStart != -1 { let mut optionalFieldsLength = codeIndex - optionalFieldsStart as usize; if resultMetadata.isLastSegment() { // do not include terminator optionalFieldsLength -= 1; } resultMetadata.setOptionalData( codewords[optionalFieldsStart as usize ..(optionalFieldsStart + optionalFieldsLength as isize) as usize] .to_vec(), ); } Ok(codeIndex) } /** * Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be * encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as * well as selected control characters. * * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ fn textCompaction( codewords: &[u32], codeIndex: usize, result: &mut ECIStringBuilder, ) -> Result { let mut codeIndex = codeIndex; // 2 character per codeword let mut textCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2]; // Used to hold the byte compaction value if there is a mode shift let mut byteCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2]; let mut index = 0; let mut end = false; let mut subMode = Mode::Alpha; while (codeIndex < codewords[0] as usize) && !end { let mut code = codewords[codeIndex]; codeIndex += 1; match code { ..=PRE_TEXT_COMPACTION_MODE_LATCH => { textCompactionData[index] = code / 30; textCompactionData[index + 1] = code % 30; index += 2; } TEXT_COMPACTION_MODE_LATCH => { // reinitialize text compaction mode to alpha sub mode textCompactionData[index] = TEXT_COMPACTION_MODE_LATCH; index += 1; } BYTE_COMPACTION_MODE_LATCH | BYTE_COMPACTION_MODE_LATCH_6 | NUMERIC_COMPACTION_MODE_LATCH | BEGIN_MACRO_PDF417_CONTROL_BLOCK | BEGIN_MACRO_PDF417_OPTIONAL_FIELD | MACRO_PDF417_TERMINATOR => { codeIndex -= 1; end = true; } MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { // The Mode Shift codeword 913 shall cause a temporary // switch from Text Compaction mode to Byte Compaction Mode:: // This switch shall be in effect for only the next codeword, // after which the mode shall revert to the prevailing sub-mode // of the Text Compaction Mode:: Codeword 913 is only available // in Text Compaction mode; its use is described in 5.4.2.4. textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE; code = codewords[codeIndex]; codeIndex += 1; byteCompactionData[index] = code; index += 1; } ECI_CHARSET => { subMode = decodeTextCompaction( &textCompactionData, &byteCompactionData, index, result, subMode, ) .ok_or(Exceptions::illegalState)?; result.appendECI(codewords[codeIndex])?; codeIndex += 1; textCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2]; byteCompactionData = vec![0; (codewords[0] as usize - codeIndex) * 2]; index = 0; } _ => {} } } decodeTextCompaction( &textCompactionData, &byteCompactionData, index, result, subMode, ); Ok(codeIndex) } /** * The Text Compaction mode includes all the printable ASCII characters * (i.e. values from 32 to 126) and three ASCII control characters: HT or tab * (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage * return (ASCII value 13). The Text Compaction mode also includes various latch * and shift characters which are used exclusively within the Mode:: The Text * Compaction mode encodes up to 2 characters per codeword. The compaction rules * for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode * switches are defined in 5.4.2.3. * * @param textCompactionData The text compaction data. * @param byteCompactionData The byte compaction data if there * was a mode shift. * @param length The size of the text compaction and byte compaction data. * @param result The decoded data is appended to the result. * @param startMode The mode in which decoding starts * @return The mode in which decoding ended */ fn decodeTextCompaction( textCompactionData: &[u32], byteCompactionData: &[u32], length: usize, result: &mut ECIStringBuilder, startMode: Mode, ) -> Option { // Beginning from an initial state // The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text // Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text // Compaction mode shall always switch to the Text Compaction Alpha sub-Mode:: let mut subMode = startMode; let mut priorToShiftMode = startMode; let mut latchedMode = startMode; let mut i = 0; const PRE_PL: u32 = PL - 1; const PRE_PAL: u32 = PAL - 1; while i < length { let subModeCh = textCompactionData[i]; let mut ch = 0 as char; match subMode { Mode::Alpha => // Alpha (uppercase alphabetic) { match subModeCh { // Upper case Alpha Character 0..=25 => ch = char::from_u32('A' as u32 + subModeCh)?, 26 => ch = ' ', LL => { subMode = Mode::Lower; latchedMode = subMode; } ML => { subMode = Mode::Mixed; latchedMode = subMode; } PS => { // Shift to punctuation priorToShiftMode = subMode; subMode = Mode::PunctShift; } MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { result.append_char(char::from_u32(byteCompactionData[i])?) } TEXT_COMPACTION_MODE_LATCH => { subMode = Mode::Alpha; latchedMode = subMode; } _ => {} } } Mode::Lower => // Lower (lowercase alphabetic) { match subModeCh { ..=25 => ch = char::from_u32('a' as u32 + subModeCh)?, 26 => ch = ' ', AS => { // Shift to alpha priorToShiftMode = subMode; subMode = Mode::AlphaShift; } ML => { subMode = Mode::Mixed; latchedMode = subMode; } PS => { // Shift to punctuation priorToShiftMode = subMode; subMode = Mode::PunctShift; } MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { result.append_char(char::from_u32(byteCompactionData[i])?) } TEXT_COMPACTION_MODE_LATCH => { subMode = Mode::Alpha; latchedMode = subMode; } _ => {} } } Mode::Mixed => // Mixed (numeric and some punctuation) { match subModeCh { 0..=PRE_PL => ch = MIXED_CHARS[subModeCh as usize], PL => { subMode = Mode::Punct; latchedMode = subMode; } 26 => ch = ' ', LL => { subMode = Mode::Lower; latchedMode = subMode; } AL | TEXT_COMPACTION_MODE_LATCH => { subMode = Mode::Alpha; latchedMode = subMode; } PS => { // Shift to punctuation priorToShiftMode = subMode; subMode = Mode::PunctShift; } MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { result.append_char(char::from_u32(byteCompactionData[i])?) } _ => {} } } Mode::Punct => // Punctuation { match subModeCh { ..=PRE_PAL => ch = PUNCT_CHARS[subModeCh as usize], PAL | TEXT_COMPACTION_MODE_LATCH => { subMode = Mode::Alpha; latchedMode = subMode; } MODE_SHIFT_TO_BYTE_COMPACTION_MODE => { result.append_char(char::from_u32(byteCompactionData[i])?) } _ => {} } } Mode::AlphaShift => { // Restore sub-mode subMode = priorToShiftMode; match subModeCh { ..=25 => ch = char::from_u32('A' as u32 + subModeCh)?, 26 => ch = ' ', TEXT_COMPACTION_MODE_LATCH => subMode = Mode::Alpha, _ => {} } } Mode::PunctShift => { // Restore sub-mode subMode = priorToShiftMode; match subModeCh { ..=PRE_PAL => ch = PUNCT_CHARS[subModeCh as usize], PAL | TEXT_COMPACTION_MODE_LATCH => subMode = Mode::Alpha, MODE_SHIFT_TO_BYTE_COMPACTION_MODE => // PS before Shift-to-Byte is used as a padding character, // see 5.4.2.4 of the specification { result.append_char(char::from_u32(byteCompactionData[i])?) } _ => {} } } } if ch as u32 != 0 { // Append decoded character to result result.append_char(ch); } i += 1; } Some(latchedMode) } /** * Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded. * This includes all ASCII characters value 0 to 127 inclusive and provides for international * character set support. * * @param mode The byte compaction mode i.e. 901 or 924 * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ fn byteCompaction( mode: u32, codewords: &[u32], codeIndex: usize, result: &mut ECIStringBuilder, ) -> Result { let mut end = false; let mut codeIndex = codeIndex; while codeIndex < codewords[0] as usize && !end { //handle leading ECIs while codeIndex < codewords[0] as usize && codewords[codeIndex] == ECI_CHARSET { codeIndex += 1; result.appendECI(codewords[codeIndex])?; codeIndex += 1; } if codeIndex >= codewords[0] as usize || codewords[codeIndex] >= TEXT_COMPACTION_MODE_LATCH { end = true; } else { //decode one block of 5 codewords to 6 bytes let mut value: u64 = 0; let mut count = 0; loop { value = 900 * value + codewords[codeIndex] as u64; codeIndex += 1; count += 1; if !(count < 5 && codeIndex < codewords[0] as usize && codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH) { break; } } if count == 5 && (mode == BYTE_COMPACTION_MODE_LATCH_6 || codeIndex < codewords[0] as usize && codewords[codeIndex] < TEXT_COMPACTION_MODE_LATCH) { for i in 0..6 { result.append_byte((value >> (8 * (5 - i))) as u8); } } else { codeIndex -= count; while (codeIndex < codewords[0] as usize) && !end { let code = codewords[codeIndex]; codeIndex += 1; if code < TEXT_COMPACTION_MODE_LATCH { result.append_byte(code as u8); } else if code == ECI_CHARSET { result.appendECI(codewords[codeIndex])?; codeIndex += 1; } else { codeIndex -= 1; end = true; } } } } } Ok(codeIndex) } /** * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. * * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ fn numericCompaction( codewords: &[u32], codeIndex: usize, result: &mut ECIStringBuilder, ) -> Result { let mut count = 0; let mut end = false; let mut codeIndex = codeIndex; let mut numericCodewords = [0; MAX_NUMERIC_CODEWORDS]; while codeIndex < codewords[0] as usize && !end { let code = codewords[codeIndex]; codeIndex += 1; if codeIndex == codewords[0] as usize { end = true; } match code { ..=PRE_TEXT_COMPACTION_MODE_LATCH => { numericCodewords[count] = code; count += 1; } TEXT_COMPACTION_MODE_LATCH | BYTE_COMPACTION_MODE_LATCH | BYTE_COMPACTION_MODE_LATCH_6 | BEGIN_MACRO_PDF417_CONTROL_BLOCK | BEGIN_MACRO_PDF417_OPTIONAL_FIELD | MACRO_PDF417_TERMINATOR | ECI_CHARSET => { codeIndex -= 1; end = true; } _ => {} } if (count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) && count > 0 { // Re-invoking Numeric Compaction mode (by using codeword 902 // while in Numeric Compaction mode) serves to terminate the // current Numeric Compaction mode grouping as described in 5.4.4.2, // and then to start a new one grouping. result.append_string(&decodeBase900toBase10(&numericCodewords, count)?); count = 0; } } Ok(codeIndex) } /** * Convert a list of Numeric Compacted codewords from Base 900 to Base 10. * * @param codewords The array of codewords * @param count The number of codewords * @return The decoded string representing the Numeric data. */ /* EXAMPLE Encode the fifteen digit numeric string 000213298174000 Prefix the numeric string with a 1 and set the initial value of t = 1 000 213 298 174 000 Calculate codeword 0 d0 = 1 000 213 298 174 000 mod 900 = 200 t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082 Calculate codeword 1 d1 = 1 111 348 109 082 mod 900 = 282 t = 1 111 348 109 082 div 900 = 1 234 831 232 Calculate codeword 2 d2 = 1 234 831 232 mod 900 = 632 t = 1 234 831 232 div 900 = 1 372 034 Calculate codeword 3 d3 = 1 372 034 mod 900 = 434 t = 1 372 034 div 900 = 1 524 Calculate codeword 4 d4 = 1 524 mod 900 = 624 t = 1 524 div 900 = 1 Calculate codeword 5 d5 = 1 mod 900 = 1 t = 1 div 900 = 0 Codeword sequence is: 1, 624, 434, 632, 282, 200 Decode the above codewords involves 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 + 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000 Remove leading 1 => RXingResult is 000213298174000 */ fn decodeBase900toBase10(codewords: &[u32], count: usize) -> Result { let mut result = 0 .to_biguint() .ok_or(Exceptions::ArithmeticException(None))?; for i in 0..count { result += &EXP900[count - i - 1] * (codewords[i].to_biguint().ok_or(Exceptions::arithmetic)?); } let resultString = result.to_string(); if !resultString.starts_with('1') { return Err(Exceptions::format); } Ok(resultString[1..].to_owned()) }