Add stack
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
5c5498e65b
commit
cf83bc8c5d
@ -0,0 +1,204 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Cijber\OpenSSL;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Cijber\OpenSSL;
|
||||||
|
use Cijber\OpenSSL\C\CBackedObject;
|
||||||
|
use Cijber\OpenSSL\C\CBackedObjectWithOwner;
|
||||||
|
use Countable;
|
||||||
|
use FFI\CData;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use IteratorAggregate;
|
||||||
|
use RuntimeException;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Stack
|
||||||
|
* @package Cijber\OpenSSL
|
||||||
|
*/
|
||||||
|
abstract class Stack extends CBackedObjectWithOwner implements Countable, ArrayAccess, Traversable, IteratorAggregate
|
||||||
|
{
|
||||||
|
const CLASSNAME = CBackedObjectWithOwner::class;
|
||||||
|
|
||||||
|
abstract protected function spawn(CData $cData);
|
||||||
|
|
||||||
|
public static function new()
|
||||||
|
{
|
||||||
|
$ffi = OpenSSL::getFFI();
|
||||||
|
$cObj = $ffi->sk_new_null();
|
||||||
|
return new static($ffi, $cObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function freeObject()
|
||||||
|
{
|
||||||
|
$this->ffi->sk_free($this->cObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->ffi->sk_num($this->cObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(int $offset)
|
||||||
|
{
|
||||||
|
$res = $this->ffi->sk_value($this->cObj, $offset);
|
||||||
|
|
||||||
|
if ($res === null) {
|
||||||
|
throw new RuntimeException("Failed to retrieve item from stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->spawn($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(int $offset, CBackedObject $object): void
|
||||||
|
{
|
||||||
|
/***
|
||||||
|
* Make sure ref count is updated for old object
|
||||||
|
*/
|
||||||
|
/** @var CBackedObjectWithOwner $obj */
|
||||||
|
$obj = $this->get($offset);
|
||||||
|
$res = $this->ffi->sk_set($this->cObj, $offset, $object->cObj);
|
||||||
|
|
||||||
|
if ($res === null) {
|
||||||
|
throw new RuntimeException("Failed to set item on stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
$obj->decreaseRefCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shift()
|
||||||
|
{
|
||||||
|
$cObj = $this->ffi->sk_shift($this->cObj);
|
||||||
|
return $this->handleResult($cObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleResult(?CData $cObj)
|
||||||
|
{
|
||||||
|
if ($cObj === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CBackedObjectWithOwner $obj */
|
||||||
|
$obj = $this->spawn($cObj);
|
||||||
|
$obj->decreaseRefCount();
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pop()
|
||||||
|
{
|
||||||
|
$cObj = $this->ffi->sk_pop($this->cObj);
|
||||||
|
return $this->handleResult($cObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function push(CBackedObject $object): int
|
||||||
|
{
|
||||||
|
$this->ensureCorrect($object);
|
||||||
|
$idx = $this->ffi->sk_push($this->cObj, $object->cObj);
|
||||||
|
if ($idx === 0) {
|
||||||
|
throw new RuntimeException("Failed to insert element");
|
||||||
|
}
|
||||||
|
|
||||||
|
$object->pushRefCount();
|
||||||
|
|
||||||
|
return $idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureCorrect($object)
|
||||||
|
{
|
||||||
|
$expectedClassName = static::CLASSNAME;
|
||||||
|
|
||||||
|
if (!($object instanceof $expectedClassName)) {
|
||||||
|
throw new InvalidArgumentException("Expected object of class $expectedClassName got object of class " . get_class($object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unshift(CBackedObject $object): int
|
||||||
|
{
|
||||||
|
$this->ensureCorrect($object);
|
||||||
|
|
||||||
|
$idx = $this->ffi->sk_unshift($this->cObj, $object->cObj);
|
||||||
|
if ($idx === 0) {
|
||||||
|
throw new RuntimeException("Failed to insert element");
|
||||||
|
}
|
||||||
|
|
||||||
|
$object->pushRefCount();
|
||||||
|
|
||||||
|
return $idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function freeAll()
|
||||||
|
{
|
||||||
|
$this->ffi->sk_pop_free($this->cObj, function (CData $cObj) {
|
||||||
|
/** @var CBackedObject $cObj */
|
||||||
|
$cObj = $this->spawn($cObj);
|
||||||
|
$cObj->decreaseRefCount();
|
||||||
|
$cObj->free();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->freed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
|
return $offset >= 0 && $offset < $this->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
|
return $this->get($offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
|
if ($offset === null) {
|
||||||
|
$this->push($value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->set($offset, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
|
return $this->delete($offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($offset)
|
||||||
|
{
|
||||||
|
$obj = $this->ffi->sk_delete($this->cObj, $offset);
|
||||||
|
if ($obj === null) {
|
||||||
|
throw new RuntimeException("Failed to delete element $offset from stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CBackedObjectWithOwner $phpObj */
|
||||||
|
$phpObj = $this->spawn($obj);
|
||||||
|
$phpObj->decreaseRefCount();
|
||||||
|
|
||||||
|
return $phpObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < $this->count(); $i++) {
|
||||||
|
yield $this[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
$cObj = $this->ffi->sk_dup($this->cObj);
|
||||||
|
|
||||||
|
if ($cObj === null) {
|
||||||
|
throw new RuntimeException("Failed to clone stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var CBackedObjectWithOwner $obj */
|
||||||
|
foreach ($this as $obj) {
|
||||||
|
$obj->pushRefCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static($this->ffi, $cObj);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Cijber\OpenSSL\Stack;
|
||||||
|
|
||||||
|
use Cijber\OpenSSL\Stack;
|
||||||
|
use Cijber\OpenSSL\X509;
|
||||||
|
use FFI;
|
||||||
|
use FFI\CData;
|
||||||
|
|
||||||
|
class X509Stack extends Stack
|
||||||
|
{
|
||||||
|
const CLASSNAME = X509::class;
|
||||||
|
|
||||||
|
public static function from(FFI $ffi, CData $cObj): X509Stack
|
||||||
|
{
|
||||||
|
return new X509Stack($ffi, $cObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function spawn(CData $cData): X509
|
||||||
|
{
|
||||||
|
return X509::cast($this->ffi, $cData);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Cijber\OpenSSL\Tests;
|
||||||
|
|
||||||
|
|
||||||
|
use Cijber\OpenSSL;
|
||||||
|
use Cijber\OpenSSL\Stack;
|
||||||
|
use Cijber\OpenSSL\X509;
|
||||||
|
use PHPStan\Testing\TestCase;
|
||||||
|
|
||||||
|
class StackTest extends TestCase
|
||||||
|
{
|
||||||
|
function testCreationAndDestruction()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$this->assertCount(0, $stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPush()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$x509 = X509::new();
|
||||||
|
$stack->push($x509);
|
||||||
|
$this->assertCount(1, $stack);
|
||||||
|
$x509Item = $stack->get(0);
|
||||||
|
$this->assertSame($x509, $x509Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSet()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$a = X509::new();
|
||||||
|
$b = X509::new();
|
||||||
|
|
||||||
|
$stack->push($a);
|
||||||
|
$stack->set(0, $b);
|
||||||
|
$this->assertNotSame($a, $stack->get(0));
|
||||||
|
$this->assertCount(1, $stack);
|
||||||
|
$this->assertEquals(0, $a->getRefCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDelete()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$a = X509::new();
|
||||||
|
$c = X509::new();
|
||||||
|
$stack->push($a);
|
||||||
|
$stack->push($c);
|
||||||
|
$this->assertEquals(1, $a->getRefCount());
|
||||||
|
$b = $stack->delete(0);
|
||||||
|
$this->assertSame($a, $b);
|
||||||
|
$this->assertCount(1, $stack);
|
||||||
|
$this->assertEquals(0, $a->getRefCount());
|
||||||
|
unset($stack[0]);
|
||||||
|
$this->assertCount(0, $stack);
|
||||||
|
$this->assertEquals(0, $c->getRefCount());
|
||||||
|
|
||||||
|
$this->expectException(\RuntimeException::class);
|
||||||
|
$stack->delete(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRetention()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$x509 = X509::new();
|
||||||
|
$stack->push($x509);
|
||||||
|
$this->assertCount(1, $stack);
|
||||||
|
$x509Item = $stack->get(0);
|
||||||
|
$this->assertSame($x509, $x509Item);
|
||||||
|
$this->assertEquals(1, $x509->getRefCount());
|
||||||
|
unset($x509Item, $x509);
|
||||||
|
|
||||||
|
/** @var X509 $x509 */
|
||||||
|
$x509 = $stack->get(0);
|
||||||
|
$this->assertFalse($x509->free());
|
||||||
|
$stack->freeAll();
|
||||||
|
$this->assertEquals(0, $x509->getRefCount());
|
||||||
|
$this->assertTrue($stack->isFreed());
|
||||||
|
$this->assertTrue($x509->isFreed());
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAddressingCorrect()
|
||||||
|
{
|
||||||
|
$ffi = OpenSSL::getFFI();
|
||||||
|
$x509 = $ffi->X509_new();
|
||||||
|
$address = OpenSSL::addressOf($x509);
|
||||||
|
$st = $ffi->sk_new_null();
|
||||||
|
$ffi->sk_push($st, $x509);
|
||||||
|
$second = $ffi->cast("X509*", $ffi->sk_value($st, 0));
|
||||||
|
$secondAddress = OpenSSL::addressOf($second);
|
||||||
|
$this->assertEquals($address, $secondAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testForeach()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$a = X509::new();
|
||||||
|
$b = X509::new();
|
||||||
|
$c = X509::new();
|
||||||
|
$stack->push($a);
|
||||||
|
$stack->push($b);
|
||||||
|
$stack->push($c);
|
||||||
|
|
||||||
|
$x = 0;
|
||||||
|
foreach ($stack as $x509) {
|
||||||
|
$x++;
|
||||||
|
$this->assertEquals(X509::class, get_class($x509));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPop()
|
||||||
|
{
|
||||||
|
$stack = Stack\X509Stack::new();
|
||||||
|
$a = X509::new();
|
||||||
|
$b = X509::new();
|
||||||
|
$c = X509::new();
|
||||||
|
$stack->push($a);
|
||||||
|
$stack->push($b);
|
||||||
|
$stack[] = $c;
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
while ($x509 = $stack->pop()) {
|
||||||
|
$this->assertEquals(X509::class, get_class($x509));
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEquals(3, $i);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue