forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p' (head 833ef88c125ba48423bc704701303ba55858336f)
to branch 'i2p.i2p.zzz.sam' (head 7814184e3e7cb4b819a0d7b4ceeda5befbe536c3)
This commit is contained in:
@ -3,13 +3,14 @@ package net.i2p.sam;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
/**
|
||||
* Modified from I2PTunnelHTTPServer
|
||||
* Handles UTF-8. Does not buffer past the end of line.
|
||||
*
|
||||
* @since 0.9.24
|
||||
*/
|
||||
@ -24,39 +25,44 @@ class ReadLine {
|
||||
* Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded
|
||||
*
|
||||
* @param buf output
|
||||
* @param timeout throws SocketTimeoutException immediately if zero or negative
|
||||
* @param timeout forever if if zero or negative
|
||||
* @throws SocketTimeoutException if timeout is reached before newline
|
||||
* @throws EOFException if EOF is reached before newline
|
||||
* @throws LineTooLongException if too long
|
||||
* @throws IOException on other errors in the underlying stream
|
||||
*/
|
||||
public static void readLine(Socket socket, StringBuilder buf, int timeout) throws IOException {
|
||||
if (timeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
long expires = System.currentTimeMillis() + timeout;
|
||||
// this reads and buffers extra bytes, so we can't use it
|
||||
// unless we're going to decode UTF-8 on-the-fly, we're stuck with ASCII
|
||||
//InputStreamReader in = new InputStreamReader(socket.getInputStream(), "UTF-8");
|
||||
InputStream in = socket.getInputStream();
|
||||
final int origTimeout = timeout;
|
||||
int c;
|
||||
int i = 0;
|
||||
socket.setSoTimeout(timeout);
|
||||
while ( (c = in.read()) != -1) {
|
||||
final long expires;
|
||||
if (origTimeout > 0) {
|
||||
socket.setSoTimeout(timeout);
|
||||
expires = System.currentTimeMillis() + timeout;
|
||||
} else {
|
||||
expires = 0;
|
||||
}
|
||||
final Reader reader = new UTF8Reader(socket.getInputStream());
|
||||
while ( (c = reader.read()) != -1) {
|
||||
if (++i > MAX_LINE_LENGTH)
|
||||
throw new LineTooLongException("Line too long - max " + MAX_LINE_LENGTH);
|
||||
if (c == '\n')
|
||||
break;
|
||||
int newTimeout = (int) (expires - System.currentTimeMillis());
|
||||
if (newTimeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
buf.append((char)c);
|
||||
if (newTimeout != timeout) {
|
||||
timeout = newTimeout;
|
||||
socket.setSoTimeout(timeout);
|
||||
if (origTimeout > 0) {
|
||||
int newTimeout = (int) (expires - System.currentTimeMillis());
|
||||
if (newTimeout <= 0)
|
||||
throw new SocketTimeoutException();
|
||||
buf.append((char)c);
|
||||
if (newTimeout != timeout) {
|
||||
timeout = newTimeout;
|
||||
socket.setSoTimeout(timeout);
|
||||
}
|
||||
} else {
|
||||
buf.append((char)c);
|
||||
}
|
||||
}
|
||||
if (c == -1) {
|
||||
if (System.currentTimeMillis() >= expires)
|
||||
if (origTimeout > 0 && System.currentTimeMillis() >= expires)
|
||||
throw new SocketTimeoutException();
|
||||
else
|
||||
throw new EOFException();
|
||||
|
@ -42,6 +42,7 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.I2PSSLSocketFactory;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
import net.i2p.util.PortMapper;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
@ -206,8 +207,8 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
*
|
||||
* @param name name of the destination
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
* @deprecated unused
|
||||
*/
|
||||
/****
|
||||
public Destination getDestination(String name) {
|
||||
synchronized (nameToPrivKeys) {
|
||||
String val = nameToPrivKeys.get(name);
|
||||
@ -223,6 +224,7 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Retrieve the I2P private keystream for the given name, formatted
|
||||
@ -255,59 +257,51 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
|
||||
/**
|
||||
* Load up the keys from the persistFilename.
|
||||
* TODO use DataHelper
|
||||
* TODO store in config dir, not base dir
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void loadKeys() {
|
||||
synchronized (nameToPrivKeys) {
|
||||
nameToPrivKeys.clear();
|
||||
BufferedReader br = null;
|
||||
File file = new File(persistFilename);
|
||||
// now in config dir but check base dir too...
|
||||
if (!file.exists()) {
|
||||
if (file.isAbsolute())
|
||||
return;
|
||||
file = new File(I2PAppContext.getGlobalContext().getConfigDir(), persistFilename);
|
||||
if (!file.exists())
|
||||
return;
|
||||
}
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(persistFilename), "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = br.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
nameToPrivKeys.put(name, privKeys);
|
||||
}
|
||||
Properties props = new Properties();
|
||||
DataHelper.loadProps(props, file);
|
||||
// unchecked
|
||||
Map foo = props;
|
||||
nameToPrivKeys.putAll(foo);
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Loaded " + nameToPrivKeys.size() + " private keys from " + persistFilename);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
_log.info("Loaded " + nameToPrivKeys.size() + " private keys from " + file);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (br != null) try { br.close(); } catch (IOException ioe) {}
|
||||
_log.error("Unable to read the keys from " + file, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current keys to disk in the location specified on creation.
|
||||
* TODO use DataHelper
|
||||
* TODO store in config dir, not base dir
|
||||
*/
|
||||
private void storeKeys() {
|
||||
synchronized (nameToPrivKeys) {
|
||||
FileOutputStream out = null;
|
||||
File file = new File(persistFilename);
|
||||
// now in config dir but check base dir too...
|
||||
if (!file.exists() && !file.isAbsolute())
|
||||
file = new File(I2PAppContext.getGlobalContext().getConfigDir(), persistFilename);
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Map.Entry<String, String> entry : nameToPrivKeys.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
String privKeys = entry.getValue();
|
||||
out.write(name.getBytes("UTF-8"));
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes("UTF-8"));
|
||||
out.write('\n');
|
||||
}
|
||||
Properties props = new OrderedProperties();
|
||||
props.putAll(nameToPrivKeys);
|
||||
DataHelper.storeProps(props, file);
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Saved " + nameToPrivKeys.size() + " private keys to " + persistFilename);
|
||||
_log.info("Saved " + nameToPrivKeys.size() + " private keys to " + file);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
_log.error("Error writing out the SAM keys to " + file, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -365,7 +359,7 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
* TODO we could have multiple servers on different hosts/ports in the future.
|
||||
*
|
||||
* @param props non-null instantiate and start server if it doesn't exist
|
||||
* @param return non-null
|
||||
* @return non-null
|
||||
* @throws IOException if can't bind to host/port, or if different than existing
|
||||
* @since 0.9.24
|
||||
*/
|
||||
@ -715,7 +709,9 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
changeState(RUNNING);
|
||||
if (_mgr != null)
|
||||
_mgr.register(this);
|
||||
I2PAppContext.getGlobalContext().portMapper().register(PortMapper.SVC_SAM, _listenPort);
|
||||
I2PAppContext.getGlobalContext().portMapper().register(_useSSL ? PortMapper.SVC_SAM_SSL : PortMapper.SVC_SAM,
|
||||
_listenHost != null ? _listenHost : "127.0.0.1",
|
||||
_listenPort);
|
||||
try {
|
||||
while (acceptConnections) {
|
||||
SocketChannel s = serverSocket.accept();
|
||||
@ -781,7 +777,7 @@ public class SAMBridge implements Runnable, ClientApp {
|
||||
if (serverSocket != null)
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {}
|
||||
I2PAppContext.getGlobalContext().portMapper().unregister(PortMapper.SVC_SAM);
|
||||
I2PAppContext.getGlobalContext().portMapper().unregister(_useSSL ? PortMapper.SVC_SAM_SSL : PortMapper.SVC_SAM);
|
||||
stopHandlers();
|
||||
changeState(STOPPED);
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ abstract class SAMHandler implements Runnable, Handler {
|
||||
public static boolean writeString(String str, SocketChannel out)
|
||||
{
|
||||
try {
|
||||
writeBytes(ByteBuffer.wrap(DataHelper.getASCII(str)), out);
|
||||
writeBytes(ByteBuffer.wrap(DataHelper.getUTF8(str)), out);
|
||||
} catch (IOException e) {
|
||||
//_log.debug("Caught IOException", e);
|
||||
return false;
|
||||
|
@ -13,7 +13,6 @@ import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -41,7 +40,7 @@ class SAMHandlerFactory {
|
||||
*/
|
||||
public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps,
|
||||
SAMBridge parent) throws SAMException {
|
||||
StringTokenizer tok;
|
||||
String line;
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMHandlerFactory.class);
|
||||
|
||||
try {
|
||||
@ -49,9 +48,8 @@ class SAMHandlerFactory {
|
||||
sock.setKeepAlive(true);
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
ReadLine.readLine(sock, buf, HELLO_TIMEOUT);
|
||||
String line = buf.toString();
|
||||
sock.setSoTimeout(0);
|
||||
tok = new StringTokenizer(line.trim(), " ");
|
||||
line = buf.toString();
|
||||
} catch (SocketTimeoutException e) {
|
||||
throw new SAMException("Timeout waiting for HELLO VERSION", e);
|
||||
} catch (IOException e) {
|
||||
@ -59,17 +57,15 @@ class SAMHandlerFactory {
|
||||
} catch (RuntimeException e) {
|
||||
throw new SAMException("Unexpected error", e);
|
||||
}
|
||||
if (log.shouldDebug())
|
||||
log.debug("New message received: [" + line + ']');
|
||||
|
||||
// Message format: HELLO VERSION [MIN=v1] [MAX=v2]
|
||||
if (tok.countTokens() < 2) {
|
||||
Properties props = SAMUtils.parseParams(line);
|
||||
if (!"HELLO".equals(props.remove(SAMUtils.COMMAND)) ||
|
||||
!"VERSION".equals(props.remove(SAMUtils.OPCODE))) {
|
||||
throw new SAMException("Must start with HELLO VERSION");
|
||||
}
|
||||
if (!tok.nextToken().equals("HELLO") ||
|
||||
!tok.nextToken().equals("VERSION")) {
|
||||
throw new SAMException("Must start with HELLO VERSION");
|
||||
}
|
||||
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
|
||||
String minVer = props.getProperty("MIN");
|
||||
if (minVer == null) {
|
||||
|
@ -9,6 +9,7 @@ package net.i2p.sam;
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
@ -32,7 +33,7 @@ import net.i2p.util.Log;
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
abstract class SAMMessageSession {
|
||||
abstract class SAMMessageSession implements Closeable {
|
||||
|
||||
protected final Log _log;
|
||||
private I2PSession session;
|
||||
|
@ -226,8 +226,7 @@ class SAMStreamSession {
|
||||
return false;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(dest);
|
||||
Destination d = SAMUtils.getDest(dest);
|
||||
|
||||
I2PSocketOptions opts = socketMgr.buildOptions(props);
|
||||
if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
|
||||
|
@ -11,9 +11,9 @@ package net.i2p.sam;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -82,6 +82,7 @@ class SAMUtils {
|
||||
*
|
||||
* @return True if the destination is valid, false otherwise
|
||||
*/
|
||||
/****
|
||||
public static boolean checkDestination(String dest) {
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
@ -92,6 +93,7 @@ class SAMUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Check whether a base64-encoded {dest,privkey,signingprivkey} is valid
|
||||
@ -105,8 +107,7 @@ class SAMUtils {
|
||||
return false;
|
||||
ByteArrayInputStream destKeyStream = new ByteArrayInputStream(b);
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.readBytes(destKeyStream);
|
||||
Destination d = Destination.create(destKeyStream);
|
||||
new PrivateKey().readBytes(destKeyStream);
|
||||
SigningPrivateKey spk = new SigningPrivateKey(d.getSigningPublicKey().getType());
|
||||
spk.readBytes(destKeyStream);
|
||||
@ -159,95 +160,186 @@ class SAMUtils {
|
||||
return d;
|
||||
}
|
||||
|
||||
public static final String COMMAND = "\"\"COMMAND\"\"";
|
||||
public static final String OPCODE = "\"\"OPCODE\"\"";
|
||||
|
||||
/**
|
||||
* Parse SAM parameters, and put them into a Propetries object
|
||||
* Parse SAM parameters, and put them into a Propetries object
|
||||
*
|
||||
* @param tok A StringTokenizer pointing to the SAM parameters
|
||||
* Modified from EepGet.
|
||||
* COMMAND and OPCODE are mapped to upper case; keys, values, and ping data are not.
|
||||
* Double quotes around values are stripped.
|
||||
*
|
||||
* @throws SAMException if the data was formatted incorrectly
|
||||
* @return Properties with the parsed SAM params, never null
|
||||
* Possible input:
|
||||
*<pre>
|
||||
* COMMAND
|
||||
* COMMAND OPCODE
|
||||
* COMMAND OPCODE [key=val]...
|
||||
* COMMAND OPCODE [key=" val with spaces "]...
|
||||
* PING
|
||||
* PONG
|
||||
* PING any thing goes
|
||||
* PONG any thing goes
|
||||
*
|
||||
* Escaping is allowed with a backslash, e.g. \"
|
||||
* No spaces before or after '=' allowed
|
||||
* Keys may not be quoted
|
||||
* COMMAND, OPCODE, and keys may not have '=' or whitespace unless escaped
|
||||
* Duplicate keys not allowed
|
||||
*</pre>
|
||||
*
|
||||
* A key without a value is not allowed by the spec, but is
|
||||
* returned with the value "true".
|
||||
*
|
||||
* COMMAND is returned as the value of the key ""COMMAND"".
|
||||
* OPCODE, or the remainder of the PING/PONG line if any, is returned as the value of the key ""OPCODE"".
|
||||
*
|
||||
* @param args non-null
|
||||
* @throws SAMException on some errors but not all
|
||||
* @return non-null, may be empty. Does not throw on missing COMMAND or OPCODE; caller must check.
|
||||
*/
|
||||
public static Properties parseParams(StringTokenizer tok) throws SAMException {
|
||||
int ntoks = tok.countTokens();
|
||||
Properties props = new Properties();
|
||||
|
||||
StringBuilder value = new StringBuilder();
|
||||
for (int i = 0; i < ntoks; ++i) {
|
||||
String token = tok.nextToken();
|
||||
public static Properties parseParams(String args) throws SAMException {
|
||||
final Properties rv = new Properties();
|
||||
final StringBuilder buf = new StringBuilder(32);
|
||||
final int length = args.length();
|
||||
boolean isQuoted = false;
|
||||
String key = null;
|
||||
// We go one past the end to force a fake trailing space
|
||||
// to make things easier, so we don't need cleanup at the end
|
||||
for (int i = 0; i <= length; i++) {
|
||||
char c = (i < length) ? args.charAt(i) : ' ';
|
||||
switch (c) {
|
||||
case '"':
|
||||
if (isQuoted) {
|
||||
// keys never quoted
|
||||
if (key != null) {
|
||||
if (rv.setProperty(key, buf.length() > 0 ? buf.toString() : "true") != null)
|
||||
throw new SAMException("Duplicate parameter " + key);
|
||||
key = null;
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
isQuoted = !isQuoted;
|
||||
break;
|
||||
|
||||
int pos = token.indexOf("=");
|
||||
if (pos <= 0) {
|
||||
//_log.debug("Error in params format");
|
||||
if (pos == 0) {
|
||||
throw new SAMException("No param specified [" + token + "]");
|
||||
} else {
|
||||
throw new SAMException("Bad formatting for param [" + token + "]");
|
||||
}
|
||||
}
|
||||
|
||||
String param = token.substring(0, pos);
|
||||
value.append(token.substring(pos+1));
|
||||
if (value.length() == 0)
|
||||
throw new SAMException("Empty value for param " + param);
|
||||
|
||||
// FIXME: The following code does not take into account that there
|
||||
// may have been multiple subsequent space chars in the input that
|
||||
// StringTokenizer treates as one.
|
||||
if (value.charAt(0) == '"') {
|
||||
while ( (i < ntoks) && (value.lastIndexOf("\"") <= 0) ) {
|
||||
value.append(' ').append(tok.nextToken());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
case '\r':
|
||||
case '\n':
|
||||
break;
|
||||
|
||||
props.setProperty(param, value.toString());
|
||||
value.setLength(0);
|
||||
case ' ':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\t':
|
||||
// whitespace - if we're in a quoted section, keep this as part of the quote,
|
||||
// otherwise use it as a delim
|
||||
if (isQuoted) {
|
||||
buf.append(c);
|
||||
} else {
|
||||
if (key != null) {
|
||||
if (rv.setProperty(key, buf.length() > 0 ? buf.toString() : "true") != null)
|
||||
throw new SAMException("Duplicate parameter " + key);
|
||||
key = null;
|
||||
} else if (buf.length() > 0) {
|
||||
// key without value
|
||||
String k = buf.toString();
|
||||
if (rv.isEmpty()) {
|
||||
k = k.toUpperCase(Locale.US);
|
||||
rv.setProperty(COMMAND, k);
|
||||
if (k.equals("PING") || k.equals("PONG")) {
|
||||
// eat the rest of the line
|
||||
if (i + 1 < args.length()) {
|
||||
String pingData = args.substring(i + 1);
|
||||
rv.setProperty(OPCODE, pingData);
|
||||
}
|
||||
// this will force an end of the loop
|
||||
i = length + 1;
|
||||
}
|
||||
} else if (rv.size() == 1) {
|
||||
rv.setProperty(OPCODE, k.toUpperCase(Locale.US));
|
||||
} else {
|
||||
if (rv.setProperty(k, "true") != null)
|
||||
throw new SAMException("Duplicate parameter " + k);
|
||||
}
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case '=':
|
||||
if (isQuoted) {
|
||||
buf.append(c);
|
||||
} else if (key != null) {
|
||||
// '=' in a value
|
||||
buf.append(c);
|
||||
} else {
|
||||
if (buf.length() == 0)
|
||||
throw new SAMException("Empty parameter name");
|
||||
key = buf.toString();
|
||||
buf.setLength(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
if (++i >= length)
|
||||
throw new SAMException("Unterminated escape");
|
||||
c = args.charAt(i);
|
||||
// fall through...
|
||||
|
||||
default:
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
// _log.debug("Parsed properties: " + dumpProperties(props));
|
||||
//}
|
||||
|
||||
return props;
|
||||
// nothing needed here, as we forced a trailing space in the loop
|
||||
// unterminated quoted content will be lost
|
||||
if (isQuoted)
|
||||
throw new SAMException("Unterminated quote");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Dump a Properties object in an human-readable form */
|
||||
/****
|
||||
private static String dumpProperties(Properties props) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String key, val;
|
||||
boolean firstIter = true;
|
||||
|
||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||
key = (String) entry.getKey();
|
||||
val = (String) entry.getValue();
|
||||
|
||||
if (!firstIter) {
|
||||
builder.append(";");
|
||||
} else {
|
||||
firstIter = false;
|
||||
}
|
||||
builder.append(" \"" + key + "\" -> \"" + val + "\"");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
****/
|
||||
|
||||
/****
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
test("a=b c=d e=\"f g h\"");
|
||||
test("a=\"b c d\" e=\"f g h\" i=\"j\"");
|
||||
test("a=\"b c d\" e=f i=\"j\"");
|
||||
if (args.length == 0) {
|
||||
System.out.println("Usage: CommandParser file || CommandParser text to parse");
|
||||
return;
|
||||
}
|
||||
if (args.length > 1 || !(new java.io.File(args[0])).exists()) {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (i != 0)
|
||||
buf.append(' ');
|
||||
buf.append(args[i]);
|
||||
}
|
||||
test(buf.toString());
|
||||
} else {
|
||||
java.io.InputStream in = new java.io.FileInputStream(args[0]);
|
||||
String line;
|
||||
while ((line = net.i2p.data.DataHelper.readLine(in)) != null) {
|
||||
try {
|
||||
test(line);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void test(String props) throws Exception {
|
||||
StringTokenizer tok = new StringTokenizer(props);
|
||||
Properties p = parseParams(tok);
|
||||
System.out.println(p);
|
||||
System.out.println("Testing: " + props);
|
||||
Properties m = parseParams(props);
|
||||
System.out.println("Found " + m.size() + " keys");
|
||||
for (Map.Entry e : m.entrySet()) {
|
||||
System.out.println(e.getKey() + "=[" + e.getValue() + ']');
|
||||
}
|
||||
System.out.println("-------------");
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,11 @@ import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
@ -49,6 +50,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
|
||||
protected final long _id;
|
||||
private static final AtomicLong __id = new AtomicLong();
|
||||
private static final int FIRST_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* Create a new SAM version 1 handler. This constructor expects
|
||||
@ -98,14 +100,15 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
String domain = null;
|
||||
String opcode = null;
|
||||
boolean canContinue = false;
|
||||
StringTokenizer tok;
|
||||
Properties props;
|
||||
final StringBuilder buf = new StringBuilder(128);
|
||||
|
||||
this.thread.setName("SAMv1Handler " + _id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("SAM handling started");
|
||||
|
||||
try {
|
||||
boolean gotFirstLine = false;
|
||||
while (true) {
|
||||
if (shouldStop()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -122,43 +125,39 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
_log.info("Connection closed by client");
|
||||
break;
|
||||
}
|
||||
java.io.InputStream is = clientSocketChannel.socket().getInputStream();
|
||||
if (is == null) {
|
||||
_log.info("Connection closed by client");
|
||||
break;
|
||||
}
|
||||
msg = DataHelper.readLine(is);
|
||||
if (msg == null) {
|
||||
_log.info("Connection closed by client (line read : null)");
|
||||
buf.setLength(0);
|
||||
// first time, set a timeout
|
||||
try {
|
||||
Socket sock = clientSocketChannel.socket();
|
||||
ReadLine.readLine(sock, buf, gotFirstLine ? 0 : FIRST_READ_TIMEOUT);
|
||||
sock.setSoTimeout(0);
|
||||
} catch (SocketTimeoutException ste) {
|
||||
writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"command timeout, bye\"\n");
|
||||
break;
|
||||
}
|
||||
msg = msg.trim();
|
||||
msg = buf.toString();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("New message received: [" + msg + "]");
|
||||
_log.debug("New message received: [" + msg + ']');
|
||||
}
|
||||
|
||||
if(msg.equals("")) {
|
||||
props = SAMUtils.parseParams(msg);
|
||||
domain = (String) props.remove(SAMUtils.COMMAND);
|
||||
if (domain == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Ignoring newline");
|
||||
continue;
|
||||
}
|
||||
|
||||
tok = new StringTokenizer(msg, " ");
|
||||
if (tok.countTokens() < 2) {
|
||||
// This is not a correct message, for sure
|
||||
opcode = (String) props.remove(SAMUtils.OPCODE);
|
||||
if (opcode == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error in message format");
|
||||
break;
|
||||
}
|
||||
domain = tok.nextToken();
|
||||
opcode = tok.nextToken();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Parsing (domain: \"" + domain
|
||||
+ "\"; opcode: \"" + opcode + "\")");
|
||||
}
|
||||
props = SAMUtils.parseParams(tok);
|
||||
|
||||
gotFirstLine = true;
|
||||
if (domain.equals("STREAM")) {
|
||||
canContinue = execStreamMessage(opcode, props);
|
||||
} else if (domain.equals("DATAGRAM")) {
|
||||
@ -370,16 +369,11 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
/* Parse and execute a NAMING message */
|
||||
protected boolean execNamingMessage(String opcode, Properties props) {
|
||||
if (opcode.equals("LOOKUP")) {
|
||||
if (props.isEmpty()) {
|
||||
_log.debug("No parameters specified in NAMING LOOKUP message");
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = props.getProperty("NAME");
|
||||
if (name == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Name to resolve not specified in NAMING message");
|
||||
return false;
|
||||
return writeString("NAMING REPLY RESULT=KEY_NOT_FOUND NAME=\"\" MESSAGE=\"Must specify NAME\"\n");
|
||||
}
|
||||
|
||||
Destination dest = null ;
|
||||
@ -1012,10 +1006,10 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
|
||||
msg = msg.replace("\r", " ");
|
||||
if (!msg.startsWith("\"")) {
|
||||
msg = msg.replace("\"", "");
|
||||
if (msg.contains("\"") || msg.contains("\t"))
|
||||
if (msg.contains(" ") || msg.contains("\t"))
|
||||
msg = '"' + msg + '"';
|
||||
}
|
||||
rv = " MESSAGE=\"" + msg + "\"";
|
||||
rv = " MESSAGE=" + msg;
|
||||
} else {
|
||||
rv = "";
|
||||
}
|
||||
|
@ -105,29 +105,18 @@ class SAMv2StreamSession extends SAMStreamSession
|
||||
return false ;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
|
||||
d.fromBase64 ( dest );
|
||||
|
||||
Destination d = SAMUtils.getDest(dest);
|
||||
I2PSocketOptions opts = socketMgr.buildOptions ( props );
|
||||
|
||||
if ( props.getProperty ( I2PSocketOptions.PROP_CONNECT_TIMEOUT ) == null )
|
||||
opts.setConnectTimeout ( 60 * 1000 );
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug ( "Connecting new I2PSocket..." );
|
||||
|
||||
|
||||
// non-blocking connection (SAMv2)
|
||||
|
||||
StreamConnector connector ;
|
||||
|
||||
connector = new StreamConnector ( id, d, opts );
|
||||
|
||||
StreamConnector connector = new StreamConnector ( id, d, opts );
|
||||
I2PAppThread connectThread = new I2PAppThread ( connector, "StreamConnector" + id ) ;
|
||||
|
||||
connectThread.start() ;
|
||||
|
||||
return true ;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
/**
|
||||
* This is the thread listening on 127.0.0.1:7655 or as specified by
|
||||
@ -90,7 +91,7 @@ class SAMv3DatagramServer implements Handler {
|
||||
/** @since 0.9.24 */
|
||||
public int getPort() { return _port; }
|
||||
|
||||
private static class Listener implements Runnable {
|
||||
private class Listener implements Runnable {
|
||||
|
||||
private final DatagramChannel server;
|
||||
|
||||
@ -98,8 +99,17 @@ class SAMv3DatagramServer implements Handler {
|
||||
{
|
||||
this.server = server ;
|
||||
}
|
||||
public void run()
|
||||
{
|
||||
|
||||
public void run() {
|
||||
I2PAppContext.getGlobalContext().portMapper().register(PortMapper.SVC_SAM_UDP, _host, _port);
|
||||
try {
|
||||
run2();
|
||||
} finally {
|
||||
I2PAppContext.getGlobalContext().portMapper().unregister(PortMapper.SVC_SAM_UDP);
|
||||
}
|
||||
}
|
||||
|
||||
private void run2() {
|
||||
ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024);
|
||||
|
||||
while (!Thread.interrupted())
|
||||
@ -135,6 +145,7 @@ class SAMv3DatagramServer implements Handler {
|
||||
public void run() {
|
||||
try {
|
||||
String header = DataHelper.readLine(is).trim();
|
||||
// we cannot use SAMUtils.parseParams() here
|
||||
StringTokenizer tok = new StringTokenizer(header, " ");
|
||||
if (tok.countTokens() < 3) {
|
||||
// This is not a correct message, for sure
|
||||
|
@ -24,7 +24,6 @@ import java.nio.channels.SocketChannel;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Properties;
|
||||
import java.util.HashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -55,6 +54,7 @@ class SAMv3Handler extends SAMv1Handler
|
||||
private volatile boolean streamForwardingSocket;
|
||||
private final boolean sendPorts;
|
||||
private long _lastPing;
|
||||
private static final int FIRST_READ_TIMEOUT = 60*1000;
|
||||
private static final int READ_TIMEOUT = 3*60*1000;
|
||||
|
||||
interface Session {
|
||||
@ -262,7 +262,6 @@ class SAMv3Handler extends SAMv1Handler
|
||||
String domain = null;
|
||||
String opcode = null;
|
||||
boolean canContinue = false;
|
||||
StringTokenizer tok;
|
||||
Properties props;
|
||||
|
||||
this.thread.setName("SAMv3Handler " + _id);
|
||||
@ -274,6 +273,7 @@ class SAMv3Handler extends SAMv1Handler
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
boolean gotFirstLine = false;
|
||||
while (true) {
|
||||
if (shouldStop()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -331,63 +331,53 @@ class SAMv3Handler extends SAMv1Handler
|
||||
}
|
||||
} else {
|
||||
buf.setLength(0);
|
||||
if (DataHelper.readLine(in, buf))
|
||||
line = buf.toString();
|
||||
else
|
||||
line = null;
|
||||
}
|
||||
if (line==null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Connection closed by client (line read : null)");
|
||||
break;
|
||||
}
|
||||
msg = line.trim();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New message received: [" + msg + "]");
|
||||
// first time, set a timeout
|
||||
try {
|
||||
ReadLine.readLine(socket, buf, gotFirstLine ? 0 : FIRST_READ_TIMEOUT);
|
||||
socket.setSoTimeout(0);
|
||||
} catch (SocketTimeoutException ste) {
|
||||
writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"command timeout, bye\"\n");
|
||||
break;
|
||||
}
|
||||
line = buf.toString();
|
||||
}
|
||||
|
||||
if(msg.equals("")) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New message received: [" + line + ']');
|
||||
props = SAMUtils.parseParams(line);
|
||||
domain = (String) props.remove(SAMUtils.COMMAND);
|
||||
if (domain == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Ignoring newline");
|
||||
continue;
|
||||
}
|
||||
|
||||
tok = new StringTokenizer(msg, " ");
|
||||
int count = tok.countTokens();
|
||||
if (count <= 0) {
|
||||
// This is not a correct message, for sure
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Ignoring whitespace");
|
||||
continue;
|
||||
gotFirstLine = true;
|
||||
opcode = (String) props.remove(SAMUtils.OPCODE);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Parsing (domain: \"" + domain
|
||||
+ "\"; opcode: \"" + opcode + "\")");
|
||||
}
|
||||
domain = tok.nextToken();
|
||||
|
||||
// these may not have a second token
|
||||
if (domain.equals("PING")) {
|
||||
execPingMessage(tok);
|
||||
execPingMessage(opcode);
|
||||
continue;
|
||||
} else if (domain.equals("PONG")) {
|
||||
execPongMessage(tok);
|
||||
execPongMessage(opcode);
|
||||
continue;
|
||||
} else if (domain.equals("QUIT") || domain.equals("STOP") ||
|
||||
domain.equals("EXIT")) {
|
||||
writeString(domain + " STATUS RESULT=OK MESSAGE=bye\n");
|
||||
break;
|
||||
}
|
||||
if (count <= 1) {
|
||||
|
||||
if (opcode == null) {
|
||||
// This is not a correct message, for sure
|
||||
if (writeString(domain + " STATUS RESULT=I2P_ERROR MESSAGE=\"command not specified\"\n"))
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
opcode = tok.nextToken();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Parsing (domain: \"" + domain
|
||||
+ "\"; opcode: \"" + opcode + "\")");
|
||||
}
|
||||
props = SAMUtils.parseParams(tok);
|
||||
|
||||
if (domain.equals("STREAM")) {
|
||||
canContinue = execStreamMessage(opcode, props);
|
||||
@ -909,13 +899,15 @@ class SAMv3Handler extends SAMv1Handler
|
||||
/**
|
||||
* Handle a PING.
|
||||
* Send a PONG.
|
||||
*
|
||||
* @param msg to append, may be null
|
||||
* @since 0.9.24
|
||||
*/
|
||||
private void execPingMessage(StringTokenizer tok) {
|
||||
private void execPingMessage(String msg) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("PONG");
|
||||
while (tok.hasMoreTokens()) {
|
||||
buf.append(' ').append(tok.nextToken());
|
||||
if (msg != null) {
|
||||
buf.append(' ').append(msg);
|
||||
}
|
||||
buf.append('\n');
|
||||
writeString(buf.toString());
|
||||
@ -923,13 +915,12 @@ class SAMv3Handler extends SAMv1Handler
|
||||
|
||||
/**
|
||||
* Handle a PONG.
|
||||
*
|
||||
* @param s received, may be null
|
||||
* @since 0.9.24
|
||||
*/
|
||||
private void execPongMessage(StringTokenizer tok) {
|
||||
String s;
|
||||
if (tok.hasMoreTokens()) {
|
||||
s = tok.nextToken();
|
||||
} else {
|
||||
private void execPongMessage(String s) {
|
||||
if (s == null) {
|
||||
s = "";
|
||||
}
|
||||
if (_lastPing > 0) {
|
||||
|
@ -149,10 +149,10 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
||||
WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream());
|
||||
|
||||
SAMBridge bridge = handler.getBridge();
|
||||
(new Thread(rec.getThreadGroup(),
|
||||
(new I2PAppThread(rec.getThreadGroup(),
|
||||
new Pipe(fromClient, toI2P, bridge),
|
||||
"ConnectV3 SAMPipeClientToI2P")).start();
|
||||
(new Thread(rec.getThreadGroup(),
|
||||
(new I2PAppThread(rec.getThreadGroup(),
|
||||
new Pipe(fromI2P, toClient, bridge),
|
||||
"ConnectV3 SAMPipeI2PToClient")).start();
|
||||
}
|
||||
@ -208,10 +208,10 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
||||
WritableByteChannel toI2P = Channels.newChannel(i2ps.getOutputStream());
|
||||
|
||||
SAMBridge bridge = handler.getBridge();
|
||||
(new Thread(rec.getThreadGroup(),
|
||||
(new I2PAppThread(rec.getThreadGroup(),
|
||||
new Pipe(fromClient, toI2P, bridge),
|
||||
"AcceptV3 SAMPipeClientToI2P")).start();
|
||||
(new Thread(rec.getThreadGroup(),
|
||||
(new I2PAppThread(rec.getThreadGroup(),
|
||||
new Pipe(fromI2P, toClient, bridge),
|
||||
"AcceptV3 SAMPipeI2PToClient")).start();
|
||||
}
|
||||
@ -258,7 +258,7 @@ class SAMv3StreamSession extends SAMStreamSession implements SAMv3Handler.Sessi
|
||||
}
|
||||
|
||||
SocketForwarder forwarder = new SocketForwarder(host, port, isSSL, this, verbose, sendPorts);
|
||||
(new Thread(rec.getThreadGroup(), forwarder, "SAMV3StreamForwarder")).start();
|
||||
(new I2PAppThread(rec.getThreadGroup(), forwarder, "SAMV3StreamForwarder")).start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
152
apps/sam/java/src/net/i2p/sam/UTF8Reader.java
Normal file
152
apps/sam/java/src/net/i2p/sam/UTF8Reader.java
Normal file
@ -0,0 +1,152 @@
|
||||
package net.i2p.sam;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
|
||||
|
||||
/**
|
||||
* An unbuffered version of InputStreamReader.
|
||||
*
|
||||
* Does not read any extra characters, as long as input is well-formed.
|
||||
* This permits the partial reading of an InputStream as UTF-8
|
||||
* and then passing the remainder of the input stream elsewhere.
|
||||
* This isn't the most robust for malformed input, so it
|
||||
* may not be appropriate for e.g. HTTP headers.
|
||||
*
|
||||
* Not thread-safe, obviously.
|
||||
*
|
||||
* May be moved to net.i2p.util if anybody else needs it.
|
||||
*
|
||||
* @since 0.9.24 somewhat adapted from net.i2p.util.TranslateReader
|
||||
*/
|
||||
public class UTF8Reader extends Reader {
|
||||
|
||||
private final InputStream _in;
|
||||
// following three are lazily initialized when needed
|
||||
private ByteBuffer _bb;
|
||||
private CharBuffer _cb;
|
||||
private CharsetDecoder _dc;
|
||||
|
||||
// Charset.forName("UTF-8").newDecoder().replacement().charAt(0) & 0xffff
|
||||
private static final int REPLACEMENT = 0xfffd;
|
||||
|
||||
/**
|
||||
* @param in UTF-8
|
||||
*/
|
||||
public UTF8Reader(InputStream in) {
|
||||
super();
|
||||
_in = in;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return replacement character on decoding error
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = _in.read();
|
||||
if (b < 0)
|
||||
return b;
|
||||
// https://en.wikipedia.org/wiki/Utf-8
|
||||
if ((b & 0x80) == 0)
|
||||
return b;
|
||||
if (_bb == null) {
|
||||
_bb = ByteBuffer.allocate(6);
|
||||
_cb = CharBuffer.allocate(1);
|
||||
_dc = Charset.forName("UTF-8").newDecoder();
|
||||
} else {
|
||||
_bb.clear();
|
||||
_cb.clear();
|
||||
}
|
||||
_bb.put((byte) b);
|
||||
int end; // how many more
|
||||
if ((b & 0xe0) == 0xc0)
|
||||
end = 1;
|
||||
else if ((b & 0xf0) == 0xe0)
|
||||
end = 2;
|
||||
else if ((b & 0xf8) == 0xf0)
|
||||
end = 3;
|
||||
else if ((b & 0xfc) == 0xf8)
|
||||
end = 4;
|
||||
else if ((b & 0xfe) == 0xfc)
|
||||
end = 5;
|
||||
else // error, 10xxxxxx
|
||||
return REPLACEMENT;
|
||||
for (int i = 0; i < end; i++) {
|
||||
b = _in.read();
|
||||
if (b < 0)
|
||||
return REPLACEMENT; // next read will return EOF
|
||||
// we aren't going to check for all errors,
|
||||
// but let's fail fast on this one
|
||||
if ((b & 0x80) == 0)
|
||||
return REPLACEMENT;
|
||||
_bb.put((byte) b);
|
||||
}
|
||||
_dc.reset();
|
||||
_bb.flip();
|
||||
CoderResult result = _dc.decode(_bb, _cb, true);
|
||||
// Overflow and underflow are not errors.
|
||||
// It seems to return underflow every time.
|
||||
// So just check if we got a character back in the buffer.
|
||||
_cb.flip();
|
||||
if (result.isError() || !_cb.hasRemaining())
|
||||
return REPLACEMENT;
|
||||
// let underflow and overflow go, return first
|
||||
return _cb.get() & 0xffff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(char cbuf[]) throws IOException {
|
||||
return read(cbuf, 0, cbuf.length);
|
||||
}
|
||||
|
||||
public int read(char cbuf[], int off, int len) throws IOException {
|
||||
for (int i = 0; i < len; i++) {
|
||||
int c = read();
|
||||
if (c < 0) {
|
||||
if (i == 0)
|
||||
return -1;
|
||||
return i;
|
||||
}
|
||||
cbuf[off + i] = (char) c;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
_in.close();
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
String s = "Consider the encoding of the Euro sign, €." +
|
||||
" The Unicode code point for \"€\" is U+20AC.";
|
||||
byte[] test = s.getBytes("UTF-8");
|
||||
InputStream bais = new java.io.ByteArrayInputStream(test);
|
||||
UTF8Reader r = new UTF8Reader(bais);
|
||||
int b;
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
while ((b = r.read()) >= 0) {
|
||||
buf.append((char) b);
|
||||
}
|
||||
System.out.println("Received: " + buf);
|
||||
System.out.println("Test passed? " + buf.toString().equals(s));
|
||||
buf.setLength(0);
|
||||
bais = new java.io.ByteArrayInputStream(new byte[] { 'x', (byte) 0xcc, 'x' } );
|
||||
r = new UTF8Reader(bais);
|
||||
while ((b = r.read()) >= 0) {
|
||||
buf.append((char) b);
|
||||
}
|
||||
System.out.println("Received: " + buf);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
@ -5,8 +5,10 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
@ -21,6 +23,7 @@ public class SAMReader {
|
||||
private final SAMClientEventListener _listener;
|
||||
private volatile boolean _live;
|
||||
private Thread _thread;
|
||||
private static final AtomicInteger _count = new AtomicInteger();
|
||||
|
||||
public SAMReader(I2PAppContext context, InputStream samIn, SAMClientEventListener listener) {
|
||||
_log = context.logManager().getLog(SAMReader.class);
|
||||
@ -32,7 +35,7 @@ public class SAMReader {
|
||||
if (_live)
|
||||
throw new IllegalStateException();
|
||||
_live = true;
|
||||
I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader");
|
||||
I2PAppThread t = new I2PAppThread(new Runner(), "SAM reader " + _count.incrementAndGet());
|
||||
t.start();
|
||||
_thread = t;
|
||||
}
|
||||
@ -111,10 +114,7 @@ public class SAMReader {
|
||||
break;
|
||||
}
|
||||
|
||||
String line = "";
|
||||
try {
|
||||
line = new String(baos.toByteArray(), "ISO-8859-1");
|
||||
} catch (IOException ioe) {}
|
||||
String line = DataHelper.getUTF8(baos.toByteArray());
|
||||
baos.reset();
|
||||
|
||||
if (_log.shouldDebug())
|
||||
@ -275,7 +275,7 @@ public class SAMReader {
|
||||
String pr = params.getProperty("PROTOCOL");
|
||||
int fromPort = 0;
|
||||
int toPort = 0;
|
||||
int protocol = 18;
|
||||
int protocol = I2PSession.PROTO_DATAGRAM_RAW;
|
||||
try {
|
||||
if (fp != null)
|
||||
fromPort = Integer.parseInt(fp);
|
||||
|
@ -69,7 +69,7 @@ public class SAMStreamSend {
|
||||
String port = "7656";
|
||||
String user = null;
|
||||
String password = null;
|
||||
String opts = "";
|
||||
String opts = "inbound.length=0 outbound.length=0";
|
||||
int c;
|
||||
while ((c = g.getopt()) != -1) {
|
||||
switch (c) {
|
||||
@ -237,9 +237,10 @@ public class SAMStreamSend {
|
||||
synchronized (samOut) {
|
||||
try {
|
||||
if (user != null && password != null)
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=" + user + " PASSWORD=" + password + '\n').getBytes());
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=\"" + user.replace("\"", "\\\"") +
|
||||
"\" PASSWORD=\"" + password.replace("\"", "\\\"") + "\"\n").getBytes("UTF-8"));
|
||||
else
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes());
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Hello sent");
|
||||
@ -256,6 +257,8 @@ public class SAMStreamSend {
|
||||
byte[] id = new byte[5];
|
||||
_context.random().nextBytes(id);
|
||||
_v3ID = Base32.encode(id);
|
||||
if (_isV32)
|
||||
_v3ID = "xx€€xx" + _v3ID;
|
||||
_conOptions = "ID=" + _v3ID;
|
||||
}
|
||||
String style;
|
||||
@ -266,7 +269,7 @@ public class SAMStreamSend {
|
||||
else
|
||||
style = "RAW";
|
||||
String req = "SESSION CREATE STYLE=" + style + " DESTINATION=TRANSIENT " + _conOptions + ' ' + opts + '\n';
|
||||
samOut.write(req.getBytes());
|
||||
samOut.write(req.getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Session create sent");
|
||||
@ -277,7 +280,7 @@ public class SAMStreamSend {
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
samOut.write(req.getBytes());
|
||||
samOut.write(req.getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Naming lookup sent");
|
||||
@ -350,7 +353,7 @@ public class SAMStreamSend {
|
||||
byte dest[] = new byte[1024];
|
||||
int read = DataHelper.read(fin, dest);
|
||||
|
||||
_remoteDestination = new String(dest, 0, read);
|
||||
_remoteDestination = DataHelper.getUTF8(dest, 0, read);
|
||||
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".totalSent", "Data size sent", "swarm", new long[] { 30*1000, 60*1000, 5*60*1000 });
|
||||
_context.statManager().createRateStat("send." + _connectionId + ".started", "When we start", "swarm", new long[] { 5*60*1000 });
|
||||
@ -363,7 +366,7 @@ public class SAMStreamSend {
|
||||
if (_isV3)
|
||||
buf.append(" FROM_PORT=1234 TO_PORT=5678");
|
||||
buf.append('\n');
|
||||
byte[] msg = DataHelper.getASCII(buf.toString());
|
||||
byte[] msg = DataHelper.getUTF8(buf.toString());
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
@ -431,7 +434,7 @@ public class SAMStreamSend {
|
||||
} else {
|
||||
throw new IOException("unsupported mode " + _mode);
|
||||
}
|
||||
byte msg[] = DataHelper.getASCII(m);
|
||||
byte msg[] = DataHelper.getUTF8(m);
|
||||
_samOut.write(msg);
|
||||
}
|
||||
_samOut.write(data, 0, read);
|
||||
@ -440,16 +443,16 @@ public class SAMStreamSend {
|
||||
} else {
|
||||
// real datagrams
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(read + 1024);
|
||||
baos.write(DataHelper.getASCII("3.0 "));
|
||||
baos.write(DataHelper.getASCII(_v3ID));
|
||||
baos.write(DataHelper.getUTF8("3.0 "));
|
||||
baos.write(DataHelper.getUTF8(_v3ID));
|
||||
baos.write((byte) ' ');
|
||||
baos.write(DataHelper.getASCII(_remoteDestination));
|
||||
baos.write(DataHelper.getUTF8(_remoteDestination));
|
||||
if (_isV32) {
|
||||
// only set TO_PORT to test session setting of FROM_PORT
|
||||
if (_mode == RAW)
|
||||
baos.write(DataHelper.getASCII(" PROTOCOL=123 TO_PORT=5678"));
|
||||
baos.write(DataHelper.getUTF8(" PROTOCOL=123 TO_PORT=5678"));
|
||||
else
|
||||
baos.write(DataHelper.getASCII(" TO_PORT=5678"));
|
||||
baos.write(DataHelper.getUTF8(" TO_PORT=5678"));
|
||||
}
|
||||
baos.write((byte) '\n');
|
||||
baos.write(data, 0, read);
|
||||
@ -476,12 +479,13 @@ public class SAMStreamSend {
|
||||
_log.info("Error closing", ioe);
|
||||
}
|
||||
} else {
|
||||
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes();
|
||||
try {
|
||||
byte msg[] = ("STREAM CLOSE ID=" + _connectionId + "\n").getBytes("UTF-8");
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
_samOut.close();
|
||||
// we can't close this yet, we will lose data
|
||||
//_samOut.close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.info("Error closing", ioe);
|
||||
@ -492,20 +496,18 @@ public class SAMStreamSend {
|
||||
}
|
||||
|
||||
closed();
|
||||
// stop the reader, since we're only doing this once for testing
|
||||
// you wouldn't do this in a real application
|
||||
// closing the master socket too fast will kill the data socket flushing through
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException ie) {}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner exiting");
|
||||
if (toSend != _totalSent)
|
||||
_log.error("Only sent " + _totalSent + " of " + toSend + " bytes");
|
||||
if (_reader2 != null)
|
||||
_reader2.stopReading();
|
||||
// stop the reader, since we're only doing this once for testing
|
||||
// you wouldn't do this in a real application
|
||||
if (_isV3) {
|
||||
// closing the master socket too fast will kill the data socket flushing through
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
_reader.stopReading();
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
|
||||
import gnu.getopt.Getopt;
|
||||
|
||||
@ -56,10 +57,13 @@ public class SAMStreamSink {
|
||||
private final Map<String, Sink> _remotePeers;
|
||||
private static I2PSSLSocketFactory _sslSocketFactory;
|
||||
|
||||
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4, RAWHDR = 5, FORWARD = 6;
|
||||
private static final String USAGE = "Usage: SAMStreamSink [-s] [-m mode] [-v version] [-b samHost] [-p samPort] [-o opt=val] [-u user] [-w password] myDestFile sinkDir\n" +
|
||||
" modes: stream: 0; datagram: 1; v1datagram: 2; raw: 3; v1raw: 4; raw-with-headers: 5; stream-forward: 6\n" +
|
||||
" -s: use SSL\n" +
|
||||
private static final int STREAM=0, DG=1, V1DG=2, RAW=3, V1RAW=4, RAWHDR = 5, FORWARD = 6, FORWARDSSL=7;
|
||||
private static final String USAGE = "Usage: SAMStreamSink [-s] [-m mode] [-v version] [-b samHost] [-p samPort]\n" +
|
||||
" [-o opt=val] [-u user] [-w password] myDestFile sinkDir\n" +
|
||||
" modes: stream: 0; datagram: 1; v1datagram: 2;\n" +
|
||||
" raw: 3; v1raw: 4; raw-with-headers: 5;\n" +
|
||||
" stream-forward: 6; stream-forward-ssl: 7\n" +
|
||||
" -s: use SSL to connect to bridge\n" +
|
||||
" multiple -o session options are allowed";
|
||||
private static final int V3FORWARDPORT=9998;
|
||||
private static final int V3DGPORT=9999;
|
||||
@ -73,7 +77,7 @@ public class SAMStreamSink {
|
||||
String port = "7656";
|
||||
String user = null;
|
||||
String password = null;
|
||||
String opts = "";
|
||||
String opts = "inbound.length=0 outbound.length=0";
|
||||
int c;
|
||||
while ((c = g.getopt()) != -1) {
|
||||
switch (c) {
|
||||
@ -83,7 +87,7 @@ public class SAMStreamSink {
|
||||
|
||||
case 'm':
|
||||
mode = Integer.parseInt(g.getOptarg());
|
||||
if (mode < 0 || mode > FORWARD) {
|
||||
if (mode < 0 || mode > FORWARDSSL) {
|
||||
System.err.println(USAGE);
|
||||
return;
|
||||
}
|
||||
@ -174,7 +178,7 @@ public class SAMStreamSink {
|
||||
Thread t = new Pinger(out);
|
||||
t.start();
|
||||
}
|
||||
if (_isV3 && (mode == STREAM || mode == FORWARD)) {
|
||||
if (_isV3 && (mode == STREAM || mode == FORWARD || mode == FORWARDSSL)) {
|
||||
// test multiple acceptors, only works in 3.2
|
||||
int acceptors = (_isV32 && mode == STREAM) ? 4 : 1;
|
||||
for (int i = 0; i < acceptors; i++) {
|
||||
@ -193,7 +197,18 @@ public class SAMStreamSink {
|
||||
}
|
||||
if (mode == FORWARD) {
|
||||
// set up a listening ServerSocket
|
||||
(new FwdRcvr(isSSL)).start();
|
||||
(new FwdRcvr(false, null)).start();
|
||||
} else if (mode == FORWARDSSL) {
|
||||
// set up a listening ServerSocket
|
||||
String scfile = SSLUtil.DEFAULT_SAMCLIENT_CONFIGFILE;
|
||||
File file = new File(scfile);
|
||||
Properties opts = new Properties();
|
||||
if (file.exists())
|
||||
DataHelper.loadProps(opts, file);
|
||||
boolean shouldSave = SSLUtil.verifyKeyStore(opts);
|
||||
if (shouldSave)
|
||||
DataHelper.storeProps(opts, file);
|
||||
(new FwdRcvr(true, opts)).start();
|
||||
}
|
||||
} else if (_isV3 && (mode == DG || mode == RAW || mode == RAWHDR)) {
|
||||
// set up a listening DatagramSocket
|
||||
@ -208,7 +223,10 @@ public class SAMStreamSink {
|
||||
private class DGRcvr extends I2PAppThread {
|
||||
private final int _mode;
|
||||
|
||||
public DGRcvr(int mode) { _mode = mode; }
|
||||
public DGRcvr(int mode) {
|
||||
super("SAM DG Rcvr");
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
byte[] buf = new byte[32768];
|
||||
@ -257,18 +275,23 @@ public class SAMStreamSink {
|
||||
|
||||
private class FwdRcvr extends I2PAppThread {
|
||||
private final boolean _isSSL;
|
||||
// for SSL only
|
||||
private final Properties _opts;
|
||||
|
||||
public FwdRcvr(boolean isSSL) {
|
||||
if (isSSL)
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
public FwdRcvr(boolean isSSL, Properties opts) {
|
||||
super("SAM Fwd Rcvr");
|
||||
_isSSL = isSSL;
|
||||
_opts = opts;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
ServerSocket ss;
|
||||
if (_isSSL) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
SSLServerSocketFactory fact = SSLUtil.initializeFactory(_opts);
|
||||
SSLServerSocket sock = (SSLServerSocket) fact.createServerSocket(V3FORWARDPORT);
|
||||
I2PSSLSocketFactory.setProtocolsAndCiphers(sock);
|
||||
ss = sock;
|
||||
} else {
|
||||
ss = new ServerSocket(V3FORWARDPORT);
|
||||
}
|
||||
@ -277,10 +300,40 @@ public class SAMStreamSink {
|
||||
Sink sink = new Sink("FAKE", "FAKEFROM");
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
boolean gotDest = false;
|
||||
byte[] dest = new byte[1024];
|
||||
int dlen = 0;
|
||||
byte[] buf = new byte[32768];
|
||||
int len;
|
||||
while((len = in.read(buf)) >= 0) {
|
||||
sink.received(buf, 0, len);
|
||||
if (!gotDest) {
|
||||
// eat the dest line
|
||||
for (int i = 0; i < len; i++) {
|
||||
byte b = buf[i];
|
||||
if (b == (byte) '\n') {
|
||||
gotDest = true;
|
||||
if (_log.shouldInfo()) {
|
||||
try {
|
||||
_log.info("Got incoming accept from: \"" + new String(dest, 0, dlen, "ISO-8859-1") + '"');
|
||||
} catch (IOException uee) {}
|
||||
}
|
||||
// feed any remaining to the sink
|
||||
i++;
|
||||
if (i < len)
|
||||
sink.received(buf, i, len - i);
|
||||
break;
|
||||
} else {
|
||||
if (dlen < dest.length) {
|
||||
dest[dlen++] = b;
|
||||
} else if (dlen == dest.length) {
|
||||
dlen++;
|
||||
_log.error("first line overflow on accept");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sink.received(buf, 0, len);
|
||||
}
|
||||
}
|
||||
sink.closed();
|
||||
} catch (IOException ioe) {
|
||||
@ -307,7 +360,7 @@ public class SAMStreamSink {
|
||||
try {
|
||||
Thread.sleep(127*1000);
|
||||
synchronized(_out) {
|
||||
_out.write(DataHelper.getASCII("PING " + System.currentTimeMillis() + '\n'));
|
||||
_out.write(DataHelper.getUTF8("PING " + System.currentTimeMillis() + '\n'));
|
||||
_out.flush();
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
@ -377,7 +430,7 @@ public class SAMStreamSink {
|
||||
_log.info("Got PING " + data + ", sending PONG " + data);
|
||||
synchronized (_out) {
|
||||
try {
|
||||
_out.write(("PONG " + data + '\n').getBytes());
|
||||
_out.write(("PONG " + data + '\n').getBytes("UTF-8"));
|
||||
_out.flush();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("PONG fail", ioe);
|
||||
@ -514,9 +567,9 @@ public class SAMStreamSink {
|
||||
synchronized (samOut) {
|
||||
try {
|
||||
if (user != null && password != null)
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=" + user + " PASSWORD=" + password + '\n').getBytes());
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + " USER=" + user + " PASSWORD=" + password + '\n').getBytes("UTF-8"));
|
||||
else
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes());
|
||||
samOut.write(("HELLO VERSION MIN=1.0 MAX=" + version + '\n').getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Hello sent");
|
||||
@ -534,13 +587,15 @@ public class SAMStreamSink {
|
||||
req = "STREAM ACCEPT SILENT=false TO_PORT=5678 ID=" + _v3ID + "\n";
|
||||
else if (mode == FORWARD)
|
||||
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + '\n';
|
||||
else if (mode == FORWARDSSL)
|
||||
req = "STREAM FORWARD ID=" + _v3ID + " PORT=" + V3FORWARDPORT + " SSL=true\n";
|
||||
else
|
||||
throw new IllegalStateException("mode " + mode);
|
||||
samOut.write(req.getBytes());
|
||||
samOut.write(req.getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("STREAM ACCEPT/FORWARD sent");
|
||||
if (mode == FORWARD) {
|
||||
if (mode == FORWARD || mode == FORWARDSSL) {
|
||||
// docs were wrong, we do not get a STREAM STATUS if SILENT=true for ACCEPT
|
||||
boolean ok = eventHandler.waitForStreamStatusReply();
|
||||
if (!ok)
|
||||
@ -587,7 +642,7 @@ public class SAMStreamSink {
|
||||
dest = _destFile;
|
||||
}
|
||||
String style;
|
||||
if (mode == STREAM || mode == FORWARD)
|
||||
if (mode == STREAM || mode == FORWARD || mode == FORWARDSSL)
|
||||
style = "STREAM";
|
||||
else if (mode == V1DG)
|
||||
style = "DATAGRAM";
|
||||
@ -600,7 +655,7 @@ public class SAMStreamSink {
|
||||
else
|
||||
style = "RAW HEADER=true PORT=" + V3DGPORT;
|
||||
String req = "SESSION CREATE STYLE=" + style + " DESTINATION=" + dest + ' ' + _conOptions + ' ' + sopts + '\n';
|
||||
samOut.write(req.getBytes());
|
||||
samOut.write(req.getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Session create sent");
|
||||
@ -612,7 +667,7 @@ public class SAMStreamSink {
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
}
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
samOut.write(req.getBytes());
|
||||
samOut.write(req.getBytes("UTF-8"));
|
||||
samOut.flush();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Naming lookup sent");
|
||||
@ -649,7 +704,7 @@ public class SAMStreamSink {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(f);
|
||||
fos.write(dest.getBytes());
|
||||
fos.write(dest.getBytes("UTF-8"));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("My destination written to " + _destFile);
|
||||
} catch (IOException e) {
|
||||
|
189
apps/sam/java/src/net/i2p/sam/client/SSLUtil.java
Normal file
189
apps/sam/java/src/net/i2p/sam/client/SSLUtil.java
Normal file
@ -0,0 +1,189 @@
|
||||
package net.i2p.sam.client;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.KeyStoreUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
|
||||
/**
|
||||
* Utilities for SAM SSL server sockets.
|
||||
*
|
||||
* @since 0.9.24 copied from net.i2p.sam for testing SSL stream forwarding
|
||||
*/
|
||||
class SSLUtil {
|
||||
|
||||
public static final String DEFAULT_SAMCLIENT_CONFIGFILE = "samclient.config";
|
||||
private static final String PROP_KEYSTORE_PASSWORD = "samclient.keystorePassword";
|
||||
private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
|
||||
private static final String PROP_KEY_PASSWORD = "samclient.keyPassword";
|
||||
private static final String PROP_KEY_ALIAS = "samclient.keyAlias";
|
||||
private static final String ASCII_KEYFILE_SUFFIX = ".local.crt";
|
||||
private static final String PROP_KS_NAME = "samclient.keystoreFile";
|
||||
private static final String KS_DIR = "keystore";
|
||||
private static final String PREFIX = "samclient-";
|
||||
private static final String KS_SUFFIX = ".ks";
|
||||
private static final String CERT_DIR = "certificates/samclient";
|
||||
|
||||
/**
|
||||
* Create a new selfsigned cert and keystore and pubkey cert if they don't exist.
|
||||
* May take a while.
|
||||
*
|
||||
* @param opts in/out, updated if rv is true
|
||||
* @return false if it already exists; if true, caller must save opts
|
||||
* @throws IOException on creation fail
|
||||
*/
|
||||
public static boolean verifyKeyStore(Properties opts) throws IOException {
|
||||
String name = opts.getProperty(PROP_KEY_ALIAS);
|
||||
if (name == null) {
|
||||
name = KeyStoreUtil.randomString();
|
||||
opts.setProperty(PROP_KEY_ALIAS, name);
|
||||
}
|
||||
String ksname = opts.getProperty(PROP_KS_NAME);
|
||||
if (ksname == null) {
|
||||
ksname = PREFIX + name + KS_SUFFIX;
|
||||
opts.setProperty(PROP_KS_NAME, ksname);
|
||||
}
|
||||
File ks = new File(ksname);
|
||||
if (!ks.isAbsolute()) {
|
||||
ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
|
||||
ks = new File(ks, ksname);
|
||||
}
|
||||
if (ks.exists())
|
||||
return false;
|
||||
File dir = ks.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||
if (!sdir.mkdirs())
|
||||
throw new IOException("Unable to create keystore " + ks);
|
||||
}
|
||||
boolean rv = createKeyStore(ks, name, opts);
|
||||
if (!rv)
|
||||
throw new IOException("Unable to create keystore " + ks);
|
||||
|
||||
// Now read it back out of the new keystore and save it in ascii form
|
||||
// where the clients can get to it.
|
||||
// Failure of this part is not fatal.
|
||||
exportCert(ks, name, opts);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call out to keytool to create a new keystore with a keypair in it.
|
||||
*
|
||||
* @param name used in CNAME
|
||||
* @param opts in/out, updated if rv is true, must contain PROP_KEY_ALIAS
|
||||
* @return success, if true, opts will have password properties added to be saved
|
||||
*/
|
||||
private static boolean createKeyStore(File ks, String name, Properties opts) {
|
||||
// make a random 48 character password (30 * 8 / 5)
|
||||
String keyPassword = KeyStoreUtil.randomString();
|
||||
// and one for the cname
|
||||
String cname = name + ".sam.i2p.net";
|
||||
|
||||
String keyName = opts.getProperty(PROP_KEY_ALIAS);
|
||||
boolean success = KeyStoreUtil.createKeys(ks, keyName, cname, "SAM", keyPassword);
|
||||
if (success) {
|
||||
success = ks.exists();
|
||||
if (success) {
|
||||
opts.setProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
opts.setProperty(PROP_KEY_PASSWORD, keyPassword);
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
logAlways("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
|
||||
"The certificate name was generated randomly, and is not associated with your " +
|
||||
"IP address, host name, router identity, or destination keys.");
|
||||
} else {
|
||||
error("Failed to create SAM SSL keystore.\n" +
|
||||
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||
" to " + (new File(I2PAppContext.getGlobalContext().getConfigDir(), DEFAULT_SAMCLIENT_CONFIGFILE)).getAbsolutePath());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the cert back OUT of the keystore and save it as ascii
|
||||
* so the clients can get to it.
|
||||
*
|
||||
* @param name used to generate output file name
|
||||
* @param opts must contain PROP_KEY_ALIAS
|
||||
*/
|
||||
private static void exportCert(File ks, String name, Properties opts) {
|
||||
File sdir = new SecureDirectory(I2PAppContext.getGlobalContext().getConfigDir(), CERT_DIR);
|
||||
if (sdir.exists() || sdir.mkdirs()) {
|
||||
String keyAlias = opts.getProperty(PROP_KEY_ALIAS);
|
||||
String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
File out = new File(sdir, PREFIX + name + ASCII_KEYFILE_SUFFIX);
|
||||
boolean success = KeyStoreUtil.exportCert(ks, ksPass, keyAlias, out);
|
||||
if (!success)
|
||||
error("Error getting SSL cert to save as ASCII");
|
||||
} else {
|
||||
error("Error saving ASCII SSL keys");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SSLContext and sets the socket factory.
|
||||
* No option prefix allowed.
|
||||
*
|
||||
* @throws IOException; GeneralSecurityExceptions are wrapped in IOE for convenience
|
||||
* @return factory, throws on all errors
|
||||
*/
|
||||
public static SSLServerSocketFactory initializeFactory(Properties opts) throws IOException {
|
||||
String ksPass = opts.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
String keyPass = opts.getProperty(PROP_KEY_PASSWORD);
|
||||
if (keyPass == null) {
|
||||
throw new IOException("No key password, set " + PROP_KEY_PASSWORD + " in " +
|
||||
(new File(I2PAppContext.getGlobalContext().getConfigDir(), DEFAULT_SAMCLIENT_CONFIGFILE)).getAbsolutePath());
|
||||
}
|
||||
String ksname = opts.getProperty(PROP_KS_NAME);
|
||||
if (ksname == null) {
|
||||
throw new IOException("No keystore, set " + PROP_KS_NAME + " in " +
|
||||
(new File(I2PAppContext.getGlobalContext().getConfigDir(), DEFAULT_SAMCLIENT_CONFIGFILE)).getAbsolutePath());
|
||||
}
|
||||
File ks = new File(ksname);
|
||||
if (!ks.isAbsolute()) {
|
||||
ks = new File(I2PAppContext.getGlobalContext().getConfigDir(), KS_DIR);
|
||||
ks = new File(ks, ksname);
|
||||
}
|
||||
|
||||
InputStream fis = null;
|
||||
try {
|
||||
SSLContext sslc = SSLContext.getInstance("TLS");
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
fis = new FileInputStream(ks);
|
||||
keyStore.load(fis, ksPass.toCharArray());
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, keyPass.toCharArray());
|
||||
sslc.init(kmf.getKeyManagers(), null, I2PAppContext.getGlobalContext().random());
|
||||
return sslc.getServerSocketFactory();
|
||||
} catch (GeneralSecurityException gse) {
|
||||
IOException ioe = new IOException("keystore error");
|
||||
ioe.initCause(gse);
|
||||
throw ioe;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void error(String s) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).error(s);
|
||||
}
|
||||
|
||||
private static void logAlways(String s) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(SSLUtil.class).logAlways(Log.INFO, s);
|
||||
}
|
||||
}
|
@ -40,12 +40,12 @@ public class TestCreateSessionDatagram {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
|
@ -56,12 +56,12 @@ public class TestCreateSessionRaw {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
//_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=RAW DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK");
|
||||
|
@ -5,6 +5,7 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestCreateSessionStream {
|
||||
@ -40,12 +41,12 @@ public class TestCreateSessionStream {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
|
@ -7,6 +7,7 @@ import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestDatagramTransfer {
|
||||
@ -22,17 +23,17 @@ public class TestDatagramTransfer {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(lookup.getBytes());
|
||||
out.write(DataHelper.getASCII(lookup));
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for ME: " + line);
|
||||
_log.debug("The above should be a NAMING REPLY");
|
||||
@ -50,7 +51,7 @@ public class TestDatagramTransfer {
|
||||
}
|
||||
|
||||
String send = "DATAGRAM SEND DESTINATION=" + value + " SIZE=3\nYo!";
|
||||
out.write(send.getBytes());
|
||||
out.write(DataHelper.getASCII(send));
|
||||
line = reader.readLine();
|
||||
tok = new StringTokenizer(line);
|
||||
maj = tok.nextToken();
|
||||
|
@ -19,17 +19,17 @@ public class TestDest {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=testNaming " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.debug("Response to creating the session with destination testNaming: " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "DEST GENERATE\n";
|
||||
out.write(lookup.getBytes());
|
||||
out.write(DataHelper.getASCII(lookup));
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the dest generate: " + line);
|
||||
_log.debug("The abouve should be a DEST REPLY");
|
||||
|
@ -5,6 +5,7 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TestHello {
|
||||
@ -21,7 +22,7 @@ public class TestHello {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for valid version: " + line);
|
||||
@ -36,7 +37,7 @@ public class TestHello {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=9.0 MAX=8.3\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=9.0 MAX=8.3\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for invalid version: " + line);
|
||||
@ -51,7 +52,7 @@ public class TestHello {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO h0 h0 h0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO h0 h0 h0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.info("line read for valid version: " + line);
|
||||
|
@ -39,17 +39,17 @@ public class TestNaming {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=testNaming " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.debug("Response to creating the session with destination testNaming: " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=" + name + "\n";
|
||||
out.write(lookup.getBytes());
|
||||
out.write(DataHelper.getASCII(lookup));
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for [" + name +"]: " + line);
|
||||
_log.debug("The abouve should be a NAMING REPLY");
|
||||
|
@ -22,17 +22,17 @@ public class TestRawTransfer {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=RAW DESTINATION=" + destName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination " + destName + ": " + line);
|
||||
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
|
||||
String lookup = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(lookup.getBytes());
|
||||
out.write(DataHelper.getASCII(lookup));
|
||||
line = reader.readLine();
|
||||
_log.info("Response from the lookup for ME: " + line);
|
||||
_log.debug("The above should be a NAMING REPLY");
|
||||
@ -50,7 +50,7 @@ public class TestRawTransfer {
|
||||
}
|
||||
|
||||
String send = "RAW SEND DESTINATION=" + value + " SIZE=3\nYo!";
|
||||
out.write(send.getBytes());
|
||||
out.write(DataHelper.getASCII(send));
|
||||
line = reader.readLine();
|
||||
try {
|
||||
tok = new StringTokenizer(line);
|
||||
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -62,17 +63,17 @@ public class TestStreamTransfer {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Alice " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Alice: " + line);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
@ -212,16 +213,16 @@ public class TestStreamTransfer {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + sessionName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination "+ sessionName+": " + line);
|
||||
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
line = reader.readLine();
|
||||
_log.info("Response to the stream connect from "+sessionName+" to Alice: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
@ -238,18 +239,18 @@ public class TestStreamTransfer {
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
|
||||
_log.info("\n** Sending BlahBlah!!");
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
out.flush();
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nFooBarBaz!";
|
||||
_log.info("\n** Sending FooBarBaz!");
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
out.flush();
|
||||
/* Don't delay here, so we can test whether all data is
|
||||
sent even if we do a STREAM CLOSE immediately. */
|
||||
_log.info("Sending close");
|
||||
req = "STREAM CLOSE ID=42\n";
|
||||
out.write(req.getBytes());
|
||||
out.write(DataHelper.getASCII(req));
|
||||
out.flush();
|
||||
synchronized (_counterLock) {
|
||||
_closeCounter++;
|
||||
|
@ -140,7 +140,7 @@ public class TestSwarm {
|
||||
private String handshake() {
|
||||
synchronized (_samOut) {
|
||||
try {
|
||||
_samOut.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
|
||||
_samOut.write(DataHelper.getASCII("HELLO VERSION MIN=1.0 MAX=1.0\n"));
|
||||
_samOut.flush();
|
||||
_log.debug("Hello sent");
|
||||
boolean ok = _eventHandler.waitForHelloReply();
|
||||
@ -148,14 +148,14 @@ public class TestSwarm {
|
||||
if (!ok)
|
||||
throw new IOException("wtf, hello failed?");
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + _destFile + " " + _conOptions + "\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.write(DataHelper.getUTF8(req));
|
||||
_samOut.flush();
|
||||
_log.debug("Session create sent");
|
||||
ok = _eventHandler.waitForSessionCreateReply();
|
||||
_log.debug("Session create reply found: " + ok);
|
||||
|
||||
req = "NAMING LOOKUP NAME=ME\n";
|
||||
_samOut.write(req.getBytes());
|
||||
_samOut.write(DataHelper.getASCII(req));
|
||||
_samOut.flush();
|
||||
_log.debug("Naming lookup sent");
|
||||
String destination = _eventHandler.waitForNamingReply("ME");
|
||||
@ -177,7 +177,7 @@ public class TestSwarm {
|
||||
private boolean writeDest(String dest) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(_destFile);
|
||||
fos.write(dest.getBytes());
|
||||
fos.write(DataHelper.getASCII(dest));
|
||||
fos.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
@ -203,7 +203,7 @@ public class TestSwarm {
|
||||
_remotePeers.put(new Integer(con), flooder);
|
||||
}
|
||||
|
||||
byte msg[] = ("STREAM CONNECT ID=" + con + " DESTINATION=" + remDest + "\n").getBytes();
|
||||
byte msg[] = (DataHelper.getUTF8("STREAM CONNECT ID=" + con + " DESTINATION=" + remDest + "\n"));
|
||||
synchronized (_samOut) {
|
||||
_samOut.write(msg);
|
||||
_samOut.flush();
|
||||
@ -257,7 +257,7 @@ public class TestSwarm {
|
||||
long value = 0;
|
||||
long lastSend = _context.clock().now();
|
||||
while (!_closed) {
|
||||
byte msg[] = ("STREAM SEND ID=" + _connectionId + " SIZE=" + data.length + "\n").getBytes();
|
||||
byte msg[] = (DataHelper.getASCII("STREAM SEND ID=" + _connectionId + " SIZE=" + data.length + "\n"));
|
||||
DataHelper.toLong(data, 0, 4, value);
|
||||
try {
|
||||
synchronized (_samOut) {
|
||||
|
Reference in New Issue
Block a user