Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefixBits to IpAddress #420

Merged
merged 3 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ fileOverride {
}
}

docstrings.wrap = "no"

3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ lazy val core = crossProject(JVMPlatform, JSPlatform)
libraryDependencies ++= Seq(
"org.typelevel" %%% "literally" % "1.1.0",
"org.typelevel" %%% "cats-core" % "2.8.0",
"org.typelevel" %%% "cats-effect-kernel" % "3.3.14"
"org.typelevel" %%% "cats-effect-kernel" % "3.3.14",
"org.scalacheck" %%% "scalacheck" % "1.16.0" % Test
)
)

Expand Down
63 changes: 35 additions & 28 deletions shared/src/main/scala/com/comcast/ip4s/Cidr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,41 @@ final class Cidr[+A <: IpAddress] private (val address: A, val prefixBits: Int)

/** Returns the routing mask.
*
* @example
* {{{ scala> Cidr(ipv4"10.11.12.13", 8).mask res0: Ipv4Address = 255.0.0.0 scala> Cidr(ipv6"2001:db8:abcd:12::",
* 96).mask res1: Ipv6Address = ffff:ffff:ffff:ffff:ffff:ffff:: }}}
* @example {{{
* scala> Cidr(ipv4"10.11.12.13", 8).mask
* res0: Ipv4Address = 255.0.0.0
* scala> Cidr(ipv6"2001:db8:abcd:12::", 96).mask
* res1: Ipv6Address = ffff:ffff:ffff:ffff:ffff:ffff::
* }}}
*/
def mask: A = address.transform(_ => Ipv4Address.mask(prefixBits), _ => Ipv6Address.mask(prefixBits))

/** Returns the routing prefix.
*
* Note: the routing prefix also serves as the first address in the range described by this CIDR.
*
* @example
* {{{ scala> Cidr(ipv4"10.11.12.13", 8).prefix res0: Ipv4Address = 10.0.0.0 scala> Cidr(ipv6"2001:db8:abcd:12::",
* 96).prefix res1: Ipv6Address = 2001:db8:abcd:12:: scala> Cidr(ipv6"2001:db8:abcd:12::", 32).prefix res2:
* Ipv6Address = 2001:db8:: }}}
* @example {{{
* scala> Cidr(ipv4"10.11.12.13", 8).prefix
* res0: Ipv4Address = 10.0.0.0
* scala> Cidr(ipv6"2001:db8:abcd:12::", 96).prefix
* res1: Ipv6Address = 2001:db8:abcd:12::
* scala> Cidr(ipv6"2001:db8:abcd:12::", 32).prefix
* res2: Ipv6Address = 2001:db8::
* }}}
*/
def prefix: A =
address.transform(_.masked(Ipv4Address.mask(prefixBits)), _.masked(Ipv6Address.mask(prefixBits)))

/** Returns the last address in the range described by this CIDR.
*
* @example
* {{{ scala> Cidr(ipv4"10.11.12.13", 8).last res0: Ipv4Address = 10.255.255.255 scala>
* Cidr(ipv6"2001:db8:abcd:12::", 96).last res1: Ipv6Address = 2001:db8:abcd:12::ffff:ffff scala>
* Cidr(ipv6"2001:db8:abcd:12::", 32).last res2: Ipv6Address = 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff }}}
* @example {{{
* scala> Cidr(ipv4"10.11.12.13", 8).last
* res0: Ipv4Address = 10.255.255.255
* scala> Cidr(ipv6"2001:db8:abcd:12::", 96).last
* res1: Ipv6Address = 2001:db8:abcd:12::ffff:ffff
* scala> Cidr(ipv6"2001:db8:abcd:12::", 32).last
* res2: Ipv6Address = 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff
* }}}
*/
def last: A =
address.transform(_.maskedLast(Ipv4Address.mask(prefixBits)), _.maskedLast(Ipv6Address.mask(prefixBits)))
Expand All @@ -70,11 +81,17 @@ final class Cidr[+A <: IpAddress] private (val address: A, val prefixBits: Int)

/** Returns a predicate which tests if the supplied address is in the range described by this CIDR.
*
* @example
* {{{ scala> Cidr(ipv4"10.11.12.13", 8).contains(ipv4"10.100.100.100") res0: Boolean = true scala>
* Cidr(ipv4"10.11.12.13", 8).contains(ipv4"11.100.100.100") res1: Boolean = false scala> val x =
* Cidr(ipv6"2001:db8:abcd:12::", 96).contains scala> x(ipv6"2001:db8:abcd:12::5") res2: Boolean = true scala>
* x(ipv6"2001:db8::") res3: Boolean = false }}}
* @example {{{
* scala> Cidr(ipv4"10.11.12.13", 8).contains(ipv4"10.100.100.100")
* res0: Boolean = true
* scala> Cidr(ipv4"10.11.12.13", 8).contains(ipv4"11.100.100.100")
* res1: Boolean = false
* scala> val x = Cidr(ipv6"2001:db8:abcd:12::", 96).contains
* scala> x(ipv6"2001:db8:abcd:12::5")
* res2: Boolean = true
* scala> x(ipv6"2001:db8::")
* res3: Boolean = false
* }}}
*/
def contains[AA >: A <: IpAddress]: AA => Boolean = {
val start = prefix
Expand Down Expand Up @@ -114,18 +131,8 @@ object Cidr {
/** Constructs a CIDR from the supplied IP address and netmask. The number of leading 1 bits in the netmask are used
* as the prefix bits for the CIDR.
*/
def fromIpAndMask[A <: IpAddress](address: A, mask: A): Cidr[A] = {
import java.lang.Long.numberOfLeadingZeros
val prefixBits = mask.fold(
m => numberOfLeadingZeros(~(m.toLong | 0xffffffff00000000L)) - 32,
m => {
val upper = (m.toBigInt >> 64).toLong
val upperNum = numberOfLeadingZeros(~upper)
if (upperNum == 64) upperNum + numberOfLeadingZeros(~(m.toBigInt.toLong)) else upperNum
}
)
apply(address, prefixBits)
}
def fromIpAndMask[A <: IpAddress](address: A, mask: A): Cidr[A] =
address / mask.prefixBits

/** Constructs a CIDR from a string of the form `ip/prefixBits`. */
def fromString(value: String): Option[Cidr[IpAddress]] = fromStringGeneral(value, IpAddress.fromString)
Expand Down
68 changes: 52 additions & 16 deletions shared/src/main/scala/com/comcast/ip4s/Host.scala
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,28 @@ sealed abstract class IpAddress extends IpAddressPlatform with Host with Seriali
/** Constructs a [[Cidr]] address from this address. */
def /(prefixBits: Int): Cidr[this.type] = Cidr(this, prefixBits)

/** Returns the number of leading ones in this address. When this address is a netmask, the result can be used to
* create a CIDR.
*
* @example {{{
* scala> val address = ipv4"192.168.1.25"
* scala> val netmask = ipv4"255.255.0.0"
* scala> (address / netmask.prefixBits): Cidr[Ipv4Address]
* res0: Cidr[Ipv4Address] = 192.168.1.25/16
* }}}
*/
def prefixBits: Int = {
import java.lang.Long.numberOfLeadingZeros
fold(
m => numberOfLeadingZeros(~(m.toLong | 0xffffffff00000000L)) - 32,
m => {
val upper = (m.toBigInt >> 64).toLong
val upperNum = numberOfLeadingZeros(~upper)
if (upperNum == 64) upperNum + numberOfLeadingZeros(~(m.toBigInt.toLong)) else upperNum
}
)
}

/** Gets the IP address after this address, with overflow from the maximum value to the minimum value. */
def next: IpAddress

Expand Down Expand Up @@ -312,16 +334,20 @@ final class Ipv4Address private (protected val bytes: Array[Byte]) extends IpAdd

/** Applies the supplied mask to this address.
*
* @example
* {{{scala> ipv4"192.168.29.1".masked(ipv4"255.255.0.0") res0: Ipv4Address = 192.168.0.0}}}
* @example {{{
* scala> ipv4"192.168.29.1".masked(ipv4"255.255.0.0")
* res0: Ipv4Address = 192.168.0.0
* }}}
*/
def masked(mask: Ipv4Address): Ipv4Address =
Ipv4Address.fromLong(toLong & mask.toLong)

/** Computes the last address in the network identified by applying the supplied mask to this address.
*
* @example
* {{{scala> ipv4"192.168.29.1".maskedLast(ipv4"255.255.0.0") res0: Ipv4Address = 192.168.255.255}}}
* @example {{{
* scala> ipv4"192.168.29.1".maskedLast(ipv4"255.255.0.0")
* res0: Ipv4Address = 192.168.255.255
* }}}
*/
def maskedLast(mask: Ipv4Address): Ipv4Address =
Ipv4Address.fromLong(toLong & mask.toLong | ~mask.toLong)
Expand Down Expand Up @@ -403,8 +429,10 @@ object Ipv4Address extends Ipv4AddressCompanionPlatform {

/** Computes a mask by setting the first / left-most `n` bits high.
*
* @example
* {{{scala> Ipv4Address.mask(16) res0: Ipv4Address = 255.255.0.0}}}
* @example {{{
* scala> Ipv4Address.mask(16)
* res0: Ipv4Address = 255.255.0.0
* }}}
*/
def mask(bits: Int): Ipv4Address = {
val b = if (bits < 0) 0 else if (bits > 32) 32 else bits
Expand Down Expand Up @@ -488,9 +516,12 @@ final class Ipv6Address private (protected val bytes: Array[Byte]) extends IpAdd
*
* This format is described in RFC4291 section 2.2.3.
*
* @example
* {{{ scala> ipv6"::7f00:1".toMixedString res0: String = ::127.0.0.1 scala>
* ipv6"ff3b:1234::ffab:7f00:1".toMixedString res0: String = ff3b:1234::ffab:127.0.0.1 }}}
* @example {{{
* scala> ipv6"::7f00:1".toMixedString
* res0: String = ::127.0.0.1
* scala> ipv6"ff3b:1234::ffab:7f00:1".toMixedString
* res0: String = ff3b:1234::ffab:127.0.0.1
* }}}
*/
def toMixedString: String = {
val bytes = toBytes
Expand Down Expand Up @@ -530,17 +561,20 @@ final class Ipv6Address private (protected val bytes: Array[Byte]) extends IpAdd

/** Applies the supplied mask to this address.
*
* @example
* {{{scala> ipv6"ff3b::1".masked(ipv6"fff0::") res0: Ipv6Address = ff30::}}}
* @example {{{
* scala> ipv6"ff3b::1".masked(ipv6"fff0::")
* res0: Ipv6Address = ff30::
* }}}
*/
def masked(mask: Ipv6Address): Ipv6Address =
Ipv6Address.fromBigInt(toBigInt & mask.toBigInt)

/** Computes the last address in the network identified by applying the supplied mask to this address.
*
* @example
* {{{ scala> ipv6"ff3b::1".maskedLast(ipv6"fff0::") res0: Ipv6Address = ff3f:ffff:ffff:ffff:ffff:ffff:ffff:ffff
* }}}
* @example {{{
* scala> ipv6"ff3b::1".maskedLast(ipv6"fff0::")
* res0: Ipv6Address = ff3f:ffff:ffff:ffff:ffff:ffff:ffff:ffff
* }}}
*/
def maskedLast(mask: Ipv6Address): Ipv6Address =
Ipv6Address.fromBigInt(toBigInt & mask.toBigInt | ~mask.toBigInt)
Expand Down Expand Up @@ -727,8 +761,10 @@ object Ipv6Address extends Ipv6AddressCompanionPlatform {

/** Computes a mask by setting the first / left-most `n` bits high.
*
* @example
* {{{scala> Ipv6Address.mask(32) res0: Ipv6Address = ffff:ffff::}}}
* @example {{{
* scala> Ipv6Address.mask(32)
* res0: Ipv6Address = ffff:ffff::
* }}}
*/
def mask(bits: Int): Ipv6Address = {
val b = if (bits < 0) 0 else if (bits > 128) 128 else bits
Expand Down