/**
* Copyright (c) 2010-2015, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package de.quist.samy.remocon;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.TimeoutException;
import org.apache.commons.codec.binary.Base64;
import org.openhab.binding.samsungtv.internal.SamsungTvConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Copied from https://github.com/keremkusmezer/SamyGo-Android-Remote/tree/master/src/de/quist/samy/remocon,
* since there is no binary build available anymore. Thanks to Tom Quist!
*
* @author Tom Quist
*/
public class RemoteSession {
private static Logger logger = LoggerFactory.getLogger(SamsungTvConnection.class);
private static final String APP_STRING = "iphone.iapp.samsung";
private static final String TV_APP_STRING = "iphone..iapp.samsung";
private static final char[] ALLOWED_BYTES = new char[] { 0x64, 0x00, 0x01, 0x00 };
private static final char[] DENIED_BYTES = new char[] { 0x64, 0x00, 0x00, 0x00 };
private static final char[] TIMEOUT_BYTES = new char[] { 0x65, 0x00 };
public static final String ALLOWED = "ALLOWED";
public static final String DENIED = "DENIED";
public static final String TIMEOUT = "TIMEOUT";
private String applicationName;
private String uniqueId;
private String host;
private int port;
private Socket socket;
private InputStreamReader reader;
private BufferedWriter writer;
private RemoteSession(String applicationName, String uniqueId, String host, int port) {
this.applicationName = applicationName;
this.uniqueId = uniqueId;
if (uniqueId == null) {
uniqueId = "";
}
this.host = host;
this.port = port;
}
public static RemoteSession create(String applicationName, String uniqueId, String host, int port)
throws IOException, ConnectionDeniedException, TimeoutException {
RemoteSession session = new RemoteSession(applicationName, uniqueId, host, port);
String result = session.initialize();
if (result.equals(ALLOWED)) {
return session;
} else if (result.equals(DENIED)) {
throw new ConnectionDeniedException();
} else if (result.equals(TIMEOUT)) {
throw new TimeoutException();
} else {
// for now we just assume to be connected
return session;
}
}
private String initialize() throws UnknownHostException, IOException {
logger.debug("Creating socket for host " + host + " on port " + port);
socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 5000);
logger.debug("Socket successfully created and connected");
InetAddress localAddress = socket.getLocalAddress();
logger.debug("Local address is " + localAddress.getHostAddress());
logger.debug("Sending registration message");
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.append((char) 0x00);
writeText(writer, APP_STRING);
writeText(writer, getRegistrationPayload(localAddress.getHostAddress()));
writer.flush();
InputStream in = socket.getInputStream();
reader = new InputStreamReader(in);
String result = readRegistrationReply(reader);
// sendPart2();
int i;
while ((i = in.available()) > 0) {
in.skip(i);
}
return result;
}
@SuppressWarnings("unused")
private void sendPart2() throws IOException {
writer.append((char) 0x00);
writeText(writer, TV_APP_STRING);
writeText(writer, new String(new char[] { 0xc8, 0x00 }));
}
private void checkConnection() throws UnknownHostException, IOException {
if (socket.isClosed() || !socket.isConnected()) {
logger.debug("Connection closed, trying to reconnect...");
try {
socket.close();
} catch (IOException e) {
// Ignore any exception
}
initialize();
logger.debug("Reconnected to server");
}
}
public void destroy() {
try {
socket.close();
} catch (IOException e) {
// Ignore exception
}
}
private String readRegistrationReply(Reader reader) throws IOException {
logger.debug("Reading registration reply");
reader.read(); // Unknown byte 0x02
String text1 = readText(reader); // Read "unknown.livingroom.iapp.samsung" for new RC and "iapp.samsung" for
// already registered RC
logger.debug("Received ID: " + text1);
char[] result = readCharArray(reader); // Read result sequence
if (Arrays.equals(result, ALLOWED_BYTES)) {
logger.debug("Registration successful");
return ALLOWED;
} else if (Arrays.equals(result, DENIED_BYTES)) {
logger.warn("Registration denied");
return DENIED;
} else if (Arrays.equals(result, TIMEOUT_BYTES)) {
logger.warn("Registration timed out");
return TIMEOUT;
} else {
StringBuilder sb = new StringBuilder();
for (char c : result) {
sb.append(Integer.toHexString(c));
sb.append(' ');
}
String hexReturn = sb.toString();
{
logger.error("Received unknown registration reply: " + hexReturn);
}
return hexReturn;
}
}
private String getRegistrationPayload(String ip) throws IOException {
StringWriter writer = new StringWriter();
writer.append((char) 0x64);
writer.append((char) 0x00);
writeBase64Text(writer, ip);
writeBase64Text(writer, uniqueId);
writeBase64Text(writer, applicationName);
writer.flush();
return writer.toString();
}
private static String readText(Reader reader) throws IOException {
char[] buffer = readCharArray(reader);
return new String(buffer);
}
private static char[] readCharArray(Reader reader) throws IOException {
if (reader.markSupported()) {
reader.mark(1024);
}
int length = reader.read();
int delimiter = reader.read();
if (delimiter != 0) {
if (reader.markSupported()) {
reader.reset();
}
throw new IOException("Unsupported reply exception");
}
char[] buffer = new char[length];
reader.read(buffer);
return buffer;
}
private static Writer writeText(Writer writer, String text) throws IOException {
return writer.append((char) text.length()).append((char) 0x00).append(text);
}
private static Writer writeBase64Text(Writer writer, String text) throws IOException {
String b64 = new String(Base64.encodeBase64(text.getBytes()));
return writeText(writer, b64);
}
@SuppressWarnings("unused")
private void internalSendKey(Key key) throws IOException {
writer.append((char) 0x00);
writeText(writer, TV_APP_STRING);
writeText(writer, getKeyPayload(key));
writer.flush();
int i = reader.read(); // Unknown byte 0x00
String t = readText(reader); // Read "iapp.samsung"
char[] c = readCharArray(reader);
}
public void sendKey(Key key) throws IOException {
logger.debug("Sending key " + key.getValue() + "...");
checkConnection();
try {
internalSendKey(key);
} catch (SocketException e) {
logger.debug("Could not send key because the server closed the connection. Reconnecting...");
initialize();
logger.debug("Sending key " + key.getValue() + " again...");
internalSendKey(key);
}
logger.debug("Successfully sent key " + key.getValue());
}
private String getKeyPayload(Key key) throws IOException {
StringWriter writer = new StringWriter();
writer.append((char) 0x00);
writer.append((char) 0x00);
writer.append((char) 0x00);
writeBase64Text(writer, key.getValue());
writer.flush();
return writer.toString();
}
@SuppressWarnings("unused")
private void internalSendText(String text) throws IOException {
writer.append((char) 0x01);
writeText(writer, TV_APP_STRING);
writeText(writer, getTextPayload(text));
writer.flush();
if (!reader.ready()) {
return;
}
int i = reader.read(); // Unknown byte 0x02
String t = readText(reader); // Read "iapp.samsung"
char[] c = readCharArray(reader);
}
public void sendText(String text) throws IOException {
logger.debug("Sending text \"" + text + "\"...");
checkConnection();
try {
internalSendText(text);
} catch (SocketException e) {
logger.debug("Could not send key because the server closed the connection. Reconnecting...");
initialize();
logger.debug("Sending text \"" + text + "\" again...");
internalSendText(text);
}
logger.debug("Successfully sent text \"" + text + "\"");
}
private String getTextPayload(String text) throws IOException {
StringWriter writer = new StringWriter();
writer.append((char) 0x01);
writer.append((char) 0x00);
writeBase64Text(writer, text);
writer.flush();
return writer.toString();
}
}