You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
14 KiB
383 lines
14 KiB
import javax.json.Json;
|
|
import javax.json.stream.JsonParser;
|
|
import javax.json.stream.JsonParsingException;
|
|
import javax.xml.bind.DatatypeConverter;
|
|
import java.io.*;
|
|
import java.math.BigDecimal;
|
|
import java.net.*;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Scanner;
|
|
import java.util.concurrent.Exchanger;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Created by itsmy on 08.08.2016.
|
|
*/
|
|
public class Client extends Thread {
|
|
public class Info{
|
|
Integer VKID,DURATION;
|
|
String PLAYLIST,PROTOCOL,AUDIOID;
|
|
BigDecimal TIME, TRACKTIME;
|
|
Boolean AP;
|
|
private void initInstance(){
|
|
VKID = 0;
|
|
DURATION = 0;
|
|
PLAYLIST = "";
|
|
PROTOCOL = "";
|
|
AUDIOID = "";
|
|
TIME = BigDecimal.ZERO;
|
|
TRACKTIME = BigDecimal.ZERO;
|
|
AP = false;
|
|
}
|
|
}
|
|
private Socket socket;
|
|
private OutputStream out;
|
|
private InputStream in;
|
|
private Integer socketid;
|
|
private Boolean _instanceClosed = false;
|
|
// private Boolean isClosing = false;
|
|
|
|
public void initSocket(Socket s) throws IOException {
|
|
socket = s;
|
|
out = socket.getOutputStream();
|
|
in = socket.getInputStream();
|
|
socketid = SocketMaps.addClient(this, new Info());
|
|
try {
|
|
SocketMaps.getInfo(socketid).initInstance();
|
|
}
|
|
catch (ctException e){
|
|
Console.socket(socketid,"Try to close!");
|
|
}
|
|
Console.debug("Create new socket " + socketid);
|
|
start();
|
|
}
|
|
|
|
|
|
public void run() {
|
|
Console.socket(socketid, "New connection");
|
|
runSocket();
|
|
}
|
|
|
|
public void closeSocket(){
|
|
if(!_instanceClosed){
|
|
_instanceClosed = true;
|
|
}
|
|
else{
|
|
return;
|
|
}
|
|
try {
|
|
in.close();
|
|
out.close();
|
|
socket.close();
|
|
} catch (IOException e) {
|
|
}
|
|
try {
|
|
SocketMaps.removeClient(socketid);
|
|
SocketMaps.removeClient(socketid);
|
|
Console.socket(socketid, "Disconnected");
|
|
}catch (ctException e){
|
|
Console.socket(socketid,e.toString());
|
|
}
|
|
}
|
|
|
|
void checkProtocol(Integer socketid, String msg) throws ctException{
|
|
if(SocketMaps.getInfo(socketid).PROTOCOL.equals("1A")){
|
|
|
|
}else{
|
|
throw new ctException("Protocol verify failed!");
|
|
}
|
|
}
|
|
|
|
public void runSocket(){
|
|
try {
|
|
handshake();
|
|
String msg = readSocket();
|
|
parseInput(msg);
|
|
checkProtocol(socketid, msg);
|
|
SocketMaps.setRole(socketid, SocketMaps.Roles.LISTNER);
|
|
EventHandler.connect(socketid, SocketMaps.getPID(socketid));
|
|
while (!_instanceClosed) {
|
|
msg = readSocket();
|
|
parseInput(msg);
|
|
switch (SocketMaps.getRole(socketid)){
|
|
case CREATOR:
|
|
EventHandler.sendParty(socketid);
|
|
break;
|
|
case LISTNER:
|
|
break;
|
|
}
|
|
}
|
|
}catch (ctException e){
|
|
closeSocket();
|
|
e.printStackTrace();
|
|
}catch (Exception e){
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
|
|
private String bytesToString(int[] ints) {
|
|
//TODO: добавить поддержку русского языка
|
|
char[] buffer = new char[ints.length];
|
|
for (int i = 0; i < buffer.length; i++) {
|
|
char c = (char) ints[i];
|
|
buffer[i] = c;
|
|
}
|
|
return new String(buffer);
|
|
}
|
|
|
|
private void handshake() throws ctException {
|
|
try {
|
|
socket.setSoTimeout(2000);
|
|
String data = new Scanner(in, "UTF-8").useDelimiter("\\r\\n\\r\\n").next();
|
|
Matcher get = Pattern.compile("^GET").matcher(data);
|
|
if (get.find()) {
|
|
Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
|
|
match.find();
|
|
try {
|
|
byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
|
|
+ "Connection: Upgrade\r\n"
|
|
+ "Upgrade: websocket\r\n"
|
|
+ "Sec-WebSocket-Accept: "
|
|
+ DatatypeConverter
|
|
.printBase64Binary(
|
|
MessageDigest
|
|
.getInstance("SHA-1")
|
|
.digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
|
.getBytes("UTF-8")))
|
|
+ "\r\n\r\n")
|
|
.getBytes("UTF-8");
|
|
out.write(response, 0, response.length);
|
|
} catch (IOException | NoSuchAlgorithmException e) {
|
|
Console.out("Server Error:", e.toString());
|
|
}
|
|
socket.setSoTimeout(0);
|
|
Console.socket(socketid, "Handshake accepted!");
|
|
}
|
|
} catch (SocketException ignored) {
|
|
} catch (NoSuchElementException e) {
|
|
Console.socket(socketid, "Handshake Timeout (2000ms)");
|
|
closeSocket();
|
|
} catch(IllegalStateException e){
|
|
Console.socket(socketid, "Bad handshake request!");
|
|
closeSocket();
|
|
}
|
|
}
|
|
|
|
public Boolean writeSocket(String message) {
|
|
byte[] response;
|
|
byte[] r;
|
|
try {
|
|
response = message.getBytes("UTF-8");
|
|
} catch (UnsupportedEncodingException e) {
|
|
Console.socket(socketid, "Unsupported encoding!");
|
|
return false;
|
|
} catch (NullPointerException e) {
|
|
return false;
|
|
}
|
|
if (message.length() < 126) {
|
|
r = new byte[response.length + 2];
|
|
System.arraycopy(response, 0, r, 2, response.length);
|
|
r[0] = (byte) 129;
|
|
r[1] = (byte) (response.length);
|
|
} else if (message.length() < 65535) {
|
|
r = new byte[response.length + 4];
|
|
System.arraycopy(response, 0, r, 4, response.length);
|
|
r[0] = (byte) 129;
|
|
r[1] = (byte) 126;
|
|
r[2] = (byte) (response.length >>> 8);
|
|
r[3] = (byte) (response.length);
|
|
} else if (response.length < Long.MAX_VALUE) {
|
|
r = new byte[response.length + 8];
|
|
System.arraycopy(response, 0, r, 8, response.length);
|
|
r[0] = (byte) 129;
|
|
r[1] = (byte) 127;
|
|
r[2] = (byte) (response.length >>> 56);
|
|
r[3] = (byte) (response.length >>> 48);
|
|
r[4] = (byte) (response.length >>> 40);
|
|
r[5] = (byte) (response.length >>> 32);
|
|
r[6] = (byte) (response.length >>> 24);
|
|
r[7] = (byte) (response.length >>> 16);
|
|
r[8] = (byte) (response.length >>> 8);
|
|
r[9] = (byte) (response.length);
|
|
} else {
|
|
Console.out("Socket Exception", "Too long message for sending! (>64bit)");
|
|
return false;
|
|
}
|
|
try {
|
|
out.write(r, 0, r.length);
|
|
return true;
|
|
} catch (IOException e) {
|
|
Console.out("Socket Exeption", "Client socket is closed?! Closing socket connection!");
|
|
closeSocket();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public String readSocket() {
|
|
String out = "";
|
|
try {
|
|
int[] MSG = new int[0];
|
|
int LENGTH;
|
|
int KEYFRAME = 0;
|
|
int[] KEY = new int[4];
|
|
int OPCODE = in.read(); //читаем OPCODE byte
|
|
switch (OPCODE) {
|
|
case -1:
|
|
closeSocket(); //закрываем чтение потока при -1
|
|
return null;
|
|
case 136:
|
|
closeSocket();
|
|
return null;
|
|
case 129:
|
|
LENGTH = in.read(); //читаем второй бит с данными о размере сообщения
|
|
switch (LENGTH) { //узнаем длину сообщения
|
|
default:
|
|
KEYFRAME = 2;
|
|
break;
|
|
case 254:
|
|
KEYFRAME = 4; //126-65535
|
|
break;
|
|
case 255:
|
|
KEYFRAME = 10; //65535-...
|
|
break;
|
|
}
|
|
switch (KEYFRAME) {
|
|
case 2:
|
|
MSG = new int[LENGTH - 128];
|
|
break;
|
|
case 4:
|
|
LENGTH = (in.read() << 8) + (in.read());
|
|
MSG = new int[LENGTH];
|
|
break;
|
|
case 10:
|
|
LENGTH = (in.read() << 56)
|
|
+ (in.read() << 48)
|
|
+ (in.read() << 40)
|
|
+ (in.read() << 32)
|
|
+ (in.read() << 24)
|
|
+ (in.read() << 16)
|
|
+ (in.read() << 8)
|
|
+ (in.read());
|
|
MSG = new int[LENGTH];
|
|
break;
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
KEY[i] = in.read();
|
|
}
|
|
for (int j = 0; j < (MSG.length); j++) {
|
|
int enc = in.read();
|
|
MSG[j] = (byte) (enc ^ KEY[j & 0x3]);
|
|
}
|
|
if (MSG.length > 0) {
|
|
out = bytesToString(MSG);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
Console.out("Socket Exception", "Client socket is closed?! Closing socket connection!");
|
|
closeSocket();
|
|
}
|
|
Console.socket(socketid, "Получаю: " + out);
|
|
return out;
|
|
}
|
|
|
|
public Boolean parseInput(String input) throws ctException{
|
|
try {
|
|
Info inf = SocketMaps.getInfo(socketid);
|
|
String keyName = null;
|
|
JsonParser jsonParser = Json.createParser(new StringReader(input));
|
|
while (jsonParser.hasNext()) {
|
|
JsonParser.Event event = jsonParser.next();
|
|
switch (event) {
|
|
case KEY_NAME:
|
|
keyName = jsonParser.getString();
|
|
break;
|
|
case VALUE_STRING:
|
|
setString(socketid, keyName, jsonParser.getString(), inf);
|
|
break;
|
|
case VALUE_NUMBER:
|
|
setInteger(socketid, keyName, jsonParser.getBigDecimal(), inf);
|
|
break;
|
|
case VALUE_FALSE:
|
|
setBoolean(socketid, keyName, false, inf);
|
|
break;
|
|
case VALUE_TRUE:
|
|
setBoolean(socketid, keyName, true, inf);
|
|
break;
|
|
case VALUE_NULL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}catch(NullPointerException e){
|
|
Console.socket(socketid,"Request parsing error!");
|
|
return false;
|
|
}catch(JsonParsingException e){
|
|
Console.socket(socketid, "JSON Parsing Error!");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static void setString(Integer sid, String keyName, String value, Info inf) throws ctException {
|
|
switch (keyName) {
|
|
case "EVENT":
|
|
switch (value){
|
|
case "play":
|
|
SocketMaps.setEID(sid, SocketMaps.Events.PLAY);
|
|
break;
|
|
case "pause":
|
|
SocketMaps.setEID(sid, SocketMaps.Events.PAUSE);
|
|
break;
|
|
case "rewind":
|
|
SocketMaps.setEID(sid, SocketMaps.Events.REWIND);
|
|
break;
|
|
case "DISCONNECT":
|
|
SocketMaps.setEID(sid, SocketMaps.Events.DISCONNECT);
|
|
}
|
|
break;
|
|
case "AID":
|
|
inf.AUDIOID = value;
|
|
break;
|
|
case "PROTOCOL":
|
|
inf.PROTOCOL = value;
|
|
break;
|
|
case "PLAYLIST":
|
|
inf.PLAYLIST = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void setInteger(Integer sid, String keyName, BigDecimal value, Info inf)
|
|
throws ctException{
|
|
switch (keyName){
|
|
case "USERID": inf.VKID = value.intValueExact();
|
|
break;
|
|
case "TIME": inf.TIME = value;
|
|
break;
|
|
case "TRACKTIME": inf.TRACKTIME = value;
|
|
break;
|
|
case "PARTYID": SocketMaps.setPID(sid, value.intValueExact());
|
|
break;
|
|
case "DURATION": inf.DURATION = value.intValueExact();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void setBoolean(Integer sid, String keyName, Boolean value, Info inf){
|
|
switch (keyName) {
|
|
case "AP":
|
|
inf.AP = value.booleanValue();
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|