package flosc;
import java.util.*;
import java.io.*;
import java.net.*;
/**
* OscServer <BR><BR> OpenSoundControl UDP Server for Gateway. Based on CommServer by Derek Clayton and dumpOSC by Matt Wright. Thanks to Jesse Gilbert (j@resistantstrain.net) for helping me with the static socket.
* @author Ben Chun ben@benchun.net
* @author Ignacio Delgado idelgado@h-umus.it
* @version 2.0
*/
public class OscServer extends Thread
{
private DatagramSocket oscSocket; // incoming UDP socket
private int port; // incoming UDP port
private Gateway gateway; // Gateway for this server
private static OscSocket outSocket; // outgoing UDP socket
private String oscOutputIP;
private int oscOutputPort;
static
{
try
{
outSocket = new OscSocket();
}
catch( SocketException se )
{
se.printStackTrace();
}
}
/**
* Constructor for the OscServer.
*
* @param port UDP port for OSC communication.
* @param gateway parent Gateway.
*/
public OscServer(int port, Gateway gateway) {
this.port = port;
this.gateway = gateway;
}
public OscServer(int port, Gateway gateway, String oscOutputIp, int oscOutputPort) {
this.port = port;
this.gateway = gateway;
this.oscOutputIP = oscOutputIp;
this.oscOutputPort = oscOutputPort;
}
/**
* Thread run method. Monitors incoming messages.
*/
public void run()
{
try{
oscSocket = new DatagramSocket(port);
Debug.writeActivity("UDP OSC server started on port: " + port,this);
while(true)
{
try{
byte[] datagram = new byte[4096];
DatagramPacket packet = new DatagramPacket(datagram, datagram.length);
// block until a datagram is received
oscSocket.receive(packet);
// parse the packet
OscPacket oscp = new OscPacket();
oscp.address = packet.getAddress();
oscp.port = packet.getPort();
parseOscPacket(datagram, packet.getLength(), oscp);
gateway.multiplexPacket(oscp, this);
}catch(SocketException exception){
Debug.writeActivity(exception.getMessage(),this);
break;
}catch(IOException ioe){
Debug.writeException("Server error...",this, ioe);
// kill this server
}catch(Exception ex) {
Debug.writeException("Exception while sending...",this, ex);
}
}
}catch(SocketException exception){
Debug.writeActivity("Server error...could not create socket: "+exception.getMessage(),this);
}
}
/**
* This method is based on dumpOSC::ParseOSCPacket. It verifies
* that the packet is well-formed and puts the data into an
* OscPacket object, or else prints useful error messages about
* what went wrong.
*
* @param datagram a byte array containing the OSC packet
* @param n size of the byte array
* @param packet the OscPacket object to put data into
*/
public void parseOscPacket(byte[] datagram, int n, OscPacket packet)
{
int size, i;
if ( (n % 4) != 0 )
{
Debug.writeActivity("SynthControl packet size (" + n +
") not a multiple of 4 bytes, dropped it.",this);
return;
}
String dataString = new String(datagram);
if ( ( n >= 8 ) && dataString.startsWith( "#bundle" ) )
{
/* This is a bundle message. */
if ( n < 16 )
{
Debug.writeActivity("Bundle message too small (" + n +
" bytes) for time tag, dropped it.",this);
return;
}
/* Get the time tag */
Long time = new Long( Bytes.toLong( Bytes.copy(datagram, 8, 8) ) );
packet.setTime(time.longValue());
i = 16; /* Skip "#bundle\0" and time tags */
while( i<n )
{
size = ( Bytes.toInt( Bytes.copy(datagram, i, i+4 ) ) );
if ((size % 4) != 0)
{
Debug.writeActivity("Bad size count" + size +
"in bundle (not a multiple of 4)",this);
return;
}
if ( (size + i + 4) > n )
{
Debug.writeActivity("Bad size count" + size + "in bundle" +
"(only" + (n-i-4) + "bytes left in entire bundle)",this);
return;
}
/* Recursively handle element of bundle */
byte[] remaining = Bytes.copy(datagram, i+4);
parseOscPacket(remaining, size, packet);
i += (4 + size);
}
}else{
/* This is not a bundle message */
Vector nameAndData = getStringAndData(datagram, n);
String name = (String) nameAndData.firstElement();
OscMessage message = new OscMessage(name);
byte[] data = (byte[]) nameAndData.lastElement();
Vector[] typesAndArgs = getTypesAndArgs(data);
if(typesAndArgs != null){
message.setTypesAndArgs(typesAndArgs[0], typesAndArgs[1]);
packet.addMessage(message);
}else{
packet = null;
}
}
}
/**
* Takes a byte array starting with a string padded with null
* characters so that the length of the entire block is a multiple
* of 4, and seperates it into a String and a byte array of the
* remaining data. These are then returned in a Vector.
*
* @param block block of data beginning with a string
* @param stringLength number of characters in the string
*/
public Vector getStringAndData(byte[] block, int stringLength)
{
Vector v = new Vector();
int i;
if ( stringLength %4 != 0)
{
Debug.writeActivity("printNameAndArgs: bad boundary",this);
return v;
}
for (i = 0; block[i] != '\0'; i++)
{
if (i >= stringLength)
{
Debug.writeActivity("printNameAndArgs: Unreasonably long string",this);
return v;
}
}
// v.firstElement() is the String
v.addElement( new String(Bytes.copy(block, 0, i)) );
i++;
for (; (i % 4) != 0; i++)
{
if (i >= stringLength)
{
Debug.writeActivity("printNameAndArgs: Unreasonably long string",this);
return v;
}
if (block[i] != '\0')
{
Debug.writeActivity("printNameAndArgs: Incorrectly padded string.",this);
return v;
}
}
// v.elementAt(1) is the position in the orginal byte[] where the data starts
v.addElement( new Integer(i) );
// v.lastElement() is the byte[] of data
v.addElement( Bytes.copy( block, i ) );
return v;
}
/**
* Returns an array of Vectors containing types and arguments.
*
* @param block byte array containing types and arguments
*/
public Vector[] getTypesAndArgs( byte[] block )
{
// TBD : throw exceptions or something when there are no type tags
int n = block.length;
Vector[] va = new Vector[2];
if (n != 0)
{
if (block[0] == ',')
{
if (block[1] != ',')
{
/* This message begins with a type-tag string */
va = getTypeTaggedArgs( block );
}
else
{
/* Double comma means an escaped real comma, not a
* type string */
va = getHeuristicallyTypeGuessedArgs( block );
}
}
else
{
va = getHeuristicallyTypeGuessedArgs( block );
}
}
return va;
}
/**
* Returns Vectors containing the types and arguments from a
* type-tagged byte array
*
* @param block a byte array with type-tagged data
*/
public Vector[] getTypeTaggedArgs( byte[] block )
{
Vector<Character> typeVector = new Vector<Character>();
Vector argVector = new Vector();
int p = 0;
/* seperate the block into the types byte array and the
* argument byte array*/
Vector typesAndArgs = getStringAndData(block, block.length);
byte[] args = (byte[]) typesAndArgs.lastElement();
for (int thisType=1; block[thisType] != 0; thisType++)
{
switch (block[thisType])
{
case '[' :
typeVector.addElement(new Character('['));
break;
case ']' :
typeVector.addElement(new Character(']'));
break;
case 'i': case 'r': case 'm': case 'c':
typeVector.addElement(new Character('i'));
argVector.addElement( new Integer( Bytes.toInt( Bytes.copy(args, p, p+4) )) );
p += 4;
break;
case 'f':
typeVector.addElement(new Character('f'));
argVector.addElement( new Float( Bytes.toFloat( Bytes.copy(args, p, p+4) )) );
p += 4;
break;
case 'h': case 't':
typeVector.addElement(new Character('h'));
argVector.addElement( new Long( Bytes.toLong( Bytes.copy(args, p, p+8) )) );
p += 8;
break;
case 'd':
typeVector.addElement(new Character('d'));
argVector.addElement( new Double( Bytes.toDouble( Bytes.copy(args, p, p+8) )) );
p += 8;
break;
case 's': case 'S':
typeVector.addElement(new Character('s'));
byte[] remaining = Bytes.copy(args, p);
Vector v = getStringAndData( remaining, remaining.length );
argVector.addElement( (String)v.firstElement() );
p += ((Integer)v.elementAt(1)).intValue();
break;
case 'T':
typeVector.addElement(new Character('T'));
break;
case 'F':
typeVector.addElement(new Character('F'));
break;
case 'N':
typeVector.addElement(new Character('N'));
break;
case 'I':
typeVector.addElement(new Character('I'));
break;
default:
Debug.writeActivity( "[Unrecognized type tag " +
block[thisType] + "]" ,this);
}
}
Vector[] returnValue = new Vector[2];
returnValue[0] = typeVector;
returnValue[1] = argVector;
return returnValue;
}
/**
* Returns the arguments from a non-type-tagged byte array
*
* @param block a byte array containing data
*/
public Vector[] getHeuristicallyTypeGuessedArgs( byte[] block )
{
// TBD : handle packets without type tags
Debug.writeActivity("Bad OSC packet: No type tags",this);
return new Vector[2];
}
/**
* Sends an OscPacket via an OscSocket. the packet contains the
* address and port information.
*
* @param packet the OscPacket to send
*/
public void sendPacket(OscPacket packet)
{
try
{
Debug.writeActivity("OscServer sending OSC packet via UDP to IP "+oscOutputIP+" on port "+oscOutputPort,this);
outSocket.sendToOutputPortAndIP(packet, InetAddress.getByName(oscOutputIP), oscOutputPort);
}
catch(IOException ioe)
{
Debug.writeException("sendPacket error...",this, ioe);
} catch(Exception ex) {
Debug.writeException("Exception while sending...",this, ex);
}
}
public void sendPacketToPort(OscPacket packet, int port) {
try {
Debug.writeActivity("OscServer sending OSC packet via UDP to localhost on port "+port,this);
outSocket.sendToOutputPort(packet, port);
} catch(IOException ioe) {
Debug.writeException("sendPacket error...",this, ioe);
} catch(Exception ex) {
Debug.writeException("Exception while sending...",this, ex);
}
}
public void sendPacketToPortAndIP(OscPacket packet, String ip, int port) {
try {
Debug.writeActivity("OscServer sending OSC packet via UDP to IP "+ ip + " on port "+port,this);
outSocket.sendToOutputPortAndIP(packet, InetAddress.getByName(ip), port);
} catch(IOException ioe) {
Debug.writeException("sendPacket error...",this, ioe);
} catch(Exception ex) {
Debug.writeException("Exception while sending...",this, ex);
}
}
/**
* Stops the UDP server.
*/
public void killServer()
{
oscSocket.close();
Debug.writeActivity("UDP OSC server stopped",this);
}
public int getPort(){
return port;
}
}
|