/* * 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) { } } }