Parser   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 238
Duplicated Lines 0 %

Test Coverage

Coverage 92.77%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 83
c 1
b 0
f 0
dl 0
loc 238
ccs 77
cts 83
cp 0.9277
rs 9.68
wmc 34

12 Methods

Rating   Name   Duplication   Size   Complexity  
A valid() 0 3 2
A __construct() 0 8 1
A rewind() 0 4 1
A unpackSize() 0 11 2
A next() 0 9 2
A getPosition() 0 3 1
A decode() 0 8 2
A key() 0 3 1
A current() 0 9 2
A slice() 0 17 4
B doNext() 0 37 10
A getHumanReadable() 0 14 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Script\Parser;
6
7
use BitWasp\Bitcoin\Math\Math;
8
use BitWasp\Bitcoin\Script\Opcodes;
9
use BitWasp\Bitcoin\Script\Script;
10
use BitWasp\Bitcoin\Script\ScriptInterface;
11
use BitWasp\Buffertools\Buffer;
12
use BitWasp\Buffertools\BufferInterface;
13
14
class Parser implements \Iterator
15
{
16
    /**
17
     * @var Math
18
     */
19
    private $math;
20
21
    /**
22
     * @var BufferInterface
23
     */
24
    private $empty;
25
26
    /**
27
     * @var ScriptInterface
28
     */
29
    private $script;
30
31
    /**
32
     * @var int
33
     */
34
    private $count = 0;
35
36
    /**
37
     * @var int
38
     */
39
    private $position = 0;
40
41
    /**
42
     * @var int
43
     */
44
    private $end = 0;
45
46
    /**
47
     * @var int
48
     */
49
    private $execPtr = 0;
50
51
    /**
52
     * @var string
53
     */
54
    private $data = '';
55
56
    /**
57
     * @var Operation[]
58
     */
59
    private $array = array();
60
61
    /**
62
     * ScriptParser constructor.
63
     * @param Math $math
64
     * @param ScriptInterface $script
65
     */
66 4960
    public function __construct(Math $math, ScriptInterface $script)
67
    {
68 4960
        $this->math = $math;
69 4960
        $buffer = $script->getBuffer();
70 4960
        $this->data = $buffer->getBinary();
71 4960
        $this->end = $buffer->getSize();
72 4960
        $this->script = $script;
73 4960
        $this->empty = new Buffer('', 0);
74 4960
    }
75
76
    /**
77
     * @return int
78
     */
79 4
    public function getPosition(): int
80
    {
81 4
        return $this->position;
82
    }
83
84
    /**
85
     * @param string $packFormat
86
     * @param integer $strSize
87
     * @return array|bool
88
     */
89 133
    private function unpackSize(string $packFormat, int $strSize)
90
    {
91 133
        if ($this->end - $this->position < $strSize) {
92
            return false;
93
        }
94
95 133
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
96 133
        $size = $size[1];
97 133
        $this->position += $strSize;
98
99 133
        return $size;
100
    }
101
102
    /**
103
     * @param int $ptr
104
     * @return Operation
105
     */
106 4943
    private function doNext(int $ptr)
107
    {
108 4943
        if ($this->position >= $this->end) {
109
            throw new \RuntimeException('Position exceeds end of script!');
110
        }
111
112 4943
        $opCode = ord($this->data[$this->position++]);
113 4943
        $pushData = $this->empty;
114 4943
        $dataSize = 0;
115
116 4943
        if ($opCode <= Opcodes::OP_PUSHDATA4) {
117 3725
            if ($opCode < Opcodes::OP_PUSHDATA1) {
118 3657
                $dataSize = $opCode;
119 133
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
120 74
                $dataSize = $this->unpackSize('C', 1);
121 67
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
122 50
                $dataSize = $this->unpackSize('v', 2);
123
            } else {
124 17
                $dataSize = $this->unpackSize('V', 4);
125
            }
126
127 3725
            $delta = ($this->end - $this->position);
128 3725
            if ($dataSize === false || $delta < 0 || $delta < $dataSize) {
129 15
                throw new \RuntimeException('Failed to unpack data from Script');
130
            }
131
132 3713
            if ($dataSize > 0) {
133 2737
                $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize);
134
            }
135
136 3713
            $this->position += $dataSize;
137
        }
138
139 4931
        $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize);
140 4931
        $this->count++;
141
142 4931
        return $this->array[$ptr];
143
    }
144
145
    /**
146
     * @param int $begin
147
     * @param null|int $length
148
     * @return Script
149
     */
150 252
    public function slice(int $begin, int $length = null)
151
    {
152 252
        if ($begin < 0) {
153
            throw new \RuntimeException("Invalid start of script - cannot be negative or ");
154
        }
155
156 252
        $maxLength = $this->end - $begin;
157
158 252
        if (null === $length) {
159
            $length = $maxLength;
160
        } else {
161 252
            if ($length > $maxLength) {
162
                throw new \RuntimeException("Cannot slice this much from script");
163
            }
164
        }
165
166 252
        return new Script(new Buffer(substr($this->data, $begin, $length)));
167
    }
168
169
    /**
170
     *
171
     */
172 4953
    public function rewind()
173
    {
174 4953
        $this->execPtr = 0;
175 4953
        $this->position = 0;
176 4953
    }
177
178
    /**
179
     * @return Operation
180
     */
181 4943
    public function current()
182
    {
183 4943
        if (isset($this->array[$this->execPtr])) {
184 252
            $exec = $this->array[$this->execPtr];
185
        } else {
186 4943
            $exec = $this->doNext($this->execPtr);
187
        }
188
189 4931
        return $exec;
190
    }
191
192
    /**
193
     * @return int
194
     */
195 142
    public function key()
196
    {
197 142
        return $this->execPtr;
198
    }
199
200
    /**
201
     * @return Operation|null
202
     */
203 4809
    public function next()
204
    {
205 4809
        $ptr = $this->execPtr;
206 4809
        if (isset($this->array[$ptr])) {
207 4803
            $this->execPtr++;
208 4803
            return $this->array[$ptr];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->array[$ptr] returns the type BitWasp\Bitcoin\Script\Parser\Operation which is incompatible with the return type mandated by Iterator::next() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
209
        }
210
211 6
        return null;
212
    }
213
214
    /**
215
     * @return bool
216
     */
217 4953
    public function valid()
218
    {
219 4953
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
220
    }
221
222
    /**
223
     * @return Operation[]
224
     */
225 2481
    public function decode(): array
226
    {
227 2481
        $result = [];
228 2481
        foreach ($this as $operation) {
229 2465
            $result[] = $operation;
230
        }
231
232 2481
        return $result;
233
    }
234
235
    /**
236
     * @return string
237
     */
238 3
    public function getHumanReadable(): string
239
    {
240 3
        return implode(' ', array_map(
241 3
            function (Operation $operation) {
242 2
                $op = $operation->getOp();
243 2
                if ($op === Opcodes::OP_0 || $op === Opcodes::OP_1NEGATE || $op >= Opcodes::OP_1 && $op <= Opcodes::OP_16) {
244
                    return $this->script->getOpcodes()->getOp($op);
245 2
                } else if ($operation->isPush()) {
246 2
                    return $operation->getData()->getHex();
247
                } else {
248 1
                    return $this->script->getOpcodes()->getOp($operation->getOp());
249
                }
250 3
            },
251
            $this->decode()
252
        ));
253
    }
254
}
255