/*
* Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
* Copyright (C) 2021 cyoung06
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package kr.syeyoung.dungeonsguide.stomp;
import lombok.Getter;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
import sun.security.ssl.SSLSocketFactoryImpl;
import javax.net.ssl.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class StompClient extends WebSocketClient implements StompInterface {
public StompClient(URI serverUri, final String token, CloseListener closeListener) throws Exception {
super(serverUri);
this.closeListener = closeListener;
addHeader("Authorization", token);
System.out.println("connecting websocket");
if (!connectBlocking()) {
throw new RuntimeException("Can't connect to ws");
}
System.out.println("connected, stomp handshake");
while(this.stompClientStatus == StompClientStatus.CONNECTING);
System.out.println("fully connected");
}
private final CloseListener closeListener;
@Getter
private volatile StompClientStatus stompClientStatus = StompClientStatus.CONNECTING;
@Getter
private StompPayload errorPayload;
private ScheduledFuture heartbeat = null;
private static final ScheduledExecutorService ex = Executors.newScheduledThreadPool(1);
@Override
public void onOpen(ServerHandshake handshakedata) {
send(new StompPayload().method(StompHeader.CONNECT)
.header("accept-version","1.2")
.header("heart-beat", "30000,30000")
.header("host",uri.getHost()).getBuilt()
);
}
@Override
public void onMessage(String message) {
try {
StompPayload payload = StompPayload.parse(message);
if (payload.method() == StompHeader.CONNECTED) {
stompClientStatus = StompClientStatus.CONNECTED;
String heartbeat = payload.headers().get("heart-beat");
if (heartbeat != null) {
// int sx = Integer.parseInt(heartbeat.split(",")[0]);
// int sy = Integer.parseInt(heartbeat.split(",")[1]);
//
// if (sy == 0) return;
int heartbeatMS = 30000;
this.heartbeat = ex.scheduleAtFixedRate(() -> {
send("\n");
}, heartbeatMS-1000, heartbeatMS-1000, TimeUnit.MILLISECONDS);
}
} else if (payload.method() == StompHeader.ERROR) {
errorPayload = payload;
stompClientStatus = StompClientStatus.ERROR;
this.close();
} else if (payload.method() == StompHeader.MESSAGE) {
// mesage
StompSubscription stompSubscription = stompSubscriptionMap.get(Integer.parseInt(payload.headers().get("subscription")));
try {
stompSubscription.getStompMessageHandler().handle(this, payload);
if (stompSubscription.getAckMode() != StompSubscription.AckMode.AUTO) {
send(new StompPayload().method(StompHeader.ACK)
.header("id",payload.headers().get("ack")).getBuilt()
);
}
} catch (Exception e) {
e.printStackTrace();
if (stompSubscription.getAckMode() != StompSubscription.AckMode.AUTO) {
send(new StompPayload().method(StompHeader.NACK)
.header("id",payload.headers().get("ack")).getBuilt()
);
}
}
} else if (payload.method() == StompHeader.RECEIPT) {
String receipt_id = payload.headers().get("receipt-id");
StompPayload payload1 = receiptMap.remove(Integer.parseInt(receipt_id));
if (payload1.method() == StompHeader.DISCONNECT) {
stompClientStatus = StompClientStatus.DISCONNECTED;
close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
if (heartbeat != null) heartbeat.cancel(true);
closeListener.onClose(code, reason, remote);
}
@Override
public void onError(Exception ex) {
ex.printStackTrace();
}
private final Map stompSubscriptionMap = new HashMap();
private final Map receiptMap = new HashMap();
private int idIncrement = 0;
@Override
public void send(StompPayload payload) {
if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected");
payload.method(StompHeader.SEND);
if (payload.headers().get("receipt") != null)
receiptMap.put(Integer.parseInt(payload.headers().get("receipt")), payload);
send(payload.getBuilt());
}
@Override
public void subscribe(StompSubscription stompSubscription) {
if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected");
stompSubscription.setId(++idIncrement);
send(new StompPayload().method(StompHeader.SUBSCRIBE)
.header("id",String.valueOf(stompSubscription.getId()))
.header("destination", stompSubscription.getDestination())
.header("ack", stompSubscription.getAckMode().getValue()).getBuilt()
);
stompSubscriptionMap.put(stompSubscription.getId(), stompSubscription);
}
@Override
public void unsubscribe(StompSubscription stompSubscription) {
if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected");
send(new StompPayload().method(StompHeader.UNSUBSCRIBE)
.header("id",String.valueOf(stompSubscription.getId())).getBuilt()
);
stompSubscriptionMap.remove(stompSubscription.getId());
}
@Override
public void disconnect() {
if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected");
StompPayload stompPayload;
stompClientStatus =StompClientStatus.DISCONNECTING;
send((stompPayload = new StompPayload().method(StompHeader.DISCONNECT)
.header("receipt", String.valueOf(++idIncrement)))
.getBuilt()
);
receiptMap.put(idIncrement, stompPayload);
}
}