1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 /** Union used to split a 16-bit unsigned integer into 2 8-bit unsigned integers or vice-versa */
27 union IPAddressByteUnion
28 {
29     uint16 combined;
30     uint8 split[2];
31 };
32 
zeroUnusedBytes(uint8 * address)33 static void zeroUnusedBytes (uint8* address) noexcept
34 {
35     for (int i = 4; i < 16; ++i)
36         address[i] = 0;
37 }
38 
IPAddress()39 IPAddress::IPAddress() noexcept
40 {
41     for (int i = 0; i < 16; ++i)
42         address[i] = 0;
43 }
44 
IPAddress(const uint8 bytes[],bool IPv6)45 IPAddress::IPAddress (const uint8 bytes[], bool IPv6) noexcept : isIPv6 (IPv6)
46 {
47     for (int i = 0; i < (isIPv6 ? 16 : 4); ++i)
48         address[i] = bytes[i];
49 
50     if (! isIPv6)
51         zeroUnusedBytes (address);
52 }
53 
IPAddress(const uint16 bytes[8])54 IPAddress::IPAddress (const uint16 bytes[8]) noexcept : isIPv6 (true)
55 {
56     IPAddressByteUnion temp;
57 
58     for (int i = 0; i < 8; ++i)
59     {
60         temp.combined = bytes[i];
61 
62         address[i * 2]     = temp.split[0];
63         address[i * 2 + 1] = temp.split[1];
64     }
65 }
66 
IPAddress(uint8 a0,uint8 a1,uint8 a2,uint8 a3)67 IPAddress::IPAddress (uint8 a0, uint8 a1, uint8 a2, uint8 a3) noexcept : isIPv6 (false)
68 {
69     address[0] = a0;  address[1] = a1;
70     address[2] = a2;  address[3] = a3;
71 
72     zeroUnusedBytes (address);
73 }
74 
IPAddress(uint16 a1,uint16 a2,uint16 a3,uint16 a4,uint16 a5,uint16 a6,uint16 a7,uint16 a8)75 IPAddress::IPAddress (uint16 a1, uint16 a2, uint16 a3, uint16 a4,
76                       uint16 a5, uint16 a6, uint16 a7, uint16 a8) noexcept : isIPv6 (true)
77 
78 {
79     uint16 array[8] = { a1, a2, a3, a4, a5, a6, a7, a8 };
80 
81     IPAddressByteUnion temp;
82 
83     for (int i = 0; i < 8; ++i)
84     {
85         temp.combined = array[i];
86         address[i * 2]     = temp.split[0];
87         address[i * 2 + 1] = temp.split[1];
88     }
89 }
90 
IPAddress(uint32 n)91 IPAddress::IPAddress (uint32 n) noexcept : isIPv6 (false)
92 {
93     address[0] = static_cast<uint8> (n >> 24);
94     address[1] = static_cast<uint8> ((n >> 16) & 255);
95     address[2] = static_cast<uint8> ((n >> 8) & 255);
96     address[3] = static_cast<uint8> ((n & 255));
97 
98     zeroUnusedBytes (address);
99 }
100 
isNull() const101 bool IPAddress::isNull() const
102 {
103     for (int i = 0; i < 16; ++i)
104         if (address[i] != 0)
105             return false;
106 
107     return true;
108 }
109 
removePort(const String & adr)110 static String removePort (const String& adr)
111 {
112     if (adr.containsAnyOf ("[]"))
113         return adr.fromFirstOccurrenceOf ("[", false, true).upToLastOccurrenceOf ("]", false, true);
114 
115     if (adr.indexOf (":") == adr.lastIndexOf (":"))
116         return adr.upToLastOccurrenceOf (":", false, true);
117 
118     return adr;
119 }
120 
IPAddress(const String & adr)121 IPAddress::IPAddress (const String& adr)
122 {
123     auto ipAddress = removePort (adr);
124 
125     isIPv6 = ipAddress.contains (":");
126 
127     if (! isIPv6)
128     {
129         auto tokens = StringArray::fromTokens (ipAddress, ".", {});
130 
131         for (int i = 0; i < 4; ++i)
132             address[i] = (uint8) tokens[i].getIntValue();
133 
134         zeroUnusedBytes (address);
135     }
136     else
137     {
138         auto tokens = StringArray::fromTokens (ipAddress, ":", {});
139 
140         if (tokens.contains ({})) // if :: shorthand has been used
141         {
142             auto idx = tokens.indexOf ({});
143             tokens.set (idx, "0");
144             tokens.removeEmptyStrings();
145 
146             // mapped IPv4 address will be treated as a single token, so pad the end of the StringArray
147             if (tokens[tokens.size() - 1].containsChar ('.'))
148                 tokens.add ({});
149 
150             while (tokens.size() < 8)
151                 tokens.insert (idx, "0");
152         }
153 
154         for (int i = 0; i < 8; ++i)
155         {
156             if (i == 6 && isIPv4MappedAddress (IPAddress (address, true)))
157             {
158                 IPAddress v4Address (tokens[i]);
159 
160                 address[12] = v4Address.address[0];
161                 address[13] = v4Address.address[1];
162                 address[14] = v4Address.address[2];
163                 address[15] = v4Address.address[3];
164 
165                 break;
166             }
167 
168             IPAddressByteUnion temp;
169             temp.combined = (uint16) CharacterFunctions::HexParser<int>::parse (tokens[i].getCharPointer());
170 
171             address[i * 2]     = temp.split[0];
172             address[i * 2 + 1] = temp.split[1];
173         }
174     }
175 }
176 
toString() const177 String IPAddress::toString() const
178 {
179     if (! isIPv6)
180     {
181         String s ((int) address[0]);
182 
183         for (int i = 1; i < 4; ++i)
184             s << '.' << (int) address[i];
185 
186         return s;
187     }
188 
189     IPAddressByteUnion temp;
190 
191     temp.split[0] = address[0];
192     temp.split[1] = address[1];
193 
194     auto addressString = String::toHexString (temp.combined);
195 
196     for (int i = 1; i < 8; ++i)
197     {
198         temp.split[0] = address[i * 2];
199         temp.split[1] = address[i * 2 + 1];
200 
201         addressString << ':' << String::toHexString (temp.combined);
202     }
203 
204     return getFormattedAddress (addressString);
205 }
206 
operator ==(const IPAddress & other) const207 bool IPAddress::operator== (const IPAddress& other) const noexcept    { return compare (other) == 0; }
operator !=(const IPAddress & other) const208 bool IPAddress::operator!= (const IPAddress& other) const noexcept    { return compare (other) != 0; }
operator <(const IPAddress & other) const209 bool IPAddress::operator<  (const IPAddress& other) const noexcept    { return compare (other) <  0; }
operator <=(const IPAddress & other) const210 bool IPAddress::operator<= (const IPAddress& other) const noexcept    { return compare (other) <= 0; }
operator >(const IPAddress & other) const211 bool IPAddress::operator>  (const IPAddress& other) const noexcept    { return compare (other) >  0; }
operator >=(const IPAddress & other) const212 bool IPAddress::operator>= (const IPAddress& other) const noexcept    { return compare (other) >= 0; }
213 
compare(const IPAddress & other) const214 int IPAddress::compare (const IPAddress& other) const noexcept
215 {
216     if (isIPv6 != other.isIPv6)
217     {
218         if (isIPv6)
219         {
220             if (isIPv4MappedAddress (*this))
221                 return convertIPv4MappedAddressToIPv4 (*this).compare (other);
222 
223             return 1;
224         }
225 
226         if (isIPv4MappedAddress (other))
227             return compare (convertIPv4MappedAddressToIPv4 (other));
228 
229         return -1;
230     }
231 
232     for (int i = 0; i < (isIPv6 ? 16 : 4); ++i)
233     {
234         if (address[i] > other.address[i])  return 1;
235         if (address[i] < other.address[i])  return -1;
236     }
237 
238     return 0;
239 }
240 
any()241 IPAddress IPAddress::any() noexcept               { return IPAddress(); }
broadcast()242 IPAddress IPAddress::broadcast() noexcept         { return IPAddress (255, 255, 255, 255); }
local(bool IPv6)243 IPAddress IPAddress::local (bool IPv6) noexcept   { return IPv6 ? IPAddress (0, 0, 0, 0, 0, 0, 0, 1)
244                                                                 : IPAddress (127, 0, 0, 1); }
245 
getFormattedAddress(const String & unformattedAddress)246 String IPAddress::getFormattedAddress (const String& unformattedAddress)
247 {
248     jassert (unformattedAddress.contains (":") && ! unformattedAddress.contains ("::")); // needs to be an unformatted IPv6 address!
249 
250     auto portString    = unformattedAddress.fromFirstOccurrenceOf ("]", false, true);
251     auto addressString = unformattedAddress.dropLastCharacters (portString.length()).removeCharacters ("[]");
252 
253     auto tokens = StringArray::fromTokens (addressString, ":", {});
254 
255     int numZeros = 0;
256     int numZerosTemp = 0;
257     bool isFirst = false;
258     bool isLast = false;
259 
260     for (int i = 0; i < tokens.size(); ++i)
261     {
262         auto& t = tokens.getReference (i);
263 
264         if (t.getHexValue32() == 0x0000)
265         {
266             ++numZeros;
267 
268             if (i == 0)
269                 isFirst = true;
270             else if (i == tokens.size() - 1 && numZeros > numZerosTemp)
271                 isLast = true;
272 
273             if (t.length() > 1)
274                 addressString = addressString.replace (String::repeatedString ("0", t.length()), "0");
275 
276             if (isFirst && numZerosTemp != 0 && numZeros > numZerosTemp)
277                 isFirst = false;
278         }
279         else
280         {
281             addressString = addressString.replace (t, t.trimCharactersAtStart ("0").toLowerCase());
282 
283             if (numZeros > 0)
284             {
285                 if (numZeros > numZerosTemp)
286                     numZerosTemp = numZeros;
287 
288                 numZeros = 0;
289             }
290         }
291     }
292 
293     if (numZerosTemp > numZeros)
294         numZeros = numZerosTemp;
295 
296     if (numZeros > 1)
297     {
298         if (numZeros == tokens.size())
299         {
300             addressString = "::,";
301         }
302         else
303         {
304             auto zeroString = isFirst ? "0" + String::repeatedString (":0", numZeros - 1)
305                                       : String::repeatedString (":0", numZeros);
306 
307             addressString = addressString.replaceFirstOccurrenceOf (zeroString, ":");
308 
309             if (isLast)
310                 addressString << ':';
311         }
312     }
313 
314     if (portString.isNotEmpty())
315         addressString = "[" + addressString + "]" + portString;
316 
317     return addressString;
318 }
319 
isIPv4MappedAddress(const IPAddress & mappedAddress)320 bool IPAddress::isIPv4MappedAddress (const IPAddress& mappedAddress)
321 {
322     if (! mappedAddress.isIPv6)
323         return false;
324 
325     for (int i = 0; i < 10; ++i)
326         if (mappedAddress.address[i] != 0)
327             return false;
328 
329     if (mappedAddress.address[10] != 255 || mappedAddress.address[11] != 255)
330         return false;
331 
332     return true;
333 }
334 
convertIPv4MappedAddressToIPv4(const IPAddress & mappedAddress)335 IPAddress IPAddress::convertIPv4MappedAddressToIPv4 (const IPAddress& mappedAddress)
336 {
337     // The address that you're converting needs to be IPv6!
338     jassert (mappedAddress.isIPv6);
339 
340     if (isIPv4MappedAddress (mappedAddress))
341         return { mappedAddress.address[12], mappedAddress.address[13],
342                  mappedAddress.address[14], mappedAddress.address[15] };
343 
344     return {};
345 }
346 
convertIPv4AddressToIPv4Mapped(const IPAddress & addressToMap)347 IPAddress IPAddress::convertIPv4AddressToIPv4Mapped (const IPAddress& addressToMap)
348 {
349     // The address that you're converting needs to be IPv4!
350     jassert (! addressToMap.isIPv6);
351 
352     return { 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff,
353             static_cast<uint16> ((addressToMap.address[0] << 8) | addressToMap.address[1]),
354             static_cast<uint16> ((addressToMap.address[2] << 8) | addressToMap.address[3]) };
355 }
356 
getLocalAddress(bool includeIPv6)357 IPAddress IPAddress::getLocalAddress (bool includeIPv6)
358 {
359     auto addresses = getAllAddresses (includeIPv6);
360 
361     for (auto& a : addresses)
362         if (a != local())
363             return a;
364 
365     return local();
366 }
367 
getAllAddresses(bool includeIPv6)368 Array<IPAddress> IPAddress::getAllAddresses (bool includeIPv6)
369 {
370     Array<IPAddress> addresses;
371     findAllAddresses (addresses, includeIPv6);
372     return addresses;
373 }
374 
375 
376 //==============================================================================
377 //==============================================================================
378 #if JUCE_UNIT_TESTS
379 
380 struct IPAddressTests : public UnitTest
381 {
IPAddressTestsjuce::IPAddressTests382     IPAddressTests()
383         : UnitTest ("IPAddress", UnitTestCategories::networking)
384     {}
385 
runTestjuce::IPAddressTests386     void runTest() override
387     {
388         testConstructors();
389         testFindAllAddresses();
390         testFindBroadcastAddress();
391     }
392 
testConstructorsjuce::IPAddressTests393     void testConstructors()
394     {
395         beginTest ("constructors");
396 
397         // Default IPAdress should be null
398         IPAddress defaultConstructed;
399         expect (defaultConstructed.isNull());
400 
401         auto local = IPAddress::local();
402         expect (! local.isNull());
403 
404         IPAddress ipv4{1, 2, 3, 4};
405         expect (! ipv4.isNull());
406         expect (! ipv4.isIPv6);
407         expect (ipv4.toString() == "1.2.3.4");
408     }
409 
testFindAllAddressesjuce::IPAddressTests410     void testFindAllAddresses()
411     {
412         beginTest ("find all addresses");
413 
414         Array<IPAddress> ipv4Addresses;
415         Array<IPAddress> allAddresses;
416 
417         IPAddress::findAllAddresses (ipv4Addresses, false);
418         IPAddress::findAllAddresses (allAddresses, true);
419 
420         expect (allAddresses.size() >= ipv4Addresses.size());
421 
422         for (auto& a : ipv4Addresses)
423         {
424             expect (! a.isNull());
425             expect (! a.isIPv6);
426         }
427 
428         for (auto& a : allAddresses)
429         {
430             expect (! a.isNull());
431         }
432     }
433 
testFindBroadcastAddressjuce::IPAddressTests434     void testFindBroadcastAddress()
435     {
436         beginTest ("broadcast addresses");
437 
438         Array<IPAddress> addresses;
439 
440         // Only IPv4 interfaces have broadcast
441         IPAddress::findAllAddresses (addresses, false);
442 
443         for (auto& a : addresses)
444         {
445             expect (! a.isNull());
446 
447             auto broadcastAddress = IPAddress::getInterfaceBroadcastAddress (a);
448 
449             // If we retrieve an address, it should be an IPv4 address
450             if (! broadcastAddress.isNull())
451             {
452                 expect (! a.isIPv6);
453             }
454         }
455 
456         // Expect to fail to find a broadcast for this address
457         IPAddress address{1, 2, 3, 4};
458         expect (IPAddress::getInterfaceBroadcastAddress (address).isNull());
459     }
460 };
461 
462 static IPAddressTests iPAddressTests;
463 
464 #endif
465 
466 } // namespace juce
467