/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.jnodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.jnodes.nio.DatagramListener;
import org.xmpp.jnodes.nio.SelDatagramChannel;
import org.xmpp.packet.*;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.*;
import java.math.*;
import org.ifsoft.*;
import org.ifsoft.rtp.*;
import org.ifsoft.rayo.*;
import com.sun.voip.server.*;
import com.sun.voip.*;
import org.dom4j.*;
import org.jitsi.impl.neomedia.codec.audio.opus.Opus;
import com.rayo.core.*;
import com.rayo.core.verb.*;
import com.rayo.core.xml.providers.*;
public class RelayChannel implements IChannel {
private final SelDatagramChannel channelA;
private final SelDatagramChannel channelB;
private final SocketAddress addressA;
private final SocketAddress addressB;
private SocketAddress lastReceivedA;
private SocketAddress lastReceivedB;
private final SelDatagramChannel channelA_;
private final SelDatagramChannel channelB_;
private SocketAddress lastReceivedA_;
private SocketAddress lastReceivedB_;
private long lastReceivedTimeA;
private long lastReceivedTimeB;
private final int portA;
private final int portB;
private final String ip;
private String attachment;
private Byte localCryptoKey[];
private Byte localCryptoSalt[];
private Byte remoteCryptoKey[];
private Byte remoteCryptoSalt[];
private Encryptor encryptor = null;
private Encryptor encryptor2 = null;
private OutgoingCallHandler callHandler = null;
private MemberReceiver memberReceiver = null;
private int kt = 0;
private int kt2 = 0;
private int kt3 = 0;
private Integer kt4 = new Integer((int)0);
private ByteBuffer wBuffer = ByteBuffer.allocate(64 * 1024 );
private RTPPacket lastAudioPacket = null;
private RTPPacket lastVideoPacket = null;
private JID from;
private RayoComponent component;
private Handset handset = null;
private Long lastPacketTicks = Long.valueOf(0L);
private Long lastVideoTimestamp = Long.valueOf(0L);
private Integer lastAudioTimestamp = new Integer((int)0);
private long decoder = 0;
private final int sampleRate = 48000;
private final int frameSizeInMillis = 20;
private final int outputFrameSize = 2;
private final int channels = 2;
private int frameSizeInSamplesPerChannel = (sampleRate * frameSizeInMillis) / 1000;
private int frameSizeInBytes = outputFrameSize * channels * frameSizeInSamplesPerChannel;
private boolean active = true;
private static final Logger Log = LoggerFactory.getLogger(RelayChannel.class);
public static RelayChannel createLocalRelayChannel(final String host, final int minPort, final int maxPort) throws IOException {
int range = maxPort - minPort;
IOException be = null;
for (int t = 0; t < 50; t++) {
try {
int a = Math.round((int) (Math.random() * range)) + minPort;
a = a % 2 == 0 ? a : a + 1;
return new RelayChannel(host, a);
} catch (BindException e) {
be = e;
} catch (IOException e) {
be = e;
}
}
throw be;
}
public RelayChannel(final String host, final int portA) throws IOException {
final int portB = portA + 2;
addressA = new InetSocketAddress(host, portA);
addressB = new InetSocketAddress(host, portB);
channelA = SelDatagramChannel.open(null, addressA);
channelB = SelDatagramChannel.open(null, addressB);
channelA.setDatagramListener(new DatagramListener() {
public synchronized void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedA = address;
lastReceivedTimeA = System.currentTimeMillis();
if (lastReceivedB != null) {
try {
buffer.flip();
if (callHandler != null)
{
ByteBuffer bb = buffer.asReadOnlyBuffer();
byte[] b = new byte[bb.remaining()];
bb.get(b, 0, b.length);
if (decryptMedia(b) == false) channelB.send(buffer, lastReceivedB);
} else {
channelB.send(buffer, lastReceivedB);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
channelB.setDatagramListener(new DatagramListener() {
public synchronized void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedB = address;
lastReceivedTimeB = System.currentTimeMillis();
if (lastReceivedA != null) {
try {
buffer.flip();
channelA.send(buffer, lastReceivedA);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
this.portA = portA;
this.portB = portB;
// RTCP Support
SocketAddress addressA_ = new InetSocketAddress(host, portA + 1);
SocketAddress addressB_ = new InetSocketAddress(host, portB + 1);
channelA_ = SelDatagramChannel.open(null, addressA_);
channelB_ = SelDatagramChannel.open(null, addressB_);
channelA_.setDatagramListener(new DatagramListener() {
public void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedA_ = address;
if (lastReceivedB_ != null) {
try {
buffer.flip();
//Log.info("RTCP A->B " + buffer.toString());
channelB_.send(buffer, lastReceivedB_);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
channelB_.setDatagramListener(new DatagramListener() {
public void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedB_ = address;
if (lastReceivedA_ != null) {
try {
buffer.flip();
//Log.info("RTCP B->A " + buffer.toString());
channelA_.send(buffer, lastReceivedA_);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
this.ip = host;
}
public SocketAddress getAddressB() {
return addressB;
}
public SocketAddress getAddressA() {
return addressA;
}
public int getPortA() {
return portA;
}
public int getPortB() {
return portB;
}
public String getIp() {
return ip;
}
public long getLastReceivedTimeA() {
return lastReceivedTimeA;
}
public long getLastReceivedTimeB() {
return lastReceivedTimeB;
}
public OutgoingCallHandler getCallHandler() {
return callHandler;
}
public void setCallHandler(OutgoingCallHandler callHandler) {
this.callHandler = callHandler;
}
public Handset getHandset() {
return handset;
}
public String getAttachment() {
return attachment;
}
public void setAttachment(String attachment) {
this.attachment = attachment;
}
public JID getFrom() {
return from;
}
public void setFrom(JID from, RayoComponent component) {
this.from = from;
this.component = component;
}
public void setCrypto(Handset handset)
{
this.handset = handset;
Byte localCryptoByte[] = Convert.fromBase64String(handset.localCrypto);
Byte remoteCryptoByte[] = Convert.fromBase64String(handset.remoteCrypto);
if(ArrayExtensions.getLength(localCryptoByte).intValue() != 30 || ArrayExtensions.getLength(remoteCryptoByte).intValue() != 30)
Log.error("Unexpected key/salt length.");
else {
localCryptoKey = BitAssistant.subArray(localCryptoByte, Integer.valueOf(0), Integer.valueOf(16));
localCryptoSalt = BitAssistant.subArray(localCryptoByte, Integer.valueOf(16), Integer.valueOf(14));
remoteCryptoKey = BitAssistant.subArray(remoteCryptoByte, Integer.valueOf(0), Integer.valueOf(16));
remoteCryptoSalt = BitAssistant.subArray(remoteCryptoByte, Integer.valueOf(16), Integer.valueOf(14));
Log.info("Crypto Suite " + handset.cryptoSuite + " " + handset.localCrypto + " " + handset.remoteCrypto + " " + " " + handset.codec + " " + handset.stereo);
try {
encryptor = new Encryptor(SDPCryptoSuite.getEncryptionMode(handset.cryptoSuite), localCryptoKey, localCryptoSalt, remoteCryptoKey, remoteCryptoSalt);
encryptor2 = new Encryptor(SDPCryptoSuite.getEncryptionMode(handset.cryptoSuite), remoteCryptoKey, remoteCryptoSalt, localCryptoKey, localCryptoSalt);
decoder = Opus.decoder_create(sampleRate, channels);
if (decoder == 0) Log.error( "Opus decoder creation error ");
if (decoder == 0)
{
handset.codec = "PCMU";
Log.warn( "Opus decoder creation failure, PCMU will be used in default");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public String getMediaPreference()
{
String mediaPreference = "PCMU/8000/1";
if (handset.codec == null || "OPUS".equals(handset.codec))
mediaPreference = "PCM/48000/2";
return mediaPreference;
}
public void close() {
try {
channelA.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
channelB.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
channelA_.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
channelB_.close();
} catch (IOException e) {
e.printStackTrace();
}
if (callHandler != null) callHandler.cancelRequest("Channel closing..");
if (decoder != 0)
{
Opus.decoder_destroy(decoder);
decoder = 0;
}
SayCompleteEvent complete = new SayCompleteEvent();
complete.setReason(SayCompleteEvent.Reason.valueOf("SUCCESS"));
Presence presence = new Presence();
presence.setFrom(getAttachment() + "@rayo." + component.getDomain() + "/" + this.from.getNode());
presence.setTo(this.from);
presence.getElement().add(component.getHandsetProvider().toXML(complete));
component.sendPacket(presence);
}
public SelDatagramChannel getChannelA() {
return channelA;
}
public SelDatagramChannel getChannelB() {
return channelB;
}
public SelDatagramChannel getChannelA_() {
return channelA_;
}
public SelDatagramChannel getChannelB_() {
return channelB_;
}
private boolean isStunPacket(Byte bytes[])
{
if(bytes == null || ArrayExtensions.getLength(bytes).intValue() < 20)
return false;
Byte buffer[] = BitAssistant.subArray(bytes, Integer.valueOf(0), Integer.valueOf(2));
Byte method = new Byte((byte)(buffer[1].byteValue() & 0xf));
Byte num2 = new Byte((byte)(buffer[0].byteValue() & 1));
Byte num3 = new Byte((byte)(buffer[1].byteValue() & 0x10));
Integer count = new Integer(BitAssistant.toShortNetwork(BitAssistant.subArray(bytes, Integer.valueOf(2), Integer.valueOf(2)), Integer.valueOf(0)).shortValue());
if(20 + count.intValue() < ArrayExtensions.getLength(bytes).intValue())
return false;
Byte buffer3[] = BitAssistant.subArray(bytes, Integer.valueOf(4), Integer.valueOf(4));
Byte magicCookie[] = new Byte[] {Byte.valueOf((byte)33), Byte.valueOf((byte)18), Byte.valueOf((byte)-92), Byte.valueOf((byte)66)};
for(Integer i = Integer.valueOf(0); i.intValue() < ArrayExtensions.getLength(magicCookie).intValue();)
{
Byte _var0 = magicCookie[i.intValue()];
if(_var0 != null ? !_var0.equals(buffer3[i.intValue()]) : _var0 != buffer3[i.intValue()])
return false;
i = Integer.valueOf(i.intValue() + 1);
}
return true;
}
private Short getLength(Byte bytes[])
{
if(ArrayExtensions.getLength(bytes).intValue() < 4)
return Short.valueOf((short)-1);
else
return BitAssistant.toShortNetwork(bytes, Integer.valueOf(2));
}
private Long getNextAudioTimestamp(Long clockRate)
{
Integer timestamp = lastAudioTimestamp;
lastAudioTimestamp = Integer.valueOf(lastAudioTimestamp.intValue() + (new Integer((new Long((20L * clockRate.longValue()) / 1000L)).intValue())).intValue());
return new Long((new Integer(timestamp.intValue())).longValue());
}
public void sendComfortNoisePayload()
{
}
public boolean encode()
{
return true;
}
public boolean isActive()
{
return active;
}
public void setActive(boolean active)
{
this.active = active;
}
public void pushAudio(int[] dataToSend)
{
}
public synchronized void pushAudio(byte[] rtpData, byte[] opus)
{
try {
if (lastAudioPacket != null)
{
RTPPacket newPacket = RTPPacket.parseBytes(BitAssistant.bytesToArray(rtpData));
RTPPacket packet = RTPPacket.parseBytes(lastAudioPacket.getBytes());
if (opus != null)
{
packet.setPayload(BitAssistant.bytesToArray(opus));
packet.setTimestamp(getNextAudioTimestamp(Long.valueOf(48000)));
} else { // ULAW
packet.setPayload(newPacket.getPayload());
packet.setTimestamp(getNextAudioTimestamp(Long.valueOf(8000)));
}
packet.setSequenceNumber(newPacket.getSequenceNumber());
Byte pcms[] = encryptor2.encryptRTP(packet);
wBuffer.clear();
wBuffer.put( BitAssistant.bytesFromArray(pcms) );
wBuffer.flip();
if (getChannelB() != null && lastReceivedB != null)
{
getChannelB().send(wBuffer, lastReceivedB);
kt++;
if ( kt < 20 ) {
Log.info( "+++ " + packet.getPayload().length );
}
}
}
} catch (Exception e) {
Log.error( "RelayChannel pushAudio exception " + e );
e.printStackTrace();
}
}
public synchronized void pushVideo(RTPPacket videoPacket)
{
}
public void pushReceiverAudio(int[] dataToSend)
{
}
private boolean decryptMedia(byte[] b)
{
Byte data[] = BitAssistant.bytesToArray(b);
boolean decoded = false;
if (isStunPacket(data) == false && encryptor != null)
{
RTPPacket packet = null;
RTPPacket packet2 = null;
RTCPPacket packets[] = null;
try
{
packet2 = RTPPacket.parseBytes(BitAssistant.bytesToArray(b));
if(packet2 != null)
{
decoded = true;
//Log.info("Decoded media " + " " + packet2.getPayloadType() + " " + packet2.getSequenceNumber() + " " + packet2.getTimestamp());
if (packet2.getPayloadType() == 0) // PCMU (uLaw), mix audio
{
packet = encryptor.decryptRTP(data);
if(packet != null)
{
lastAudioPacket = packet;
byte[] byteBuffer = BitAssistant.bytesFromArray(packet.getPayload());
int[] l16Buffer = new int[160];
AudioConversion.ulawToLinear(byteBuffer, 0, byteBuffer.length, l16Buffer);
memberReceiver = callHandler.getMemberReceiver();
if (memberReceiver != null )
{
memberReceiver.handleWebRtcMedia(l16Buffer, packet.getSequenceNumber().shortValue());
if ( kt2 < 10 ) {
Log.info( "ULAW *** " + l16Buffer );
}
kt2++;
}
} else Log.warn("cannot decode packet " + packet2.getPayloadType() + " " + packet2.getSequenceNumber() + " " + packet2.getTimestamp());
} else if (packet2.getPayloadType() == 111) { // OPUS, decode and mix audio
packet = encryptor.decryptRTP(data);
if(packet != null)
{
lastAudioPacket = packet;
byte[] in = BitAssistant.bytesFromArray(packet.getPayload());
int inputOffset = 0;
int inputLength = in.length;
int frameSizeInSamplesPerChannel = Opus.decoder_get_nb_samples(decoder, in, inputOffset, inputLength);
if (frameSizeInSamplesPerChannel > 1)
{
int frameSizeInBytes = outputFrameSize * channels * frameSizeInSamplesPerChannel;
byte[] output = new byte[frameSizeInBytes];
frameSizeInSamplesPerChannel = Opus.decode(decoder, in, inputOffset, inputLength, output, 0, frameSizeInSamplesPerChannel, 0);
memberReceiver = callHandler.getMemberReceiver();
if (memberReceiver != null )
{
int[] l16Buffer = AudioConversion.bytesToLittleEndianInts(output);
placeInStereo(l16Buffer);
memberReceiver.handleWebRtcMedia(l16Buffer, packet.getSequenceNumber().shortValue());
if ( kt2 < 10 ) {
Log.info( "OPUS *** " + l16Buffer );
}
kt2++;
}
} else Log.info( "OPUS.decode fail..." + frameSizeInSamplesPerChannel);
} else Log.warn("cannot decode packet " + packet2.getPayloadType() + " " + packet2.getSequenceNumber() + " " + packet2.getTimestamp());
} else if (packet2.getPayloadType() == 100) { // video (vp8) pass-thru
decoded = false;
} else {
if (packet2.getPayloadType() != 13)
{
byte[] byteBuffer = BitAssistant.bytesFromArray(packet2.getPayload());
//Log.info("Unexpected Payload " + packet2.getPayloadType() + " size " + byteBuffer.length);
decoded = false;
}
}
}
}
catch(Exception exception)
{
Log.error("RelayChannel - Could not decrypt data " + exception);
exception.printStackTrace();
}
}
//Log.info("Payload " + decoded + " " + b);
return decoded;
}
private void placeInStereo(int[] buffer)
{
int stereo = 0;
try {
stereo = Integer.parseInt(handset.stereo);
} catch(Exception exception) {
}
if (stereo > 0)
{
if (stereo > 90) stereo = 90;
int pan = stereo - 90;
pan = (pan < 0) ? -pan : pan;
for (int j = 0; j < buffer.length; j+=2)
{
buffer[j] = (int) (buffer[j] * pan / 90);
}
} else {
if (stereo < -90) stereo = -90;
int pan = stereo + 90;
pan = (pan < 0) ? -pan : pan;
for (int j = 1; j < buffer.length; j+=2)
{
buffer[j] = (int) (buffer[j] * pan / 90);
}
}
}
}