• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

markrogoyski / ipv4-subnet-calculator-php / 13229693217

09 Feb 2025 09:16PM UTC coverage: 99.539% (+0.002%) from 99.537%
13229693217

push

github

markrogoyski
Refactor CIDR notation usages.

2 of 2 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

216 of 217 relevant lines covered (99.54%)

84.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.43
/src/SubnetCalculator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace IPv4;
6

7
/**
8
 * Network calculator for subnet mask and other classless (CIDR) network information.
9
 *
10
 * Given an IP address and CIDR network size, it calculates the following information:
11
 *   - IP address network subnet masks, network and host portions, and provides aggregated reports.
12
 *   - Subnet mask
13
 *   - Network portion
14
 *   - Host portion
15
 *   - Number of IP addresses in the network
16
 *   - Number of addressable hosts in the network
17
 *   - IP address range
18
 *   - Broadcast address
19
 *   - Min and max host
20
 *   - All IP addresses
21
 *   - IPv4 ARPA Domain
22
 * Provides each data in dotted quads, hexadecimal, and binary formats, as well as array of quads.
23
 *
24
 * Aggregated network calculation reports:
25
 *  - Associative array
26
 *  - JSON
27
 *  - String
28
 *  - Printed to STDOUT
29
 */
30
class SubnetCalculator implements \JsonSerializable
31
{
32
    /** @var string IP address as dotted quads: xxx.xxx.xxx.xxx */
33
    private $ipAddress;
34

35
    /** @var int CIDR network size */
36
    private $networkSize;
37

38
    /** @var string[] of four elements containing the four quads of the IP address */
39
    private $quads = [];
40

41
    /** @var int Subnet mask in format used for subnet calculations */
42
    private $subnetMask;
43

44
    /** @var SubnetReportInterface */
45
    private $report;
46

47
    private const FORMAT_QUADS  = '%d';
48
    private const FORMAT_HEX    = '%02X';
49
    private const FORMAT_BINARY = '%08b';
50

51
    /**
52
     * Constructor - Takes IP address and network size, validates inputs, and assigns class attributes.
53
     * For example: 192.168.1.120/24 would be $ip = 192.168.1.120 $network_size = 24
54
     *
55
     * @param string                     $ipAddress   IP address in dotted quad notation.
56
     * @param int                        $networkSize CIDR network size.
57
     * @param SubnetReportInterface|null $report
58
     */
59
    public function __construct(string $ipAddress, int $networkSize, ?SubnetReportInterface $report = null)
60
    {
61
        $this->validateInputs($ipAddress, $networkSize);
743✔
62

63
        $this->ipAddress   = $ipAddress;
743✔
64
        $this->networkSize = $networkSize;
743✔
65
        $this->quads       = \explode('.', $ipAddress);
743✔
66
        $this->subnetMask  = $this->calculateSubnetMask($networkSize);
743✔
67
        $this->report      = $report ?? new SubnetReport();
743✔
68
    }
743✔
69

70
    /* **************** *
71
     * PUBLIC INTERFACE
72
     * **************** */
73

74
    /**
75
     * Get IP address as dotted quads: xxx.xxx.xxx.xxx
76
     *
77
     * @return string IP address as dotted quads.
78
     */
79
    public function getIPAddress(): string
80
    {
81
        return $this->ipAddress;
41✔
82
    }
83

84
    /**
85
     * Get IP address as array of quads: [xxx, xxx, xxx, xxx]
86
     *
87
     * @return string[]
88
     */
89
    public function getIPAddressQuads(): array
90
    {
91
        return $this->quads;
3✔
92
    }
93

94
    /**
95
     * Get IP address as hexadecimal
96
     *
97
     * @return string IP address in hex
98
     */
99
    public function getIPAddressHex(): string
100
    {
101
        return $this->ipAddressCalculation(self::FORMAT_HEX);
12✔
102
    }
103

104
    /**
105
     * Get IP address as binary
106
     *
107
     * @return string IP address in binary
108
     */
109
    public function getIPAddressBinary(): string
110
    {
111
        return $this->ipAddressCalculation(self::FORMAT_BINARY);
12✔
112
    }
113

114
    /**
115
     * Get the IP address as an integer
116
     *
117
     * @return int
118
     */
119
    public function getIPAddressInteger(): int
120
    {
121
        return $this->convertIpToInt($this->ipAddress);
12✔
122
    }
123

124
    /**
125
     * Get network size
126
     *
127
     * @return int network size
128
     */
129
    public function getNetworkSize(): int
130
    {
131
        return $this->networkSize;
58✔
132
    }
133

134
    /**
135
     * Get the CIDR notation of the subnet: IP Address/Network size.
136
     * Example: 192.168.0.15/24
137
     *
138
     * @return string
139
     */
140
    public function getCidrNotation(): string
141
    {
142
        return $this->ipAddress . '/' . $this->networkSize;
57✔
143
    }
144

145
    /**
146
     * Get the number of IP addresses in the network
147
     *
148
     * @return int Number of IP addresses
149
     */
150
    public function getNumberIPAddresses(): int
151
    {
152
        return $this->getNumberIPAddressesOfNetworkSize($this->networkSize);
373✔
153
    }
154

155
    /**
156
     * Get the number of addressable hosts in the network
157
     *
158
     * @return int Number of IP addresses that are addressable
159
     */
160
    public function getNumberAddressableHosts(): int
161
    {
162
        if ($this->networkSize == 32) {
41✔
163
            return 1;
1✔
164
        }
165
        if ($this->networkSize == 31) {
40✔
166
            return 2;
1✔
167
        }
168

169
        return ($this->getNumberIPAddresses() - 2);
39✔
170
    }
171

172
    /**
173
     * Get range of IP addresses in the network
174
     *
175
     * @return string[] containing start and end of IP address range. IP addresses in dotted quad notation.
176
     */
177
    public function getIPAddressRange(): array
178
    {
179
        return [$this->getNetworkPortion(), $this->getBroadcastAddress()];
183✔
180
    }
181

182
    /**
183
     * Get range of IP addresses in the network
184
     *
185
     * @return string[] containing start and end of IP address range. IP addresses in dotted quad notation.
186
     */
187
    public function getAddressableHostRange(): array
188
    {
189
        return [$this->getMinHost(), $this->getMaxHost()];
32✔
190
    }
191

192
    /**
193
     * Get the broadcast IP address
194
     *
195
     * @return string IP address as dotted quads
196
     */
197
    public function getBroadcastAddress(): string
198
    {
199
        $network_quads       = $this->getNetworkPortionQuads();
215✔
200
        $number_ip_addresses = $this->getNumberIPAddresses();
215✔
201

202
        $network_range_quads = [
203
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[0] & ($this->subnetMask >> 24)) + ((($number_ip_addresses - 1) >> 24) & 0xFF)),
215✔
204
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[1] & ($this->subnetMask >> 16)) + ((($number_ip_addresses - 1) >> 16) & 0xFF)),
215✔
205
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[2] & ($this->subnetMask >>  8)) + ((($number_ip_addresses - 1) >>  8) & 0xFF)),
215✔
206
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[3] & ($this->subnetMask >>  0)) + ((($number_ip_addresses - 1) >>  0) & 0xFF)),
215✔
207
        ];
208

209
        return \implode('.', $network_range_quads);
215✔
210
    }
211

212
    /**
213
     * Get minimum host IP address as dotted quads: xxx.xxx.xxx.xxx
214
     *
215
     * @return string min host as dotted quads
216
     */
217
    public function getMinHost(): string
218
    {
219
        if ($this->networkSize === 32 || $this->networkSize === 31) {
73✔
220
            return $this->ipAddress;
4✔
221
        }
222
        return $this->minHostCalculation(self::FORMAT_QUADS, '.');
69✔
223
    }
224

225
    /**
226
     * Get minimum host IP address as array of quads: [xxx, xxx, xxx, xxx]
227
     *
228
     * @return string[] min host portion as dotted quads.
229
     */
230
    public function getMinHostQuads(): array
231
    {
232
        if ($this->networkSize === 32 || $this->networkSize === 31) {
11✔
233
            return $this->quads;
2✔
234
        }
235
        return \explode('.', $this->minHostCalculation(self::FORMAT_QUADS, '.'));
9✔
236
    }
237

238
    /**
239
     * Get minimum host IP address as hex
240
     *
241
     * @return string min host portion as hex
242
     */
243
    public function getMinHostHex(): string
244
    {
245
        if ($this->networkSize === 32 || $this->networkSize === 31) {
11✔
246
            return \implode('', \array_map(
2✔
247
                function ($quad) {
248
                    return \sprintf(self::FORMAT_HEX, $quad);
2✔
249
                },
2✔
250
                $this->quads
2✔
251
            ));
252
        }
253
        return $this->minHostCalculation(self::FORMAT_HEX);
9✔
254
    }
255

256
    /**
257
     * Get minimum host IP address as binary
258
     *
259
     * @return string min host portion as binary
260
     */
261
    public function getMinHostBinary(): string
262
    {
263
        if ($this->networkSize === 32 || $this->networkSize === 31) {
11✔
264
            return \implode('', \array_map(
2✔
265
                function ($quad) {
266
                    return \sprintf(self::FORMAT_BINARY, $quad);
2✔
267
                },
2✔
268
                $this->quads
2✔
269
            ));
270
        }
271
        return $this->minHostCalculation(self::FORMAT_BINARY);
9✔
272
    }
273

274
    /**
275
     * Get minimum host IP address as an Integer
276
     *
277
     * @return int min host portion as integer
278
     */
279
    public function getMinHostInteger(): int
280
    {
281
        return $this->networkSize === 32 || $this->networkSize === 31
11✔
282
            ? $this->convertIpToInt(\implode('.', $this->quads))
2✔
283
            : $this->convertIpToInt($this->minHostCalculation(self::FORMAT_QUADS, '.'));
11✔
284
    }
285

286
    /**
287
     * Get maximum host IP address as dotted quads: xxx.xxx.xxx.xxx
288
     *
289
     * @return string max host as dotted quads.
290
     */
291
    public function getMaxHost(): string
292
    {
293
        if ($this->networkSize === 32 || $this->networkSize === 31) {
73✔
294
            return $this->ipAddress;
4✔
295
        }
296
        return $this->maxHostCalculation(self::FORMAT_QUADS, '.');
69✔
297
    }
298

299
    /**
300
     * Get maximum host IP address as array of quads: [xxx, xxx, xxx, xxx]
301
     *
302
     * @return string[] min host portion as dotted quads
303
     */
304
    public function getMaxHostQuads(): array
305
    {
306
        if ($this->networkSize === 32 || $this->networkSize === 31) {
11✔
307
            return $this->quads;
2✔
308
        }
309
        return \explode('.', $this->maxHostCalculation(self::FORMAT_QUADS, '.'));
9✔
310
    }
311

312
    /**
313
     * Get maximum host IP address as hex
314
     *
315
     * @return string max host portion as hex
316
     */
317
    public function getMaxHostHex(): string
318
    {
319
        if ($this->networkSize === 32 || $this->networkSize === 31) {
11✔
320
            return \implode('', \array_map(
2✔
321
                function ($quad) {
322
                    return \sprintf(self::FORMAT_HEX, $quad);
2✔
323
                },
2✔
324
                $this->quads
2✔
325
            ));
326
        }
327
        return $this->maxHostCalculation(self::FORMAT_HEX);
9✔
328
    }
329

330
    /**
331
     * Get maximum host IP address as binary
332
     *
333
     * @return string man host portion as binary
334
     */
335
    public function getMaxHostBinary(): string
336
    {
337
        if ($this->networkSize === 32 || $this->networkSize === 31) {
11✔
338
            return \implode('', \array_map(
2✔
339
                function ($quad) {
340
                    return \sprintf(self::FORMAT_BINARY, $quad);
2✔
341
                },
2✔
342
                $this->quads
2✔
343
            ));
344
        }
345
        return $this->maxHostCalculation(self::FORMAT_BINARY);
9✔
346
    }
347

348
    /**
349
     * Get maximum host IP address as an Integer
350
     *
351
     * @return int max host portion as integer
352
     */
353
    public function getMaxHostInteger(): int
354
    {
355
        return $this->networkSize === 32 || $this->networkSize === 31
11✔
356
            ? $this->convertIpToInt(\implode('.', $this->quads))
2✔
357
            : $this->convertIpToInt($this->maxHostCalculation(self::FORMAT_QUADS, '.'));
11✔
358
    }
359

360
    /**
361
     * Get subnet mask as dotted quads: xxx.xxx.xxx.xxx
362
     *
363
     * @return string subnet mask as dotted quads
364
     */
365
    public function getSubnetMask(): string
366
    {
367
        return $this->subnetCalculation(self::FORMAT_QUADS, '.');
41✔
368
    }
369

370
    /**
371
     * Get subnet mask as array of quads: [xxx, xxx, xxx, xxx]
372
     *
373
     * @return string[] of four elements containing the four quads of the subnet mask.
374
     */
375
    public function getSubnetMaskQuads(): array
376
    {
377
        return \explode('.', $this->subnetCalculation(self::FORMAT_QUADS, '.'));
32✔
378
    }
379

380
    /**
381
     * Get subnet mask as hexadecimal
382
     *
383
     * @return string subnet mask in hex
384
     */
385
    public function getSubnetMaskHex(): string
386
    {
387
        return $this->subnetCalculation(self::FORMAT_HEX);
41✔
388
    }
389

390
    /**
391
     * Get subnet mask as binary
392
     *
393
     * @return string subnet mask in binary
394
     */
395
    public function getSubnetMaskBinary(): string
396
    {
397
        return $this->subnetCalculation(self::FORMAT_BINARY);
41✔
398
    }
399

400
    /**
401
     * Get subnet mask as an integer
402
     *
403
     * @return int
404
     */
405
    public function getSubnetMaskInteger(): int
406
    {
407
        return $this->convertIpToInt($this->subnetCalculation(self::FORMAT_QUADS, '.'));
41✔
408
    }
409

410

411
    /**
412
     * Split the network into smaller networks
413
     *
414
     * @param int $networkSize
415
     * @return SubnetCalculator[]
416
     */
417
    public function split(int $networkSize): array
418
    {
419
        if ($networkSize <= $this->networkSize) {
30✔
420
            throw new \RuntimeException('New networkSize must be larger than the base networkSize.');
6✔
421
        }
422

423
        if ($networkSize > 32) {
24✔
424
            throw new \RuntimeException('New networkSize must be smaller than the maximum networkSize.');
8✔
425
        }
426

427
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
16✔
428

429
        $addressCount = $this->getNumberIPAddressesOfNetworkSize($networkSize);
16✔
430

431
        $ranges = [];
16✔
432
        for ($ip = $startIp; $ip <= $endIp; $ip += $addressCount) {
16✔
433
            $ranges[] = new SubnetCalculator($this->convertIpToDottedQuad($ip), $networkSize);
16✔
434
        }
435

436
        return $ranges;
16✔
437
    }
438

439
    /**
440
     * Get network portion of IP address as dotted quads: xxx.xxx.xxx.xxx
441
     *
442
     * @return string network portion as dotted quads
443
     */
444
    public function getNetworkPortion(): string
445
    {
446
        return $this->networkCalculation(self::FORMAT_QUADS, '.');
247✔
447
    }
448

449
    /**
450
     * Get network portion as array of quads: [xxx, xxx, xxx, xxx]
451
     *
452
     * @return string[] of four elements containing the four quads of the network portion
453
     */
454
    public function getNetworkPortionQuads(): array
455
    {
456
        return \explode('.', $this->networkCalculation(self::FORMAT_QUADS, '.'));
343✔
457
    }
458

459
    /**
460
     * Get network portion of IP address as hexadecimal
461
     *
462
     * @return string network portion in hex
463
     */
464
    public function getNetworkPortionHex(): string
465
    {
466
        return $this->networkCalculation(self::FORMAT_HEX);
41✔
467
    }
468

469
    /**
470
     * Get network portion of IP address as binary
471
     *
472
     * @return string network portion in binary
473
     */
474
    public function getNetworkPortionBinary(): string
475
    {
476
        return $this->networkCalculation(self::FORMAT_BINARY);
41✔
477
    }
478

479
    /**
480
     * Get network portion of IP address as an integer
481
     *
482
     * @return int
483
     */
484
    public function getNetworkPortionInteger(): int
485
    {
486
        return $this->convertIpToInt($this->networkCalculation(self::FORMAT_QUADS, '.'));
41✔
487
    }
488

489
    /**
490
     * Get host portion of IP address as dotted quads: xxx.xxx.xxx.xxx
491
     *
492
     * @return string host portion as dotted quads
493
     */
494
    public function getHostPortion(): string
495
    {
496
        return $this->hostCalculation(self::FORMAT_QUADS, '.');
41✔
497
    }
498

499
    /**
500
     * Get host portion as array of quads: [xxx, xxx, xxx, xxx]
501
     *
502
     * @return string[] of four elements containing the four quads of the host portion
503
     */
504
    public function getHostPortionQuads(): array
505
    {
506
        return \explode('.', $this->hostCalculation(self::FORMAT_QUADS, '.'));
32✔
507
    }
508

509
    /**
510
     * Get host portion of IP address as hexadecimal
511
     *
512
     * @return string host portion in hex
513
     */
514
    public function getHostPortionHex(): string
515
    {
516
        return $this->hostCalculation(self::FORMAT_HEX);
41✔
517
    }
518

519
    /**
520
     * Get host portion of IP address as binary
521
     *
522
     * @return string host portion in binary
523
     */
524
    public function getHostPortionBinary(): string
525
    {
526
        return $this->hostCalculation(self::FORMAT_BINARY);
41✔
527
    }
528

529
    /**
530
     * Get host portion of IP address as an integer
531
     *
532
     * @return int
533
     */
534
    public function getHostPortionInteger(): int
535
    {
536
        return $this->convertIpToInt($this->hostCalculation(self::FORMAT_QUADS, '.'));
41✔
537
    }
538

539
    /**
540
     * Get all IP addresses
541
     *
542
     * @return \Generator|string[]|false[]
543
     */
544
    public function getAllIPAddresses(): \Generator
545
    {
546
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
25✔
547

548
        for ($ip = $startIp; $ip <= $endIp; $ip++) {
24✔
549
            yield $this->convertIpToDottedQuad($ip);
24✔
550
        }
551
    }
24✔
552

553
    /**
554
     * Get all host IP addresses
555
     * Removes broadcast and network address if they exist.
556
     *
557
     * @return \Generator|string[]|false[]
558
     *
559
     * @throws \RuntimeException if there is an error in the IP address range calculation
560
     */
561
    public function getAllHostIPAddresses(): \Generator
562
    {
563
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
22✔
564

565
        if ($this->getNetworkSize() < 31) {
21✔
566
            $startIp += 1;
18✔
567
            $endIp   -= 1;
18✔
568
        }
569

570
        for ($ip = $startIp; $ip <= $endIp; $ip++) {
21✔
571
            yield $this->convertIpToDottedQuad($ip);
21✔
572
        }
573
    }
21✔
574

575
    /**
576
     * Is the IP address in the subnet?
577
     *
578
     * @param string $ipAddressString
579
     *
580
     * @return bool
581
     */
582
    public function isIPAddressInSubnet($ipAddressString): bool
583
    {
584
        $ipAddress = \ip2long($ipAddressString);
82✔
585
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
82✔
586

587
        return $ipAddress >= $startIp && $ipAddress <= $endIp;
82✔
588
    }
589

590
    /**
591
     * Get the IPv4 Arpa Domain
592
     *
593
     * Reverse DNS lookups for IPv4 addresses use the special domain in-addr.arpa.
594
     * In this domain, an IPv4 address is represented as a concatenated sequence of four decimal numbers,
595
     * separated by dots, to which is appended the second level domain suffix .in-addr.arpa.
596
     *
597
     * The four decimal numbers are obtained by splitting the 32-bit IPv4 address into four octets and converting
598
     * each octet into a decimal number. These decimal numbers are then concatenated in the order:
599
     * least significant octet first (leftmost), to most significant octet last (rightmost).
600
     * It is important to note that this is the reverse order to the usual dotted-decimal convention for writing
601
     * IPv4 addresses in textual form.
602
     *
603
     * Ex: to do a reverse lookup of the IP address 8.8.4.4 the PTR record for the domain name 4.4.8.8.in-addr.arpa would be looked up.
604
     *
605
     * @link https://en.wikipedia.org/wiki/Reverse_DNS_lookup
606
     *
607
     * @return string
608
     */
609
    public function getIPv4ArpaDomain(): string
610
    {
611
        $reverseQuads = \implode('.', \array_reverse($this->quads));
17✔
612
        return $reverseQuads . '.in-addr.arpa';
17✔
613
    }
614

615
    /**
616
     * Get subnet calculations as an associated array
617
     *
618
     * @return mixed[] of subnet calculations
619
     */
620
    public function getSubnetArrayReport(): array
621
    {
622
        return $this->report->createArrayReport($this);
1✔
623
    }
624

625
    /**
626
     * Get subnet calculations as a JSON string
627
     *
628
     * @return string JSON string of subnet calculations
629
     *
630
     * @throws \RuntimeException if there is a JSON encode error
631
     */
632
    public function getSubnetJsonReport(): string
633
    {
634
        $json = $this->report->createJsonReport($this);
2✔
635

636
        if ($json === false) {
2✔
637
            throw new \RuntimeException('JSON report failure: ' . json_last_error_msg());
1✔
638
        }
639

640
        return $json;
1✔
641
    }
642

643
    /**
644
     * Print a report of subnet calculations
645
     */
646
    public function printSubnetReport(): void
647
    {
648
        $this->report->printReport($this);
1✔
649
    }
1✔
650

651
    /**
652
     * Print a report of subnet calculations
653
     *
654
     * @return string Subnet Calculator report
655
     */
656
    public function getPrintableReport(): string
657
    {
658
        return $this->report->createPrintableReport($this);
1✔
659
    }
660

661
    /**
662
     * String representation of a report of subnet calculations
663
     *
664
     * @return string
665
     */
666
    public function __toString(): string
667
    {
668
        return $this->report->createPrintableReport($this);
2✔
669
    }
670

671
    /* ************** *
672
     * PHP INTERFACES
673
     * ************** */
674

675
    /**
676
     * \JsonSerializable interface
677
     *
678
     * @return mixed[]
679
     */
680
    public function jsonSerialize(): array
681
    {
682
        return $this->report->createArrayReport($this);
1✔
683
    }
684

685
    /* ********************** *
686
     * PRIVATE IMPLEMENTATION
687
     * ********************** */
688

689
    /**
690
     * Calculate subnet mask
691
     *
692
     * @param  int $networkSize
693
     *
694
     * @return int
695
     */
696
    private function calculateSubnetMask(int $networkSize): int
697
    {
698
        return 0xFFFFFFFF << (32 - $networkSize);
743✔
699
    }
700

701
    /**
702
     * Calculate IP address for formatting
703
     *
704
     * @param string $format    sprintf format to determine if decimal, hex or binary
705
     * @param string $separator implode separator for formatting quads vs hex and binary
706
     *
707
     * @return string formatted IP address
708
     */
709
    private function ipAddressCalculation(string $format, string $separator = ''): string
710
    {
711
        return \implode($separator, array_map(
15✔
712
            function ($quad) use ($format) {
713
                return \sprintf($format, $quad);
15✔
714
            },
15✔
715
            $this->quads
15✔
716
        ));
717
    }
718

719
    /**
720
     * Subnet calculation
721
     *
722
     * @param string $format    sprintf format to determine if decimal, hex or binary
723
     * @param string $separator implode separator for formatting quads vs hex and binary
724
     *
725
     * @return string subnet
726
     */
727
    private function subnetCalculation(string $format, string $separator = ''): string
728
    {
729
        $maskQuads = [
730
            \sprintf($format, ($this->subnetMask >> 24) & 0xFF),
41✔
731
            \sprintf($format, ($this->subnetMask >> 16) & 0xFF),
41✔
732
            \sprintf($format, ($this->subnetMask >>  8) & 0xFF),
41✔
733
            \sprintf($format, ($this->subnetMask >>  0) & 0xFF),
41✔
734
        ];
735

736
        return implode($separator, $maskQuads);
41✔
737
    }
738

739
    /**
740
     * Calculate network portion for formatting
741
     *
742
     * @param string $format    sprintf format to determine if decimal, hex or binary
743
     * @param string $separator implode separator for formatting quads vs hex and binary
744
     *
745
     * @return string formatted subnet mask
746
     */
747
    private function networkCalculation(string $format, string $separator = ''): string
748
    {
749
        $networkQuads = [
750
            \sprintf($format, (int) $this->quads[0] & ($this->subnetMask >> 24)),
375✔
751
            \sprintf($format, (int) $this->quads[1] & ($this->subnetMask >> 16)),
375✔
752
            \sprintf($format, (int) $this->quads[2] & ($this->subnetMask >>  8)),
375✔
753
            \sprintf($format, (int) $this->quads[3] & ($this->subnetMask >>  0)),
375✔
754
        ];
755

756
        return implode($separator, $networkQuads);
375✔
757
    }
758

759
    /**
760
     * Calculate host portion for formatting
761
     *
762
     * @param string $format    sprintf format to determine if decimal, hex or binary
763
     * @param string $separator implode separator for formatting quads vs hex and binary
764
     *
765
     * @return string formatted subnet mask
766
     */
767
    private function hostCalculation(string $format, string $separator = ''): string
768
    {
769
        $networkQuads = [
770
            \sprintf($format, (int) $this->quads[0] & ~($this->subnetMask >> 24)),
41✔
771
            \sprintf($format, (int) $this->quads[1] & ~($this->subnetMask >> 16)),
41✔
772
            \sprintf($format, (int) $this->quads[2] & ~($this->subnetMask >>  8)),
41✔
773
            \sprintf($format, (int) $this->quads[3] & ~($this->subnetMask >>  0)),
41✔
774
        ];
775

776
        return implode($separator, $networkQuads);
41✔
777
    }
778

779
    /**
780
     * Calculate min host for formatting
781
     *
782
     * @param string $format    sprintf format to determine if decimal, hex or binary
783
     * @param string $separator implode separator for formatting quads vs hex and binary
784
     *
785
     * @return string formatted min host
786
     */
787
    private function minHostCalculation(string $format, string $separator = ''): string
788
    {
789
        $networkQuads = [
790
            \sprintf($format, (int) $this->quads[0] & ($this->subnetMask >> 24)),
105✔
791
            \sprintf($format, (int) $this->quads[1] & ($this->subnetMask >> 16)),
105✔
792
            \sprintf($format, (int) $this->quads[2] & ($this->subnetMask >>  8)),
105✔
793
            \sprintf($format, ((int) $this->quads[3] & ($this->subnetMask >> 0)) + 1),
105✔
794
        ];
795

796
        return implode($separator, $networkQuads);
105✔
797
    }
798

799
    /**
800
     * Calculate max host for formatting
801
     *
802
     * @param string $format    sprintf format to determine if decimal, hex or binary
803
     * @param string $separator implode separator for formatting quads vs hex and binary
804
     *
805
     * @return string formatted max host
806
     */
807
    private function maxHostCalculation(string $format, string $separator = ''): string
808
    {
809
        $networkQuads      = $this->getNetworkPortionQuads();
105✔
810
        $numberIpAddresses = $this->getNumberIPAddresses();
105✔
811

812
        $network_range_quads = [
813
            \sprintf($format, ((int) $networkQuads[0] & ($this->subnetMask >> 24)) + ((($numberIpAddresses - 1) >> 24) & 0xFF)),
105✔
814
            \sprintf($format, ((int) $networkQuads[1] & ($this->subnetMask >> 16)) + ((($numberIpAddresses - 1) >> 16) & 0xFF)),
105✔
815
            \sprintf($format, ((int) $networkQuads[2] & ($this->subnetMask >>  8)) + ((($numberIpAddresses - 1) >>  8) & 0xFF)),
105✔
816
            \sprintf($format, ((int) $networkQuads[3] & ($this->subnetMask >>  0)) + ((($numberIpAddresses - 1) >>  0) & 0xFE)),
105✔
817
        ];
818

819
        return implode($separator, $network_range_quads);
105✔
820
    }
821

822
    /**
823
     * Validate IP address and network
824
     *
825
     * @param string $ipAddress   IP address in dotted quads format
826
     * @param int    $networkSize Network size
827
     *
828
     * @throws \UnexpectedValueException IP or network size not valid
829
     */
830
    private function validateInputs(string $ipAddress, int $networkSize): void
831
    {
832
        if (!\filter_var($ipAddress, FILTER_VALIDATE_IP)) {
743✔
833
            throw new \UnexpectedValueException("IP address $ipAddress not valid.");
6✔
834
        }
835
        if (($networkSize < 1) || ($networkSize > 32)) {
743✔
836
            throw new \UnexpectedValueException("Network size $networkSize not valid.");
12✔
837
        }
838
    }
743✔
839

840
    /**
841
     * Get the start and end of the IP address range as ints
842
     *
843
     * @return int[] [start IP, end IP]
844
     */
845
    private function getIPAddressRangeAsInts(): array
846
    {
847
        [$startIp, $endIp] = $this->getIPAddressRange();
144✔
848
        $startIp = \ip2long($startIp);
144✔
849
        $endIp   = \ip2long($endIp);
144✔
850

851
        if ($startIp === false || $endIp === false) {
144✔
852
            throw new \RuntimeException('IP address range calculation failed: ' . \print_r($this->getIPAddressRange(), true));
2✔
853
        }
854

855
        return [$startIp, $endIp];
142✔
856
    }
857

858
    /**
859
     * Get the number of IP addresses in the given network size
860
     *
861
     * @param int $networkSize
862
     *
863
     * @return int Number of IP addresses
864
     */
865
    private function getNumberIPAddressesOfNetworkSize($networkSize): int
866
    {
867
        return \pow(2, (32 - $networkSize));
373✔
868
    }
869

870

871
    /**
872
     * Convert a dotted-quad IP address to an integer
873
     *
874
     * @param string $ipAddress Dotted-quad IP address
875
     *
876
     * @return int Integer representation of an IP address
877
     */
878
    private function convertIpToInt(string $ipAddress): int
879
    {
880
        $ipAsInt = \ip2long($ipAddress);
131✔
881
        if ($ipAsInt === false) {
131✔
882
            throw new \RuntimeException('Invalid IP address string. Could not convert dotted-quad string address to an integer: ' . $ipAddress);
1✔
883
        }
884
        return $ipAsInt;
130✔
885
    }
886

887
    /**
888
     * Convert an integer IP address to a dotted-quad IP string
889
     *
890
     * @param int $ipAsInt Integer representation of an IP address
891
     *
892
     * @return string Dotted-quad IP address
893
     */
894
    private function convertIpToDottedQuad(int $ipAsInt): string
895
    {
896
        $ipDottedQuad = \long2ip($ipAsInt);
61✔
897
        if ($ipDottedQuad == false) {
61✔
UNCOV
898
            throw new \RuntimeException('Invalid IP address integer. Could not convert integer address to dotted-quad string: ' . $ipAsInt);
×
899
        }
900
        return $ipDottedQuad;
61✔
901
    }
902
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc