[Webfunds-commits] java/webfunds/sox/server DirSSDStore.java SSD.java SSDException.java SimpleServer.java SmartServer.java
Ian Grigg
iang@cypherpunks.ai
Fri, 6 Apr 2001 18:50:00 -0400 (AST)
iang 01/04/06 18:50:00
Added: webfunds/sox/server DirSSDStore.java SSD.java
SSDException.java SimpleServer.java
SmartServer.java
Log:
New package for SSDs, was the SOXServer code in webfunds.ricardian;
Now Compiled, but not Tested.
Revision Changes Path
1.1 java/webfunds/sox/server/DirSSDStore.java
Index: DirSSDStore.java
===================================================================
/*
* $Id: DirSSDStore.java,v 1.1 2001/04/06 22:49:59 iang Exp $
*
* Copyright (c) Systemics Ltd 1995-1999 on behalf of
* the WebFunds Development Team. All Rights Reserved.
*/
package webfunds.sox.server;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Enumeration;
import java.net.URL;
import java.net.MalformedURLException;
import webfunds.util.Panic;
import webfunds.utils.Debug;
import webfunds.comms.CommsManager;
import webfunds.comms.BasicCommsManager;
import webfunds.sox.Issuer;
import webfunds.sox.SOXIssuerException;
import webfunds.sox.SOXServerException;
import webfunds.sox.SOXLaterException;
import webfunds.sox.ItemId;
import webfunds.ricardian.*; // shouldn't be here!
/**
* A Store for SSDs (SOX Server Descriptors).
* Provides access to SSDs, and caches the latest file on each one.
*
* This was webfunds.ricardian.DirSOXStore
*/
public class DirSSDStore
extends Debug // implements SSDStore
{
/**
* All the SSDs, indexed by unique name.
* String ==> SSDs
*/
protected Hashtable ssds;
/**
* All the SSDs, indexed by urls, pointing to unique name.
* String ==> String
*/
protected Hashtable ssdNames;
protected File dir = new File(".");
public File getDirectory() { return dir ; }
final static public String suffix = ".ssd",
oldSuffix = ".sox";
protected String postfix = " SSDs: ";
/**
* Only way we are allowed onto the net is via this CommsManager.
*/
protected CommsManager comms = null;
protected void setCommsManager(CommsManager comms) { this.comms = comms; }
//////// Construction ////////////////////////////////////
/**
* Create a memory store without any backing store.
*/
public DirSSDStore() { initHashtables(); }
/**
* Creates the store in the given directory.
* @param dir a directory with server files
public DirSSDStore(File dir) { initHashtables(); setDir(dir); }
*/
/**
* Read in a directory of SSD files and provide access to them.
* @param dir a directory with server files
* @param bug place to put debugging during construction
*/
public DirSSDStore(CommsManager comms, File dir, PrintWriter bug)
{
debug(bug, " dS: ");
this.comms = comms;
initHashtables();
setDir(dir);
}
protected void initHashtables()
{
ssdNames = new Hashtable();
ssds = new Hashtable();
}
/**
* Read in a directory of SSD files.
* Make the directory if needed.
* It is not an error if files cannot be rewritten.
*/
protected void setDir(File dir)
{
if (!dir.exists())
dir.mkdirs();
if (!dir.isDirectory())
throw new IllegalArgumentException("Not a dir: "+dir.getPath());
if (!dir.canRead())
throw new Panic("Cannot read: " + dir.getPath());
//
// Read in all the files in the directory.
//
this.dir = dir;
String[] files = dir.list();
for (int i = 0; i < files.length; i++)
{
String name = files[i];
File f = new File(dir, files[i]);
if (name.indexOf(":") > 0) // has a colon - Mac change 1.4.1
{
logmsg("removing old server file: " + name);
f.delete();
}
else if (name.endsWith(suffix)) // current server files
{
SSD ssdfile;
try {
ssdfile = SSD.getInstance(f, getDebug());
} catch (SSDException ex) {
ex.printStackTrace(System.err);
logmsg("removing " + name);
f.delete();
continue ;
}
addSSD(ssdfile);
// ssdfile.saveAsFile(dir);
}
else if (name.endsWith(oldSuffix)) // old server files - 1.7
{
logmsg("removing old server file: " + name);
f.delete();
}
else if (name.endsWith(".srv")) // old server files - 1.3
{
logmsg("removing old server file: " + name);
f.delete();
}
else // ignore - might belong to another store
logmsg("ignoring: " + name);
}
}
/**
* Index the SSD into the temporary hashtables.
* This is the running cache, not the persistant store.
*/
protected void addSSD(SSD ssd)
{
//
// Two lists:
// ssds exact name - taken from file
// allNames any name, URLs of file location
//
// The ssds list can be overriden as it is synonymous with
// the storage method.
//
String name = ssd.getName();
logmsg("adding to local: " + name);
ssds.put(name, ssd);
//
// The SSD has a list of names by which it might
// be known. These are the URLs where the file can be got from.
//
String[] all = ssd.getAllNames();
for (int i = 0; i < all.length; i++)
addThisSSDName(all[i], name);
}
/**
* Add a name (url) for an SSD.
*/
protected void addThisSSDName(String name, String unique)
{
//
// Each URL points to one and one only SSD
// (unlike contracts).
//
String already = (String) ssdNames.get(name);
if (already != null)
{
if (!already.equals(unique))
logmsg("Clash: " + name + " points to " + already +
" , trying to add " + unique + " (ignored)");
return ;
}
ssdNames.put(name, unique);
logmsg(" name: " + name);
}
//////// Main Caller Access ////////////////////////////////////
static final public String no_urls = "no urls supplied (bad contract?)";
/**
* Refresh an SSD from the URLs off the net.
* For some reason - bounced connections - the caller has decided
* that the Issuer is bad. One reason might be that the Issuer
* has moved. Refresh the SSD and see if it has changed.
*
* Experimental!
*
* @return true if it changed (caller should retry activity)
*/
public boolean refreshSSD(SSD old)
throws SSDException, SOXLaterException
{
String[] urls = old.getArray("server_file_url");
if (urls.length == 0)
throw new SSDException(no_urls);
//
// Collect the existing cache one and try for the new one on the net.
//
// SSD old = getSSDFromCache(urls);
SSD ssd = getSSDFromNet(urls);
int oldV = old.getVersion();
int newV = ssd.getVersion();
if (newV == 0)
throw new SSDException("cannot refresh, new version is 0");
if (oldV >= newV)
return false ;
String name = old.getName();
logmsg("updating " + name + " from " + oldV + " to " + newV);
//
// A quick name check.
//
String newName = ssd.getName();
if (!name.equals(newName))
throw new SSDException("refreshed SSD changed name: " +
name + " ==> " + newName);
//
// We've found a new version.
// Need to store it over the top of the old.
//
addAndSave(ssd);
return true ;
}
/**
* Request an SSD by its name. Only call this if you know the name
* is available and in the store, elsewise, call get(String, String[]).
*
* @return the SSD that answers to name,
* else null if it is unknown, or no name supplied
*/
public SSD get(String name)
{
if (name == null || name.length() == 0)
return null;
return (SSD) ssds.get(name);
}
/**
* Request an SSD by its name or URLs.
* This is the backdoor way to get new SSDs into the store,
* for when the SSD is expressed differently.
*
* @return the SSD that uniquely matches hash, never null
* @except SSDException there is no matching SSD available
* @except SOXLaterException try again later
*/
public SSD get(String name, String[] urls)
throws SSDException, SOXLaterException
{
SSD ssd;
if ((name != null) && (name.length() > 0))
{
ssd = (SSD) ssds.get(name);
if (ssd != null)
return ssd;
}
if (urls == null || urls.length == 0)
throw new SSDException(no_urls);
ssd = getSSDFromCache(urls);
if (ssd != null)
return ssd ;
logmsg(urls[0] + " not in cache, going to net");
//
// if we got this far, this must be a new contract.
//
ssd = getSSDFromNet(urls);
addAndSave(ssd);
return ssd ;
}
//////// Internal Utility Code ////////////////////////////////////
/**
* Request an SSD for URLs but get it from the cache.
* @return the SSD that matches the urls
*/
protected SSD getSSDFromCache(String[] urls)
throws SSDException
{
SSD ssd;
for (int i = 0; i < urls.length; i++)
{
logmsg("url (" + i + ") " + urls[i]);
String name = (String) ssdNames.get(urls[i]);
if (name == null)
continue ;
// found one (unusual to find one, not the other, means a change)
ssd = (SSD) ssds.get(name);
if (ssd == null)
{ logmsg("huh? no SSD for name " + name); continue ; }
// logmsg("found: " + ssd.getName());
return ssd ;
}
return null ;
}
/**
* Browse an SSD from some URLs.
* @return the first SSD that we find
*/
protected SSD getSSDFromNet(String[] urls)
throws SSDException, SOXLaterException
{
// only used locally I think
if (comms == null)
throw new SSDException("no CommsManager?");
SSD ssd;
//
// Extract the urls - again - and go out on the net.
// Might want to think about multithreading this stage.
//
for (int i = 0; i < urls.length; i++)
{
logmsg("net <" + i + "> " + urls[i]);
URL url;
try {
url = new URL(urls[i]);
} catch (MalformedURLException ex) {
logmsg(ex + ": " + urls[i]);
continue ;
}
if (url == null)
continue ;
ssd = SSD.getInstance(url, comms, getDebug());
if (ssd != null)
return ssd ;
// might want to keep searching here, if we are refreshing
}
throw new SSDException("no SSD file from " + urls[0]);
}
/**
* If you wish to change the way that things are stored,
* override these methods.
* // this method not used - deprecate
*/
protected SSD getFromStore(String name)
throws SSDException
{
//return ssds.get(name);
File f = new File(dir, name);
SSD ssdfile = SSD.getInstance(f, getDebug());
return ssdfile ;
}
protected void addAndSave(SSD ssd)
throws SSDException
{
addSSD(ssd);
savePersistant(ssd);
}
/**
* Override this method to change the nature of the persistant storage.
*/
protected void savePersistant(SSD ssd)
throws SSDException
{
try {
ssd.saveAsFile(dir);
} catch (IOException ex) {
ex.printStackTrace(err());
throw new SSDException(ex.getMessage());
}
}
protected void cleanStore()
throws IOException
{
initHashtables();
String[] files = dir.list();
for (int i = 0; i < files.length; i++)
{
String name = files[i];
if (name.endsWith(suffix) || name.endsWith(oldSuffix))
{ // current server files
File f = new File(dir, name);
f.delete();
}
}
}
//////// User-level Utility Code ////////////////////////////////////
/**
* @return a list of the names of all SSDs
*/
public String[] getAllNames()
{
String[] names = new String[ssds.size()];
Enumeration e = ssds.keys();
for (int i = 0; e.hasMoreElements(); i++)
{
names[i] = (String)e.nextElement();
}
return names;
}
/**
* @return a list of all SSDs
*/
public SSD[] getAllSSDs()
{
SSD[] sss = new SSD[ssds.size()];
Enumeration e = ssds.elements();
for (int i = 0; e.hasMoreElements(); i++)
{
sss[i] = (SSD)e.nextElement();
}
return sss;
}
//////// Self-test ////////////////////////////////////
public String toString()
{
String retval = "DirSSDStore: " +
"\n\t\tSSDs = " + ssds.size() +
"\n\t\tnames = " + ssdNames.size() +
"\n\t\tDirectory = " + dir;
return retval;
}
static final String usage =
"Test Usage: DirSSDStore directory";
public static void main(String[] arg)
throws ContractException, ContractDirectoryException,
SSDException, SOXLaterException
{
if (arg.length == 0)
{
System.err.println(usage);
System.exit(1);
}
File d = new File(arg[0]);
PrintWriter bug = new PrintWriter(System.err, true);
DirContractStore cons = new DirContractStore(d, bug);
CommsManager cm = new BasicCommsManager();
DirSSDStore store = new DirSSDStore(cm, d, bug);
System.err.println("Started: " + store);
//
// For all contracts, ask for the SSD file (and the urls).
//
Contract[] contracts = cons.getAllContracts();
int found = 0;
for (int i = 0; i < contracts.length; i++)
{
System.err.println(" " + i + ": " + contracts[i]);
Contract con = contracts[i];
String[] locs = SSDFields.getLocation(con);
String name = SSDFields.getName(con);
SSD ssd = store.get(name, locs);
found++;
System.err.println(" " + ssd);
String[] urls = ssd.getArray("sox", "server_url");
for (int k = 0; k < urls.length; k++)
{
System.err.println(" " + urls[k]);
}
}
}
}
1.1 java/webfunds/sox/server/SSD.java
Index: SSD.java
===================================================================
/*
* $Id: SSD.java,v 1.1 2001/04/06 22:49:59 iang Exp $
*
* Copyright (c) Systemics Ltd 1995-1999 on behalf of
* the WebFunds Development Team. All Rights Reserved.
*/
package webfunds.sox.server;
import java.io.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.security.cert.Certificate;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Vector;
import webfunds.utils.Diagnostics;
import webfunds.util.Panic;
import webfunds.util.IniFileReader;
import webfunds.util.FormattedFileException;
import webfunds.util.Support;
import webfunds.comms.RawHttp;
import webfunds.comms.CommsManager;
import webfunds.comms.BasicCommsManager;
import webfunds.comms.SingleRequestor;
import webfunds.comms.RawException;
import webfunds.comms.RawReplyException;
import webfunds.comms.RawURLException;
import webfunds.comms.RawConnectException;
import webfunds.sox.Encodable;
import webfunds.sox.Issuer;
import webfunds.sox.SmartIssuer;
import webfunds.sox.SOXIssuerException;
import webfunds.sox.SOXLaterException;
import webfunds.sox.SOXPacketException;
/**
* SOX Server Descriptor.
*
* Every SOX server has file(s) on the web somewhere which
* indicates various static paramaters, known as the
* SOX Server Descriptor, or SSD. This class manages the
* (duplicated) files into one SSD.
*
* Somewhere there has to be a bridge between contract and server:
*
* Contract ==> SOX Server Descriptor ==> SOX Server
*
* This class is it.
*
* NOTE this was once webfunds.ricardian.SOXServer.
*/
public class SSD
extends Encodable implements Diagnostics
{
/**
* The version of the encoded object:
* 1 - first Encodable version.
*/
public static final int VERSION = 1;
// implements Diagnostics - EXPERIMENTAL !!!
// mixing these classes is a bit non-OO. But, it seems many need both.
// another option would be to extend from Debug.
protected PrintWriter bug = null;
protected String fix = " ss- ";
public void logmsg(String s) { if (bug != null) bug.println(fix + s); }
public PrintWriter getDebug() { return bug ; }
// hmm, no autoflush.
public PrintWriter err()
{ return (bug == null) ? new PrintWriter(System.err, true) : bug ; }
protected byte[] fileData;
protected IniFileReader serverFile;
// protected Issuer issuer;
protected String originalName;
/////// Constructors /////////////////////////////////////////////
public SSD(byte[] fileData, String name, PrintWriter bug)
throws SSDException
{
if (bug != null)
this.bug = new PrintWriter(bug, true);
this.fileData = fileData;
this.originalName = name;
init();
}
public SSD(byte[] data)
throws SOXPacketException
{
decode(data);
init2();
}
public SSD(InputStream is)
throws SOXPacketException
{
try {
decode(is);
} catch (IOException ex) {
ex.printStackTrace(System.err);
throw new SOXPacketException("IOEx: " + ex);
}
init2();
}
protected void init2()
throws SOXPacketException
{
try {
serverFile = new IniFileReader(fileData);
} catch (FormattedFileException ex) {
ex.printStackTrace(System.err);
throw new SOXPacketException("ContractEx: " + ex);
};
}
protected void init()
throws SSDException
{
try {
serverFile = new IniFileReader(fileData);
} catch (FormattedFileException ex) {
ex.printStackTrace(System.err);
throw new SSDException(ex.getMessage());
};
refreshServerData();
}
////// Recover, Save /////////////////////////////////////
public void encode(OutputStream os)
throws IOException
{
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(VERSION); // match with encode()
writeString(dos, originalName);
writeByteArray(dos, fileData);
}
public void decode(InputStream is)
throws IOException, SOXPacketException
{
DataInputStream dis = new DataInputStream(is);
int v = dis.readInt();
if (v != VERSION)
throw new SOXPacketException("wrong version: "+v+" != "+VERSION);
originalName = readString(dis);
fileData = readByteArray(dis);
}
////// Initial Instances /////////////////////////////////////
/**
* Create a SSD given the file.
*
* @param file name for SSD file
*/
public static SSD getInstance(File serverfile, PrintWriter bug)
throws SSDException
{
// Read ServerFile
if (!serverfile.exists() || !serverfile.canRead())
throw new SSDException("cannot read: " + serverfile);
byte[] serverData;
try {
FileInputStream fis = new FileInputStream(serverfile);
serverData = new byte[fis.available() ];
fis.read(serverData);
fis.close();
} catch (IOException ex) {
ex.printStackTrace(bug);
throw new SSDException(ex.getMessage());
}
return new SSD(serverData, serverfile+"", bug);
}
/**
* Create a SSD object given an URL
*
* @param url URL to read the object from
* @param comms where to get a comms requestor from
*/
public static SSD getInstance(URL url, CommsManager comms,
PrintWriter bug)
throws SSDException, SOXLaterException
{
String proto = url.getProtocol();
if (proto.equals("file"))
return getInstance(new File(url.getFile()), bug);
if (!proto.equals("http"))
throw new SSDException("unknown proto " + proto +
" in URL " + url);
// RawHttp http = new RawHttp(url, bug);
SingleRequestor sr = comms.getSingleRequestor(url);
byte[] getRequest = RawHttp.getGetData(url);
byte[] buf;
try {
// do a Get on the URL
buf = sr.get(getRequest);
} catch (RawConnectException ex) {
throw new SOXLaterException(ex.getErrno(), "no net?\n" + ex);
} catch (RawURLException ex) {
ex.printStackTrace();
throw new SSDException("bad url?\n" + ex);
} catch (RawException ex) {
ex.printStackTrace();
throw new Panic("Bad RawEx: " + ex);
}
byte[] data;
try {
data = RawHttp.getReply(buf);
} catch (RawReplyException ex) {
throw new SSDException(ex.getMessage() +
"\n\nThe server file <<" + url + ">> is not found.\n" +
"Check this URL (as found in the contract local file).");
}
return new SSD(data, url+"", bug) ;
}
/**
* Take the data and make a SSD object.
* There is no constructor for this.
*
* @return a SSD made from the file data
*/
public static SSD getInstance(byte[] server)
throws SSDException
{
return new SSD(server, null, null);
}
/////// Issuers /////////////////////////////////////////////
SmartIssuer smart = null;
/**
* Return an Issuer.
* Ready to go? Tested? Probably.
* XXX: deprecate, no CommsManager
Issuer getIssuer(Contract con)
throws SOXIssuerException, SOXLaterException
{
logmsg("DEPRECATED getIssuer ( " + con + " ) == " + smart);
if (smart == null)
smart = initSmartIssuer(con, new BasicCommsManager());
logmsg("smart.getReady()");
smart.getReady();
logmsg("Ready for Action!");
// to go any further - re-read the SSD file - we need
// to know whether we are online.
return (Issuer)smart;
}
*/
/**
* Return an Issuer.
* Ready to go? Tested? Probably.
Issuer getIssuer(Contract con, CommsManager comms)
throws SOXIssuerException, SOXLaterException
{
logmsg("getIssuer ( " + con + " ) == " + smart);
if (smart == null)
smart = initSmartIssuer(con, comms);
logmsg("smart.getReady()");
smart.getReady();
logmsg("Ready for Action!");
// to go any further - re-read the SSD file - we need
// to know whether we are online.
return (Issuer)smart;
}
private SmartIssuer initSmartIssuer(Contract con, CommsManager comms)
throws SOXIssuerException, SOXLaterException
{
//
// Extract the cert.
//
Certificate cert;
try {
cert = con.getServerCert();
} catch (ContractException ex) {
throw new SOXIssuerException("contract cert is bad: " + con +
"\n" + ex);
}
return initSmartIssuer(cert, comms);
}
private SmartIssuer initSmartIssuer(Certificate cert, CommsManager comms)
throws SOXIssuerException, SOXLaterException
{
//
// Extract the URLs for the server(s).
//
String[] urls;
urls = getArray("server_url");
logmsg("smart with " + urls.length + " urls");
SmartIssuer smart = new SmartIssuer(urls, cert, comms, getDebug());
//
// Pass the URLs of other non-SOX servers to the SmartIssuer
// so that it can check the status of the net.
//
urls = getArray("nearby_urls");
smart.setOtherURLs(urls);
return smart;
}
*/
/////// Contents /////////////////////////////////////////////
static final String http = "http://",
www = "www.";
/**
* Get the name of the server. This should be useful for a filename.
* @return unique name of the server
*/
public String getName()
{
/*
* There should be a name in the file.
*/
String s = getField("server_name");
if (s != null && !"".equals(s))
return s;
/*
* No name in SOX server file, take the file URL and build some
* sort of canonical name out of that. Just so it works.
* But, there might be many of these. Should take the first.
* There are some complications here, such as changed orders,
* and shared URLs. This is not a recommended method.
*/
String[] ss = getArray("server_file_url");
s = ss[0];
if (s.startsWith(http))
s = s.substring(http.length(), s.length());
if (s.startsWith(www))
s = s.substring(www.length(), s.length());
// this should be domain:port type, so drop / at end
if (s.endsWith("/"))
s = s.substring(0, s.length() - 1);
s = s.replace('.', '_'); // doesn't work on Doze
s = s.replace(':', '_'); // doesn't work on Mac
return s;
}
/**
* Get all the names of the server file.
* These will mostly be the URLs where the file can be found.
* @return list of names
*/
public String[] getAllNames()
{
Hashtable hush = new Hashtable();
String name = getName();
hush.put(name, name);
if (originalName != null) // this may be some local store
hush.put(originalName, originalName);
String[] all = getArray("server_file_url");
for (int i = 0; i < all.length; i++)
hush.put(all[i], all[i]);
String[] names = new String[hush.size()];
Enumeration e = hush.keys();
for (int i = 0; i < hush.size(); i++)
names[i] = (String)e.nextElement();
return names ;
}
/**
* Convert a list of strings to URLs.
* Ignores malformed ones.
* Always returns an array.
*/
public static URL[] convertToURLs(String[] stringURLs, PrintWriter bug)
{
Vector v = new Vector();
for (int i = 0; i < stringURLs.length; i++)
{
URL url;
try {
url = new URL(stringURLs[i]);
} catch(MalformedURLException ex) {
bug.println(i + ": " + ex + ":\n<<" + stringURLs[i] + ">>");
continue ;
}
v.addElement(url);
bug.println("ok " + stringURLs[i]);
}
URL[] urls = new URL[v.size()];
v.copyInto(urls);
return urls;
}
public URL[] getServerLocation()
{
//
// looks like:
// server_url += http://hayek.econ:8888/
// server_url += http://mises.econ:8888/
//
String[] strings = getArray("server_url" );
return convertToURLs(strings, bug);
}
public URL[] getNearbyURLs()
{
//
// looks like:
// nearby_urls += http://www.site.econ/
// nearby_urls += http://www.help.econ/
//
String[] strings = getArray("nearby_urls" );
return convertToURLs(strings, bug);
}
/**
* Get the version number of the server file.
* Useful for seeing which is the most recent copy of the file,
* a la DNS.
* @return current version number of the server, 0 if failed
*/
public int getVersion()
{
/*
* There should be a name in the file.
*/
String s = getField("server_version");
if (s == null || "".equals(s))
return 0 ;
Integer i;
try {
i = new Integer(s);
} catch (NumberFormatException ex) {
logmsg("bad version in " + this + ": " + s);
return 0 ;
}
int i2 = i.intValue();
if (i2 < 0)
{
logmsg("bad version in " + this + ": " + s);
return 0;
}
return i2;
}
/////// Access to Fields /////////////////////////////////////////////
protected void setField(String section, String item, String value)
{
System.err.println("trying to set field" +
"[" + section + "] " + item + " = " + value);
serverFile.changeSectionItemValue(section, item, value);
}
protected String getField(String item) { return getField("sox", item) ; }
protected String[] getArray(String item) { return getArray("sox", item) ; }
protected String getField(String section, String item)
{
String s = "";
// blows up if a vector?
s = serverFile.getSectionItemValue(section, item);
if (s == null || "".equals(s) )
{
item = section + "_" + item;
s = serverFile.getSectionItemValue(section, item);
}
return s;
}
protected String[] getArray(String section, String item)
{
String[] ss = new String[0];
ss = serverFile.getSectionItemArray(section, item);
if (ss == null || ss.length == 0)
{
item = section + "_" + item;
ss = serverFile.getSectionItemArray(section, item);
}
return ss;
}
/////// Save & Restore /////////////////////////////////////////////
public void saveAsFile(File dir)
throws IOException
{
saveAsFile(dir, getName());
}
/**
* This file doesn't need to differentiate itself from other
* files, so it saves itself as name. Caller can add a suffix.
*/
public void saveAsFile(File dir, String name)
throws IOException
{
File servfile = new File(dir, name);
FileOutputStream fos;
fos = new FileOutputStream(servfile);
fos.write(fileData);
fos.close();
}
public URL[] getCachedSOXFileLocation()
{
String[] strings;
strings = getArray("server_file_url" );
URL[] urls = new URL[strings.length];
for (int i = 0; i < urls.length; i++)
{
try {
urls[i] = new URL(strings[i]);
} catch (MalformedURLException mfex) {
logmsg(mfex + " (" + i + ") " + strings[i]);
urls[i] = null;
}
}
return urls;
}
public URL[] getSOXFileLocation()
{
URL[] urls = getCachedSOXFileLocation();
if (urls.length != 0)
return urls ;
refreshServerData();
return getCachedSOXFileLocation();
}
/**
* With a bunch of URLs, go out and get a SOX Server file from the net.
* Should be called using the contract data by a Store.
* @except SSDException if the format is duff
*
* XXX: deprecate!
private static SSD getNewSSDFile(URL[] urls)
throws SSDException
{
for (int i = 0; i < urls.length; i++)
{
byte data[];
try {
data = Support.getURL(urls[i]);
} catch (IOException ex) {
continue ;
}
if (data == null)
continue ;
SSD ssd;
try {
ssd = SSD.getInstance(data);
} catch (SSDException ex) {
continue ;
}
return ssd ;
}
return null ;
}
*/
/**
* Go out on the net and see if it is changed?
* Not implemented yet, see the DirSOXStore.
*/
protected void refreshServerData()
{
}
/////// Self-Test /////////////////////////////////////////////
// public int hashCode()
// {
// return id.hashCode();
// }
public boolean equals(Object obj)
{
if(!(obj instanceof SSD))
return false;
SSD other = (SSD)obj;
// check if the data components are equivalent
boolean ok = Support.equals(other.fileData, this.fileData) ;
return ok ;
}
public String toString()
{
return getName();
}
public static void main(String[] arg)
throws IOException
{
if (arg.length == 0)
{
System.err.println("Test Usage: SSD {filename | URL}");
System.exit(1);
}
String name = arg[0];
SSD ssd = null;
PrintWriter bug = new PrintWriter(System.err);
CommsManager comms = new BasicCommsManager();
try
{
if (name.startsWith("http://"))
{
URL url = new URL(name);
ssd = SSD.getInstance(url, comms, bug);
}
else
{
File fil = new File(name);
ssd = SSD.getInstance(fil, bug);
}
}
catch (Exception ex)
{
ex.printStackTrace(bug);
System.exit(1);
}
// ssd.debug(new PrintWriter(System.err, true));
System.err.println("SSD: " + ssd.getName());
String[] names = ssd.getAllNames();
for (int i = 0; i < names.length; i++)
System.err.println("Name " + i + ": <" + names[i] + ">");
File d = new File("/tmp");
String ff = "flooples";
ssd.saveAsFile(d, ff);
File f = new File(d, ff);
SSD ssd2 = null;
try {
ssd2 = SSD.getInstance(f, bug);
} catch (Exception ex) {
ex.printStackTrace(bug);
System.exit(1);
}
if (ssd2.equals(ssd))
System.err.println("Equal after saving");
else
throw new RuntimeException("!equal: " + d + "/" + f);
System.exit(0);
}
}
1.1 java/webfunds/sox/server/SSDException.java
Index: SSDException.java
===================================================================
/*
* $Id: SSDException.java,v 1.1 2001/04/06 22:49:59 iang Exp $
*
* Copyright (c) 2001 Systemics Inc on behalf of
* the WebFunds Development Team. All Rights Reserved.
*/
package webfunds.sox.server;
import webfunds.util.ExceptionModel;
public class SSDException extends ExceptionModel
{
public SSDException(int number, String msg) { super(number, msg); }
public SSDException(String msg) { super(UNKNOWN, msg); }
}
1.1 java/webfunds/sox/server/SimpleServer.java
Index: SimpleServer.java
===================================================================
/*
* $Id: SimpleServer.java,v 1.1 2001/04/06 22:49:59 iang Exp $
*
* Copyright (c) Systemics Ltd 1995-1999 on behalf of
* the WebFunds Development Team. All Rights Reserved.
*/
package webfunds.sox.server;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.util.Date;
import cryptix.openpgp.*;
import cryptix.openpgp.util.PGPArmoury;
import webfunds.utils.Debug;
import webfunds.comms.*;
import webfunds.sox.Server;
import webfunds.sox.Crypto;
import webfunds.sox.SOXException;
import webfunds.sox.SOXServerException;
import webfunds.sox.SOXIssuerException;
import webfunds.sox.SOXLaterException;
import webfunds.sox.SOXPacketException;
import webfunds.sox.SOXKeyException;
import webfunds.sox.BasicAgent;
import webfunds.sox.Request;
import webfunds.sox.EncryptedRequest;
import webfunds.sox.EncryptedReply;
import webfunds.sox.TimeSyncReply;
import webfunds.sox.TimeSyncRequest;
import webfunds.sox.AccountId;
/**
* This class is a "Server Agent" that passes basic requests to the Server.
* It should be passive until requested.
*
* @version 1.3
*/
public final class SimpleServer
extends Debug
implements Server
{
protected String logfix = " i-";
/**
* The name of the server.
* Not used here, just carried.
*/
protected String name;
/**
* The agent communicating to the server (at the "basic" level)
*/
protected BasicAgent basicAgent;
/**
* The PKI is evolving...
*
* The [operator] certificate is the high level key used by
* the operator to sign and authenticate different servers
* in his administrative domain. It is infrequently used,
* the [server] keys are delegated with the task of day-to-day
* authentication of comms keys.
*
* The [server] key is the key that each physical server has
* in order to sign ephemeral (temporary, session) comms keys.
* The client can expected the [server] key to be around for
* many days, even months. It is signed by the [operator] key.
*
* Each session will request a comms key, which is made by the
* lower-level key exchange protocol. This comms key will be
* authenticated by being signed by the [server] key.
* The client can expect the comms key to be valid for at least
* one request, and hopefully more, up to many hours worth.
*/
protected Certificate signer = null;
/** The [server] certificate for this physical server. */
protected Certificate serverCert = null;
/** The communications certificate (key) for this current session. */
protected PublicKey commsKey = null;
protected int reqNo = 0;
///////// Instantiation //////////////////////////////////////////
/**
* Create a new SimpleServer object
* The SimpleServer object will normally be cached by the caller,
* but is not usefully stored on disk.
* This call is passive, call getReady() to cause action.
*
* @param name our name for the server
* @param signer the certificate which signs this servers certificate
* @param agent the comms agent that sends requests at the transport layer
*/
public SimpleServer(String name, Certificate signer, CommsAgent agent,
PrintWriter bug)
{
debug(bug, " i ");
this.name = name;
this.signer = signer;
if (signer == null)
throw new IllegalArgumentException("signer <null>");
if (agent == null)
throw new IllegalArgumentException("agent <null>");
basicAgent = new BasicAgent(agent);
logmsg("SimpleServer(" + name + ", signer, " + agent + ", bug)");
}
/**
* Do the things necessary for being ready for a request.
*/
public void getReady()
throws SOXIssuerException, SOXLaterException
{
try {
getReadyToRequest();
} catch (SOXServerException ex) {
throw new SOXIssuerException("" + ex);
}
}
/**
* Do the things necessary for being ready for a request.
*/
public void getReadyToRequest()
throws SOXServerException, SOXLaterException
{
// checkSync(); now on demand, not pre-emptive
if (isDead())
timesync(); // will set Alive if it works
}
/**
* The name of this server
* @return the name of the server
*/
public String getName()
{
return name;
}
///////// Keys and Certs //////////////////////////////////////////
private void refetchCommsKey()
throws SOXServerException, SOXLaterException
{
commsKey = null;
fetchCommsKey();
}
/**
* Fetch the current communications certificate for this server.
* The certificate signatures are verified before assigning.
*
* None of the PKI is done properly yet.
*
* This won't do anything if commsKey is already set.
*/
private void fetchCommsKey()
throws SOXServerException, SOXLaterException
{
if (commsKey != null)
return;
logmsg("Fetching the SOX Server comms certificate");
Certificate commsCert;
try {
commsCert = basicAgent.getCommsKey();
} catch (SOXLaterException ex) {
setDead(ex.getMessage()); // URL is wrong or server is down
throw ex ;
} catch (SOXPacketException ex) { // what was this for?
setDead(ex.getMessage());
throw new SOXServerException(ex.getNumber(),
"SOXPE 1: " + ex.getMessage());
} catch (SOXServerException ex) {
setDead(ex.getMessage()); // BA thinks info is wrong
throw ex ;
}
/*
* If it's X.509, ignore the signature business because the existing
* server is broken and we don't want to fix it.
*/
if( !commsCert.getType().equals("X.509") )
{
fetchServerCert();
PublicKey signerKey = Crypto.getPublicKeyFromCert(signer);
logmsg("Verifying ServerCert is signed by Server CA certificate");
if (!Crypto.verifyCertificate(serverCert, signerKey)) {
byte[] b = signerKey.getEncoded();
PGPArmoury ok = new PGPArmoury(b, PGPArmoury.TYPE_PUBLIC_KEY);
b = Crypto.getPublicKeyFromCert(serverCert).getEncoded();
PGPArmoury sk = new PGPArmoury(b, PGPArmoury.TYPE_PUBLIC_KEY);
logmsg(
"serverCert (first) not signed by operator Cert (2nd)\n\n"+
sk + "\n\n\n" + ok + "\n\n");
throw new SOXServerException(SOXException.SERVER_CERT,
"serverCert not signed by operator Cert");
}
PublicKey serverKey = Crypto.getPublicKeyFromCert(serverCert);
logmsg("Verifying CommsCert is signed by serverCert");
if (!Crypto.verifyCertificate(commsCert, serverKey))
{
String e = "commsCert not signed by serverCert";
serverCert = null; // must have rolled over?
// fetchServerCert(); // do a retry on the ServerCert?
setDead(e); // no, do a high level retry
throw new SOXServerException(SOXException.COMMS_CERT, e);
}
logmsg("Using comms key (valid signature by the server)");
}
// Careful not to set this before validating the signature
commsKey = Crypto.getPublicKeyFromCert(commsCert);
logmsg("Finished fetchCommsKey at " + System.currentTimeMillis() );
}
/**
* Fetch the current [server] certificate for this server.
*
* There is one [server] cert for each physical server (i.e.,
* the server defined by this object.
*
* The one [operator] key is used across the entire virtual server
* (of many entry points, as described by the URLs in the
* SOX Server File).
*
* The certificate signatures are verified before assigning.
*
* This won't do anything if [server] is already set. If a
* signature failure has occurred then set the key to null first.
*/
private void fetchServerCert()
throws SOXServerException, SOXLaterException
{
if (serverCert != null)
return;
logmsg("Requesting the SOX Server [server] certificate");
Certificate cert = null;
try {
cert = basicAgent.getServerKey();
logmsg("Got a cert!" + serverCert);
} catch (SOXLaterException ex) {
setDead(ex.getMessage()); // URL is wrong or server is down
throw ex ;
} catch (SOXPacketException ex) { // what was this for?
setDead(ex.getMessage());
throw new SOXServerException(ex.getNumber(),
"SOXPE 2: " + ex.getMessage());
} catch (SOXServerException ex) {
setDead(ex.getMessage()); // BA thinks info is wrong
throw ex ;
}
PublicKey signerKey = Crypto.getPublicKeyFromCert(signer);
logmsg("Verifying ServerCert is signed by Server CA certificate");
if (!Crypto.verifyCertificate(cert, signerKey))
{
String e = "serverCert not signed by operator Cert";
byte[] b = signerKey.getEncoded();
PGPArmoury ok = new PGPArmoury(b, PGPArmoury.TYPE_PUBLIC_KEY);
b = Crypto.getPublicKeyFromCert(cert).getEncoded();
PGPArmoury sk = new PGPArmoury(b, PGPArmoury.TYPE_PUBLIC_KEY);
logmsg("serverCert (first) not signed by operator Cert (2nd)\n\n"+
sk + "\n\n\n" + ok + "\n\n");
setDead(e);
throw new SOXServerException(SOXException.SERVER_CERT, e);
}
// Careful not to set this before validating the signature
serverCert = cert;
}
///////// Requests //////////////////////////////////////////
/**
* Do the things necessary for being ready for a request.
* On return, current is set to a hopefully valid Issuer.
*/
public byte[] request(Request req)
throws SOXLaterException, SOXIssuerException
{
try {
return sendRequest(req);
} catch (SOXServerException ex) {
throw new SOXIssuerException("" + ex);
}
}
/**
* Issue a request.
* Checks first to see if timesync is recent.
* @except SOXServerException if this Server is dead, try another
*/
public byte[] sendRequest(Request request)
throws SOXServerException, SOXLaterException
{
// checkSync(); no, on demand, not pre-emptive
return internalRequest(request);
}
/**
* Issue a request, ignoring timesync (used by timesync itself).
* // Strategy: if the first request fails, fetch a new key and retry.
* @except SOXServerException if this Server is dead, try another
*/
private byte[] internalRequest(Request request)
throws SOXServerException, SOXLaterException
{
fetchCommsKey();
try {
try {
return requestOnce(request);
} catch(SOXKeyException ex) {
/*
* We are here because the key is stale. Try and get a new
* CommsKey *once* and retry the request.
*/
logmsg("*** first request failed, refetching comms...");
refetchCommsKey();
logmsg("*** trying request again...");
return requestOnce(request);
}
}
catch (SOXKeyException ex) {
//
// SOXKeyException is thrown when my key is duff.
// Let parent (SmartServer) sort it out.
//
setDead(ex.getMessage());
throw new SOXServerException(ex.getNumber(),
"request: " + ex.getMessage());
} catch (SOXPacketException ex) {
setDead(ex.getMessage());
throw new SOXServerException(ex.getNumber(),
"SOXPE 3: " + ex.getMessage());
} catch (SOXServerException ex) { // Even if I don't deal with it...
logmsg("catching (and dying on)" + ex);
setDead(ex.getMessage()); // ...I should still bail out.
throw ex ;
} catch (SOXLaterException ex) { // Even if I don't deal with it...
logmsg("catching (and dying on)" + ex);
setDead(ex.getMessage()); // ...I should still bail out.
throw ex ;
}
}
/**
* Issue a request once only.
*/
private byte[] requestOnce(Request request)
throws SOXPacketException, SOXKeyException,
SOXLaterException, SOXServerException
{
Key key;
try {
key = Crypto.generateKey();
} catch (java.security.KeyException ex) {
ex.printStackTrace();
throw new SOXKeyException("cannot generate key: " + ex);
}
EncryptedRequest encReq;
encReq = new EncryptedRequest(request, key, commsKey);
long tim = System.currentTimeMillis();
// logmsg("start " + tim + "... ");
EncryptedReply reply;
reply = basicAgent.encryptedRequest(encReq);
setAlive();
timeLastRequest = System.currentTimeMillis() - tim;
logmsg(timeLastRequest + "ms only !");
byte[] kkk = reply.decrypt(key);
logmsg(kkk.length + " bytes in packet");
return kkk ;
}
///////// Time Syncronisation //////////////////////////////////////////
// if a fatal exception is found, set me as Dead, force user to re-invoke
protected boolean isDead = false;
protected String reason = "";
public boolean isDead() { return isDead ; }
public void setDead() { isDead = true ; }
public void setDead(String s) { isDead = true; reason = s; }
public void setAlive() { isDead = false ; }
public String getDead() { return reason; }
/**
* Just to make it obvious what the numbers are used for...
*/
static final long SECOND = 1000,
HOUR = 60 * 60 * SECOND;
/**
* This is the difference between local time and the server's time.
* It is used to set time values to within a short range as a possible
* defence against replay attacks.
* It is not really relied upon by any application, but is
* useful to have for payment window settings.
* (Deposit replay attack is protected by the unique id.)
*
* The time should be persistant so that offline payments can be
* prepared.
*/
protected long timediff = 0; //Long.MAX_VALUE;
protected long deviation = 24 * HOUR;
protected long lastsync = 0;
protected long timeLastRequest;
// should we call checkSync() here?
public long getTimeDifference() { return timediff ; }
public long getTimeDeviation() { return deviation ; }
/**
* Do a timesync request to check on the server time.
*
* @except SOXServerException if this Server is dead, try another
*/
protected void timesync()
throws SOXServerException, SOXLaterException
{
/*
* Ha, we need to measure the time from before the creation
* of the TimeSyncRequest to the return of the time, as that
* is the relevant deviation including any internal slowdowns
* (i.e., the first crypto causes a SecureRandom 20 second
* delay! e.g., swapping...)
*/
long tim = System.currentTimeMillis();
logmsg(" Timesyncing.. (" + tim + " now, last was " + lastsync + ")");
TimeSyncRequest tsr = new TimeSyncRequest(""+reqNo++, new AccountId());
byte[] packet = this.internalRequest(tsr); // avoids timesync :-)
TimeSyncReply reply;
try {
reply = new TimeSyncReply(tsr, packet);
} catch (SOXPacketException ex) {
setDead(ex.getMessage());
throw new SOXServerException(ex.getNumber(),
"SOXPE TSR: " + ex.getMessage());
}
timediff = reply.getTimeDifference(); // as seen by SOX Server
/*
* Now measure the delay of the request and use that as deviation.
*/
// deviation = timeLastRequest;
lastsync = System.currentTimeMillis();
deviation = lastsync - tim;
logmsg("Timediff = " + timediff + " +- " + deviation +
" (" + timeLastRequest + ", complete at " + lastsync + ")");
if ( (timeLastRequest > (2*SECOND)) || deviation > (3*SECOND) )
logmsg("Warning: timesync is taking too long? " +
"(dev == "+deviation+", last == "+timeLastRequest+")");
}
/**
* Check the sync is reasonably new. If not, do a timesync.
*
* There is a presumption that is used within the requests,
* but that is not really the case as it only includes a
* timestamp which is ignored for the request code.
* (Historically, this was due to confusion as to which layer
* would prevent against replay attacks.)
*
* @except SOXServerException if this Server is dead, try another
*/
public void checkSync()
throws SOXServerException, SOXLaterException
{
long timeNow = System.currentTimeMillis();
if (timeNow - lastsync > 24 * HOUR)
{
logmsg("trying timesync, last has expired...");
timesync();
}
else if (deviation > (10*SECOND))
{
logmsg("trying timesync, last was not quick...");
timesync();
}
}
///////// Misc / Debug //////////////////////////////////////////
public String toString()
{
String retval = "SimpleServer " + name + ": " + basicAgent;
//retval += "\tName: "+name +"\n";
//retval += "\tBasic Agent: "+basicAgent+"\n";
//retval += "\tPrimary certificate: "+serverCert+"\n";
//retval += "\tComms certificate: "+commsKey+"\n";
return retval;
}
}
1.1 java/webfunds/sox/server/SmartServer.java
Index: SmartServer.java
===================================================================
/*
* $Id: SmartServer.java,v 1.1 2001/04/06 22:49:59 iang Exp $
*
* Copyright (c) 2001 Systemics Inc on behalf of
* the WebFunds Development Team. All Rights Reserved.
*/
package webfunds.sox.server;
import java.io.PrintWriter;
import java.net.URL;
import java.net.MalformedURLException;
import java.security.cert.Certificate;
import java.util.Date;
import webfunds.utils.Debug;
import webfunds.util.Panic;
import webfunds.comms.*;
import webfunds.sox.Server;
import webfunds.sox.SOXServerException;
import webfunds.sox.SOXIssuerException;
import webfunds.sox.SOXLaterException;
import webfunds.sox.Request;
/**
* Pretend to be a single SOX Server but actually manage through
* a list of equivalent entry points into the same virtual server.
* Hold a bunch of URLs, invoke objects on them on demand, and
* switch whenever a problem is detected.
*
* This was webfunds.sox.SmartIssuer.
*/
public class SmartServer
extends Debug implements Server // , IssuerFinder
{
protected URL[] urls;
protected URL[] others;
protected int which;
protected int size;
protected String name;
protected Certificate cert;
protected SSD ssd;
protected DirSSDStore ssds;
protected CommsManager comms = null;
/**
* Initialise the real data.
* Can be called multiple times if a new SSD is needed.
*
*/
private void init()
throws SOXServerException
{
this.which = 0;
this.others = null;
ssd = ssds.get(name);
if (ssd == null)
throw new Panic("unknown name in SSD store: " + name);
urls = ssd.getServerLocation();
if (urls.length == 0)
throw new SOXServerException("no servers listed");
// this.urls = convertToURLs(serverURLs);
size = urls.length;
if (size == 0)
throw new SOXServerException("all server URLs are malformed!");
startSimpleServers(urls);
others = ssd.getNearbyURLs();
}
/**
* Create a new Issuer object
* The Issuer object will normally be cached by the caller,
* but is not usefully stored on disk.
* This call is passive, call getReady() to cause action.
*
* @param name is the unique name of the SSD/SOX Server
* @param ssds must provide the SSD for name (must already be there)
* @param cert the [operator] certificate which signs the
* server's [server] certificate
* @param comms can provide requestor objects at the transport layer
*/
public SmartServer(String name,
DirSSDStore ssds,
Certificate cert,
CommsManager comms,
PrintWriter bug)
throws SOXServerException
{
debug(bug, " I ");
this.name = name;
this.ssds = ssds;
this.cert = cert;
this.comms = comms;
init();
logmsg("SmartServer(" + size + " urls, operCert, bug);");
}
/**
* For a single server scenario, this works as an IssuerFinder,
* by simply returning self.
public Issuer getIssuer(ItemId id) { id = null; return (Issuer)this ; }
*/
protected SimpleServer current = null;
protected SimpleServer[] servers = null;
/**
* Do the things necessary for being ready for a request.
* On return, current is set to a hopefully valid Issuer.
*/
public void startSimpleServers(URL[] urls)
{
int len = urls.length;
servers = new SimpleServer[len];
for (int i = 0; i < len; i++)
{
URL url = urls[which % len];
logmsg("new Simple at " + url);
CommsAgent agent = new HttpSocketAgent(url, comms, getDebug());
SimpleServer simp = new SimpleServer(""+i, cert, agent, getDebug());
servers[i] = simp;
}
}
/**
* Do the things necessary for being ready for a request.
* On return, current is set to a hopefully valid Issuer.
*/
public void getReady()
throws SOXLaterException, SOXIssuerException
{
try {
getReadyToRequest();
} catch (SOXServerException ex) {
throw new SOXIssuerException("" + ex);
}
}
/**
* Do the things necessary for being ready for a request.
* On return, current is set to a hopefully valid Issuer.
*/
public void getReadyToRequest()
throws SOXLaterException, SOXServerException
{
logmsg("getReady()");
if (current != null)
{
if (!current.isDead())
{
logmsg(" ... smart 'n ready!");
return ;
}
current = null;
logmsg("old is dead, need new SimpleServer");
}
else
logmsg("first time, need new SimpleServer");
URL url;
HttpAgent http;
SimpleServer simp;
int failures = 0;
int laters = 0;
int guard = which + servers.length; // record where we stop
while (which++ < guard)
{
simp = servers[which % servers.length];
logmsg(which + ": " + simp);
try {
simp.getReadyToRequest();
} catch (SOXLaterException ex) {
laters++;
logmsg(simp + " : later?");
continue ;
} catch (SOXServerException ex) {
failures++;
logmsg(simp + " : dead! " + ex);
continue ;
}
logmsg(which + " is good");
if (!simp.isDead()) // found a good one
{
current = simp;
// checkTimes();
logmsg("times " + timediff + " / " + deviation);
return ;
}
failures++; // does this ever happen?
}
//
// All issuers gave grief.
// What we are left with is all issuers down or
// unavailable. However, there may be no net.
//
checkNet();
if (failures == 0 && laters == 0) // cannot happen?
throw new Panic("failures == laters == 0: no Issuers?");
if (failures == 0)
throw new SOXLaterException(SOXLaterException.LATER_DOWN,
"tried them all, but all are down");
else if (laters == 0)
throw new SOXServerException("tried them all, all dead. Fatal?");
else
throw new SOXServerException("tried them all, " +
laters + " later, " + failures + " failed");
}
/**
* Do the things necessary for being ready for a request.
* On return, current is set to a hopefully valid Issuer.
*/
public byte[] request(Request req)
throws SOXLaterException, SOXIssuerException
{
// try {
return sendRequest(req);
// } catch (SOXServerException ex) {
// throw new SOXIssuerException("" + ex);
// }
}
public byte[] sendRequest(Request req)
throws SOXLaterException, SOXServerException
{
int guard = which + 2 * servers.length; // record where we stop
int failures = 0;
int laters = 0;
while (which < guard)
{
if (current.isDead())
getReadyToRequest();
byte[] b;
try {
b = current.sendRequest(req);
} catch (SOXLaterException ex) {
laters++;
continue ;
} catch (SOXServerException ex) {
failures++;
continue ;
}
// checkTimes();
return b ;
}
checkNet();
if (failures == 0 && laters == 0)
throw new Panic("failures == laters == 0: no Issuers?");
if (failures == 0)
throw new SOXLaterException(SOXLaterException.LATER_DOWN,
"tried them all, but all are down");
else if (laters == 0)
throw new SOXServerException("tried them all, all dead. Fatal?");
else // mixed results
throw new SOXLaterException(SOXLaterException.LATER_DOWN,
"tried them all, " +
laters + " later, " + failures + " failed");
}
static final String httpGet = "GET / HTTP/1.1\r\nHost: ";
static final String httpEnd = "\r\nConnection: close\r\n\r\n";
// static final String httpEnd = "\r\n\r\n";
/**
* Try some net hits.
*/
public int checkNet()
throws SOXLaterException
{
logmsg("checkNet()");
int num = others.length;
if (num == 0)
return 0;
URL url;
HttpAgent http;
int failures = 0;
int good = 0;
for (int i = 0; i < num; i++)
{
logmsg("ping: " + i + " of " + num);
url = others[i];
logmsg("other URL at " + url);
String host = url.getHost();
String getRequest = httpGet + host + httpEnd;
logmsg(i + ": " + host);
byte[] page;
SingleRequestor sr = comms.getSingleRequestor(url);
sr.setMax(256);
try {
page = sr.get(getRequest.getBytes());
} catch (RawURLException ex) {
failures++;
logmsg(url + " : is bad?");
continue ;
} catch (RawConnectException ex) {
failures++;
logmsg(url + " : later?");
continue ;
} catch (RawException ex) {
String s = "Unknown RawEx: " + ex;
logmsg(s);
ex.printStackTrace();
throw new Panic(s);
}
if (page == null) // why is this?
throw new Panic("http.get() returned null?");
if (page.length == 0)
{
failures++;
logmsg(url + " : was empty?");
continue ;
}
logmsg(i + " is good: " + page.length);
logmsg("-----------\n" + new String(page) + "\n----------");
good++;
}
if (good > 0)
return good ;
if (failures == 0) // cannot happen?
throw new Panic("failures == laters == 0: no Issuers?");
throw new SOXLaterException(SOXLaterException.LATER_NET,
"cannot see net, check your connectivity");
}
/**
* This is the difference between local time and the server's time.
* It is used to set time values to within a short range as a possible
* defence against replay attacks.
* It is not really relied upon by any application, but is
* useful to have for payment window settings.
* (Deposit replay attack is protected by the unique id.)
*
* The time should be persistant so that offline payments can be
* prepared.
*/
protected long timediff = 0;
protected long deviation = 24 * SimpleServer.HOUR;
public long getTimeDifference() { checkTimes(); return timediff ; }
public long getTimeDeviation() { return deviation ; }
protected void checkTimes()
{
if (!current.isDead())
{
try {
current.checkSync();
} catch (SOXLaterException ex) {
logmsg("checkTimes: (ignoring) current throws Later " + ex);
return ;
} catch (SOXServerException ex) {
logmsg("checkTimes: (ignoring) current throws ServerEx " + ex);
return ;
}
}
timediff = current.getTimeDifference();
deviation = current.getTimeDeviation();
}
public String toString()
{
String retval = "SmartServer. ";
if (current != null)
retval += "\tName: " + current.toString();
return retval;
}
}