Skip to content

Commit

Permalink
minimal Cidr.Strict
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnhoekstra committed Oct 17, 2023
1 parent fda88a3 commit fe556b2
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 1 deletion.
27 changes: 26 additions & 1 deletion shared/src/main/scala/com/comcast/ip4s/Cidr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import scala.util.Try
import scala.util.hashing.MurmurHash3

import cats.{Order, Show}
import com.comcast.ip4s.Cidr.Strict

/** Classless Inter-Domain Routing address, which represents an IP address and its routing prefix.
*
Expand All @@ -28,10 +29,17 @@ import cats.{Order, Show}
* @param prefixBits
* number of leading 1s in the routing mask
*/
final class Cidr[+A <: IpAddress] private (val address: A, val prefixBits: Int) extends Product with Serializable {
sealed class Cidr[+A <: IpAddress] protected (val address: A, val prefixBits: Int) extends Product with Serializable {
def copy[AA >: A <: IpAddress](address: AA = this.address, prefixBits: Int = this.prefixBits): Cidr[AA] =
Cidr[AA](address, prefixBits)

/** Returns a normalized cidr range, where the address is truncated to the prefix, so that the returned range
* is (and prints as) a spec-valid cidr range, with no bits outside the routing mask set.
*
* @return a normalized cidr range
*/
def normalized: Strict[A] = Cidr.Strict(this)

/** Returns the routing mask.
*
* @example {{{
Expand Down Expand Up @@ -118,6 +126,23 @@ final class Cidr[+A <: IpAddress] private (val address: A, val prefixBits: Int)

object Cidr {

/** A normalized cidr range, of which the address is identical to the prefix.
*
* This means the address will never have any bits set outside the prefix. For example, a range
* such as 192.168.0.1/31 is not allowed.
*/
final class Strict[+A <: IpAddress] private (override val prefix: A, override val prefixBits: Int)
extends Cidr[A](prefix, prefixBits) {
override def normalized: this.type = this
}

object Strict {
def apply[A <: IpAddress](cidr: Cidr[A]): Cidr.Strict[A] = cidr match {
case already: Strict[_] => already.asInstanceOf[Strict[A]]
case _ => new Cidr.Strict(cidr.prefix, cidr.prefixBits)
}
}

/** Constructs a CIDR from the supplied IP address and prefix bit count. Note if `prefixBits` is less than 0, the
* built `Cidr` will have `prefixBits` set to 0. Similarly, if `prefixBits` is greater than the bit length of the
* address, it will be set to the bit length of the address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ object Arbitraries {
implicit def cidrArbitrary[A <: IpAddress](implicit arbIp: Arbitrary[A]): Arbitrary[Cidr[A]] =
Arbitrary(cidrGenerator(arbIp.arbitrary))

implicit def cidrStrictArbitrary[A <: IpAddress](implicit arbIp: Arbitrary[A]): Arbitrary[Cidr.Strict[A]] = Arbitrary(
cidrGenerator(arbIp.arbitrary).map(_.normalized)
)

val portGenerator: Gen[Port] = Gen.chooseNum(0, 65535).map(Port.fromInt(_).get)

implicit val portArbitrary: Arbitrary[Port] = Arbitrary(portGenerator)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.comcast.ip4s

import org.scalacheck.Prop.forAll
import Arbitraries._

class CidrStrictTest extends BaseTestSuite {
property("prefix and address are identical") {
forAll { (cidr: Cidr.Strict[IpAddress]) => assertEquals(cidr.address, cidr.prefix) }
}
}

0 comments on commit fe556b2

Please sign in to comment.