Completed
Pull Request — master (#195)
by thomas
26:54 queued 01:40
created

Parser   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 90.43%

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 231
wmc 30
lcom 1
cbo 3
ccs 85
cts 94
cp 0.9043
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getPosition() 0 4 1
A unpackSize() 0 11 2
A validateSize() 0 9 2
A __construct() 0 9 1
C doNext() 0 34 8
A rewind() 0 4 1
A current() 0 10 2
A key() 0 4 1
A next() 0 10 2
A valid() 0 4 2
A parse() 0 21 4
A decode() 0 9 2
A getHumanReadable() 0 15 2
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 int
14
     */
15
    private $position = 0;
16
17
    /**
18
     * @var int
19
     */
20
    private $end = 0;
21
22
    /**
23
     * @var int
24
     */
25
    private $execPtr = 0;
26
27
    /**
28
     * @var string
29
     */
30
    private $data = '';
31
32
    /**
33
     * @var Operation[]
34
     */
35
    private $array = array();
36
37
    /**
38
     * ScriptParser constructor.
39
     * @param Math $math
40
     * @param ScriptInterface $script
41
     */
42 1125
    public function __construct(Math $math, ScriptInterface $script)
43
    {
44 1125
        $this->math = $math;
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...
45 1125
        $buffer = $script->getBuffer();
46 1125
        $this->data = $buffer->getBinary();
47 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...
48 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...
49 1125
        $this->empty = new Buffer('', 0, $math);
0 ignored issues
show
Bug introduced by
The property empty 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 771
    private function validateSize($size)
82
    {
83 771
        $pdif = ($this->end - $this->position);
84 771
        $a = $pdif < 0;
85 771
        $b = $pdif < $size;
86 771
        $r = !($a || $b);
87
88 771
        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 = $this->empty;
103 1041
        $dataSize = 0;
104
105 1041
        if ($opCode <= Opcodes::OP_PUSHDATA4) {
106 771
            if ($opCode < Opcodes::OP_PUSHDATA1) {
107 747
                $dataSize = $opCode;
108 771
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
109 30
                $dataSize = $this->unpackSize('C', 1);
110 54
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
111 12
                $dataSize = $this->unpackSize('v', 2);
112 12
            } else {
113 12
                $dataSize = $this->unpackSize('V', 4);
114
            }
115
116 771
            if ($dataSize === false || $this->validateSize($dataSize) === false) {
117
                throw new \RuntimeException('Failed to unpack data from Script');
118
            }
119
120 771
            $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
121
122 771
            $this->position += $dataSize;
123 771
        }
124
125 1041
        $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize);
126
127 1041
        return $this->array[$ptr];
128
    }
129
130
    /**
131
     *
132
     */
133 1083
    public function rewind()
134
    {
135 1083
        $this->execPtr = 0;
136 1083
    }
137
138
    /**
139
     * @return Operation
140
     */
141 1041
    public function current()
142
    {
143 1041
        if (isset($this->array[$this->execPtr])) {
144
            $exec = $this->array[$this->execPtr];
145
        } else {
146 1041
            $exec = $this->doNext($this->execPtr);
147
        }
148
149 1041
        return $exec;
150
    }
151
152
    /**
153
     * @return int
154
     */
155
    public function key()
156
    {
157
        return $this->execPtr;
158
    }
159
160
    /**
161
     * @return Operation
162
     */
163 1065
    public function next()
164
    {
165 1065
        $ptr = $this->execPtr;
166 1065
        if (isset($this->array[$ptr])) {
167 1029
            $this->execPtr++;
168 1029
            return $this->array[$ptr];
169
        }
170
171 36
        return null;
172
    }
173
174
    /**
175
     * @return bool
176
     */
177 1083
    public function valid()
178
    {
179 1083
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
180
    }
181
182
    /**
183
     * returns a mix of Buffer objects and strings
184
     *
185
     * @return Buffer[]|string[]
186
     */
187 915
    public function parse()
188
    {
189 915
        $data = array();
190
191 915
        $it = $this;
192 915
        foreach ($it as $exec) {
193 903
            $opCode = $exec->getOp();
194 903
            if ($opCode == 0) {
195 162
                $push = Buffer::hex('00', 1, $this->math);
196 903
            } elseif ($opCode <= 78) {
197 519
                $push = $exec->getData();
198 519
            } else {
199
                // None of these are pushdatas, so just an opcode
200 843
                $push = $this->script->getOpcodes()->getOp($opCode);
201
            }
202
203 897
            $data[] = $push;
204 909
        }
205
206 909
        return $data;
207
    }
208
209
    /**
210
     * @return Operation[]
211
     */
212 687
    public function decode()
213
    {
214 687
        $result = [];
215 687
        foreach ($this as $operation) {
216 651
            $result[] = $operation;
217 687
        }
218
219 687
        return $result;
220
    }
221
222
    /**
223
     * @return string
224
     */
225 672
    public function getHumanReadable()
226
    {
227 672
        $parse = $this->parse();
228
229 672
        $array = array_map(
230 672
            function ($value) {
231 672
                return ($value instanceof Buffer)
232 672
                    ? $value->getHex()
233 672
                    : $value;
234 672
            },
235
            $parse
236 672
        );
237
238 672
        return implode(' ', $array);
239
    }
240
}
241