Skip to content

Commit

Permalink
Merge pull request #277 from Comcast/topic/mapped-v4
Browse files Browse the repository at this point in the history
Add some utilities for working with mapped v4 addresses
  • Loading branch information
mpilquist authored May 11, 2021
2 parents aa47118 + b309894 commit 83afe82
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 1 deletion.
13 changes: 12 additions & 1 deletion shared/src/main/scala/com/comcast/ip4s/Host.scala
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,21 @@ sealed abstract class IpAddress extends IpAddressPlatform with Host with Seriali
SourceSpecificMulticast.fromIpAddress(this)

/** Narrows this address to an Ipv4Address if that is the underlying type. */
def asIpv4: Option[Ipv4Address] = fold(Some(_), _ => None)
def asIpv4: Option[Ipv4Address] = collapseMappedV4.fold(Some(_), _ => None)

/** Narrows this address to an Ipv6Address if that is the underlying type. */
def asIpv6: Option[Ipv6Address] = fold(_ => None, Some(_))

/** Returns the version of this address. */
def version: IpVersion = fold(_ => IpVersion.V4, _ => IpVersion.V6)

/** Returns true if this address is a V6 address containing a mapped V4 address. */
def isMappedV4: Boolean = fold(_ => false, Ipv6Address.MappedV4Block.contains)

/** If this address is an IPv4 mapped IPv6 address, converts to an IPv4 address, otherwise returns this. */
def collapseMappedV4: IpAddress =
fold(identity, v6 => if (v6.isMappedV4) IpAddress.fromBytes(v6.toBytes.takeRight(4)).get else v6)

/** Constructs a [[Cidr]] address from this address. */
def /(prefixBits: Int): Cidr[this.type] = Cidr(this, prefixBits)

Expand Down Expand Up @@ -559,6 +566,10 @@ object Ipv6Address extends Ipv6AddressCompanionPlatform {
val SourceSpecificMulticastRangeEnd: Ipv6Address =
fromBytes(255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

/** CIDR which defines the mapped IPv4 address block (https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2). */
val MappedV4Block: Cidr[Ipv6Address] =
Cidr(Ipv6Address.fromBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0), 96)

/** Parses an IPv6 address from a string in RFC4291 notation, returning `None` if the string is not a valid IPv6 address. */
def fromString(value: String): Option[Ipv6Address] =
fromNonMixedString(value) orElse fromMixedString(value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,14 @@ class Ipv6AddressTest extends BaseTestSuite {
)
forAll { (ip: Ipv6Address) => assertEquals(ip.previous, Ipv6Address.fromBigInt(ip.toBigInt - 1)) }
}

test("converting V4 mapped address") {
val addr = ip"::ffff:f:f"
assertEquals[Any, Any](addr.getClass, classOf[Ipv6Address])
assertEquals(addr.version, IpVersion.V6)
assertEquals(addr.toString, "::ffff:f:f")
assertEquals[Any, Any](addr.collapseMappedV4.getClass, classOf[Ipv4Address])
assertEquals[Any, Any](addr.asIpv6, Some(addr))
assertEquals[Any, Any](addr.asIpv4, Some(ip"0.15.0.15"))
}
}

0 comments on commit 83afe82

Please sign in to comment.