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

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;
}
}
}