Completed
Pull Request — master (#204)
by thomas
25:00
created

Parser::doNext()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9.0468

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 36
ccs 22
cts 24
cp 0.9167
rs 4.9091
cc 9
eloc 22
nc 14
nop 1
crap 9.0468
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Parser;
4
5
use BitWasp\Bitcoin\Script\Opcodes;
6
use BitWasp\Bitcoin\Math\Math;
7
use BitWasp\Bitcoin\Script\ScriptInterface;
8
use BitWasp\Buffertools\Buffer;
9
10
class Parser implements \Iterator
11
{
12
    /**
13
     * @var Math
14
     */
15
    private $math;
16
17
    /**
18
     * @var Buffer
19
     */
20
    private $empty;
21
22
    /**
23
     * @var int
24
     */
25
    private $position = 0;
26
27
    /**
28
     * @var int
29
     */
30
    private $end = 0;
31
32
    /**
33
     * @var int
34
     */
35
    private $execPtr = 0;
36
37
    /**
38
     * @var string
39
     */
40
    private $data = '';
41
42
    /**
43
     * @var Operation[]
44
     */
45
    private $array = array();
46
47
    /**
48
     * ScriptParser constructor.
49
     * @param Math $math
50
     * @param ScriptInterface $script
51
     */
52 1059
    public function __construct(Math $math, ScriptInterface $script)
53
    {
54 1059
        $this->math = $math;
55 1059
        $buffer = $script->getBuffer();
56 1059
        $this->data = $buffer->getBinary();
57 1059
        $this->end = $buffer->getSize();
0 ignored issues
show
Documentation Bug introduced by
It seems like $buffer->getSize() can also be of type string. However, the property $end is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
58 1059
        $this->script = $script;
0 ignored issues
show
Bug introduced by
The property script does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
59 1059
        $this->empty = new Buffer('', 0, $math);
60 1059
    }
61
62
    /**
63
     * @return int
64
     */
65
    public function getPosition()
66
    {
67
        return $this->position;
68
    }
69
70
    /**
71
     * @param string $packFormat
72
     * @param integer $strSize
73
     * @return array|bool
74
     */
75 54
    private function unpackSize($packFormat, $strSize)
76
    {
77 54
        if ($this->end - $this->position < $strSize) {
78
            return false;
79
        }
80
81 54
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
82 54
        $size = $size[1];
83 54
        $this->position += $strSize;
84
85 54
        return $size;
86
    }
87
88
    /**
89
     * @param int $size
90
     * @return bool
91
     */
92 753
    private function validateSize($size)
93
    {
94 753
        $pdif = ($this->end - $this->position);
95 753
        $a = $pdif < 0;
96 753
        $b = $pdif < $size;
97 753
        $r = !($a || $b);
98
99 753
        return $r;
100
    }
101
102
    /**
103
     * @param int $ptr
104
     * @return Operation
105
     */
106 1017
    private function doNext($ptr)
107
    {
108 1017
        if ($this->math->cmp($this->position, $this->end) >= 0) {
109
            throw new \RuntimeException('Position exceeds end of script!');
110
        }
111
112 1017
        $opCode = ord($this->data[$this->position++]);
113 1017
        $pushData = $this->empty;
114 1017
        $dataSize = 0;
115
116 1017
        if ($opCode <= Opcodes::OP_PUSHDATA4) {
117 753
            if ($opCode < Opcodes::OP_PUSHDATA1) {
118 729
                $dataSize = $opCode;
119 753
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
120 30
                $dataSize = $this->unpackSize('C', 1);
121 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
122 12
                $dataSize = $this->unpackSize('v', 2);
123 12
            } else {
124 12
                $dataSize = $this->unpackSize('V', 4);
125
            }
126
127 753
            if ($dataSize === false || $this->validateSize($dataSize) === false) {
128
                throw new \RuntimeException('Failed to unpack data from Script');
129
            }
130
131 753
            if ($dataSize > 0) {
132 615
                $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
133 615
            }
134
135 753
            $this->position += $dataSize;
136 753
        }
137
138 1017
        $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize);
139
140 1017
        return $this->array[$ptr];
141
    }
142
143
    /**
144
     *
145
     */
146 1059
    public function rewind()
147
    {
148 1059
        $this->execPtr = 0;
149 1059
    }
150
151
    /**
152
     * @return Operation
153
     */
154 1017
    public function current()
155
    {
156 1017
        if (isset($this->array[$this->execPtr])) {
157
            $exec = $this->array[$this->execPtr];
158
        } else {
159 1017
            $exec = $this->doNext($this->execPtr);
160
        }
161
162 1017
        return $exec;
163
    }
164
165
    /**
166
     * @return int
167
     */
168
    public function key()
169
    {
170
        return $this->execPtr;
171
    }
172
173
    /**
174
     * @return Operation
175
     */
176 1017
    public function next()
177
    {
178 1017
        $ptr = $this->execPtr;
179 1017
        if (isset($this->array[$ptr])) {
180 1017
            $this->execPtr++;
181 1017
            return $this->array[$ptr];
182
        }
183
184
        return null;
185
    }
186
187
    /**
188
     * @return bool
189
     */
190 1059
    public function valid()
191
    {
192 1059
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
193
    }
194
195
    /**
196
     * @return Operation[]
197
     */
198 987
    public function decode()
199
    {
200 987
        $result = [];
201 987
        foreach ($this as $operation) {
202 957
            $result[] = $operation;
203 987
        }
204
205 987
        return $result;
206
    }
207
208
    /**
209
     * @return string
210
     */
211 666
    public function getHumanReadable()
212
    {
213 666
        return implode(' ', array_map(
214 666
            function (Operation $operation) {
215 666
                return $operation->isPush()
216 666
                    ? $operation->getData()->getHex()
217 666
                    : $this->script->getOpcodes()->getOp($operation->getOp());
218 666
            },
219 666
            $this->decode()
220 666
        ));
221
    }
222
}
223