Completed
Pull Request — master (#191)
by thomas
58:45 queued 54:54
created

Parser::doNext()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.0533

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 34
ccs 21
cts 23
cp 0.913
rs 4.9091
cc 9
eloc 22
nc 11
nop 1
crap 9.0533
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Parser;
4
5
use BitWasp\Bitcoin\Bitcoin;
6
use BitWasp\Bitcoin\Script\Opcodes;
7
use BitWasp\Bitcoin\Script\Parser\Operation;
8
use BitWasp\Bitcoin\Math\Math;
9
use BitWasp\Bitcoin\Script\ScriptInterface;
10
use BitWasp\Buffertools\Buffer;
11
12
class Parser implements \Iterator
13
{
14
    /**
15
     * @var int
16
     */
17
    private $position = 0;
18
19
    /**
20
     * @var int
21
     */
22
    private $end = 0;
23
24
    /**
25
     * @var int
26
     */
27
    private $execPtr = 0;
28
29
    /**
30
     * @var string
31
     */
32
    private $data = '';
33
34
    /**
35
     * @var Operation[]
36
     */
37
    private $array = array();
38
39
    /**
40
     * ScriptParser constructor.
41
     * @param Math $math
42
     * @param ScriptInterface $script
43
     */
44 1827
    public function __construct(Math $math, ScriptInterface $script)
0 ignored issues
show
Unused Code introduced by
The parameter $math is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
45
    {
46 1827
        $this->math = Bitcoin::getMath();
0 ignored issues
show
Bug introduced by
The property math 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...
47 1827
        $buffer = $script->getBuffer();
48 1827
        $this->data = $buffer->getBinary();
49 1827
        $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...
50 1827
        $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...
51 1827
    }
52
53
    /**
54
     * @return int
55
     */
56
    public function getPosition()
57
    {
58
        return $this->position;
59
    }
60
61
    /**
62
     * @param string $packFormat
63
     * @param integer $strSize
64
     * @return array|bool
65
     */
66 54
    private function unpackSize($packFormat, $strSize)
67
    {
68 54
        if ($this->end - $this->position < $strSize) {
69
            return false;
70
        }
71 54
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
72 54
        $size = $size[1];
73 54
        $this->position += $strSize;
74
75 54
        return $size;
76
    }
77
78
    /**
79
     * @param int $size
80
     * @return bool
81
     */
82 651
    private function validateSize($size)
83
    {
84 651
        $pdif = ($this->end - $this->position);
85 651
        return ! ($pdif < 0 || $pdif < $size);
86
    }
87
88
    /**
89
     * @param int $ptr
90
     * @return Operation
91
     */
92 1737
    private function doNext($ptr)
93
    {
94 1737
        if ($this->math->cmp($this->position, $this->end) >= 0) {
95
            throw new \RuntimeException('Position exceeds end of script!');
96
        }
97
98 1737
        $opCode = ord($this->data[$this->position++]);
99 1737
        $pushData = null;
100
101 1737
        if ($opCode === Opcodes::OP_0) {
102 216
            $pushData = new Buffer('', 0);
103 1737
        } elseif ($opCode <= Opcodes::OP_PUSHDATA4) {
104 651
            if ($opCode < Opcodes::OP_PUSHDATA1) {
105 627
                $size = $opCode;
106 651
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
107 30
                $size = $this->unpackSize('C', 1);
108 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
109 12
                $size = $this->unpackSize('v', 2);
110 12
            } else {
111 12
                $size = $this->unpackSize('V', 4);
112
            }
113
114 651
            if ($size === false || $this->validateSize($size) === false) {
115
                throw new \RuntimeException('Failed to unpack data from Script');
116
            }
117
118 651
            $pushData = new Buffer(substr($this->data, $this->position, $size), $size, $this->math);
119 651
            $this->position += $size;
120 651
        }
121
122 1737
        $this->array[$ptr] = $result = new Operation($opCode, $pushData);
123
124 1737
        return $result;
125
    }
126
127
    /**
128
     *
129
     */
130 1791
    public function rewind()
131
    {
132 1791
        $this->execPtr = 0;
133 1791
    }
134
135
    /**
136
     * @return Operation
137
     */
138 1737
    public function current()
139
    {
140 1737
        if (isset($this->array[$this->execPtr])) {
141
            $exec = $this->array[$this->execPtr];
142
        } else {
143 1737
            $exec = $this->doNext($this->execPtr);
144
        }
145
146 1737
        return $exec;
147
    }
148
149
    /**
150
     * @return int
151
     */
152
    public function key()
153
    {
154
        return $this->execPtr;
155
    }
156
157
    /**
158
     * @return Operation
159
     */
160 1089
    public function next()
161
    {
162 1089
        $ptr = $this->execPtr;
163 1089
        if (isset($this->array[$ptr])) {
164 1053
            $this->execPtr++;
165 1053
            return $this->array[$ptr];
166
        }
167
168 36
        return null;
169
    }
170
171
    /**
172
     * @return bool
173
     */
174 1791
    public function valid()
175
    {
176 1791
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
177
    }
178
179
    /**
180
     * returns a mix of Buffer objects and strings
181
     *
182
     * @return Buffer[]|string[]
183
     */
184 1023
    public function parse()
185
    {
186 1023
        $data = array();
187
188 1023
        $it = $this;
189 1023
        foreach ($it as $exec) {
190 987
            $opCode = $exec->getOp();
191 987
            if ($opCode == 0) {
192 186
                $push = Buffer::hex('00', 1, $this->math);
193 987
            } elseif ($opCode <= 78) {
194 597
                $push = $exec->getData();
195 597
            } else {
196
                // None of these are pushdatas, so just an opcode
197 921
                $push = $this->script->getOpcodes()->getOp($opCode);
198
            }
199
200 981
            $data[] = $push;
201 1017
        }
202
203 1017
        return $data;
204
    }
205
206
    /**
207
     * @return string
208
     */
209 672
    public function getHumanReadable()
210
    {
211 672
        $parse = $this->parse();
212
213 672
        $array = array_map(
214 672
            function ($value) {
215 672
                return ($value instanceof Buffer)
216 672
                    ? $value->getHex()
217 672
                    : $value;
218 672
            },
219
            $parse
220 672
        );
221
222 672
        return implode(' ', $array);
223
    }
224
}
225