Completed
Pull Request — master (#192)
by thomas
19:54
created

Parser::decode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6667
cc 2
eloc 5
nc 2
nop 0
crap 2
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\Math\Math;
8
use BitWasp\Bitcoin\Script\ScriptInterface;
9
use BitWasp\Buffertools\Buffer;
10
11
class Parser implements \Iterator
12
{
13
    /**
14
     * @var int
15
     */
16
    private $position = 0;
17
18
    /**
19
     * @var int
20
     */
21
    private $end = 0;
22
23
    /**
24
     * @var int
25
     */
26
    private $execPtr = 0;
27
28
    /**
29
     * @var string
30
     */
31
    private $data = '';
32
33
    /**
34
     * @var Operation[]
35
     */
36
    private $array = array();
37
38
    /**
39
     * ScriptParser constructor.
40
     * @param Math $math
41
     * @param ScriptInterface $script
42
     */
43 1125
    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...
44
    {
45 1125
        $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...
46 1125
        $buffer = $script->getBuffer();
47 1125
        $this->data = $buffer->getBinary();
48 1125
        $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...
49 1125
        $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...
50 1125
    }
51
52
    /**
53
     * @return int
54
     */
55
    public function getPosition()
56
    {
57
        return $this->position;
58
    }
59
60
    /**
61
     * @param string $packFormat
62
     * @param integer $strSize
63
     * @return array|bool
64
     */
65 54
    private function unpackSize($packFormat, $strSize)
66
    {
67 54
        if ($this->end - $this->position < $strSize) {
68
            return false;
69
        }
70 54
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
71 54
        $size = $size[1];
72 54
        $this->position += $strSize;
73
74 54
        return $size;
75
    }
76
77
    /**
78
     * @param int $size
79
     * @return bool
80
     */
81 633
    private function validateSize($size)
82
    {
83 633
        $pdif = ($this->end - $this->position);
84 633
        $a = $pdif < 0;
85 633
        $b = $pdif < $size;
86 633
        $r = !($a || $b);
87
88 633
        return $r;
89
    }
90
91
    /**
92
     * @param int $ptr
93
     * @return Operation
94
     */
95 1041
    private function doNext($ptr)
96
    {
97 1041
        if ($this->math->cmp($this->position, $this->end) >= 0) {
98
            throw new \RuntimeException('Position exceeds end of script!');
99
        }
100
101 1041
        $opCode = ord($this->data[$this->position++]);
102 1041
        $pushData = null;
103 1041
        $dataSize = 0;
104
105 1041
        if ($opCode === Opcodes::OP_0) {
106 210
            $pushData = new Buffer('', 0);
107 1041
        } elseif ($opCode <= Opcodes::OP_PUSHDATA4) {
108 633
            if ($opCode < Opcodes::OP_PUSHDATA1) {
109 609
                $dataSize = $opCode;
110 633
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
111 30
                $dataSize = $this->unpackSize('C', 1);
112 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
113 12
                $dataSize = $this->unpackSize('v', 2);
114 12
            } else {
115 12
                $dataSize = $this->unpackSize('V', 4);
116
            }
117
118 633
            if ($dataSize === false || $this->validateSize($dataSize) === false) {
119
                throw new \RuntimeException('Failed to unpack data from Script');
120
            }
121
122 633
            $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
123
124 633
            $this->position += $dataSize;
125 633
        }
126
127 1041
        $this->array[$ptr] = $result = new Operation($opCode, $pushData, $dataSize);
128
129 1041
        return $result;
130
    }
131
132
    /**
133
     *
134
     */
135 1083
    public function rewind()
136
    {
137 1083
        $this->execPtr = 0;
138 1083
    }
139
140
    /**
141
     * @return Operation
142
     */
143 1041
    public function current()
144
    {
145 1041
        if (isset($this->array[$this->execPtr])) {
146
            $exec = $this->array[$this->execPtr];
147
        } else {
148 1041
            $exec = $this->doNext($this->execPtr);
149
        }
150
151 1041
        return $exec;
152
    }
153
154
    /**
155
     * @return int
156
     */
157
    public function key()
158
    {
159
        return $this->execPtr;
160
    }
161
162
    /**
163
     * @return Operation
164
     */
165 1065
    public function next()
166
    {
167 1065
        $ptr = $this->execPtr;
168 1065
        if (isset($this->array[$ptr])) {
169 1029
            $this->execPtr++;
170 1029
            return $this->array[$ptr];
171
        }
172
173 36
        return null;
174
    }
175
176
    /**
177
     * @return bool
178
     */
179 1083
    public function valid()
180
    {
181 1083
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
182
    }
183
184
    /**
185
     * returns a mix of Buffer objects and strings
186
     *
187
     * @return Buffer[]|string[]
188
     */
189 915
    public function parse()
190
    {
191 915
        $data = array();
192
193 915
        $it = $this;
194 915
        foreach ($it as $exec) {
195 903
            $opCode = $exec->getOp();
196 903
            if ($opCode == 0) {
197 162
                $push = Buffer::hex('00', 1, $this->math);
198 903
            } elseif ($opCode <= 78) {
199 519
                $push = $exec->getData();
200 519
            } else {
201
                // None of these are pushdatas, so just an opcode
202 843
                $push = $this->script->getOpcodes()->getOp($opCode);
203
            }
204
205 897
            $data[] = $push;
206 909
        }
207
208 909
        return $data;
209
    }
210
211
    /**
212
     * @return Operation[]
213
     */
214 687
    public function decode()
215
    {
216 687
        $result = [];
217 687
        foreach ($this as $operation) {
218 651
            $result[] = $operation;
219 687
        }
220
221 687
        return $result;
222
    }
223
224
    /**
225
     * @return string
226
     */
227 672
    public function getHumanReadable()
228
    {
229 672
        $parse = $this->parse();
230
231 672
        $array = array_map(
232 672
            function ($value) {
233 672
                return ($value instanceof Buffer)
234 672
                    ? $value->getHex()
235 672
                    : $value;
236 672
            },
237
            $parse
238 672
        );
239
240 672
        return implode(' ', $array);
241
    }
242
}
243