├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src └── TrafficCophp │ └── ByteBuffer │ ├── AbstractBuffer.php │ ├── Buffer.php │ ├── LengthMap.php │ ├── ReadableBuffer.php │ └── WriteableBuffer.php └── tests └── TrafficCophp └── ByteBuffer └── BufferTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/.composer/ 2 | vendor/ 3 | /phpunit.xml 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 5.3 7 | - 5.4 8 | - 5.5 9 | 10 | before_script: 11 | - composer --prefer-source --dev install 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Library for reading and writing binary data 2 | 3 | [![Build Status](https://secure.travis-ci.org/nesQuick/ByteBuffer.png?branch=master)](http://travis-ci.org/nesQuick/ByteBuffer) [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 4 | 5 | I intentionally needed that for writing a PHP Client for [TrafficCop](https://github.com/santosh79/traffic_cop/). 6 | But the source grows so I decided to move it into an own package. 7 | You can also call this a [pack()](http://www.php.net/manual/en/function.pack.php) wrapper. 8 | 9 | ## Install 10 | 11 | Installation should be done via [composer](http://packagist.org/). 12 | 13 | ``` 14 | { 15 | "require": { 16 | "TrafficCophp/ByteBuffer": "dev-master" 17 | } 18 | } 19 | ``` 20 | 21 | ## Example 22 | 23 | A simple usage example could look like this 24 | 25 | ```php 26 | writeInt32BE($buffer->length(), 0); 37 | $buffer->writeInt8(0x1, 4); 38 | $buffer->writeInt32BE(strlen($channel), 5); 39 | $buffer->write($channel, 9); 40 | $buffer->write($message, 9 + strlen($channel)); 41 | 42 | $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 43 | $result = socket_connect($socket, '127.0.0.1', 3542); 44 | 45 | socket_write($socket, (string) $buffer, $buffer->length()); 46 | ``` 47 | 48 | ## ToDo's 49 | 50 | * Write Documentation 51 | * Improve examples 52 | * Allow Buffer as constructor 53 | * Write test for concatinating buffers 54 | 55 | ## License 56 | 57 | Licensed under the MIT license. 58 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TrafficCophp/ByteBuffer", 3 | "type": "library", 4 | "description": "Node.js inspired byte stream buffer for PHP.", 5 | "keywords": ["Buffer", "Stream", "Socket", "Library", "Bytehandling", "pack", "wrapper", "binary data"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Ole 'nesQuick' Michaelis", 10 | "email": "ole.michaelis@googlemail.com", 11 | "homepage": "http://www.codestars.eu" 12 | } 13 | ], 14 | "autoload": { 15 | "psr-4": { 16 | "TrafficCophp\\ByteBuffer\\": "src/TrafficCophp/ByteBuffer/" 17 | } 18 | }, 19 | "require": {} 20 | } 21 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "6cf3d618c76525d67fe4ca75786c4440", 3 | "packages": [], 4 | "aliases": [] 5 | } 6 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | tests/TrafficCophp/ByteBuffer 13 | 14 | 15 | 16 | 17 | 18 | src 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/TrafficCophp/ByteBuffer/AbstractBuffer.php: -------------------------------------------------------------------------------- 1 | lengthMap = new LengthMap(); 24 | if (is_string($argument)) { 25 | $this->initializeStructs(strlen($argument), $argument); 26 | } else if (is_int($argument)) { 27 | $this->initializeStructs($argument, pack(self::DEFAULT_FORMAT.$argument)); 28 | } else { 29 | throw new \InvalidArgumentException('Constructor argument must be an binary string or integer'); 30 | } 31 | } 32 | 33 | protected function initializeStructs($length, $content) { 34 | $this->buffer = new \SplFixedArray($length); 35 | for ($i = 0; $i < $length; $i++) { 36 | $this->buffer[$i] = $content[$i]; 37 | } 38 | } 39 | 40 | protected function insert($format, $value, $offset, $length) { 41 | $bytes = pack($format, $value); 42 | for ($i = 0; $i < strlen($bytes); $i++) { 43 | $this->buffer[$offset++] = $bytes[$i]; 44 | } 45 | } 46 | 47 | protected function extract($format, $offset, $length) { 48 | $encoded = ''; 49 | for ($i = 0; $i < $length; $i++) { 50 | $encoded .= $this->buffer->offsetGet($offset + $i); 51 | } 52 | if ($format == 'N'&& PHP_INT_SIZE <= 4) { 53 | list(, $h, $l) = unpack('n*', $encoded); 54 | $result = ($l + ($h * 0x010000)); 55 | } else if ($format == 'V' && PHP_INT_SIZE <= 4) { 56 | list(, $h, $l) = unpack('v*', $encoded); 57 | $result = ($h + ($l * 0x010000)); 58 | } else { 59 | list(, $result) = unpack($format, $encoded); 60 | } 61 | return $result; 62 | } 63 | 64 | protected function checkForOverSize($excpected_max, $actual) { 65 | if ($actual > $excpected_max) { 66 | throw new \InvalidArgumentException(sprintf('%d exceeded limit of %d', $actual, $excpected_max)); 67 | } 68 | } 69 | 70 | public function __toString() { 71 | $buf = ''; 72 | foreach ($this->buffer as $bytes) { 73 | $buf .= $bytes; 74 | } 75 | return $buf; 76 | } 77 | 78 | public function length() { 79 | return $this->buffer->getSize(); 80 | } 81 | 82 | public function write($string, $offset) { 83 | $length = strlen($string); 84 | $this->insert('a' . $length, $string, $offset, $length); 85 | } 86 | 87 | public function writeInt8($value, $offset) { 88 | $format = 'C'; 89 | $this->checkForOverSize(0xff, $value); 90 | $this->insert($format, $value, $offset, $this->lengthMap->getLengthFor($format)); 91 | } 92 | 93 | public function writeInt16BE($value, $offset) { 94 | $format = 'n'; 95 | $this->checkForOverSize(0xffff, $value); 96 | $this->insert($format, $value, $offset, $this->lengthMap->getLengthFor($format)); 97 | } 98 | 99 | public function writeInt16LE($value, $offset) { 100 | $format = 'v'; 101 | $this->checkForOverSize(0xffff, $value); 102 | $this->insert($format, $value, $offset, $this->lengthMap->getLengthFor($format)); 103 | } 104 | 105 | public function writeInt32BE($value, $offset) { 106 | $format = 'N'; 107 | $this->checkForOverSize(0xffffffff, $value); 108 | $this->insert($format, $value, $offset, $this->lengthMap->getLengthFor($format)); 109 | } 110 | 111 | public function writeInt32LE($value, $offset) { 112 | $format = 'V'; 113 | $this->checkForOverSize(0xffffffff, $value); 114 | $this->insert($format, $value, $offset, $this->lengthMap->getLengthFor($format)); 115 | } 116 | 117 | public function read($offset, $length) { 118 | $format = 'a' . $length; 119 | return $this->extract($format, $offset, $length); 120 | } 121 | 122 | public function readInt8($offset) { 123 | $format = 'C'; 124 | return $this->extract($format, $offset, $this->lengthMap->getLengthFor($format)); 125 | } 126 | 127 | public function readInt16BE($offset) { 128 | $format = 'n'; 129 | return $this->extract($format, $offset, $this->lengthMap->getLengthFor($format)); 130 | } 131 | 132 | public function readInt16LE($offset) { 133 | $format = 'v'; 134 | return $this->extract($format, $offset, $this->lengthMap->getLengthFor($format)); 135 | } 136 | 137 | public function readInt32BE($offset) { 138 | $format = 'N'; 139 | return $this->extract($format, $offset, $this->lengthMap->getLengthFor($format)); 140 | } 141 | 142 | public function readInt32LE($offset) { 143 | $format = 'V'; 144 | return $this->extract($format, $offset, $this->lengthMap->getLengthFor($format)); 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /src/TrafficCophp/ByteBuffer/LengthMap.php: -------------------------------------------------------------------------------- 1 | map = array( 14 | 'n' => 2, 15 | 'N' => 4, 16 | 'v' => 2, 17 | 'V' => 4, 18 | 'c' => 1, 19 | 'C' => 1 20 | ); 21 | } 22 | 23 | public function getLengthFor($format) { 24 | return $this->map[$format]; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/TrafficCophp/ByteBuffer/ReadableBuffer.php: -------------------------------------------------------------------------------- 1 | writeInt32LE(0xfeedface, 0); 13 | $this->assertSame(pack('Vx', 0xfeedface), (string) $buffer); 14 | } 15 | 16 | public function testSurroundedEmptyByte() { 17 | $buffer = new Buffer(9); 18 | $buffer->writeInt32BE(0xfeedface, 0); 19 | $buffer->writeInt32BE(0xcafebabe, 5); 20 | $this->assertSame(pack('NxN', 0xfeedface, 0xcafebabe), (string) $buffer); 21 | } 22 | 23 | public function testTooSmallBuffer() { 24 | $buffer = new Buffer(4); 25 | $buffer->writeInt32BE(0xfeedface, 0); 26 | $this->setExpectedException('RuntimeException'); 27 | $buffer->writeInt32LE(0xfeedface, 4); 28 | } 29 | 30 | public function testTwo4ByteIntegers() { 31 | $buffer = new Buffer(8); 32 | $buffer->writeInt32BE(0xfeedface, 0); 33 | $buffer->writeInt32LE(0xfeedface, 4); 34 | $this->assertSame(pack('NV', 0xfeedface, 0xfeedface), (string) $buffer); 35 | } 36 | 37 | public function testWritingString() { 38 | $buffer = new Buffer(10); 39 | $buffer->writeInt32BE(0xcafebabe, 0); 40 | $buffer->write('please', 4); 41 | $this->assertSame(pack('Na6', 0xcafebabe, 'please'), (string) $buffer); 42 | } 43 | 44 | public function testTooLongIntegers() { 45 | $buffer = new Buffer(12); 46 | $this->setExpectedException('InvalidArgumentException'); 47 | $buffer->writeInt32BE(0xfeedfacefeed, 0); 48 | } 49 | 50 | public function testLength() { 51 | $buffer = new Buffer(8); 52 | $this->assertEquals(8, $buffer->length()); 53 | } 54 | 55 | public function testWriteInt8() { 56 | $buffer = new Buffer(1); 57 | $buffer->writeInt8(0xfe, 0); 58 | $this->assertSame(pack('C', 0xfe), (string) $buffer); 59 | } 60 | 61 | public function testWriteInt16BE() { 62 | $buffer = new Buffer(2); 63 | $buffer->writeInt16BE(0xbabe, 0); 64 | $this->assertSame(pack('n', 0xbabe), (string) $buffer); 65 | } 66 | 67 | public function testWriteInt16LE() { 68 | $buffer = new Buffer(2); 69 | $buffer->writeInt16LE(0xabeb, 0); 70 | $this->assertSame(pack('v', 0xabeb), (string) $buffer); 71 | } 72 | 73 | public function testWriteInt32BE() { 74 | $buffer = new Buffer(4); 75 | $buffer->writeInt32BE(0xfeedface, 0); 76 | $this->assertSame(pack('N', 0xfeedface), (string) $buffer); 77 | } 78 | 79 | public function testWriteInt32LE() { 80 | $buffer = new Buffer(4); 81 | $buffer->writeInt32LE(0xfeedface, 0); 82 | $this->assertSame(pack('V', 0xfeedface), (string) $buffer); 83 | } 84 | 85 | public function testReaderBufferInitializeLenght() { 86 | $buffer = new Buffer(pack('V', 0xfeedface)); 87 | $this->assertEquals(4, $buffer->length()); 88 | } 89 | 90 | public function testReadInt8() { 91 | $buffer = new Buffer(pack('C', 0xfe)); 92 | $this->assertSame(0xfe, $buffer->readInt8(0)); 93 | } 94 | 95 | public function testReadInt16BE() { 96 | $buffer = new Buffer(pack('n', 0xbabe)); 97 | $this->assertSame(0xbabe, $buffer->readInt16BE(0)); 98 | } 99 | 100 | public function testReadInt16LE() { 101 | $buffer = new Buffer(pack('v', 0xabeb)); 102 | $this->assertSame(0xabeb, $buffer->readInt16LE(0)); 103 | } 104 | 105 | public function testReadInt32BE() { 106 | $buffer = new Buffer(pack('N', 0xfeedface)); 107 | $this->assertSame(0xfeedface, $buffer->readInt32BE(0)); 108 | } 109 | 110 | public function testReadInt32LE() { 111 | $buffer = new Buffer(pack('V', 0xfeedface)); 112 | $this->assertSame(0xfeedface, $buffer->readInt32LE(0)); 113 | } 114 | 115 | public function testRead() { 116 | $buffer = new Buffer(pack('a7', 'message')); 117 | $this->assertSame('message', $buffer->read(0, 7)); 118 | } 119 | 120 | public function testComplexRead() { 121 | $buffer = new Buffer(pack('Na7', 0xfeedface, 'message')); 122 | $this->assertSame(0xfeedface, $buffer->readInt32BE(0)); 123 | $this->assertSame('message', $buffer->read(4, 7)); 124 | } 125 | 126 | public function testWritingAndReadingOnTheSameBuffer() { 127 | $buffer = new Buffer(10); 128 | $int32be = 0xfeedface; 129 | $string = 'hello!'; 130 | $buffer->writeInt32BE($int32be, 0); 131 | $buffer->write($string, 4); 132 | $this->assertSame($string, $buffer->read(4, 6)); 133 | $this->assertSame($int32be, $buffer->readInt32BE(0)); 134 | } 135 | 136 | public function testInvalidConstructorWithArray() { 137 | $this->setExpectedException('\InvalidArgumentException'); 138 | $buffer = new Buffer(array('asdf')); 139 | } 140 | 141 | public function testInvalidConstructorWithFloat() { 142 | $this->setExpectedException('\InvalidArgumentException'); 143 | $buffer = new Buffer(324.23); 144 | } 145 | 146 | } --------------------------------------------------------------------------------