squash changes

This commit is contained in:
Mark 2014-10-27 18:35:26 +01:00
parent b8bafbe5c9
commit b04edfec68
11 changed files with 377 additions and 53 deletions

View file

@ -30,8 +30,23 @@ public final class ChecksumException extends ReaderException {
// do nothing
}
public static ChecksumException getChecksumInstance() {
return instance;
private ChecksumException(Throwable cause) {
super(cause);
}
public static ChecksumException getChecksumInstance() {
if (isStackTrace) {
return new ChecksumException();
} else {
return instance;
}
}
public static ChecksumException getChecksumInstance(Throwable cause) {
if (isStackTrace) {
return new ChecksumException(cause);
} else {
return instance;
}
}
}

View file

@ -28,11 +28,25 @@ public final class FormatException extends ReaderException {
private static final FormatException instance = new FormatException();
private FormatException() {
// do nothing
}
private FormatException(Throwable cause) {
super(cause);
}
public static FormatException getFormatInstance() {
return instance;
if (isStackTrace) {
return new FormatException();
} else {
return instance;
}
}
public static FormatException getFormatInstance(Throwable cause) {
if (isStackTrace) {
return new FormatException(cause);
} else {
return instance;
}
}
}

View file

@ -25,10 +25,17 @@ package com.google.zxing;
*/
public abstract class ReaderException extends Exception {
// disable stack traces when not running inside test units
protected static final boolean isStackTrace = System.getProperty("surefire.test.class.path") != null;
ReaderException() {
// do nothing
}
ReaderException(Throwable cause) {
super(cause);
}
// Prevent stack traces from being taken
// srowen says: huh, my IDE is saying this is not an override. native methods can't be overridden?
// This, at least, does not hurt. Because we use a singleton pattern here, it doesn't matter anyhow.

View file

@ -229,8 +229,8 @@ public final class Decoder {
try {
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
rsDecoder.decode(dataWords, numECCodewords);
} catch (ReedSolomonException ignored) {
throw FormatException.getFormatInstance();
} catch (ReedSolomonException ex) {
throw FormatException.getFormatInstance(ex);
}
// Now perform the unstuffing operation.

View file

@ -62,6 +62,65 @@ public final class BitMatrix implements Cloneable {
this.bits = bits;
}
public static BitMatrix parse(String stringRepresentation, String setString, String unsetString) {
int pos = 0;
if (stringRepresentation == null) {
throw new IllegalArgumentException();
}
boolean[] bits = new boolean[stringRepresentation.length()];
int bitsPos = 0;
int rowStartPos = 0;
int rowLength = -1;
int nRows = 0;
while (pos < stringRepresentation.length()) {
if (stringRepresentation.substring(pos, pos + 1).equals("\n") || stringRepresentation.substring(pos, pos + 1).equals("\r")) {
if (bitsPos > rowStartPos) {
if(rowLength == -1) {
rowLength = bitsPos - rowStartPos;
}
else if (bitsPos - rowStartPos != rowLength) {
throw new IllegalArgumentException("row lengths do not match");
}
rowStartPos = bitsPos;
nRows++;
}
pos++;
}
else if (stringRepresentation.substring(pos, pos + setString.length()).equals(setString)) {
pos += setString.length();
bits[bitsPos] = true;
bitsPos++;
}
else if (stringRepresentation.substring(pos, pos + unsetString.length()).equals(unsetString)) {
pos += unsetString.length();
bits[bitsPos] = false;
bitsPos++;
} else {
throw new IllegalArgumentException("illegal character encountered: " + stringRepresentation.substring(pos));
}
}
// no EOL at end?
if (bitsPos > rowStartPos) {
if(rowLength == -1) {
rowLength = bitsPos - rowStartPos;
}
else if (bitsPos - rowStartPos != rowLength) {
throw new IllegalArgumentException("row lengths do not match");
}
nRows++;
}
BitMatrix matrix = new BitMatrix(rowLength, nRows);
for (int i=0; i<bitsPos; i++) {
if (bits[i]) {
matrix.set(i % rowLength, i / rowLength);
}
}
return matrix;
}
/**
* <p>Gets the requested bit, where true means black.</p>
*
@ -85,6 +144,11 @@ public final class BitMatrix implements Cloneable {
bits[offset] |= 1 << (x & 0x1f);
}
public void unset(int x, int y) {
int offset = y * rowSize + (x / 32);
bits[offset] &= ~(1 << (x & 0x1f));
}
/**
* <p>Flips the given bit.</p>
*
@ -96,6 +160,27 @@ public final class BitMatrix implements Cloneable {
bits[offset] ^= 1 << (x & 0x1f);
}
/**
* <p>XOR for {@link BitMatrix}.</p>
* Flip the bit in this {@link BitMatrix} if the corresponding mask bit is set.
*
* @param mask
*/
public void xor(BitMatrix mask) {
if (width != mask.getWidth() || height != mask.getHeight()
|| rowSize != mask.getRowSize()) {
throw new IllegalArgumentException("input matrix dimensions do not match");
}
BitArray rowArray = new BitArray(width/32+1);
for (int y = 0; y < height; y++) {
int offset = y * rowSize;
int[] row = mask.getRow(y, rowArray).getBitArray();
for (int x = 0; x < rowSize; x++) {
bits[offset + x] ^= row[x];
}
}
}
/**
* Clears all bits (sets to false).
*/
@ -295,6 +380,13 @@ public final class BitMatrix implements Cloneable {
return height;
}
/**
* @return The row size of the matrix
*/
public int getRowSize() {
return rowSize;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof BitMatrix)) {
@ -317,12 +409,20 @@ public final class BitMatrix implements Cloneable {
@Override
public String toString() {
return toString("X ", " ");
}
public String toString(String setString, String unsetString) {
return toString(setString, unsetString, System.lineSeparator());
}
public String toString(String setString, String unsetString, String lineSeparator) {
StringBuilder result = new StringBuilder(height * (width + 1));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
result.append(get(x, y) ? "X " : " ");
result.append(get(x, y) ? setString : unsetString);
}
result.append('\n');
result.append(lineSeparator);
}
return result.toString();
}

View file

@ -0,0 +1,117 @@
/*
* Copyright 2014 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.aztec.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.ResultPoint;
import com.google.zxing.aztec.AztecDetectorResult;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import org.junit.Test;
import static org.junit.Assert.*;
public class DecoderTest {
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
/**
* throws
* <pre>com.google.zxing.FormatException: com.google.zxing.common.reedsolomon.ReedSolomonException: Error locator degree does not match number of roots
* at com.google.zxing.common.reedsolomon.ReedSolomonDecoder.findErrorLocations(ReedSolomonDecoder.java:158)
* at com.google.zxing.common.reedsolomon.ReedSolomonDecoder.decode(ReedSolomonDecoder.java:77)
* at com.google.zxing.aztec.decoder.Decoder.correctBits(Decoder.java:231)
* at com.google.zxing.aztec.decoder.Decoder.decode(Decoder.java:77)
* at com.google.zxing.aztec.decoder.DecoderTest.testDecodeBug1(DecoderTest.java:66)</pre>
*/
@Test
public void testDecodeTooManyErrors() {
BitMatrix matrix = BitMatrix.parse(""
+ "X X . X . . . X X . . . X . . X X X . X . X X X X X . \n"
+ "X X . . X X . . . . . X X . . . X X . . . X . X . . X \n"
+ "X . . . X X . . X X X . X X . X X X X . X X . . X . . \n"
+ ". . . . X . X X . . X X . X X . X . X X X X . X . . X \n"
+ "X X X . . X X X X X . . . . . X X . . . X . X . X . X \n"
+ "X X . . . . . . . . X . . . X . X X X . X . . X . . . \n"
+ "X X . . X . . . . . X X . . . . . X . . . . X . . X X \n"
+ ". . . X . X . X . . . . . X X X X X X . . . . . . X X \n"
+ "X . . . X . X X X X X X . . X X X . X . X X X X X X . \n"
+ "X . . X X X . X X X X X X X X X X X X X . . . X . X X \n"
+ ". . . . X X . . . X . . . . . . . X X . . . X X . X . \n"
+ ". . . X X X . . X X . X X X X X . X . . X . . . . . . \n"
+ "X . . . . X . X . X . X . . . X . X . X X . X X . X X \n"
+ "X . X . . X . X . X . X . X . X . X . . . . . X . X X \n"
+ "X . X X X . . X . X . X . . . X . X . X X X . . . X X \n"
+ "X X X X X X X X . X . X X X X X . X . X . X . X X X . \n"
+ ". . . . . . . X . X . . . . . . . X X X X . . . X X X \n"
+ "X X . . X . . X . X X X X X X X X X X X X X . . X . X \n"
+ "X X X . X X X X . . X X X X . . X . . . . X . . X X X \n"
+ ". . . . X . X X X . . . . X X X X . . X X X X . . . . \n"
+ ". . X . . X . X . . . X . X X . X X . X . . . X . X . \n"
+ "X X . . X . . X X X X X X X . . X . X X X X X X X . . \n"
+ "X . X X . . X X . . . . . X . . . . . . X X . X X X . \n"
+ "X . . X X . . X X . X . X . . . . X . X . . X . . X . \n"
+ "X . X . X . . X . X X X X X X X X . X X X X . . X X . \n"
+ "X X X X . . . X . . X X X . X X . . X . . . . X X X . \n"
+ "X X . X . X . . . X . X . . . . X X . X . . X X . . . \n",
"X ", ". ");
AztecDetectorResult r = new AztecDetectorResult(matrix, NO_POINTS, true, 16, 4);
try {
DecoderResult res = new Decoder().decode(r);
fail();
} catch (FormatException ex) {
}
}
@Test
public void testDecodeTooManyErrors2() {
BitMatrix matrix = BitMatrix.parse(""
+ ". X X . . X . X X . . . X . . X X X . . . X X . X X . \n"
+ "X X . X X . . X . . . X X . . . X X . X X X . X . X X \n"
+ ". . . . X . . . X X X . X X . X X X X . X X . . X . . \n"
+ "X . X X . . X . . . X X . X X . X . X X . . . . . X . \n"
+ "X X . X . . X . X X . . . . . X X . . . . . X . . . X \n"
+ "X . . X . . . . . . X . . . X . X X X X X X X . . . X \n"
+ "X . . X X . . X . . X X . . . . . X . . . . . X X X . \n"
+ ". . X X X X . X . . . . . X X X X X X . . . . . . X X \n"
+ "X . . . X . X X X X X X . . X X X . X . X X X X X X . \n"
+ "X . . X X X . X X X X X X X X X X X X X . . . X . X X \n"
+ ". . . . X X . . . X . . . . . . . X X . . . X X . X . \n"
+ ". . . X X X . . X X . X X X X X . X . . X . . . . . . \n"
+ "X . . . . X . X . X . X . . . X . X . X X . X X . X X \n"
+ "X . X . . X . X . X . X . X . X . X . . . . . X . X X \n"
+ "X . X X X . . X . X . X . . . X . X . X X X . . . X X \n"
+ "X X X X X X X X . X . X X X X X . X . X . X . X X X . \n"
+ ". . . . . . . X . X . . . . . . . X X X X . . . X X X \n"
+ "X X . . X . . X . X X X X X X X X X X X X X . . X . X \n"
+ "X X X . X X X X . . X X X X . . X . . . . X . . X X X \n"
+ ". . X X X X X . X . . . . X X X X . . X X X . X . X . \n"
+ ". . X X . X . X . . . X . X X . X X . . . . X X . . . \n"
+ "X . . . X . X . X X X X X X . . X . X X X X X . X . . \n"
+ ". X . . . X X X . . . . . X . . . . . X X X X X . X . \n"
+ "X . . X . X X X X . X . X . . . . X . X X . X . . X . \n"
+ "X . . . X X . X . X X X X X X X X . X X X X . . X X . \n"
+ ". X X X X . . X . . X X X . X X . . X . . . . X X X . \n"
+ "X X . . . X X . . X . X . . . . X X . X . . X . X . X \n",
"X ", ". ");
AztecDetectorResult r = new AztecDetectorResult(matrix, NO_POINTS, true, 16, 4);
try {
DecoderResult res = new Decoder().decode(r);
fail();
} catch (FormatException ex) {
}
}
}

View file

@ -37,6 +37,7 @@ import java.util.EnumMap;
import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;
import org.junit.Before;
/**
* Aztec 2D generator unit tests.
@ -48,6 +49,12 @@ public final class EncoderTest extends Assert {
private static final Pattern DOTX = Pattern.compile("[^.X]");
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
private static Random random;
@Before
public void beforeTest() {
random = new Random(0);
}
// real life tests
@ -127,23 +134,24 @@ public final class EncoderTest extends Assert {
"X X X X X X X X X X X X X \n");
}
@Ignore("Flaky test for unknown reasons -- disabling for now")
@Test
public void testAztecWriter() throws Exception {
testWriter("\u20AC 1 sample data.", "ISO-8859-1", 25, true, 2);
testWriter("\u20AC 1 sample data.", "ISO-8859-15", 25, true, 2);
testWriter("\u20AC 1 sample data.", "UTF-8", 25, true, 2);
testWriter("\u20AC 1 sample data.", "UTF-8", 100, true, 3);
testWriter("\u20AC 1 sample data.", "UTF-8", 300, true, 4);
testWriter("\u20AC 1 sample data.", "UTF-8", 500, false, 5);
// Test AztecWriter defaults
String data = "In ut magna vel mauris malesuada";
AztecWriter writer = new AztecWriter();
BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0);
AztecCode aztec = Encoder.encode(data.getBytes(StandardCharsets.ISO_8859_1),
Encoder.DEFAULT_EC_PERCENT, Encoder.DEFAULT_AZTEC_LAYERS);
BitMatrix expectedMatrix = aztec.getMatrix();
assertEquals(matrix, expectedMatrix);
for (int i = 0; i < 1000; i++) {
testWriter("\u20AC 1 sample data.", "ISO-8859-1", 25, true, 2);
testWriter("\u20AC 1 sample data.", "ISO-8859-15", 25, true, 2);
testWriter("\u20AC 1 sample data.", "UTF-8", 25, true, 2);
testWriter("\u20AC 1 sample data.", "UTF-8", 100, true, 3);
testWriter("\u20AC 1 sample data.", "UTF-8", 300, true, 4);
testWriter("\u20AC 1 sample data.", "UTF-8", 500, false, 5);
// Test AztecWriter defaults
String data = "In ut magna vel mauris malesuada";
AztecWriter writer = new AztecWriter();
BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0);
AztecCode aztec = Encoder.encode(data.getBytes(StandardCharsets.ISO_8859_1),
Encoder.DEFAULT_EC_PERCENT, Encoder.DEFAULT_AZTEC_LAYERS);
BitMatrix expectedMatrix = aztec.getMatrix();
assertEquals(matrix, expectedMatrix);
}
}
// synthetic tests (encode-decode round-trip)
@ -486,8 +494,8 @@ public final class EncoderTest extends Assert {
new AztecDetectorResult(matrix, NO_POINTS, aztec.isCompact(), aztec.getCodeWords(), aztec.getLayers());
DecoderResult res = new Decoder().decode(r);
assertEquals(expectedData, res.getText());
// Check error correction by introducing up to eccPercent errors
int ecWords = aztec.getCodeWords() * eccPercent / 100;
// Check error correction by introducing up to eccPercent/2 errors
int ecWords = aztec.getCodeWords() * eccPercent / 100 / 2;
Random random = getPseudoRandom();
for (int i = 0; i < ecWords; i++) {
// don't touch the core
@ -505,7 +513,7 @@ public final class EncoderTest extends Assert {
}
private static Random getPseudoRandom() {
return new SecureRandom(new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF});
return random;
}
private static void testModeMessage(boolean compact, int layers, int words, String expected) {

View file

@ -153,6 +153,89 @@ public final class BitMatrixTestCase extends Assert {
testRotate180(8, 5);
}
@Test
public void testParse() {
BitMatrix emptyMatrix = new BitMatrix(3, 3);
BitMatrix fullMatrix = new BitMatrix(3, 3);
fullMatrix.setRegion(0, 0, 3, 3);
BitMatrix centerMatrix = new BitMatrix(3, 3);
centerMatrix.setRegion(1, 1, 1, 1);
BitMatrix emptyMatrix24 = new BitMatrix(2, 4);
assertEquals(emptyMatrix, BitMatrix.parse(" \n \n \n", "x", " "));
assertEquals(emptyMatrix, BitMatrix.parse(" \n \r\r\n \n\r", "x", " "));
assertEquals(emptyMatrix, BitMatrix.parse(" \n \n ", "x", " "));
assertEquals(fullMatrix, BitMatrix.parse("xxx\nxxx\nxxx\n", "x", " "));
assertEquals(centerMatrix, BitMatrix.parse(" \n x \n \n", "x", " "));
assertEquals(centerMatrix, BitMatrix.parse(" \n x \n \n", "x ", " "));
try {
assertEquals(centerMatrix, BitMatrix.parse(" \n xy\n \n", "x", " "));
fail();
} catch (IllegalArgumentException ex) {}
assertEquals(emptyMatrix24, BitMatrix.parse(" \n \n \n \n", "x", " "));
assertEquals(centerMatrix, BitMatrix.parse(centerMatrix.toString("x", ".", "\n"), "x", "."));
}
@Test
public void testUnset() {
BitMatrix emptyMatrix = new BitMatrix(3, 3);
BitMatrix matrix = emptyMatrix.clone();
matrix.set(1, 1);
assertNotEquals(emptyMatrix, matrix);
matrix.unset(1, 1);
assertEquals(emptyMatrix, matrix);
matrix.unset(1, 1);
assertEquals(emptyMatrix, matrix);
}
@Test
public void testXOR() {
BitMatrix emptyMatrix = new BitMatrix(3, 3);
BitMatrix fullMatrix = new BitMatrix(3, 3);
fullMatrix.setRegion(0, 0, 3, 3);
BitMatrix centerMatrix = new BitMatrix(3, 3);
centerMatrix.setRegion(1, 1, 1, 1);
BitMatrix invertedCenterMatrix = fullMatrix.clone();
invertedCenterMatrix.unset(1, 1);
BitMatrix badMatrix = new BitMatrix(4, 4);
testXOR(emptyMatrix, emptyMatrix, emptyMatrix);
testXOR(emptyMatrix, centerMatrix, centerMatrix);
testXOR(emptyMatrix, fullMatrix, fullMatrix);
testXOR(centerMatrix, emptyMatrix, centerMatrix);
testXOR(centerMatrix, centerMatrix, emptyMatrix);
testXOR(centerMatrix, fullMatrix, invertedCenterMatrix);
testXOR(invertedCenterMatrix, emptyMatrix, invertedCenterMatrix);
testXOR(invertedCenterMatrix, centerMatrix, fullMatrix);
testXOR(invertedCenterMatrix, fullMatrix, centerMatrix);
testXOR(fullMatrix, emptyMatrix, fullMatrix);
testXOR(fullMatrix, centerMatrix, invertedCenterMatrix);
testXOR(fullMatrix, fullMatrix, emptyMatrix);
try {
emptyMatrix.clone().xor(badMatrix);
fail();
} catch(IllegalArgumentException ex) {}
try {
badMatrix.clone().xor(emptyMatrix);
fail();
} catch(IllegalArgumentException ex) {}
}
private static void testXOR(BitMatrix dataMatrix, BitMatrix flipMatrix, BitMatrix expectedMatrix) {
BitMatrix matrix = dataMatrix.clone();
matrix.xor(flipMatrix);
assertEquals(expectedMatrix, matrix);
}
private static void testRotate180(int width, int height) {
BitMatrix input = getInput(width, height);
input.rotate180();

View file

@ -394,7 +394,7 @@ public final class ReedSolomonTestCase extends Assert {
testEncodeDecodeRandom(GenericGF.AZTEC_DATA_12, 3072, 1023);
}
private static void corrupt(int[] received, int howMany, Random random, int max) {
public static void corrupt(int[] received, int howMany, Random random, int max) {
BitSet corrupted = new BitSet(received.length);
for (int j = 0; j < howMany; j++) {
int location = random.nextInt(received.length);

View file

@ -16,9 +16,9 @@
package com.google.zxing.pdf417.decoder.ec;
import com.google.zxing.common.reedsolomon.ReedSolomonTestCase;
import org.junit.Assert;
import java.security.SecureRandom;
import java.util.BitSet;
import java.util.Random;
@ -28,16 +28,7 @@ import java.util.Random;
abstract class AbstractErrorCorrectionTestCase extends Assert {
static void corrupt(int[] received, int howMany, Random random) {
BitSet corrupted = new BitSet(received.length);
for (int j = 0; j < howMany; j++) {
int location = random.nextInt(received.length);
if (corrupted.get(location)) {
j--;
} else {
corrupted.set(location);
received[location] = random.nextInt(929);
}
}
ReedSolomonTestCase.corrupt(received, howMany, random, 929);
}
static int[] erase(int[] received, int howMany, Random random) {
@ -58,7 +49,7 @@ abstract class AbstractErrorCorrectionTestCase extends Assert {
}
static Random getRandom() {
return new SecureRandom(new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF});
return new Random(0);
}
}

View file

@ -28,12 +28,6 @@ import java.util.Random;
*/
public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCase {
/** See ISO 15438, Annex Q */
//private static final int[] PDF417_TEST =
// { 5, 453, 178, 121, 239 };
//private static final int[] PDF417_TEST_WITH_EC =
// { 5, 453, 178, 121, 239, 452, 327, 657, 619 };
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,
@ -45,10 +39,7 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa
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;
// Example is EC level 1 (s=1). The number of erasures (l) and substitutions (f) must obey:
// l + 2f <= 2^(s+1) - 3
private static final int EC_LEVEL = 5;
private static final int ERROR_LIMIT = (1 << (EC_LEVEL + 1)) - 3;
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;
@ -81,13 +72,11 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa
}
}
@Ignore("Unresolved flakiness with OpenJDK 7 only")
@Test
public void testTooManyErrors() {
int[] received = PDF417_TEST_WITH_EC.clone();
Random random = getRandom();
// +3 since the algorithm can actually correct 2 more than it should here
corrupt(received, MAX_ERRORS + 3, random);
corrupt(received, MAX_ERRORS + 1, random);
try {
checkDecode(received);
fail("Should not have decoded");