diff --git a/src/Lib/Enums/ParsedNetAddressStringType.cs b/src/Lib/Enums/ParsedNetAddressStringType.cs new file mode 100644 index 0000000..915e7f3 --- /dev/null +++ b/src/Lib/Enums/ParsedNetAddressStringType.cs @@ -0,0 +1,8 @@ +namespace SmallsOnline.Subnetting.Lib.Enums +{ + public enum ParsedNetAddressStringType + { + CidrNotation = 0, + SubnetMask = 1 + } +} \ No newline at end of file diff --git a/src/Lib/Models/BinaryNumber.cs b/src/Lib/Models/BinaryNumber.cs new file mode 100644 index 0000000..8186037 --- /dev/null +++ b/src/Lib/Models/BinaryNumber.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + /// + /// A representation of a binary number. + /// + public class BinaryNumber + { + /// + /// Create from a byte. + /// + /// A byte value. + public BinaryNumber(byte byteItem) + { + List bitValuesList = new(); + + BitArray bitArray = new(new byte[] { byteItem }); + for (int i = bitArray.Length - 1; i >= 0; i--) + { + bitValuesList.Add(new(i, bitArray[i])); + } + + bitValues = bitValuesList.ToArray(); + } + + /// + /// An array of the bits used for the binary number. + /// + public BitRepresentation[] BitValues + { + get => bitValues; + } + + private readonly BitRepresentation[] bitValues; + + /// + /// Get the amount of unused bits. + /// + /// The amount of unused bits. + public int GetUnusedBits() + { + int bitsUnused = 0; + + foreach (BitRepresentation bitItem in bitValues) + { + bitsUnused += bitItem.BitActualValue; + } + + return bitsUnused; + } + + /// + /// Get the amount of used bits. + /// + /// The amount of used bits. + public int GetUsedBits() + { + int bitsUsed = 255; + + foreach (BitRepresentation bitItem in bitValues) + { + bitsUsed -= bitItem.BitActualValue; + } + + return bitsUsed; + } + + /// + /// Return the amount of unused bits as a string. + /// + /// The amount of unused bits. + public override string ToString() + { + return $"{GetUnusedBits()}"; + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/BitRepresentation.cs b/src/Lib/Models/BitRepresentation.cs new file mode 100644 index 0000000..796746d --- /dev/null +++ b/src/Lib/Models/BitRepresentation.cs @@ -0,0 +1,62 @@ +using System; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + /// + /// A representation of a bit. + /// + public class BitRepresentation + { + /// + /// Create from the position of the bit and if it's on. + /// + /// The position of the bit. + /// The bit is on and used. + public BitRepresentation(int position, bool isOn) + { + bitPosition = position; + bitIsOn = isOn; + } + + /// + /// The position of the bit. + /// + public int BitPosition + { + get => bitPosition; + } + + /// + /// The value of the bit. + /// 2^bitPosition + /// + public int BitValue + { + get => (int)Math.Pow(2, bitPosition); + } + + /// + /// If the bit is on or not. + /// + public bool BitIsOn + { + get => bitIsOn; + } + + /// + /// The actual value of the bit if it's on or not. + /// + public int BitActualValue + { + get => bitIsOn ? BitValue : 0; + } + + private readonly int bitPosition; + private readonly bool bitIsOn; + + public override string ToString() + { + return $"{BitActualValue}"; + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/IPv4Subnet.cs b/src/Lib/Models/IPv4Subnet.cs new file mode 100644 index 0000000..493d68b --- /dev/null +++ b/src/Lib/Models/IPv4Subnet.cs @@ -0,0 +1,204 @@ +using System; +using System.Net; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + using SmallsOnline.Subnetting.Lib.Enums; + /// + /// A representation of an IPv4 subnet. + /// + public class IPv4Subnet + { + /// + /// Create from an IP address and CIDR mask. + /// + /// An IP address in a subnet. + /// The CIDR mask for the subnet. + public IPv4Subnet(IPAddress ipAddress, double cidr) + { + _subnetMask = new(cidr); + _networkAddress = GetSubnetBoundary(ipAddress, _subnetMask); + _broadcastAddress = GetBroadcastAddress(_networkAddress, _subnetMask); + _usableHostRange = new(_networkAddress, _broadcastAddress); + } + + /// + /// Create from an IP address and CIDR mask. + /// + /// An IP address in a subnet. + /// The subnet mask. + public IPv4Subnet(IPAddress ipAddress, IPv4SubnetMask subnetMask) + { + _subnetMask = subnetMask; + _networkAddress = GetSubnetBoundary(ipAddress, _subnetMask); + _broadcastAddress = GetBroadcastAddress(_networkAddress, _subnetMask); + _usableHostRange = new(_networkAddress, _broadcastAddress); + } + + /// + /// Create from an IP address and CIDR mask. + /// + /// An IP address in a subnet. + /// The subnet mask. + public IPv4Subnet(IPAddress ipAddress, IPAddress subnetMask) + { + _subnetMask = new(subnetMask.GetAddressBytes()); + _networkAddress = GetSubnetBoundary(ipAddress, _subnetMask); + _broadcastAddress = GetBroadcastAddress(_networkAddress, _subnetMask); + _usableHostRange = new(_networkAddress, _broadcastAddress); + } + + /// + /// Create from a string of a network. + /// For example: + /// 10.21.6.0/18 + /// + /// A network written in a string format. + public IPv4Subnet(string networkString) + { + ParsedNetAddressString parsedNetAddress = new(networkString); + + _subnetMask = parsedNetAddress.ParsedType switch + { + ParsedNetAddressStringType.SubnetMask => new(parsedNetAddress.SubnetMask.GetAddressBytes()), + _ => new(parsedNetAddress.CidrNotation) + }; + + _networkAddress = GetSubnetBoundary(parsedNetAddress.IPAddress, _subnetMask); + _broadcastAddress = GetBroadcastAddress(_networkAddress, _subnetMask); + _usableHostRange = new(_networkAddress, _broadcastAddress); + } + + /// + /// The network address of the subnet. + /// + public IPAddress NetworkAddress + { + get => _networkAddress; + } + + /// + /// The subnet mask of the subnet. + /// + public IPv4SubnetMask SubnetMask + { + get => _subnetMask; + } + + /// + /// The CIDR mask of the subnet. + /// + public double CidrMask + { + get => _subnetMask.CidrNotation; + } + + /// + /// The broadcast address of the subnet. + /// + public IPAddress BroadcastAddress + { + get => _broadcastAddress; + } + + /// + /// The total amount of addresses in the subnet. + /// + public double TotalAddresses + { + get => _subnetMask.TotalAddresses; + } + + /// + /// The total amount of usable addresses in the subnet. + /// + public double UsableAddresses + { + get => _subnetMask.TotalAddresses - 2; + } + + /// + /// The range of hosts available for use in the subnet. + /// + public UsableHostRange UsableHostRange + { + get => _usableHostRange; + } + + private readonly IPAddress _networkAddress; + private readonly IPv4SubnetMask _subnetMask; + private readonly IPAddress _broadcastAddress; + private readonly UsableHostRange _usableHostRange; + + /// + /// Display the subnet as a string. + /// + /// A string representation of the subnet with the network address and CIDR mask. + public override string ToString() + { + return $"{_networkAddress}/{CidrMask}"; + } + + /// + /// Gets the network address of the subnet from the supplied IP address and the subnet mask. + /// + /// The IP address in the subnet. + /// The subnet mask of the subnet. + /// The network address of the subnet. + private static IPAddress GetSubnetBoundary(IPAddress ipAddress, IPv4SubnetMask subnetMask) + { + // Get the byte arrays of the IP address and the subnet mask. + byte[] ipAddressBytes = ipAddress.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.ToBytes(); + + // Get the last used octet in the subnet mask. + Octet lastUsedOctet = subnetMask.GetLastUsedOctet(); + + // Create the byte array for generating the network address. + byte[] netAddressBytes = new byte[4]; + for (int i = 0; i < netAddressBytes.Length; i++) + { + if (i < lastUsedOctet.OctetPosition - 1) + { + // If the current loop count is less than the last used octet's position + // then set the current index for the network address to the same value as + // the IP address. + netAddressBytes[i] = ipAddressBytes[i]; + } + else + { + // If the current loop count is less than the last used octet's position + // then set the current index for the network address to the value of a + // bitwise AND operation of the IP address and the subnet mask. + netAddressBytes[i] = ipAddressBytes[i] &= subnetMaskBytes[i]; + } + } + + return new(netAddressBytes); + } + + /// + /// Gets the broadcast address of the subnet. + /// + /// The network address of the subnet. + /// The subnet mask of the subnet. + /// The broadcast address for the subnet. + private static IPAddress GetBroadcastAddress(IPAddress networkAddress, IPv4SubnetMask subnetMask) + { + // Create an empty byte array for the broadcast address. + byte[] broadcastAddressBytes = new byte[4]; + + // Get the bytes for the wildcard subnet mask. + byte[] networkAddressBytes = networkAddress.GetAddressBytes(); + byte[] wildcardBytes = subnetMask.WildcardMask.WildcardBytes; + + // Iterate through each index of the network address bytes and add the amount from the wildcard bytes. + for (int i = 0; i < broadcastAddressBytes.Length; i++) + { + broadcastAddressBytes[i] = (byte)(networkAddressBytes[i] + wildcardBytes[i]); + } + + return new(broadcastAddressBytes); + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/IPv4SubnetMask.cs b/src/Lib/Models/IPv4SubnetMask.cs new file mode 100644 index 0000000..0ea1c53 --- /dev/null +++ b/src/Lib/Models/IPv4SubnetMask.cs @@ -0,0 +1,245 @@ +using System; +using System.Net; +using System.Collections.Generic; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + /// + /// A representation of an IPv4 subnet mask. + /// + public class IPv4SubnetMask + { + /// + /// Create from a CIDR notation. + /// + /// The CIDR notation of the subnet mask. + public IPv4SubnetMask(double cidr) + { + wildcardMask = GetWildcardMask_FromCidr(cidr); + octets = GetSubnetMaskOctets_FromWildcardMask(wildcardMask); + cidrNotation = cidr; + totalAddresses = (int)GetMaxAddresses_FromCidr(cidr); + } + + /// + /// Create from a byte array. + /// + /// The byte array of the subnet mask. + public IPv4SubnetMask(byte[] bytes) + { + octets = GetOctets_FromBytes(bytes); + wildcardMask = new(bytes); + cidrNotation = GetCidrMask_FromSubnetMask(octets); + totalAddresses = (int)GetMaxAddresses_FromCidr(cidrNotation); + } + + /// + /// The octets of the subnet mask. + /// + public Octet[] Octets + { + get => octets; + } + + /// + /// The wildcard mask of the subnet mask. + /// + public IPv4WildcardMask WildcardMask + { + get => wildcardMask; + } + + /// + /// The CIDR notation of the subnet mask. + /// + public double CidrNotation + { + get => cidrNotation; + } + + /// + /// The total amount of addresses available from the subnet mask. + /// + public int TotalAddresses + { + get => totalAddresses; + } + + private readonly Octet[] octets; + private readonly IPv4WildcardMask wildcardMask; + private readonly double cidrNotation; + private readonly int totalAddresses; + + /// + /// Gets the last octet that was borrowed from. + /// + /// The last octet borrowed from. + public Octet GetLastUsedOctet() + { + List octetsList = new(octets); + + Octet lastUsedOctet = octetsList.FindAll((octetItem) => octetItem.BitsUsed == true)[0]; + + return lastUsedOctet; + } + + /// + /// Returns the subnet mask as a byte array. + /// + /// A byte array of the subnet mask. + public byte[] ToBytes() + { + byte[] byteArray = new byte[4]; + for (int i = 0; i < octets.Length; i++) + { + byteArray[i] = (byte)(byte.MinValue + octets[i].ToByte()); + } + + return byteArray; + } + + /// + /// Returns the subnet mask as an IPAddress. + /// + /// An IPAddress type. + public IPAddress ToIPAddress() + { + return new(ToBytes()); + } + + /// + /// Displays the subnet mask as a string. + /// + /// A string representation of the subnet mask. + public override string ToString() + { + return string.Join(".", ToBytes()); + } + + /// + /// Gets the value of the octets for the subnet mask from a byte array. + /// + /// A byte array of the subnet mask. + /// An array of the octets. + private static Octet[] GetOctets_FromBytes(byte[] bytes) + { + Octet[] _octets = new Octet[4]; + for (int i = 0; i < _octets.Length; i++) + { + _octets[i] = new(i + 1, bytes[i]); + } + + return _octets; + } + + /// + /// Calculate the total amount of addresses available from a CIDR notation. + /// + /// The CIDR notation of the subnet mask. + /// The total amount of addresses. + private static double GetMaxAddresses_FromCidr(double cidr) + { + // Get the maximum amount of addresses that can be used. + // Calculated by: 2^(32-cidrNotation) + return Math.Pow(2, GetBitBlock_FromCidr(cidr)); + } + + private static double GetBitBlock_FromCidr(double cidr) + { + return 32 - cidr; + } + + /// + /// Get the wildcard mask of the subnet mask from a CIDR notation. + /// + /// The CIDR notation of the subnet mask. + /// The wildcard mask of the subnet mask. + private static IPv4WildcardMask GetWildcardMask_FromCidr(double cidr) + { + byte[] wildcardByteArray = new byte[4]; + + double maxAddresses = GetMaxAddresses_FromCidr(cidr); + + // Get the amount of bytes filled. + // This is calculated by getting the log of the max addresses from the base of 256 and rounding to the lowest number. + double bytesFilled = Math.Floor(Math.Log(maxAddresses, 256)); + + // Get the amount of bits used. + // This is calculated by: maxAddresses / 256^bytesFilled + double bitsUsed = maxAddresses / Math.Pow(256, bytesFilled); + + // Determine the position to fill the amount of bits used. + int byteArrayPosition = bytesFilled switch + { + 0 => 3, + 1 => 2, + 2 => 1, + 3 => 0, + _ => 0 + }; + + // Set the position in the wildcard byte array to the amount of bits used. + // The value of 'bitsUsed' is subtracted by 1, since a byte is denoted from 0 - 255. + wildcardByteArray[byteArrayPosition] = (byte)(bitsUsed - 1); + + // If needed, fill the remaining bytes to the right of the previously modified byte position. + for (int i = byteArrayPosition + 1; i < wildcardByteArray.Length; i++) + { + wildcardByteArray[i] = byte.MaxValue; + } + + return new(wildcardByteArray, true); + } + + /// + /// Get the octets of a subnet mask from the wildcard mask. + /// + /// The wildcard mask of a subnet mask. + /// An array of octets of the subnet mask. + private static Octet[] GetSubnetMaskOctets_FromWildcardMask(IPv4WildcardMask _wildcardMask) + { + Octet[] _octets = new Octet[4]; + for (int i = _wildcardMask.WildcardBytes.Length - 1; i >= 0; i--) + { + _octets[i] = new(i + 1, (byte)(byte.MaxValue - _wildcardMask.WildcardBytes[i])); + } + + return _octets; + } + + /// + /// Get the CIDR notation from the subnet mask's octets. + /// + /// The octets of the subnet mask. + /// The CIDR notation of the subnet mask. + private static double GetCidrMask_FromSubnetMask(Octet[] _octets) + { + List bitList = new(); + foreach (Octet octetItem in _octets) + { + bitList.AddRange(octetItem.BinaryValue.BitValues); + } + int countOfUsedBits = bitList.FindAll((item) => item.BitIsOn == false).Count; + + return 32 - countOfUsedBits; + } + + /// + /// Calculate the total amount of addresses from the octets of a subnet mask. + /// + /// The octets of the subnet mask. + /// The total amount of addresses. + private static int GetTotalAddresses_FromSubnetMask(Octet _octet) + { + // Note: + // ------ + // This is still bugging out. + // 255.255.255.0 (/24), 255.255.0 (/16), and 255.0.0.0 (/8) are still returning 0. + // Since I've gotten the CIDR notation being calculated already, I've switched to using + // the GetMaxAddresses_FromCidr() method instead. Will keep looking into fixing this. + // ------ + + return (int)(Math.Pow(256, _octet.OctetPosition - 1) * _octet.BinaryValue.GetUnusedBits()); + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/IPv4WildcardMask.cs b/src/Lib/Models/IPv4WildcardMask.cs new file mode 100644 index 0000000..9469f0d --- /dev/null +++ b/src/Lib/Models/IPv4WildcardMask.cs @@ -0,0 +1,50 @@ +using System; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + /// + /// A representation of the wildcard mask of an IPv4 subnet mask. + /// + public class IPv4WildcardMask + { + /// + /// Generate the wildcard mask from a byte array. + /// + /// A byte array of a subnet mask or wildcard mask. + /// Whether the input byte array has already been calculated. + public IPv4WildcardMask(byte[] bytes, bool isAlreadyCalculated = false) + { + if (isAlreadyCalculated) + { + wildcardBytes = bytes; + } + else + { + wildcardBytes = new byte[4]; + for (int i = 0; i < bytes.Length; i++) + { + wildcardBytes[i] = (byte)(byte.MaxValue - bytes[i]); + } + } + } + + /// + /// The bytes of the wildcard mask. + /// + public byte[] WildcardBytes + { + get => wildcardBytes; + } + + private readonly byte[] wildcardBytes; + + /// + /// Displays the wildcard mask as a string. + /// + /// A string representation of the wildcard mask. + public override string ToString() + { + return string.Join(".", wildcardBytes); + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/Octet.cs b/src/Lib/Models/Octet.cs new file mode 100644 index 0000000..ca997da --- /dev/null +++ b/src/Lib/Models/Octet.cs @@ -0,0 +1,66 @@ +using System; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + /// + /// A representation of an octet. + /// + public class Octet + { + /// + /// Create with the position of the octet and it's byte value. + /// + /// The position of the octet. + /// The byte value of the octet. + public Octet(int position, byte byteItem) + { + octetPosition = position; + binaryValue = new(byteItem); + } + + /// + /// The position of the octet. + /// + public int OctetPosition + { + get => octetPosition; + } + + /// + /// The binary value of the octet. + /// + public BinaryNumber BinaryValue + { + get => binaryValue; + } + + /// + /// If bits have been used. + /// + public bool BitsUsed + { + get => binaryValue.GetUsedBits() > 0; + } + + private readonly int octetPosition; + private readonly BinaryNumber binaryValue; + + /// + /// Return the byte value of the octet. + /// + /// The byte value of the octet. + public byte ToByte() + { + return Convert.ToByte(binaryValue.GetUnusedBits()); + } + + /// + /// Returns the value of the octet as a string. + /// + /// The value of the octet as a string. + public override string ToString() + { + return $"{binaryValue.GetUnusedBits()}"; + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/ParsedNetAddressString.cs b/src/Lib/Models/ParsedNetAddressString.cs new file mode 100644 index 0000000..e82fd13 --- /dev/null +++ b/src/Lib/Models/ParsedNetAddressString.cs @@ -0,0 +1,79 @@ +using System; +using System.Net; +using System.Text.RegularExpressions; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + using SmallsOnline.Subnetting.Lib.Enums; + /// + /// A representation of a parsed network address. + /// + public class ParsedNetAddressString + { + /// + /// Create from a network string. + /// + /// The string of the network. + public ParsedNetAddressString(string netAddressString) + { + Initialize(netAddressString); + } + + /// + /// The parsed IP address. + /// + public IPAddress IPAddress + { + get => _ipAddress; + } + + /// + /// The parsed CIDR notation. + /// + public double CidrNotation + { + get => _cidrNotation; + } + + /// + /// The parsed subnet mask. + /// + public IPAddress SubnetMask + { + get => _subnetMask; + } + + public ParsedNetAddressStringType ParsedType + { + get => _parsedType; + } + + private IPAddress _ipAddress; + private double _cidrNotation; + private IPAddress _subnetMask; + private ParsedNetAddressStringType _parsedType; + + /// + /// Parses the string and sets the properties for the IP address and CIDR notation. + /// + /// The string of the network. + private void Initialize(string netAddressString) + { + Regex netAddressRegex = new(@"^(?'netAddress'(?:\d{1,3}(?:\.|)){4})(?:(?:\/)(?'cidrNotation'\d{1,2})|(?:\/|\s)(?'subnetMask'(?:\d{1,3}(?:\.|)){4}))$"); + Match netAddressMatch = netAddressRegex.Match(netAddressString); + + _ipAddress = IPAddress.Parse(netAddressMatch.Groups["netAddress"].Value); + + if (netAddressMatch.Groups["cidrNotation"].Success) + { + _cidrNotation = Convert.ToDouble(netAddressMatch.Groups["cidrNotation"].Value); + _parsedType = ParsedNetAddressStringType.CidrNotation; + } + else if (netAddressMatch.Groups["subnetMask"].Success) + { + _subnetMask = IPAddress.Parse(netAddressMatch.Groups["subnetMask"].Value); + _parsedType = ParsedNetAddressStringType.SubnetMask; + } + } + } +} \ No newline at end of file diff --git a/src/Lib/Models/UsableHostRange.cs b/src/Lib/Models/UsableHostRange.cs new file mode 100644 index 0000000..8686f66 --- /dev/null +++ b/src/Lib/Models/UsableHostRange.cs @@ -0,0 +1,54 @@ +using System; +using System.Net; + +namespace SmallsOnline.Subnetting.Lib.Models +{ + /// + /// A representation of the usable host range of a subnet. + /// + public class UsableHostRange + { + /// + /// Create from the network and broadcast address. + /// + /// The network address of the subnet. + /// The broadcast address of the subnet. + public UsableHostRange(IPAddress netAddress, IPAddress broadcastAddress) + { + Initialize(netAddress, broadcastAddress); + } + + /// + /// The first usable host address in the subnet. + /// + public IPAddress FirstUsableHostAddress + { + get => _firstUsableHostAddress; + } + + /// + /// The last usable host address in the subnet. + /// + public IPAddress LastUsableHostAddress + { + get => _lastUsableHostAddress; + } + + private IPAddress _firstUsableHostAddress; + private IPAddress _lastUsableHostAddress; + + private void Initialize(IPAddress netAddress, IPAddress broadcastAddress) + { + byte[] netAddressBytes = netAddress.GetAddressBytes(); + byte[] broadcastAddressBytes = broadcastAddress.GetAddressBytes(); + + _firstUsableHostAddress = new(new byte[] { netAddressBytes[0], netAddressBytes[1], netAddressBytes[2], (byte)(netAddressBytes[3] + 1) }); + _lastUsableHostAddress = new(new byte[] { broadcastAddressBytes[0], broadcastAddressBytes[1], broadcastAddressBytes[2], (byte)(broadcastAddressBytes[3] - 1) }); + } + + public override string ToString() + { + return $"{_firstUsableHostAddress} - {_lastUsableHostAddress}"; + } + } +} \ No newline at end of file