package the8472.mldht;
import static the8472.bencode.Utils.str2buf;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.Key;
import lbms.plugins.mldht.kad.messages.GetPeersRequest;
import lbms.plugins.mldht.kad.messages.MessageBase;
import lbms.plugins.mldht.utils.NIOConnectionManager;
import lbms.plugins.mldht.utils.Selectable;
import the8472.utils.ConfigReader;
public class Firehose implements Component {
List<Connection> connections = new CopyOnWriteArrayList<>();
@Override
public void start(Collection<DHT> dhts, ConfigReader config) {
selector = new NIOConnectionManager("firehose");
dhts.forEach(d -> {
d.addIncomingMessageListener(this::incomingMessage);
});
try {
selector.register(new Server());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
NIOConnectionManager selector;
class Server implements Selectable {
public Server() throws IOException {
chan = ServerSocketChannel.open();
chan.configureBlocking(false);
// listen on [::0]:35465
chan.bind(new InetSocketAddress(InetAddress.getByAddress(new byte[16]), 35465));
}
ServerSocketChannel chan ;
@Override
public SelectableChannel getChannel() {
return chan;
}
@Override
public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException {}
@Override
public void selectionEvent(SelectionKey key) throws IOException {
SocketChannel connection;
while((connection = chan.accept()) != null) {
new Connection(connection);
}
}
@Override
public void doStateChecks(long now) throws IOException {}
@Override
public int calcInterestOps() {
return SelectionKey.OP_ACCEPT;
}
};
class Connection implements Selectable {
final SocketChannel chan;
AtomicInteger writePointer = new AtomicInteger();
AtomicInteger readPointer = new AtomicInteger();
// length must be power of 2
AtomicReferenceArray<ByteBuffer> toWrite = new AtomicReferenceArray<>(1024);
volatile boolean empty = true;
void add(ByteBuffer buf) {
int current = writePointer.getAndIncrement();
toWrite.set(current & (toWrite.length() - 1), buf);
if(empty) {
readPointer.set(current);
empty = false;
selector.interestOpsChanged(this);
}
}
ByteBuffer poll() {
if(currentBuf != null && currentBuf.remaining() > 0)
return currentBuf;
int current = readPointer.getAndIncrement();
currentBuf = toWrite.getAndSet(current & (toWrite.length() - 1), null);
if(currentBuf == null) {
empty = true;
selector.interestOpsChanged(this);
}
return currentBuf;
}
public Connection(SocketChannel chan) throws IOException {
this.chan = chan;
chan.configureBlocking(false);
selector.register(this);
connections.add(this);
}
@Override
public SelectableChannel getChannel() {
return chan;
}
@Override
public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException {}
@Override
public void selectionEvent(SelectionKey key) throws IOException {
if(key.isValid() && key.isReadable())
read();
if(key.isValid() && key.isWritable())
write();
}
ByteBuffer readBuf = ByteBuffer.allocateDirect(4096);
void read() throws IOException {
try {
while(true) {
int read = chan.read(readBuf);
readBuf.rewind();
if(read < 0)
chan.close();
if(read == 0)
break;
}
} catch(ClosedChannelException ex) {
chan.close();
}
}
ByteBuffer currentBuf;
void write() throws IOException {
while(true) {
ByteBuffer buf = poll();
if(buf == null)
break;
if(chan.write(buf) == 0)
break;
}
}
@Override
public void doStateChecks(long now) throws IOException {
if(!chan.isOpen())
connections.remove(this);
}
@Override
public int calcInterestOps() {
int ops = SelectionKey.OP_READ;
if(!empty)
ops |= SelectionKey.OP_WRITE;
return ops;
}
}
void incomingMessage(DHT dht, MessageBase msg) {
if(msg.getType() != MessageBase.Type.REQ_MSG || msg.getMethod() != MessageBase.Method.GET_PEERS)
return;
if(connections.isEmpty())
return;
GetPeersRequest req = (GetPeersRequest) msg;
StringBuilder b = new StringBuilder();
Key k = req.getInfoHash();
String addr = req.getOrigin().getAddress().getHostAddress();
long now = System.currentTimeMillis();
b.append(now).append('\t').append(k.toString(false)).append('\t').append(addr).append('\n');
ByteBuffer buf = str2buf(b.toString());
connections.forEach(c -> {
c.add(buf.duplicate());
});
}
@Override
public void stop() {
connections.forEach(c -> {
try {
c.chan.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
}