@@ -4,6 +4,8 @@ use std::net::SocketAddr;
44use serde:: { Deserialize , Serialize } ;
55use url:: Url ;
66
7+ const DUAL_STACK_IP_V4_MAPPED_V6_PREFIX : & str = "::ffff:" ;
8+
79/// Represents the supported network protocols.
810#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize , Hash ) ]
911pub enum Protocol {
@@ -23,6 +25,29 @@ impl fmt::Display for Protocol {
2325 }
2426}
2527
28+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize , Hash ) ]
29+ pub enum AddressType {
30+ /// Represents a plain IPv4 or IPv6 address.
31+ Plain ,
32+
33+ /// Represents an IPv6 address that is a mapped IPv4 address.
34+ ///
35+ /// This is used for IPv6 addresses that represent an IPv4 address in a dual-stack network.
36+ ///
37+ /// For example: `[::ffff:192.0.2.33]`
38+ V4MappedV6 ,
39+ }
40+
41+ impl fmt:: Display for AddressType {
42+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
43+ let addr_type_str = match self {
44+ Self :: Plain => "plain" ,
45+ Self :: V4MappedV6 => "v4_mapped_v6" ,
46+ } ;
47+ write ! ( f, "{addr_type_str}" )
48+ }
49+ }
50+
2651#[ derive( thiserror:: Error , Debug , Clone ) ]
2752pub enum Error {
2853 #[ error( "The port number cannot be zero. It must be an assigned valid port." ) ]
@@ -94,6 +119,15 @@ impl ServiceBinding {
94119 self . bind_address
95120 }
96121
122+ #[ must_use]
123+ pub fn bind_address_type ( & self ) -> AddressType {
124+ if self . is_v4_mapped_v6 ( ) {
125+ return AddressType :: V4MappedV6 ;
126+ }
127+
128+ AddressType :: Plain
129+ }
130+
97131 /// # Panics
98132 ///
99133 /// It never panics because the URL is always valid.
@@ -102,6 +136,15 @@ impl ServiceBinding {
102136 Url :: parse ( & format ! ( "{}://{}" , self . protocol, self . bind_address) )
103137 . expect ( "Service binding can always be parsed into a URL" )
104138 }
139+
140+ fn is_v4_mapped_v6 ( & self ) -> bool {
141+ self . bind_address . ip ( ) . is_ipv6 ( )
142+ && self
143+ . bind_address
144+ . ip ( )
145+ . to_string ( )
146+ . starts_with ( DUAL_STACK_IP_V4_MAPPED_V6_PREFIX )
147+ }
105148}
106149
107150impl From < ServiceBinding > for Url {
@@ -126,7 +169,7 @@ mod tests {
126169 use rstest:: rstest;
127170 use url:: Url ;
128171
129- use crate :: service_binding:: { Error , Protocol , ServiceBinding } ;
172+ use crate :: service_binding:: { AddressType , Error , Protocol , ServiceBinding } ;
130173
131174 #[ rstest]
132175 #[ case( "wildcard_ip" , Protocol :: UDP , SocketAddr :: from_str( "0.0.0.0:6969" ) . unwrap( ) ) ]
@@ -156,6 +199,29 @@ mod tests {
156199 ) ;
157200 }
158201
202+ #[ test]
203+ fn should_return_the_bind_address_plain_type_for_ipv4_ips ( ) {
204+ let service_binding = ServiceBinding :: new ( Protocol :: UDP , SocketAddr :: from_str ( "127.0.0.1:6969" ) . unwrap ( ) ) . unwrap ( ) ;
205+
206+ assert_eq ! ( service_binding. bind_address_type( ) , AddressType :: Plain ) ;
207+ }
208+
209+ #[ test]
210+ fn should_return_the_bind_address_plain_type_for_ipv6_ips ( ) {
211+ let service_binding =
212+ ServiceBinding :: new ( Protocol :: UDP , SocketAddr :: from_str ( "[0:0:0:0:0:0:0:1]:6969" ) . unwrap ( ) ) . unwrap ( ) ;
213+
214+ assert_eq ! ( service_binding. bind_address_type( ) , AddressType :: Plain ) ;
215+ }
216+
217+ #[ test]
218+ fn should_return_the_bind_address_v4_mapped_v7_type_for_ipv4_ips_mapped_to_ipv6 ( ) {
219+ let service_binding =
220+ ServiceBinding :: new ( Protocol :: UDP , SocketAddr :: from_str ( "[::ffff:192.0.2.33]:6969" ) . unwrap ( ) ) . unwrap ( ) ;
221+
222+ assert_eq ! ( service_binding. bind_address_type( ) , AddressType :: V4MappedV6 ) ;
223+ }
224+
159225 #[ test]
160226 fn should_return_the_corresponding_url ( ) {
161227 let service_binding = ServiceBinding :: new ( Protocol :: UDP , SocketAddr :: from_str ( "127.0.0.1:6969" ) . unwrap ( ) ) . unwrap ( ) ;
0 commit comments