Repository: koush/AndroidAsync
Branch: master
Commit: 12f9e20443db
Files: 244
Total size: 769.9 KB
Directory structure:
gitextract_o_k792rl/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── AndroidAsync/
│ ├── .classpath
│ ├── Android.mk
│ ├── AndroidManifest.xml
│ ├── build.gradle
│ ├── lint.xml
│ ├── maven.gradle
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res/
│ │ └── .gitignore
│ ├── src/
│ │ └── com/
│ │ └── koushikdutta/
│ │ └── async/
│ │ ├── AsyncDatagramSocket.java
│ │ ├── AsyncNetworkSocket.java
│ │ ├── AsyncSSLException.java
│ │ ├── AsyncSSLServerSocket.java
│ │ ├── AsyncSSLSocket.java
│ │ ├── AsyncSSLSocketWrapper.java
│ │ ├── AsyncSemaphore.java
│ │ ├── AsyncServer.java
│ │ ├── AsyncServerSocket.java
│ │ ├── AsyncSocket.java
│ │ ├── BufferedDataSink.java
│ │ ├── ByteBufferList.java
│ │ ├── ChannelWrapper.java
│ │ ├── DataEmitter.java
│ │ ├── DataEmitterBase.java
│ │ ├── DataEmitterReader.java
│ │ ├── DataSink.java
│ │ ├── DataTrackingEmitter.java
│ │ ├── DatagramChannelWrapper.java
│ │ ├── FileDataEmitter.java
│ │ ├── FilteredDataEmitter.java
│ │ ├── FilteredDataSink.java
│ │ ├── HostnameResolutionException.java
│ │ ├── LineEmitter.java
│ │ ├── PushParser.java
│ │ ├── SelectorWrapper.java
│ │ ├── ServerSocketChannelWrapper.java
│ │ ├── SocketChannelWrapper.java
│ │ ├── TapCallback.java
│ │ ├── ThreadQueue.java
│ │ ├── Util.java
│ │ ├── ZipDataSink.java
│ │ ├── callback/
│ │ │ ├── CompletedCallback.java
│ │ │ ├── ConnectCallback.java
│ │ │ ├── ContinuationCallback.java
│ │ │ ├── DataCallback.java
│ │ │ ├── ListenCallback.java
│ │ │ ├── ResultCallback.java
│ │ │ ├── SocketCreateCallback.java
│ │ │ ├── ValueCallback.java
│ │ │ ├── ValueFunction.java
│ │ │ └── WritableCallback.java
│ │ ├── dns/
│ │ │ ├── Dns.java
│ │ │ └── DnsResponse.java
│ │ ├── future/
│ │ │ ├── Cancellable.java
│ │ │ ├── Continuation.java
│ │ │ ├── Converter.java
│ │ │ ├── DependentCancellable.java
│ │ │ ├── DependentFuture.java
│ │ │ ├── DoneCallback.java
│ │ │ ├── FailCallback.java
│ │ │ ├── FailConvertCallback.java
│ │ │ ├── FailRecoverCallback.java
│ │ │ ├── Future.java
│ │ │ ├── FutureCallback.java
│ │ │ ├── FutureRunnable.java
│ │ │ ├── FutureThread.java
│ │ │ ├── Futures.java
│ │ │ ├── HandlerFuture.java
│ │ │ ├── MultiFuture.java
│ │ │ ├── MultiTransformFuture.java
│ │ │ ├── SimpleCancellable.java
│ │ │ ├── SimpleFuture.java
│ │ │ ├── SuccessCallback.java
│ │ │ ├── ThenCallback.java
│ │ │ ├── ThenFutureCallback.java
│ │ │ ├── TransformFuture.java
│ │ │ └── TypeConverter.java
│ │ ├── http/
│ │ │ ├── AsyncHttpClient.java
│ │ │ ├── AsyncHttpClientMiddleware.java
│ │ │ ├── AsyncHttpDelete.java
│ │ │ ├── AsyncHttpGet.java
│ │ │ ├── AsyncHttpHead.java
│ │ │ ├── AsyncHttpPost.java
│ │ │ ├── AsyncHttpPut.java
│ │ │ ├── AsyncHttpRequest.java
│ │ │ ├── AsyncHttpResponse.java
│ │ │ ├── AsyncHttpResponseImpl.java
│ │ │ ├── AsyncSSLEngineConfigurator.java
│ │ │ ├── AsyncSSLSocketMiddleware.java
│ │ │ ├── AsyncSocketMiddleware.java
│ │ │ ├── BasicNameValuePair.java
│ │ │ ├── BodyDecoderException.java
│ │ │ ├── ConnectionClosedException.java
│ │ │ ├── ConnectionFailedException.java
│ │ │ ├── Headers.java
│ │ │ ├── HttpDate.java
│ │ │ ├── HttpTransportMiddleware.java
│ │ │ ├── HttpUtil.java
│ │ │ ├── HybiParser.java
│ │ │ ├── Multimap.java
│ │ │ ├── NameValuePair.java
│ │ │ ├── Protocol.java
│ │ │ ├── ProtocolVersion.java
│ │ │ ├── RedirectLimitExceededException.java
│ │ │ ├── RequestLine.java
│ │ │ ├── SSLEngineSNIConfigurator.java
│ │ │ ├── SimpleMiddleware.java
│ │ │ ├── WebSocket.java
│ │ │ ├── WebSocketHandshakeException.java
│ │ │ ├── WebSocketImpl.java
│ │ │ ├── body/
│ │ │ │ ├── AsyncHttpRequestBody.java
│ │ │ │ ├── ByteBufferListRequestBody.java
│ │ │ │ ├── DocumentBody.java
│ │ │ │ ├── FileBody.java
│ │ │ │ ├── FilePart.java
│ │ │ │ ├── JSONArrayBody.java
│ │ │ │ ├── JSONObjectBody.java
│ │ │ │ ├── MultipartFormDataBody.java
│ │ │ │ ├── Part.java
│ │ │ │ ├── StreamBody.java
│ │ │ │ ├── StreamPart.java
│ │ │ │ ├── StringBody.java
│ │ │ │ ├── StringPart.java
│ │ │ │ └── UrlEncodedFormBody.java
│ │ │ ├── cache/
│ │ │ │ ├── HeaderParser.java
│ │ │ │ ├── Objects.java
│ │ │ │ ├── RawHeaders.java
│ │ │ │ ├── RequestHeaders.java
│ │ │ │ ├── ResponseCacheMiddleware.java
│ │ │ │ ├── ResponseHeaders.java
│ │ │ │ ├── ResponseSource.java
│ │ │ │ └── StrictLineReader.java
│ │ │ ├── callback/
│ │ │ │ ├── HttpConnectCallback.java
│ │ │ │ └── RequestCallback.java
│ │ │ ├── filter/
│ │ │ │ ├── ChunkedDataException.java
│ │ │ │ ├── ChunkedInputFilter.java
│ │ │ │ ├── ChunkedOutputFilter.java
│ │ │ │ ├── ContentLengthFilter.java
│ │ │ │ ├── DataRemainingException.java
│ │ │ │ ├── GZIPInputFilter.java
│ │ │ │ ├── InflaterInputFilter.java
│ │ │ │ └── PrematureDataEndException.java
│ │ │ └── server/
│ │ │ ├── AsyncHttpRequestBodyProvider.java
│ │ │ ├── AsyncHttpServer.java
│ │ │ ├── AsyncHttpServerRequest.java
│ │ │ ├── AsyncHttpServerRequestImpl.java
│ │ │ ├── AsyncHttpServerResponse.java
│ │ │ ├── AsyncHttpServerResponseImpl.java
│ │ │ ├── AsyncHttpServerRouter.java
│ │ │ ├── AsyncProxyServer.java
│ │ │ ├── BoundaryEmitter.java
│ │ │ ├── HttpServerRequestCallback.java
│ │ │ ├── MalformedRangeException.java
│ │ │ ├── MimeEncodingException.java
│ │ │ ├── RouteMatcher.java
│ │ │ ├── StreamSkipException.java
│ │ │ └── UnknownRequestBody.java
│ │ ├── parser/
│ │ │ ├── AsyncParser.java
│ │ │ ├── ByteBufferListParser.java
│ │ │ ├── DocumentParser.java
│ │ │ ├── JSONArrayParser.java
│ │ │ ├── JSONObjectParser.java
│ │ │ └── StringParser.java
│ │ ├── stream/
│ │ │ ├── ByteBufferListInputStream.java
│ │ │ ├── FileDataSink.java
│ │ │ ├── InputStreamDataEmitter.java
│ │ │ ├── OutputStreamDataCallback.java
│ │ │ └── OutputStreamDataSink.java
│ │ ├── util/
│ │ │ ├── Allocator.java
│ │ │ ├── ArrayDeque.java
│ │ │ ├── Charsets.java
│ │ │ ├── Deque.java
│ │ │ ├── FileCache.java
│ │ │ ├── FileUtility.java
│ │ │ ├── HashList.java
│ │ │ ├── IdleTimeout.java
│ │ │ ├── LruCache.java
│ │ │ ├── StreamUtility.java
│ │ │ ├── TaggedList.java
│ │ │ ├── ThrottleTimeout.java
│ │ │ ├── TimeoutBase.java
│ │ │ └── UntypedHashtable.java
│ │ └── wrapper/
│ │ ├── AsyncSocketWrapper.java
│ │ └── DataEmitterWrapper.java
│ └── test/
│ ├── assets/
│ │ ├── 6691924d7d24237d3b3679310157d640
│ │ ├── hello.txt
│ │ └── test.json
│ ├── res/
│ │ ├── raw/
│ │ │ └── keystore.bks
│ │ └── values/
│ │ └── strings.xml
│ └── src/
│ └── com/
│ └── koushikdutta/
│ └── async/
│ └── test/
│ ├── BodyTests.java
│ ├── ByteUtilTests.java
│ ├── CacheTests.java
│ ├── CallbackTests.java
│ ├── ConscryptTests.java
│ ├── ConvertTests.java
│ ├── DnsTests.java
│ ├── FileCacheTests.java
│ ├── FileTests.java
│ ├── FutureTests.java
│ ├── HttpClientTests.java
│ ├── HttpServerTests.java
│ ├── Issue59.java
│ ├── IssueWithWebSocketFuturesTests.java
│ ├── LineEmitterTests.java
│ ├── Md5.java
│ ├── MultipartTests.java
│ ├── ParserTests.java
│ ├── ProxyTests.java
│ ├── RedirectTests.java
│ ├── SSLTests.java
│ ├── SanityChecks.java
│ ├── TimeoutTests.java
│ ├── TriggerFuture.java
│ └── WebSocketTests.java
├── AndroidAsync-Kotlin/
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── koushikdutta/
│ │ └── async/
│ │ └── kotlin/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── koushikdutta/
│ │ └── async/
│ │ └── kotlin/
│ │ └── FutureExtensions.kt
│ └── test/
│ └── java/
│ └── com/
│ └── koushikdutta/
│ └── async/
│ └── kotlin/
│ └── ExampleUnitTest.kt
├── AndroidAsyncSample/
│ ├── AndroidManifest.xml
│ ├── build.gradle
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res/
│ │ ├── layout/
│ │ │ └── activity_main.xml
│ │ ├── menu/
│ │ │ └── activity_main.xml
│ │ ├── values/
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── values-v11/
│ │ │ └── styles.xml
│ │ └── values-v14/
│ │ └── styles.xml
│ └── src/
│ └── com/
│ └── koushikdutta/
│ └── async/
│ └── sample/
│ ├── MainActivity.java
│ └── middleware/
│ ├── BasicAuthMiddleware.java
│ └── CacheOverrideMiddleware.java
├── LICENSE
├── README.md
├── build.gradle
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: koush
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
bin
.settings
local.properties
gen
.gradle
build
.idea/
.DS_Store
okhttp/
okio/
libs
*.iml
================================================
FILE: AndroidAsync/.classpath
================================================
================================================
FILE: AndroidAsync/Android.mk
================================================
#
# Copyright (C) 2011 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := AndroidAsync
LOCAL_SDK_VERSION := 8
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_STATIC_JAVA_LIBRARY)
================================================
FILE: AndroidAsync/AndroidManifest.xml
================================================
================================================
FILE: AndroidAsync/build.gradle
================================================
apply plugin: 'com.android.library'
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
jniLibs.srcDirs = ['libs/']
java.srcDirs=['src/'
// , '../conscrypt/'
// , '../compat/'
]
}
androidTest.java.srcDirs=['test/src/']
androidTest.res.srcDirs=['test/res/']
androidTest.assets.srcDirs=['test/assets/']
}
lintOptions {
abortOnError false
}
defaultConfig {
targetSdkVersion 30
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
compileSdkVersion 30
buildToolsVersion '30.0.2'
dependencies {
// this is only necessary to get compilation working for self signed certificates. dependency isn't added.
compileOnly group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.60'
compileOnly group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.60'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
// for when i wanna test this against gms conscrypt
androidTestImplementation 'com.google.android.gms:play-services-base:17.0.0'
}
}
apply from: 'maven.gradle'
================================================
FILE: AndroidAsync/lint.xml
================================================
================================================
FILE: AndroidAsync/maven.gradle
================================================
// Setup
// 0) Setup your sonatype credentials by editing/creating ~/.gradle/gradle.properties and enter:
// signing.keyId=
// signing.password=
// signing.secretKeyRingFile=
// sonatypeUsername=
// sonatypePassword=
// 1) Setup your build.gradle for your android project and add this one line of code which imports this gist:
// apply from: 'https://raw.github.com/koush/mvn-repo/master/maven.gradle'
// 2) gradle clean && gradle build && gradle uploadArchives
// 3) That's it!
apply plugin: 'maven'
apply plugin: 'signing'
if (hasProperty('sonatypeUsername') && hasProperty('sonatypePassword') && hasProperty('githubToken')) {
afterEvaluate { project ->
String user = null
String repo = null
'git remote -v'.execute(null, project.projectDir).getText().find('.*?git@github.com/(.*?)/(.*?) .*?') {
match ->
user = match[1]
repo = match[2]
}
String githubUrl = 'https://api.github.com/repos/' + user + '/' + repo
if (System.getenv().GITHUB_TOKEN)
githubUrl += '?access_token=' + System.getenv().GITHUB_TOKEN
def repoInfo = new groovy.json.JsonSlurper().parseText(new URL(githubUrl).getText())
def android_manifest
try {
android_manifest = new XmlParser(false, false).parseText(new File(project.projectDir, 'AndroidManifest.xml').getText())
}
catch (e) {
android_manifest = new XmlParser(false, false).parseText(new File(project.projectDir, 'src/main/AndroidManifest.xml').getText())
}
def versionName = android_manifest.'@android:versionName'
def package_name = android_manifest.'@package'
def artifact_id = project.projectDir.getName().toLowerCase()
project.version = versionName
project.group = package_name
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = package_name
pom.artifactId = artifact_id
pom.version = versionName
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: sonatypeUsername, password: sonatypePassword)
}
pom.project {
name repo
packaging 'jar'
description repoInfo.description
url repoInfo.html_url
scm {
url repoInfo.git_url
connection repoInfo.git_url
developerConnection repoInfo.ssh_url
}
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}
developers {
developer {
id user
name user
}
}
}
}
}
}
signing {
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
}
task androidJavadocsJar(type: Jar) {
classifier = 'javadoc'
baseName = artifact_id
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
baseName = artifact_id
from android.sourceSets.main.java.srcDirs
}
artifacts {
// archives packageReleaseJar
archives androidSourcesJar
archives androidJavadocsJar
}
}
}
================================================
FILE: AndroidAsync/proguard-project.txt
================================================
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keep class * extends com.koushikdutta.async.TapCallback {
public protected private *;
}
================================================
FILE: AndroidAsync/project.properties
================================================
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
android.library=true
================================================
FILE: AndroidAsync/res/.gitignore
================================================
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncDatagramSocket.java
================================================
package com.koushikdutta.async;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
public class AsyncDatagramSocket extends AsyncNetworkSocket {
public void disconnect() throws IOException {
socketAddress = null;
((DatagramChannelWrapper)getChannel()).disconnect();
}
@Override
public InetSocketAddress getRemoteAddress() {
if (isOpen())
return super.getRemoteAddress();
return ((DatagramChannelWrapper)getChannel()).getRemoteAddress();
}
public void connect(InetSocketAddress address) throws IOException {
socketAddress = address;
((DatagramChannelWrapper)getChannel()).mChannel.connect(address);
}
public void send(final String host, final int port, final ByteBuffer buffer) {
if (getServer().getAffinity() != Thread.currentThread()) {
getServer().run(new Runnable() {
@Override
public void run() {
send(host, port, buffer);
}
});
return;
}
try {
((DatagramChannelWrapper)getChannel()).mChannel.send(buffer, new InetSocketAddress(host, port));
}
catch (IOException e) {
// close();
// reportEndPending(e);
// reportClose(e);
}
}
public void send(final InetSocketAddress address, final ByteBuffer buffer) {
if (getServer().getAffinity() != Thread.currentThread()) {
getServer().run(new Runnable() {
@Override
public void run() {
send(address, buffer);
}
});
return;
}
try {
int sent = ((DatagramChannelWrapper)getChannel()).mChannel.send(buffer, new InetSocketAddress(address.getHostName(), address.getPort()));
}
catch (IOException e) {
// Log.e("SEND", "send error", e);
// close();
// reportEndPending(e);
// reportClose(e);
}
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java
================================================
package com.koushikdutta.async;
import android.util.Log;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.util.Allocator;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
public class AsyncNetworkSocket implements AsyncSocket {
AsyncNetworkSocket() {
}
@Override
public void end() {
mChannel.shutdownOutput();
}
public boolean isChunked() {
return mChannel.isChunked();
}
InetSocketAddress socketAddress;
void attach(SocketChannel channel, InetSocketAddress socketAddress) throws IOException {
this.socketAddress = socketAddress;
allocator = new Allocator();
mChannel = new SocketChannelWrapper(channel);
}
void attach(DatagramChannel channel) throws IOException {
mChannel = new DatagramChannelWrapper(channel);
// keep udp at roughly the mtu, which is 1540 or something
// letting it grow freaks out nio apparently.
allocator = new Allocator(8192);
}
ChannelWrapper getChannel() {
return mChannel;
}
public void onDataWritable() {
if (!mChannel.isChunked()) {
// turn write off
mKey.interestOps(~SelectionKey.OP_WRITE & mKey.interestOps());
}
if (mWriteableHandler != null)
mWriteableHandler.onWriteable();
}
private ChannelWrapper mChannel;
private SelectionKey mKey;
private AsyncServer mServer;
void setup(AsyncServer server, SelectionKey key) {
mServer = server;
mKey = key;
}
@Override
public void write(final ByteBufferList list) {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
write(list);
}
});
return;
}
if (!mChannel.isConnected()) {
return;
}
try {
int before = list.remaining();
ByteBuffer[] arr = list.getAllArray();
mChannel.write(arr);
list.addAll(arr);
handleRemaining(list.remaining());
mServer.onDataSent(before - list.remaining());
}
catch (IOException e) {
closeInternal();
reportEndPending(e);
reportClose(e);
}
}
private void handleRemaining(int remaining) throws IOException {
if (!mKey.isValid())
throw new IOException(new CancelledKeyException());
if (remaining > 0) {
// chunked channels should not fail
// register for a write notification if a write fails
// turn write on
mKey.interestOps(SelectionKey.OP_WRITE | mKey.interestOps());
}
else {
// turn write off
mKey.interestOps(~SelectionKey.OP_WRITE & mKey.interestOps());
}
}
private ByteBufferList pending = new ByteBufferList();
// private ByteBuffer[] buffers = new ByteBuffer[8];
Allocator allocator;
int onReadable() {
spitPending();
// even if the socket is paused,
// it may end up getting a queued readable event if it is
// already in the selector's ready queue.
if (mPaused)
return 0;
int total = 0;
boolean closed = false;
// ByteBufferList.obtainArray(buffers, Math.min(Math.max(mToAlloc, 2 << 11), maxAlloc));
ByteBuffer b = allocator.allocate();
// keep track of the max mount read during this read cycle
// so we can be quicker about allocations during the next
// time this socket reads.
long read;
try {
read = mChannel.read(b);
}
catch (Exception e) {
read = -1;
closeInternal();
reportEndPending(e);
reportClose(e);
}
if (read < 0) {
closeInternal();
closed = true;
}
else {
total += read;
}
if (read > 0) {
allocator.track(read);
b.flip();
// for (int i = 0; i < buffers.length; i++) {
// ByteBuffer b = buffers[i];
// buffers[i] = null;
// b.flip();
// pending.add(b);
// }
pending.add(b);
Util.emitAllData(this, pending);
}
else {
ByteBufferList.reclaim(b);
}
if (closed) {
reportEndPending(null);
reportClose(null);
}
return total;
}
boolean closeReported;
protected void reportClose(Exception e) {
if (closeReported)
return;
closeReported = true;
if (mClosedHander != null) {
mClosedHander.onCompleted(e);
mClosedHander = null;
}
}
@Override
public void close() {
closeInternal();
reportClose(null);
}
private void closeInternal() {
mKey.cancel();
try {
mChannel.close();
}
catch (IOException e) {
}
}
WritableCallback mWriteableHandler;
@Override
public void setWriteableCallback(WritableCallback handler) {
mWriteableHandler = handler;
}
DataCallback mDataHandler;
@Override
public void setDataCallback(DataCallback callback) {
mDataHandler = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataHandler;
}
CompletedCallback mClosedHander;
@Override
public void setClosedCallback(CompletedCallback handler) {
mClosedHander = handler;
}
@Override
public CompletedCallback getClosedCallback() {
return mClosedHander;
}
@Override
public WritableCallback getWriteableCallback() {
return mWriteableHandler;
}
void reportEnd(Exception e) {
if (mEndReported)
return;
mEndReported = true;
if (mCompletedCallback != null)
mCompletedCallback.onCompleted(e);
else if (e != null) {
Log.e("NIO", "Unhandled exception", e);
}
}
boolean mEndReported;
Exception mPendingEndException;
void reportEndPending(Exception e) {
if (pending.hasRemaining()) {
mPendingEndException = e;
return;
}
reportEnd(e);
}
private CompletedCallback mCompletedCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
mCompletedCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
return mCompletedCallback;
}
@Override
public boolean isOpen() {
return mChannel.isConnected() && mKey.isValid();
}
boolean mPaused = false;
@Override
public void pause() {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
pause();
}
});
return;
}
if (mPaused)
return;
mPaused = true;
try {
mKey.interestOps(~SelectionKey.OP_READ & mKey.interestOps());
}
catch (Exception ex) {
}
}
private void spitPending() {
if (pending.hasRemaining()) {
Util.emitAllData(this, pending);
}
}
@Override
public void resume() {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
resume();
}
});
return;
}
if (!mPaused)
return;
mPaused = false;
try {
mKey.interestOps(SelectionKey.OP_READ | mKey.interestOps());
}
catch (Exception ex) {
}
spitPending();
if (!isOpen())
reportEndPending(mPendingEndException);
}
@Override
public boolean isPaused() {
return mPaused;
}
@Override
public AsyncServer getServer() {
return mServer;
}
public InetSocketAddress getRemoteAddress() {
return socketAddress;
}
public InetAddress getLocalAddress() {
return mChannel.getLocalAddress();
}
public int getLocalPort() {
return mChannel.getLocalPort();
}
public Object getSocket() {
return getChannel().getSocket();
}
@Override
public String charset() {
return null;
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncSSLException.java
================================================
package com.koushikdutta.async;
public class AsyncSSLException extends Exception {
public AsyncSSLException(Throwable cause) {
super("Peer not trusted by any of the system trust managers.", cause);
}
private boolean mIgnore = false;
public void setIgnore(boolean ignore) {
mIgnore = ignore;
}
public boolean getIgnore() {
return mIgnore;
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncSSLServerSocket.java
================================================
package com.koushikdutta.async;
import java.security.PrivateKey;
import java.security.cert.Certificate;
public interface AsyncSSLServerSocket extends AsyncServerSocket {
PrivateKey getPrivateKey();
Certificate getCertificate();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocket.java
================================================
package com.koushikdutta.async;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLEngine;
public interface AsyncSSLSocket extends AsyncSocket {
public X509Certificate[] getPeerCertificates();
public SSLEngine getSSLEngine();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java
================================================
package com.koushikdutta.async;
import android.content.Context;
import android.os.Build;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.ConnectCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.ListenCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.future.SimpleCancellable;
import com.koushikdutta.async.http.SSLEngineSNIConfigurator;
import com.koushikdutta.async.util.Allocator;
import com.koushikdutta.async.util.StreamUtility;
import com.koushikdutta.async.wrapper.AsyncSocketWrapper;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket {
private static final String LOGTAG = "AsyncSSLSocketWrapper";
public interface HandshakeCallback {
public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket);
}
static SSLContext defaultSSLContext;
static SSLContext trustAllSSLContext;
static TrustManager[] trustAllManagers;
static HostnameVerifier trustAllVerifier;
AsyncSocket mSocket;
BufferedDataSink mSink;
boolean mUnwrapping;
SSLEngine engine;
boolean finishedHandshake;
private int mPort;
private String mHost;
private boolean mWrapping;
HostnameVerifier hostnameVerifier;
HandshakeCallback handshakeCallback;
X509Certificate[] peerCertificates;
WritableCallback mWriteableCallback;
DataCallback mDataCallback;
TrustManager[] trustManagers;
boolean clientMode;
static {
// following is the "trust the system" certs setup
try {
// critical extension 2.5.29.15 is implemented improperly prior to 4.0.3.
// https://code.google.com/p/android/issues/detail?id=9307
// https://groups.google.com/forum/?fromgroups=#!topic/netty/UCfqPPk5O4s
// certs that use this extension will throw in Cipher.java.
// fallback is to use a custom SSLContext, and hack around the x509 extension.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
throw new Exception();
defaultSSLContext = SSLContext.getInstance("Default");
}
catch (Exception ex) {
try {
defaultSSLContext = SSLContext.getInstance("TLS");
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
for (X509Certificate cert : certs) {
if (cert != null && cert.getCriticalExtensionOIDs() != null)
cert.getCriticalExtensionOIDs().remove("2.5.29.15");
}
}
} };
defaultSSLContext.init(null, trustAllCerts, null);
}
catch (Exception ex2) {
ex.printStackTrace();
ex2.printStackTrace();
}
}
try {
trustAllSSLContext = SSLContext.getInstance("TLS");
trustAllManagers = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
} };
trustAllSSLContext.init(null, trustAllManagers, null);
trustAllVerifier = (hostname, session) -> true;
}
catch (Exception ex2) {
ex2.printStackTrace();
}
}
public static SSLContext getDefaultSSLContext() {
return defaultSSLContext;
}
public static void handshake(AsyncSocket socket,
String host, int port,
SSLEngine sslEngine,
TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode,
final HandshakeCallback callback) {
AsyncSSLSocketWrapper wrapper = new AsyncSSLSocketWrapper(socket, host, port, sslEngine, trustManagers, verifier, clientMode);
wrapper.handshakeCallback = callback;
socket.setClosedCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (ex != null)
callback.onHandshakeCompleted(ex, null);
else
callback.onHandshakeCompleted(new SSLException("socket closed during handshake"), null);
}
});
try {
wrapper.engine.beginHandshake();
wrapper.handleHandshakeStatus(wrapper.engine.getHandshakeStatus());
} catch (SSLException e) {
wrapper.report(e);
}
}
public static Cancellable connectSocket(AsyncServer server, String host, int port, ConnectCallback callback) {
return connectSocket(server, host, port, false, callback);
}
public static Cancellable connectSocket(AsyncServer server, String host, int port, boolean trustAllCerts, ConnectCallback callback) {
SimpleCancellable cancellable = new SimpleCancellable();
Cancellable connect = server.connectSocket(host, port, (ex, netSocket) -> {
if (ex != null) {
if (cancellable.setComplete())
callback.onConnectCompleted(ex, null);
return;
}
handshake(netSocket, host, port,
(trustAllCerts ? trustAllSSLContext : defaultSSLContext).createSSLEngine(host, port),
trustAllCerts ? trustAllManagers : null,
trustAllCerts ? trustAllVerifier : null,
true, (e, socket) -> {
if (!cancellable.setComplete()) {
if (socket != null)
socket.close();
return;
}
if (e != null)
callback.onConnectCompleted(e, null);
else
callback.onConnectCompleted(null, socket);
});
});
cancellable.setParent(connect);
return cancellable;
}
boolean mEnded;
Exception mEndException;
final ByteBufferList pending = new ByteBufferList();
private AsyncSSLSocketWrapper(AsyncSocket socket,
String host, int port,
SSLEngine sslEngine,
TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode) {
mSocket = socket;
hostnameVerifier = verifier;
this.clientMode = clientMode;
this.trustManagers = trustManagers;
this.engine = sslEngine;
mHost = host;
mPort = port;
engine.setUseClientMode(clientMode);
mSink = new BufferedDataSink(socket);
mSink.setWriteableCallback(new WritableCallback() {
@Override
public void onWriteable() {
if (mWriteableCallback != null)
mWriteableCallback.onWriteable();
}
});
// on pause, the emitter is paused to prevent the buffered
// socket and itself from firing.
// on resume, emitter is resumed, ssl buffer is flushed as well
mSocket.setEndCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (mEnded)
return;
mEnded = true;
mEndException = ex;
if (!pending.hasRemaining() && mEndCallback != null)
mEndCallback.onCompleted(ex);
}
});
mSocket.setDataCallback(dataCallback);
}
final DataCallback dataCallback = new DataCallback() {
final Allocator allocator = new Allocator().setMinAlloc(8192);
final ByteBufferList buffered = new ByteBufferList();
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
if (mUnwrapping)
return;
try {
mUnwrapping = true;
bb.get(buffered);
if (buffered.hasRemaining()) {
ByteBuffer all = buffered.getAll();
buffered.add(all);
}
ByteBuffer b = ByteBufferList.EMPTY_BYTEBUFFER;
while (true) {
if (b.remaining() == 0 && buffered.size() > 0) {
b = buffered.remove();
}
int remaining = b.remaining();
int before = pending.remaining();
SSLEngineResult res;
{
// wrap to prevent access to the readBuf
ByteBuffer readBuf = allocator.allocate();
res = engine.unwrap(b, readBuf);
addToPending(pending, readBuf);
allocator.track(pending.remaining() - before);
}
if (res.getStatus() == Status.BUFFER_OVERFLOW) {
allocator.setMinAlloc(allocator.getMinAlloc() * 2);
remaining = -1;
}
else if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
buffered.addFirst(b);
if (buffered.size() <= 1) {
break;
}
// pack it
remaining = -1;
b = buffered.getAll();
buffered.addFirst(b);
b = ByteBufferList.EMPTY_BYTEBUFFER;
}
handleHandshakeStatus(res.getHandshakeStatus());
if (b.remaining() == remaining && before == pending.remaining()) {
buffered.addFirst(b);
break;
}
}
AsyncSSLSocketWrapper.this.onDataAvailable();
}
catch (SSLException ex) {
// ex.printStackTrace();
report(ex);
}
finally {
mUnwrapping = false;
}
}
};
public void onDataAvailable() {
Util.emitAllData(this, pending);
if (mEnded && !pending.hasRemaining() && mEndCallback != null)
mEndCallback.onCompleted(mEndException);
}
@Override
public SSLEngine getSSLEngine() {
return engine;
}
void addToPending(ByteBufferList out, ByteBuffer mReadTmp) {
mReadTmp.flip();
if (mReadTmp.hasRemaining()) {
out.add(mReadTmp);
}
else {
ByteBufferList.reclaim(mReadTmp);
}
}
@Override
public void end() {
mSocket.end();
}
public String getHost() {
return mHost;
}
public int getPort() {
return mPort;
}
private void handleHandshakeStatus(HandshakeStatus status) {
if (status == HandshakeStatus.NEED_TASK) {
final Runnable task = engine.getDelegatedTask();
task.run();
}
if (status == HandshakeStatus.NEED_WRAP) {
write(writeList);
}
if (status == HandshakeStatus.NEED_UNWRAP) {
dataCallback.onDataAvailable(this, new ByteBufferList());
}
try {
if (!finishedHandshake && (engine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING || engine.getHandshakeStatus() == HandshakeStatus.FINISHED)) {
if (clientMode) {
Exception peerUnverifiedCause = null;
boolean trusted = false;
try {
peerCertificates = (X509Certificate[]) engine.getSession().getPeerCertificates();
if (mHost != null) {
if (hostnameVerifier == null) {
StrictHostnameVerifier verifier = new StrictHostnameVerifier();
verifier.verify(mHost, StrictHostnameVerifier.getCNs(peerCertificates[0]), StrictHostnameVerifier.getDNSSubjectAlts(peerCertificates[0]));
}
else {
if (!hostnameVerifier.verify(mHost, engine.getSession())) {
throw new SSLException("hostname <" + mHost + "> has been denied");
}
}
}
trusted = true;
}
catch (SSLException ex) {
peerUnverifiedCause = ex;
}
finishedHandshake = true;
if (!trusted) {
AsyncSSLException e = new AsyncSSLException(peerUnverifiedCause);
report(e);
if (!e.getIgnore())
throw e;
}
}
else {
finishedHandshake = true;
}
handshakeCallback.onHandshakeCompleted(null, this);
handshakeCallback = null;
mSocket.setClosedCallback(null);
// handshake can complete during a wrap, so make sure that the call
// stack and wrap flag is cleared before invoking writable
getServer().post(new Runnable() {
@Override
public void run() {
if (mWriteableCallback != null)
mWriteableCallback.onWriteable();
}
});
onDataAvailable();
}
}
catch (Exception ex) {
report(ex);
}
}
int calculateAlloc(int remaining) {
// alloc 50% more than we need for writing
int alloc = remaining * 3 / 2;
if (alloc == 0)
alloc = 8192;
return alloc;
}
ByteBufferList writeList = new ByteBufferList();
@Override
public void write(ByteBufferList bb) {
if (mWrapping)
return;
if (mSink.remaining() > 0)
return;
mWrapping = true;
int remaining;
SSLEngineResult res = null;
ByteBuffer writeBuf = ByteBufferList.obtain(calculateAlloc(bb.remaining()));
do {
// if the handshake is finished, don't send
// 0 bytes of data, since that makes the ssl connection die.
// it wraps a 0 byte package, and craps out.
if (finishedHandshake && bb.remaining() == 0)
break;
remaining = bb.remaining();
try {
ByteBuffer[] arr = bb.getAllArray();
res = engine.wrap(arr, writeBuf);
bb.addAll(arr);
writeBuf.flip();
writeList.add(writeBuf);
if (writeList.remaining() > 0)
mSink.write(writeList);
int previousCapacity = writeBuf.capacity();
writeBuf = null;
if (res.getStatus() == Status.BUFFER_OVERFLOW) {
writeBuf = ByteBufferList.obtain(previousCapacity * 2);
remaining = -1;
}
else {
writeBuf = ByteBufferList.obtain(calculateAlloc(bb.remaining()));
handleHandshakeStatus(res.getHandshakeStatus());
}
}
catch (SSLException e) {
report(e);
}
}
while ((remaining != bb.remaining() || (res != null && res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)) && mSink.remaining() == 0);
mWrapping = false;
ByteBufferList.reclaim(writeBuf);
}
@Override
public void setWriteableCallback(WritableCallback handler) {
mWriteableCallback = handler;
}
@Override
public WritableCallback getWriteableCallback() {
return mWriteableCallback;
}
private void report(Exception e) {
final HandshakeCallback hs = handshakeCallback;
if (hs != null) {
handshakeCallback = null;
mSocket.setDataCallback(new DataCallback.NullDataCallback());
mSocket.end();
// handshake sets this callback. unset it.
mSocket.setClosedCallback(null);
mSocket.close();
hs.onHandshakeCompleted(e, null);
return;
}
CompletedCallback cb = getEndCallback();
if (cb != null)
cb.onCompleted(e);
}
@Override
public void setDataCallback(DataCallback callback) {
mDataCallback = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataCallback;
}
@Override
public boolean isChunked() {
return mSocket.isChunked();
}
@Override
public boolean isOpen() {
return mSocket.isOpen();
}
@Override
public void close() {
mSocket.close();
}
@Override
public void setClosedCallback(CompletedCallback handler) {
mSocket.setClosedCallback(handler);
}
@Override
public CompletedCallback getClosedCallback() {
return mSocket.getClosedCallback();
}
CompletedCallback mEndCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
mEndCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
return mEndCallback;
}
@Override
public void pause() {
mSocket.pause();
}
@Override
public void resume() {
mSocket.resume();
onDataAvailable();
}
@Override
public boolean isPaused() {
return mSocket.isPaused();
}
@Override
public AsyncServer getServer() {
return mSocket.getServer();
}
@Override
public AsyncSocket getSocket() {
return mSocket;
}
@Override
public DataEmitter getDataEmitter() {
return mSocket;
}
@Override
public X509Certificate[] getPeerCertificates() {
return peerCertificates;
}
@Override
public String charset() {
return null;
}
private static Certificate selfSign(KeyPair keyPair, String subjectDN) throws Exception
{
Provider bcProvider = new BouncyCastleProvider();
Security.addProvider(bcProvider);
long now = System.currentTimeMillis();
Date startDate = new Date(now);
X500Name dnName = new X500Name("CN=" + subjectDN);
BigInteger certSerialNumber = new BigInteger(Long.toString(now)); // <-- Using the current timestamp as the certificate serial number
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
calendar.add(Calendar.YEAR, 1); // <-- 1 Yr validity
Date endDate = calendar.getTime();
String signatureAlgorithm = "SHA256WithRSA"; // <-- Use appropriate signature algorithm based on your keyPair algorithm.
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.getPrivate());
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, keyPair.getPublic());
// Extensions --------------------------
// Basic Constraints
BasicConstraints basicConstraints = new BasicConstraints(true); // <-- true for CA, false for EndEntity
certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints); // Basic Constraints is usually marked as critical.
// -------------------------------------
return new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(contentSigner));
}
public static Pair selfSignCertificate(final Context context, String subjectName) throws Exception {
File keyPath = context.getFileStreamPath(subjectName + "-key.txt");
KeyPair pair;
Certificate cert;
try {
String[] keyParts = StreamUtility.readFile(keyPath).split("\n");
X509EncodedKeySpec pub = new X509EncodedKeySpec(Base64.decode(keyParts[0], 0));
PKCS8EncodedKeySpec priv = new PKCS8EncodedKeySpec(Base64.decode(keyParts[1], 0));
cert = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(Base64.decode(keyParts[2], 0)));
KeyFactory fact = KeyFactory.getInstance("RSA");
pair = new KeyPair(fact.generatePublic(pub), fact.generatePrivate(priv));
}
catch (Exception e) {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
pair = keyGen.generateKeyPair();
cert = selfSign(pair, subjectName);
StreamUtility.writeFile(keyPath,
Base64.encodeToString(pair.getPublic().getEncoded(), Base64.NO_WRAP)
+ "\n"
+ Base64.encodeToString(pair.getPrivate().getEncoded(), Base64.NO_WRAP)
+ "\n"
+ Base64.encodeToString(cert.getEncoded(), Base64.NO_WRAP));
}
return new Pair<>(pair, cert);
}
public static AsyncSSLServerSocket listenSecure(final Context context, final AsyncServer server, final String subjectName, final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
server.run(() -> {
try {
Pair keyCert = selfSignCertificate(context, subjectName);
KeyPair pair = keyCert.first;
Certificate cert = keyCert.second;
holder.held = listenSecure(server, pair.getPrivate(), cert, host, port, handler);
}
catch (Exception e) {
handler.onCompleted(e);
}
});
return holder.held;
}
public static AsyncSSLServerSocket listenSecure(AsyncServer server, String keyDer, String certDer, final InetAddress host, final int port, final ListenCallback handler) {
return listenSecure(server, Base64.decode(keyDer, Base64.DEFAULT), Base64.decode(certDer, Base64.DEFAULT), host, port, handler);
}
private static class ObjectHolder {
T held;
}
public static AsyncSSLServerSocket listenSecure(final AsyncServer server, final byte[] keyDer, final byte[] certDer, final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
server.run(() -> {
try {
PKCS8EncodedKeySpec key = new PKCS8EncodedKeySpec(keyDer);
Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(certDer));
PrivateKey pk = KeyFactory.getInstance("RSA").generatePrivate(key);
holder.held = listenSecure(server, pk, cert, host, port, handler);
}
catch (Exception e) {
handler.onCompleted(e);
}
});
return holder.held;
}
public static AsyncSSLServerSocket listenSecure(final AsyncServer server, final PrivateKey pk, final Certificate cert, final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
server.run(() -> {
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
ks.setKeyEntry("key", pk, null, new Certificate[] { cert });
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(ks, "".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
final AsyncServerSocket socket = listenSecure(server, sslContext, host, port, handler);
holder.held = new AsyncSSLServerSocket() {
@Override
public PrivateKey getPrivateKey() {
return pk;
}
@Override
public Certificate getCertificate() {
return cert;
}
@Override
public void stop() {
socket.stop();
}
@Override
public int getLocalPort() {
return socket.getLocalPort();
}
};
}
catch (Exception e) {
handler.onCompleted(e);
}
});
return holder.held;
}
public static AsyncServerSocket listenSecure(AsyncServer server, final SSLContext sslContext, final InetAddress host, final int port, final ListenCallback handler) {
final SSLEngineSNIConfigurator conf = new SSLEngineSNIConfigurator() {
@Override
public SSLEngine createEngine(SSLContext sslContext, String peerHost, int peerPort) {
SSLEngine engine = super.createEngine(sslContext, peerHost, peerPort);
// String[] ciphers = engine.getEnabledCipherSuites();
// for (String cipher: ciphers) {
// Log.i(LOGTAG, cipher);
// }
// todo: what's this for? some vestigal vysor code i think. required by audio mirroring?
engine.setEnabledCipherSuites(new String[] { "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" });
return engine;
}
};
return server.listen(host, port, new ListenCallback() {
@Override
public void onAccepted(final AsyncSocket socket) {
AsyncSSLSocketWrapper.handshake(socket, null, port, conf.createEngine(sslContext, null, port), null, null, false,
(e, sslSocket) -> {
if (e != null) {
// chrome seems to do some sort of SSL probe and cancels handshakes. not sure why.
// i suspect it is to pick an optimal strong cipher.
// seeing a lot of the following in the log (but no actual connection errors)
// javax.net.ssl.SSLHandshakeException: error:10000416:SSL routines:OPENSSL_internal:SSLV3_ALERT_CERTIFICATE_UNKNOWN
// seen on Shield TV running API 26
// todo fix: conscrypt ssl context?
// Log.e(LOGTAG, "Error while handshaking", e);
socket.close();
return;
}
handler.onAccepted(sslSocket);
});
}
@Override
public void onListening(AsyncServerSocket socket) {
handler.onListening(socket);
}
@Override
public void onCompleted(Exception ex) {
handler.onCompleted(ex);
}
});
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncSemaphore.java
================================================
package com.koushikdutta.async;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class AsyncSemaphore {
Semaphore semaphore = new Semaphore(0);
public void acquire() throws InterruptedException {
ThreadQueue threadQueue = ThreadQueue.getOrCreateThreadQueue(Thread.currentThread());
AsyncSemaphore last = threadQueue.waiter;
threadQueue.waiter = this;
Semaphore queueSemaphore = threadQueue.queueSemaphore;
try {
if (semaphore.tryAcquire())
return;
while (true) {
// run the queue
while (true) {
Runnable run = threadQueue.remove();
if (run == null)
break;
// Log.i(LOGTAG, "Pumping for AsyncSemaphore");
run.run();
}
int permits = Math.max(1, queueSemaphore.availablePermits());
queueSemaphore.acquire(permits);
if (semaphore.tryAcquire())
break;
}
}
finally {
threadQueue.waiter = last;
}
}
public boolean tryAcquire(long timeout, TimeUnit timeunit) throws InterruptedException {
long timeoutMs = TimeUnit.MILLISECONDS.convert(timeout, timeunit);
ThreadQueue threadQueue = ThreadQueue.getOrCreateThreadQueue(Thread.currentThread());
AsyncSemaphore last = threadQueue.waiter;
threadQueue.waiter = this;
Semaphore queueSemaphore = threadQueue.queueSemaphore;
try {
if (semaphore.tryAcquire())
return true;
long start = System.currentTimeMillis();
do {
// run the queue
while (true) {
Runnable run = threadQueue.remove();
if (run == null)
break;
// Log.i(LOGTAG, "Pumping for AsyncSemaphore");
run.run();
}
int permits = Math.max(1, queueSemaphore.availablePermits());
if (!queueSemaphore.tryAcquire(permits, timeoutMs, TimeUnit.MILLISECONDS))
return false;
if (semaphore.tryAcquire())
return true;
}
while (System.currentTimeMillis() - start < timeoutMs);
return false;
}
finally {
threadQueue.waiter = last;
}
}
public void release() {
semaphore.release();
ThreadQueue.release(this);
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
================================================
package com.koushikdutta.async;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.ConnectCallback;
import com.koushikdutta.async.callback.ListenCallback;
import com.koushikdutta.async.callback.SocketCreateCallback;
import com.koushikdutta.async.callback.ValueFunction;
import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.async.future.SimpleCancellable;
import com.koushikdutta.async.future.SimpleFuture;
import com.koushikdutta.async.util.StreamUtility;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AsyncServer {
public static final String LOGTAG = "NIO";
private static class RunnableWrapper implements Runnable {
boolean hasRun;
Runnable runnable;
ThreadQueue threadQueue;
Handler handler;
@Override
public void run() {
synchronized (this) {
if (hasRun)
return;
hasRun = true;
}
try {
runnable.run();
}
finally {
threadQueue.remove(this);
handler.removeCallbacks(this);
threadQueue = null;
handler = null;
runnable = null;
}
}
}
public static void post(Handler handler, Runnable runnable) {
RunnableWrapper wrapper = new RunnableWrapper();
ThreadQueue threadQueue = ThreadQueue.getOrCreateThreadQueue(handler.getLooper().getThread());
wrapper.threadQueue = threadQueue;
wrapper.handler = handler;
wrapper.runnable = runnable;
// run it in a blocking AsyncSemaphore or a Handler, whichever gets to it first.
threadQueue.add(wrapper);
handler.post(wrapper);
// run the queue if the thread is blocking
threadQueue.queueSemaphore.release();
}
static {
try {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.FROYO) {
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
}
}
catch (Throwable ex) {
}
}
static AsyncServer mInstance = new AsyncServer();
public static AsyncServer getDefault() {
return mInstance;
}
private SelectorWrapper mSelector;
public boolean isRunning() {
return mSelector != null;
}
String mName;
public AsyncServer() {
this(null);
}
public AsyncServer(String name) {
if (name == null)
name = "AsyncServer";
mName = name;
}
private static ExecutorService synchronousWorkers = newSynchronousWorkers("AsyncServer-worker-");
private static void wakeup(final SelectorWrapper selector) {
synchronousWorkers.execute(() -> {
try {
selector.wakeupOnce();
}
catch (Exception e) {
}
});
}
boolean killed;
public void kill() {
synchronized (this) {
killed = true;
}
stop(false);
}
int postCounter = 0;
public Cancellable postDelayed(Runnable runnable, long delay) {
Scheduled s;
synchronized (this) {
if (killed)
return SimpleCancellable.CANCELLED;
// Calculate when to run this queue item:
// If there is a delay (non-zero), add it to the current time
// When delay is zero, ensure that this follows all other
// zero-delay queue items. This is done by setting the
// "time" to the queue size. This will make sure it is before
// all time-delayed queue items (for all real world scenarios)
// as it will always be less than the current time and also remain
// behind all other immediately run queue items.
long time;
if (delay > 0)
time = SystemClock.elapsedRealtime() + delay;
else if (delay == 0)
time = postCounter++;
else if (mQueue.size() > 0)
time = Math.min(0, mQueue.peek().time - 1);
else
time = 0;
mQueue.add(s = new Scheduled(this, runnable, time));
// start the server up if necessary
if (mSelector == null)
run();
if (!isAffinityThread()) {
wakeup(mSelector);
}
}
return s;
}
public Cancellable postImmediate(Runnable runnable) {
if (Thread.currentThread() == getAffinity()) {
runnable.run();
return null;
}
return postDelayed(runnable, -1);
}
public Cancellable post(Runnable runnable) {
return postDelayed(runnable, 0);
}
public Cancellable post(final CompletedCallback callback, final Exception e) {
return post(() -> callback.onCompleted(e));
}
public void run(final Runnable runnable) {
if (Thread.currentThread() == mAffinity) {
post(runnable);
lockAndRunQueue(this, mQueue);
return;
}
final Semaphore semaphore;
synchronized (this) {
if (killed)
return;
semaphore = new Semaphore(0);
post(() -> {
runnable.run();
semaphore.release();
});
}
try {
semaphore.acquire();
}
catch (InterruptedException e) {
Log.e(LOGTAG, "run", e);
}
}
private static class Scheduled implements Cancellable, Runnable {
// this constructor is only called when the async execution should not be preserved
// ie... AsyncServer.stop.
public Scheduled(AsyncServer server, Runnable runnable, long time) {
this.server = server;
this.runnable = runnable;
this.time = time;
}
public AsyncServer server;
public Runnable runnable;
public long time;
@Override
public void run() {
this.runnable.run();
}
@Override
public boolean isDone() {
synchronized (server) {
return !cancelled && !server.mQueue.contains(this);
}
}
boolean cancelled;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean cancel() {
synchronized (server) {
return cancelled = server.mQueue.remove(this);
}
}
}
PriorityQueue mQueue = new PriorityQueue(1, Scheduler.INSTANCE);
static class Scheduler implements Comparator {
public static Scheduler INSTANCE = new Scheduler();
private Scheduler() {
}
@Override
public int compare(Scheduled s1, Scheduled s2) {
// keep the smaller ones at the head, so they get tossed out quicker
if (s1.time == s2.time)
return 0;
if (s1.time > s2.time)
return 1;
return -1;
}
}
public void stop() {
stop(false);
}
public void stop(boolean wait) {
// Log.i(LOGTAG, "****AsyncServer is shutting down.****");
final SelectorWrapper currentSelector;
final Semaphore semaphore;
final boolean isAffinityThread;
synchronized (this) {
isAffinityThread = isAffinityThread();
currentSelector = mSelector;
if (currentSelector == null)
return;
semaphore = new Semaphore(0);
// post a shutdown and wait
mQueue.add(new Scheduled(this, new Runnable() {
@Override
public void run() {
shutdownEverything(currentSelector);
semaphore.release();
}
}, 0));
synchronousWorkers.execute(() -> {
try {
currentSelector.wakeupOnce();
}
catch (Exception e) {
}
});
// force any existing connections to die
shutdownKeys(currentSelector);
mQueue = new PriorityQueue<>(1, Scheduler.INSTANCE);
mSelector = null;
mAffinity = null;
}
try {
if (!isAffinityThread && wait)
semaphore.acquire();
}
catch (Exception e) {
}
}
protected void onDataReceived(int transmitted) {
}
protected void onDataSent(int transmitted) {
}
private static class ObjectHolder {
T held;
}
public AsyncServerSocket listen(final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
run(new Runnable() {
@Override
public void run() {
ServerSocketChannel closeableServer = null;
ServerSocketChannelWrapper closeableWrapper = null;
try {
closeableServer = ServerSocketChannel.open();
closeableWrapper = new ServerSocketChannelWrapper(
closeableServer);
final ServerSocketChannel server = closeableServer;
final ServerSocketChannelWrapper wrapper = closeableWrapper;
InetSocketAddress isa;
if (host == null)
isa = new InetSocketAddress(port);
else
isa = new InetSocketAddress(host, port);
server.socket().bind(isa);
final SelectionKey key = wrapper.register(mSelector.getSelector());
key.attach(handler);
handler.onListening(holder.held = new AsyncServerSocket() {
@Override
public int getLocalPort() {
return server.socket().getLocalPort();
}
@Override
public void stop() {
StreamUtility.closeQuietly(wrapper);
try {
key.cancel();
}
catch (Exception e) {
}
}
});
}
catch (IOException e) {
Log.e(LOGTAG, "wtf", e);
StreamUtility.closeQuietly(closeableWrapper, closeableServer);
handler.onCompleted(e);
}
}
});
return holder.held;
}
private class ConnectFuture extends SimpleFuture {
@Override
protected void cancelCleanup() {
super.cancelCleanup();
try {
if (socket != null)
socket.close();
}
catch (IOException e) {
}
}
SocketChannel socket;
ConnectCallback callback;
}
public Cancellable connectResolvedInetSocketAddress(final InetSocketAddress address, final ConnectCallback callback) {
return connectResolvedInetSocketAddress(address, callback, null);
}
public ConnectFuture connectResolvedInetSocketAddress(final InetSocketAddress address, final ConnectCallback callback, final SocketCreateCallback createCallback) {
final ConnectFuture cancel = new ConnectFuture();
post(new Runnable() {
@Override
public void run() {
if (cancel.isCancelled())
return;
cancel.callback = callback;
SelectionKey ckey = null;
SocketChannel socket = null;
try {
socket = cancel.socket = SocketChannel.open();
socket.configureBlocking(false);
ckey = socket.register(mSelector.getSelector(), SelectionKey.OP_CONNECT);
ckey.attach(cancel);
if (createCallback != null)
createCallback.onSocketCreated(socket.socket().getLocalPort());
socket.connect(address);
}
catch (Throwable e) {
if (ckey != null)
ckey.cancel();
StreamUtility.closeQuietly(socket);
cancel.setComplete(new RuntimeException(e));
}
}
});
return cancel;
}
public Cancellable connectSocket(final InetSocketAddress remote, final ConnectCallback callback) {
if (!remote.isUnresolved())
return connectResolvedInetSocketAddress(remote, callback);
final SimpleFuture ret = new SimpleFuture();
Future lookup = getByName(remote.getHostName());
ret.setParent(lookup);
lookup
.setCallback(new FutureCallback() {
@Override
public void onCompleted(Exception e, InetAddress result) {
if (e != null) {
callback.onConnectCompleted(e, null);
ret.setComplete(e);
return;
}
ret.setComplete((ConnectFuture)connectResolvedInetSocketAddress(new InetSocketAddress(result, remote.getPort()), callback));
}
});
return ret;
}
public Cancellable connectSocket(final String host, final int port, final ConnectCallback callback) {
return connectSocket(InetSocketAddress.createUnresolved(host, port), callback);
}
private static ExecutorService newSynchronousWorkers(String prefix) {
ThreadFactory tf = new NamedThreadFactory(prefix);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(0, 4, 10L,
TimeUnit.SECONDS, new LinkedBlockingQueue(), tf);
return tpe;
}
private static final Comparator ipSorter = new Comparator() {
@Override
public int compare(InetAddress lhs, InetAddress rhs) {
if (lhs instanceof Inet4Address && rhs instanceof Inet4Address)
return 0;
if (lhs instanceof Inet6Address && rhs instanceof Inet6Address)
return 0;
if (lhs instanceof Inet4Address && rhs instanceof Inet6Address)
return -1;
return 1;
}
};
private static ExecutorService synchronousResolverWorkers = newSynchronousWorkers("AsyncServer-resolver-");
public Future getAllByName(final String host) {
final SimpleFuture ret = new SimpleFuture();
synchronousResolverWorkers.execute(new Runnable() {
@Override
public void run() {
try {
final InetAddress[] result = InetAddress.getAllByName(host);
Arrays.sort(result, ipSorter);
if (result == null || result.length == 0)
throw new HostnameResolutionException("no addresses for host");
post(new Runnable() {
@Override
public void run() {
ret.setComplete(null, result);
}
});
} catch (final Exception e) {
post(new Runnable() {
@Override
public void run() {
ret.setComplete(e, null);
}
});
}
}
});
return ret;
}
public Future getByName(String host) {
return getAllByName(host).thenConvert(addresses -> addresses[0]);
}
private void handleSocket(final AsyncNetworkSocket handler) throws ClosedChannelException {
final ChannelWrapper sc = handler.getChannel();
SelectionKey ckey = sc.register(mSelector.getSelector());
ckey.attach(handler);
handler.setup(this, ckey);
}
public AsyncDatagramSocket connectDatagram(final String host, final int port) throws IOException {
final DatagramChannel socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
run(new Runnable() {
@Override
public void run() {
try {
final SocketAddress remote = new InetSocketAddress(host, port);
handleSocket(handler);
socket.connect(remote);
}
catch (IOException e) {
Log.e(LOGTAG, "Datagram error", e);
StreamUtility.closeQuietly(socket);
}
}
});
return handler;
}
public AsyncDatagramSocket openDatagram() {
return openDatagram(null, 0, false);
}
public Cancellable createDatagram(String address, int port, boolean reuseAddress, FutureCallback callback) {
return createDatagram(() -> InetAddress.getByName(address), port, reuseAddress, callback);
}
public Cancellable createDatagram(InetAddress address, int port, boolean reuseAddress, FutureCallback callback) {
return createDatagram(() -> address, port, reuseAddress, callback);
}
private Cancellable createDatagram(ValueFunction inetAddressValueFunction, final int port, final boolean reuseAddress, FutureCallback callback) {
SimpleFuture ret = new SimpleFuture<>();
ret.setCallback(callback);
post(() -> {
DatagramChannel socket = null;
try {
socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
InetSocketAddress address;
if (inetAddressValueFunction == null)
address = new InetSocketAddress(port);
else
address = new InetSocketAddress(inetAddressValueFunction.getValue(), port);
if (reuseAddress)
socket.socket().setReuseAddress(reuseAddress);
socket.socket().bind(address);
handleSocket(handler);
if (!ret.setComplete(handler))
socket.close();
}
catch (Exception e) {
StreamUtility.closeQuietly(socket);
ret.setComplete(e);
}
});
return ret;
}
public AsyncDatagramSocket openDatagram(final InetAddress host, final int port, final boolean reuseAddress) {
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
Runnable runnable = () -> {
final DatagramChannel socket;
try {
socket = DatagramChannel.open();
}
catch (Exception e) {
return;
}
try {
handler.attach(socket);
InetSocketAddress address;
if (host == null)
address = new InetSocketAddress(port);
else
address = new InetSocketAddress(host, port);
if (reuseAddress)
socket.socket().setReuseAddress(reuseAddress);
socket.socket().bind(address);
handleSocket(handler);
}
catch (IOException e) {
Log.e(LOGTAG, "Datagram error", e);
StreamUtility.closeQuietly(socket);
}
};
if (getAffinity() != Thread.currentThread()) {
run(runnable);
return handler;
}
runnable.run();
return handler;
}
public AsyncDatagramSocket connectDatagram(final SocketAddress remote) throws IOException {
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
final DatagramChannel socket = DatagramChannel.open();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
Runnable runnable = () -> {
try {
handleSocket(handler);
socket.connect(remote);
}
catch (IOException e) {
StreamUtility.closeQuietly(socket);
}
};
if (getAffinity() != Thread.currentThread()) {
run(runnable);
return handler;
}
runnable.run();
return handler;
}
final private static ThreadLocal threadServer = new ThreadLocal<>();
public static AsyncServer getCurrentThreadServer() {
return threadServer.get();
}
Thread mAffinity;
private void run() {
final SelectorWrapper selector;
final PriorityQueue queue;
synchronized (this) {
if (mSelector == null) {
try {
selector = mSelector = new SelectorWrapper(SelectorProvider.provider().openSelector());
queue = mQueue;
}
catch (IOException e) {
throw new RuntimeException("unable to create selector?", e);
}
mAffinity = new Thread(mName) {
public void run() {
try {
threadServer.set(AsyncServer.this);
AsyncServer.run(AsyncServer.this, selector, queue);
}
finally {
threadServer.remove();
}
}
};
mAffinity.start();
// kicked off the new thread, let's bail.
return;
}
// this is a reentrant call
selector = mSelector;
queue = mQueue;
// fall through to outside of the synchronization scope
// to allow the thread to run without locking.
}
try {
runLoop(this, selector, queue);
}
catch (AsyncSelectorException e) {
Log.i(LOGTAG, "Selector closed", e);
try {
// StreamUtility.closeQuiety is throwing ArrayStoreException?
selector.getSelector().close();
}
catch (Exception ex) {
}
}
}
private static void run(final AsyncServer server, final SelectorWrapper selector, final PriorityQueue queue) {
// Log.i(LOGTAG, "****AsyncServer is starting.****");
// at this point, this local queue and selector are owned
// by this thread.
// if a stop is called, the instance queue and selector
// will be replaced and nulled respectively.
// this will allow the old queue and selector to shut down
// gracefully, while also allowing a new selector thread
// to start up while the old one is still shutting down.
while(true) {
try {
runLoop(server, selector, queue);
}
catch (AsyncSelectorException e) {
if (!(e.getCause() instanceof ClosedSelectorException))
Log.i(LOGTAG, "Selector exception, shutting down", e);
StreamUtility.closeQuietly(selector);
}
// see if we keep looping, this must be in a synchronized block since the queue is accessed.
synchronized (server) {
if (selector.isOpen() && (selector.keys().size() > 0 || queue.size() > 0))
continue;
shutdownEverything(selector);
if (server.mSelector == selector) {
server.mQueue = new PriorityQueue(1, Scheduler.INSTANCE);
server.mSelector = null;
server.mAffinity = null;
}
break;
}
}
// Log.i(LOGTAG, "****AsyncServer has shut down.****");
}
private static void shutdownKeys(SelectorWrapper selector) {
try {
for (SelectionKey key: selector.keys()) {
StreamUtility.closeQuietly(key.channel());
try {
key.cancel();
}
catch (Exception e) {
}
}
}
catch (Exception ex) {
}
}
private static void shutdownEverything(SelectorWrapper selector) {
shutdownKeys(selector);
// SHUT. DOWN. EVERYTHING.
StreamUtility.closeQuietly(selector);
}
private static final long QUEUE_EMPTY = Long.MAX_VALUE;
private static long lockAndRunQueue(final AsyncServer server, final PriorityQueue queue) {
long wait = QUEUE_EMPTY;
// find the first item we can actually run
while (true) {
Scheduled run = null;
synchronized (server) {
long now = SystemClock.elapsedRealtime();
if (queue.size() > 0) {
Scheduled s = queue.remove();
if (s.time <= now) {
run = s;
}
else {
wait = s.time - now;
queue.add(s);
}
}
}
if (run == null)
break;
run.run();
}
server.postCounter = 0;
return wait;
}
private static class AsyncSelectorException extends IOException {
public AsyncSelectorException(Exception e) {
super(e);
}
}
private static void runLoop(final AsyncServer server, final SelectorWrapper selector, final PriorityQueue queue) throws AsyncSelectorException {
// Log.i(LOGTAG, "Keys: " + selector.keys().size());
boolean needsSelect = true;
// run the queue to populate the selector with keys
long wait = lockAndRunQueue(server, queue);
try {
synchronized (server) {
// select now to see if anything is ready immediately. this
// also clears the canceled key queue.
int readyNow = selector.selectNow();
if (readyNow == 0) {
// if there is nothing to select now, make sure we don't have an empty key set
// which means it would be time to turn this thread off.
if (selector.keys().size() == 0 && wait == QUEUE_EMPTY) {
// Log.i(LOGTAG, "Shutting down. keys: " + selector.keys().size() + " keepRunning: " + keepRunning);
return;
}
}
else {
needsSelect = false;
}
}
if (needsSelect) {
if (wait == QUEUE_EMPTY) {
// wait until woken up
selector.select();
}
else {
// nothing to select immediately but there's something pending so let's block that duration and wait.
selector.select(wait);
}
}
}
catch (Exception e) {
throw new AsyncSelectorException(e);
}
// process whatever keys are ready
Set readyKeys = selector.selectedKeys();
for (SelectionKey key: readyKeys) {
try {
if (key.isAcceptable()) {
ServerSocketChannel nextReady = (ServerSocketChannel) key.channel();
SocketChannel sc = null;
SelectionKey ckey = null;
try {
sc = nextReady.accept();
if (sc == null)
continue;
sc.configureBlocking(false);
ckey = sc.register(selector.getSelector(), SelectionKey.OP_READ);
ListenCallback serverHandler = (ListenCallback) key.attachment();
AsyncNetworkSocket handler = new AsyncNetworkSocket();
handler.attach(sc, (InetSocketAddress)sc.socket().getRemoteSocketAddress());
handler.setup(server, ckey);
ckey.attach(handler);
serverHandler.onAccepted(handler);
}
catch (IOException e) {
StreamUtility.closeQuietly(sc);
if (ckey != null)
ckey.cancel();
}
}
else if (key.isReadable()) {
AsyncNetworkSocket handler = (AsyncNetworkSocket) key.attachment();
int transmitted = handler.onReadable();
server.onDataReceived(transmitted);
}
else if (key.isWritable()) {
AsyncNetworkSocket handler = (AsyncNetworkSocket) key.attachment();
handler.onDataWritable();
}
else if (key.isConnectable()) {
ConnectFuture cancel = (ConnectFuture) key.attachment();
SocketChannel sc = (SocketChannel) key.channel();
key.interestOps(SelectionKey.OP_READ);
AsyncNetworkSocket newHandler;
try {
sc.finishConnect();
newHandler = new AsyncNetworkSocket();
newHandler.setup(server, key);
newHandler.attach(sc, (InetSocketAddress)sc.socket().getRemoteSocketAddress());
key.attach(newHandler);
}
catch (IOException ex) {
key.cancel();
StreamUtility.closeQuietly(sc);
if (cancel.setComplete(ex))
cancel.callback.onConnectCompleted(ex, null);
continue;
}
if (cancel.setComplete(newHandler))
cancel.callback.onConnectCompleted(null, newHandler);
}
else {
Log.i(LOGTAG, "wtf");
throw new RuntimeException("Unknown key state.");
}
}
catch (CancelledKeyException ex) {
}
}
readyKeys.clear();
}
public void dump() {
post(new Runnable() {
@Override
public void run() {
if (mSelector == null) {
Log.i(LOGTAG, "Server dump not possible. No selector?");
return;
}
Log.i(LOGTAG, "Key Count: " + mSelector.keys().size());
for (SelectionKey key: mSelector.keys()) {
Log.i(LOGTAG, "Key: " + key);
}
}
});
}
public Thread getAffinity() {
return mAffinity;
}
public boolean isAffinityThread() {
return mAffinity == Thread.currentThread();
}
public boolean isAffinityThreadOrStopped() {
Thread affinity = mAffinity;
return affinity == null || affinity == Thread.currentThread();
}
private static class NamedThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
NamedThreadFactory(String namePrefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
this.namePrefix = namePrefix;
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncServerSocket.java
================================================
package com.koushikdutta.async;
public interface AsyncServerSocket {
public void stop();
public int getLocalPort();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/AsyncSocket.java
================================================
package com.koushikdutta.async;
public interface AsyncSocket extends DataEmitter, DataSink {
public AsyncServer getServer();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/BufferedDataSink.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.WritableCallback;
public class BufferedDataSink implements DataSink {
DataSink mDataSink;
public BufferedDataSink(DataSink datasink) {
setDataSink(datasink);
}
public boolean isBuffering() {
return mPendingWrites.hasRemaining() || forceBuffering;
}
public boolean isWritable() {
synchronized (mPendingWrites) {
return mPendingWrites.remaining() < mMaxBuffer;
}
}
public DataSink getDataSink() {
return mDataSink;
}
boolean forceBuffering;
public void forceBuffering(boolean forceBuffering) {
this.forceBuffering = forceBuffering;
if (!forceBuffering)
writePending();
}
public void setDataSink(DataSink datasink) {
mDataSink = datasink;
mDataSink.setWriteableCallback(this::writePending);
}
private void writePending() {
if (forceBuffering)
return;
// Log.i("NIO", "Writing to buffer...");
boolean empty;
synchronized (mPendingWrites) {
mDataSink.write(mPendingWrites);
empty = mPendingWrites.isEmpty();
}
if (empty) {
if (endPending)
mDataSink.end();
}
if (empty && mWritable != null)
mWritable.onWriteable();
}
final ByteBufferList mPendingWrites = new ByteBufferList();
// before the data is queued, let inheritors know. allows for filters, without
// issues with having to filter before writing which may fail in the buffer.
protected void onDataAccepted(ByteBufferList bb) {
}
@Override
public void write(final ByteBufferList bb) {
if (getServer().getAffinity() != Thread.currentThread()) {
synchronized (mPendingWrites) {
if (mPendingWrites.remaining() >= mMaxBuffer)
return;
onDataAccepted(bb);
bb.get(mPendingWrites);
}
getServer().post(this::writePending);
return;
}
onDataAccepted(bb);
if (!isBuffering())
mDataSink.write(bb);
synchronized (mPendingWrites) {
bb.get(mPendingWrites);
}
}
WritableCallback mWritable;
@Override
public void setWriteableCallback(WritableCallback handler) {
mWritable = handler;
}
@Override
public WritableCallback getWriteableCallback() {
return mWritable;
}
public int remaining() {
return mPendingWrites.remaining();
}
int mMaxBuffer = Integer.MAX_VALUE;
public int getMaxBuffer() {
return mMaxBuffer;
}
public void setMaxBuffer(int maxBuffer) {
mMaxBuffer = maxBuffer;
}
@Override
public boolean isOpen() {
return mDataSink.isOpen();
}
boolean endPending;
@Override
public void end() {
if (getServer().getAffinity() != Thread.currentThread()) {
getServer().post(this::end);
return;
}
synchronized (mPendingWrites) {
if (mPendingWrites.hasRemaining()) {
endPending = true;
return;
}
}
mDataSink.end();
}
@Override
public void setClosedCallback(CompletedCallback handler) {
mDataSink.setClosedCallback(handler);
}
@Override
public CompletedCallback getClosedCallback() {
return mDataSink.getClosedCallback();
}
@Override
public AsyncServer getServer() {
return mDataSink.getServer();
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java
================================================
package com.koushikdutta.async;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Looper;
import com.koushikdutta.async.util.ArrayDeque;
import com.koushikdutta.async.util.Charsets;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.PriorityQueue;
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public class ByteBufferList {
ArrayDeque mBuffers = new ArrayDeque();
ByteOrder order = ByteOrder.BIG_ENDIAN;
public ByteOrder order() {
return order;
}
public ByteBufferList order(ByteOrder order) {
this.order = order;
return this;
}
public ByteBufferList() {
}
public ByteBufferList(ByteBuffer... b) {
addAll(b);
}
public ByteBufferList(byte[] buf) {
super();
ByteBuffer b = ByteBuffer.wrap(buf);
add(b);
}
public ByteBufferList addAll(ByteBuffer... bb) {
for (ByteBuffer b: bb)
add(b);
return this;
}
public ByteBufferList addAll(ByteBufferList... bb) {
for (ByteBufferList b: bb)
b.get(this);
return this;
}
public byte[] getBytes(int length) {
byte[] ret = new byte[length];
get(ret);
return ret;
}
public byte[] getAllByteArray() {
byte[] ret = new byte[remaining()];
get(ret);
return ret;
}
public ByteBuffer[] getAllArray() {
ByteBuffer[] ret = new ByteBuffer[mBuffers.size()];
ret = mBuffers.toArray(ret);
mBuffers.clear();
remaining = 0;
return ret;
}
public boolean isEmpty() {
return remaining == 0;
}
private int remaining = 0;
public int remaining() {
return remaining;
}
public boolean hasRemaining() {
return remaining() > 0;
}
public short peekShort() {
return read(2).getShort(mBuffers.peekFirst().position());
}
public byte peek() {
return read(1).get(mBuffers.peekFirst().position());
}
public int peekInt() {
return read(4).getInt(mBuffers.peekFirst().position());
}
public long peekLong() {
return read(8).getLong(mBuffers.peekFirst().position());
}
public byte[] peekBytes(int size) {
byte[] ret = new byte[size];
read(size).get(ret, mBuffers.peekFirst().position(), ret.length);
return ret;
}
public ByteBufferList skip(int length) {
get(null, 0, length);
return this;
}
public int getInt() {
int ret = read(4).getInt();
remaining -= 4;
return ret;
}
public char getByteChar() {
char ret = (char)read(1).get();
remaining--;
return ret;
}
public short getShort() {
short ret = read(2).getShort();
remaining -= 2;
return ret;
}
public byte get() {
byte ret = read(1).get();
remaining--;
return ret;
}
public long getLong() {
long ret = read(8).getLong();
remaining -= 8;
return ret;
}
public void get(byte[] bytes) {
get(bytes, 0, bytes.length);
}
public void get(byte[] bytes, int offset, int length) {
if (remaining() < length)
throw new IllegalArgumentException("length");
int need = length;
while (need > 0) {
ByteBuffer b = mBuffers.peek();
int read = Math.min(b.remaining(), need);
if (bytes != null){
b.get(bytes, offset, read);
} else {
//when bytes is null, just skip data.
b.position(b.position() + read);
}
need -= read;
offset += read;
if (b.remaining() == 0) {
ByteBuffer removed = mBuffers.remove();
reclaim(b);
}
}
remaining -= length;
}
public void get(ByteBufferList into, int length) {
if (remaining() < length)
throw new IllegalArgumentException("length");
int offset = 0;
while (offset < length) {
ByteBuffer b = mBuffers.remove();
int remaining = b.remaining();
if (remaining == 0) {
reclaim(b);
continue;
}
if (offset + remaining > length) {
int need = length - offset;
// this is shared between both
ByteBuffer subset = obtain(need);
subset.limit(need);
b.get(subset.array(), 0, need);
into.add(subset);
mBuffers.addFirst(b);
break;
}
else {
// this belongs to the new list
into.add(b);
}
offset += remaining;
}
remaining -= length;
}
public void get(ByteBufferList into) {
get(into, remaining());
}
public ByteBufferList get(int length) {
ByteBufferList ret = new ByteBufferList();
get(ret, length);
return ret.order(order);
}
public ByteBuffer getAll() {
if (remaining() == 0)
return EMPTY_BYTEBUFFER;
read(remaining());
return remove();
}
private ByteBuffer read(int count) {
if (remaining() < count)
throw new IllegalArgumentException("count : " + remaining() + "/" + count);
ByteBuffer first = mBuffers.peek();
while (first != null && !first.hasRemaining()) {
reclaim(mBuffers.remove());
first = mBuffers.peek();
}
if (first == null) {
return EMPTY_BYTEBUFFER;
}
if (first.remaining() >= count) {
return first.order(order);
}
ByteBuffer ret = obtain(count);
ret.limit(count);
byte[] bytes = ret.array();
int offset = 0;
ByteBuffer bb = null;
while (offset < count) {
bb = mBuffers.remove();
int toRead = Math.min(count - offset, bb.remaining());
bb.get(bytes, offset, toRead);
offset += toRead;
if (bb.remaining() == 0) {
reclaim(bb);
bb = null;
}
}
// if there was still data left in the last buffer we popped
// toss it back into the head
if (bb != null && bb.remaining() > 0)
mBuffers.addFirst(bb);
mBuffers.addFirst(ret);
return ret.order(order);
}
public void trim() {
// this clears out buffers that are empty in the beginning of the list
read(0);
}
public ByteBufferList add(ByteBufferList b) {
b.get(this);
return this;
}
public ByteBufferList add(ByteBuffer b) {
if (b.remaining() <= 0) {
// System.out.println("reclaiming remaining: " + b.remaining());
reclaim(b);
return this;
}
addRemaining(b.remaining());
// see if we can fit the entirety of the buffer into the end
// of the current last buffer
if (mBuffers.size() > 0) {
ByteBuffer last = mBuffers.getLast();
if (last.capacity() - last.limit() >= b.remaining()) {
last.mark();
last.position(last.limit());
last.limit(last.capacity());
last.put(b);
last.limit(last.position());
last.reset();
reclaim(b);
trim();
return this;
}
}
mBuffers.add(b);
trim();
return this;
}
public void addFirst(ByteBuffer b) {
if (b.remaining() <= 0) {
reclaim(b);
return;
}
addRemaining(b.remaining());
// see if we can fit the entirety of the buffer into the beginning
// of the current first buffer
if (mBuffers.size() > 0) {
ByteBuffer first = mBuffers.getFirst();
if (first.position() >= b.remaining()) {
first.position(first.position() - b.remaining());
first.mark();
first.put(b);
first.reset();
reclaim(b);
return;
}
}
mBuffers.addFirst(b);
}
private void addRemaining(int remaining) {
if (this.remaining() >= 0)
this.remaining += remaining;
}
public void recycle() {
while (mBuffers.size() > 0) {
reclaim(mBuffers.remove());
}
remaining = 0;
}
public ByteBuffer remove() {
ByteBuffer ret = mBuffers.remove();
remaining -= ret.remaining();
return ret;
}
public int size() {
return mBuffers.size();
}
public void spewString() {
System.out.println(peekString());
}
public String peekString() {
return peekString(null);
}
// not doing toString as this is really nasty in the debugger...
public String peekString(Charset charset) {
if (charset == null)
charset = Charsets.UTF_8;
StringBuilder builder = new StringBuilder();
for (ByteBuffer bb: mBuffers) {
byte[] bytes;
int offset;
int length;
if (bb.isDirect()) {
bytes = new byte[bb.remaining()];
offset = 0;
length = bb.remaining();
bb.get(bytes);
}
else {
bytes = bb.array();
offset = bb.arrayOffset() + bb.position();
length = bb.remaining();
}
builder.append(new String(bytes, offset, length, charset));
}
return builder.toString();
}
public String readString() {
return readString(null);
}
public String readString(Charset charset) {
String ret = peekString(charset);
recycle();
return ret;
}
static class Reclaimer implements Comparator {
@Override
public int compare(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) {
// keep the smaller ones at the head, so they get tossed out quicker
if (byteBuffer.capacity() == byteBuffer2.capacity())
return 0;
if (byteBuffer.capacity() > byteBuffer2.capacity())
return 1;
return -1;
}
}
static PriorityQueue reclaimed = new PriorityQueue(8, new Reclaimer());
private static PriorityQueue getReclaimed() {
Looper mainLooper = Looper.getMainLooper();
if (mainLooper != null) {
if (Thread.currentThread() == mainLooper.getThread())
return null;
}
return reclaimed;
}
private static int MAX_SIZE = 1024 * 1024;
public static int MAX_ITEM_SIZE = 1024 * 256;
static int currentSize = 0;
static int maxItem = 0;
public static void setMaxPoolSize(int size) {
MAX_SIZE = size;
}
public static void setMaxItemSize(int size) {
MAX_ITEM_SIZE = size;
}
private static boolean reclaimedContains(ByteBuffer b) {
for (ByteBuffer other: reclaimed) {
if (other == b)
return true;
}
return false;
}
public static void reclaim(ByteBuffer b) {
if (b == null || b.isDirect())
return;
if (b.arrayOffset() != 0 || b.array().length != b.capacity())
return;
if (b.capacity() < 8192)
return;
if (b.capacity() > MAX_ITEM_SIZE)
return;
PriorityQueue r = getReclaimed();
if (r == null)
return;
synchronized (LOCK) {
while (currentSize > MAX_SIZE && r.size() > 0 && r.peek().capacity() < b.capacity()) {
// System.out.println("removing for better: " + b.capacity());
ByteBuffer head = r.remove();
currentSize -= head.capacity();
}
if (currentSize > MAX_SIZE) {
// System.out.println("too full: " + b.capacity());
return;
}
b.position(0);
b.limit(b.capacity());
currentSize += b.capacity();
r.add(b);
maxItem = Math.max(maxItem, b.capacity());
}
}
private static final Object LOCK = new Object();
public static ByteBuffer obtain(int size) {
if (size <= maxItem) {
PriorityQueue r = getReclaimed();
if (r != null) {
synchronized (LOCK) {
while (r.size() > 0) {
ByteBuffer ret = r.remove();
if (r.size() == 0)
maxItem = 0;
currentSize -= ret.capacity();
if (ret.capacity() >= size) {
// System.out.println("using " + ret.capacity());
return ret;
}
// System.out.println("dumping " + ret.capacity());
}
}
}
}
// System.out.println("alloc for " + size);
ByteBuffer ret = ByteBuffer.allocate(Math.max(8192, size));
return ret;
}
public static void obtainArray(ByteBuffer[] arr, int size) {
PriorityQueue r = getReclaimed();
int index = 0;
int total = 0;
if (r != null) {
synchronized (LOCK) {
while (r.size() > 0 && total < size && index < arr.length - 1) {
ByteBuffer b = r.remove();
currentSize -= b.capacity();
int needed = Math.min(size - total, b.capacity());
total += needed;
arr[index++] = b;
}
}
}
if (total < size) {
ByteBuffer b = ByteBuffer.allocate(Math.max(8192, size - total));
arr[index++] = b;
}
for (int i = index; i < arr.length; i++) {
arr[i] = EMPTY_BYTEBUFFER;
}
}
public static ByteBuffer deepCopy(ByteBuffer copyOf) {
if (copyOf == null)
return null;
return (ByteBuffer)obtain(copyOf.remaining()).put(copyOf.duplicate()).flip();
}
public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
public static void writeOutputStream(OutputStream out, ByteBuffer b) throws IOException {
byte[] bytes;
int offset;
int length;
if (b.isDirect()) {
bytes = new byte[b.remaining()];
offset = 0;
length = b.remaining();
b.get(bytes);
}
else {
bytes = b.array();
offset = b.arrayOffset() + b.position();
length = b.remaining();
}
out.write(bytes, offset, length);
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/ChannelWrapper.java
================================================
package com.koushikdutta.async;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.AbstractSelectableChannel;
abstract class ChannelWrapper implements ReadableByteChannel, ScatteringByteChannel {
private AbstractSelectableChannel mChannel;
ChannelWrapper(AbstractSelectableChannel channel) throws IOException {
channel.configureBlocking(false);
mChannel = channel;
}
public abstract void shutdownInput();
public abstract void shutdownOutput();
public abstract boolean isConnected();
public abstract int write(ByteBuffer src) throws IOException;
public abstract int write(ByteBuffer[] src) throws IOException;
// register for default events appropriate for this channel
public abstract SelectionKey register(Selector sel) throws ClosedChannelException;
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException {
return mChannel.register(sel, ops);
}
public boolean isChunked() {
return false;
}
@Override
public boolean isOpen() {
return mChannel.isOpen();
}
@Override
public void close() throws IOException {
mChannel.close();
}
public abstract int getLocalPort();
public abstract InetAddress getLocalAddress();
public abstract Object getSocket();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/DataEmitter.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
public interface DataEmitter {
void setDataCallback(DataCallback callback);
DataCallback getDataCallback();
boolean isChunked();
void pause();
void resume();
void close();
boolean isPaused();
void setEndCallback(CompletedCallback callback);
CompletedCallback getEndCallback();
AsyncServer getServer();
String charset();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
/**
* Created by koush on 5/27/13.
*/
public abstract class DataEmitterBase implements DataEmitter {
private boolean ended;
protected void report(Exception e) {
if (ended)
return;
ended = true;
if (getEndCallback() != null)
getEndCallback().onCompleted(e);
}
@Override
public final void setEndCallback(CompletedCallback callback) {
endCallback = callback;
}
CompletedCallback endCallback;
@Override
public final CompletedCallback getEndCallback() {
return endCallback;
}
DataCallback mDataCallback;
@Override
public void setDataCallback(DataCallback callback) {
mDataCallback = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataCallback;
}
@Override
public String charset() {
return null;
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/DataEmitterReader.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.DataCallback;
public class DataEmitterReader implements com.koushikdutta.async.callback.DataCallback {
DataCallback mPendingRead;
int mPendingReadLength;
ByteBufferList mPendingData = new ByteBufferList();
public void read(int count, DataCallback callback) {
mPendingReadLength = count;
mPendingRead = callback;
mPendingData.recycle();
}
private boolean handlePendingData(DataEmitter emitter) {
if (mPendingReadLength > mPendingData.remaining())
return false;
DataCallback pendingRead = mPendingRead;
mPendingRead = null;
pendingRead.onDataAvailable(emitter, mPendingData);
return true;
}
public DataEmitterReader() {
}
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
// if we're registered for data, we must be waiting for a read
do {
int need = Math.min(bb.remaining(), mPendingReadLength - mPendingData.remaining());
bb.get(mPendingData, need);
bb.remaining();
}
while (handlePendingData(emitter) && mPendingRead != null);
bb.remaining();
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/DataSink.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.WritableCallback;
public interface DataSink {
public void write(ByteBufferList bb);
public void setWriteableCallback(WritableCallback handler);
public WritableCallback getWriteableCallback();
public boolean isOpen();
public void end();
public void setClosedCallback(CompletedCallback handler);
public CompletedCallback getClosedCallback();
public AsyncServer getServer();
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/DataTrackingEmitter.java
================================================
package com.koushikdutta.async;
/**
* Created by koush on 5/28/13.
*/
public interface DataTrackingEmitter extends DataEmitter {
interface DataTracker {
void onData(int totalBytesRead);
}
void setDataTracker(DataTracker tracker);
DataTracker getDataTracker();
int getBytesRead();
void setDataEmitter(DataEmitter emitter);
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/DatagramChannelWrapper.java
================================================
package com.koushikdutta.async;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
class DatagramChannelWrapper extends ChannelWrapper {
DatagramChannel mChannel;
@Override
public InetAddress getLocalAddress() {
return mChannel.socket().getLocalAddress();
}
@Override
public int getLocalPort() {
return mChannel.socket().getLocalPort();
}
InetSocketAddress address;
public InetSocketAddress getRemoteAddress() {
return address;
}
public void disconnect() throws IOException {
mChannel.disconnect();
}
DatagramChannelWrapper(DatagramChannel channel) throws IOException {
super(channel);
mChannel = channel;
}
@Override
public int read(ByteBuffer buffer) throws IOException {
if (!isConnected()) {
int position = buffer.position();
address = (InetSocketAddress)mChannel.receive(buffer);
if (address == null)
return -1;
return buffer.position() - position;
}
address = null;
return mChannel.read(buffer);
}
@Override
public boolean isConnected() {
return mChannel.isConnected();
}
@Override
public int write(ByteBuffer src) throws IOException {
return mChannel.write(src);
}
@Override
public int write(ByteBuffer[] src) throws IOException {
return (int)mChannel.write(src);
}
@Override
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException {
return mChannel.register(sel, ops);
}
@Override
public boolean isChunked() {
return true;
}
@Override
public SelectionKey register(Selector sel) throws ClosedChannelException {
return register(sel, SelectionKey.OP_READ);
}
@Override
public void shutdownOutput() {
}
@Override
public void shutdownInput() {
}
@Override
public long read(ByteBuffer[] byteBuffers) throws IOException {
return mChannel.read(byteBuffers);
}
@Override
public long read(ByteBuffer[] byteBuffers, int i, int i2) throws IOException {
return mChannel.read(byteBuffers, i, i2);
}
@Override
public Object getSocket() {
return mChannel.socket();
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/FileDataEmitter.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.util.StreamUtility;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* Created by koush on 5/22/13.
*/
public class FileDataEmitter extends DataEmitterBase {
AsyncServer server;
File file;
public FileDataEmitter(AsyncServer server, File file) {
this.server = server;
this.file = file;
paused = !server.isAffinityThread();
if (!paused)
doResume();
}
DataCallback callback;
@Override
public void setDataCallback(DataCallback callback) {
this.callback = callback;
}
@Override
public DataCallback getDataCallback() {
return callback;
}
@Override
public boolean isChunked() {
return false;
}
boolean paused;
@Override
public void pause() {
paused = true;
}
@Override
public void resume() {
paused = false;
doResume();
}
@Override
protected void report(Exception e) {
StreamUtility.closeQuietly(channel);
super.report(e);
}
ByteBufferList pending = new ByteBufferList();
FileChannel channel;
Runnable pumper = new Runnable() {
@Override
public void run() {
try {
if (channel == null)
channel = new FileInputStream(file).getChannel();
if (!pending.isEmpty()) {
Util.emitAllData(FileDataEmitter.this, pending);
if (!pending.isEmpty())
return;
}
ByteBuffer b;
do {
b = ByteBufferList.obtain(8192);
if (-1 == channel.read(b)) {
report(null);
return;
}
b.flip();
pending.add(b);
Util.emitAllData(FileDataEmitter.this, pending);
}
while (pending.remaining() == 0 && !isPaused());
}
catch (Exception e) {
report(e);
}
}
};
private void doResume() {
server.post(pumper);
}
@Override
public boolean isPaused() {
return paused;
}
@Override
public AsyncServer getServer() {
return server;
}
@Override
public void close() {
try {
channel.close();
}
catch (Exception e) {
}
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.wrapper.DataEmitterWrapper;
public class FilteredDataEmitter extends DataEmitterBase implements DataEmitter, DataCallback, DataEmitterWrapper, DataTrackingEmitter {
private DataEmitter mEmitter;
@Override
public DataEmitter getDataEmitter() {
return mEmitter;
}
@Override
public void setDataEmitter(DataEmitter emitter) {
if (mEmitter != null) {
mEmitter.setDataCallback(null);
}
mEmitter = emitter;
mEmitter.setDataCallback(this);
mEmitter.setEndCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
report(ex);
}
});
}
@Override
public int getBytesRead() {
return totalRead;
}
@Override
public DataTracker getDataTracker() {
return tracker;
}
@Override
public void setDataTracker(DataTracker tracker) {
this.tracker = tracker;
}
private DataTracker tracker;
private int totalRead;
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
if (closed) {
// this emitter was closed but for some reason data is still being spewed...
// eat it, nom nom.
bb.recycle();
return;
}
if (bb != null)
totalRead += bb.remaining();
Util.emitAllData(this, bb);
if (bb != null)
totalRead -= bb.remaining();
if (tracker != null && bb != null)
tracker.onData(totalRead);
// if there's data after the emitting, and it is paused... the underlying implementation
// is obligated to cache the byte buffer list.
}
@Override
public boolean isChunked() {
return mEmitter.isChunked();
}
@Override
public void pause() {
mEmitter.pause();
}
@Override
public void resume() {
mEmitter.resume();
}
@Override
public boolean isPaused() {
return mEmitter.isPaused();
}
@Override
public AsyncServer getServer() {
return mEmitter.getServer();
}
boolean closed;
@Override
public void close() {
closed = true;
if (mEmitter != null)
mEmitter.close();
}
@Override
public String charset() {
if (mEmitter == null)
return null;
return mEmitter.charset();
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/FilteredDataSink.java
================================================
package com.koushikdutta.async;
public class FilteredDataSink extends BufferedDataSink {
public FilteredDataSink(DataSink sink) {
super(sink);
setMaxBuffer(0);
}
public ByteBufferList filter(ByteBufferList bb) {
return bb;
}
@Override
protected void onDataAccepted(ByteBufferList bb) {
ByteBufferList filtered = filter(bb);
// filtering may return the same byte buffer, so watch for that.
if (filtered != bb) {
bb.recycle();
filtered.get(bb);
}
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/HostnameResolutionException.java
================================================
package com.koushikdutta.async;
public class HostnameResolutionException extends Exception {
public HostnameResolutionException(String message) {
super(message);
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/LineEmitter.java
================================================
package com.koushikdutta.async;
import com.koushikdutta.async.callback.DataCallback;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class LineEmitter implements DataCallback {
public interface StringCallback {
void onStringAvailable(String s);
}
public LineEmitter() {
this(null);
}
public LineEmitter(Charset charset) {
this.charset = charset;
}
Charset charset;
ByteBufferList data = new ByteBufferList();
StringCallback mLineCallback;
public void setLineCallback(StringCallback callback) {
mLineCallback = callback;
}
public StringCallback getLineCallback() {
return mLineCallback;
}
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
ByteBuffer buffer = ByteBuffer.allocate(bb.remaining());
while (bb.remaining() > 0) {
byte b = bb.get();
if (b == '\n') {
buffer.flip();
data.add(buffer);
mLineCallback.onStringAvailable(data.readString(charset));
data = new ByteBufferList();
return;
}
else {
buffer.put(b);
}
}
buffer.flip();
data.add(buffer);
}
}
================================================
FILE: AndroidAsync/src/com/koushikdutta/async/PushParser.java
================================================
package com.koushikdutta.async;
import android.util.Log;
import com.koushikdutta.async.callback.DataCallback;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
public class PushParser implements DataCallback {
public interface ParseCallback {
public void parsed(T data);
}
static abstract class Waiter {
int length;
public Waiter(int length) {
this.length = length;
}
/**
* Consumes received data, and/or returns next waiter to continue reading instead of this waiter.
* @param bb received data, bb.remaining >= length
* @return - a waiter that should continue reading right away, or null if this waiter is finished
*/
public abstract Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb);
}
static class IntWaiter extends Waiter {
ParseCallback callback;
public IntWaiter(ParseCallback callback) {
super(4);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
callback.parsed(bb.getInt());
return null;
}
}
static class ByteArrayWaiter extends Waiter {
ParseCallback callback;
public ByteArrayWaiter(int length, ParseCallback callback) {
super(length);
if (length <= 0)
throw new IllegalArgumentException("length should be > 0");
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
byte[] bytes = new byte[length];
bb.get(bytes);
callback.parsed(bytes);
return null;
}
}
static class LenByteArrayWaiter extends Waiter {
private final ParseCallback callback;
public LenByteArrayWaiter(ParseCallback callback) {
super(4);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
int length = bb.getInt();
if (length == 0) {
callback.parsed(new byte[0]);
return null;
}
return new ByteArrayWaiter(length, callback);
}
}
static class ByteBufferListWaiter extends Waiter {
ParseCallback callback;
public ByteBufferListWaiter(int length, ParseCallback callback) {
super(length);
if (length <= 0) throw new IllegalArgumentException("length should be > 0");
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
callback.parsed(bb.get(length));
return null;
}
}
static class LenByteBufferListWaiter extends Waiter {
private final ParseCallback callback;
public LenByteBufferListWaiter(ParseCallback callback) {
super(4);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
int length = bb.getInt();
return new ByteBufferListWaiter(length, callback);
}
}
static class UntilWaiter extends Waiter {
byte value;
DataCallback callback;
public UntilWaiter(byte value, DataCallback callback) {
super(1);
this.value = value;
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
boolean found = true;
ByteBufferList cb = new ByteBufferList();
while (bb.size() > 0) {
ByteBuffer b = bb.remove();
b.mark();
int index = 0;
while (b.remaining() > 0 && !(found = (b.get() == value))) {
index++;
}
b.reset();
if (found) {
bb.addFirst(b);
bb.get(cb, index);
// eat the one we're waiting on
bb.get();
break;
} else {
cb.add(b);
}
}
callback.onDataAvailable(emitter, cb);
if (found) {
return null;
} else {
return this;
}
}
}
private class TapWaiter extends Waiter {
private final TapCallback callback;
public TapWaiter(TapCallback callback) {
super(0);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
Method method = getTap(callback);
method.setAccessible(true);
try {
method.invoke(callback, args.toArray());
} catch (Exception e) {
Log.e("PushParser", "Error while invoking tap callback", e);
}
args.clear();
return null;
}
}
private Waiter noopArgWaiter = new Waiter(0) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(null);
return null;
}
};
private Waiter byteArgWaiter = new Waiter(1) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.get());
return null;
}
};
private Waiter shortArgWaiter = new Waiter(2) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.getShort());
return null;
}
};
private Waiter intArgWaiter = new Waiter(4) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.getInt());
return null;
}
};
private Waiter longArgWaiter = new Waiter(8) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.getLong());
return null;
}
};
private ParseCallback byteArrayArgCallback = new ParseCallback() {
@Override
public void parsed(byte[] data) {
args.add(data);
}
};
private ParseCallback byteBufferListArgCallback = new ParseCallback() {
@Override
public void parsed(ByteBufferList data) {
args.add(data);
}
};
private ParseCallback stringArgCallback = new ParseCallback() {
@Override
public void parsed(byte[] data) {
args.add(new String(data));
}
};
DataEmitter mEmitter;
private LinkedList mWaiting = new LinkedList();
private ArrayList