Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.94% covered (warning)
85.94%
55 / 64
78.95% covered (warning)
78.95%
15 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Buffer
85.94% covered (warning)
85.94%
55 / 64
78.95% covered (warning)
78.95%
15 / 19
46.91
0.00% covered (danger)
0.00%
0 / 1
 __construct
86.96% covered (warning)
86.96%
20 / 23
0.00% covered (danger)
0.00%
0 / 1
9.18
 concat
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 fromArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 from
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromBase58
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 pad
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 push
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 slice
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 splice
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 shift
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fixed
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toBase58String
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 value
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 computedFormat
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
17.56
 alloc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Attestto\SolanaPhpSdk\Util;
4
5use Countable;
6use Attestto\SolanaPhpSdk\Exceptions\InputValidationException;
7use Attestto\SolanaPhpSdk\Exceptions\TodoException;
8use Attestto\SolanaPhpSdk\PublicKey;
9use SplFixedArray;
10
11/**
12 * A convenient wrapper class around an array of bytes (int's).
13 *
14 */
15class Buffer implements Countable
16{
17    const TYPE_STRING = 'string';
18    const TYPE_BYTE = 'byte';
19    const TYPE_SHORT = 'short';
20    const TYPE_INT = 'int';
21    const TYPE_LONG = 'long';
22    const TYPE_FLOAT = 'float';
23
24    const FORMAT_CHAR_SIGNED = 'c';
25    const FORMAT_CHAR_UNSIGNED = 'C';
26    const FORMAT_SHORT_16_SIGNED = 's';
27    const FORMAT_SHORT_16_UNSIGNED = 'v';
28    const FORMAT_LONG_32_SIGNED = 'l';
29    const FORMAT_LONG_32_UNSIGNED = 'V';
30    const FORMAT_LONG_LONG_64_SIGNED = 'q';
31    const FORMAT_LONG_LONG_64_UNSIGNED = 'P';
32    const FORMAT_FLOAT = 'e';
33
34    /**
35     * @var array<int>
36     */
37    protected array $data;
38
39    /**
40     * @var bool is this a signed or unsigned value?
41     */
42    protected ?bool $signed = null;
43
44    /**
45     * @var ?string $datatype
46     */
47    protected ?string $datatype = null;
48
49
50    /**
51     * @param mixed $value
52     */
53    public function __construct($value = null, ?string $datatype = null, ?bool $signed = null)
54    {
55        $this->datatype = $datatype;
56        $this->signed = $signed;
57
58        $isString = is_string($value);
59        $isNumeric = is_numeric($value);
60
61        if ($isString || $isNumeric) {
62            $this->datatype = $datatype;
63            $this->signed = $signed;
64
65            // unpack returns an array indexed at 1.
66            $this->data = $isString
67                ? array_values(unpack("C*", $value))
68                : array_values(unpack("C*", pack($this->computedFormat(), $value)));
69        } elseif (is_array($value)) {
70            $this->data = $value;
71        } elseif ($value instanceof PublicKey) {
72            $this->data = $value->toBytes();
73        } elseif ($value instanceof Buffer) {
74            $this->data = $value->toArray();
75            $this->datatype = $value->datatype;
76            $this->signed = $value->signed;
77        } elseif ($value == null) {
78            $this->data = [];
79        } elseif (method_exists($value, 'toArray')) {
80            $this->data = $value->toArray();
81        } else {
82            throw new InputValidationException('Unsupported $value for Buffer: ' . get_class($value));
83        }
84    }
85
86    /**
87     * @throws InputValidationException
88     */
89    public static function concat(array $buffers): static
90    {
91        $data = [];
92        foreach ($buffers as $buffer) {
93            $data = array_merge($data, $buffer->toArray());
94        }
95
96        return new static($data);
97    }
98
99    /**
100     * @throws InputValidationException
101     */
102    public static function fromArray(array $array): static
103    {
104        return new static($array);
105    }
106
107    /**
108     * For convenience.
109     *
110     * @param $value
111     * @return Buffer
112     * @throws InputValidationException
113     */
114    public static function from($value = null, ?string $format = null, ?bool $signed = null): Buffer
115    {
116        return new static($value, $format, $signed);
117    }
118
119    /**
120     * For convenience.
121     *
122     * @param string $value
123     * @return Buffer
124     * @throws InputValidationException
125     */
126    public static function fromBase58(string $value): Buffer
127    {
128        $value = PublicKey::base58()->decode($value);
129
130        return new static($value);
131    }
132
133    /**
134     * @param $len
135     * @param int $val
136     * @return $this
137     */
138    public function pad($len, int $val = 0): Buffer
139    {
140        $this->data = array_pad($this->data, $len, $val);
141
142        return $this;
143    }
144
145    /**
146     * @param $source
147     * @return $this
148     */
149    public function push($source): Buffer
150    {
151        $sourceAsBuffer = Buffer::from($source);
152
153        array_push($this->data, ...$sourceAsBuffer->toArray());
154
155        return $this;
156    }
157
158    /**
159     * @return Buffer
160     */
161    public function slice(int $offset, ?int $length = null, ?string $format = null, ?bool $signed = null): Buffer
162    {
163        return static::from(array_slice($this->data, $offset, $length), $format, $signed);
164    }
165
166    /**
167     * @return Buffer
168     */
169    public function splice(int $offset, ?int $length = null): Buffer
170    {
171        return static::from(array_splice($this->data, $offset, $length));
172    }
173
174    /**
175     * @return ?int
176     */
177    public function shift(): ?int
178    {
179        return array_shift($this->data);
180    }
181
182    /**
183     * @return $this
184     */
185    public function fixed(int $size): Buffer
186    {
187        $fixedSizeData = SplFixedArray::fromArray($this->data);
188        $fixedSizeData->setSize($size);
189        $this->data = $fixedSizeData->toArray();
190
191        return $this;
192    }
193
194    /**
195     * Return binary representation of $value.
196     *
197     * @return array
198     */
199    public function toArray(): array
200    {
201        return $this->data;
202    }
203
204    /**
205     * Return binary string representation of $value.
206     *
207     * @return string
208     */
209    public function toString(): string
210    {
211        return $this;
212    }
213
214    /**
215     * Return string representation of $value.
216     *
217     * @return string
218     */
219    public function toBase58String(): string
220    {
221        return PublicKey::base58()->encode($this->toString());
222    }
223
224    /**
225     * @return int
226     * @throws InputValidationException
227     */
228    #[\ReturnTypeWillChange]
229    public function count()
230    {
231        return count($this->toArray());
232    }
233
234    /**
235     * @return string
236     * @throws InputValidationException
237     */
238    public function __toString()
239    {
240        return pack('C*', ...$this->toArray());
241    }
242
243    /**
244     * Convert the binary array to its corresponding value derived from $datatype, $signed, and sizeof($data).
245     *
246     * Note: it is expected that the ->fixed($length) method has already been called.
247     *
248     * @return mixed
249     */
250    public function value(?int $length = null)
251    {
252        if ($length) {
253            $this->fixed($length);
254        }
255
256        if ($this->datatype === self::TYPE_STRING) {
257            return ord(pack("C*", ...$this->toArray()));
258        } else {
259            return unpack($this->computedFormat(), pack("C*", ...$this->toArray()))[1];
260        }
261    }
262
263    /**
264     * @return string
265     * @throws InputValidationException
266     */
267    protected function computedFormat(): string
268    {
269        if (! $this->datatype) {
270            throw new InputValidationException('Trying to calculate format of unspecified buffer. Please specify a datatype.');
271        }
272
273        switch ($this->datatype) {
274            case self::TYPE_STRING: return self::FORMAT_CHAR_UNSIGNED;
275            case self::TYPE_BYTE: return $this->signed ? self::FORMAT_CHAR_SIGNED : self::FORMAT_CHAR_UNSIGNED;
276            case self::TYPE_SHORT: return $this->signed ? self::FORMAT_SHORT_16_SIGNED : self::FORMAT_SHORT_16_UNSIGNED;
277            case self::TYPE_INT: return $this->signed ? self::FORMAT_LONG_32_SIGNED : self::FORMAT_LONG_32_UNSIGNED;
278            case self::TYPE_LONG: return $this->signed ? self::FORMAT_LONG_LONG_64_SIGNED : self::FORMAT_LONG_LONG_64_UNSIGNED;
279            case self::TYPE_FLOAT: return self::FORMAT_FLOAT;
280            default: throw new InputValidationException("Unsupported datatype.");
281        }
282    }
283
284    /**
285     * @return Buffer
286     * @throws InputValidationException
287     */
288    public static function alloc(int $size): Buffer
289    {
290        return new static(array_fill(0, $size, 0));
291
292    }
293
294}