From 75026d69368b36016b24cacb69309e3d4a24f10a Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 19 Dec 2022 12:01:11 -0600 Subject: [PATCH] pdf417 ec + test --- src/common/reedsolomon/ReedSolomonTestCase.rs | 7 +- src/common/reedsolomon/mod.rs | 7 +- src/common/reedsolomon/reedsolomon_decoder.rs | 8 +- .../ec/AbstractErrorCorrectionTestCase.java | 55 --- src/pdf417/decoder/ec/ErrorCorrection.java | 190 ----------- .../decoder/ec/ErrorCorrectionTestCase.java | 98 ------ src/pdf417/decoder/ec/ModulusGF.java | 112 ------ src/pdf417/decoder/ec/ModulusPoly.java | 233 ------------- .../ec/abstract_error_correction_test_case.rs | 56 +++ src/pdf417/decoder/ec/error_correction.rs | 235 +++++++++++++ .../decoder/ec/error_correction_test_case.rs | 115 +++++++ src/pdf417/decoder/ec/mod.rs | 11 + src/pdf417/decoder/ec/modulus_gf.rs | 117 +++++++ src/pdf417/decoder/ec/modulus_poly.rs | 321 ++++++++++++++++++ 14 files changed, 870 insertions(+), 695 deletions(-) delete mode 100644 src/pdf417/decoder/ec/AbstractErrorCorrectionTestCase.java delete mode 100644 src/pdf417/decoder/ec/ErrorCorrection.java delete mode 100644 src/pdf417/decoder/ec/ErrorCorrectionTestCase.java delete mode 100644 src/pdf417/decoder/ec/ModulusGF.java delete mode 100644 src/pdf417/decoder/ec/ModulusPoly.java create mode 100644 src/pdf417/decoder/ec/abstract_error_correction_test_case.rs create mode 100644 src/pdf417/decoder/ec/error_correction.rs create mode 100644 src/pdf417/decoder/ec/error_correction_test_case.rs create mode 100644 src/pdf417/decoder/ec/modulus_gf.rs create mode 100644 src/pdf417/decoder/ec/modulus_poly.rs diff --git a/src/common/reedsolomon/ReedSolomonTestCase.rs b/src/common/reedsolomon/ReedSolomonTestCase.rs index 7b09324..6f90798 100644 --- a/src/common/reedsolomon/ReedSolomonTestCase.rs +++ b/src/common/reedsolomon/ReedSolomonTestCase.rs @@ -340,7 +340,12 @@ fn test_aztec() { test_encode_decode_random(azd12, 3072, 1023); } -fn corrupt(received: &mut Vec, howMany: i32, random: &mut rand::rngs::ThreadRng, max: i32) { +pub(crate) fn corrupt( + received: &mut Vec, + howMany: i32, + random: &mut rand::rngs::ThreadRng, + max: i32, +) { let mut corrupted = vec![false; received.len()]; //BitSet corrupted = new BitSet(received.length); let mut j = 0isize; diff --git a/src/common/reedsolomon/mod.rs b/src/common/reedsolomon/mod.rs index 664cc34..224b59f 100644 --- a/src/common/reedsolomon/mod.rs +++ b/src/common/reedsolomon/mod.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod GenericGFPolyTestCase; #[cfg(test)] -mod ReedSolomonTestCase; +pub(crate) mod ReedSolomonTestCase; /* * Copyright 2007 ZXing authors @@ -26,12 +26,13 @@ pub type GenericGFRef = &'static GenericGF; use lazy_static::lazy_static; lazy_static! { - static ref AZTEC_DATA_12: GenericGF = GenericGF::new(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1 + static ref AZTEC_DATA_12: GenericGF = GenericGF::new(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1 static ref AZTEC_DATA_10: GenericGF = GenericGF::new(0x409, 1024, 1); // x^10 + x^3 + 1 static ref AZTEC_DATA_6: GenericGF = GenericGF::new(0x43, 64, 1); // x^6 + x + 1 static ref AZTEC_PARAM: GenericGF = GenericGF::new(0x13, 16, 1); // x^4 + x + 1 static ref QR_CODE_FIELD_256: GenericGF = GenericGF::new(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1 static ref DATA_MATRIX_FIELD_256: GenericGF = GenericGF::new(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1 + // static ref PDF_417_FIELD: GenericGF = GenericGF::new(3,crate::pdf417::pdf_417_common::NUMBER_OF_CODEWORDS as usize,1); } // pub const AZTEC_DATA_12: GenericGF = GenericGF::new(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1 @@ -52,6 +53,7 @@ pub enum PredefinedGenericGF { DataMatrixField256, AztecData8, MaxicodeField64, + // PDF417, } /// Replacement for old const options, has the downside of generating new versions whenever one is requested. @@ -65,6 +67,7 @@ pub fn get_predefined_genericgf(request: PredefinedGenericGF) -> GenericGFRef { PredefinedGenericGF::DataMatrixField256 | PredefinedGenericGF::AztecData8 => { &DATA_MATRIX_FIELD_256 } // x^8 + x^5 + x^3 + x^2 + 1 + // PredefinedGenericGF::PDF417 => &PDF_417_FIELD, } } diff --git a/src/common/reedsolomon/reedsolomon_decoder.rs b/src/common/reedsolomon/reedsolomon_decoder.rs index e890ba5..9d74d12 100644 --- a/src/common/reedsolomon/reedsolomon_decoder.rs +++ b/src/common/reedsolomon/reedsolomon_decoder.rs @@ -60,7 +60,7 @@ impl ReedSolomonDecoder { * @param twoS number of error-correction codewords available * @throws ReedSolomonException if decoding fails for any reason */ - pub fn decode(&self, received: &mut Vec, twoS: i32) -> Result<(), Exceptions> { + pub fn decode(&self, received: &mut Vec, twoS: i32) -> Result { let poly = GenericGFPoly::new(self.field, received).unwrap(); let mut syndromeCoefficients = vec![0; twoS as usize]; let mut noError = true; @@ -74,7 +74,7 @@ impl ReedSolomonDecoder { } } if noError { - return Ok(()); + return Ok(0); } let syndrome = match GenericGFPoly::new(self.field, &syndromeCoefficients) { Ok(res) => res, @@ -97,7 +97,7 @@ impl ReedSolomonDecoder { //for (int i = 0; i < errorLocations.length; i++) { let log_value = self.field.log(errorLocations[i] as i32)?; if log_value > received.len() as i32 - 1 { - return Ok(()); + return Ok(0); } let position: isize = received.len() as isize - 1 - log_value as isize; if position < 0 { @@ -108,7 +108,7 @@ impl ReedSolomonDecoder { received[position as usize] = GenericGF::addOrSubtract(received[position as usize], errorMagnitudes[i]); } - Ok(()) + Ok(errorLocations.len()) } fn runEuclideanAlgorithm( diff --git a/src/pdf417/decoder/ec/AbstractErrorCorrectionTestCase.java b/src/pdf417/decoder/ec/AbstractErrorCorrectionTestCase.java deleted file mode 100644 index b017f61..0000000 --- a/src/pdf417/decoder/ec/AbstractErrorCorrectionTestCase.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012 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. - */ - -package com.google.zxing.pdf417.decoder.ec; - -import com.google.zxing.common.reedsolomon.ReedSolomonTestCase; -import org.junit.Assert; - -import java.util.BitSet; -import java.util.Random; - -/** - * @author Sean Owen - */ -abstract class AbstractErrorCorrectionTestCase extends Assert { - - static void corrupt(int[] received, int howMany, Random random) { - ReedSolomonTestCase.corrupt(received, howMany, random, 929); - } - - static int[] erase(int[] received, int howMany, Random random) { - BitSet erased = new BitSet(received.length); - int[] erasures = new int[howMany]; - int erasureOffset = 0; - for (int j = 0; j < howMany; j++) { - int location = random.nextInt(received.length); - if (erased.get(location)) { - j--; - } else { - erased.set(location); - received[location] = 0; - erasures[erasureOffset++] = location; - } - } - return erasures; - } - - static Random getRandom() { - return new Random(0xDEADBEEF); - } - -} \ No newline at end of file diff --git a/src/pdf417/decoder/ec/ErrorCorrection.java b/src/pdf417/decoder/ec/ErrorCorrection.java deleted file mode 100644 index 06bd06d..0000000 --- a/src/pdf417/decoder/ec/ErrorCorrection.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2012 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. - */ - -package com.google.zxing.pdf417.decoder.ec; - -import com.google.zxing.ChecksumException; - -/** - *

PDF417 error correction implementation.

- * - *

This example - * is quite useful in understanding the algorithm.

- * - * @author Sean Owen - * @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder - */ -public final class ErrorCorrection { - - private final ModulusGF field; - - public ErrorCorrection() { - this.field = ModulusGF.PDF417_GF; - } - - /** - * @param received received codewords - * @param numECCodewords number of those codewords used for EC - * @param erasures location of erasures - * @return number of errors - * @throws ChecksumException if errors cannot be corrected, maybe because of too many errors - */ - public int decode(int[] received, - int numECCodewords, - int[] erasures) throws ChecksumException { - - ModulusPoly poly = new ModulusPoly(field, received); - int[] S = new int[numECCodewords]; - boolean error = false; - for (int i = numECCodewords; i > 0; i--) { - int eval = poly.evaluateAt(field.exp(i)); - S[numECCodewords - i] = eval; - if (eval != 0) { - error = true; - } - } - - if (!error) { - return 0; - } - - ModulusPoly knownErrors = field.getOne(); - if (erasures != null) { - for (int erasure : erasures) { - int b = field.exp(received.length - 1 - erasure); - // Add (1 - bx) term: - ModulusPoly term = new ModulusPoly(field, new int[]{field.subtract(0, b), 1}); - knownErrors = knownErrors.multiply(term); - } - } - - ModulusPoly syndrome = new ModulusPoly(field, S); - //syndrome = syndrome.multiply(knownErrors); - - ModulusPoly[] sigmaOmega = - runEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords); - ModulusPoly sigma = sigmaOmega[0]; - ModulusPoly omega = sigmaOmega[1]; - - //sigma = sigma.multiply(knownErrors); - - int[] errorLocations = findErrorLocations(sigma); - int[] errorMagnitudes = findErrorMagnitudes(omega, sigma, errorLocations); - - for (int i = 0; i < errorLocations.length; i++) { - int position = received.length - 1 - field.log(errorLocations[i]); - if (position < 0) { - throw ChecksumException.getChecksumInstance(); - } - received[position] = field.subtract(received[position], errorMagnitudes[i]); - } - return errorLocations.length; - } - - private ModulusPoly[] runEuclideanAlgorithm(ModulusPoly a, ModulusPoly b, int R) - throws ChecksumException { - // Assume a's degree is >= b's - if (a.getDegree() < b.getDegree()) { - ModulusPoly temp = a; - a = b; - b = temp; - } - - ModulusPoly rLast = a; - ModulusPoly r = b; - ModulusPoly tLast = field.getZero(); - ModulusPoly t = field.getOne(); - - // Run Euclidean algorithm until r's degree is less than R/2 - while (r.getDegree() >= R / 2) { - ModulusPoly rLastLast = rLast; - ModulusPoly tLastLast = tLast; - rLast = r; - tLast = t; - - // Divide rLastLast by rLast, with quotient in q and remainder in r - if (rLast.isZero()) { - // Oops, Euclidean algorithm already terminated? - throw ChecksumException.getChecksumInstance(); - } - r = rLastLast; - ModulusPoly q = field.getZero(); - int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); - int dltInverse = field.inverse(denominatorLeadingTerm); - while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { - int degreeDiff = r.getDegree() - rLast.getDegree(); - int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse); - q = q.add(field.buildMonomial(degreeDiff, scale)); - r = r.subtract(rLast.multiplyByMonomial(degreeDiff, scale)); - } - - t = q.multiply(tLast).subtract(tLastLast).negative(); - } - - int sigmaTildeAtZero = t.getCoefficient(0); - if (sigmaTildeAtZero == 0) { - throw ChecksumException.getChecksumInstance(); - } - - int inverse = field.inverse(sigmaTildeAtZero); - ModulusPoly sigma = t.multiply(inverse); - ModulusPoly omega = r.multiply(inverse); - return new ModulusPoly[]{sigma, omega}; - } - - private int[] findErrorLocations(ModulusPoly errorLocator) throws ChecksumException { - // This is a direct application of Chien's search - int numErrors = errorLocator.getDegree(); - int[] result = new int[numErrors]; - int e = 0; - for (int i = 1; i < field.getSize() && e < numErrors; i++) { - if (errorLocator.evaluateAt(i) == 0) { - result[e] = field.inverse(i); - e++; - } - } - if (e != numErrors) { - throw ChecksumException.getChecksumInstance(); - } - return result; - } - - private int[] findErrorMagnitudes(ModulusPoly errorEvaluator, - ModulusPoly errorLocator, - int[] errorLocations) { - int errorLocatorDegree = errorLocator.getDegree(); - if (errorLocatorDegree < 1) { - return new int[0]; - } - int[] formalDerivativeCoefficients = new int[errorLocatorDegree]; - for (int i = 1; i <= errorLocatorDegree; i++) { - formalDerivativeCoefficients[errorLocatorDegree - i] = - field.multiply(i, errorLocator.getCoefficient(i)); - } - ModulusPoly formalDerivative = new ModulusPoly(field, formalDerivativeCoefficients); - - // This is directly applying Forney's Formula - int s = errorLocations.length; - int[] result = new int[s]; - for (int i = 0; i < s; i++) { - int xiInverse = field.inverse(errorLocations[i]); - int numerator = field.subtract(0, errorEvaluator.evaluateAt(xiInverse)); - int denominator = field.inverse(formalDerivative.evaluateAt(xiInverse)); - result[i] = field.multiply(numerator, denominator); - } - return result; - } -} diff --git a/src/pdf417/decoder/ec/ErrorCorrectionTestCase.java b/src/pdf417/decoder/ec/ErrorCorrectionTestCase.java deleted file mode 100644 index 7af2ae8..0000000 --- a/src/pdf417/decoder/ec/ErrorCorrectionTestCase.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2012 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. - */ - -package com.google.zxing.pdf417.decoder.ec; - -import com.google.zxing.ChecksumException; - -import org.junit.Test; - -import java.util.Random; - -/** - * @author Sean Owen - */ -public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCase { - - private static final int[] PDF417_TEST = { - 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, 778, 387, 89, 869, - 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, - 900, 900}; - private static final int[] PDF417_TEST_WITH_EC = { - 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, 778, 387, 89, 869, - 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, - 900, 900, 769, 843, 591, 910, 605, 206, 706, 917, 371, 469, 79, 718, 47, 777, 249, 262, 193, 620, 597, 477, 450, - 806, 908, 309, 153, 871, 686, 838, 185, 674, 68, 679, 691, 794, 497, 479, 234, 250, 496, 43, 347, 582, 882, 536, - 322, 317, 273, 194, 917, 237, 420, 859, 340, 115, 222, 808, 866, 836, 417, 121, 833, 459, 64, 159}; - private static final int ECC_BYTES = PDF417_TEST_WITH_EC.length - PDF417_TEST.length; - private static final int ERROR_LIMIT = ECC_BYTES; - private static final int MAX_ERRORS = ERROR_LIMIT / 2; - private static final int MAX_ERASURES = ERROR_LIMIT; - - private final ErrorCorrection ec = new ErrorCorrection(); - - @Test - public void testNoError() throws ChecksumException { - int[] received = PDF417_TEST_WITH_EC.clone(); - // no errors - checkDecode(received); - } - - @Test - public void testOneError() throws ChecksumException { - Random random = getRandom(); - for (int i = 0; i < PDF417_TEST_WITH_EC.length; i++) { - int[] received = PDF417_TEST_WITH_EC.clone(); - received[i] = random.nextInt(256); - checkDecode(received); - } - } - - @Test - public void testMaxErrors() throws ChecksumException { - Random random = getRandom(); - for (int testIterations = 0; testIterations < 100; testIterations++) { // # iterations is kind of arbitrary - int[] received = PDF417_TEST_WITH_EC.clone(); - corrupt(received, MAX_ERRORS, random); - checkDecode(received); - } - } - - @Test - public void testTooManyErrors() { - int[] received = PDF417_TEST_WITH_EC.clone(); - Random random = getRandom(); - corrupt(received, MAX_ERRORS + 1, random); - try { - checkDecode(received); - fail("Should not have decoded"); - } catch (ChecksumException ce) { - // good - } - } - - private void checkDecode(int[] received) throws ChecksumException { - checkDecode(received, new int[0]); - } - - private void checkDecode(int[] received, int[] erasures) throws ChecksumException { - ec.decode(received, ECC_BYTES, erasures); - for (int i = 0; i < PDF417_TEST.length; i++) { - assertEquals(received[i], PDF417_TEST[i]); - } - } - -} diff --git a/src/pdf417/decoder/ec/ModulusGF.java b/src/pdf417/decoder/ec/ModulusGF.java deleted file mode 100644 index 61c86c5..0000000 --- a/src/pdf417/decoder/ec/ModulusGF.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2012 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. - */ - -package com.google.zxing.pdf417.decoder.ec; - -import com.google.zxing.pdf417.PDF417Common; - -/** - *

A field based on powers of a generator integer, modulo some modulus.

- * - * @author Sean Owen - * @see com.google.zxing.common.reedsolomon.GenericGF - */ -public final class ModulusGF { - - public static final ModulusGF PDF417_GF = new ModulusGF(PDF417Common.NUMBER_OF_CODEWORDS, 3); - - private final int[] expTable; - private final int[] logTable; - private final ModulusPoly zero; - private final ModulusPoly one; - private final int modulus; - - private ModulusGF(int modulus, int generator) { - this.modulus = modulus; - expTable = new int[modulus]; - logTable = new int[modulus]; - int x = 1; - for (int i = 0; i < modulus; i++) { - expTable[i] = x; - x = (x * generator) % modulus; - } - for (int i = 0; i < modulus - 1; i++) { - logTable[expTable[i]] = i; - } - // logTable[0] == 0 but this should never be used - zero = new ModulusPoly(this, new int[]{0}); - one = new ModulusPoly(this, new int[]{1}); - } - - - ModulusPoly getZero() { - return zero; - } - - ModulusPoly getOne() { - return one; - } - - ModulusPoly buildMonomial(int degree, int coefficient) { - if (degree < 0) { - throw new IllegalArgumentException(); - } - if (coefficient == 0) { - return zero; - } - int[] coefficients = new int[degree + 1]; - coefficients[0] = coefficient; - return new ModulusPoly(this, coefficients); - } - - int add(int a, int b) { - return (a + b) % modulus; - } - - int subtract(int a, int b) { - return (modulus + a - b) % modulus; - } - - int exp(int a) { - return expTable[a]; - } - - int log(int a) { - if (a == 0) { - throw new IllegalArgumentException(); - } - return logTable[a]; - } - - int inverse(int a) { - if (a == 0) { - throw new ArithmeticException(); - } - return expTable[modulus - logTable[a] - 1]; - } - - int multiply(int a, int b) { - if (a == 0 || b == 0) { - return 0; - } - return expTable[(logTable[a] + logTable[b]) % (modulus - 1)]; - } - - int getSize() { - return modulus; - } - -} diff --git a/src/pdf417/decoder/ec/ModulusPoly.java b/src/pdf417/decoder/ec/ModulusPoly.java deleted file mode 100644 index acc49b7..0000000 --- a/src/pdf417/decoder/ec/ModulusPoly.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2012 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. - */ - -package com.google.zxing.pdf417.decoder.ec; - -/** - * @author Sean Owen - */ -final class ModulusPoly { - - private final ModulusGF field; - private final int[] coefficients; - - ModulusPoly(ModulusGF field, int[] coefficients) { - if (coefficients.length == 0) { - throw new IllegalArgumentException(); - } - this.field = field; - int coefficientsLength = coefficients.length; - if (coefficientsLength > 1 && coefficients[0] == 0) { - // Leading term must be non-zero for anything except the constant polynomial "0" - int firstNonZero = 1; - while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) { - firstNonZero++; - } - if (firstNonZero == coefficientsLength) { - this.coefficients = new int[]{0}; - } else { - this.coefficients = new int[coefficientsLength - firstNonZero]; - System.arraycopy(coefficients, - firstNonZero, - this.coefficients, - 0, - this.coefficients.length); - } - } else { - this.coefficients = coefficients; - } - } - - int[] getCoefficients() { - return coefficients; - } - - /** - * @return degree of this polynomial - */ - int getDegree() { - return coefficients.length - 1; - } - - /** - * @return true iff this polynomial is the monomial "0" - */ - boolean isZero() { - return coefficients[0] == 0; - } - - /** - * @return coefficient of x^degree term in this polynomial - */ - int getCoefficient(int degree) { - return coefficients[coefficients.length - 1 - degree]; - } - - /** - * @return evaluation of this polynomial at a given point - */ - int evaluateAt(int a) { - if (a == 0) { - // Just return the x^0 coefficient - return getCoefficient(0); - } - if (a == 1) { - // Just the sum of the coefficients - int result = 0; - for (int coefficient : coefficients) { - result = field.add(result, coefficient); - } - return result; - } - int result = coefficients[0]; - int size = coefficients.length; - for (int i = 1; i < size; i++) { - result = field.add(field.multiply(a, result), coefficients[i]); - } - return result; - } - - ModulusPoly add(ModulusPoly other) { - if (!field.equals(other.field)) { - throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); - } - if (isZero()) { - return other; - } - if (other.isZero()) { - return this; - } - - int[] smallerCoefficients = this.coefficients; - int[] largerCoefficients = other.coefficients; - if (smallerCoefficients.length > largerCoefficients.length) { - int[] temp = smallerCoefficients; - smallerCoefficients = largerCoefficients; - largerCoefficients = temp; - } - int[] sumDiff = new int[largerCoefficients.length]; - int lengthDiff = largerCoefficients.length - smallerCoefficients.length; - // Copy high-order terms only found in higher-degree polynomial's coefficients - System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); - - for (int i = lengthDiff; i < largerCoefficients.length; i++) { - sumDiff[i] = field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); - } - - return new ModulusPoly(field, sumDiff); - } - - ModulusPoly subtract(ModulusPoly other) { - if (!field.equals(other.field)) { - throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); - } - if (other.isZero()) { - return this; - } - return add(other.negative()); - } - - ModulusPoly multiply(ModulusPoly other) { - if (!field.equals(other.field)) { - throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field"); - } - if (isZero() || other.isZero()) { - return field.getZero(); - } - int[] aCoefficients = this.coefficients; - int aLength = aCoefficients.length; - int[] bCoefficients = other.coefficients; - int bLength = bCoefficients.length; - int[] product = new int[aLength + bLength - 1]; - for (int i = 0; i < aLength; i++) { - int aCoeff = aCoefficients[i]; - for (int j = 0; j < bLength; j++) { - product[i + j] = field.add(product[i + j], field.multiply(aCoeff, bCoefficients[j])); - } - } - return new ModulusPoly(field, product); - } - - ModulusPoly negative() { - int size = coefficients.length; - int[] negativeCoefficients = new int[size]; - for (int i = 0; i < size; i++) { - negativeCoefficients[i] = field.subtract(0, coefficients[i]); - } - return new ModulusPoly(field, negativeCoefficients); - } - - ModulusPoly multiply(int scalar) { - if (scalar == 0) { - return field.getZero(); - } - if (scalar == 1) { - return this; - } - int size = coefficients.length; - int[] product = new int[size]; - for (int i = 0; i < size; i++) { - product[i] = field.multiply(coefficients[i], scalar); - } - return new ModulusPoly(field, product); - } - - ModulusPoly multiplyByMonomial(int degree, int coefficient) { - if (degree < 0) { - throw new IllegalArgumentException(); - } - if (coefficient == 0) { - return field.getZero(); - } - int size = coefficients.length; - int[] product = new int[size + degree]; - for (int i = 0; i < size; i++) { - product[i] = field.multiply(coefficients[i], coefficient); - } - return new ModulusPoly(field, product); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(8 * getDegree()); - for (int degree = getDegree(); degree >= 0; degree--) { - int coefficient = getCoefficient(degree); - if (coefficient != 0) { - if (coefficient < 0) { - result.append(" - "); - coefficient = -coefficient; - } else { - if (result.length() > 0) { - result.append(" + "); - } - } - if (degree == 0 || coefficient != 1) { - result.append(coefficient); - } - if (degree != 0) { - if (degree == 1) { - result.append('x'); - } else { - result.append("x^"); - result.append(degree); - } - } - } - } - return result.toString(); - } - -} diff --git a/src/pdf417/decoder/ec/abstract_error_correction_test_case.rs b/src/pdf417/decoder/ec/abstract_error_correction_test_case.rs new file mode 100644 index 0000000..19ebdfb --- /dev/null +++ b/src/pdf417/decoder/ec/abstract_error_correction_test_case.rs @@ -0,0 +1,56 @@ +/* + * Copyright 2012 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 rand::Rng; + +use crate::common::reedsolomon::ReedSolomonTestCase; + +/** + * @author Sean Owen + */ + +pub fn corrupt(received: &mut [u32], howMany: u32, random: &mut rand::rngs::ThreadRng) { + let mut pass: Vec = received.iter().map(|x| *x as i32).collect(); + ReedSolomonTestCase::corrupt(&mut pass, howMany as i32, random, 929); + for i in 0..received.len() { + received[i] = pass[i] as u32; + } +} + +pub fn erase(received: &mut [u32], howMany: u32, random: &mut rand::rngs::ThreadRng) -> Vec { + let mut erased = vec![false; received.len()]; //BitSet::new(received.len()); + let mut erasures = vec![0_u32; howMany as usize]; + let mut erasureOffset = 0; + let mut j = 0; + while j < howMany { + // for (int j = 0; j < howMany; j++) { + let location = random.gen_range(0..received.len()); //random.nextInt(received.len()); + if *erased.get(location).unwrap() { + j -= 1; + } else { + erased[location] = true; + received[location] = 0; + erasures[erasureOffset] = location as u32; + erasureOffset += 1; + } + j += 1; + } + erasures +} + +pub fn getRandom() -> rand::rngs::ThreadRng { + rand::thread_rng() +} diff --git a/src/pdf417/decoder/ec/error_correction.rs b/src/pdf417/decoder/ec/error_correction.rs new file mode 100644 index 0000000..16a9fc7 --- /dev/null +++ b/src/pdf417/decoder/ec/error_correction.rs @@ -0,0 +1,235 @@ +/* + * Copyright 2012 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 std::rc::Rc; + +use crate::{ + pdf417::{decoder::ec::ModulusGF, pdf_417_common::NUMBER_OF_CODEWORDS}, + Exceptions, +}; + +use super::ModulusPoly; + +use lazy_static::lazy_static; + +lazy_static! { + // static ref PDF417_GF : Rc<&ModulusGF> = Rc::new(&ModulusGF::new(NUMBER_OF_CODEWORDS, 3)); + static ref fld_interior : ModulusGF = ModulusGF::new(NUMBER_OF_CODEWORDS, 3); +} + +/** + *

PDF417 error correction implementation.

+ * + *

This example + * is quite useful in understanding the algorithm.

+ * + * @author Sean Owen + * @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder + */ + +/** + * @param received received codewords + * @param numECCodewords number of those codewords used for EC + * @param erasures location of erasures + * @return number of errors + * @throws ChecksumException if errors cannot be corrected, maybe because of too many errors + */ +pub fn decode<'a>( + received: &mut [u32], + numECCodewords: u32, + erasures: &mut [u32], +) -> Result { + let field: Rc<&'static ModulusGF> = Rc::new(&fld_interior); + let poly = ModulusPoly::new(field.clone(), received.to_vec())?; + let mut S = vec![0u32; numECCodewords as usize]; + let mut error = false; + for i in (1..=numECCodewords).rev() { + // for (int i = numECCodewords; i > 0; i--) { + let eval = poly.evaluateAt(field.exp(i)); + S[(numECCodewords - i) as usize] = eval; + if eval != 0 { + error = true; + } + } + + if !error { + return Ok(0); + } + + let mut knownErrors: Rc = ModulusPoly::getOne(field.clone()); + let mut b; + let mut term; + let mut kE: Rc; + if !erasures.is_empty() { + for erasure in erasures { + // for (int erasure : erasures) { + b = field.exp(received.len() as u32 - 1 - *erasure); + // Add (1 - bx) term: + term = ModulusPoly::new(field.clone(), vec![field.subtract(0, b), 1])?; + kE = knownErrors.clone(); + knownErrors = kE.multiply(Rc::new(term))?; + } + } + + let syndrome = Rc::new(ModulusPoly::new(field.clone(), S)?); + //syndrome = syndrome.multiply(knownErrors); + + let sigmaOmega = runEuclideanAlgorithm( + ModulusPoly::buildMonomial(field.clone(), numECCodewords as usize, 1), + syndrome, + numECCodewords, + field.clone(), + )?; + let sigma = sigmaOmega[0].clone(); + let omega = sigmaOmega[1].clone(); + + //sigma = sigma.multiply(knownErrors); + + let mut errorLocations = findErrorLocations(sigma.clone(), field.clone())?; + let errorMagnitudes = findErrorMagnitudes(omega, sigma, &mut errorLocations, field.clone()); + + for i in 0..errorLocations.len() { + // for (int i = 0; i < errorLocations.length; i++) { + let position = received.len() as isize - 1 - field.log(errorLocations[i])? as isize; + if position < 0 { + return Err(Exceptions::ChecksumException(format!("{}", file!()))); + } + received[position as usize] = + field.subtract(received[position as usize], errorMagnitudes[i]); + } + + Ok(errorLocations.len()) +} + +fn runEuclideanAlgorithm( + a: Rc, + b: Rc, + R: u32, + field: Rc<&'static ModulusGF>, +) -> Result<[Rc; 2], Exceptions> { + // Assume a's degree is >= b's + let mut a = a; + let mut b = b; + if a.getDegree() < b.getDegree() { + let temp = a; + a = b; + b = temp; + } + + let mut rLast = a; + let mut r = b; + let mut tLast = ModulusPoly::getZero(field.clone()); + let mut t = ModulusPoly::getOne(field.clone()); + + // Run Euclidean algorithm until r's degree is less than R/2 + while r.getDegree() >= R / 2 { + let rLastLast = rLast.clone(); + let tLastLast = tLast.clone(); + rLast = r; + tLast = t; + + // Divide rLastLast by rLast, with quotient in q and remainder in r + if rLast.isZero() { + // Oops, Euclidean algorithm already terminated? + return Err(Exceptions::ChecksumException(format!("{}", file!()))); + } + r = rLastLast; + let mut q = ModulusPoly::getZero(field.clone()); //field.getZero(); + let denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree() as usize); + let dltInverse = field.inverse(denominatorLeadingTerm)?; + while r.getDegree() >= rLast.getDegree() && !r.isZero() { + let degreeDiff = r.getDegree() - rLast.getDegree(); + let scale = field.multiply(r.getCoefficient(r.getDegree() as usize), dltInverse); + q = q.add(ModulusPoly::buildMonomial( + field.clone(), + degreeDiff as usize, + scale, + ))?; + r = r.subtract(rLast.multiplyByMonomial(degreeDiff as usize, scale))?; + } + + t = q.multiply(tLast.clone())?.subtract(tLastLast)?.negative(); + } + + let sigmaTildeAtZero = t.getCoefficient(0); + if sigmaTildeAtZero == 0 { + return Err(Exceptions::ChecksumException(format!("{}", file!()))); + } + + let inverse = field.inverse(sigmaTildeAtZero)?; + let sigma = t.multiplyByScaler(inverse); + let omega = r.multiplyByScaler(inverse); + + Ok([sigma, omega]) +} + +fn findErrorLocations( + errorLocator: Rc, + field: Rc<&ModulusGF>, +) -> Result, Exceptions> { + // This is a direct application of Chien's search + let numErrors = errorLocator.getDegree(); + let mut result = vec![0u32; numErrors as usize]; + let mut e = 0; + let mut i = 1; + while i < field.getSize() && e < numErrors { + // for (int i = 1; i < PDF417_GF.getSize() && e < numErrors; i++) { + if errorLocator.evaluateAt(i) == 0 { + result[e as usize] = field.inverse(i)?; + e += 1; + } + i += 1; + } + if e != numErrors { + return Err(Exceptions::ChecksumException(format!("{}", file!()))); + } + Ok(result) +} + +fn findErrorMagnitudes( + errorEvaluator: Rc, + errorLocator: Rc, + errorLocations: &mut [u32], + field: Rc<&'static ModulusGF>, +) -> Vec { + let errorLocatorDegree = errorLocator.getDegree(); + if errorLocatorDegree < 1 { + return vec![0; 0]; + } + let mut formalDerivativeCoefficients = vec![0u32; errorLocatorDegree as usize]; + for i in 1..=errorLocatorDegree { + // for (int i = 1; i <= errorLocatorDegree; i++) { + formalDerivativeCoefficients[errorLocatorDegree as usize - i as usize] = + field.multiply(i, errorLocator.getCoefficient(i as usize)); + } + let formalDerivative = ModulusPoly::new(field.clone(), formalDerivativeCoefficients) + .expect("should generate good poly"); + + // This is directly applying Forney's Formula + let s = errorLocations.len(); + let mut result = vec![0u32; s]; + for i in 0..s { + // for (int i = 0; i < s; i++) { + let xiInverse = field.inverse(errorLocations[i]).expect("must invert"); + let numerator = field.subtract(0, errorEvaluator.evaluateAt(xiInverse)); + let denominator = field + .inverse(formalDerivative.evaluateAt(xiInverse)) + .expect("must invert"); + result[i] = field.multiply(numerator, denominator); + } + + result +} diff --git a/src/pdf417/decoder/ec/error_correction_test_case.rs b/src/pdf417/decoder/ec/error_correction_test_case.rs new file mode 100644 index 0000000..19779ce --- /dev/null +++ b/src/pdf417/decoder/ec/error_correction_test_case.rs @@ -0,0 +1,115 @@ +/* + * Copyright 2012 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 rand::Rng; + +use crate::{datamatrix::encoder::error_correction, Exceptions}; + +use super::{ + abstract_error_correction_test_case::{corrupt, getRandom}, + error_correction::decode, +}; + +/** + * @author Sean Owen + */ + +const PDF417_TEST: [u32; 48] = [ + 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, + 778, 387, 89, 869, 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, + 900, 900, 900, 900, 900, 900, 900, 900, 900, +]; +const PDF417_TEST_WITH_EC: [u32; 112] = [ + 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, + 778, 387, 89, 869, 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, + 900, 900, 900, 900, 900, 900, 900, 900, 900, 769, 843, 591, 910, 605, 206, 706, 917, 371, 469, + 79, 718, 47, 777, 249, 262, 193, 620, 597, 477, 450, 806, 908, 309, 153, 871, 686, 838, 185, + 674, 68, 679, 691, 794, 497, 479, 234, 250, 496, 43, 347, 582, 882, 536, 322, 317, 273, 194, + 917, 237, 420, 859, 340, 115, 222, 808, 866, 836, 417, 121, 833, 459, 64, 159, +]; +const ECC_BYTES: usize = PDF417_TEST_WITH_EC.len() - PDF417_TEST.len(); +const ERROR_LIMIT: usize = ECC_BYTES; +const MAX_ERRORS: usize = ERROR_LIMIT / 2; +const _MAX_ERASURES: usize = ERROR_LIMIT; + +// private final ErrorCorrection ec = new ErrorCorrection(); + +#[test] +fn testNoError() { + let mut received = PDF417_TEST_WITH_EC.clone(); + // no errors + checkDecode(&mut received).expect("ok"); +} + +#[test] +fn testExplicitError() { + let mut random = getRandom(); + for i in 0..PDF417_TEST_WITH_EC.len() { + // for (int i = 0; i < PDF417_TEST_WITH_EC.length; i++) { + let mut received = PDF417_TEST_WITH_EC.clone(); + received[i] = 610; //random.gen_range(0..256);// random.nextInt(256); + checkDecode(&mut received).expect("ok"); + } +} + +#[test] +fn testOneError() { + let mut random = getRandom(); + for i in 0..PDF417_TEST_WITH_EC.len() { + // for (int i = 0; i < PDF417_TEST_WITH_EC.length; i++) { + let mut received = PDF417_TEST_WITH_EC.clone(); + received[i] = random.gen_range(0..256); //random.gen_range(0..256);// random.nextInt(256); + checkDecode(&mut received).expect("ok"); + } +} + +#[test] +fn testMaxErrors() { + let mut random = getRandom(); + for _testIterations in 0..100 { + // for (int testIterations = 0; testIterations < 100; testIterations++) { // # iterations is kind of arbitrary + let mut received = PDF417_TEST_WITH_EC.clone(); + corrupt(&mut received, MAX_ERRORS as u32, &mut random); + checkDecode(&mut received).expect("ok"); + } +} + +#[test] +fn testTooManyErrors() { + let mut received = PDF417_TEST_WITH_EC.clone(); + let mut random = getRandom(); + corrupt(&mut received, MAX_ERRORS as u32 + 1, &mut random); + // try { + assert!(checkDecode(&mut received).is_err()); + // fail("Should not have decoded"); + // } catch (ChecksumException ce) { + // // good + // } +} + +fn checkDecode(received: &mut [u32]) -> Result<(), Exceptions> { + checkDecodeErasures(received, &mut [0_u32; 0]) +} + +fn checkDecodeErasures(received: &mut [u32], erasures: &mut [u32]) -> Result<(), Exceptions> { + decode(received, ECC_BYTES as u32, erasures)?; + // ec.decode(received, ECC_BYTES, erasures); + for i in 0..PDF417_TEST.len() { + // for (int i = 0; i < PDF417_TEST.length; i++) { + assert_eq!(received[i], PDF417_TEST[i]); + } + Ok(()) +} diff --git a/src/pdf417/decoder/ec/mod.rs b/src/pdf417/decoder/ec/mod.rs index 8b13789..7966072 100644 --- a/src/pdf417/decoder/ec/mod.rs +++ b/src/pdf417/decoder/ec/mod.rs @@ -1 +1,12 @@ +mod modulus_gf; +pub use modulus_gf::*; +mod modulus_poly; +pub use modulus_poly::*; + +pub mod error_correction; + +#[cfg(test)] +mod abstract_error_correction_test_case; +#[cfg(test)] +mod error_correction_test_case; diff --git a/src/pdf417/decoder/ec/modulus_gf.rs b/src/pdf417/decoder/ec/modulus_gf.rs new file mode 100644 index 0000000..3a1bdba --- /dev/null +++ b/src/pdf417/decoder/ec/modulus_gf.rs @@ -0,0 +1,117 @@ +/* + * Copyright 2012 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. + */ + +//public static final ModulusGF PDF417_GF = new ModulusGF(PDF417Common.NUMBER_OF_CODEWORDS, 3); + +use std::rc::Rc; + +use crate::Exceptions; + +use super::ModulusPoly; + +/** + *

A field based on powers of a generator integer, modulo some modulus.

+ * + * @author Sean Owen + * @see com.google.zxing.common.reedsolomon.GenericGF + */ +#[derive(Debug, Clone)] +pub struct ModulusGF { + expTable: Vec, + logTable: Vec, + // zero: Option>>, + // one: Option>>, + modulus: u32, + generator: u32, +} +impl ModulusGF { + pub fn new(modulus: u32, generator: u32) -> Self { + let mut expTable = vec![0u32; modulus as usize]; //new int[modulus]; + let mut logTable = vec![0u32; modulus as usize]; //new int[modulus]; + let mut x = 1; + for i in 0..modulus as usize { + // for (int i = 0; i < modulus; i++) { + expTable[i] = x; + x = (x * generator) % modulus; + } + for i in 0..modulus as usize - 1 { + // for (int i = 0; i < modulus - 1; i++) { + logTable[expTable[i] as usize] = i as u32; + } + // logTable[0] == 0 but this should never be used + + let potential_self = Self { + expTable, + logTable, + // zero: None, + // one: None, + modulus, + generator, + }; + // zero = new ModulusPoly(this, new int[]{0}); + // one = new ModulusPoly(this, new int[]{1}); + + potential_self + } + + pub fn add(&self, a: u32, b: u32) -> u32 { + (a + b) % self.modulus + } + + pub fn subtract(&self, a: u32, b: u32) -> u32 { + (self.modulus + a - b) % self.modulus + } + + pub fn exp(&self, a: u32) -> u32 { + self.expTable[a as usize] + } + + pub fn log(&self, a: u32) -> Result { + if a == 0 { + Err(Exceptions::ArithmeticException("".to_owned())) + } else { + Ok(self.logTable[a as usize]) + } + } + + pub fn inverse(&self, a: u32) -> Result { + if a == 0 { + Err(Exceptions::ArithmeticException("".to_owned())) + } else { + Ok(self.expTable[self.modulus as usize - self.logTable[a as usize] as usize - 1]) + } + } + + pub fn multiply(&self, a: u32, b: u32) -> u32 { + if a == 0 || b == 0 { + 0 + } else { + self.expTable[(self.logTable[a as usize] + self.logTable[b as usize]) as usize + % (self.modulus - 1) as usize] + } + } + + pub fn getSize(&self) -> u32 { + self.modulus + } +} + +impl PartialEq for ModulusGF { + fn eq(&self, other: &Self) -> bool { + self.modulus == other.modulus && self.generator == other.generator + } +} +impl Eq for ModulusGF {} diff --git a/src/pdf417/decoder/ec/modulus_poly.rs b/src/pdf417/decoder/ec/modulus_poly.rs new file mode 100644 index 0000000..1e3bd67 --- /dev/null +++ b/src/pdf417/decoder/ec/modulus_poly.rs @@ -0,0 +1,321 @@ +/* + * Copyright 2012 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 std::rc::Rc; + +use crate::Exceptions; + +use super::ModulusGF; + +/** + * @author Sean Owen + */ +#[derive(Clone, Debug)] +pub struct ModulusPoly { + field: Rc<&'static ModulusGF>, + coefficients: Vec, + // zero: Option>, + // one: Option>, +} +impl ModulusPoly { + pub fn new( + field: Rc<&'static ModulusGF>, + coefficients: Vec, + ) -> Result { + if coefficients.is_empty() { + return Err(Exceptions::IllegalArgumentException("".to_owned())); + } + let orig_coefs = coefficients.clone(); + let mut coefficients = coefficients; + let coefficientsLength = coefficients.len(); + if coefficientsLength > 1 && coefficients[0] == 0 { + // Leading term must be non-zero for anything except the constant polynomial "0" + let mut firstNonZero = 1; + while firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0 { + firstNonZero += 1; + } + if firstNonZero == coefficientsLength { + coefficients = vec![0]; + } else { + coefficients = vec![0u32; coefficientsLength - firstNonZero]; + coefficients[..].copy_from_slice(&&orig_coefs[firstNonZero..]); + // System.arraycopy(coefficients, + // firstNonZero, + // this.coefficients, + // 0, + // this.coefficients.length); + } + } else { + coefficients = coefficients; + } + + Ok(ModulusPoly { + field: field.clone(), + coefficients: coefficients, + // zero: Some(Self::getZero(field.clone())), + // one: Some(Self::getOne(field.clone())), + }) + } + + pub fn getCoefficients(&self) -> &[u32] { + &self.coefficients + } + + /** + * @return degree of this polynomial + */ + pub fn getDegree(&self) -> u32 { + self.coefficients.len() as u32 - 1 + } + + /** + * @return true iff this polynomial is the monomial "0" + */ + pub fn isZero(&self) -> bool { + self.coefficients[0] == 0 + } + + /** + * @return coefficient of x^degree term in this polynomial + */ + pub fn getCoefficient(&self, degree: usize) -> u32 { + self.coefficients[self.coefficients.len() - 1 - degree] + } + + /** + * @return evaluation of this polynomial at a given point + */ + pub fn evaluateAt(&self, a: u32) -> u32 { + if a == 0 { + // Just return the x^0 coefficient + return self.getCoefficient(0); + } + if a == 1 { + // Just the sum of the coefficients + let mut result = 0; + for coefficient in self.coefficients.iter() { + // for (int coefficient : coefficients) { + result = self.field.add(result, *coefficient); + } + return result; + } + let mut result = self.coefficients[0]; + let size = self.coefficients.len(); + for i in 1..size { + // for (int i = 1; i < size; i++) { + result = self + .field + .add(self.field.multiply(a, result), self.coefficients[i]); + } + return result; + } + + pub fn add(&self, other: Rc) -> Result, Exceptions> { + if self.field != other.field { + return Err(Exceptions::IllegalArgumentException( + "ModulusPolys do not have same ModulusGF field".to_owned(), + )); + } + if self.isZero() { + return Ok(other); + } + if other.isZero() { + return Ok(Rc::new(self.clone())); + } + + let mut smallerCoefficients = &self.coefficients; + let mut largerCoefficients = &other.coefficients; + if smallerCoefficients.len() > largerCoefficients.len() { + let temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + let mut sumDiff = vec![0032; largerCoefficients.len()]; + let lengthDiff = largerCoefficients.len() - smallerCoefficients.len(); + // Copy high-order terms only found in higher-degree polynomial's coefficients + sumDiff[..lengthDiff].copy_from_slice(&largerCoefficients[..lengthDiff]); + // System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); + + for i in lengthDiff..largerCoefficients.len() { + // for (int i = lengthDiff; i < largerCoefficients.length; i++) { + sumDiff[i] = self + .field + .add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + + Ok(Rc::new( + ModulusPoly::new(self.field.clone(), sumDiff) + .expect("should always generate with known goods"), + )) + } + + pub fn subtract(&self, other: Rc) -> Result, Exceptions> { + if self.field != other.field { + return Err(Exceptions::IllegalArgumentException( + "ModulusPolys do not have same ModulusGF field".to_owned(), + )); + } + if other.isZero() { + return Ok(Rc::new(self.clone())); + }; + self.add(other.negative().clone()) + } + + pub fn multiply(&self, other: Rc) -> Result, Exceptions> { + if !(self.field == other.field) { + return Err(Exceptions::IllegalArgumentException( + "ModulusPolys do not have same ModulusGF field".to_owned(), + )); + } + if self.isZero() || other.isZero() { + return Ok(Self::getZero(self.field.clone()).clone()); + } + let aCoefficients = &self.coefficients; + let aLength = aCoefficients.len(); + let bCoefficients = &other.coefficients; + let bLength = bCoefficients.len(); + let mut product = vec![0u32; aLength + bLength - 1]; + for i in 0..aLength { + // for (int i = 0; i < aLength; i++) { + let aCoeff = aCoefficients[i]; + for j in 0..bLength { + // for (int j = 0; j < bLength; j++) { + product[i + j] = self.field.add( + product[i + j], + self.field.multiply(aCoeff, bCoefficients[j]), + ); + } + } + + Ok(Rc::new( + ModulusPoly::new(self.field.clone(), product) + .expect("should always generate with known goods"), + )) + } + + pub fn negative(&self) -> Rc { + let size = self.coefficients.len(); + let mut negativeCoefficients = vec![0u32; size]; + for i in 0..size { + // for (int i = 0; i < size; i++) { + negativeCoefficients[i] = self.field.subtract(0, self.coefficients[i]); + } + Rc::new( + ModulusPoly::new(self.field.clone(), negativeCoefficients) + .expect("should always generate with known goods"), + ) + } + + pub fn multiplyByScaler(&self, scalar: u32) -> Rc { + if scalar == 0 { + return Self::getZero(self.field.clone()).clone(); + } + if scalar == 1 { + return Rc::new(self.clone()); + } + let size = self.coefficients.len(); + let mut product = vec![0u32; size]; + for i in 0..size { + // for (int i = 0; i < size; i++) { + product[i] = self.field.multiply(self.coefficients[i], scalar); + } + + Rc::new( + ModulusPoly::new(self.field.clone(), product) + .expect("should always generate with known goods"), + ) + } + + pub fn multiplyByMonomial(&self, degree: usize, coefficient: u32) -> Rc { + if coefficient == 0 { + return Self::getZero(self.field.clone()).clone(); + } + let size = self.coefficients.len(); + let mut product = vec![0u32; size + degree]; + for i in 0..size { + // for (int i = 0; i < size; i++) { + product[i] = self.field.multiply(self.coefficients[i], coefficient); + } + + Rc::new( + ModulusPoly::new(self.field.clone(), product) + .expect("should always generate with known goods"), + ) + } + + pub fn getZero(field: Rc<&'static ModulusGF>) -> Rc { + Rc::new( + ModulusPoly::new(field.clone(), vec![0]) + .expect("should always generate with known goods"), + ) + } + + pub fn getOne(field: Rc<&'static ModulusGF>) -> Rc { + Rc::new( + ModulusPoly::new(field.clone(), vec![1]) + .expect("should always generate with known goods"), + ) + } + + pub fn buildMonomial( + field: Rc<&'static ModulusGF>, + degree: usize, + coefficient: u32, + ) -> Rc { + // if degree < 0 { + // throw new IllegalArgumentException(); + // } + if coefficient == 0 { + return Self::getZero(field); + } + let mut coefficients = vec![0_u32; degree + 1]; + coefficients[0] = coefficient; + Rc::new( + ModulusPoly::new(field.clone(), coefficients) + .expect("should always generate with known goods"), + ) + } + + // @Override + // public String toString() { + // StringBuilder result = new StringBuilder(8 * getDegree()); + // for (int degree = getDegree(); degree >= 0; degree--) { + // int coefficient = getCoefficient(degree); + // if (coefficient != 0) { + // if (coefficient < 0) { + // result.append(" - "); + // coefficient = -coefficient; + // } else { + // if (result.length() > 0) { + // result.append(" + "); + // } + // } + // if (degree == 0 || coefficient != 1) { + // result.append(coefficient); + // } + // if (degree != 0) { + // if (degree == 1) { + // result.append('x'); + // } else { + // result.append("x^"); + // result.append(degree); + // } + // } + // } + // } + // return result.toString(); + // } +}