/*
* Minha.pt: middleware testing platform.
* Copyright (c) 2011-2014, Universidade do Minho.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package pt.minha.models.global.net;
import java.io.Closeable;
import java.net.BindException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import pt.minha.api.sim.Calibration;
import pt.minha.kernel.simulation.Event;
import pt.minha.kernel.simulation.SimpleResource;
import pt.minha.kernel.simulation.Timeline;
public class NetworkStack implements Closeable {
private Network network;
private Timeline timeline;
private InetAddress localAddress, broadcastAddress;
private final List<Integer> usedTCPPorts = new LinkedList<Integer>();
private final List<Integer> usedUDPPorts = new LinkedList<Integer>();
private int INITIAL_PORT = 10000;
private String macAddress;
private SimpleResource bandwidth;
public NetworkStack(Timeline timeline, String host, Network network) throws UnknownHostException {
this.timeline = timeline;
this.network = network;
this.localAddress = network.addHost(host, this);
this.broadcastAddress = network.getBroadcastAddress();
this.macAddress = "02:00";
for(int i=0;i<localAddress.getAddress().length;i++)
macAddress+=":"+Integer.toHexString(localAddress.getAddress()[i]);
this.bandwidth = new SimpleResource(timeline);
}
public InetAddress getLocalAddress() {
return this.localAddress;
}
public String getMACAddress() {
return this.macAddress;
}
public InetAddress getBroadcastAddress() {
return this.broadcastAddress;
}
private int getAvailablePort(List<Integer> usedPorts) {
boolean reset = false;
int port = -1;
do {
if (INITIAL_PORT>=(1<<16)-1) {
if (reset)
break;
INITIAL_PORT = 10000;
reset = true;
}
port = this.INITIAL_PORT++;
}
while ( usedPorts.contains(port) );
if ( port <= 0 )
throw new IllegalArgumentException("port out of range: " + port);
return port;
}
public InetSocketAddress getUDPBindAddress(int port) {
if ( port < 0 || port> 0xFFFF )
throw new IllegalArgumentException("bind: " + port);
if ( 0 == port )
port = this.getAvailablePort(usedUDPPorts);
if ( this.usedUDPPorts.contains(port) )
throw new IllegalArgumentException("Port already used: " + port);
this.usedUDPPorts.add(port);
return new InetSocketAddress(this.localAddress, port);
}
public InetSocketAddress getTCPBindAddress(int port) {
if ( port < 0 || port> 0xFFFF )
throw new IllegalArgumentException("bind: " + port);
if ( 0 == port )
port = this.getAvailablePort(usedTCPPorts);
if ( this.usedTCPPorts.contains(port) )
throw new IllegalArgumentException("Port already used: " + port);
this.usedTCPPorts.add(port);
return new InetSocketAddress(this.localAddress, port);
}
public Network getNetwork() {
return network;
}
public Timeline getTimeline() {
return timeline;
}
private final Map<InetSocketAddress, UDPSocket> socketsUDP = new HashMap<InetSocketAddress, UDPSocket>();
private final Map<InetSocketAddress, ListeningTCPSocket> socketsTCP = new HashMap<InetSocketAddress, ListeningTCPSocket>();
private final Set<ClientTCPSocket> connTCP = new HashSet<ClientTCPSocket>();
public InetSocketAddress addTCPSocket(InetSocketAddress isa, ListeningTCPSocket ab) throws SocketException {
if ( socketsTCP.containsKey(isa) )
throw new BindException(isa.toString() + " Cannot assign requested address on TCP");
socketsTCP.put(isa, ab);
return isa;
}
public void addTCPConnection(ClientTCPSocket ab) {
connTCP.add(ab);
}
public InetSocketAddress addUDPSocket(InetSocketAddress isa, UDPSocket udpSocket) throws SocketException {
if ( socketsUDP.containsKey(isa) )
throw new BindException(isa.toString() + " Cannot assign requested address on UDP");
socketsUDP.put(isa, udpSocket);
return isa;
}
public void removeTCPSocket(InetSocketAddress isa) {
usedTCPPorts.remove((Object)isa.getPort());
socketsTCP.remove(isa);
}
public void removeTCPConn(ClientTCPSocket isa) {
connTCP.remove(isa);
}
public void removeUDPSocket(InetSocketAddress isa) {
usedUDPPorts.remove((Object)isa.getPort());
socketsUDP.remove(isa);
}
public Event handleDatagram(final InetSocketAddress destination, final DatagramPacket packet) {
return new Event(timeline) {
public void run() {
UDPSocket sgds = socketsUDP.get(destination);
if (sgds==null && destination.getAddress().equals(broadcastAddress)) {
InetSocketAddress dest =new InetSocketAddress(localAddress, destination.getPort());
sgds=socketsUDP.get(dest);
}
if (sgds!=null)
sgds.queue(packet);
}
};
}
public Event handleConnect(final InetSocketAddress destination, final TCPPacket p) {
return new Event(timeline) {
public void run() {
ListeningTCPSocket ssi = socketsTCP.get(destination);
if (ssi==null)
ssi = socketsTCP.get(new InetSocketAddress(destination.getPort()));
if (ssi==null || !ssi.queueConnect(p)) // connection refused
relayTCPData(new TCPPacket(null, p.getSource(), 0, 0, new byte[0], TCPPacket.RST));
}
};
}
public void relayTCPConnect(final InetSocketAddress serverAddr, final TCPPacket p) {
long qdelay = bandwidth.use(getConfig().getLineDelay(p.getSize())) + getConfig().getLineLatency(p.getSize()) - getTimeline().getTime();
NetworkStack target = network.getHost(serverAddr.getAddress());
if (target==null)
p.getSource().scheduleRead(new TCPPacket(null, p.getSource(), 0, 0, new byte[0], TCPPacket.RST)).schedule(qdelay);
else
target.handleConnect(serverAddr, p).scheduleFrom(timeline, qdelay);
}
public void relayTCPData(final TCPPacket p) {
long qdelay = bandwidth.use(getConfig().getLineDelay(p.getSize())) + getConfig().getLineLatency(p.getSize()) - getTimeline().getTime();
p.getDestination().scheduleRead(p).scheduleFrom(timeline, qdelay);
}
public void relayUDP(final InetSocketAddress destination, final DatagramPacket p) {
long time = bandwidth.useConditionally(getConfig().getMaxDelay(), getConfig().getLineDelay(p.getLength()));
if (time < 0)
return;
long qdelay = time + getConfig().getLineLatency(p.getLength()) - getTimeline().getTime();
if (destination.getAddress().isMulticastAddress()) {
Collection<NetworkStack> stacks = network.getMulticastHosts(destination.getAddress());
if (stacks != null)
for (NetworkStack stack: stacks)
stack.handleDatagram(destination, p).scheduleFrom(timeline, qdelay);
} else if (destination.getAddress().equals(broadcastAddress)) {
Collection<NetworkStack> stacks = network.getBroadcastHosts();
for (NetworkStack stack: stacks)
stack.handleDatagram(destination, p).scheduleFrom(timeline, qdelay);
} else {
NetworkStack stack = network.getHost(destination.getAddress());
if (stack!=null)
stack.handleDatagram(destination, p).scheduleFrom(timeline, qdelay);
}
}
public Calibration getConfig() {
return network.config;
}
@Override
public void close() {
ArrayList<ClientTCPSocket> l = new ArrayList<ClientTCPSocket>(connTCP);
for(ClientTCPSocket tcp: l) {
tcp.shutdownInput();
tcp.shutdownOutput();
}
}
}