blob: 103aecb4c61a69be0f65d4936421769cf170e761 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi.hotspot2.soap;
import android.annotation.NonNull;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.URL;
import java.util.Random;
import fi.iki.elonen.NanoHTTPD;
/**
* Server for listening for redirect request from the OSU server to indicate the completion
* of user input.
*
* A HTTP server will be started in the {@link RedirectListener#startServer} of {@link
* RedirectListener}, so the caller will need to invoke {@link RedirectListener#stop} once the
* redirect server no longer needed.
*/
public class RedirectListener extends NanoHTTPD {
// 10 minutes for the maximum wait time.
@VisibleForTesting
static final int USER_TIMEOUT_MILLIS = 10 * 60 * 1000;
private static final String TAG = "PasspointRedirectListener";
private final String mPath;
private final URL mServerUrl;
private final Handler mHandler;
private Runnable mTimeOutTask;
private RedirectCallback mRedirectCallback;
/**
* Listener interface for handling redirect events.
*/
public interface RedirectCallback {
/**
* Invoked when HTTP redirect response is received.
*/
void onRedirectReceived();
/**
* Invoked when timeout occurs on receiving HTTP redirect response.
*/
void onRedirectTimedOut();
}
@VisibleForTesting
/* package */ RedirectListener(Looper looper, int port)
throws IOException {
super(InetAddress.getLocalHost().getHostAddress(), port);
Random rnd = new Random(System.currentTimeMillis());
mPath = "rnd" + Integer.toString(Math.abs(rnd.nextInt()), Character.MAX_RADIX);
mServerUrl = new URL("http", getHostname(), port, mPath);
mHandler = new Handler(looper);
mTimeOutTask = () -> mRedirectCallback.onRedirectTimedOut();
}
/**
* Create an instance of {@link RedirectListener}
*
* @param looper Looper on which the {@link RedirectCallback} will be called.
* @return Instance of {@link RedirectListener}, {@code null} in any failure.
*/
public static RedirectListener createInstance(@NonNull Looper looper) {
RedirectListener redirectListener;
try {
ServerSocket serverSocket = new ServerSocket(0, 1, InetAddress.getLocalHost());
redirectListener = new RedirectListener(looper, serverSocket.getLocalPort());
redirectListener.setServerSocketFactory(() -> {
// Close current server socket so that new server socket is able to bind the port
// in the start() of NanoHTTPD.
serverSocket.close();
return new ServerSocket();
});
} catch (IOException e) {
Log.e(TAG, "fails to create an instance: " + e);
return null;
}
return redirectListener;
}
/**
* Start redirect listener
*
* @param callback to be notified when the redirect request is received or timed out.
* @param startHandler handler on which the start code is executed.
* @return {@code true} in success, {@code false} if the {@code callback} and {@code
* startHandler} are {@code null} or the server is already running.
*/
public boolean startServer(@NonNull RedirectCallback callback, @NonNull Handler startHandler) {
if (callback == null) {
return false;
}
if (startHandler == null) {
return false;
}
if (isAlive()) {
Log.e(TAG, "redirect listener is already running");
return false;
}
mRedirectCallback = callback;
startHandler.post(() -> {
try {
start();
} catch (IOException e) {
Log.e(TAG, "unable to start redirect listener: " + e);
}
});
mHandler.postDelayed(mTimeOutTask, USER_TIMEOUT_MILLIS);
return true;
}
/**
* Stop redirect listener
*
* @param stopHandler handler on which the stop code is executed.
*/
public void stopServer(@NonNull Handler stopHandler) {
if (mHandler.hasCallbacks(mTimeOutTask)) {
mHandler.removeCallbacks(mTimeOutTask);
}
if (stopHandler == null) {
return;
}
if (isServerAlive()) {
stopHandler.post(() -> stop());
}
}
/**
* Check if the server is alive or not.
*
* @return {@code true} if the server is alive.
*/
public boolean isServerAlive() {
return isAlive();
}
/**
* Get URL to which the local redirect server listens
*
* @return The URL for the local redirect server.
*/
public URL getServerUrl() {
return mServerUrl;
}
@Override
public Response serve(IHTTPSession session) {
// Ignore all other requests except for a HTTP request that has the server url path with
// GET method.
if (session.getMethod() != Method.GET || !mServerUrl.getPath().equals(session.getUri())) {
return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_HTML, "");
}
mHandler.removeCallbacks(mTimeOutTask);
mRedirectCallback.onRedirectReceived();
return newFixedLengthResponse("");
}
}