diff --git a/src/Common/BitBuffer.php b/src/Common/BitBuffer.php index cd79bd95e..9cd0547fe 100644 --- a/src/Common/BitBuffer.php +++ b/src/Common/BitBuffer.php @@ -112,7 +112,7 @@ final class BitBuffer{ */ public function read(int $numBits):int{ - if($numBits < 1 || $numBits > 32 || $numBits > $this->available()){ + if($numBits < 1 || $numBits > $this->available()){ throw new QRCodeException('invalid $numBits: '.$numBits); } diff --git a/src/Common/EccLevel.php b/src/Common/EccLevel.php index 99b397005..9f4a8d18f 100644 --- a/src/Common/EccLevel.php +++ b/src/Common/EccLevel.php @@ -33,7 +33,7 @@ final class EccLevel{ /** * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40 * - * @var int [][] + * @var int[][] */ private const MAX_BITS = [ // [ L, M, Q, H] // v => modules diff --git a/src/Common/Mode.php b/src/Common/Mode.php index e18b01e5a..2eb404f48 100644 --- a/src/Common/Mode.php +++ b/src/Common/Mode.php @@ -90,9 +90,16 @@ final class Mode{ /** * returns the array of length bits for the given mode + * + * @throws \chillerlan\QRCode\QRCodeException */ public static function getLengthBitsForMode(int $mode):array{ - return self::LENGTH_BITS[$mode]; + + if(isset(self::LENGTH_BITS[$mode])){ + return self::LENGTH_BITS[$mode]; + } + + throw new QRCodeException('invalid mode given'); } } diff --git a/src/Common/Version.php b/src/Common/Version.php index 55f4eddde..5f68270d6 100644 --- a/src/Common/Version.php +++ b/src/Common/Version.php @@ -217,7 +217,7 @@ final class Version{ public function __construct(int $version){ if($version < 1 || $version > 40){ - throw new QRCodeException('invalid version number'); + throw new QRCodeException('invalid version given'); } $this->version = $version; diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index bbf356b60..856858c87 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -109,7 +109,7 @@ final class AlphaNum extends QRDataModeAbstract{ while($length > 1){ if($bitBuffer->available() < 11){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $nextTwoCharsBits = $bitBuffer->read(11); @@ -121,7 +121,7 @@ final class AlphaNum extends QRDataModeAbstract{ if($length === 1){ // special case: one character left if($bitBuffer->available() < 6){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $result .= $toAlphaNumericChar($bitBuffer->read(6)); diff --git a/src/Data/Byte.php b/src/Data/Byte.php index 53b9f9a2d..2e0867331 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -70,7 +70,7 @@ final class Byte extends QRDataModeAbstract{ $length = $bitBuffer->read(self::getLengthBits($versionNumber)); if($bitBuffer->available() < 8 * $length){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $readBytes = ''; diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index 79004739b..1b1809008 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -119,7 +119,7 @@ final class Kanji extends QRDataModeAbstract{ $length = $bitBuffer->read(self::getLengthBits($versionNumber)); if($bitBuffer->available() < $length * 13){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $buffer = []; diff --git a/src/Data/Number.php b/src/Data/Number.php index 5afa969e3..193f3cc75 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -126,7 +126,7 @@ final class Number extends QRDataModeAbstract{ while($length >= 3){ // Each 10 bits encodes three digits if($bitBuffer->available() < 10){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $threeDigitsBits = $bitBuffer->read(10); @@ -145,7 +145,7 @@ final class Number extends QRDataModeAbstract{ if($length === 2){ // Two digits left over to read, encoded in 7 bits if($bitBuffer->available() < 7){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $twoDigitsBits = $bitBuffer->read(7); @@ -160,7 +160,7 @@ final class Number extends QRDataModeAbstract{ elseif($length === 1){ // One digit left over to read if($bitBuffer->available() < 4){ - throw new QRCodeDataException('not enough bits available'); + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } $digitBits = $bitBuffer->read(4); diff --git a/src/Data/QRMatrix.php b/src/Data/QRMatrix.php index 8e1cf515e..87bc1a344 100755 --- a/src/Data/QRMatrix.php +++ b/src/Data/QRMatrix.php @@ -534,17 +534,17 @@ class QRMatrix{ throw new QRCodeDataException('ECC level "H" required to add logo space'); } - // if width and height happen to be exactly 0 (default value), just return - nothing to do - if($width === 0 || $height === 0){ - return $this; + // if width and height happen to be negative or 0 (default value), just return - nothing to do + if($width <= 0 || $height <= 0){ + return $this; // @codeCoverageIgnore } // $this->moduleCount includes the quiet zone (if created), we need the QR size here $length = $this->version->getDimension(); - // throw if the size is negative or exceeds the qrcode size - if($width < 0 || $height < 0 || $width > $length || $height > $length){ - throw new QRCodeDataException('invalid logo dimensions'); + // throw if the size is exceeds the qrcode size + if($width > $length || $height > $length){ + throw new QRCodeDataException('logo dimensions exceed matrix size'); } // we need uneven sizes to center the logo space, adjust if needed diff --git a/src/Decoder/Binarizer.php b/src/Decoder/Binarizer.php index b0088544c..42dc8f6e6 100644 --- a/src/Decoder/Binarizer.php +++ b/src/Decoder/Binarizer.php @@ -103,7 +103,7 @@ final class Binarizer{ // If there is too little contrast in the image to pick a meaningful black point, throw rather // than waste time trying to decode the image, and risk false positives. if($secondPeak - $firstPeak <= $numBuckets / 16){ - throw new QRCodeDecoderException('no meaningful dark point found'); + throw new QRCodeDecoderException('no meaningful dark point found'); // @codeCoverageIgnore } // Find a valley between them that is low and closer to the white peak. diff --git a/tests/Common/BitBufferTest.php b/tests/Common/BitBufferTest.php index b92ffa538..913ad60ec 100644 --- a/tests/Common/BitBufferTest.php +++ b/tests/Common/BitBufferTest.php @@ -11,6 +11,7 @@ namespace chillerlan\QRCodeTest\Common; use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use chillerlan\QRCode\QRCodeException; use PHPUnit\Framework\TestCase; /** @@ -42,4 +43,12 @@ final class BitBufferTest extends TestCase{ $this::assertSame(4, $this->bitBuffer->getLength()); } + public function testReadException():void{ + $this->expectException(QRCodeException::class); + $this->expectExceptionMessage('invalid $numBits'); + + $this->bitBuffer->put(Mode::KANJI, 4); + $this->bitBuffer->read($this->bitBuffer->available() + 1); + } + } diff --git a/tests/Common/EccLevelTest.php b/tests/Common/EccLevelTest.php new file mode 100644 index 000000000..73e2c4359 --- /dev/null +++ b/tests/Common/EccLevelTest.php @@ -0,0 +1,60 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Common; + +use chillerlan\QRCode\Common\EccLevel; +use chillerlan\QRCode\Common\MaskPattern; +use chillerlan\QRCode\QRCodeException; +use PHPUnit\Framework\TestCase; + +/** + * EccLevel coverage test + */ +final class EccLevelTest extends TestCase{ + + public function testConstructInvalidEccException():void{ + $this->expectException(QRCodeException::class); + $this->expectExceptionMessage('invalid ECC level'); + + $ecc = new EccLevel(69); + } + + public function testToString():void{ + $ecc = new EccLevel(EccLevel::L); + + $this::assertSame('L', (string)$ecc); + } + + public function testGetLevel():void{ + $ecc = new EccLevel(EccLevel::L); + + $this::assertSame(EccLevel::L, $ecc->getLevel()); + } + + public function testGetOrdinal():void{ + $ecc = new EccLevel(EccLevel::L); + + $this::assertSame(0, $ecc->getOrdinal()); + } + + public function testGetformatPattern():void{ + $ecc = new EccLevel(EccLevel::Q); + + $this::assertSame(0b010010010110100, $ecc->getformatPattern(new MaskPattern(4))); + } + + public function getMaxBits():void{ + $ecc = new EccLevel(EccLevel::Q); + + $this::assertSame(4096, $ecc->getMaxBits()[21]); + } + +} diff --git a/tests/Common/ModeTest.php b/tests/Common/ModeTest.php new file mode 100644 index 000000000..1e6ec3bb0 --- /dev/null +++ b/tests/Common/ModeTest.php @@ -0,0 +1,64 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Common; + +use chillerlan\QRCode\Common\Mode; +use chillerlan\QRCode\QRCodeException; +use PHPUnit\Framework\TestCase; + +/** + * Mode coverage test + */ +final class ModeTest extends TestCase{ + + /** + * version breakpoints for numeric mode + */ + public function versionProvider():array{ + return [ + [ 1, 10], + [ 9, 10], + [10, 12], + [26, 12], + [27, 14], + [40, 14], + ]; + } + + /** + * @dataProvider versionProvider + */ + public function testGetLengthBitsForVersionBreakpoints(int $version, int $expected):void{ + $this::assertSame($expected, Mode::getLengthBitsForVersion(Mode::NUMBER, $version)); + } + + public function testGetLengthBitsForVersionInvalidModeException():void{ + $this->expectException(QRCodeException::class); + $this->expectExceptionMessage('invalid mode given'); + + $bits = Mode::getLengthBitsForVersion(42, 69); + } + + public function testGetLengthBitsForVersionInvalidVersionException():void{ + $this->expectException(QRCodeException::class); + $this->expectExceptionMessage('invalid version number'); + + $bits = Mode::getLengthBitsForVersion(Mode::BYTE, 69); + } + + public function testGetLengthBitsForModeInvalidModeException():void{ + $this->expectException(QRCodeException::class); + $this->expectExceptionMessage('invalid mode given'); + + $bits = Mode::getLengthBitsForMode(42); + } + +} diff --git a/tests/Common/VersionTest.php b/tests/Common/VersionTest.php new file mode 100644 index 000000000..e9a4b2936 --- /dev/null +++ b/tests/Common/VersionTest.php @@ -0,0 +1,67 @@ + + * @copyright 2022 smiley + * @license MIT + */ + +namespace chillerlan\QRCodeTest\Common; + +use chillerlan\QRCode\Common\EccLevel; +use chillerlan\QRCode\Common\Mode; +use chillerlan\QRCode\Common\Version; +use chillerlan\QRCode\QRCodeException; +use PHPUnit\Framework\TestCase; + +/** + * Version coverage test + */ +final class VersionTest extends TestCase{ + + private Version $version; + + protected function setUp():void{ + $this->version = new Version(7); + } + + public function testToString():void{ + $this::assertSame('7', (string)$this->version); + } + + public function testGetVersionNumber():void{ + $this::assertSame(7, $this->version->getVersionNumber()); + } + + public function testGetDimension():void{ + $this::assertSame(45, $this->version->getDimension()); + } + + public function testGetVersionPattern():void{ + $this::assertSame(0b000111110010010100, $this->version->getVersionPattern()); + // no pattern for version < 7 + $this::assertNull((new Version(6))->getVersionPattern()); + } + + public function testGetAlignmentPattern():void{ + $this::assertSame([6, 22, 38], $this->version->getAlignmentPattern()); + } + + public function testGetRSBlocks():void{ + $this::assertSame([18, [[2, 14], [4, 15]]], $this->version->getRSBlocks(new EccLevel(EccLevel::Q))); + } + + public function testGetTotalCodewords():void{ + $this::assertSame(196, $this->version->getTotalCodewords()); + } + + public function testConstructInvalidVersion():void{ + $this->expectException(QRCodeException::class); + $this->expectExceptionMessage('invalid version given'); + + $version = new Version(69); + } + +} diff --git a/tests/Data/QRMatrixTest.php b/tests/Data/QRMatrixTest.php index 47e707ea3..2d6de08fc 100755 --- a/tests/Data/QRMatrixTest.php +++ b/tests/Data/QRMatrixTest.php @@ -155,6 +155,10 @@ final class QRMatrixTest extends TestCase{ $this->matrix->set(20, 20, false, QRMatrix::M_TEST); $this::assertSame(QRMatrix::M_TEST, $this->matrix->get(20, 20)); $this::assertFalse($this->matrix->check(20, 20)); + + // out of range + $this::assertFalse($this->matrix->check(-1, -1)); + $this::assertSame(-1, $this->matrix->get(-1, -1)); } /** @@ -413,6 +417,20 @@ final class QRMatrixTest extends TestCase{ (new QRCode)->addByteSegment('testdata')->getMatrix()->setLogoSpace(50, 50); } + /** + * Tests whether an exception is thrown when width or height exceed the matrix size + */ + public function testSetLogoSpaceExceedsException():void{ + $this->expectException(QRCodeDataException::class); + $this->expectExceptionMessage('logo dimensions exceed matrix size'); + + $o = new QROptions; + $o->version = 5; + $o->eccLevel = EccLevel::H; + + (new QRCode($o))->addByteSegment('testdata')->getMatrix()->setLogoSpace(69, 1); + } + /** * Tests whether an exception is thrown when the logo space size exceeds the maximum ECC capacity */ @@ -450,4 +468,95 @@ final class QRMatrixTest extends TestCase{ $this::assertSame(QRMatrix::M_DARKMODULE | QRMatrix::IS_DARK, $this->matrix->get($x, $y)); } + /** + * Tests checking whether the M_TYPE of a module is not one of an array of M_TYPES + */ + public function testCheckTypeNotIn():void{ + $this->matrix->set(10, 10, true, QRMatrix::M_QUIETZONE); + + $this::assertTrue($this->matrix->checkTypeNotIn(10, 10, [QRMatrix::M_DATA, QRMatrix::M_FINDER])); + $this::assertFalse($this->matrix->checkTypeNotIn(10, 10, [QRMatrix::M_QUIETZONE, QRMatrix::M_FINDER])); + } + + /** + * Tests checking the adjacent modules + */ + public function testCheckNeighbours():void{ + + $this->matrix + ->setFinderPattern() + ->setAlignmentPattern() + ; + + /* + * center of finder pattern (surrounded by all dark) + * + * # # # # # # # + * # # + * # # # # # + * # # 0 # # + * # # # # # + * # # + * # # # # # # # + */ + $this::assertSame(0b11111111, $this->matrix->checkNeighbours(3, 3)); + + /* + * center of alignment pattern (surrounded by all light) + * + * # # # # # + * # # + * # 0 # + * # # + * # # # # # + */ + $this::assertSame(0b00000000, $this->matrix->checkNeighbours(30, 30)); + + /* + * top left light block of finder pattern + * + * # # # + * # 0 + * # # + */ + $this::assertSame(0b11010111, $this->matrix->checkNeighbours(1, 1)); + + /* + * bottom left light block of finder pattern + * + * # # + * # 0 + * # # # + */ + $this::assertSame(0b11110101, $this->matrix->checkNeighbours(1, 5)); + + /* + * top right light block of finder pattern + * + * # # # + * 0 # + * # # + */ + $this::assertSame(0b01011111, $this->matrix->checkNeighbours(5, 1)); + + /* + * bottom right light block of finder pattern + * + * # # + * 0 # + * # # # + */ + $this::assertSame(0b01111101, $this->matrix->checkNeighbours(5, 5)); + + + /* + * M_TYPE check + * + * # # # + * 0 + * X X X + */ + $this::assertSame(0b00000111, $this->matrix->checkNeighbours(3, 1, QRMatrix::M_FINDER)); + $this::assertSame(0b01110000, $this->matrix->checkNeighbours(3, 1, QRMatrix::M_FINDER_DOT)); + } }