New utility Number::constrainInteger() (#15663)

* New utility Number::constrainInteger()
Fixes a bug with Number::unsignedAsSigned() and implements signed support as well.

* cleanup

* Apply fixes from StyleCI

* Remove default cases

---------

Co-authored-by: StyleCI Bot <bot@styleci.io>
This commit is contained in:
Tony Murray 2023-12-20 09:21:40 -06:00 committed by GitHub
parent b4a61636c1
commit 6bea8cffa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 18 deletions

View File

@ -0,0 +1,60 @@
<?php
/**
* IntegerType.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*
* @copyright 2023 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Enum;
enum IntegerType
{
case int8;
case int16;
case int32;
case int64;
case uint8;
case uint16;
case uint32;
case uint64;
public function maxValue(): int
{
return match ($this) {
self::int8 => 127,
self::int16 => 32767,
self::int32 => 2147483647,
self::int64 => 4611686018427387903,
self::uint8 => 255,
self::uint16 => 65535,
self::uint32 => 4294967295,
self::uint64 => 9223372036854775807,
};
}
public function isSigned(): bool
{
return match ($this) {
self::int8,self::int16,self::int32,self::int64 => true,
self::uint8,self::uint16,self::uint32,self::uint64 => false,
};
}
}

View File

@ -32,6 +32,7 @@ use App\Models\PortVdsl;
use App\Observers\ModuleModelObserver;
use Illuminate\Support\Collection;
use LibreNMS\DB\SyncsModels;
use LibreNMS\Enum\IntegerType;
use LibreNMS\Interfaces\Data\DataStorageInterface;
use LibreNMS\Interfaces\Module;
use LibreNMS\OS;
@ -139,7 +140,7 @@ class Xdsl implements Module
if (isset($data[$oid])) {
if ($oid == 'adslAtucCurrOutputPwr') {
// workaround Cisco Bug CSCvj53634
$data[$oid] = Number::unsignedAsSigned($data[$oid]);
$data[$oid] = Number::constrainInteger($data[$oid], IntegerType::int32);
}
$data[$oid] = $data[$oid] / 10;
}

View File

@ -25,6 +25,8 @@
namespace LibreNMS\Util;
use LibreNMS\Enum\IntegerType;
class Number
{
public static function formatBase($value, $base = 1000, $round = 2, $sf = 3, $suffix = 'B')
@ -176,21 +178,36 @@ class Number
return (int) ($number * (1024 ** $exponent));
}
public static function unsignedAsSigned(int $unsignedValue, int $bitLength = 32): int
public static function constrainInteger(int $value, IntegerType $integerSize): int
{
// Maximum representable value for signed integer
$maxSignedValue = pow(2, $bitLength - 1) - 1;
if ($integerSize->isSigned()) {
$maxSignedValue = $integerSize->maxValue();
// Check if the unsigned value is greater than the maximum representable unsigned value
// If so, convert it to its two's complement representation
if ($unsignedValue > $maxSignedValue) {
if ($unsignedValue > 2 ** $bitLength - 1) {
throw new \InvalidArgumentException('Unsigned value exceeds the maximum representable value of the give bit length: ' . $bitLength);
if ($value > $maxSignedValue) {
$signedValue = $value - $maxSignedValue * 2 - 2;
// if conversion was successfull, the number will still be in the valid range
if ($signedValue > $maxSignedValue) {
throw new \InvalidArgumentException('Unsigned value exceeds the maximum representable value of ' . $integerSize->name);
}
return $signedValue;
}
return $unsignedValue - ($maxSignedValue + 1) * 2;
return $value;
}
return $unsignedValue;
// unsigned check if value is negative
if ($value < 0) {
$unsignedValue = $value + $integerSize->maxValue() - 1;
if ($unsignedValue < 0) {
throw new \InvalidArgumentException('Unsigned value exceeds the minimum representable value of ' . $integerSize->name);
}
return $unsignedValue;
}
return $value;
}
}

View File

@ -26,6 +26,7 @@
namespace LibreNMS\Tests;
use LibreNMS\Device\YamlDiscovery;
use LibreNMS\Enum\IntegerType;
use LibreNMS\Util\Number;
use LibreNMS\Util\Time;
@ -129,18 +130,26 @@ class FunctionsTest extends TestCase
public function testNumberAsUnsigned()
{
$this->assertSame(42, Number::unsignedAsSigned('42')); /** @phpstan-ignore-line */
$this->assertSame(2147483647, Number::unsignedAsSigned(2147483647));
$this->assertSame(-2147483648, Number::unsignedAsSigned(2147483648));
$this->assertSame(-2147483647, Number::unsignedAsSigned(2147483649));
$this->assertSame(-1, Number::unsignedAsSigned(4294967295));
$this->assertSame(42, Number::constrainInteger('42', IntegerType::int32)); /** @phpstan-ignore-line */
$this->assertSame(2147483647, Number::constrainInteger(2147483647, IntegerType::int32));
$this->assertSame(-2147483648, Number::constrainInteger(2147483648, IntegerType::int32));
$this->assertSame(-2147483647, Number::constrainInteger(2147483649, IntegerType::int32));
$this->assertSame(-1, Number::constrainInteger(4294967295, IntegerType::int32));
$this->assertSame(-3757, Number::constrainInteger(61779, IntegerType::int16));
$this->assertSame(0, Number::constrainInteger(0, IntegerType::uint32));
$this->assertSame(42, Number::constrainInteger(42, IntegerType::uint32));
$this->assertSame(4294967252, Number::constrainInteger(-42, IntegerType::uint32));
$this->assertSame(2147483648, Number::constrainInteger(-2147483646, IntegerType::uint32));
$this->assertSame(2147483647, Number::constrainInteger(-2147483647, IntegerType::uint32));
$this->assertSame(2147483646, Number::constrainInteger(-2147483648, IntegerType::uint32));
$this->assertSame(2147483645, Number::constrainInteger(-2147483649, IntegerType::uint32));
}
public function testNumberAsUnsignedValueExceedsMaxUnsignedValue()
{
$this->expectException(\InvalidArgumentException::class);
// Exceeds the maximum representable value for a 32-bit unsigned integer
Number::unsignedAsSigned(4294967296, 32);
// Exceeds the maximum representable value for a 16-bit unsigned integer
Number::constrainInteger(4294967296, IntegerType::int16);
}
}