Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cycle through AP if blocked #532

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 47 additions & 20 deletions psst-core/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,42 +99,69 @@ pub struct Transport {
}

impl Transport {
pub fn resolve_ap_with_fallback(proxy_url: Option<&str>) -> String {
pub fn resolve_ap_with_fallback(proxy_url: Option<&str>) -> Vec<String> {
match Self::resolve_ap(proxy_url) {
Ok(ap) => ap,
Ok(ap_list) => {
log::info!("Successfully resolved {} access points", ap_list.len());
ap_list
}
Err(err) => {
log::error!("using AP fallback, error while resolving: {:?}", err);
AP_FALLBACK.into()
log::error!("Error while resolving APs, using fallback: {:?}", err);
vec![AP_FALLBACK.into()]
}
}
}

pub fn resolve_ap(proxy_url: Option<&str>) -> Result<String, Error> {
pub fn resolve_ap(proxy_url: Option<&str>) -> Result<Vec<String>, Error> {
#[derive(Clone, Debug, Deserialize)]
struct APResolveData {
ap_list: Vec<String>,
}

let agent = default_ureq_agent_builder(proxy_url)?.build();
log::info!("Requesting AP list from {}", AP_RESOLVE_ENDPOINT);
let data: APResolveData = agent.get(AP_RESOLVE_ENDPOINT).call()?.into_json()?;
data.ap_list
.into_iter()
.next()
.ok_or(Error::UnexpectedResponse)
if data.ap_list.is_empty() {
log::warn!("Received empty AP list from server");
Err(Error::UnexpectedResponse)
} else {
log::info!("Received {} APs from server", data.ap_list.len());
Ok(data.ap_list)
}
}

pub fn connect(ap: &str, proxy_url: Option<&str>) -> Result<Self, Error> {
log::trace!("connecting to: {:?} with proxy: {:?}", ap, proxy_url);
let stream = if let Some(url) = proxy_url {
Self::stream_through_proxy(ap, url)?
} else {
Self::stream_without_proxy(ap)?
};
if let Err(err) = stream.set_write_timeout(Some(NET_IO_TIMEOUT)) {
log::warn!("failed to set TCP write timeout: {:?}", err);
pub fn connect(ap_list: &[String], proxy_url: Option<&str>) -> Result<Self, Error> {
log::info!(
"Attempting to connect using {} access points",
ap_list.len()
);
for (index, ap) in ap_list.iter().enumerate() {
log::info!("Trying AP {} of {}: {}", index + 1, ap_list.len(), ap);
let stream = if let Some(url) = proxy_url {
match Self::stream_through_proxy(ap, url) {
Ok(s) => s,
Err(e) => {
log::warn!("Failed to connect to AP {} through proxy: {:?}", ap, e);
continue;
}
}
} else {
match Self::stream_without_proxy(ap) {
Ok(s) => s,
Err(e) => {
log::warn!("Failed to connect to AP {} without proxy: {:?}", ap, e);
continue;
}
}
};
if let Err(err) = stream.set_write_timeout(Some(NET_IO_TIMEOUT)) {
log::warn!("Failed to set TCP write timeout: {:?}", err);
}
log::info!("Successfully connected to AP: {}", ap);
return Self::exchange_keys(stream);
}
log::trace!("connected");
Self::exchange_keys(stream)
log::error!("Failed to connect to any access point");
Err(Error::ConnectionFailed)
}

fn stream_without_proxy(ap: &str) -> Result<TcpStream, io::Error> {
Expand Down
2 changes: 2 additions & 0 deletions psst-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub enum Error {
MediaFileNotFound,
ProxyUrlInvalid,
AuthFailed { code: i32 },
ConnectionFailed,
JsonError(Box<dyn error::Error + Send>),
AudioFetchingError(Box<dyn error::Error + Send>),
AudioDecodingError(Box<dyn error::Error + Send>),
Expand Down Expand Up @@ -40,6 +41,7 @@ impl fmt::Display for Error {
17 => write!(f, "Authentication failed: application banned"),
_ => write!(f, "Authentication failed with error code {}", code),
},
Self::ConnectionFailed => write!(f, "Failed to connect to any access point"),
Self::ResamplingError(code) => {
write!(f, "Resampling failed with error code {}", code)
}
Expand Down
6 changes: 2 additions & 4 deletions psst-core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,8 @@ impl SessionConnection {
pub fn open(config: SessionConfig) -> Result<Self, Error> {
// Connect to the server and exchange keys.
let proxy_url = config.proxy_url.as_deref();
let ap_url = Transport::resolve_ap_with_fallback(proxy_url);
let mut transport = Transport::connect(&ap_url, proxy_url)?;
// Authenticate with provided credentials (either username/password, or saved,
// reusable credential blob from an earlier run).
let ap_list = Transport::resolve_ap_with_fallback(proxy_url);
let mut transport = Transport::connect(&ap_list, proxy_url)?;
let credentials = transport.authenticate(config.login_creds)?;
Ok(Self {
credentials,
Expand Down
41 changes: 33 additions & 8 deletions psst-gui/src/ui/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ impl<W: Widget<AppState>> Controller<AppState, W> for Authenticate {
) {
match event {
Event::Command(cmd) if cmd.is(Self::REQUEST) => {
log::info!("Received Authenticate::REQUEST command");
data.preferences.auth.result.defer_default();

let (auth_url, pkce_verifier) = oauth::generate_auth_url(8888);
Expand All @@ -347,14 +348,37 @@ impl<W: Widget<AppState>> Controller<AppState, W> for Authenticate {
) {
Ok(code) => {
let token = oauth::exchange_code_for_token(8888, code, pkce_verifier);
let response =
Authentication::authenticate_and_get_credentials(SessionConfig {
login_creds: Credentials::from_access_token(token),
..config
});
event_sink
.submit_command(Self::RESPONSE, response, widget_id)
.unwrap();
let mut retries = 3;
while retries > 0 {
let response = Authentication::authenticate_and_get_credentials(
SessionConfig {
login_creds: Credentials::from_access_token(token.clone()),
..config.clone()
},
);
match response {
Ok(credentials) => {
event_sink
.submit_command(
Self::RESPONSE,
Ok(credentials),
widget_id,
)
.unwrap();
return;
}
Err(e) if retries > 1 => {
log::warn!("Authentication failed, retrying: {:?}", e);
retries -= 1;
}
Err(e) => {
event_sink
.submit_command(Self::RESPONSE, Err(e), widget_id)
.unwrap();
return;
}
}
}
}
Err(e) => {
event_sink
Expand All @@ -367,6 +391,7 @@ impl<W: Widget<AppState>> Controller<AppState, W> for Authenticate {
ctx.set_handled();
}
Event::Command(cmd) if cmd.is(Self::RESPONSE) => {
log::info!("Received Authenticate::RESPONSE command");
self.thread.take();

let result = cmd
Expand Down