/*
* Copyright 2016 Sam Sun <me@samczsun.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.samczsun.skype4j.internal;
import com.eclipsesource.json.JsonObject;
import com.samczsun.skype4j.exceptions.ConnectionException;
import com.samczsun.skype4j.exceptions.handler.ErrorSource;
import com.samczsun.skype4j.internal.client.FullClient;
import org.java_websocket.SSLSocketChannel2;
import org.java_websocket.client.DefaultSSLWebSocketClientFactory;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
public class SkypeWebSocket extends WebSocketClient {
private final SkypeImpl skype;
private final ExecutorService singleThreaded;
private Thread pingThread;
public SkypeWebSocket(final SkypeImpl skype, URI uri) throws NoSuchAlgorithmException, KeyManagementException {
super(uri, new Draft_17(), null, 2000);
this.skype = skype;
this.singleThreaded = Executors.newSingleThreadExecutor(new SkypeThreadFactory(skype, "WSFactory"));
TrustManager[] trustAllCerts = new TrustManager[]{new TrustAllManager()};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
this.setWebSocketFactory(new DefaultSSLWebSocketClientFactory(sc, singleThreaded) {
private boolean called = false;
@Override
public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key, String host, int port) throws IOException {
if (!called) {
Thread.currentThread().setName("Skype4J-WSMainThread-" + skype.getUsername());
}
SSLEngine e = sslcontext.createSSLEngine(host, port);
e.setUseClientMode(true);
ByteChannel c = new SSLSocketChannel2(channel, e, exec, key) {
private boolean called = false;
@Override
public int write(ByteBuffer buffer) throws IOException {
if (!called) {
Thread.currentThread().setName("Skype4J-WSWriteThread-" + skype.getUsername());
called = true;
}
return super.write(buffer);
}
};
return c;
}
});
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
(pingThread = new Thread("Skype4J-Pinger-" + skype.getUsername()) {
AtomicInteger currentPing = new AtomicInteger(1);
public void run() {
while (true) {
try {
Thread.sleep(55 * 1000);
send("5:" + currentPing.getAndIncrement() + "+::{\"name\":\"ping\"}");
} catch (InterruptedException e) {
break;
}
}
}
}).start();
}
@Override
public void onMessage(String s) {
if (s.startsWith("3:::")) {
JsonObject message = JsonObject.readFrom(s.substring(4));
JsonObject body = JsonObject.readFrom(message.get("body").asString());
int event = body.get("evt").asInt();
if (event == 6) {
try {
skype.updateContactList();
} catch (ConnectionException e) {
skype.handleError(ErrorSource.UPDATING_CONTACT_LIST, e, false);
}
} else if (event == 14) {
try {
if (skype instanceof FullClient) {
skype.getContactRequests(true);
}
} catch (ConnectionException e) {
skype.getLogger().log(Level.SEVERE, String.format("Unhandled exception while parsing websocket message '%s'", s), e);
}
} else {
skype.getLogger().log(Level.SEVERE, String.format("Unhandled websocket message '%s'", s));
}
JsonObject trouterRequest = new JsonObject();
trouterRequest.add("ts", System.currentTimeMillis());
trouterRequest.add("auth", true);
JsonObject headers = new JsonObject();
headers.add("trouter-request", trouterRequest);
JsonObject trouterClient = new JsonObject();
trouterClient.add("cd", 0);
JsonObject response = new JsonObject();
response.add("id", message.get("id").asInt());
response.add("status", 200);
response.add("headers", headers);
response.add("trouter-client", trouterClient);
response.add("body", "");
this.send("3:::" + response.toString());
} else if (s.startsWith("5:")) {
if (s.contains("reconnect")) {
try {
skype.registerWebSocket();
} catch (Exception e) {
skype.handleError(ErrorSource.REGISTERING_WEBSOCKET, e, false);
}
}
} else if (s.equals("0::")) {
try {
this.closeBlocking();
} catch (InterruptedException e) {
skype.handleError(ErrorSource.CLOSING_WEBSOCKET, e, false);
} finally {
if (this.pingThread.isAlive()) {
this.pingThread.interrupt();
}
if (!this.singleThreaded.isTerminated()) {
singleThreaded.shutdown();
while (!singleThreaded.isTerminated()) ;
}
}
}
}
@Override
public void onClose(int i, String s, boolean b) {
if (pingThread != null) {
pingThread.interrupt();
}
singleThreaded.shutdown();
while (!singleThreaded.isTerminated()) ;
if (skype.getWebSocket() == this) {
try {
skype.registerWebSocket();
} catch (Exception e) {
skype.handleError(ErrorSource.REGISTERING_WEBSOCKET, e, false);
}
}
}
@Override
public void onError(Exception e) {
skype.getLogger().log(Level.SEVERE, "Exception in websocket client", e);
}
private static class TrustAllManager implements 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) {
}
}
}