Completed
Push — master ( 838d3a...3b658f )
by thomas
22:41 queued 01:10
created

Parser::doNext()   D

Complexity

Conditions 9
Paths 15

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9.14

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 37
ccs 22
cts 25
cp 0.88
rs 4.9091
cc 9
eloc 24
nc 15
nop 1
crap 9.14
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 1119
    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 1119
        $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 1119
        $buffer = $script->getBuffer();
48 1119
        $this->data = $buffer->getBinary();
49 1119
        $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 1119
        $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 1119
    }
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 627
    private function validateSize($size)
83
    {
84 627
        $pdif = ($this->end - $this->position);
85 627
        $a = $pdif < 0;
86 627
        $b = $pdif < $size;
87 627
        $r = !($a || $b);
88
89 627
        return $r;
90
    }
91
92
    /**
93
     * @param int $ptr
94
     * @return Operation
95
     */
96 1035
    private function doNext($ptr)
97
    {
98 1035
        if ($this->math->cmp($this->position, $this->end) >= 0) {
99
            throw new \RuntimeException('Position exceeds end of script!');
100
        }
101
102 1035
        $opCode = ord($this->data[$this->position++]);
103 1035
        $pushData = null;
104
105 1035
        if ($opCode === Opcodes::OP_0) {
106 210
            $pushData = new Buffer('', 0);
107 1035
        } elseif ($opCode <= Opcodes::OP_PUSHDATA4) {
108 627
            if ($opCode < Opcodes::OP_PUSHDATA1) {
109 603
                $size = $opCode;
110 627
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
111 30
                $size = $this->unpackSize('C', 1);
112 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
113 12
                $size = $this->unpackSize('v', 2);
114 12
            } else {
115 12
                $size = $this->unpackSize('V', 4);
116
            }
117
118 627
            if ($size === false) {
119
                throw new \RuntimeException('Failed to unpack data from Script1');
120
            }
121 627
            if ($this->validateSize($size) === false) {
122
                throw new \RuntimeException('Failed to unpack data from Script2');
123
            }
124
125 627
            $pushData = new Buffer(substr($this->data, $this->position, $size), $size, $this->math);
126 627
            $this->position += $size;
127 627
        }
128
129 1035
        $this->array[$ptr] = $result = new Operation($opCode, $pushData);
130
131 1035
        return $result;
132
    }
133
134
    /**
135
     *
136
     */
137 1083
    public function rewind()
138
    {
139 1083
        $this->execPtr = 0;
140 1083
    }
141
142
    /**
143
     * @return Operation
144
     */
145 1035
    public function current()
146
    {
147 1035
        if (isset($this->array[$this->execPtr])) {
148
            $exec = $this->array[$this->execPtr];
149
        } else {
150 1035
            $exec = $this->doNext($this->execPtr);
151
        }
152
153 1035
        return $exec;
154
    }
155
156
    /**
157
     * @return int
158
     */
159
    public function key()
160
    {
161
        return $this->execPtr;
162
    }
163
164
    /**
165
     * @return Operation
166
     */
167 1065
    public function next()
168
    {
169 1065
        $ptr = $this->execPtr;
170 1065
        if (isset($this->array[$ptr])) {
171 1029
            $this->execPtr++;
172 1029
            return $this->array[$ptr];
173
        }
174
175 36
        return null;
176
    }
177
178
    /**
179
     * @return bool
180
     */
181 1083
    public function valid()
182
    {
183 1083
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
184
    }
185
186
    /**
187
     * returns a mix of Buffer objects and strings
188
     *
189
     * @return Buffer[]|string[]
190
     */
191 1011
    public function parse()
192
    {
193 1011
        $data = array();
194
195 1011
        $it = $this;
196 1011
        foreach ($it as $exec) {
197 975
            $opCode = $exec->getOp();
198 975
            if ($opCode == 0) {
199 180
                $push = Buffer::hex('00', 1, $this->math);
200 975
            } elseif ($opCode <= 78) {
201 585
                $push = $exec->getData();
202 585
            } else {
203
                // None of these are pushdatas, so just an opcode
204 909
                $push = $this->script->getOpcodes()->getOp($opCode);
205
            }
206
207 969
            $data[] = $push;
208 1005
        }
209
210 1005
        return $data;
211
    }
212
213
    /**
214
     * @return string
215
     */
216 672
    public function getHumanReadable()
217
    {
218 672
        $parse = $this->parse();
219
220 672
        $array = array_map(
221 672
            function ($value) {
222 672
                return ($value instanceof Buffer)
223 672
                    ? $value->getHex()
224 672
                    : $value;
225 672
            },
226
            $parse
227 672
        );
228
229 672
        return implode(' ', $array);
230
    }
231
}
232