// SNMP Object Identifier type.
// Copyright (C) 2008-2010 Malcolm Crowe, Lex Li, and other contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
namespace GSF.Net.Snmp
{
///
/// ObjectIdentifier type.
///
#if !NETFX_CORE
[TypeConverter(typeof(ObjectIdentifierConverter))]
#endif
[DataContract]
public sealed class ObjectIdentifier :
ISnmpData, IEquatable, IComparable, IComparable
{
private readonly uint[] _oid;
private readonly int _hashcode;
private readonly byte[] _length;
private byte[] _raw;
#region Constructor
///
/// Creates an instance from textual ID.
///
/// String in this format, "*.*.*.*".
public ObjectIdentifier(string text)
: this(Convert(text))
{
}
///
/// Creates an instance from numerical ID.
///
/// OID array
[CLSCompliant(false)]
public ObjectIdentifier(uint[] id)
{
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
if (id.Length == 0)
{
// IMPORTANT: convert to zeroDotZero.
id = new[] {0U, 0U};
}
if (id.Length < 2)
{
throw new ArgumentException("The length of the shortest identifier is two.", nameof(id));
}
if (id[0] > 2)
{
throw new ArgumentException("The first sub-identifier must be 0, 1, or 2.", nameof(id));
}
if (id[1] > 39)
{
throw new ArgumentException("The second sub-identifier must be less than 40.", nameof(id));
}
_oid = id;
unchecked
{
if (_hashcode == 0)
{
int hash = 0;
for (int i = _oid.Length - 1; i >= 0; i--)
{
hash ^= (int)_oid[i];
}
_hashcode = hash != 0 ? hash : 1; // Very unlikely that hash=0, but I prefer to foresee the case.
}
}
}
///
/// Creates an instance from raw bytes.
///
/// Raw bytes
internal ObjectIdentifier(byte[] raw)
: this(new Tuple(raw.Length, raw.Length.WritePayloadLength()), new MemoryStream(raw))
{
// IMPORTANT: for test project only.
}
///
/// Initializes a new instance of the class.
///
/// The length.
/// The stream.
public ObjectIdentifier(Tuple length, Stream stream)
{
if (length == null)
{
throw new ArgumentNullException(nameof(length));
}
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
_raw = new byte[length.Item1];
stream.Read(_raw, 0, length.Item1);
if (length.Item1 == 0)
{
throw new ArgumentException("Byte length cannot be 0.", nameof(length));
}
List result = new List { (uint)(_raw[0] / 40), (uint)(_raw[0] % 40) };
uint buffer = 0;
for (int i = 1; i < _raw.Length; i++)
{
if ((_raw[i] & 0x80) == 0)
{
result.Add(_raw[i] + (buffer << 7));
buffer = 0;
}
else
{
buffer <<= 7;
buffer += (uint)(_raw[i] & 0x7F);
}
}
_oid = result.ToArray();
_length = length.Item2;
unchecked
{
if (_hashcode == 0)
{
int hash = 0;
for (int i = _oid.Length - 1; i >= 0; i--)
{
hash ^= (int)_oid[i];
}
_hashcode = hash != 0 ? hash : 1; // Very unlikely that hash=0, but I prefer to foresee the case.
}
}
}
#endregion Constructor
///
/// Convers to numerical ID.
///
///
[CLSCompliant(false)]
public uint[] ToNumerical()
{
return _oid;
}
///
/// Compares the current object with another object of the same type.
///
/// An object to compare with this object.
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
/// Value
/// Meaning
/// Less than zero
/// This object is less than the parameter.
/// Zero
/// This object is equal to .
/// Greater than zero
/// This object is greater than .
///
public int Compare(ObjectIdentifier other)
{
return CompareTo(other);
}
///
/// Compares the current object with another object of the same type.
///
/// An object to compare with this object.
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
/// Value
/// Meaning
/// Less than zero
/// This object is less than the parameter.
/// Zero
/// This object is equal to .
/// Greater than zero
/// This object is greater than .
///
public int CompareTo(ObjectIdentifier other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
int shortest = (_oid.Length < other._oid.Length) ? _oid.Length : other._oid.Length;
for (int i = 0; i < shortest; i++)
{
if (_oid[i] > other._oid[i])
{
return 1;
}
if (_oid[i] < other._oid[i])
{
return -1;
}
}
return _oid.Length - other._oid.Length;
}
///
/// Returns a that represents this .
///
///
public override string ToString()
{
return Convert(_oid);
}
///
/// Converts unsigned integer array to dotted .
///
///
///
[CLSCompliant(false)]
public static string Convert(uint[] numerical)
{
if (numerical == null)
{
throw new ArgumentNullException(nameof(numerical));
}
StringBuilder result = new StringBuilder(numerical[0].ToString(CultureInfo.InvariantCulture));
for (int k = 1; k < numerical.Length; k++)
{
result.Append(".").Append(numerical[k].ToString(CultureInfo.InvariantCulture));
}
return result.ToString();
}
///
/// Converts dotted to unsigned integer array.
///
/// Dotted string.
/// Unsigned integer array.
[CLSCompliant(false)]
public static uint[] Convert(string dotted)
{
if (dotted == null)
{
throw new ArgumentNullException(nameof(dotted));
}
string[] parts = dotted.Split(new[] { '.' });
List result = new List();
foreach (string s in parts.Where(s => !string.IsNullOrEmpty(s)))
{
uint temp;
if (uint.TryParse(s, out temp))
{
result.Add(temp);
}
else
{
throw new ArgumentException($"Parameter {s} is out of 32 bit unsigned integer range.", nameof(dotted));
}
}
return result.ToArray();
}
///
/// Appends the bytes to .
///
/// The stream.
public void AppendBytesTo(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
stream.AppendBytes(TypeCode, _length, GetRaw());
}
private byte[] GetRaw()
{
if (_raw != null)
{
return _raw;
}
// TODO: improve here.
List temp = new List();
byte first = (byte)((40 * _oid[0]) + _oid[1]);
temp.Add(first);
for (int i = 2; i < _oid.Length; i++)
{
temp.AddRange(ConvertToBytes(_oid[i]));
}
return _raw = temp.ToArray();
}
private static IEnumerable ConvertToBytes(uint subIdentifier)
{
List result = new List { (byte)(subIdentifier & 0x7F) };
while ((subIdentifier = subIdentifier >> 7) > 0)
{
result.Add((byte)((subIdentifier & 0x7F) | 0x80));
}
result.Reverse();
return result;
}
///
/// Type code.
///
public SnmpType TypeCode
{
get
{
return SnmpType.ObjectIdentifier;
}
}
///
/// Determines whether the specified is equal to the current .
///
/// The to compare with the current .
/// true if the specified is equal to the current ; otherwise, false.
///
public override bool Equals(object obj)
{
return Equals(this, obj as ObjectIdentifier);
}
///
/// Indicates whether the current object is equal to another object of the same type.
///
/// An object to compare with this object.
/// true if the current object is equal to the parameter; otherwise, false.
///
public bool Equals(ObjectIdentifier other)
{
return Equals(this, other);
}
///
/// Serves as a hash function for a particular type.
///
/// A hash code for the current .
public override int GetHashCode()
{
return _hashcode;
}
///
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
///
/// An object to compare with this instance.
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings:
/// Value
/// Meaning
/// Less than zero
/// This instance is less than .
/// Zero
/// This instance is equal to .
/// Greater than zero
/// This instance is greater than .
///
///
/// is not the same type as this instance.
///
public int CompareTo(object obj)
{
ObjectIdentifier o = obj as ObjectIdentifier;
if (o == null)
{
throw new ArgumentException("obj is not the same type as this instance.", nameof(obj));
}
return CompareTo(o);
}
///
/// The equality operator.
///
/// Left object
/// Right object
///
/// Returns true if the values of its operands are equal, false otherwise.
public static bool operator ==(ObjectIdentifier left, ObjectIdentifier right)
{
return Equals(left, right);
}
///
/// The inequality operator.
///
/// Left object
/// Right object
///
/// Returns true if the values of its operands are not equal, false otherwise.
public static bool operator !=(ObjectIdentifier left, ObjectIdentifier right)
{
return !(left == right);
}
///
/// Implements the operator >.
///
/// The left.
/// The right.
/// The result of the operator.
public static bool operator >(ObjectIdentifier left, ObjectIdentifier right)
{
return left.CompareTo(right) > 0;
}
///
/// Implements the operator <.
///
/// The left.
/// The right.
/// The result of the operator.
public static bool operator <(ObjectIdentifier left, ObjectIdentifier right)
{
return left.CompareTo(right) < 0;
}
///
/// The comparison.
///
/// Left object
/// Right object
///
/// Returns true if the values of its operands are not equal, false otherwise.
private static bool Equals(IComparable left, ObjectIdentifier right)
{
object lo = left;
object ro = right;
if (lo == ro)
{
return true;
}
if (lo == null || ro == null)
{
return false;
}
return left.CompareTo(right) == 0;
}
///
/// Creates a new instance.
///
/// The numerical.
/// The extra.
///
[CLSCompliant(false)]
public static ObjectIdentifier Create(uint[] numerical, uint extra)
{
if (numerical == null)
{
throw new ArgumentNullException(nameof(numerical));
}
return new ObjectIdentifier(AppendTo(numerical, extra));
}
///
/// Appends an extra number to the array.
///
/// The original array.
/// The extra.
///
[CLSCompliant(false)]
public static uint[] AppendTo(uint[] original, uint extra)
{
if (original == null)
{
return new[] { extra };
}
// Old method with List dropped as it incurred two copies of the array (vs 1 for this method).
int length = original.Length;
uint[] tmp = new uint[length + 1];
Array.Copy(original, tmp, length);
tmp[length] = extra;
return tmp;
}
}
}