@@ -70,6 +70,79 @@ struct DstInfo {
7070 port : u16 ,
7171 is_tls : bool ,
7272 is_h2 : bool ,
73+ is_pp : bool ,
74+ }
75+
76+ fn parse_app_addr ( addr : & str ) -> Result < DstInfo > {
77+ let ( app_id, port_part) = addr
78+ . rsplit_once ( '-' )
79+ . or_else ( || addr. rsplit_once ( ':' ) )
80+ . unwrap_or ( ( addr, "" ) ) ;
81+ if app_id. is_empty ( ) {
82+ bail ! ( "app id is empty" ) ;
83+ }
84+ let mut dst = DstInfo {
85+ app_id : app_id. to_owned ( ) ,
86+ port : 80 ,
87+ is_tls : false ,
88+ is_h2 : false ,
89+ is_pp : false ,
90+ } ;
91+
92+ if port_part. is_empty ( ) {
93+ return Ok ( dst) ;
94+ } ;
95+
96+ // Parse suffixes from right to left: g, s, p
97+ let part_bytes = port_part. as_bytes ( ) ;
98+ let mut end_idx = part_bytes. len ( ) ;
99+
100+ // Parse from right to left until we hit a digit
101+ while end_idx > 0 {
102+ let ch = part_bytes[ end_idx - 1 ] as char ;
103+ match ch {
104+ c if c. is_ascii_digit ( ) => {
105+ break ;
106+ }
107+ 'g' => {
108+ if dst. is_h2 {
109+ bail ! ( "invalid app address: duplicate suffix 'g'" ) ;
110+ }
111+ dst. is_h2 = true ;
112+ end_idx -= 1 ;
113+ }
114+ 's' => {
115+ if dst. is_tls {
116+ bail ! ( "invalid app address: duplicate suffix 's'" ) ;
117+ }
118+ dst. is_tls = true ;
119+ end_idx -= 1 ;
120+ }
121+ 'p' => {
122+ if dst. is_pp {
123+ bail ! ( "invalid app address: duplicate suffix 'p'" ) ;
124+ }
125+ dst. is_pp = true ;
126+ end_idx -= 1 ;
127+ }
128+ _ => {
129+ bail ! ( "invalid app address: unrecognized suffix character '{ch}'" ) ;
130+ }
131+ }
132+ }
133+
134+ if dst. is_h2 && dst. is_tls {
135+ bail ! ( "invalid app address: both 's' and 'g' suffixes are present" ) ;
136+ }
137+
138+ let port_str = & port_part[ ..end_idx] ;
139+ let port = if port_str. is_empty ( ) {
140+ None
141+ } else {
142+ Some ( port_str. parse :: < u16 > ( ) . context ( "invalid port" ) ?)
143+ } ;
144+ dst. port = port. unwrap_or ( if dst. is_tls { 443 } else { 80 } ) ;
145+ Ok ( dst)
73146}
74147
75148fn parse_destination ( sni : & str , dotted_base_domain : & str ) -> Result < DstInfo > {
@@ -80,53 +153,7 @@ fn parse_destination(sni: &str, dotted_base_domain: &str) -> Result<DstInfo> {
80153 if subdomain. contains ( '.' ) {
81154 bail ! ( "only one level of subdomain is supported, got sni={sni}, subdomain={subdomain}" ) ;
82155 }
83- let mut parts = subdomain. split ( '-' ) ;
84- let app_id = parts. next ( ) . context ( "no app id found" ) ?. to_owned ( ) ;
85- if app_id. is_empty ( ) {
86- bail ! ( "app id is empty" ) ;
87- }
88- let last_part = parts. next ( ) ;
89- let is_tls;
90- let port;
91- let is_h2;
92- match last_part {
93- None => {
94- is_tls = false ;
95- is_h2 = false ;
96- port = None ;
97- }
98- Some ( last_part) => {
99- let ( port_str, has_g) = match last_part. strip_suffix ( 'g' ) {
100- Some ( without_g) => ( without_g, true ) ,
101- None => ( last_part, false ) ,
102- } ;
103-
104- let ( port_str, has_s) = match port_str. strip_suffix ( 's' ) {
105- Some ( without_s) => ( without_s, true ) ,
106- None => ( port_str, false ) ,
107- } ;
108- if has_g && has_s {
109- bail ! ( "invalid sni format: `gs` is not allowed" ) ;
110- }
111- is_h2 = has_g;
112- is_tls = has_s;
113- port = if port_str. is_empty ( ) {
114- None
115- } else {
116- Some ( port_str. parse :: < u16 > ( ) . context ( "invalid port" ) ?)
117- } ;
118- }
119- } ;
120- let port = port. unwrap_or ( if is_tls { 443 } else { 80 } ) ;
121- if parts. next ( ) . is_some ( ) {
122- bail ! ( "invalid sni format" ) ;
123- }
124- Ok ( DstInfo {
125- app_id,
126- port,
127- is_tls,
128- is_h2,
129- } )
156+ parse_app_addr ( subdomain)
130157}
131158
132159pub static NUM_CONNECTIONS : AtomicU64 = AtomicU64 :: new ( 0 ) ;
0 commit comments