diff --git a/jni/serval-dna b/jni/serval-dna index 2f32635b..6b9ad2b2 160000 --- a/jni/serval-dna +++ b/jni/serval-dna @@ -1 +1 @@ -Subproject commit 2f32635b8760d2e7408b5d2733bfe5917649dd4c +Subproject commit 6b9ad2b2a5feef2af6a2ee64c2ddc5a9558e328b diff --git a/src/org/servalproject/servald/mdp/MeshPacket.java b/src/org/servalproject/servald/mdp/MeshPacket.java new file mode 100644 index 00000000..85da2e30 --- /dev/null +++ b/src/org/servalproject/servald/mdp/MeshPacket.java @@ -0,0 +1,341 @@ +package org.servalproject.servald.mdp; + +import java.net.DatagramPacket; + +import org.servalproject.servald.SubscriberId; + +/** + * Equivalent of {@link DatagramPacket} for MDP protocol. + * + * @author Romain Vimont (®om) + */ +public class MeshPacket { + + /* QoS flags copied from constants.h */ + public static final int OQ_ISOCHRONOUS_VOICE = 0; + public static final int OQ_MESH_MANAGEMENT = 1; + public static final int OQ_ISOCHRONOUS_VIDEO = 2; + public static final int OQ_ORDINARY = 3; + public static final int OQ_OPPORTUNISTIC = 4; + public static final int OQ_MAX = 5; + + /* MDP flags copied from constants.h */ + public static final int FLAG_MDP_FORCE = 1 << 8; + public static final int FLAG_MDP_NOCRYPT = 1 << 9; + public static final int FLAG_MDP_NOSIGN = 1 << 10; + + /** Flag mask for {@code mdpTypeAndFlag} field. */ + private static final int FLAG_MASK = 0xff00; + + /** Data buffer. */ + private byte[] buf; + + /** Offset. */ + private int offset; + + /** Length. */ + private int length; + + /** SID address. */ + private SubscriberId sid; + + /** Port. */ + private int port = -1; + + /** Flags. */ + private int flags; + + /** QoS type. */ + private int qos = OQ_ORDINARY; + + /** + * Constructs a mesh packet for receiving packets of length {@code length} with offset + * {@code offset}. + * + * The {@code length} argument must be less than or equal to {@code buf.length}. + * + * @param buf + * buffer for holding the incoming data. + * @param offset + * offset. + * @param length + * the number of bytes to read. + */ + public MeshPacket(byte[] buf, int offset, int length) { + setData(buf, offset, length); + } + + /** + * Constructs a mesh packet for receiving packets of length {@code length}. + * + * The {@code length} argument must be less than or equal to {@code buf.length}. + * + * @param buf + * buffer for holding the incoming data. + * @param length + * the number of bytes to read. + */ + public MeshPacket(byte[] buf, int length) { + this(buf, 0, length); + } + + /** + * Constructs a mesh packet for sending packets of length {@code length} with offset + * {@code offset} to the specified port number on the specified host. The {@code length} + * argument must be less than or equal to {@code buf.length}. + * + * @param buf + * packet data. + * @param offset + * offset. + * @param length + * length. + * @param sid + * destination SID address. + * @param port + * destination port. + */ + public MeshPacket(byte[] buf, int offset, int length, SubscriberId sid, int port) { + this(buf, offset, length); + setSid(sid); + setPort(port); + } + + /** + * Constructs a mesh packet for sending packets of length {@code length} to the specified port + * number on the specified host. The {@code length} argument must be less than or equal to + * {@code buf.length}. + * + * @param buf + * packet data. + * @param length + * length. + * @param sid + * destination SID address. + * @param port + * destination port. + */ + public MeshPacket(byte[] buf, int length, SubscriberId sid, int port) { + this(buf, 0, length, sid, port); + } + + /** + * Constructs a mesh packet for sending packets of length {@code length} with offset + * {@code offset} to the specified port number on the specified host. The {@code length} + * argument must be less than or equal to {@code buf.length}. + * + * @param buf + * packet data. + * @param offset + * offset. + * @param length + * length. + * @param address + * destination address. + */ + public MeshPacket(byte[] buf, int offset, int length, MeshSocketAddress address) { + setData(buf, offset, length); + setMeshSocketAddress(address); + } + + /** + * Constructs a mesh packet for sending packets of length {@code length} to the specified port + * number on the specified host. The {@code length} argument must be less than or equal to + * {@code buf.length}. + * + * @param buf + * packet data. + * @param length + * length. + * @param address + * destination address. + */ + public MeshPacket(byte[] buf, int length, MeshSocketAddress address) { + this(buf, 0, length, address); + } + + /** + * Set the data buffer, with the specified offset and length. + * + * @param buf + * buffer. + * @param offset + * offset. + * @param length + * length. + */ + public synchronized void setData(byte[] buf, int offset, int length) { + /* this will check to see if buf is null */ + if (length < 0 || offset < 0 || length + offset < 0 || length + offset > buf.length) { + throw new IllegalArgumentException("Illegal length or offset"); + } + this.buf = buf; + this.offset = offset; + this.length = length; + } + + /** + * Set the data buffer, with the offset set to 0, and the length set to the length of + * {@code buf}. + * + * @param buf + * buffer. + */ + public synchronized void setData(byte[] buf) { + setData(buf, 0, buf.length); + } + + /** + * Sets the SID address of the machine to which this mesh packet is being sent. + * + * @param sid + * SID address. + */ + public synchronized void setSid(SubscriberId sid) { + this.sid = sid; + } + + /** + * Sets the port number on the remote host to which this mesh packet is being sent. + * + * @param port + * port. + */ + public synchronized void setPort(int port) { + this.port = port; + } + + /** + * Sets the mesh socket address of the remote host to which this packet is being sent. + * + * @param address + * mesh socket address. + */ + public synchronized void setMeshSocketAddress(MeshSocketAddress address) { + setSid(address.getSid()); + setPort(address.getPort()); + } + + /** + * Set the length for this packet. The length of the packet is the number of bytes from the + * packet's data buffer that will be sent, or the number of bytes of the packet's data buffer + * that will be used for receiving data. The length must be lesser or equal to the offset plus + * the length of the packet's buffer. + * + * @param length + * data length. + */ + public synchronized void setLength(int length) { + if (length < 0 || length + offset < 0 || length + offset > buf.length) { + throw new IllegalArgumentException("illegal length"); + } + this.length = length; + } + + /** + * Set the flags. + * + * @param flags + * flags. + */ + public synchronized void setFlags(int flags) { + this.flags = flags & FLAG_MASK; + } + + /** + * Add the flags. + * + * @param flags + * flags. + */ + public synchronized void addFlags(int flags) { + this.flags |= flags & FLAG_MASK; + } + + /** + * Remove the flags. + * + * @param flags + * flags. + */ + public synchronized void removeFlag(int flags) { + this.flags &= ~(flags & FLAG_MASK); + } + + /** + * Set the QoS type. + * + * @param qos + * QoS. + */ + public synchronized void setQos(int qos) { + this.qos = qos; + } + + /** + * Returns the data buffer. The data received or the data to be sent starts from the offset in + * the buffer, and runs for length long. + * + * @return data buffer. + */ + public synchronized byte[] getBuf() { + return buf; + } + + /** + * Returns the offset of the data to be sent or the offset of the data received. + * + * @return data offset. + */ + public synchronized int getOffset() { + return offset; + } + + /** + * Returns the length of the data to be sent or the length of the data received. + * + * @return data length. + */ + public synchronized int getLength() { + return length; + } + + /** + * Returns the SID address of the machine to which this packet is being sent or from which the + * packet was received. + * + * @return SID address. + */ + public synchronized SubscriberId getSid() { + return sid; + } + + /** + * Returns the port number on the remote host to which this packet is being sent or from which + * the packet was received. + * + * @return Port. + */ + public synchronized int getPort() { + return port; + } + + /** + * Returns the flags. + * + * @return Flags. + */ + public synchronized int getFlags() { + return flags; + } + + /** + * Return the QoS type. + * + * @return QoS. + */ + public synchronized int getQos() { + return qos; + } + +} diff --git a/src/org/servalproject/servald/mdp/MeshSocket.java b/src/org/servalproject/servald/mdp/MeshSocket.java new file mode 100644 index 00000000..bcbb5676 --- /dev/null +++ b/src/org/servalproject/servald/mdp/MeshSocket.java @@ -0,0 +1,238 @@ +package org.servalproject.servald.mdp; + +import java.io.IOException; +import java.net.DatagramSocket; + +import org.servalproject.servald.SubscriberId; + +/** + * Equivalent of {@link DatagramSocket} for MDP protocol. + * + * Server-side example: + * + *
+ * byte[] data = new byte[BUF_SIZE];
+ * MeshSocket serverSocket = new MeshSocket(SERVER_PORT);
+ * MeshPacket packet = new MeshPacket(data, data.length);
+ * serverSocket.receive(packet); // blocking call
+ * 
+ * + * Client-side example: + * + *
+ * byte[] data = ...;
+ * MeshSocket clientSocket = new MeshSocket(CLIENT_PORT);
+ * MeshPacket packet = new MeshPacket(data, data.length, SERVER_SID, SERVER_PORT);
+ * clientSocket.send(packet);
+ * 
+ * + * @author Romain Vimont (®om) + */ +public class MeshSocket { + + static { + // Some native initializations + init(); + } + + /** File descriptor. */ + private int fd; + + /** Flag indicating whether the socket is bound. */ + private boolean bound; + + /** Flag indicating whether the socket is closed. */ + private boolean closed; + + /** SubscriberID as binary. */ + private byte[] rawSid; + + /** Port */ + private int port; + + /** Lock for closing socket. */ + private final Object closeLock = new Object(); + + /** + * Constructs a mesh socket and binds it to any available port on the local host machine. The + * socket will be bound to the wildcard address. + * + * @throws MeshSocketException + * if the socket could not be opened, or the socket could not bind to the specified + * local port. + */ + public MeshSocket() throws MeshSocketException { + _create(); + bind(new MeshSocketAddress(0)); + } + + /** + * Creates a mesh socket, bound to the specified local mesh address. + * + * If, if the address is {@code null}, creates an unbound socket. + * + * @param bindaddr + * local mesh address to bind, or {@code null} for an unbound socket. + * @throws MeshSocketException + * if the socket could not be opened, or the socket could not bind to the specified + * local port. + */ + public MeshSocket(MeshSocketAddress bindaddr) throws MeshSocketException { + _create(); + if (bindaddr != null) { + bind(bindaddr); + } + } + + /** + * Constructs a mesh socket and binds it to the specified port on the local host machine. The + * socket will be bound to the wildcard address. + * + * @param port + * port to use. + * @throws MeshSocketException + * if the socket could not be opened, or the socket could not bind to the specified + * local port. + */ + public MeshSocket(int port) throws MeshSocketException { + this(port, null); + } + + /** + * Creates a mesh socket, bound to the specified local SID address. If the SID address is 0, the + * socket will be bound to the wildcard address. + * + * @param port + * local port to use. + * @param sid + * local address to bind. + * @throws MeshSocketException + * if the socket could not be opened, or the socket could not bind to the specified + * local port. + */ + public MeshSocket(int port, SubscriberId sid) throws MeshSocketException { + this(new MeshSocketAddress(sid, port)); + } + + /** + * Returns the binding state of the socket. + * + * @return {@code true} if the socket successfully bound to an address, {@code false} otherwise. + */ + public synchronized boolean isBound() { + return bound; + } + + /** + * Returns whether the socket is closed or not. + * + * @return {@code true} if the socket has been closed, {@code false} otherwise. + */ + public boolean isClosed() { + synchronized (closeLock) { + return closed; + } + } + + /** + * Binds this mesh socket to a specific address and port. + * + * If the address is {@code null}, then the system will pick up an ephemeral port and a valid + * local address to bind the socket. + * + * @param addr + * mesh socket address. + * @throws MeshSocketException + */ + public synchronized void bind(MeshSocketAddress addr) throws MeshSocketException { + if (isClosed()) { + throw new MeshSocketException("Mesh socket is closed"); + } + if (isBound()) { + throw new MeshSocketException("Mesh socket already bound"); + } + if (addr == null) { + addr = new MeshSocketAddress(0); + } + try { + SubscriberId sid = addr.getSid(); + _bind(addr.getPort(), addr.getSid()); + port = addr.getPort(); + rawSid = sid == null ? null : sid.binary; + } catch (MeshSocketException e) { + close(); + throw e; + } + bound = true; + } + + /** + * Sends a mesh packet from this socket. The mesh packet includes information indicating the + * data to be sent, its length, the SID address of the remote host, and the port number on the + * remote host. + * + * @param packet + * mesh packet to send. + * @throws IOException + * if an I/O error occurs. + */ + public synchronized void send(MeshPacket packet) throws IOException { + if (isClosed()) { + throw new MeshSocketException("Mesh socket is closed"); + } + if (!isBound()) { + bind(new MeshSocketAddress(0)); + } + _send(packet); + } + + /** + * Receives a mesh packet from this socket. When this method returns, the mesh packet's buffer + * is filled with the data received. The mesh packet also contains the sender's SID address, and + * the port number on the sender's machine. + * + * This method blocks until data is received. The length field of the mesh packet object + * contains the length of the received message. If the message is longer than the packet's + * length, the message is truncated. + * + * @param packet + * the mesh packet into which to place the incoming data. + * @throws IOException + * if an I/O error occurs. + */ + public synchronized void receive(MeshPacket packet) throws IOException { + if (isClosed()) { + throw new MeshSocketException("Mesh socket is closed"); + } + if (!isBound()) { + bind(new MeshSocketAddress(0)); + } + _receive(packet); + } + + /** + * Closes this mesh socket. + * + * Any thread currently blocked in {@link #receive(MeshPacket)} upon this socket will throw a + * {@link MeshSocketException}. TODO not yet implemented + */ + public void close() { + synchronized (closeLock) { + closed = true; + _close(); + } + } + + private native static void init(); + + private native void _create() throws MeshSocketException; + + private native void _bind(int port, SubscriberId sid) throws MeshSocketException; + + private native void _send(MeshPacket packet) throws IOException; + + private native void _receive(MeshPacket packet) throws IOException; + + private native void _close(); + +} diff --git a/src/org/servalproject/servald/mdp/MeshSocketAddress.java b/src/org/servalproject/servald/mdp/MeshSocketAddress.java new file mode 100644 index 00000000..7a8c536b --- /dev/null +++ b/src/org/servalproject/servald/mdp/MeshSocketAddress.java @@ -0,0 +1,62 @@ +package org.servalproject.servald.mdp; + +import java.net.InetSocketAddress; + +import org.servalproject.servald.Identity; +import org.servalproject.servald.SubscriberId; + +/** + * Equivalent of {@link InetSocketAddress} for MDP protocol. + * + * @author Romain Vimont (®om) + */ +public class MeshSocketAddress { + + /** SID address. */ + private SubscriberId sid; + + /** Port. */ + private int port; + + /** + * Constructs a mesh socket address (a SID address and a port). + * + * @param sid + * SID address (can be {@code null}). + * @param port + * Port. + */ + public MeshSocketAddress(SubscriberId sid, int port) { + this.sid = sid; + this.port = port; + } + + /** + * Constructs a mesh socket address with {@code null} SID (wildcard address). + * + * @param port + * Port. + */ + public MeshSocketAddress(int port) { + this(Identity.getMainIdentity().subscriberId, port); + } + + /** + * Returns the SID address. + * + * @return SID address. + */ + public SubscriberId getSid() { + return sid; + } + + /** + * Returns the port. + * + * @return Port. + */ + public int getPort() { + return port; + } + +} diff --git a/src/org/servalproject/servald/mdp/MeshSocketException.java b/src/org/servalproject/servald/mdp/MeshSocketException.java new file mode 100644 index 00000000..7f223ff0 --- /dev/null +++ b/src/org/servalproject/servald/mdp/MeshSocketException.java @@ -0,0 +1,29 @@ +package org.servalproject.servald.mdp; + +import java.io.IOException; +import java.net.SocketException; + +/** + * Equivalent of {@link SocketException} for MDP. + * + * @author Romain Vimont (®om) + */ +@SuppressWarnings("serial") +public class MeshSocketException extends IOException { + + /** + * Constructs a {@code MeshSocketException} without any message. + */ + public MeshSocketException() {} + + /** + * Constructs a {@code MeshSocketException} with a message. + * + * @param msg + * Message. + */ + public MeshSocketException(String msg) { + super(msg); + } + +}