Skip to content

Commit

Permalink
Improved test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
denpamusic committed Feb 21, 2019
1 parent 246881c commit 07d1ef5
Show file tree
Hide file tree
Showing 26 changed files with 619 additions and 223 deletions.
156 changes: 137 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,152 @@
[![Code Coverage](https://codeclimate.com/github/denpamusic/php-levin/badges/coverage.svg)](https://codeclimate.com/github/denpamusic/php-levin/coverage)


## Example
## Examples
### Using helpers
```php
require 'vendor/autoload.php';

use Denpa\Levin;

$vars = [
'network_id' => 'somenetwork',
];

Levin\connection($ip, $port, $vars)->listen(function ($bucket, $connection) {
if ($bucket->isRequest('supportflags', 'timedsync', 'ping')) {
// respond to supportflags, timedsync and ping requests
// to keep the connection open
$connection->write($bucket->response());
Levin\connection($ip, $port, $vars)->connect(
function ($bucket, $connection) {
if ($bucket->isRequest('supportflags', 'timedsync', 'ping')) {
// respond to supportflags, timedsync and ping requests
// to keep the connection open
$connection->write($bucket->response());
}

if ($bucket->isResponse('handshake')) {
// send ping request to the server after
// receiving handshake response
$connection->write(Levin\request('ping'));
}

if ($bucket->isResponse('ping')) {
// dump server response to the console
var_dump($bucket->getPayload());

// returning false closes connection
return false;
}
}
);
```

### Using objects
```php
require 'vendor/autoload.php';

use Denpa\Levin\Bucket;
use Denpa\Levin\Connection;
use Denpa\Levin\Requests\Handshake;

$handshake = new Handshake(['network_id' => 'somenetwork']);
$request = (new Bucket())->request($handshake);

$connection = new Connection($ip, $port);
$connection->write($request);

while ($bucket = $connection->read()) {
// ...
}
```

### Fetching peers
```php
require 'vendor/autoload.php';

use Denpa\Levin;

function peerlist(array $entries) : array
{
$peers = [];

foreach ($entries as $entry) {
$addr = $entry['adr']['addr'] ?? null;

if (is_null($addr)) continue;

// convert ip to big-endian int
$ip = Levin\uint32($addr['m_ip']->toBinary());

$peer = [];
$peer['ip'] = inet_ntop($ip->toBinary());
$peer['port'] = $addr['m_port']->toInt();
$peer['last_seen'] = isset($entry['last_seen']) ?
date('Y-m-d H:i:s', $entry['last_seen']->toInt()) : null;

$peers[] = $peer;
}

if ($bucket->isResponse('ping')) {
// dump server response to the console
var_dump($bucket->payload());

// returning false closes connection
return false;
return $peers;
}

$vars = [
'network_id' => 'somenetwork',
];

$section = [];

Levin\connection($ip, $port, $vars)->connect(
function ($bucket, $connection) use ($section) {
if ($bucket->isResponse('handshake')) {
$section = $bucket->getPayload();

return false;
}
}

if ($bucket->isResponse('handshake')) {
// send ping request to the server after
// receiving handshake response
$connection->write(Levin\request('ping'));
);

$peers = peerlist($section['local_peerlist_new'] ?? []);

var_dump($peers);
/**
* Array(
* Array(
* 'ip' => '88.99.122.111',
* 'port' => 1000,
* 'last_seen' => '2019-02-21 12:00:00'
* ),
* ...
* )
*/
```
### Monitoring blocks
```php
require 'vendor/autoload.php';

use Denpa\Levin;

$vars = [
'network_id' => 'somenetwork',
];

Levin\connection($ip, $port, $vars)->connect(
function ($bucket, $connection) {
if ($bucket->isRequest('supportflags', 'timedsync', 'ping')) {
// respond to supportflags, timedsync and ping requests
// to keep the connection open
$connection->write($bucket->response());
}

if ($bucket->isRequest('newblock')) {
$section = $bucket->getPayload();

printf("New block: %d\n", $section['current_blockchain_height']);
var_dump($section['b']);

// no need to respond to notification
}
}
});
);
```

## Command Support
## Request Support
| command | link | request | response |
|--------------|---------------------------------------------------------------------------------------|---------|----------|
| Handshake | [p2p_protocol_defs.h#L177](https://github.com/monero-project/monero/blob/master/src/p2p/p2p_protocol_defs.h#L177) |||
Expand All @@ -61,7 +175,11 @@ Levin\connection($ip, $port, $vars)->listen(function ($bucket, $connection) {

## Exceptions
* `Denpa\Levin\Exceptions\ConnectionException` - thrown on connection errors.
* `Denpa\Levin\Exceptions\EntryTooLargeException` - thrown when type or packet size is too large.
* `Denpa\Levin\Exceptions\SignatureMismatchException` - thrown on section or bucket signature mismatches.
* `Denpa\Levin\Exceptions\UnexpectedTypeException` - thrown on unexpected or invalid type.
* `Denpa\Levin\Exceptions\UnknownCommandException` - thrown on unknown command.
* `Denpa\Levin\Exceptions\UnpackException` - thrown when unable to unpack binary data.

## License
This product is distributed under the [MIT license](https://github.com/denpamusic/php-levin/blob/master/LICENSE).
Expand Down
78 changes: 41 additions & 37 deletions src/Bucket.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@

namespace Denpa\Levin;

use BadMethodCallException;
use Denpa\Levin\Exceptions\EntryTooLargeException;
use Denpa\Levin\Exceptions\SignatureMismatchException;
use Denpa\Levin\Section\Reader;
use Denpa\Levin\Section\Section;
use Denpa\Levin\Types\Boolean;
use Denpa\Levin\Types\Int32;
use Denpa\Levin\Types\Uint32;
use Denpa\Levin\Types\Uint64;
use LengthException;
use UnexpectedValueException;

/**
* @method \Denpa\Levin\Types\Uint64 getSignature()
* @method \Denpa\Levin\Types\Uint64 getCb()
* @method \Denpa\Levin\Types\Uint32 getReturnData()
* @method \Denpa\Levin\Command getCommand()
* @method \Denpa\Levin\Types\Int32 getReturnCode()
* @method \Denpa\Levin\Types\Uint32 getFlags()
* @method \Denpa\Levin\Types\Uint32 getProtocolVersion()
* @method \Denpa\Levin\Section getPayload()
*/
class Bucket implements BucketInterface
{
/**
Expand Down Expand Up @@ -52,7 +63,7 @@ class Bucket implements BucketInterface
/**
* @var \Denpa\Levin\Section|null
*/
protected $payloadSection = null;
protected $payload = null;

/**
* @return void
Expand All @@ -72,7 +83,7 @@ public function __construct(array $params = [])
$params = $params + $defaults;

foreach ($params as $key => $value) {
$mutator = 'set'.camel_case($key);
$mutator = 'set'.ucfirst(camel_case($key));
if (method_exists($this, $mutator)) {
$this->$mutator($value);
}
Expand Down Expand Up @@ -155,20 +166,12 @@ public function setCb($cb) : self
if ($this->cb->toInt() > self::LEVIN_DEFAULT_MAX_PACKET_SIZE) {
$maxsize = self::LEVIN_DEFAULT_MAX_PACKET_SIZE;

throw new LengthException("Packet is too large [> $maxsize]");
throw new EntryTooLargeException("Bucket is too large [> $maxsize]");
}

return $this;
}

/**
* @return \Denpa\Levin\Types\Uint64
*/
public function getCb() : Uint64
{
return $this->cb;
}

/**
* @param mixed $returnData
*
Expand Down Expand Up @@ -197,14 +200,6 @@ public function setCommand($command) : self
return $this;
}

/**
* @return \Denpa\Levin\CommandInterface|null
*/
public function getCommand() : ?CommandInterface
{
return $this->command;
}

/**
* @param \Denpa\Levin\CommandInterface $command
*
Expand All @@ -215,7 +210,7 @@ public function fill(CommandInterface $command) : self
$method = $this->isRequest() ? 'request' : 'response';

$this->command = $command;
$this->setPayloadSection($command->$method());
$this->setPayload($command->$method());

return $this;
}
Expand Down Expand Up @@ -263,18 +258,18 @@ public function setProtocolVersion($protocolVersion) : self
*
* @return self
*/
public function setPayloadSection(Section $section) : self
public function setPayload(Section $section) : self
{
$this->setCb($section->getByteSize());
$this->payloadSection = $section;
$this->payload = $section;

return $this;
}

/**
* @return string
*/
public function head() : string
public function getHead() : string
{
$head = [
'signature' => $this->signature,
Expand All @@ -301,25 +296,17 @@ public function head() : string
return implode('', $head);
}

/**
* @return \Denpa\Levin\Section|null
*/
public function payload() : ?Section
{
return $this->payloadSection;
}

/**
* @param \Denpa\Levin\Connection $connection
*
* @return void
*/
public function write(Connection $connection) : void
{
$connection->write($this->head());
$connection->write($this->getHead());

if (!is_null($this->payloadSection)) {
$connection->write($this->payload()->toBinary());
if (!is_null($this->payload)) {
$connection->write($this->getPayload()->toBinary());
}
}

Expand All @@ -344,9 +331,9 @@ public function read(Connection $connection) : ?self
'protocol_version' => $connection->read(uint32le()),
]);

if ($bucket->getCb()->toInt() > 0) {
if ($bucket->cb->toInt() > 0) {
$section = (new Reader($connection))->read();
$bucket->setPayloadSection($section);
$bucket->setPayload($section);
}

return $bucket;
Expand Down Expand Up @@ -387,4 +374,21 @@ public function response(?CommandInterface $command = null) : self

return $this;
}

/**
* @param string $method
* @param array $args
*
* @return mixed
*/
public function __call(string $method, array $args = [])
{
if (substr($method, 0, 3) == 'get') {
$variable = camel_case(substr($method, 3));

return $this->$variable ?? null;
}

throw new BadMethodCallException("Method [$method] does not exist");
}
}
12 changes: 0 additions & 12 deletions src/BucketInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Denpa\Levin;

use Denpa\Levin\Section\Section;

interface BucketInterface
{
/**
Expand Down Expand Up @@ -31,16 +29,6 @@ interface BucketInterface
*/
const LEVIN_DEFAULT_MAX_PACKET_SIZE = 100000000; // 100MB

/**
* @return string
*/
public function head() : string;

/**
* @return \Denpa\Levin\Section|null
*/
public function payload() : ?Section;

/**
* @param \Denpa\Levin\Connection $connection
*
Expand Down
Loading

0 comments on commit 07d1ef5

Please sign in to comment.