Completed
Pull Request — master (#446)
by thomas
105:00 queued 101:56
created

Parser::decode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Parser;
4
5
use BitWasp\Bitcoin\Math\Math;
6
use BitWasp\Bitcoin\Script\Opcodes;
7
use BitWasp\Bitcoin\Script\ScriptInterface;
8
use BitWasp\Buffertools\Buffer;
9
use BitWasp\Buffertools\BufferInterface;
10
11
class Parser implements \Iterator
12
{
13
    /**
14
     * @var Math
15
     */
16
    private $math;
17
18
    /**
19
     * @var BufferInterface
20
     */
21
    private $empty;
22
23
    /**
24
     * @var ScriptInterface
25
     */
26
    private $script;
27
28
    /**
29
     * @var int
30
     */
31
    private $position = 0;
32
33
    /**
34
     * @var int
35
     */
36
    private $end = 0;
37
38
    /**
39
     * @var int
40
     */
41
    private $execPtr = 0;
42
43
    /**
44
     * @var string
45
     */
46
    private $data = '';
47
48
    /**
49
     * @var Operation[]
50
     */
51
    private $array = array();
52
53
    /**
54
     * ScriptParser constructor.
55
     * @param Math $math
56
     * @param ScriptInterface $script
57
     */
58 1446
    public function __construct(Math $math, ScriptInterface $script)
59
    {
60 1446
        $this->math = $math;
61 1446
        $buffer = $script->getBuffer();
62 1446
        $this->data = $buffer->getBinary();
63 1446
        $this->end = $buffer->getSize();
64 1446
        $this->script = $script;
65 1446
        $this->empty = new Buffer('', 0, $math);
66 1446
    }
67
68
    /**
69
     * @return int
70
     */
71 1
    public function getPosition()
72
    {
73 1
        return $this->position;
74
    }
75
76
    /**
77
     * @param string $packFormat
78
     * @param integer $strSize
79
     * @return array|bool
80
     */
81 42
    private function unpackSize($packFormat, $strSize)
82
    {
83 42
        if ($this->end - $this->position < $strSize) {
84
            return false;
85
        }
86
87 42
        $size = unpack($packFormat, substr($this->data, $this->position, $strSize));
88 42
        $size = $size[1];
89 42
        $this->position += $strSize;
90
91 42
        return $size;
92
    }
93
94
    /**
95
     * @param int $ptr
96
     * @return Operation
97
     */
98 1421
    private function doNext($ptr)
99
    {
100 1421
        if ($this->position >= $this->end) {
101
            throw new \RuntimeException('Position exceeds end of script!');
102
        }
103
104 1421
        $opCode = ord($this->data[$this->position++]);
105 1421
        $pushData = $this->empty;
106 1421
        $dataSize = 0;
107
108 1421
        if ($opCode <= Opcodes::OP_PUSHDATA4) {
109 1119
            if ($opCode < Opcodes::OP_PUSHDATA1) {
110 1095
                $dataSize = $opCode;
111 42
            } else if ($opCode === Opcodes::OP_PUSHDATA1) {
112 22
                $dataSize = $this->unpackSize('C', 1);
113 22
            } else if ($opCode === Opcodes::OP_PUSHDATA2) {
114 16
                $dataSize = $this->unpackSize('v', 2);
115
            } else {
116 6
                $dataSize = $this->unpackSize('V', 4);
117
            }
118
119 1119
            $delta = ($this->end - $this->position);
120 1119
            if ($dataSize === false || $delta < 0 || $delta < $dataSize) {
121 7
                throw new \RuntimeException('Failed to unpack data from Script');
122
            }
123
124 1116
            if ($dataSize > 0) {
125 868
                $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize, $this->math);
126
            }
127
128 1116
            $this->position += $dataSize;
129
        }
130
131 1418
        $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize);
132
133 1418
        return $this->array[$ptr];
134
    }
135
136
    /**
137
     *
138
     */
139 1432
    public function rewind()
140
    {
141 1432
        $this->execPtr = 0;
142 1432
    }
143
144
    /**
145
     * @return Operation
146
     */
147 1421
    public function current()
148
    {
149 1421
        if (isset($this->array[$this->execPtr])) {
150
            $exec = $this->array[$this->execPtr];
151
        } else {
152 1421
            $exec = $this->doNext($this->execPtr);
153
        }
154
155 1418
        return $exec;
156
    }
157
158
    /**
159
     * @return int
160
     */
161
    public function key()
162
    {
163
        return $this->execPtr;
164
    }
165
166
    /**
167
     * @return Operation
168
     */
169 1394
    public function next()
170
    {
171 1394
        $ptr = $this->execPtr;
172 1394
        if (isset($this->array[$ptr])) {
173 1382
            $this->execPtr++;
174 1382
            return $this->array[$ptr];
175
        }
176
177 12
        return null;
178
    }
179
180
    /**
181
     * @return bool
182
     */
183 1432
    public function valid()
184
    {
185 1432
        return isset($this->array[$this->execPtr]) || $this->position < $this->end;
186
    }
187
188
    /**
189
     * @return Operation[]
190
     */
191 749
    public function decode()
192
    {
193 749
        $result = [];
194 749
        foreach ($this as $operation) {
195 740
            $result[] = $operation;
196
        }
197
198 749
        return $result;
199
    }
200
201
    /**
202
     * @return string
203
     */
204 6
    public function getHumanReadable()
205
    {
206 6
        return implode(' ', array_map(
207 6
            function (Operation $operation) {
208 4
                $op = $operation->getOp();
209 4
                if ($op === Opcodes::OP_0 || $op === Opcodes::OP_1NEGATE || $op >= Opcodes::OP_1 && $op <= Opcodes::OP_16) {
210
                    return $this->script->getOpcodes()->getOp($op);
211 4
                } else if ($operation->isPush()) {
212 4
                    return $operation->getData()->getHex();
213
                } else {
214 2
                    return $this->script->getOpcodes()->getOp($operation->getOp());
215
                }
216 6
            },
217 6
            $this->decode()
218
        ));
219
    }
220
}
221