From a1610f8ccb825af951aeb3e465c05123912d3267 Mon Sep 17 00:00:00 2001 From: Matthieu Racine Date: Thu, 21 Mar 2019 23:23:02 +0100 Subject: [PATCH 01/10] refactor communication using stream --- src/APIConnector.php | 56 ++++++ src/APILengthCoDec.php | 197 ++++++++++++++++++ src/Client.php | 111 ++-------- src/Exceptions/StreamException.php | 14 ++ src/Helpers/BinaryStringHelper.php | 60 ++++++ src/Interfaces/StreamInterface.php | 39 ++++ src/Streams/ResourceStream.php | 80 ++++++++ src/Streams/StringStream.php | 93 +++++++++ tests/APIConnectorTest.php | 92 +++++++++ tests/APILengthCoDecTest.php | 120 +++++++++++ tests/Helpers/BinaryStringHelperTest.php | 43 ++++ tests/Streams/ResourceStreamTest.php | 246 +++++++++++++++++++++++ tests/Streams/StringStreamTest.php | 144 +++++++++++++ 13 files changed, 1202 insertions(+), 93 deletions(-) create mode 100644 src/APIConnector.php create mode 100644 src/APILengthCoDec.php create mode 100644 src/Exceptions/StreamException.php create mode 100644 src/Helpers/BinaryStringHelper.php create mode 100644 src/Interfaces/StreamInterface.php create mode 100644 src/Streams/ResourceStream.php create mode 100644 src/Streams/StringStream.php create mode 100644 tests/APIConnectorTest.php create mode 100644 tests/APILengthCoDecTest.php create mode 100644 tests/Helpers/BinaryStringHelperTest.php create mode 100644 tests/Streams/ResourceStreamTest.php create mode 100644 tests/Streams/StringStreamTest.php diff --git a/src/APIConnector.php b/src/APIConnector.php new file mode 100644 index 0000000..cee50ed --- /dev/null +++ b/src/APIConnector.php @@ -0,0 +1,56 @@ +stream = $stream; + } + + /** + * Reads a WORD from the stream + * + * WORDs are part of SENTENCE. Each WORD has to be encoded in certain way - length of the WORD followed by WORD content. + * Length of the WORD should be given as count of bytes that are going to be sent + * + * @return string The word content, en empty string for end of SENTENCE + */ + public function readWord() : string + { + // Get length of next word + $length = APILengthCoDec::decodeLength($this->stream); + if ($length>0) { + return $this->stream->read($length); + } + return ''; + } + + public function writeWord(string $word) + { + $encodedLength = APILengthCoDec::encodeLength(strlen($word)); + return $this->stream->write($encodedLength.$word); + } +} diff --git a/src/APILengthCoDec.php b/src/APILengthCoDec.php new file mode 100644 index 0000000..b3a59d2 --- /dev/null +++ b/src/APILengthCoDec.php @@ -0,0 +1,197 @@ + 7 bits set to 1) + // - encode length with one byte + // - set the byte to length value, as length maximal value is 7 bits set to 1, the most significant bit is always 0 + // - end + // - length <= 0x3FFF (binary : 00111111 11111111 => 14 bits set to 1) + // - encode length with two bytes + // - set length value to 0x8000 (=> 10000000 00000000) + // - add length : as length maximumal value is 14 bits to 1, this does not modify the 2 most significants bits (10) + // - end + // => minimal encoded value is 10000000 10000000 + // - length <= 0x1FFFFF (binary : 00011111 11111111 11111111 => 21 bits set to 1) + // - encode length with three bytes + // - set length value to 0xC00000 (binary : 11000000 00000000 00000000) + // - add length : as length maximal vlaue is 21 bits to 1, this does not modify the 3 most significants bits (110) + // - end + // => minimal encoded value is 11000000 01000000 00000000 + // - length <= 0x0FFFFFFF (binary : 00001111 11111111 11111111 11111111 => 28 bits set to 1) + // - encode length with four bytes + // - set length value to 0xE0000000 (binary : 11100000 00000000 00000000 00000000) + // - add length : as length maximal vlaue is 28 bits to 1, this does not modify the 4 most significants bits (1110) + // - end + // => minimal encoded value is 11100000 00100000 00000000 00000000 + // - length <= 0x7FFFFFFFFF (binary : 00000111 11111111 11111111 11111111 11111111 => 35 bits set to 1) + // - encode length with five bytes + // - set length value to 0xF000000000 (binary : 11110000 00000000 00000000 00000000 00000000) + // - add length : as length maximal vlaue is 35 bits to 1, this does not modify the 5 most significants bits (11110) + // - end + // - length > 0x7FFFFFFFFF : not supported + + + if ($length<0) + { + throw new \DomainException(sprintf("Length of word can not be negative (%d)", $length)); + } + + if ($length<=0x7F) { + return BinaryStringHelper::IntegerToNBOBinaryString($length); + } + else if ($length<=0x3FFF) { + return BinaryStringHelper::IntegerToNBOBinaryString(0x8000+$length); + } + else if ($length<=0x1FFFFF) { + return BinaryStringHelper::IntegerToNBOBinaryString(0xC00000+$length); + } + else if ($length<=0x0FFFFFFF){ + return BinaryStringHelper::IntegerToNBOBinaryString(0xE0000000+$length); + } + // cannot compare with 0x7FFFFFFFFF on 32 bits systems + else { + if (PHP_INT_SIZE<8) { + // Cannot be done on 32 bits systems + // PHP5 windows versions of php, even on 64 bits systems was impacted + // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit + + // @codeCoverageIgnoreStart + throw new \OverflowException(sprintf("Your system is using 32 bits integers, cannot encode length of %d bytes on this system", $length)); + // @codeCoverageIgnoreEnd + } + if ($length<=0x7FFFFFFFFF) + { + return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000+$length); + } + } + throw new \DomainException(sprintf('Length of word too huge (%x)', $length)); + } + + // Decode length of data when reading : + // The 5 firsts bits of the first byte specify how the length is encoded. + // The position of the first 0 value bit, starting from the most significant postion. + // - 0xxxxxxx => The 7 remainings bits of the first byte is the length : + // => min value of length is 0x00 + // => max value of length is 0x7F (127 bytes) + // - 10xxxxxx => The 6 remainings bits of the first byte plus the next byte represent the lenght + // NOTE : the next byte MUST be at least 0x80 !! + // => min value of length is 0x80 + // => max value of length is 0x3FFF (16,383 bytes, near 16 KB) + // - 110xxxxx => The 5 remainings bits of th first byte and the two next bytes represent the length + // => max value of length is 0x1FFFFF (2,097,151 bytes, near 2 MB) + // - 1110xxxx => The 4 remainings bits of the first byte and the three next bytes represent the length + // => max value of length is 0xFFFFFFF (268,435,455 bytes, near 270 MB) + // - 11110xxx => The 3 remainings bits of the first byte and the four next bytes represent the length + // => max value of length is 0x7FFFFFFF (2,147,483,647 byes, 2GB) + // - 11111xxx => This byte is not a length-encoded word but a control byte. + // => Extracted from Mikrotik API doc : + // it is a reserved control byte. + // After receiving unknown control byte API client cannot proceed, because it cannot know how to interpret following bytes + // Currently control bytes are not used + + public static function decodeLength(StreamInterface $stream) + { + // if (false === is_resource($stream)) { + // throw new \InvalidArgumentException( + // sprintf( + // 'Argument must be a stream resource type. %s given.', + // gettype($stream) + // ) + // ); + // } + + // Read first byte + $firstByte = ord($stream->read(1)); + + // If first byte is not set, length is the value of the byte + if (0==($firstByte&0x80)) { + return $firstByte; + } + + // if 10xxxxxx, length is 2 bytes encoded + if (0x80==($firstByte&0xC0)) + { + // Set 2 most significants bits to 0 + $result = $firstByte & 0x3F; + // shift left 8 bits to have 2 bytes + $result = $result << 8; + // read next byte and use it as least significant + $result |= ord($stream->read(1)); + return $result; + } + + // if 110xxxxx, length is 3 bytes encoded + if (0xC0 == ($firstByte & 0xE0)) + { + // Set 3 most significants bits to 0 + $result = $firstByte & 0x1F; + // shift left 16 bits to have 3 bytes + $result = $result << 16; + // read next 2 bytes as value and use it as least significant position + $result |= (ord($stream->read(1)) << 8); + $result |= ord($stream->read(1)); + return $result; + } + + // if 1110xxxx, length is 4 bytes encoded + if (0xE0 == ($firstByte & 0xF0)) + { + // Set 4 most significants bits to 0 + $result = $firstByte & 0x0F; + // shift left 24 bits to have 4 bytes + $result = $result << 24; + // read next 3 bytes as value and use it as least significant position + $result |= (ord($stream->read(1)) << 16); + $result |= (ord($stream->read(1)) << 8); + $result |= ord($stream->read(1)); + return $result; + } + + // if 11110xxx, length is 5 bytes encoded + if (0xF0 == ($firstByte & 0xF8)) + { + // Not possibe on 32 bits systems + if (PHP_INT_SIZE<8) { + // Cannot be done on 32 bits systems + // PHP5 windows versions of php, even on 64 bits systems was impacted + // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit + // How can we test it ? + // @codeCoverageIgnoreStart + throw new \OverflowException(sprintf("Your system is using 32 bits integers, cannot decode this value (%x) on this system", $firstByte)); + // @codeCoverageIgnoreEnd + } + // Set 5 most significants bits to 0 + $result = $firstByte & 0x07; + // shift left 232 bits to have 5 bytes + $result = $result << 32; + // read next 4 bytes as value and use it as least significant position + $result |= (ord($stream->read(1)) << 24); + $result |= (ord($stream->read(1)) << 16); + $result |= (ord($stream->read(1)) << 8); + $result |= ord($stream->read(1)); + return $result; + } + + // Now the only solution is 5 most significants bits are set to 1 (11111xxx) + // This is a control word, not implemented by Mikrotik for the moment + throw new \UnexpectedValueException("Control Word found\n"); + } +} \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index bf3925d..e9f7553 100644 --- a/src/Client.php +++ b/src/Client.php @@ -45,6 +45,14 @@ class Client implements Interfaces\ClientInterface */ private $_config; + /** + * API communication object + * + * @var APIConnector + */ + + private $connector; + /** * Client constructor. * @@ -108,86 +116,6 @@ public function setConfig(Config $config) $this->_config = $config; } - /** - * Encode given length in RouterOS format - * - * @param string $string - * @return string Encoded length - * @throws \RouterOS\Exceptions\ClientException - */ - private function encodeLength(string $string): string - { - $length = \strlen($string); - - if ($length < 128) { - $orig_length = $length; - $offset = -1; - } elseif ($length < 16384) { - $orig_length = $length | 0x8000; - $offset = -2; - } elseif ($length < 2097152) { - $orig_length = $length | 0xC00000; - $offset = -3; - } elseif ($length < 268435456) { - $orig_length = $length | 0xE0000000; - $offset = -4; - } else { - throw new ClientException("Unable to encode length of '$string'"); - } - - // Pack string to binary format - $result = pack('I*', $orig_length); - // Parse binary string to array - $result = str_split($result); - // Reverse array - $result = array_reverse($result); - // Extract values from offset to end of array - $result = \array_slice($result, $offset); - - // Sew items into one line - $output = null; - foreach ($result as $item) { - $output .= $item; - } - - return $output; - } - - /** - * Read length of line - * - * @param int $byte - * @return int - */ - private function getLength(int $byte): int - { - // If the first bit is set then we need to remove the first four bits, shift left 8 - // and then read another byte in. - // We repeat this for the second and third bits. - // If the fourth bit is set, we need to remove anything left in the first byte - // and then read in yet another byte. - if ($byte & 128) { - if (($byte & 192) === 128) { - $length = (($byte & 63) << 8) + \ord(fread($this->_socket, 1)); - } elseif (($byte & 224) === 192) { - $length = (($byte & 31) << 8) + \ord(fread($this->_socket, 1)); - $length = ($length << 8) + \ord(fread($this->_socket, 1)); - } elseif (($byte & 240) === 224) { - $length = (($byte & 15) << 8) + \ord(fread($this->_socket, 1)); - $length = ($length << 8) + \ord(fread($this->_socket, 1)); - $length = ($length << 8) + \ord(fread($this->_socket, 1)); - } else { - $length = \ord(fread($this->_socket, 1)); - $length = ($length << 8) + \ord(fread($this->_socket, 1)) * 3; - $length = ($length << 8) + \ord(fread($this->_socket, 1)); - $length = ($length << 8) + \ord(fread($this->_socket, 1)); - } - } else { - $length = $byte; - } - return $length; - } - /** * Send write query to RouterOS (with or without tag) * @@ -211,12 +139,11 @@ public function write($query): Client // Send commands via loop to router foreach ($query->getQuery() as $command) { - $command = trim($command); - fwrite($this->_socket, $this->encodeLength($command) . $command); + $this->connector->writeWord(trim($command)); } - // Write zero-terminator - fwrite($this->_socket, \chr(0)); + // Write zero-terminator (empty string) + $this->connector->writeWord(''); return $this; } @@ -242,12 +169,9 @@ public function read(bool $parse = true): array // Read answer from socket in loop while (true) { - // Read the first byte of input which gives us some or all of the length - // of the remaining reply. - $byte = fread($this->_socket, 1); - $length = $this->getLength(\ord($byte)); + $word = $this->connector->readWord(); - if ($length == 0) { + if (''===$word) { if ($lastReply) { // We received a !done or !fatal message in a precedent loop // response is complete @@ -260,12 +184,12 @@ public function read(bool $parse = true): array } // Save output line to response array - $response[] = $line = stream_get_contents($this->_socket, $length); + $response[] = $word; // If we get a !done or !fatal line in response, we are now ready to finish the read // but we need to wait a 0 length message, switch the flag - if ('!done' === $line || '!fatal' === $line) { - $lastReply = true; + if ('!done' === $word || '!fatal' === $word) { + $lastReply = true; } } @@ -449,7 +373,7 @@ private function connect(): bool // If socket is active if (null !== $this->getSocket()) { - + $this->connector = new APIConnector(new Streams\ResourceStream($this->getSocket())); // If we logged in then exit from loop if (true === $this->login()) { $connected = true; @@ -458,6 +382,7 @@ private function connect(): bool // Else close socket and start from begin $this->closeSocket(); + $this->stream = null; } // Sleep some time between tries diff --git a/src/Exceptions/StreamException.php b/src/Exceptions/StreamException.php new file mode 100644 index 0000000..c7dca3e --- /dev/null +++ b/src/Exceptions/StreamException.php @@ -0,0 +1,14 @@ + chr(0x0F).chr(0xF7) + * 0x12345678 => chr(0x12).chr(0x34).chr(0x56).chr(0x76) + * Compatible with 8, 16, 32, 64 etc.. bits systems + * + * @see https://en.wikipedia.org/wiki/Endianness + * @param int $value the integer value to be converted + * @return string the binary string + */ + public static function IntegerToNBOBinaryString(int $value) + { + // Initialize an empty string + $buffer = ''; + // Lets start from the most significant byte + for ($i=(PHP_INT_SIZE-1); $i>=0; $i--) { + // Prepare a mask to keep only the most significant byte of $value + $mask = 0xFF << ($i*8); + // If the most significant byte is not 0, the final string must contain it + // If we have already started to construct the string (i.e. there are more signficant digits) + // we must set the byte, even if it is a 0. + // 0xFF00FF, for example, require to set the second byte byte with a 0 value + if (($value & $mask) || strlen($buffer)!=0) { + // Get the curent byte by shifting it to least significant position and add it to the string + // 0xFF12345678 => 0xFF + $byte = $value>>(8*$i); + $buffer .= chr($byte); + // Set the most significant byte to 0 so we can restart the process being shure + // that the value is left padded with 0 + // 0xFF12345678 => 0x12345678 + // -1 = 0xFFFFF.... (number of F depend of PHP_INT_SIZE ) + $mask = -1 >> ((PHP_INT_SIZE-$i)*8); + $value &= $mask; + } + } + // Special case, 0 will not fill the buffer, have to construct it manualy + if (0==$value) { + $buffer = chr(0); + } + return $buffer; + } +} \ No newline at end of file diff --git a/src/Interfaces/StreamInterface.php b/src/Interfaces/StreamInterface.php new file mode 100644 index 0000000..2832a37 --- /dev/null +++ b/src/Interfaces/StreamInterface.php @@ -0,0 +1,39 @@ +stream = $stream; + } + + /** + * {@inheritDoc} + * @throws \InvalidArgumentException + */ + public function read(int $length) : string + { + if ($length<=0) { + throw new \InvalidArgumentException("Cannot read zero ot negative count of bytes from a stream"); + } + + $result = @fread($this->stream, $length); + + if (false === $result) { + throw new StreamException(sprintf("Error reading %d bytes", $length)); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function write(string $string, $length=null) : int + { + if (is_null($length)) { + $length = strlen($string); + } + $result = @fwrite($this->stream, $string, $length); + if (false === $result) { + throw new StreamException(sprintf("Error writing %d bytes", $length)); + } + return $result; + } + + public function close() + { + $hasBeenClosed = false; + if (!is_null($this->stream)) { + $hasBeenClosed = @fclose($this->stream); + $this->stream=null; + } + if (false===$hasBeenClosed) { + throw new StreamException("Error closing stream"); + + } + } +} \ No newline at end of file diff --git a/src/Streams/StringStream.php b/src/Streams/StringStream.php new file mode 100644 index 0000000..11fbe06 --- /dev/null +++ b/src/Streams/StringStream.php @@ -0,0 +1,93 @@ +buffer = $string; + } + + /** + * {@inheritDoc} + * + * @throws \InvalidArgumentException when length parameter is invalid + * @throws StreamException when the stream have been tatly red and read methd is called again + */ + public function read(int $length) : string + { + $remaining = strlen($this->buffer); + + if ($length<0) { + throw new \InvalidArgumentException("Cannot read a negative count of bytes from a stream"); + } + + if (0 == $remaining) { + throw new StreamException("End of stream"); + } + + if ($length>=$remaining) { + // returns all + $result = $this->buffer; + // No more in the buffer + $this->buffer=''; + } + else { + // acquire $length characters from the buffer + $result = substr($this->buffer, 0, $length); + // remove $length characters from the buffer + $this->buffer = substr_replace($this->buffer, '', 0, $length); + } + + return $result; + } + + /** + * Fake write method, do nothing except return the "writen" length + * + * @param string $string The string to write + * @param int|null $length the number of characters to write + * @throws \InvalidArgumentException on invalid length + * @return number of "writen" bytes + */ + public function write(string $string, $length=null) : int + { + if(null === $length) { + $length = strlen($string); + } + + if ($length<0) { + throw new \InvalidArgumentException("Cannot write a negative count of bytes"); + } + + return min($length, strlen($string)); + } + + public function close() + { + $this->buffer = ''; + } +} \ No newline at end of file diff --git a/tests/APIConnectorTest.php b/tests/APIConnectorTest.php new file mode 100644 index 0000000..1e1edc5 --- /dev/null +++ b/tests/APIConnectorTest.php @@ -0,0 +1,92 @@ +assertInstanceOf(APIConnector::class, $apiStream); + if ($closeResource) { + $apiStream->close(); + } + } + + public function constructProvider() + { + return [ + [ new ResourceStream(fopen(__FILE__, 'r')), ], // Myself, sure I exists + [ new ResourceStream(fsockopen('tcp://127.0.0.1', 18728)), ], // Socket + [ new ResourceStream(STDIN), false ], // Try it, but do not close STDIN please !!! + [ new StringStream('Hello World !!!') ], // Try it, but do not close STDIN please !!! + [ new StringStream('') ], // Try it, but do not close STDIN please !!! + // What else ? + ]; + } + + /** + * @covers ::readWord + * @dataProvider readWordProvider + */ + + public function test__readWord(APIConnector $connector, $expected) + { + $this->assertSame($expected, $connector->readWord()); + } + + public function readWordProvider() + { + $longString = '=comment='.str_repeat('a',10000); + $length = strlen($longString); + return [ + [ new APIConnector(new StringStream(chr(0))), ''], + [ new APIConnector(new StringStream(chr(3).'!re')), '!re'], + [ new APIConnector(new StringStream(chr(5).'!done')), '!done'], + [ new APIConnector(new StringStream(APILengthCoDec::encodeLength($length).$longString)), $longString], + ]; + } + + /** + * @covers ::writeWord + * @dataProvider writeWordProvider + */ + public function test_writeWord(APIConnector $connector, string $toWrite, int $expected) + { + $this->assertEquals($expected, $connector->writeWord($toWrite)); + } + + public function writeWordProvider() + { + return [ + [ new APIConnector(new StringStream('Have FUN !!!')), '', 1 ], // length is 0, but have to write it on 1 byte, minimum + [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 54), 55 ], // arbitrary value + [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 127), 128 ], // maximum value for 1 byte encoding lentgth + [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 128), 130 ], // minimum value for 2 bytes encoding lentgth + [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 254), 256 ], // special value isn't it ? + [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 255), 257 ], // special value isn't it ? + ]; + } +} \ No newline at end of file diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php new file mode 100644 index 0000000..69c37e2 --- /dev/null +++ b/tests/APILengthCoDecTest.php @@ -0,0 +1,120 @@ +assertEquals(BinaryStringHelper::IntegerToNBOBinaryString($expected), APILengthCoDec::encodeLength($length)); + } + + public function encodedLengthProvider() + { + // [encoded length value, length value] + return [ + [0, 0], // Low limit value for 1 byte encoded length + [0x39, 0x39], // Arbitrary median value for 1 byte encoded length + [0x7f, 0x7F], // High limit value for 1 byte encoded length + + [0x8080, 0x80], // Low limit value for 2 bytes encoded length + [0x9C42, 0x1C42], // Arbitrary median value for 2 bytes encoded length + [0xBFFF, 0x3FFF], // High limit value for 2 bytes encoded length + + [0xC04000, 0x4000], // Low limit value for 3 bytesv + [0xCAD73B, 0xAD73B], // Arbitrary median value for 3 bytes encoded length + [0xDFFFFF, 0x1FFFFF], // High limit value for 3 bytes encoded length + + [0xE0200000, 0x200000], // Low limit value for 4 bytes encoded length + [0xE5AD736B, 0x5AD736B], // Arbitrary median value for 4 bytes encoded length + [0xEFFFFFFF, 0xFFFFFFF], // High limit value for 4 bytes encoded length + + [0xF010000000, 0x10000000], // Low limit value for 5 bytes encoded length + [0xF10D4EF9C3, 0x10D4EF9C3], // Arbitrary median value for 5 bytes encoded length + [0xF7FFFFFFFF, 0x7FFFFFFFF], // High limit value for 5 bytes encoded length + ]; + } + + /** + * @dataProvider encodedLengthProvider + * @covers ::decodeLength + */ + + public function test__decodeLength($encodedLength, $expected) + { + // We have to provide $encodedLength as a "bytes" stream + $stream = new StringStream(BinaryStringHelper::IntegerToNBOBinaryString($encodedLength)); + $this->assertEquals($expected, APILengthCoDec::decodeLength($stream)); + } + + /** + * @dataProvider decodeLengthControlWordProvider + * @covers ::decodeLength + * @expectedException UnexpectedValueException + */ + public function test_decodeLengthControlWord(string $encodedLength) + { + APILengthCoDec::decodeLength(new StringStream($encodedLength)); + } + + public function decodeLengthControlWordProvider() + { + // Control bytes : 5 most signficants its sets to 1 + return [ + [chr(0xF8)], // minimum + [chr(0xFC)], // arbitraty value + [chr(0xFF)], // maximum + ]; + } +} \ No newline at end of file diff --git a/tests/Helpers/BinaryStringHelperTest.php b/tests/Helpers/BinaryStringHelperTest.php new file mode 100644 index 0000000..2b30b83 --- /dev/null +++ b/tests/Helpers/BinaryStringHelperTest.php @@ -0,0 +1,43 @@ +assertEquals($expected, BinaryStringHelper::IntegerToNBOBinaryString($value)); + } + + public function IntegerToNBOBinaryStringProvider() + { + return [ + [0, chr(0)], // lower boundary value + [0xFFFFFFFF, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], // 32 bits maximal value + + // strange behaviour : + // TypeError: Argument 1 passed to RouterOS\Tests\Helpers\BinaryStringHelperTest::test__IntegerToNBOBinaryString() must be of the type integer, float given + // Seems that php auto convert to float 0xFFF.... + // + // [0xFFFFFFFFFFFFFFFF, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], + + // -1 is encoded with 0xFFFFFFF..... + // 64 bits maximal value (on a 64 bits system) + [-1, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], // 64 bits upper boundary value + // Let's try random value + [0x390DDD99, chr(0x39).chr(0x0D).chr(0xDD).chr(0x99)], + + ]; + } +} \ No newline at end of file diff --git a/tests/Streams/ResourceStreamTest.php b/tests/Streams/ResourceStreamTest.php new file mode 100644 index 0000000..9cda9a2 --- /dev/null +++ b/tests/Streams/ResourceStreamTest.php @@ -0,0 +1,246 @@ +getObjectAttribute($resourceStream, 'stream'); + $this->assertInternalType(IsType::TYPE_RESOURCE, $stream); + + if ($closeResource) + { + fclose($resource); + } + } + + /** + * Data provider for test__construct + * + * returns data of type resource + */ + public function constructProvider() + { + return [ + [ fopen(__FILE__, 'r'), ], // Myself, sure I exists + [ fsockopen('tcp://127.0.0.1', 18728), ], // Socket + [ STDIN, false ], // Try it, but do not close STDIN please !!! + // What else ? + ]; + } + + /** + * Test that read function return expected values, and that consecutive reads return data + * + * @covers ::read + * @dataProvider readProvider + * @param resource $resource Cannot typehint, PHP refuse it + * @param string $expected the rsult we should have + */ + public function test__read(ResourceStream $stream, string $expected) + { + $this->assertSame($expected, $stream->read(strlen($expected))); + } + + public function readProvider() + { + $resource = fopen(__FILE__, 'r'); + $me = new ResourceStream($resource); + return [ + [ $me, '<'], // Read for byte + [ $me, '?php'], // Read following bytes. File statrts with "read($length); + } + + public function readBadLengthProvider() + { + $resource = fopen(__FILE__, 'r'); + $me = new ResourceStream($resource); + return [ + [ $me, 0 ], + [ $me, -1 ], + ]; + fclose($resource); + } + /** + * Test read to invalid resource + * + * @covers ::read + * @dataProvider readBadResourceProvider + * @expectedException RouterOS\Exceptions\StreamException + * @param resource $resource Cannot typehint, PHP refuse it + */ + public function test__readBadResource(ResourceStream $stream, int $length) + { + $stream->read($length); + } + + public function readBadResourceProvider() + { + $resource = fopen(__FILE__, 'r'); + $me = new ResourceStream($resource); + fclose($resource); + return [ + [ $me, 1 ], + ]; + } + + /** + * Test that write function returns writen length + * + * @covers ::write + * @dataProvider writeProvider + * @param ResourceStram $resource to test + * @param string $toWrite the writed string + */ + public function test__write(ResourceStream $stream, string $toWrite) + { + $this->assertEquals(strlen($toWrite) , $stream->write($toWrite)); + } + + public function writeProvider() + { + $resource = fopen("/dev/null", 'w'); + $null = new ResourceStream($resource); + return [ + [ $null, 'yyaagagagag'], // Take that + ]; + fclose($resource); + } + + /** + * Test write to invalid resource + * + * @covers ::write + * @dataProvider writeBadResourceProvider + * @expectedException RouterOS\Exceptions\StreamException + * @param resource $resource to test + * @param string $toWrite the writed string + */ + public function test__writeBadResource(ResourceStream $stream, string $toWrite) + { + $stream->write($toWrite); + } + + public function writeBadResourceProvider() + { + $resource = fopen('/dev/null', 'w'); + $me = new ResourceStream($resource); + fclose($resource); + return [ + [ $me, 'sasasaas' ], // Take that + ]; + } + + /** + * Test double close resource + * + * @covers ::close + * @dataProvider doubleCloseProvider + * @expectedException RouterOS\Exceptions\StreamException + * @param resource $resource to test + */ + public function test_doubleClose(ResourceStream $stream) + { + $stream->close(); + $stream->close(); + } + + public function doubleCloseProvider() + { + return [ + [ new ResourceStream(fopen('/dev/null', 'w')), 'sasasaas' ], // Take that + ]; + } + + /** + * Test write to closed resource + * + * @covers ::close + * @covers ::write + * @dataProvider writeClosedResourceProvider + * @expectedException RouterOS\Exceptions\StreamException + * @param resource $resource to test + * @param string $toWrite the writed string + */ + public function test_close(ResourceStream $stream, string $toWrite) + { + $stream->close(); + $stream->write($toWrite); + } + + public function writeClosedResourceProvider() + { + return [ + [ new ResourceStream(fopen('/dev/null', 'w')), 'sasasaas' ], // Take that + ]; + } + +} \ No newline at end of file diff --git a/tests/Streams/StringStreamTest.php b/tests/Streams/StringStreamTest.php new file mode 100644 index 0000000..ebabf89 --- /dev/null +++ b/tests/Streams/StringStreamTest.php @@ -0,0 +1,144 @@ +assertInstanceOf(StringStream::class, new StringStream($string)); + } + + public function constructProvider() + { + return [ + [ chr(0) ], + [ '' ], + [ '1' ], + [ 'lkjl'.chr(0).'kjkljllkjkljljklkjkljlkjljlkjkljkljlkjjll'], + ]; + } + + + /** + * test that write function returns the effective writen bytes + * @covers ::write + * @dataProvider writeProvider + * @param string $toWrite the string to write + * @param int|null $length the count if bytes to write + * @param int $expected the number of bytes that must be writen + */ + + public function test__write(string $string, $length, int $expected) + { + $stream = new StringStream('Does not matters'); + if (is_null($length)) { + $this->assertEquals($expected, $stream->write($string)); + } + else { + $this->assertEquals($expected, $stream->write($string, $length)); + } + + } + + public function writeProvider() + { + return [ + [ '', 0, 0 ], + [ '', 10, 0 ], + [ '', null, 0 ], + [ 'Yabala', 0, 0], + [ 'Yabala', 1, 1], + [ 'Yabala', 6, 6], + [ 'Yabala', 100, 6], + [ 'Yabala', null, 6], + [ chr(0), 0, 0], + [ chr(0), 1, 1], + [ chr(0), 100, 1], + [ chr(0), null, 1], + ]; + } + + /** + * @covers ::write + * @expectedException \InvalidArgumentException + */ + public function test__writeWithNegativeLength() + { + $stream = new StringStream('Does not matters'); + $stream->write("PLOP", -1); + } + + /** + * Test read function + */ + public function test__read() + { + $stream = new StringStream('123456789'); + + $this->assertEquals('', $stream->read(0)); + $this->assertEquals('1', $stream->read(1)); + $this->assertEquals('23', $stream->read(2)); + $this->assertEquals('456', $stream->read(3)); + $this->assertEquals('', $stream->read(0)); + $this->assertEquals('789', $stream->read(4)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function test__readBadLength() + { + $stream = new StringStream('123456789'); + $stream->read(-1); + } + + /** + * @covers ::read + * @dataProvider readWhileEmptyProvider + * @expectedException \RouterOS\Exceptions\StreamException + */ + public function test__readWhileEmpty(StringStream $stream, int $length) + { + $stream->read($length); + } + + public function readWhileEmptyProvider() + { + $stream = new StringStream('123456789'); + $stream->read(9); + yield [$stream, 1]; + + $stream = new StringStream('123456789'); + $stream->read(5); + $stream->read(4); + yield [$stream, 1]; + + $stream = new StringStream(''); + yield [$stream, 1]; + } + + /** + * @expectedException \RouterOS\Exceptions\StreamException + */ + public function testReadClosed() + { + $stream = new StringStream('123456789'); + $stream->close(); + $stream->read(1); + } +} \ No newline at end of file From efd9e831798d5fad674b7ff99762bf9ca43319b7 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sat, 23 Mar 2019 19:47:38 +0300 Subject: [PATCH 02/10] code style finetyne --- src/APIConnector.php | 25 +++-- src/APILengthCoDec.php | 130 +++++++++++----------- src/Client.php | 40 ++----- src/Helpers/BinaryStringHelper.php | 39 ++++--- src/Interfaces/StreamInterface.php | 23 ++-- src/SocketTrait.php | 21 ++++ src/Streams/ResourceStream.php | 70 ++++++++---- src/Streams/StringStream.php | 48 ++++---- tests/APIConnectorTest.php | 78 +++++++------ tests/APILengthCoDecTest.php | 4 +- tests/Streams/ResourceStreamTest.php | 159 +++++++++++++++------------ tests/Streams/StringStreamTest.php | 80 ++++++++------ 12 files changed, 403 insertions(+), 314 deletions(-) diff --git a/src/APIConnector.php b/src/APIConnector.php index cee50ed..f85c013 100644 --- a/src/APIConnector.php +++ b/src/APIConnector.php @@ -1,17 +1,17 @@ stream); - if ($length>0) { - return $this->stream->read($length); - } - return ''; + return ($length > 0) ? $this->stream->read($length) : ''; } - public function writeWord(string $word) + /** + * Write word to stream + * + * @param string $word + * @return int return number of written bytes + */ + public function writeWord(string $word): int { $encodedLength = APILengthCoDec::encodeLength(strlen($word)); - return $this->stream->write($encodedLength.$word); + return $this->stream->write($encodedLength . $word); } } diff --git a/src/APILengthCoDec.php b/src/APILengthCoDec.php index b3a59d2..b37c15a 100644 --- a/src/APILengthCoDec.php +++ b/src/APILengthCoDec.php @@ -7,16 +7,15 @@ /** * class APILengthCoDec - * + * * Coder / Decoder for length field in mikrotik API communication protocol * * @package RouterOS * @since 0.9 */ - class APILengthCoDec { - public static function encodeLength(int $length) + public static function encodeLength(int $length): string { // Encode the length : // - if length <= 0x7F (binary : 01111111 => 7 bits set to 1) @@ -26,63 +25,63 @@ public static function encodeLength(int $length) // - length <= 0x3FFF (binary : 00111111 11111111 => 14 bits set to 1) // - encode length with two bytes // - set length value to 0x8000 (=> 10000000 00000000) - // - add length : as length maximumal value is 14 bits to 1, this does not modify the 2 most significants bits (10) + // - add length : as length maximumal value is 14 bits to 1, this does not modify the 2 most significance bits (10) // - end // => minimal encoded value is 10000000 10000000 // - length <= 0x1FFFFF (binary : 00011111 11111111 11111111 => 21 bits set to 1) // - encode length with three bytes // - set length value to 0xC00000 (binary : 11000000 00000000 00000000) - // - add length : as length maximal vlaue is 21 bits to 1, this does not modify the 3 most significants bits (110) + // - add length : as length maximal vlaue is 21 bits to 1, this does not modify the 3 most significance bits (110) // - end // => minimal encoded value is 11000000 01000000 00000000 // - length <= 0x0FFFFFFF (binary : 00001111 11111111 11111111 11111111 => 28 bits set to 1) // - encode length with four bytes // - set length value to 0xE0000000 (binary : 11100000 00000000 00000000 00000000) - // - add length : as length maximal vlaue is 28 bits to 1, this does not modify the 4 most significants bits (1110) + // - add length : as length maximal vlaue is 28 bits to 1, this does not modify the 4 most significance bits (1110) // - end // => minimal encoded value is 11100000 00100000 00000000 00000000 // - length <= 0x7FFFFFFFFF (binary : 00000111 11111111 11111111 11111111 11111111 => 35 bits set to 1) // - encode length with five bytes // - set length value to 0xF000000000 (binary : 11110000 00000000 00000000 00000000 00000000) - // - add length : as length maximal vlaue is 35 bits to 1, this does not modify the 5 most significants bits (11110) + // - add length : as length maximal vlaue is 35 bits to 1, this does not modify the 5 most significance bits (11110) // - end // - length > 0x7FFFFFFFFF : not supported - - if ($length<0) - { - throw new \DomainException(sprintf("Length of word can not be negative (%d)", $length)); + if ($length < 0) { + throw new \DomainException("Length of word can not be negative ($length)"); } - - if ($length<=0x7F) { + + if ($length <= 0x7F) { return BinaryStringHelper::IntegerToNBOBinaryString($length); } - else if ($length<=0x3FFF) { - return BinaryStringHelper::IntegerToNBOBinaryString(0x8000+$length); + + if ($length <= 0x3FFF) { + return BinaryStringHelper::IntegerToNBOBinaryString(0x8000 + $length); } - else if ($length<=0x1FFFFF) { - return BinaryStringHelper::IntegerToNBOBinaryString(0xC00000+$length); + + if ($length <= 0x1FFFFF) { + return BinaryStringHelper::IntegerToNBOBinaryString(0xC00000 + $length); } - else if ($length<=0x0FFFFFFF){ - return BinaryStringHelper::IntegerToNBOBinaryString(0xE0000000+$length); + + if ($length <= 0x0FFFFFFF) { + return BinaryStringHelper::IntegerToNBOBinaryString(0xE0000000 + $length); + } // cannot compare with 0x7FFFFFFFFF on 32 bits systems + + if (PHP_INT_SIZE < 8) { + // Cannot be done on 32 bits systems + // PHP5 windows versions of php, even on 64 bits systems was impacted + // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit + + // @codeCoverageIgnoreStart + throw new \OverflowException("Your system is using 32 bits integers, cannot encode length of $length bytes on this system"); + // @codeCoverageIgnoreEnd } - // cannot compare with 0x7FFFFFFFFF on 32 bits systems - else { - if (PHP_INT_SIZE<8) { - // Cannot be done on 32 bits systems - // PHP5 windows versions of php, even on 64 bits systems was impacted - // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit - - // @codeCoverageIgnoreStart - throw new \OverflowException(sprintf("Your system is using 32 bits integers, cannot encode length of %d bytes on this system", $length)); - // @codeCoverageIgnoreEnd - } - if ($length<=0x7FFFFFFFFF) - { - return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000+$length); - } + + if ($length <= 0x7FFFFFFFFF) { + return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000 + $length); } - throw new \DomainException(sprintf('Length of word too huge (%x)', $length)); + + throw new \DomainException("Length of word too huge ($length)"); } // Decode length of data when reading : @@ -107,7 +106,7 @@ public static function encodeLength(int $length) // After receiving unknown control byte API client cannot proceed, because it cannot know how to interpret following bytes // Currently control bytes are not used - public static function decodeLength(StreamInterface $stream) + public static function decodeLength(StreamInterface $stream): int { // if (false === is_resource($stream)) { // throw new \InvalidArgumentException( @@ -122,76 +121,81 @@ public static function decodeLength(StreamInterface $stream) $firstByte = ord($stream->read(1)); // If first byte is not set, length is the value of the byte - if (0==($firstByte&0x80)) { + if (0 === ($firstByte & 0x80)) { return $firstByte; } // if 10xxxxxx, length is 2 bytes encoded - if (0x80==($firstByte&0xC0)) - { - // Set 2 most significants bits to 0 + if (0x80 === ($firstByte & 0xC0)) { + // Set 2 most significands bits to 0 $result = $firstByte & 0x3F; + // shift left 8 bits to have 2 bytes - $result = $result << 8; - // read next byte and use it as least significant + $result <<= 8; + + // read next byte and use it as least significant $result |= ord($stream->read(1)); return $result; } // if 110xxxxx, length is 3 bytes encoded - if (0xC0 == ($firstByte & 0xE0)) - { - // Set 3 most significants bits to 0 + if (0xC0 === ($firstByte & 0xE0)) { + // Set 3 most significands bits to 0 $result = $firstByte & 0x1F; + // shift left 16 bits to have 3 bytes - $result = $result << 16; + $result <<= 16; + // read next 2 bytes as value and use it as least significant position $result |= (ord($stream->read(1)) << 8); $result |= ord($stream->read(1)); - return $result; + return $result; } // if 1110xxxx, length is 4 bytes encoded - if (0xE0 == ($firstByte & 0xF0)) - { - // Set 4 most significants bits to 0 + if (0xE0 === ($firstByte & 0xF0)) { + // Set 4 most significance bits to 0 $result = $firstByte & 0x0F; + // shift left 24 bits to have 4 bytes - $result = $result << 24; + $result <<= 24; + // read next 3 bytes as value and use it as least significant position $result |= (ord($stream->read(1)) << 16); $result |= (ord($stream->read(1)) << 8); $result |= ord($stream->read(1)); - return $result; + return $result; } // if 11110xxx, length is 5 bytes encoded - if (0xF0 == ($firstByte & 0xF8)) - { - // Not possibe on 32 bits systems - if (PHP_INT_SIZE<8) { + if (0xF0 === ($firstByte & 0xF8)) { + // Not possible on 32 bits systems + if (PHP_INT_SIZE < 8) { // Cannot be done on 32 bits systems // PHP5 windows versions of php, even on 64 bits systems was impacted // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit // How can we test it ? // @codeCoverageIgnoreStart - throw new \OverflowException(sprintf("Your system is using 32 bits integers, cannot decode this value (%x) on this system", $firstByte)); + throw new \OverflowException("Your system is using 32 bits integers, cannot decode this value ($firstByte) on this system"); // @codeCoverageIgnoreEnd } - // Set 5 most significants bits to 0 + + // Set 5 most significance bits to 0 $result = $firstByte & 0x07; + // shift left 232 bits to have 5 bytes - $result = $result << 32; + $result <<= 32; + // read next 4 bytes as value and use it as least significant position $result |= (ord($stream->read(1)) << 24); $result |= (ord($stream->read(1)) << 16); $result |= (ord($stream->read(1)) << 8); $result |= ord($stream->read(1)); - return $result; + return $result; } - // Now the only solution is 5 most significants bits are set to 1 (11111xxx) + // Now the only solution is 5 most significance bits are set to 1 (11111xxx) // This is a control word, not implemented by Mikrotik for the moment - throw new \UnexpectedValueException("Control Word found\n"); + throw new \UnexpectedValueException('Control Word found'); } -} \ No newline at end of file +} diff --git a/src/Client.php b/src/Client.php index e9f7553..d43cf16 100644 --- a/src/Client.php +++ b/src/Client.php @@ -17,27 +17,6 @@ class Client implements Interfaces\ClientInterface { use SocketTrait; - /** - * Socket resource - * - * @var resource|null - */ - private $_socket; - - /** - * Code of error - * - * @var int - */ - private $_socket_err_num; - - /** - * Description of socket error - * - * @var string - */ - private $_socket_err_str; - /** * Configuration of connection * @@ -48,10 +27,10 @@ class Client implements Interfaces\ClientInterface /** * API communication object * - * @var APIConnector + * @var \RouterOS\APIConnector */ - private $connector; + private $_connector; /** * Client constructor. @@ -121,7 +100,6 @@ public function setConfig(Config $config) * * @param string|array|\RouterOS\Query $query * @return \RouterOS\Client - * @throws \RouterOS\Exceptions\ClientException * @throws \RouterOS\Exceptions\QueryException */ public function write($query): Client @@ -139,11 +117,11 @@ public function write($query): Client // Send commands via loop to router foreach ($query->getQuery() as $command) { - $this->connector->writeWord(trim($command)); + $this->_connector->writeWord(trim($command)); } // Write zero-terminator (empty string) - $this->connector->writeWord(''); + $this->_connector->writeWord(''); return $this; } @@ -156,7 +134,7 @@ public function write($query): Client * Each block end with an zero byte (empty line) * Reply ends with a complete !done or !fatal block (ended with 'empty line') * A !fatal block precedes TCP connexion close - * + * * @param bool $parse * @return array */ @@ -169,9 +147,9 @@ public function read(bool $parse = true): array // Read answer from socket in loop while (true) { - $word = $this->connector->readWord(); + $word = $this->_connector->readWord(); - if (''===$word) { + if ('' === $word) { if ($lastReply) { // We received a !done or !fatal message in a precedent loop // response is complete @@ -202,7 +180,6 @@ public function read(bool $parse = true): array * * @param string|array|\RouterOS\Query $query * @return \RouterOS\Client - * @throws \RouterOS\Exceptions\ClientException * @throws \RouterOS\Exceptions\QueryException */ public function w($query): Client @@ -373,7 +350,7 @@ private function connect(): bool // If socket is active if (null !== $this->getSocket()) { - $this->connector = new APIConnector(new Streams\ResourceStream($this->getSocket())); + $this->_connector = new APIConnector(new Streams\ResourceStream($this->getSocket())); // If we logged in then exit from loop if (true === $this->login()) { $connected = true; @@ -382,7 +359,6 @@ private function connect(): bool // Else close socket and start from begin $this->closeSocket(); - $this->stream = null; } // Sleep some time between tries diff --git a/src/Helpers/BinaryStringHelper.php b/src/Helpers/BinaryStringHelper.php index aeb5a0f..a5e7e70 100644 --- a/src/Helpers/BinaryStringHelper.php +++ b/src/Helpers/BinaryStringHelper.php @@ -1,60 +1,65 @@ chr(0x0F).chr(0xF7) * 0x12345678 => chr(0x12).chr(0x34).chr(0x56).chr(0x76) * Compatible with 8, 16, 32, 64 etc.. bits systems * * @see https://en.wikipedia.org/wiki/Endianness - * @param int $value the integer value to be converted - * @return string the binary string - */ - public static function IntegerToNBOBinaryString(int $value) + * @param int $value the integer value to be converted + * @return string the binary string + */ + public static function IntegerToNBOBinaryString(int $value): string { // Initialize an empty string $buffer = ''; + // Lets start from the most significant byte - for ($i=(PHP_INT_SIZE-1); $i>=0; $i--) { + for ($i = (PHP_INT_SIZE - 1); $i >= 0; $i--) { // Prepare a mask to keep only the most significant byte of $value - $mask = 0xFF << ($i*8); + $mask = 0xFF << ($i * 8); + // If the most significant byte is not 0, the final string must contain it // If we have already started to construct the string (i.e. there are more signficant digits) // we must set the byte, even if it is a 0. // 0xFF00FF, for example, require to set the second byte byte with a 0 value - if (($value & $mask) || strlen($buffer)!=0) { - // Get the curent byte by shifting it to least significant position and add it to the string + if (($value & $mask) || $buffer !== '') { + // Get the current byte by shifting it to least significant position and add it to the string // 0xFF12345678 => 0xFF - $byte = $value>>(8*$i); + $byte = $value >> (8 * $i); $buffer .= chr($byte); + // Set the most significant byte to 0 so we can restart the process being shure // that the value is left padded with 0 // 0xFF12345678 => 0x12345678 // -1 = 0xFFFFF.... (number of F depend of PHP_INT_SIZE ) - $mask = -1 >> ((PHP_INT_SIZE-$i)*8); + $mask = -1 >> ((PHP_INT_SIZE - $i) * 8); $value &= $mask; } } + // Special case, 0 will not fill the buffer, have to construct it manualy - if (0==$value) { + if (0 === $value) { $buffer = chr(0); } + return $buffer; } -} \ No newline at end of file +} diff --git a/src/Interfaces/StreamInterface.php b/src/Interfaces/StreamInterface.php index 2832a37..4131992 100644 --- a/src/Interfaces/StreamInterface.php +++ b/src/Interfaces/StreamInterface.php @@ -1,4 +1,5 @@ stream = $stream; } /** - * {@inheritDoc} - * @throws \InvalidArgumentException + * @param int $length + * @return string + * @throws \RouterOS\Exceptions\StreamException + * @throws \InvalidArgumentException */ - public function read(int $length) : string + public function read(int $length): string { - if ($length<=0) { - throw new \InvalidArgumentException("Cannot read zero ot negative count of bytes from a stream"); + if ($length <= 0) { + throw new \InvalidArgumentException('Cannot read zero ot negative count of bytes from a stream'); } + // TODO: Ignore errors here, but why? $result = @fread($this->stream, $length); if (false === $result) { - throw new StreamException(sprintf("Error reading %d bytes", $length)); + throw new StreamException("Error reading $length bytes"); } return $result; } /** - * {@inheritDoc} + * Writes a string to a stream + * + * Write $length bytes of string, if not mentioned, write all the string + * Must be binary safe (as fread). + * if $length is greater than string length, write all string and return number of writen bytes + * if $length os smaller than string length, remaining bytes are losts. + * + * @param string $string + * @param int|null $length the numer of bytes to read + * @return int the number of written bytes + * @throws \RouterOS\Exceptions\StreamException */ - public function write(string $string, $length=null) : int + public function write(string $string, int $length = null): int { - if (is_null($length)) { + if (null === $length) { $length = strlen($string); } + + // TODO: Ignore errors here, but why? $result = @fwrite($this->stream, $string, $length); + if (false === $result) { - throw new StreamException(sprintf("Error writing %d bytes", $length)); + throw new StreamException("Error writing $length bytes"); } + return $result; } + /** + * Close stream connection + * + * @return void + * @throws \RouterOS\Exceptions\StreamException + */ public function close() { $hasBeenClosed = false; - if (!is_null($this->stream)) { + + if (null !== $this->stream) { $hasBeenClosed = @fclose($this->stream); - $this->stream=null; + $this->stream = null; } - if (false===$hasBeenClosed) { - throw new StreamException("Error closing stream"); - + + if (false === $hasBeenClosed) { + throw new StreamException('Error closing stream'); } } -} \ No newline at end of file +} diff --git a/src/Streams/StringStream.php b/src/Streams/StringStream.php index 11fbe06..e845f32 100644 --- a/src/Streams/StringStream.php +++ b/src/Streams/StringStream.php @@ -1,4 +1,5 @@ buffer); - if ($length<0) { - throw new \InvalidArgumentException("Cannot read a negative count of bytes from a stream"); + if ($length < 0) { + throw new \InvalidArgumentException('Cannot read a negative count of bytes from a stream'); } - if (0 == $remaining) { - throw new StreamException("End of stream"); + if (0 === $remaining) { + throw new StreamException('End of stream'); } - if ($length>=$remaining) { + if ($length >= $remaining) { // returns all $result = $this->buffer; // No more in the buffer - $this->buffer=''; - } - else { + $this->buffer = ''; + } else { // acquire $length characters from the buffer $result = substr($this->buffer, 0, $length); // remove $length characters from the buffer @@ -68,26 +67,31 @@ public function read(int $length) : string /** * Fake write method, do nothing except return the "writen" length * - * @param string $string The string to write - * @param int|null $length the number of characters to write - * @throws \InvalidArgumentException on invalid length - * @return number of "writen" bytes + * @param string $string The string to write + * @param int|null $length the number of characters to write + * @return int number of "writen" bytes + * @throws \InvalidArgumentException on invalid length */ - public function write(string $string, $length=null) : int + public function write(string $string, int $length = null): int { - if(null === $length) { + if (null === $length) { $length = strlen($string); } - if ($length<0) { - throw new \InvalidArgumentException("Cannot write a negative count of bytes"); + if ($length < 0) { + throw new \InvalidArgumentException('Cannot write a negative count of bytes'); } return min($length, strlen($string)); } + /** + * Close stream connection + * + * @return void + */ public function close() { $this->buffer = ''; } -} \ No newline at end of file +} diff --git a/tests/APIConnectorTest.php b/tests/APIConnectorTest.php index 1e1edc5..72c0c43 100644 --- a/tests/APIConnectorTest.php +++ b/tests/APIConnectorTest.php @@ -3,30 +3,29 @@ namespace RouterOS\Tests; use PHPUnit\Framework\TestCase; - use RouterOS\APIConnector; use RouterOS\Streams\StringStream; use RouterOS\Streams\ResourceStream; use RouterOS\APILengthCoDec; use RouterOS\Interfaces\StreamInterface; - /** * Limit code coverage to the class RouterOS\APIStream - * @coversDefaultClass RouterOS\APIConnector + * + * @coversDefaultClass \RouterOS\APIConnector */ class APIConnectorTest extends TestCase { - /** * Test that constructor is OK with different kinds of resources - * + * * @covers ::__construct * @dataProvider constructProvider - * @param Resource $resource Cannot typehint, PHP refuse it - * @param bool $closeResource shall we close the resource ? + * + * @param StreamInterface $stream Cannot typehint, PHP refuse it + * @param bool $closeResource shall we close the resource ? */ - public function test_construct(StreamInterface $stream, bool $closeResource=false) + public function test_construct(StreamInterface $stream, bool $closeResource = false) { $apiStream = new APIConnector($stream); $this->assertInstanceOf(APIConnector::class, $apiStream); @@ -35,14 +34,14 @@ public function test_construct(StreamInterface $stream, bool $closeResource=fals } } - public function constructProvider() + public function constructProvider(): array { return [ - [ new ResourceStream(fopen(__FILE__, 'r')), ], // Myself, sure I exists - [ new ResourceStream(fsockopen('tcp://127.0.0.1', 18728)), ], // Socket - [ new ResourceStream(STDIN), false ], // Try it, but do not close STDIN please !!! - [ new StringStream('Hello World !!!') ], // Try it, but do not close STDIN please !!! - [ new StringStream('') ], // Try it, but do not close STDIN please !!! + [new ResourceStream(fopen(__FILE__, 'rb')),], // Myself, sure I exists + [new ResourceStream(fsockopen('tcp://127.0.0.1', 18728)),], // Socket + [new ResourceStream(STDIN), false], // Try it, but do not close STDIN please !!! + [new StringStream('Hello World !!!')], // Try it, but do not close STDIN please !!! + [new StringStream('')], // Try it, but do not close STDIN please !!! // What else ? ]; } @@ -50,43 +49,50 @@ public function constructProvider() /** * @covers ::readWord * @dataProvider readWordProvider + * + * @param APIConnector $connector + * @param string $expected */ - - public function test__readWord(APIConnector $connector, $expected) + public function test__readWord(APIConnector $connector, string $expected) { $this->assertSame($expected, $connector->readWord()); } - public function readWordProvider() + public function readWordProvider(): array { - $longString = '=comment='.str_repeat('a',10000); - $length = strlen($longString); - return [ - [ new APIConnector(new StringStream(chr(0))), ''], - [ new APIConnector(new StringStream(chr(3).'!re')), '!re'], - [ new APIConnector(new StringStream(chr(5).'!done')), '!done'], - [ new APIConnector(new StringStream(APILengthCoDec::encodeLength($length).$longString)), $longString], + $longString = '=comment=' . str_repeat('a', 10000); + $length = strlen($longString); + + return [ + [new APIConnector(new StringStream(chr(0))), ''], + [new APIConnector(new StringStream(chr(3) . '!re')), '!re'], + [new APIConnector(new StringStream(chr(5) . '!done')), '!done'], + [new APIConnector(new StringStream(APILengthCoDec::encodeLength($length) . $longString)), $longString], ]; } - /** - * @covers ::writeWord - * @dataProvider writeWordProvider - */ + /** + * @covers ::writeWord + * @dataProvider writeWordProvider + * + * @param APIConnector $connector + * @param string $toWrite + * @param int $expected + */ public function test_writeWord(APIConnector $connector, string $toWrite, int $expected) { $this->assertEquals($expected, $connector->writeWord($toWrite)); } - public function writeWordProvider() + public function writeWordProvider(): array { return [ - [ new APIConnector(new StringStream('Have FUN !!!')), '', 1 ], // length is 0, but have to write it on 1 byte, minimum - [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 54), 55 ], // arbitrary value - [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 127), 128 ], // maximum value for 1 byte encoding lentgth - [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 128), 130 ], // minimum value for 2 bytes encoding lentgth - [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 254), 256 ], // special value isn't it ? - [ new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 255), 257 ], // special value isn't it ? + [new APIConnector(new StringStream('Have FUN !!!')), '', 1], // length is 0, but have to write it on 1 byte, minimum + [new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 54), 55], // arbitrary value + [new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 127), 128], // maximum value for 1 byte encoding lentgth + [new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 128), 130], // minimum value for 2 bytes encoding lentgth + [new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 254), 256], // special value isn't it ? + [new APIConnector(new StringStream('Have FUN !!!')), str_repeat(' ', 255), 257], // special value isn't it ? ]; } -} \ No newline at end of file +} diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php index 69c37e2..08a0e77 100644 --- a/tests/APILengthCoDecTest.php +++ b/tests/APILengthCoDecTest.php @@ -11,7 +11,7 @@ /** * Limit code coverage to the class - * @coversDefaultClass RouterOS\APILengthCoDec + * @coversDefaultClass \RouterOS\APILengthCoDec */ class APILengthCoDecTest extends TestCase { @@ -117,4 +117,4 @@ public function decodeLengthControlWordProvider() [chr(0xFF)], // maximum ]; } -} \ No newline at end of file +} diff --git a/tests/Streams/ResourceStreamTest.php b/tests/Streams/ResourceStreamTest.php index 9cda9a2..d2c466a 100644 --- a/tests/Streams/ResourceStreamTest.php +++ b/tests/Streams/ResourceStreamTest.php @@ -4,22 +4,23 @@ use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Constraint\IsType; - use RouterOS\Streams\ResourceStream; -use RouterOS\Exceptions\StreamException; /** * Limit code coverage to the class RouterOS\APIStream - * @coversDefaultClass RouterOS\Streams\ResourceStream + * + * @coversDefaultClass \RouterOS\Streams\ResourceStream */ class ResourceStreamTest extends TestCase { /** * Test that constructor throws an InvalidArgumentException on bad parameter type - * + * * @covers ::__construct * @expectedException \InvalidArgumentException * @dataProvider constructNotResourceProvider + * + * @param $notResource */ public function test__constructNotResource($notResource) @@ -32,37 +33,37 @@ public function test__constructNotResource($notResource) * * returns data not of type resource */ - public function constructNotResourceProvider() + public function constructNotResourceProvider(): array { return [ [0], // integer [3.14], // float ['a string'], // string [ - [ 0 , 3.14 ] // Array + [0, 3.14] // Array ], - [ new \stdClass() ], // Object + [new \stdClass()], // Object // What else ? ]; } /** * Test that constructor is OK with different kinds of resources - * + * * @covers ::__construct * @dataProvider constructProvider - * @param resource $resource Cannot typehint, PHP refuse it - * @param bool $closeResource shall we close the resource ? + * + * @param resource $resource Cannot typehint, PHP refuse it + * @param bool $closeResource shall we close the resource ? */ - public function test_construct($resource, bool $closeResource=true) + public function test_construct($resource, bool $closeResource = true) { $resourceStream = new ResourceStream($resource); $stream = $this->getObjectAttribute($resourceStream, 'stream'); $this->assertInternalType(IsType::TYPE_RESOURCE, $stream); - if ($closeResource) - { + if ($closeResource) { fclose($resource); } } @@ -70,142 +71,157 @@ public function test_construct($resource, bool $closeResource=true) /** * Data provider for test__construct * - * returns data of type resource + * @return array data of type resource */ - public function constructProvider() + public function constructProvider(): array { return [ - [ fopen(__FILE__, 'r'), ], // Myself, sure I exists - [ fsockopen('tcp://127.0.0.1', 18728), ], // Socket - [ STDIN, false ], // Try it, but do not close STDIN please !!! + [fopen(__FILE__, 'rb'),], // Myself, sure I exists + [fsockopen('tcp://127.0.0.1', 18728),], // Socket + [STDIN, false], // Try it, but do not close STDIN please !!! // What else ? ]; } /** * Test that read function return expected values, and that consecutive reads return data - * + * * @covers ::read * @dataProvider readProvider - * @param resource $resource Cannot typehint, PHP refuse it - * @param string $expected the rsult we should have + * + * @param ResourceStream $stream Cannot typehint, PHP refuse it + * @param string $expected the result we should have + * @throws \RouterOS\Exceptions\StreamException + * @throws \InvalidArgumentException */ public function test__read(ResourceStream $stream, string $expected) { $this->assertSame($expected, $stream->read(strlen($expected))); } - public function readProvider() + public function readProvider(): array { - $resource = fopen(__FILE__, 'r'); - $me = new ResourceStream($resource); + $resource = fopen(__FILE__, 'rb'); + $me = new ResourceStream($resource); + return [ - [ $me, '<'], // Read for byte - [ $me, '?php'], // Read following bytes. File statrts with "read($length); } - public function readBadLengthProvider() + public function readBadLengthProvider(): array { - $resource = fopen(__FILE__, 'r'); - $me = new ResourceStream($resource); + $resource = fopen(__FILE__, 'rb'); + $me = new ResourceStream($resource); + return [ - [ $me, 0 ], - [ $me, -1 ], + [$me, 0], + [$me, -1], ]; - fclose($resource); } + /** * Test read to invalid resource - * + * * @covers ::read * @dataProvider readBadResourceProvider - * @expectedException RouterOS\Exceptions\StreamException - * @param resource $resource Cannot typehint, PHP refuse it + * @expectedException \RouterOS\Exceptions\StreamException + * + * @param ResourceStream $stream Cannot typehint, PHP refuse it + * @param int $length */ public function test__readBadResource(ResourceStream $stream, int $length) { $stream->read($length); } - public function readBadResourceProvider() + public function readBadResourceProvider(): array { - $resource = fopen(__FILE__, 'r'); - $me = new ResourceStream($resource); + $resource = fopen(__FILE__, 'rb'); + $me = new ResourceStream($resource); fclose($resource); return [ - [ $me, 1 ], + [$me, 1], ]; } /** * Test that write function returns writen length - * + * * @covers ::write * @dataProvider writeProvider - * @param ResourceStram $resource to test - * @param string $toWrite the writed string + * + * @param ResourceStream $stream to test + * @param string $toWrite the writed string + * @throws \RouterOS\Exceptions\StreamException */ public function test__write(ResourceStream $stream, string $toWrite) { - $this->assertEquals(strlen($toWrite) , $stream->write($toWrite)); + $this->assertEquals(strlen($toWrite), $stream->write($toWrite)); } - public function writeProvider() + public function writeProvider(): array { - $resource = fopen("/dev/null", 'w'); - $null = new ResourceStream($resource); + $resource = fopen('/dev/null', 'wb'); + $null = new ResourceStream($resource); + return [ - [ $null, 'yyaagagagag'], // Take that + [$null, 'yyaagagagag'], // Take that ]; - fclose($resource); } /** * Test write to invalid resource - * + * * @covers ::write * @dataProvider writeBadResourceProvider - * @expectedException RouterOS\Exceptions\StreamException - * @param resource $resource to test - * @param string $toWrite the writed string + * @expectedException \RouterOS\Exceptions\StreamException + * + * @param ResourceStream $stream to test + * @param string $toWrite the written string */ public function test__writeBadResource(ResourceStream $stream, string $toWrite) { $stream->write($toWrite); } - public function writeBadResourceProvider() + public function writeBadResourceProvider(): array { - $resource = fopen('/dev/null', 'w'); - $me = new ResourceStream($resource); + $resource = fopen('/dev/null', 'wb'); + $me = new ResourceStream($resource); fclose($resource); + return [ - [ $me, 'sasasaas' ], // Take that + [$me, 'sasasaas'], // Take that ]; } /** * Test double close resource - * + * * @covers ::close * @dataProvider doubleCloseProvider - * @expectedException RouterOS\Exceptions\StreamException - * @param resource $resource to test + * @expectedException \RouterOS\Exceptions\StreamException + * + * @param ResourceStream $stream to test */ public function test_doubleClose(ResourceStream $stream) { @@ -213,22 +229,23 @@ public function test_doubleClose(ResourceStream $stream) $stream->close(); } - public function doubleCloseProvider() + public function doubleCloseProvider(): array { return [ - [ new ResourceStream(fopen('/dev/null', 'w')), 'sasasaas' ], // Take that + [new ResourceStream(fopen('/dev/null', 'wb')), 'sasasaas'], // Take that ]; } /** * Test write to closed resource - * + * * @covers ::close * @covers ::write * @dataProvider writeClosedResourceProvider - * @expectedException RouterOS\Exceptions\StreamException - * @param resource $resource to test - * @param string $toWrite the writed string + * @expectedException \RouterOS\Exceptions\StreamException + * + * @param ResourceStream $stream to test + * @param string $toWrite the written string */ public function test_close(ResourceStream $stream, string $toWrite) { @@ -236,11 +253,11 @@ public function test_close(ResourceStream $stream, string $toWrite) $stream->write($toWrite); } - public function writeClosedResourceProvider() + public function writeClosedResourceProvider(): array { return [ - [ new ResourceStream(fopen('/dev/null', 'w')), 'sasasaas' ], // Take that + [new ResourceStream(fopen('/dev/null', 'wb')), 'sasasaas'], // Take that ]; } -} \ No newline at end of file +} diff --git a/tests/Streams/StringStreamTest.php b/tests/Streams/StringStreamTest.php index ebabf89..4fb4f1c 100644 --- a/tests/Streams/StringStreamTest.php +++ b/tests/Streams/StringStreamTest.php @@ -10,66 +10,70 @@ /** * Limit code coverage to the class RouterOS\APIStream - * @coversDefaultClass RouterOS\Streams\StringStream + * + * @coversDefaultClass \RouterOS\Streams\StringStream */ class StringStreamTest extends TestCase { /** * @covers ::__construct * @dataProvider constructProvider + * + * @param string $string */ public function test__construct(string $string) { $this->assertInstanceOf(StringStream::class, new StringStream($string)); } - public function constructProvider() + public function constructProvider(): array { return [ - [ chr(0) ], - [ '' ], - [ '1' ], - [ 'lkjl'.chr(0).'kjkljllkjkljljklkjkljlkjljlkjkljkljlkjjll'], + [chr(0)], + [''], + ['1'], + ['lkjl' . chr(0) . 'kjkljllkjkljljklkjkljlkjljlkjkljkljlkjjll'], ]; - } + } /** - * test that write function returns the effective writen bytes + * Test that write function returns the effective written bytes + * * @covers ::write * @dataProvider writeProvider - * @param string $toWrite the string to write - * @param int|null $length the count if bytes to write - * @param int $expected the number of bytes that must be writen + * + * @param string $string the string to write + * @param int|null $length the count if bytes to write + * @param int $expected the number of bytes that must be writen */ public function test__write(string $string, $length, int $expected) { $stream = new StringStream('Does not matters'); - if (is_null($length)) { + if (null === $length) { $this->assertEquals($expected, $stream->write($string)); - } - else { + } else { $this->assertEquals($expected, $stream->write($string, $length)); } } - public function writeProvider() + public function writeProvider(): array { return [ - [ '', 0, 0 ], - [ '', 10, 0 ], - [ '', null, 0 ], - [ 'Yabala', 0, 0], - [ 'Yabala', 1, 1], - [ 'Yabala', 6, 6], - [ 'Yabala', 100, 6], - [ 'Yabala', null, 6], - [ chr(0), 0, 0], - [ chr(0), 1, 1], - [ chr(0), 100, 1], - [ chr(0), null, 1], + ['', 0, 0], + ['', 10, 0], + ['', null, 0], + ['Yabala', 0, 0], + ['Yabala', 1, 1], + ['Yabala', 6, 6], + ['Yabala', 100, 6], + ['Yabala', null, 6], + [chr(0), 0, 0], + [chr(0), 1, 1], + [chr(0), 100, 1], + [chr(0), null, 1], ]; } @@ -80,11 +84,13 @@ public function writeProvider() public function test__writeWithNegativeLength() { $stream = new StringStream('Does not matters'); - $stream->write("PLOP", -1); + $stream->write('PLOP', -1); } /** * Test read function + * + * @throws \RouterOS\Exceptions\StreamException */ public function test__read() { @@ -99,7 +105,9 @@ public function test__read() } /** - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException + * + * @throws \RouterOS\Exceptions\StreamException */ public function test__readBadLength() { @@ -111,12 +119,20 @@ public function test__readBadLength() * @covers ::read * @dataProvider readWhileEmptyProvider * @expectedException \RouterOS\Exceptions\StreamException + * + * @param StringStream $stream + * @param int $length + * @throws \RouterOS\Exceptions\StreamException */ public function test__readWhileEmpty(StringStream $stream, int $length) { - $stream->read($length); + $stream->read($length); } + /** + * @return \Generator + * @throws StreamException + */ public function readWhileEmptyProvider() { $stream = new StringStream('123456789'); @@ -133,7 +149,7 @@ public function readWhileEmptyProvider() } /** - * @expectedException \RouterOS\Exceptions\StreamException + * @expectedException \RouterOS\Exceptions\StreamException */ public function testReadClosed() { @@ -141,4 +157,4 @@ public function testReadClosed() $stream->close(); $stream->read(1); } -} \ No newline at end of file +} From ccee7ff2b7412a1bfd551778cbc763cab940a6c9 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 14:52:53 +0300 Subject: [PATCH 03/10] host, port, user, pass settings moved to phpunit.xml config --- phpunit.xml | 7 ++++ tests/APIConnectorTest.php | 2 +- tests/ClientTest.php | 50 ++++++++++++++-------------- tests/Streams/ResourceStreamTest.php | 2 +- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 1c4a840..644e542 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,4 +16,11 @@ ./tests/ + + + + + + + diff --git a/tests/APIConnectorTest.php b/tests/APIConnectorTest.php index 72c0c43..b4784fe 100644 --- a/tests/APIConnectorTest.php +++ b/tests/APIConnectorTest.php @@ -38,7 +38,7 @@ public function constructProvider(): array { return [ [new ResourceStream(fopen(__FILE__, 'rb')),], // Myself, sure I exists - [new ResourceStream(fsockopen('tcp://127.0.0.1', 18728)),], // Socket + [new ResourceStream(fsockopen('tcp://' . getenv('ROS_HOST'), getenv('ROS_PORT_MODERN'))),], // Socket [new ResourceStream(STDIN), false], // Try it, but do not close STDIN please !!! [new StringStream('Hello World !!!')], // Try it, but do not close STDIN please !!! [new StringStream('')], // Try it, but do not close STDIN please !!! diff --git a/tests/ClientTest.php b/tests/ClientTest.php index a899a5d..3203b23 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -16,7 +16,7 @@ public function test__construct() { try { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); $this->assertInternalType('object', $obj); $socket = $obj->getSocket(); @@ -30,9 +30,9 @@ public function test__construct2() { try { $config = new Config([ - 'user' => 'admin', - 'pass' => 'admin', - 'host' => '127.0.0.1' + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST') ]); $obj = new Client($config); $this->assertInternalType('object', $obj); @@ -47,9 +47,9 @@ public function test__construct3() { try { $obj = new Client([ - 'user' => 'admin', - 'pass' => 'admin', - 'host' => '127.0.0.1' + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST') ]); $this->assertInternalType('object', $obj); $socket = $obj->getSocket(); @@ -64,8 +64,8 @@ public function test__constructEx() $this->expectException(ConfigException::class); $obj = new Client([ - 'user' => 'admin', - 'pass' => 'admin', + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), ]); } @@ -73,8 +73,8 @@ public function test__constructLegacy() { try { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin') - ->set('host', '127.0.0.1')->set('port', 18728)->set('legacy', true); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS')) + ->set('host', getenv('ROS_HOST'))->set('port', (int) getenv('ROS_PORT_MODERN'))->set('legacy', true); $obj = new Client($config); $this->assertInternalType('object', $obj); } catch (\Exception $e) { @@ -84,15 +84,15 @@ public function test__constructLegacy() /** * Test non legacy connection on legacy router (pre 6.43) - * + * * login() method recognise legacy router response and swap to legacy mode - */ + */ public function test__constructLegacy2() { try { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin') - ->set('host', '127.0.0.1')->set('port', 18728)->set('legacy', false); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS')) + ->set('host', getenv('ROS_HOST'))->set('port', (int) getenv('ROS_PORT_MODERN'))->set('legacy', false); $obj = new Client($config); $this->assertInternalType('object', $obj); } catch (\Exception $e) { @@ -106,7 +106,7 @@ public function test__constructWrongPass() $this->expectException(ClientException::class); $config = (new Config())->set('attempts', 2); - $config->set('user', 'admin')->set('pass', 'admin2')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', 'admin2')->set('host', getenv('ROS_HOST')); $obj = new Client($config); } @@ -118,14 +118,14 @@ public function test__constructWrongNet() $this->expectException(ClientException::class); $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1')->set('port', 11111); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST'))->set('port', 11111); $obj = new Client($config); } public function testWriteRead() { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); $query = new Query('/ip/address/print'); @@ -157,7 +157,7 @@ public function testWriteRead() public function testWriteReadString() { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); $readTrap = $obj->wr('/interface', false); @@ -168,7 +168,7 @@ public function testWriteReadString() public function testWriteReadArray() { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); $readTrap = $obj->wr(['/interface'], false); @@ -179,7 +179,7 @@ public function testWriteReadArray() public function testFatal() { $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); $readTrap = $obj->wr('/quit'); @@ -192,7 +192,7 @@ public function testWriteEx() $this->expectException(QueryException::class); $config = new Config(); - $config->set('user', 'admin')->set('pass', 'admin')->set('host', '127.0.0.1'); + $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); $error = $obj->write($obj)->read(false); } @@ -200,9 +200,9 @@ public function testWriteEx() public function testGetConfig() { $obj = new Client([ - 'user' => 'admin', - 'pass' => 'admin', - 'host' => '127.0.0.1' + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST') ]); $config = $obj->getConfig(); diff --git a/tests/Streams/ResourceStreamTest.php b/tests/Streams/ResourceStreamTest.php index d2c466a..3fdc70a 100644 --- a/tests/Streams/ResourceStreamTest.php +++ b/tests/Streams/ResourceStreamTest.php @@ -77,7 +77,7 @@ public function constructProvider(): array { return [ [fopen(__FILE__, 'rb'),], // Myself, sure I exists - [fsockopen('tcp://127.0.0.1', 18728),], // Socket + [fsockopen('tcp://127.0.0.1', getenv('ROS_PORT_MODERN')),], // Socket [STDIN, false], // Try it, but do not close STDIN please !!! // What else ? ]; From cfb4343df17eaf947d6d5da61416de8f0934e1d5 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 14:54:23 +0300 Subject: [PATCH 04/10] host env of socked fixed --- tests/Streams/ResourceStreamTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Streams/ResourceStreamTest.php b/tests/Streams/ResourceStreamTest.php index 3fdc70a..fedb316 100644 --- a/tests/Streams/ResourceStreamTest.php +++ b/tests/Streams/ResourceStreamTest.php @@ -77,7 +77,7 @@ public function constructProvider(): array { return [ [fopen(__FILE__, 'rb'),], // Myself, sure I exists - [fsockopen('tcp://127.0.0.1', getenv('ROS_PORT_MODERN')),], // Socket + [fsockopen('tcp://' . getenv('ROS_HOST'), getenv('ROS_PORT_MODERN')),], // Socket [STDIN, false], // Try it, but do not close STDIN please !!! // What else ? ]; From a18c165b5a61bf3234ce052db2e71e0ecb78dcae Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 15:09:10 +0300 Subject: [PATCH 05/10] encoding of string lenght is updated by recommendation from official docs --- src/APILengthCoDec.php | 20 ++++---------------- tests/APILengthCoDecTest.php | 18 ------------------ 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/APILengthCoDec.php b/src/APILengthCoDec.php index b37c15a..fcbc93e 100644 --- a/src/APILengthCoDec.php +++ b/src/APILengthCoDec.php @@ -48,7 +48,7 @@ public static function encodeLength(int $length): string // - length > 0x7FFFFFFFFF : not supported if ($length < 0) { - throw new \DomainException("Length of word can not be negative ($length)"); + throw new \DomainException("Length of word could not to be negative ($length)"); } if ($length <= 0x7F) { @@ -65,23 +65,11 @@ public static function encodeLength(int $length): string if ($length <= 0x0FFFFFFF) { return BinaryStringHelper::IntegerToNBOBinaryString(0xE0000000 + $length); - } // cannot compare with 0x7FFFFFFFFF on 32 bits systems - - if (PHP_INT_SIZE < 8) { - // Cannot be done on 32 bits systems - // PHP5 windows versions of php, even on 64 bits systems was impacted - // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit - - // @codeCoverageIgnoreStart - throw new \OverflowException("Your system is using 32 bits integers, cannot encode length of $length bytes on this system"); - // @codeCoverageIgnoreEnd - } - - if ($length <= 0x7FFFFFFFFF) { - return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000 + $length); } - throw new \DomainException("Length of word too huge ($length)"); + // https://wiki.mikrotik.com/wiki/Manual:API#API_words + // If len >= 0x10000000 then 0xF0 and len as four bytes + return BinaryStringHelper::IntegerToNBOBinaryString(0xF000000000 + $length); } // Decode length of data when reading : diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php index 08a0e77..a1252df 100644 --- a/tests/APILengthCoDecTest.php +++ b/tests/APILengthCoDecTest.php @@ -33,24 +33,6 @@ public function encodeLengthNegativeProvider() ]; } - /** - * @dataProvider encodeLengthTooLargeProvider - * @expectedException \DomainException - * @covers ::encodeLength - */ - public function test__encodeLengthTooLarge($length) - { - APILengthCoDec::encodeLength($length); - } - - public function encodeLengthTooLargeProvider() - { - return [ - [0x7FFFFFFFFF+1], - [PHP_INT_MAX], - ]; - } - /** * @dataProvider encodedLengthProvider * @covers ::encodeLength From ecc2bd87c29ace5f6d000fad798125c558b42a86 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 15:43:44 +0300 Subject: [PATCH 06/10] a lot of tunes for correct work on 32 bit operation systems --- src/APILengthCoDec.php | 11 +++++++++-- src/Helpers/BinaryStringHelper.php | 4 ++-- tests/APILengthCoDecTest.php | 25 ++++++++++++------------ tests/Helpers/BinaryStringHelperTest.php | 24 ++++++++++++----------- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/APILengthCoDec.php b/src/APILengthCoDec.php index fcbc93e..2f275bd 100644 --- a/src/APILengthCoDec.php +++ b/src/APILengthCoDec.php @@ -15,7 +15,13 @@ */ class APILengthCoDec { - public static function encodeLength(int $length): string + /** + * Encode string to length of string + * + * @param int|float $length + * @return string + */ + public static function encodeLength($length): string { // Encode the length : // - if length <= 0x7F (binary : 01111111 => 7 bits set to 1) @@ -161,8 +167,9 @@ public static function decodeLength(StreamInterface $stream): int if (PHP_INT_SIZE < 8) { // Cannot be done on 32 bits systems // PHP5 windows versions of php, even on 64 bits systems was impacted - // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit + // see : https://stackoverflow.com/questions/27865340/php-int-size-returns-4-but-my-operating-system-is-64-bit // How can we test it ? + // @codeCoverageIgnoreStart throw new \OverflowException("Your system is using 32 bits integers, cannot decode this value ($firstByte) on this system"); // @codeCoverageIgnoreEnd diff --git a/src/Helpers/BinaryStringHelper.php b/src/Helpers/BinaryStringHelper.php index a5e7e70..9bb2de1 100644 --- a/src/Helpers/BinaryStringHelper.php +++ b/src/Helpers/BinaryStringHelper.php @@ -23,10 +23,10 @@ class BinaryStringHelper * Compatible with 8, 16, 32, 64 etc.. bits systems * * @see https://en.wikipedia.org/wiki/Endianness - * @param int $value the integer value to be converted + * @param int|float $value the integer value to be converted * @return string the binary string */ - public static function IntegerToNBOBinaryString(int $value): string + public static function IntegerToNBOBinaryString($value): string { // Initialize an empty string $buffer = ''; diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php index a1252df..5339591 100644 --- a/tests/APILengthCoDecTest.php +++ b/tests/APILengthCoDecTest.php @@ -11,6 +11,7 @@ /** * Limit code coverage to the class + * * @coversDefaultClass \RouterOS\APILengthCoDec */ class APILengthCoDecTest extends TestCase @@ -25,11 +26,11 @@ public function test__encodeLengthNegative($length) APILengthCoDec::encodeLength($length); } - public function encodeLengthNegativeProvider() + public function encodeLengthNegativeProvider(): array { return [ - [-1], - [PHP_INT_MIN], + [-1], + [PHP_INT_MIN], ]; } @@ -39,10 +40,10 @@ public function encodeLengthNegativeProvider() */ public function test__encodeLength($expected, $length) { - $this->assertEquals(BinaryStringHelper::IntegerToNBOBinaryString($expected), APILengthCoDec::encodeLength($length)); + $this->assertEquals(BinaryStringHelper::IntegerToNBOBinaryString((int) $expected), APILengthCoDec::encodeLength($length)); } - public function encodedLengthProvider() + public function encodedLengthProvider(): array { // [encoded length value, length value] return [ @@ -54,14 +55,14 @@ public function encodedLengthProvider() [0x9C42, 0x1C42], // Arbitrary median value for 2 bytes encoded length [0xBFFF, 0x3FFF], // High limit value for 2 bytes encoded length - [0xC04000, 0x4000], // Low limit value for 3 bytesv + [0xC04000, 0x4000], // Low limit value for 3 bytes [0xCAD73B, 0xAD73B], // Arbitrary median value for 3 bytes encoded length [0xDFFFFF, 0x1FFFFF], // High limit value for 3 bytes encoded length [0xE0200000, 0x200000], // Low limit value for 4 bytes encoded length [0xE5AD736B, 0x5AD736B], // Arbitrary median value for 4 bytes encoded length [0xEFFFFFFF, 0xFFFFFFF], // High limit value for 4 bytes encoded length - + [0xF010000000, 0x10000000], // Low limit value for 5 bytes encoded length [0xF10D4EF9C3, 0x10D4EF9C3], // Arbitrary median value for 5 bytes encoded length [0xF7FFFFFFFF, 0x7FFFFFFFF], // High limit value for 5 bytes encoded length @@ -83,19 +84,19 @@ public function test__decodeLength($encodedLength, $expected) /** * @dataProvider decodeLengthControlWordProvider * @covers ::decodeLength - * @expectedException UnexpectedValueException + * @expectedException \UnexpectedValueException */ public function test_decodeLengthControlWord(string $encodedLength) { APILengthCoDec::decodeLength(new StringStream($encodedLength)); } - public function decodeLengthControlWordProvider() - { - // Control bytes : 5 most signficants its sets to 1 + public function decodeLengthControlWordProvider(): array + { + // Control bytes: 5 most significance its sets to 1 return [ [chr(0xF8)], // minimum - [chr(0xFC)], // arbitraty value + [chr(0xFC)], // arbitrary value [chr(0xFF)], // maximum ]; } diff --git a/tests/Helpers/BinaryStringHelperTest.php b/tests/Helpers/BinaryStringHelperTest.php index 2b30b83..b45b921 100644 --- a/tests/Helpers/BinaryStringHelperTest.php +++ b/tests/Helpers/BinaryStringHelperTest.php @@ -1,4 +1,5 @@ assertEquals($expected, BinaryStringHelper::IntegerToNBOBinaryString($value)); } - public function IntegerToNBOBinaryStringProvider() + public function IntegerToNBOBinaryStringProvider(): array { return [ - [0, chr(0)], // lower boundary value - [0xFFFFFFFF, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], // 32 bits maximal value + [0, chr(0)], // lower boundary value + [0xFFFFFFFF, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 32 bits maximal value // strange behaviour : // TypeError: Argument 1 passed to RouterOS\Tests\Helpers\BinaryStringHelperTest::test__IntegerToNBOBinaryString() must be of the type integer, float given @@ -33,11 +35,11 @@ public function IntegerToNBOBinaryStringProvider() // [0xFFFFFFFFFFFFFFFF, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], // -1 is encoded with 0xFFFFFFF..... - // 64 bits maximal value (on a 64 bits system) - [-1, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], // 64 bits upper boundary value - // Let's try random value - [0x390DDD99, chr(0x39).chr(0x0D).chr(0xDD).chr(0x99)], + // 64 bits maximal value (on a 64 bits system only) + [-1, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 64 bits upper boundary value + // Let's try random value + [0x390DDD99, chr(0x39) . chr(0x0D) . chr(0xDD) . chr(0x99)], ]; } -} \ No newline at end of file +} From 34820dc5641bf98dec3339fd9b92c06fe631f7f7 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 16:15:59 +0300 Subject: [PATCH 07/10] skips of tests on x32 CPUs added --- tests/APILengthCoDecTest.php | 35 +++++++++++++++++++++++- tests/Helpers/BinaryStringHelperTest.php | 22 +++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php index 5339591..3ccfbef 100644 --- a/tests/APILengthCoDecTest.php +++ b/tests/APILengthCoDecTest.php @@ -62,7 +62,26 @@ public function encodedLengthProvider(): array [0xE0200000, 0x200000], // Low limit value for 4 bytes encoded length [0xE5AD736B, 0x5AD736B], // Arbitrary median value for 4 bytes encoded length [0xEFFFFFFF, 0xFFFFFFF], // High limit value for 4 bytes encoded length + ]; + } + + /** + * @dataProvider encodedLengthProvider64 + * @covers ::encodeLength + */ + public function test__encodeLength64($expected, $length) + { + if (PHP_INT_SIZE < 8) { + $this->markTestSkipped('Available only on x64 CPUs'); + } + + $this->assertEquals(BinaryStringHelper::IntegerToNBOBinaryString((int) $expected), APILengthCoDec::encodeLength($length)); + } + public function encodedLengthProvider64(): array + { + // [encoded length value, length value] + return [ [0xF010000000, 0x10000000], // Low limit value for 5 bytes encoded length [0xF10D4EF9C3, 0x10D4EF9C3], // Arbitrary median value for 5 bytes encoded length [0xF7FFFFFFFF, 0x7FFFFFFFF], // High limit value for 5 bytes encoded length @@ -73,7 +92,6 @@ public function encodedLengthProvider(): array * @dataProvider encodedLengthProvider * @covers ::decodeLength */ - public function test__decodeLength($encodedLength, $expected) { // We have to provide $encodedLength as a "bytes" stream @@ -81,6 +99,21 @@ public function test__decodeLength($encodedLength, $expected) $this->assertEquals($expected, APILengthCoDec::decodeLength($stream)); } + /** + * @dataProvider encodedLengthProvider64 + * @covers ::decodeLength + */ + public function test__decodeLength64($encodedLength, $expected) + { + if (PHP_INT_SIZE < 8) { + $this->markTestSkipped('Available only on x64 CPUs'); + } + + // We have to provide $encodedLength as a "bytes" stream + $stream = new StringStream(BinaryStringHelper::IntegerToNBOBinaryString($encodedLength)); + $this->assertEquals($expected, APILengthCoDec::decodeLength($stream)); + } + /** * @dataProvider decodeLengthControlWordProvider * @covers ::decodeLength diff --git a/tests/Helpers/BinaryStringHelperTest.php b/tests/Helpers/BinaryStringHelperTest.php index b45b921..ad57101 100644 --- a/tests/Helpers/BinaryStringHelperTest.php +++ b/tests/Helpers/BinaryStringHelperTest.php @@ -42,4 +42,26 @@ public function IntegerToNBOBinaryStringProvider(): array [0x390DDD99, chr(0x39) . chr(0x0D) . chr(0xDD) . chr(0x99)], ]; } + + /** + * @dataProvider IntegerToNBOBinaryStringProvider64 + * @covers ::IntegerToNBOBinaryString + */ + public function test__IntegerToNBOBinaryString64($value, $expected) + { + if (PHP_INT_SIZE < 8) { + $this->markTestSkipped('Available only on x64 CPUs'); + } + + $this->assertEquals($expected, BinaryStringHelper::IntegerToNBOBinaryString($value)); + } + + public function IntegerToNBOBinaryStringProvider64(): array + { + return [ + // -1 is encoded with 0xFFFFFFF..... + // 64 bits maximal value (on a 64 bits system only) + [-1, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 64 bits upper boundary value + ]; + } } From 407c8a9fd3216f24dbec38b22950c5310ba2ad34 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 16:17:01 +0300 Subject: [PATCH 08/10] typo if tests fixed --- tests/Helpers/BinaryStringHelperTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Helpers/BinaryStringHelperTest.php b/tests/Helpers/BinaryStringHelperTest.php index ad57101..403dadd 100644 --- a/tests/Helpers/BinaryStringHelperTest.php +++ b/tests/Helpers/BinaryStringHelperTest.php @@ -34,10 +34,6 @@ public function IntegerToNBOBinaryStringProvider(): array // // [0xFFFFFFFFFFFFFFFF, chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF)], - // -1 is encoded with 0xFFFFFFF..... - // 64 bits maximal value (on a 64 bits system only) - [-1, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 64 bits upper boundary value - // Let's try random value [0x390DDD99, chr(0x39) . chr(0x0D) . chr(0xDD) . chr(0x99)], ]; From c9bdcae83cdd5388eee943bafea55327566d935b Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 16:57:05 +0300 Subject: [PATCH 09/10] simplification of data providers --- tests/APILengthCoDecTest.php | 36 ++++-------------------- tests/Helpers/BinaryStringHelperTest.php | 20 ++++--------- 2 files changed, 10 insertions(+), 46 deletions(-) diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php index 3ccfbef..d26870c 100644 --- a/tests/APILengthCoDecTest.php +++ b/tests/APILengthCoDecTest.php @@ -46,7 +46,7 @@ public function test__encodeLength($expected, $length) public function encodedLengthProvider(): array { // [encoded length value, length value] - return [ + $default = [ [0, 0], // Low limit value for 1 byte encoded length [0x39, 0x39], // Arbitrary median value for 1 byte encoded length [0x7f, 0x7F], // High limit value for 1 byte encoded length @@ -63,29 +63,18 @@ public function encodedLengthProvider(): array [0xE5AD736B, 0x5AD736B], // Arbitrary median value for 4 bytes encoded length [0xEFFFFFFF, 0xFFFFFFF], // High limit value for 4 bytes encoded length ]; - } - /** - * @dataProvider encodedLengthProvider64 - * @covers ::encodeLength - */ - public function test__encodeLength64($expected, $length) - { if (PHP_INT_SIZE < 8) { - $this->markTestSkipped('Available only on x64 CPUs'); + return $default; } - $this->assertEquals(BinaryStringHelper::IntegerToNBOBinaryString((int) $expected), APILengthCoDec::encodeLength($length)); - } - - public function encodedLengthProvider64(): array - { - // [encoded length value, length value] - return [ + $append = [ [0xF010000000, 0x10000000], // Low limit value for 5 bytes encoded length [0xF10D4EF9C3, 0x10D4EF9C3], // Arbitrary median value for 5 bytes encoded length [0xF7FFFFFFFF, 0x7FFFFFFFF], // High limit value for 5 bytes encoded length ]; + + return array_merge($default, $append); } /** @@ -99,21 +88,6 @@ public function test__decodeLength($encodedLength, $expected) $this->assertEquals($expected, APILengthCoDec::decodeLength($stream)); } - /** - * @dataProvider encodedLengthProvider64 - * @covers ::decodeLength - */ - public function test__decodeLength64($encodedLength, $expected) - { - if (PHP_INT_SIZE < 8) { - $this->markTestSkipped('Available only on x64 CPUs'); - } - - // We have to provide $encodedLength as a "bytes" stream - $stream = new StringStream(BinaryStringHelper::IntegerToNBOBinaryString($encodedLength)); - $this->assertEquals($expected, APILengthCoDec::decodeLength($stream)); - } - /** * @dataProvider decodeLengthControlWordProvider * @covers ::decodeLength diff --git a/tests/Helpers/BinaryStringHelperTest.php b/tests/Helpers/BinaryStringHelperTest.php index 403dadd..df12129 100644 --- a/tests/Helpers/BinaryStringHelperTest.php +++ b/tests/Helpers/BinaryStringHelperTest.php @@ -24,7 +24,7 @@ public function test__IntegerToNBOBinaryString($value, $expected) public function IntegerToNBOBinaryStringProvider(): array { - return [ + $default = [ [0, chr(0)], // lower boundary value [0xFFFFFFFF, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 32 bits maximal value @@ -37,27 +37,17 @@ public function IntegerToNBOBinaryStringProvider(): array // Let's try random value [0x390DDD99, chr(0x39) . chr(0x0D) . chr(0xDD) . chr(0x99)], ]; - } - /** - * @dataProvider IntegerToNBOBinaryStringProvider64 - * @covers ::IntegerToNBOBinaryString - */ - public function test__IntegerToNBOBinaryString64($value, $expected) - { if (PHP_INT_SIZE < 8) { - $this->markTestSkipped('Available only on x64 CPUs'); + return $default; } - $this->assertEquals($expected, BinaryStringHelper::IntegerToNBOBinaryString($value)); - } - - public function IntegerToNBOBinaryStringProvider64(): array - { - return [ + $append = [ // -1 is encoded with 0xFFFFFFF..... // 64 bits maximal value (on a 64 bits system only) [-1, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 64 bits upper boundary value ]; + + return array_merge($default, $append); } } From 0a60e8cf5f400be6270276252a76928dcb5777a7 Mon Sep 17 00:00:00 2001 From: Paul Rock Date: Sun, 24 Mar 2019 17:03:54 +0300 Subject: [PATCH 10/10] yet another simplification of data providers in tests --- tests/APILengthCoDecTest.php | 16 ++++++---------- tests/Helpers/BinaryStringHelperTest.php | 14 +++++--------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/tests/APILengthCoDecTest.php b/tests/APILengthCoDecTest.php index d26870c..5b59514 100644 --- a/tests/APILengthCoDecTest.php +++ b/tests/APILengthCoDecTest.php @@ -46,7 +46,7 @@ public function test__encodeLength($expected, $length) public function encodedLengthProvider(): array { // [encoded length value, length value] - $default = [ + $result = [ [0, 0], // Low limit value for 1 byte encoded length [0x39, 0x39], // Arbitrary median value for 1 byte encoded length [0x7f, 0x7F], // High limit value for 1 byte encoded length @@ -64,17 +64,13 @@ public function encodedLengthProvider(): array [0xEFFFFFFF, 0xFFFFFFF], // High limit value for 4 bytes encoded length ]; - if (PHP_INT_SIZE < 8) { - return $default; + if (PHP_INT_SIZE > 4) { + $result[] = [0xF010000000, 0x10000000]; // Low limit value for 5 bytes encoded length + $result[] = [0xF10D4EF9C3, 0x10D4EF9C3]; // Arbitrary median value for 5 bytes encoded length + $result[] = [0xF7FFFFFFFF, 0x7FFFFFFFF]; // High limit value for 5 bytes encoded length } - $append = [ - [0xF010000000, 0x10000000], // Low limit value for 5 bytes encoded length - [0xF10D4EF9C3, 0x10D4EF9C3], // Arbitrary median value for 5 bytes encoded length - [0xF7FFFFFFFF, 0x7FFFFFFFF], // High limit value for 5 bytes encoded length - ]; - - return array_merge($default, $append); + return $result; } /** diff --git a/tests/Helpers/BinaryStringHelperTest.php b/tests/Helpers/BinaryStringHelperTest.php index df12129..014f17a 100644 --- a/tests/Helpers/BinaryStringHelperTest.php +++ b/tests/Helpers/BinaryStringHelperTest.php @@ -24,7 +24,7 @@ public function test__IntegerToNBOBinaryString($value, $expected) public function IntegerToNBOBinaryStringProvider(): array { - $default = [ + $result = [ [0, chr(0)], // lower boundary value [0xFFFFFFFF, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 32 bits maximal value @@ -38,16 +38,12 @@ public function IntegerToNBOBinaryStringProvider(): array [0x390DDD99, chr(0x39) . chr(0x0D) . chr(0xDD) . chr(0x99)], ]; - if (PHP_INT_SIZE < 8) { - return $default; - } - - $append = [ + if (PHP_INT_SIZE > 4) { // -1 is encoded with 0xFFFFFFF..... // 64 bits maximal value (on a 64 bits system only) - [-1, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)], // 64 bits upper boundary value - ]; + $result[] = [-1, chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF) . chr(0xFF)]; // 64 bits upper boundary value + } - return array_merge($default, $append); + return $result; } }