package org.deviceconnect.android.deviceplugin.awsiot.udt;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.UUID;
import de.javawi.jstun.attribute.ChangeRequest;
import de.javawi.jstun.attribute.ErrorCode;
import de.javawi.jstun.attribute.MappedAddress;
import de.javawi.jstun.attribute.MessageAttribute;
import de.javawi.jstun.attribute.MessageAttributeParsingException;
import de.javawi.jstun.header.MessageHeader;
import de.javawi.jstun.header.MessageHeaderParsingException;
import de.javawi.jstun.util.UtilityException;
public class StunClient {
private static final boolean DEBUG = false;
private static final String TAG = "UDT";
private String mStunServer = "stun1.l.google.com";
private int mPort = 19302;
private int mTimeout = 30000;
private byte[] mUniqueId;
private MappedAddress mMappedAddress;
public StunClient() {
mUniqueId = generateUUID();
}
public void setStunServer(final String server, final int port) {
mStunServer = server;
mPort = port;
}
public void setTimeout(final int timeout) {
mTimeout = timeout;
}
public boolean bindingRequest() {
return bindingRequest(-1);
}
public boolean bindingRequest(final int port) {
DatagramSocket socket = null;
try {
if (port <= 0) {
socket = new DatagramSocket();
} else {
socket = new DatagramSocket(port);
}
socket.connect(InetAddress.getByName(mStunServer), mPort);
socket.setSoTimeout(mTimeout);
return bindingCommunicationInitialSocket(socket);
} catch (IOException | UtilityException | MessageHeaderParsingException | MessageAttributeParsingException e) {
if (DEBUG) {
Log.e(TAG, "Failed to binding Request." + e.getMessage(), e);
}
return false;
} finally {
if (socket != null) {
socket.close();
}
}
}
public String getMappedAddress() {
if (mMappedAddress == null) {
return null;
}
return mMappedAddress.getAddress().toString();
}
public int getMappedPort() {
if (mMappedAddress == null) {
return -1;
}
return mMappedAddress.getPort();
}
private boolean bindingCommunicationInitialSocket(final DatagramSocket socket) throws UtilityException, IOException, MessageHeaderParsingException, MessageAttributeParsingException {
MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
sendMH.setTransactionID(mUniqueId);
ChangeRequest changeRequest = new ChangeRequest();
sendMH.addMessageAttribute(changeRequest);
byte[] data = sendMH.getBytes();
DatagramPacket send = new DatagramPacket(data, data.length, InetAddress.getByName(mStunServer), mPort);
socket.send(send);
if (DEBUG) {
Log.i(TAG, "Binding Request sent.");
}
final byte[] buf = new byte[256];
MessageHeader receiveMH = new MessageHeader();
while (!(receiveMH.equalTransactionID(sendMH))) {
DatagramPacket receive = new DatagramPacket(buf, buf.length);
socket.receive(receive);
receiveMH = MessageHeader.parseHeader(receive.getData());
receiveMH.parseAttributes(receive.getData());
}
mMappedAddress = (MappedAddress) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.MappedAddress);
ErrorCode ec = (ErrorCode) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.ErrorCode);
if (ec != null) {
if (DEBUG) {
Log.e(TAG, "Message header contains an ErrorCode message attribute. ErrorCode=" + ec);
}
return false;
}
if (mMappedAddress == null) {
if (DEBUG) {
Log.e(TAG, "Response does not contain a Mapped Address message attribute.");
}
return false;
}
if (DEBUG) {
Log.i(TAG, "Address: " + mMappedAddress.getAddress().toString());
Log.i(TAG, "Port: " + mMappedAddress.getPort());
}
return true;
}
private byte[] generateUUID() {
byte[] uniqueId = new byte[16];
UUID uuid = UUID.randomUUID();
byte[] m = fromLong(uuid.getMostSignificantBits());
byte[] l = fromLong(uuid.getLeastSignificantBits());
System.arraycopy(m, 0, uniqueId, 0, m.length);
System.arraycopy(l, 0, uniqueId, m.length, l.length);
return uniqueId;
}
private byte[] fromLong(long value) {
int arraySize = Long.SIZE / Byte.SIZE;
ByteBuffer buffer = ByteBuffer.allocate(arraySize);
return buffer.putLong(value).array();
}
}