Skip to content

Commit 83afe82

Browse files
authored
Merge pull request #277 from Comcast/topic/mapped-v4
Add some utilities for working with mapped v4 addresses
2 parents aa47118 + b309894 commit 83afe82

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

shared/src/main/scala/com/comcast/ip4s/Host.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,21 @@ sealed abstract class IpAddress extends IpAddressPlatform with Host with Seriali
174174
SourceSpecificMulticast.fromIpAddress(this)
175175

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

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

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

185+
/** Returns true if this address is a V6 address containing a mapped V4 address. */
186+
def isMappedV4: Boolean = fold(_ => false, Ipv6Address.MappedV4Block.contains)
187+
188+
/** If this address is an IPv4 mapped IPv6 address, converts to an IPv4 address, otherwise returns this. */
189+
def collapseMappedV4: IpAddress =
190+
fold(identity, v6 => if (v6.isMappedV4) IpAddress.fromBytes(v6.toBytes.takeRight(4)).get else v6)
191+
185192
/** Constructs a [[Cidr]] address from this address. */
186193
def /(prefixBits: Int): Cidr[this.type] = Cidr(this, prefixBits)
187194

@@ -559,6 +566,10 @@ object Ipv6Address extends Ipv6AddressCompanionPlatform {
559566
val SourceSpecificMulticastRangeEnd: Ipv6Address =
560567
fromBytes(255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)
561568

569+
/** CIDR which defines the mapped IPv4 address block (https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2). */
570+
val MappedV4Block: Cidr[Ipv6Address] =
571+
Cidr(Ipv6Address.fromBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0), 96)
572+
562573
/** Parses an IPv6 address from a string in RFC4291 notation, returning `None` if the string is not a valid IPv6 address. */
563574
def fromString(value: String): Option[Ipv6Address] =
564575
fromNonMixedString(value) orElse fromMixedString(value)

test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv6AddressTest.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,14 @@ class Ipv6AddressTest extends BaseTestSuite {
116116
)
117117
forAll { (ip: Ipv6Address) => assertEquals(ip.previous, Ipv6Address.fromBigInt(ip.toBigInt - 1)) }
118118
}
119+
120+
test("converting V4 mapped address") {
121+
val addr = ip"::ffff:f:f"
122+
assertEquals[Any, Any](addr.getClass, classOf[Ipv6Address])
123+
assertEquals(addr.version, IpVersion.V6)
124+
assertEquals(addr.toString, "::ffff:f:f")
125+
assertEquals[Any, Any](addr.collapseMappedV4.getClass, classOf[Ipv4Address])
126+
assertEquals[Any, Any](addr.asIpv6, Some(addr))
127+
assertEquals[Any, Any](addr.asIpv4, Some(ip"0.15.0.15"))
128+
}
119129
}

0 commit comments

Comments
 (0)