Completed
Push — develop ( 75d7aa...a3c683 )
by Stuart
04:31
created

ParseNetLink::fromTraversable()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 32
rs 8.8571
cc 2
eloc 17
nc 2
nop 1
1
<?php
2
3
/**
4
 * Copyright (c) 2016-present Ganbaro Digital Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   OperatingSystem/NetInterface/Parsers
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2016-present Ganbaro Digital Ltd www.ganbarodigital.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://code.ganbarodigital.com/php-operating-system
42
 */
43
44
namespace GanbaroDigital\OperatingSystem\IpRoute\Parsers;
45
46
use GanbaroDigital\ArrayTools\Filters\ExtractFirstItem;
47
use GanbaroDigital\ArrayTools\Parsers\ConvertKeyValuePairsToArray;
48
use GanbaroDigital\ArrayTools\ValueBuilders\ConvertToArray;
49
use GanbaroDigital\OperatingSystem\Exceptions\E4xx_CannotParseNetLinkLine;
50
use GanbaroDigital\OperatingSystem\Exceptions\E4xx_UnsupportedType;
51
use GanbaroDigital\OperatingSystem\NetInterfaces\Values\NetLink;
52
use GanbaroDigital\Reflection\Maps\MapTypeToMethod;
53
use GanbaroDigital\Reflection\ValueBuilders\SimpleType;
54
use GanbaroDigital\TextTools\Filters\FilterOutEmptyValues;
55
56
class ParseNetLink
57
{
58
    /**
59
     * extract a NetLink from the output of 'ip addr show' or 'ip link show'
60
     *
61
     * @param  mixed $linkLines
62
     *         the output to parse
63
     * @return NetLink
64
     */
65
    public function __invoke($linkLines)
66
    {
67
        return self::from($linkLines);
68
    }
69
70
    /**
71
     * extract a NetLink from the output of 'ip addr show' or 'ip link show'
72
     *
73
     * @param  mixed $linkLines
74
     *         the output to parse
75
     * @return NetLink
76
     */
77
    public static function from($linkLines)
78
    {
79
        $method = MapTypeToMethod::using($linkLines, self::$dispatchMap);
80
        return self::$method($linkLines);
81
    }
82
83
    /**
84
     * extract a NetLink from the output of 'ip addr show' or 'ip link show'
85
     *
86
     * @param  string $linkLines
87
     *         the output to parse
88
     * @return NetLink
89
     */
90
    private static function fromString($linkLines)
91
    {
92
        $lines = explode("\n", $linkLines);
93
        return self::fromTraversable($lines);
94
    }
95
96
    /**
97
     * extract a NetLink from the output of 'ip addr show' or 'ip link show'
98
     *
99
     * @param  mixed $linkLines
100
     *         the output to parse
101
     * @return NetLink
102
     */
103
    private static function fromTraversable($linkLines)
104
    {
105
        // we want a real PHP array for this
106
        $linkLines = ConvertToArray::from($linkLines);
107
108
        // get rid of any empty lines of text
109
        $linkLines = FilterOutEmptyValues::from($linkLines);
110
111
        // line 0 has the majority interface properties
112
        $linkDetails = self::parseFirstLine(array_shift($linkLines));
113
114
        // if there is a line 1, it contains link layer type and (optional) addresses
115
        while (!empty($linkLines)) {
116
            $additionalDetails = self::parseSecondLine(array_shift($linkLines));
117
            $linkDetails = array_merge($linkDetails, $additionalDetails);
118
        }
119
120
        // now to convert this into an NetLink value
121
        $retval = new NetLink(
122
            $linkDetails['index'],
123
            $linkDetails['name'],
124
            $linkDetails['master'],
125
            $linkDetails['flags'],
126
            $linkDetails['properties'],
127
            $linkDetails['linkType'],
128
            $linkDetails['physicalAddress'],
129
            $linkDetails['broadcastAddress']
130
        );
131
132
        // all done
133
        return $retval;
134
    }
135
136
    /**
137
     * called when we have been given a data type that we do not support
138
     *
139
     * @param  mixed $linkLines
140
     * @return void
141
     * @throws E4xx_UnsupportedType
142
     */
143
    private static function nothingMatchesTheInputType($linkLines)
144
    {
145
        throw new E4xx_UnsupportedType(SimpleType::from($linkLines));
146
    }
147
148
    /**
149
     * extract data from the first line of the link definition
150
     *
151
     * @param  string $line
152
     *         the line to parse
153
     * @return array
154
     *         the extracted data
155
     */
156
    private static function parseFirstLine($line)
157
    {
158
        // this regex should parse every permutation of the link definition
159
        // that we know about
160
        $regex = "~(?<index>[0-9]+): (?<name>[^:@]+)(@(?<master>[^:]+)){0,1}: \\<(?<flags>[^>]+)\\>( (?<extra>.*)){0,1}~";
161
        $matches = [];
162
        if (!preg_match_all($regex, $line, $matches)) {
163
            throw new E4xx_CannotParseNetLinkLine($line);
164
        }
165
166
        // the 'master' tells us which interface that this interface is linked to
167
        $master = self::extractMasterFromMatches($matches);
168
169
        // a list of key/value pairs of interface settings
170
        $properties = self::extractPropertiesFromMatches($matches);
171
172
        // a list of flags that are set on this interface
173
        $flags = self::extractFlagsFromMatches($matches);
174
175
        // all done
176
        return [
177
            'index' => (int)$matches['index'][0],
178
            'name' => $matches['name'][0],
179
            'master' => $master,
180
            'flags' => $flags,
181
            'properties' => $properties,
182
            'linkType' => null,
183
            'physicalAddress' => null,
184
            'broadcastAddress' => null
185
        ];
186
    }
187
188
    /**
189
     * extract the master interface (if there is one) from the first line of
190
     * the link definition
191
     *
192
     * @param  array $matches
193
     *         the match results from running a regex against the first line
194
     *         of the link definition
195
     * @return string|null
196
     */
197
    private static function extractMasterFromMatches($matches)
198
    {
199
        // extract whatever value is there
200
        $master = ExtractFirstItem::from($matches['master'], null);
201
202
        // make sure we return NULL if there was no actual value
203
        if (empty($master)) {
204
            $master = null;
205
        }
206
207
        // all done
208
        return $master;
209
    }
210
211
    /**
212
     * convert the key/value pairs from the first line of the link definition
213
     * into a list of key/value pairs
214
     *
215
     * @param  array $matches
216
     *         the match results from running a regex against the first line
217
     *         of the link definition
218
     * @return array
219
     *         the extracted key/value pairs
220
     */
221
    private static function extractPropertiesFromMatches($matches)
222
    {
223
        // turn the properties into a list of key/value pairs
224
        return ConvertKeyValuePairsToArray::from($matches['extra'][0], ' ', ' ');
225
    }
226
227
    /**
228
     * convert the interface flags from the first line of the link definition
229
     * into a list of flags that are set
230
     *
231
     * @param  array $matches
232
     *         the match results from running a regex against the first line
233
     *         of the link definition
234
     * @return array
235
     *         the extracted flags - the flag name is the key, and the value
236
     *         is always TRUE
237
     */
238
    private static function extractFlagsFromMatches($matches)
239
    {
240
        // turn the flags into a list, where the name of the flag is the key,
241
        // and the value is always TRUE
242
        return array_fill_keys(explode(',', $matches['flags'][0]), true);
243
    }
244
245
    /**
246
     * extract the data from the second line of the link definition
247
     *
248
     * @param  string $line
249
     *         the second line of the link definition
250
     * @return array
251
     *         a list of the extracted link properties
252
     */
253
    private static function parseSecondLine($line)
254
    {
255
        // our return value
256
        $retval = [];
257
258
        $parts = explode(" ", trim($line));
259
260
        // the link type is always the first entry on the line
261
        $retval['linkType'] = $parts[0];
262
263
        // the second entry is always the MAC address
264
        if (isset($parts[1])) {
265
            $retval['physicalAddress'] = $parts[1];
266
        }
267
268
        // the third entry (if it exists) is always the hardware broadcast
269
        // address
270
        if (isset($parts[3]) && $parts[2] == 'brd') {
271
            $retval['broadcastAddress'] = $parts[3];
272
        }
273
274
        // all done
275
        return $retval;
276
    }
277
278
    /**
279
     * a map of how to handle supported data types
280
     *
281
     * @var array
282
     */
283
    private static $dispatchMap = [
284
        'String' => 'fromString',
285
        'Traversable' => 'fromTraversable',
286
    ];
287
}
288