diff --git a/LibreNMS/Enum/IntegerType.php b/LibreNMS/Enum/IntegerType.php new file mode 100644 index 0000000000..c9c8895b34 --- /dev/null +++ b/LibreNMS/Enum/IntegerType.php @@ -0,0 +1,60 @@ +. + * + * @link https://www.librenms.org + * + * @copyright 2023 Tony Murray + * @author Tony Murray + */ + +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, + }; + } +} diff --git a/LibreNMS/Modules/Xdsl.php b/LibreNMS/Modules/Xdsl.php index 81b60f5c9d..63d3995b26 100644 --- a/LibreNMS/Modules/Xdsl.php +++ b/LibreNMS/Modules/Xdsl.php @@ -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; } diff --git a/LibreNMS/Util/Number.php b/LibreNMS/Util/Number.php index b2a60bbe67..e66ea75686 100644 --- a/LibreNMS/Util/Number.php +++ b/LibreNMS/Util/Number.php @@ -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; } } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 55494c781c..d2c58ab277 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -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); } }