package jelectrum;
import java.lang.Math;
import java.util.Map;
import java.util.UUID;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import java.net.Socket;
import java.net.ServerSocket;
import org.json.JSONObject;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Block;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.sql.Connection;
import java.sql.PreparedStatement;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import java.security.KeyStore;
import java.io.FileInputStream;
public class StratumServer
{
private long max_idle_time = 1800L * 1000L * 1000L * 1000L;//30 minutes in nanos
private Map<String, StratumConnection> conn_map=new HashMap<String, StratumConnection>(1024, 0.5f);
private Config config;
private NetworkParameters network_params;
private EventLog event_log;
private String instance_id;
private StratumServer server;
private Jelectrum jelectrum;
private RateLimit global_rate_limit;
private int tcp_port = -1;
private int ssl_port = -1;
public StratumServer(Jelectrum jelectrum, Config config)
{
this.jelectrum = jelectrum;
this.config = config;
//config.require("tcp_port");
//config.require("ssl_port");
if (config.isSet("ssl_port"))
{
config.require("keystore_path");
config.require("keystore_store_password");
config.require("keystore_key_password");
}
if (config.isSet("global_rate_limit"))
{
global_rate_limit = new RateLimit(config.getDouble("global_rate_limit"), 2.0);
}
server = this;
}
public void start()
throws java.net.SocketException, java.io.IOException, java.security.GeneralSecurityException
{
getEventLog().log("SERVER START");
new TimeoutThread().start();
if (config.isSet("tcp_port"))
{
List<String> ports = config.getList("tcp_port");
for(String s : ports)
{
int port = Integer.parseInt(s);
if (tcp_port < 0) tcp_port = port;
ServerSocket ss = new ServerSocket(port, 256);
ss.setReuseAddress(true);
new ListenThread(ss).start();
}
}
if (config.isSet("ssl_port"))
{
char ks_pass[] = config.get("keystore_store_password").toCharArray();
char key_pass[] = config.get("keystore_key_password").toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(config.get("keystore_path")), ks_pass);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, key_pass);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory ssf = sc.getServerSocketFactory();
List<String> ports = config.getList("ssl_port");
for(String s : ports)
{
int port = Integer.parseInt(s);
if (ssl_port < 0) ssl_port = port;
SSLServerSocket ss = (SSLServerSocket)ssf.createServerSocket(port, 256);
ss.setWantClientAuth(false);
ss.setNeedClientAuth(false);
ss.setReuseAddress(true);
new ListenThread(ss).start();
}
}
}
public Config getConfig()
{
return config;
}
public String getInstanceId()
{
return instance_id;
}
public void setInstanceId(String instance_id)
{
this.instance_id = instance_id;
}
public void setEventLog(EventLog log)
{
this.event_log = log;
}
public EventLog getEventLog()
{
return event_log;
}
public int getTcpPort() {return tcp_port;}
public int getSslPort() {return ssl_port;}
public int getConnectionCount()
{
synchronized(conn_map)
{
return conn_map.size();
}
}
public boolean applyGlobalRateLimit(double bytes)
{
if (global_rate_limit == null) return false;
return global_rate_limit.waitForRate(bytes);
}
public NetworkParameters getNetworkParameters(){return network_params;}
public void setNetworkParameters(NetworkParameters network_params)
{
this.network_params = network_params;
}
public class ListenThread extends Thread
{
private ServerSocket ss;
public ListenThread(ServerSocket ss)
{
this.ss = ss;
setName("Listen:"+ss);
}
public void run()
{
System.out.println("Listening on port: " + ss);
jelectrum.getEventLog().log("Listening on port: " + ss);
while(ss.isBound())
{
try
{
Socket sock = ss.accept();
sock.setTcpNoDelay(true);
String id = UUID.randomUUID().toString();
StratumConnection conn = new StratumConnection(jelectrum, server, sock, id);
synchronized(conn_map)
{
conn_map.put(id, conn);
}
}
catch(Throwable t)
{
t.printStackTrace();
}
}
}
}
public class TimeoutThread extends Thread
{
public TimeoutThread()
{
setName("TimeoutThread");
setDaemon(true);
}
public void run()
{
while(true)
{
LinkedList<Map.Entry<String, StratumConnection> > lst= new LinkedList<Map.Entry<String, StratumConnection> >();
synchronized(conn_map)
{
lst.addAll(conn_map.entrySet());
}
for(Map.Entry<String, StratumConnection> me : lst)
{
if (!me.getValue().isOpen())
{
synchronized(conn_map)
{
conn_map.remove(me.getKey());
}
}
if (me.getValue().getLastNetworkAction() + max_idle_time < System.nanoTime())
{
jelectrum.getEventLog().log(me.getKey() + " - Closing connection due to inactivity");
me.getValue().close();
synchronized(conn_map)
{
conn_map.remove(me.getKey());
}
}
}
try{Thread.sleep(15000);}catch(Throwable t){}
}
}
}
}