1<?php
2
3namespace IPLib;
4
5use IPLib\Address\AddressInterface;
6
7/**
8 * Factory methods to build class instances.
9 */
10class Factory
11{
12    /**
13     * Parse an IP address string.
14     *
15     * @param string $address the address to parse
16     * @param bool $mayIncludePort set to false to avoid parsing addresses with ports
17     * @param bool $mayIncludeZoneID set to false to avoid parsing IPv6 addresses with zone IDs (see RFC 4007)
18     *
19     * @return \IPLib\Address\AddressInterface|null
20     */
21    public static function addressFromString($address, $mayIncludePort = true, $mayIncludeZoneID = true)
22    {
23        $result = null;
24        if ($result === null) {
25            $result = Address\IPv4::fromString($address, $mayIncludePort);
26        }
27        if ($result === null) {
28            $result = Address\IPv6::fromString($address, $mayIncludePort, $mayIncludeZoneID);
29        }
30
31        return $result;
32    }
33
34    /**
35     * Convert a byte array to an address instance.
36     *
37     * @param int[]|array $bytes
38     *
39     * @return \IPLib\Address\AddressInterface|null
40     */
41    public static function addressFromBytes(array $bytes)
42    {
43        $result = null;
44        if ($result === null) {
45            $result = Address\IPv4::fromBytes($bytes);
46        }
47        if ($result === null) {
48            $result = Address\IPv6::fromBytes($bytes);
49        }
50
51        return $result;
52    }
53
54    /**
55     * Parse an IP range string.
56     *
57     * @param string $range
58     *
59     * @return \IPLib\Range\RangeInterface|null
60     */
61    public static function rangeFromString($range)
62    {
63        $result = null;
64        if ($result === null) {
65            $result = Range\Subnet::fromString($range);
66        }
67        if ($result === null) {
68            $result = Range\Pattern::fromString($range);
69        }
70        if ($result === null) {
71            $result = Range\Single::fromString($range);
72        }
73
74        return $result;
75    }
76
77    /**
78     * Create a Range instance starting from its boundaries.
79     *
80     * @param string|\IPLib\Address\AddressInterface $from
81     * @param string|\IPLib\Address\AddressInterface $to
82     *
83     * @return \IPLib\Range\RangeInterface|null
84     */
85    public static function rangeFromBoundaries($from, $to)
86    {
87        $result = null;
88        $invalid = false;
89        foreach (array('from', 'to') as $param) {
90            if (!($$param instanceof AddressInterface)) {
91                $$param = (string) $$param;
92                if ($$param === '') {
93                    $$param = null;
94                } else {
95                    $$param = static::addressFromString($$param);
96                    if ($$param === null) {
97                        $invalid = true;
98                    }
99                }
100            }
101        }
102        if ($invalid === false) {
103            $result = static::rangeFromBoundaryAddresses($from, $to);
104        }
105
106        return $result;
107    }
108
109    /**
110     * @param \IPLib\Address\AddressInterface $from
111     * @param \IPLib\Address\AddressInterface $to
112     *
113     * @return \IPLib\Range\RangeInterface|null
114     */
115    protected static function rangeFromBoundaryAddresses(AddressInterface $from = null, AddressInterface $to = null)
116    {
117        if ($from === null && $to === null) {
118            $result = null;
119        } elseif ($to === null) {
120            $result = Range\Single::fromAddress($from);
121        } elseif ($from === null) {
122            $result = Range\Single::fromAddress($to);
123        } else {
124            $result = null;
125            $addressType = $from->getAddressType();
126            if ($addressType === $to->getAddressType()) {
127                $cmp = strcmp($from->getComparableString(), $to->getComparableString());
128                if ($cmp === 0) {
129                    $result = Range\Single::fromAddress($from);
130                } else {
131                    if ($cmp > 0) {
132                        list($from, $to) = array($to, $from);
133                    }
134                    $fromBytes = $from->getBytes();
135                    $toBytes = $to->getBytes();
136                    $numBytes = count($fromBytes);
137                    $sameBits = 0;
138                    for ($byteIndex = 0; $byteIndex < $numBytes; ++$byteIndex) {
139                        $fromByte = $fromBytes[$byteIndex];
140                        $toByte = $toBytes[$byteIndex];
141                        if ($fromByte === $toByte) {
142                            $sameBits += 8;
143                        } else {
144                            $differentBitsInByte = decbin($fromByte ^ $toByte);
145                            $sameBits += 8 - strlen($differentBitsInByte);
146                            break;
147                        }
148                    }
149                    $result = static::rangeFromString($from->toString(true).'/'.(string) $sameBits);
150                }
151            }
152        }
153
154        return $result;
155    }
156}
157