| 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 |
3 | namespace Attestto\SolanaPhpSdk\Util;
4 |
5 | use Countable;
6 | use Attestto\SolanaPhpSdk\Exceptions\InputValidationException;
7 | use Attestto\SolanaPhpSdk\Exceptions\TodoException;
8 | use Attestto\SolanaPhpSdk\PublicKey;
9 | use SplFixedArray;
10 |
11 | /**
12 | * A convenient wrapper class around an array of bytes (int's).
13 | *
14 | */
15 | class 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 | }