1use attohttpc::{Method, RequestBuilder};
2use std::collections::HashMap;
3use std::fmt;
4use std::net::{IpAddr, SocketAddr};
5
6use crate::common::{self, messages, parsing, parsing::RequestResult};
7use crate::errors::{self, AddAnyPortError, AddPortError, GetExternalIpError, RemovePortError, RequestError};
8use crate::PortMappingProtocol;
9use crate::RequestError::AttoHttpError;
10
11#[derive(Clone, Debug)]
13pub struct Gateway {
14 pub addr: SocketAddr,
16 pub root_url: String,
18 pub control_url: String,
20 pub control_schema_url: String,
22 pub control_schema: HashMap<String, Vec<String>>,
24}
25
26impl Gateway {
27 fn perform_request(&self, header: &str, body: &str, ok: &str) -> RequestResult {
28 let url = format!("http://{}{}", self.addr, self.control_url);
29
30 let response = match RequestBuilder::try_new(Method::POST, url) {
31 Ok(request_builder) => request_builder
32 .header("SOAPAction", header)
33 .header("Content-Type", "text/xml")
34 .text(body)
35 .send()?,
36 Err(e) => return Err(AttoHttpError(e)),
37 };
38
39 parsing::parse_response(response.text()?, ok)
40 }
41
42 pub fn get_external_ip(&self) -> Result<IpAddr, GetExternalIpError> {
44 parsing::parse_get_external_ip_response(self.perform_request(
45 messages::GET_EXTERNAL_IP_HEADER,
46 &messages::format_get_external_ip_message(),
47 "GetExternalIPAddressResponse",
48 ))
49 }
50
51 pub fn get_any_address(
61 &self,
62 protocol: PortMappingProtocol,
63 local_addr: SocketAddr,
64 lease_duration: u32,
65 description: &str,
66 ) -> Result<SocketAddr, AddAnyPortError> {
67 let ip = self.get_external_ip()?;
68 let port = self.add_any_port(protocol, local_addr, lease_duration, description)?;
69 Ok(SocketAddr::new(ip, port))
70 }
71
72 pub fn add_any_port(
81 &self,
82 protocol: PortMappingProtocol,
83 local_addr: SocketAddr,
84 lease_duration: u32,
85 description: &str,
86 ) -> Result<u16, AddAnyPortError> {
87 if local_addr.port() == 0 {
94 return Err(AddAnyPortError::InternalPortZeroInvalid);
95 }
96
97 let schema = self.control_schema.get("AddAnyPortMapping");
98 if let Some(schema) = schema {
99 let external_port = common::random_port();
100
101 parsing::parse_add_any_port_mapping_response(self.perform_request(
102 messages::ADD_ANY_PORT_MAPPING_HEADER,
103 &messages::format_add_any_port_mapping_message(
104 schema,
105 protocol,
106 external_port,
107 local_addr,
108 lease_duration,
109 description,
110 ),
111 "AddAnyPortMappingResponse",
112 ))
113 } else {
114 self.retry_add_random_port_mapping(protocol, local_addr, lease_duration, description)
115 }
116 }
117
118 fn retry_add_random_port_mapping(
119 &self,
120 protocol: PortMappingProtocol,
121 local_addr: SocketAddr,
122 lease_duration: u32,
123 description: &str,
124 ) -> Result<u16, AddAnyPortError> {
125 const ATTEMPTS: usize = 20;
126
127 for _ in 0..ATTEMPTS {
128 if let Ok(port) = self.add_random_port_mapping(protocol, local_addr, lease_duration, description) {
129 return Ok(port);
130 }
131 }
132
133 Err(AddAnyPortError::NoPortsAvailable)
134 }
135
136 fn add_random_port_mapping(
137 &self,
138 protocol: PortMappingProtocol,
139 local_addr: SocketAddr,
140 lease_duration: u32,
141 description: &str,
142 ) -> Result<u16, AddAnyPortError> {
143 let external_port = common::random_port();
144
145 if let Err(err) = self.add_port_mapping(protocol, external_port, local_addr, lease_duration, description) {
146 match parsing::convert_add_random_port_mapping_error(err) {
147 Some(err) => return Err(err),
148 None => return self.add_same_port_mapping(protocol, local_addr, lease_duration, description),
149 }
150 }
151
152 Ok(external_port)
153 }
154
155 fn add_same_port_mapping(
156 &self,
157 protocol: PortMappingProtocol,
158 local_addr: SocketAddr,
159 lease_duration: u32,
160 description: &str,
161 ) -> Result<u16, AddAnyPortError> {
162 match self.add_port_mapping(protocol, local_addr.port(), local_addr, lease_duration, description) {
163 Ok(_) => Ok(local_addr.port()),
164 Err(e) => Err(parsing::convert_add_same_port_mapping_error(e)),
165 }
166 }
167
168 fn add_port_mapping(
169 &self,
170 protocol: PortMappingProtocol,
171 external_port: u16,
172 local_addr: SocketAddr,
173 lease_duration: u32,
174 description: &str,
175 ) -> Result<(), RequestError> {
176 self.perform_request(
177 messages::ADD_PORT_MAPPING_HEADER,
178 &messages::format_add_port_mapping_message(
179 self.control_schema
180 .get("AddPortMapping")
181 .ok_or_else(|| RequestError::UnsupportedAction("AddPortMapping".to_string()))?,
182 protocol,
183 external_port,
184 local_addr,
185 lease_duration,
186 description,
187 ),
188 "AddPortMappingResponse",
189 )?;
190
191 Ok(())
192 }
193
194 pub fn add_port(
199 &self,
200 protocol: PortMappingProtocol,
201 external_port: u16,
202 local_addr: SocketAddr,
203 lease_duration: u32,
204 description: &str,
205 ) -> Result<(), AddPortError> {
206 if external_port == 0 {
207 return Err(AddPortError::ExternalPortZeroInvalid);
208 }
209 if local_addr.port() == 0 {
210 return Err(AddPortError::InternalPortZeroInvalid);
211 }
212
213 self.add_port_mapping(protocol, external_port, local_addr, lease_duration, description)
214 .map_err(parsing::convert_add_port_error)
215 }
216
217 pub fn remove_port(&self, protocol: PortMappingProtocol, external_port: u16) -> Result<(), RemovePortError> {
219 parsing::parse_delete_port_mapping_response(self.perform_request(
220 messages::DELETE_PORT_MAPPING_HEADER,
221 &messages::format_delete_port_message(
222 self.control_schema.get("DeletePortMapping").ok_or_else(|| {
223 RemovePortError::RequestError(RequestError::UnsupportedAction("DeletePortMapping".to_string()))
224 })?,
225 protocol,
226 external_port,
227 ),
228 "DeletePortMappingResponse",
229 ))
230 }
231
232 pub fn get_generic_port_mapping_entry(
238 &self,
239 index: u32,
240 ) -> Result<parsing::PortMappingEntry, errors::GetGenericPortMappingEntryError> {
241 parsing::parse_get_generic_port_mapping_entry(self.perform_request(
242 messages::GET_GENERIC_PORT_MAPPING_ENTRY,
243 &messages::formate_get_generic_port_mapping_entry_message(index),
244 "GetGenericPortMappingEntryResponse",
245 ))
246 }
247}
248
249impl fmt::Display for Gateway {
250 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251 write!(f, "http://{}{}", self.addr, self.control_url)
252 }
253}