Completed
Pull Request — master (#591)
by thomas
20:21
created

HierarchicalKeySequence::fromNode()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0144

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 5
nop 1
dl 0
loc 20
ccs 11
cts 12
cp 0.9167
crap 5.0144
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Key\Deterministic;
6
7
/**
8
 * NB: Paths returned by this library omit m/M. This is because
9
 * some knowledge is lost during derivations, so the full path
10
 * is already considered 'meta-data'. It also allows the library
11
 * to assume derivations are relative to the current instance.
12
 */
13
class HierarchicalKeySequence
14
{
15
16
    const START_HARDENED = 2147483648;
17
18
    /**
19
     * @param int $sequence
20
     * @return bool
21
     */
22 25
    public function isHardened(int $sequence): bool
23
    {
24 25
        return ($sequence >> 31) === 1;
25
    }
26
27
    /**
28
     * @param int $sequence
29
     * @return int
30
     */
31 19
    public function getHardened(int $sequence): int
32
    {
33 19
        if ($this->isHardened($sequence)) {
34 1
            throw new \LogicException('Sequence is already for a hardened key');
35
        }
36
37 18
        $flag = 1 << 31;
38 18
        $hardened = $sequence | $flag;
39
40 18
        return (int) $hardened;
41
    }
42
43
    /**
44
     * Convert a human readable path node (eg, "0", "0'", or "0h") into the correct sequence (0, 0x80000000, 0x80000000)
45
     *
46
     * @param string $node
47
     * @return int
48
     */
49 22
    public function fromNode(string $node): int
50
    {
51 22
        if (strlen($node) < 1) {
52
            throw new \RuntimeException("Invalid node in sequence - empty value");
53
        }
54
55 22
        $last = substr(strtolower($node), -1);
56 22
        $hardened = false;
57 22
        if ($last === "h" || $last === "'") {
58 18
            $node = substr($node, 0, -1);
59 18
            $hardened = true;
60
        }
61
62 22
        $node = (int) $node;
63 22
        if ($hardened) {
64 18
            $node = $this->getHardened($node);
65
        }
66
67 22
        return $node;
68
    }
69
70
    /**
71
     * Given a sequence, get the human readable node. Ie, 0 -> 0, 0x80000000 -> 0h
72
     *
73
     * @param int $sequence
74
     * @return string
75
     */
76 12
    public function getNode(int $sequence): string
77
    {
78 12
        if ($this->isHardened($sequence)) {
79 9
            $sequence = $sequence - self::START_HARDENED;
80 9
            $sequence = (string) $sequence . 'h';
81
        }
82
83 12
        return (string) $sequence;
84
    }
85
86
    /**
87
     * Decodes a human-readable path, into an array of integers (sequences)
88
     *
89
     * @param string $path
90
     * @return int[]
91
     */
92 18
    public function decodePath(string $path): array
93
    {
94 18
        if ($path === '') {
95 1
            throw new \InvalidArgumentException('Invalid path passed to decodePath()');
96
        }
97
98 17
        $list = [];
99 17
        foreach (explode('/', $path) as $segment) {
100 17
            if ($segment !== 'm' && $segment !== 'M') {
101 17
                $list[] = $this->fromNode($segment);
102
            }
103
        }
104
105 17
        return $list;
106
    }
107
108
    /**
109
     * Encodes a list of sequences to the human-readable path.
110
     *
111
     * @param array|\stdClass|\Traversable $list
112
     * @return string
113
     */
114 3
    public function encodePath($list)
115
    {
116 3
        self::validateListType($list);
117
118 3
        $path = [];
119 3
        foreach ($list as $sequence) {
0 ignored issues
show
Bug introduced by
The expression $list of type array|object<stdClass>|object<Traversable> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
120 3
            $path[] = $this->getNode((int) $sequence);
121
        }
122
123 3
        return implode('/', $path);
124
    }
125
126
    /**
127
     * Check the list, mainly that it works for foreach()
128
     *
129
     * @param \stdClass|array|\Traversable $list
130
     */
131 7
    public static function validateListType($list)
132
    {
133 7
        if (!is_array($list) && !$list instanceof \Traversable && !$list instanceof \stdClass) {
134
            throw new \InvalidArgumentException('Sequence list must be an array or \Traversable');
135
        }
136 7
    }
137
}
138