initial import of Connelly's public domain I2P python lib
This commit is contained in:
18
apps/sam/python/bugs.txt
Normal file
18
apps/sam/python/bugs.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
Known Bugs:
|
||||||
|
* TunnelServer may crash the I2P router in the following
|
||||||
|
ways when a large file is downloaded:
|
||||||
|
|
||||||
|
* Out of memory exception (for large files)
|
||||||
|
* Mysterious router death with no errors in the router logs
|
||||||
|
(more recently)
|
||||||
|
* BUG! in SAM proxy
|
||||||
|
See http://oregonstate.edu/~barnesc/temp/sam_crash.txt
|
||||||
|
* A small number of datagram packets sent are lost (even in a local
|
||||||
|
loopback). This is apparently a bug in I2P.
|
||||||
|
* Errors raised for sockets are non entirely consistent.
|
||||||
|
See todo.txt for how to fix this.
|
||||||
|
* A session does not close until a program exits.
|
||||||
|
This should be fine once I2P is patched to allow multiple
|
||||||
|
programs to use a single session at once.
|
||||||
|
* i2p.router.start() does not work.
|
33
apps/sam/python/doc/guide/eeproxy.html
Normal file
33
apps/sam/python/doc/guide/eeproxy.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>Eeproxy - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>Eeproxy</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'>
|
||||||
|
The <strong>Eeproxy</strong> is run by the I2P router. The proxy is normally used for web browsers, as a means of accessing eepsites.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The eeproxy is usually available at <a href="http://127.0.0.1:4444/" class='printable' title="http://127.0.0.1:4444/">http://127.0.0.1:4444/</a>.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
</em><!-- Time since request: 0.77 secs. -->
|
||||||
|
</body></html>
|
55
apps/sam/python/doc/guide/i2p.eep.html
Normal file
55
apps/sam/python/doc/guide/i2p.eep.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>User's Guide:i2p.eep - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>User's Guide:i2p.eep</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'>
|
||||||
|
Module <code >i2p.eep</code > allows Python programs to access the <a href="./eeproxy.html" class='printable' title ="Eeproxy">Eeproxy</a>.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
With this module, a program can easily download eepsites.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="Functions"> Functions </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>urlopen</strong>(url, eepaddr='127.0.0.1:4444')
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Like urllib2.urlopen(url), but only works for eep-sites.
|
||||||
|
Example: f = urlopen('<a href="http://duck.i2p/index.html" class='printable' title="http://duck.i2p/index.html">http://duck.i2p/index.html</a>')
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>urlget</strong>(url, eepaddr='127.0.0.1:4444')
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Get contents of an eepsite.
|
||||||
|
Example: urlget('<a href="http://duck.i2p/" class='printable' title="http://duck.i2p/">http://duck.i2p/</a>').
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
|
||||||
|
</em><!-- Time since request: 0.78 secs. -->
|
||||||
|
</body></html>
|
61
apps/sam/python/doc/guide/i2p.html
Normal file
61
apps/sam/python/doc/guide/i2p.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>User's Guide:i2p - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>User's Guide:i2p</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'>
|
||||||
|
Package <code >i2p</code > is a container package for more specific modules.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It exports the following names:
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> <a href="./i2p.sam.html" class='printable' title ="User's Guide:i2p.sam">sam</a>
|
||||||
|
<a href="./i2p.eep.html" class='printable' title ="User's Guide:i2p.eep">eep</a>
|
||||||
|
<a href="./i2p.router.html" class='printable' title ="User's Guide:i2p.router">router</a>
|
||||||
|
<a href="#Error" class='printable' title ="User's Guide:i2p">Error</a>
|
||||||
|
<a href="#RouterError" class='printable' title ="User's Guide:i2p">RouterError</a>
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
class <strong>Error</strong>(Exception):
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Base class for all I2P errors.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
class <strong>RouterError</strong>(Error):
|
||||||
|
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Could not connect to router.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
</em><!-- Time since request: 0.85 secs. -->
|
||||||
|
|
||||||
|
</body></html>
|
87
apps/sam/python/doc/guide/i2p.router.html
Normal file
87
apps/sam/python/doc/guide/i2p.router.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>User's Guide:i2p.router - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>User's Guide:i2p.router</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'>
|
||||||
|
Module <code >i2p.sam</code > allows Python programs to control the I2P router.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="Functions"> Functions </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>check</strong>(dir=None)
|
||||||
|
<ul ><pre>
|
||||||
|
Checks whether a locally installed router is running. Does
|
||||||
|
nothing if successful, otherwise raises i2p.RouterError.
|
||||||
|
|
||||||
|
An I2P installation is located by using find(dir).
|
||||||
|
The router.config file is parsed for 'router.adminPort'.
|
||||||
|
This port is queried to determine whether the router is
|
||||||
|
running.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>find</strong>(dir=None)
|
||||||
|
<ul ><pre>
|
||||||
|
|
||||||
|
Find the absolute path to a locally installed I2P router.
|
||||||
|
|
||||||
|
An I2P installation is located by looking in the
|
||||||
|
environment I2P, then in PATH, then in the dir argument
|
||||||
|
given to the function. It looks for startRouter.sh or
|
||||||
|
startRouter.bat. Raises ValueError if an I2P installation
|
||||||
|
could not be located.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>start</strong>(dir=None, hidden=False)
|
||||||
|
<ul ><pre>
|
||||||
|
Start a locally installed I2P router. Does nothing if
|
||||||
|
the router has already been started.
|
||||||
|
|
||||||
|
An I2P installation is located by using find(dir).
|
||||||
|
|
||||||
|
If hidden is True, do not show a terminal for the router.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>stop</strong>(dir=None, force=False)
|
||||||
|
<ul ><pre>
|
||||||
|
Stop a locally installed I2P router, if it was started by
|
||||||
|
the current Python program. If force is True, stop the
|
||||||
|
router even if it was started by another process. Do nothing
|
||||||
|
if force is False and the router was started by another program.
|
||||||
|
|
||||||
|
The file 'router.config' is located using the same search
|
||||||
|
process used for find(dir). It is parsed for
|
||||||
|
'router.shutdownPassword' and 'router.adminPort'. The
|
||||||
|
router is shut down through the admin port.
|
||||||
|
|
||||||
|
Raises i2p.RouterError if the I2P router is running but cannot
|
||||||
|
be stopped. You must uncomment the
|
||||||
|
'router.shutdownPassword' line for this command to work.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
</em><!-- Time since request: 0.77 secs. -->
|
||||||
|
</body></html>
|
308
apps/sam/python/doc/guide/i2p.sam.html
Normal file
308
apps/sam/python/doc/guide/i2p.sam.html
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>User's Guide:i2p.sam - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>User's Guide:i2p.sam</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'><a name="top"></a>
|
||||||
|
Module <code >i2p.sam</code > allows Python programs to access the <a href="./samproxy.html" class='printable' title ="SAM proxy">SAM proxy</a>.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
With this module, a program can send stream data, datagrams, and raw packets across the I2P network.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<p><table border="0" id="toc"><tr><td align="center">
|
||||||
|
<b>Table of contents</b></td></tr><tr id='tocinside'><td>
|
||||||
|
<div style="margin-bottom:0px;">
|
||||||
|
<A CLASS="internal" HREF="#Sockets">1 Sockets</A><BR>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:0px;">
|
||||||
|
<A CLASS="internal" HREF="#Tunnels">2 Tunnels</A><BR>
|
||||||
|
</div>
|
||||||
|
<div style="margin-left:2em;">
|
||||||
|
<A CLASS="internal" HREF="#Tunnel_Server">2.1 Tunnel Server</A><BR>
|
||||||
|
|
||||||
|
<A CLASS="internal" HREF="#Tunnel_Client">2.2 Tunnel Client</A><BR>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:0px;">
|
||||||
|
<A CLASS="internal" HREF="#Errors">3 Errors</A><BR>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:0px;">
|
||||||
|
<A CLASS="internal" HREF="#Constants">4 Constants</A><BR>
|
||||||
|
</div>
|
||||||
|
</td></tr></table><P>
|
||||||
|
<h2><a name="Sockets"> Sockets </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<strong>socket</strong>(session, type, samaddr='127.0.0.1:7656', **kwargs)
|
||||||
|
<ul ><pre>
|
||||||
|
Create a new socket. Argument session should be a session
|
||||||
|
name -- if the name has not yet been used, an I2P
|
||||||
|
Destination will be created for it, otherwise, the
|
||||||
|
existing Destination will be re-used. An empty session
|
||||||
|
string causes a transient session to be created. Argument
|
||||||
|
type is one of SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.
|
||||||
|
|
||||||
|
I2P configuration keyword arguments:
|
||||||
|
|
||||||
|
* in_depth - depth of incoming tunnel (default 2)
|
||||||
|
* out_depth - depth of outgoing tunnel (default 2)
|
||||||
|
|
||||||
|
A single session may be shared by more than one socket, if
|
||||||
|
the sockets are the same type, and if the sockets are
|
||||||
|
created within the same Python process. The socket
|
||||||
|
objects are multithread-safe.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
a = i2p.socket('Alice', i2p.SOCK_STREAM)
|
||||||
|
b = i2p.socket('Bob', i2p.SOCK_DGRAM,
|
||||||
|
in_depth=2, out_depth=5)
|
||||||
|
|
||||||
|
The created object behaves identically to a socket from
|
||||||
|
module socket, with the following exceptions:
|
||||||
|
|
||||||
|
* I2P Destinations are used as address arguments [1].
|
||||||
|
* bind is a no-op: sockets are always bound.
|
||||||
|
* send* methods send all data and are non-blocking.
|
||||||
|
|
||||||
|
A given session name can only be open in a single Python
|
||||||
|
program at a time. If you need to overcome this
|
||||||
|
limitation, consider patching I2P.
|
||||||
|
|
||||||
|
[1]. Alternatively, a host name can be used as an address.
|
||||||
|
It will be resolved using hosts.txt.
|
||||||
|
|
||||||
|
For details on how to use socket objects, see
|
||||||
|
http://www.python.org/doc/current/lib/socket-objects.html
|
||||||
|
|
||||||
|
See the examples directory for code examples.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>socket()</strong> object properties:
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> dest - Local I2P Destination of socket
|
||||||
|
session - Session name
|
||||||
|
type - Socket type: SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>poll</strong>()
|
||||||
|
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Returns a polling object. Works on SAM sockets and Python sockets.
|
||||||
|
See <a href='http://www.python.org/doc/current/lib/module-select.html' class='printable' title="http://www.python.org/doc/current/lib/module-select.html">select.poll()</a> in the Python library for more information.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>resolve</strong>(host, samaddr='127.0.0.1:7656')
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Resolve I2P host name --> I2P Destination.
|
||||||
|
Returns the same string if host is already a Destination.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>select</strong>(readlist, writelist, errlist, timeout=None)
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Performs a select call. Works on SAM sockets and Python sockets.
|
||||||
|
See <a href='http://www.python.org/doc/current/lib/module-select.html' class='printable' title="http://www.python.org/doc/current/lib/module-select.html">select.select()</a> in the Python library for more information.
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="Tunnels"> Tunnels </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Tunnels allow stream sockets to be joined, so that connections to a listening socket are relayed to one or more sending sockets. This allows an ordinary web server to be exposed as an I2P Destination, or an I2P Destination to be bound as a local port, and so on.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
class <strong>Tunnel</strong>(self, receive, make_send, nconnect=-1, timeout=60.0)
|
||||||
|
<ul ><pre>
|
||||||
|
A Tunnel relays connections from a 'receive' socket to one
|
||||||
|
or more 'send' sockets. The receive socket must be bound
|
||||||
|
and listening. For each incoming connection, a new send
|
||||||
|
socket is created by calling make_send(). Data is then
|
||||||
|
exchanged between the created streams until one socket is
|
||||||
|
closed. nconnect is the maximum number of simultaneous
|
||||||
|
connections (-1 for infinite), and timeout is the time that
|
||||||
|
a single connection can last for (None allows a connection
|
||||||
|
to last forever).
|
||||||
|
|
||||||
|
Sockets must accept stream traffic and support the Python
|
||||||
|
socket interface. A separate daemonic thread is created to
|
||||||
|
manage the tunnel. For high performance, make_send() should
|
||||||
|
make a socket and connect in non-blocking mode (you should
|
||||||
|
catch and discard the sam.BlockError or socket.error due to
|
||||||
|
executing connect on a non-blocking socket).
|
||||||
|
|
||||||
|
Security Note:
|
||||||
|
A firewall is needed to maintain the end user's anonymity.
|
||||||
|
An attacker could keep a tunnel socket open by pinging it
|
||||||
|
regularly. The accepted sockets from 'receive' must prevent
|
||||||
|
this by closing down eventually.
|
||||||
|
|
||||||
|
Socket errors do not cause the Tunnel to shut down.
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>close</strong>()
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> Close all connections made for this tunnel.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h3><a name="Tunnel_Server"> Tunnel Server </a></h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
class <strong>TunnelServer</strong>(session, port, samaddr='127.0.0.1:7656', nconnect=-1, timeout=None, **kwargs)
|
||||||
|
<ul ><pre>
|
||||||
|
Tunnels incoming SAM streams --> localhost:port.
|
||||||
|
|
||||||
|
nconnect and timeout are the maximum number of connections
|
||||||
|
and maximum time per connection. All other arguments are
|
||||||
|
passed to sam.socket(). This call blocks until the tunnel
|
||||||
|
is ready.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>TunnelServer</strong> properties:
|
||||||
|
|
||||||
|
<ul ><pre>
|
||||||
|
dest - I2P Destination of server.
|
||||||
|
session - Session name for server.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h3><a name="Tunnel_Client"> Tunnel Client </a></h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
class <strong>TunnelClient</strong>(session, port, dest, samaddr='127.0.0.1:7656', nconnect=-1, timeout=None, **kwargs)
|
||||||
|
<ul ><pre>
|
||||||
|
|
||||||
|
Derived from Tunnel.
|
||||||
|
Tunnels localhost:port --> I2P Destination dest.
|
||||||
|
|
||||||
|
A session named 'session' is created locally, for purposes
|
||||||
|
of routing to 'dest'. nconnect and timeout are the maximum
|
||||||
|
number of connections and maximum time per connection. All
|
||||||
|
other arguments are passed to sam.socket(). This call blocks
|
||||||
|
until the tunnel is ready.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>TunnelClient</strong> properties:
|
||||||
|
<ul ><pre>
|
||||||
|
dest - Local Destination used for routing.
|
||||||
|
remotedest - Remote Destination.
|
||||||
|
session - Session name for local Destination.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="Errors"> Errors </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
class <strong>Error</strong>(i2p.Error)
|
||||||
|
<ul ><pre>
|
||||||
|
Base class for all SAM errors.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
class <strong>BlockError</strong>(Error)
|
||||||
|
|
||||||
|
<ul ><pre>
|
||||||
|
Socket call would have blocked.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
class <strong>ClosedError</strong>(Error)
|
||||||
|
<ul ><pre>
|
||||||
|
A command was used on a socket that closed gracefully.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
class <strong>NetworkError</strong>(Error)
|
||||||
|
<ul ><pre>
|
||||||
|
|
||||||
|
Network error occurred within I2P.
|
||||||
|
|
||||||
|
The error object is a 2-tuple: (errtag, errdesc).
|
||||||
|
errtag is a SAM error string,
|
||||||
|
errdesc is a human readable error description.
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="Constants"> Constants </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Socket types</strong>
|
||||||
|
<ul ><pre>
|
||||||
|
SOCK_STREAM = 1
|
||||||
|
SOCK_DGRAM = 2
|
||||||
|
SOCK_RAW = 3
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>Packet sizes</strong>
|
||||||
|
<ul ><pre>
|
||||||
|
MAX_DGRAM = 31744 # Maximum size for datagram packet
|
||||||
|
MAX_RAW = 32768 # Maximum size for raw packet
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>Flags for recv()</strong>
|
||||||
|
<ul ><pre>
|
||||||
|
MSG_DONTWAIT = 128 # Don't block
|
||||||
|
MSG_PEEK = 2 # Peek at incoming data
|
||||||
|
MSG_WAITALL = 64 # Wait for all data or error
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
<strong>Polling flags</strong>
|
||||||
|
|
||||||
|
<ul ><pre>
|
||||||
|
POLLIN = 1
|
||||||
|
POLLOUT = 4
|
||||||
|
POLLERR = 8
|
||||||
|
POLLHUP = 16
|
||||||
|
POLLNVAL = 32
|
||||||
|
POLLPRI = 1
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
</em><!-- Time since request: 0.86 secs. -->
|
||||||
|
</body></html>
|
76
apps/sam/python/doc/guide/index.html
Normal file
76
apps/sam/python/doc/guide/index.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>Main Page - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>Main Page</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'>
|
||||||
|
<strong>Python-I2P</strong> is a Python interface to <a href='http://www.i2p.net' class='printable' title="http://www.i2p.net">I2P</a>.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="Quick_Start"> Quick Start </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Install:
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<ul ><pre>
|
||||||
|
python setup.py install
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Use:
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<ul ><pre>
|
||||||
|
>>> from i2p import sam
|
||||||
|
>>> s = sam.socket('Alice', sam.SOCK_STREAM)
|
||||||
|
>>> s.connect('duck.i2p')
|
||||||
|
>>> s.send('GET / HTTP/1.0\r\n\r\n')
|
||||||
|
>>> s.recv(1000)
|
||||||
|
(HTTP response from duck.i2p)
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h2><a name="User's_Guide"> User's Guide </a></h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The following modules are available:
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<ul >
|
||||||
|
|
||||||
|
<pre> <a href="./i2p.html" class='printable' title ="User's Guide:i2p">i2p</a> (Container package)
|
||||||
|
<a href="./i2p.sam.html" class='printable' title ="User's Guide:i2p.sam">i2p.sam</a> (Send and receive across the I2P network)
|
||||||
|
<a href="./i2p.eep.html" class='printable' title ="User's Guide:i2p.eep">i2p.eep</a> (Retrieve eepsites)
|
||||||
|
<a href="./i2p.router.html" class='printable' title ="User's Guide:i2p.router">i2p.router</a> (Manage the I2P router)
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
</em><!-- Time since request: 0.78 secs. -->
|
||||||
|
</body></html>
|
37
apps/sam/python/doc/guide/samproxy.html
Normal file
37
apps/sam/python/doc/guide/samproxy.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en"><head><title>SAM proxy - Wikipedia</title>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="robots" content="index,follow">
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico">
|
||||||
|
<script type="text/javascript" src="/~barnesc/wiki/stylesheets/wikibits.js"></script>
|
||||||
|
<style type='text/css'><!--
|
||||||
|
@import url("/~barnesc/wiki/stylesheets/wikiprintable.css");
|
||||||
|
/*/*/
|
||||||
|
a.new, #quickbar a.new { color: #CC2200; }
|
||||||
|
#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }
|
||||||
|
#article { margin-left: 152px; margin-right: 4px; }
|
||||||
|
/* */
|
||||||
|
//--></style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body bgcolor='#FFFFFF' onload=''>
|
||||||
|
<h1 class='pagetitle'>SAM proxy</h1><p class='subtitle'>From Python-I2P.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='bodytext'>
|
||||||
|
A <strong>SAM proxy</strong> is run by the I2P router. The proxy allows streams, datagrams, and raw packets to be sent through the I2P network. A client application uses a telnet-like session to communicate with the proxy. In this way, the core features of the I2P library can be used by any language.
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The protocol used for SAM is described in <a href='http://dev.i2p.net/pipermail/i2p/2004-July/000353.html' class='printable' title="http://dev.i2p.net/pipermail/i2p/2004-July/000353.html">SAM 1.0</a>.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In practice, a <em>SAM library</em> is usually written, so that client applications do not need to use the low-level SAM commands.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</div>
|
||||||
|
<p><em>
|
||||||
|
|
||||||
|
</em><!-- Time since request: 0.77 secs. -->
|
||||||
|
</body></html>
|
14
apps/sam/python/doc/index.html
Normal file
14
apps/sam/python/doc/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<html>
|
||||||
|
<body bgcolor=#ffffff text=#000000 link=#0000ff alink=#0000ff vlink=#0000ff>
|
||||||
|
|
||||||
|
<h1>Python-I2P</h1>
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="./guide/index.html">User's Guide</a><br>
|
||||||
|
<li><a href="./pydoc/i2p.html">Pydoc Documentation</a><br>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
40
apps/sam/python/doc/pydoc/i2p.eep.html
Normal file
40
apps/sam/python/doc/pydoc/i2p.eep.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||||
|
<html><head><title>Python: module i2p.eep</title>
|
||||||
|
</head><body bgcolor="#f0f0f8">
|
||||||
|
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||||
|
<tr bgcolor="#7799ee">
|
||||||
|
<td valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong><a href="i2p.html"><font color="#ffffff">i2p</font></a>.eep</strong></big></big></font></td
|
||||||
|
><td align=right valign=bottom
|
||||||
|
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///D|/code/i2p/i2p/eep.py">d:\code\i2p\i2p\eep.py</a></font></td></tr></table>
|
||||||
|
<p><tt>Eeproxy Python API</tt></p>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#aa55cc">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#fffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="urllib2.html">urllib2</a><br>
|
||||||
|
</td><td width="25%" valign=top></td><td width="25%" valign=top></td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#eeaa77">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#eeaa77"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><dl><dt><a name="-urlget"><strong>urlget</strong></a>(url, eepaddr<font color="#909090">='127.0.0.1:4444'</font>)</dt><dd><tt>Get contents of an eepsite.<br>
|
||||||
|
Example: <a href="#-urlget">urlget</a>('<a href="http://duck.i2p/">http://duck.i2p/</a>').</tt></dd></dl>
|
||||||
|
<dl><dt><a name="-urlopen"><strong>urlopen</strong></a>(url, eepaddr<font color="#909090">='127.0.0.1:4444'</font>)</dt><dd><tt>Like urllib2.<a href="#-urlopen">urlopen</a>(url), but only works for eep-sites.<br>
|
||||||
|
Example: f = <a href="#-urlopen">urlopen</a>('<a href="http://duck.i2p/index.html">http://duck.i2p/index.html</a>')</tt></dd></dl>
|
||||||
|
</td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#55aa55">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><strong>eepaddr</strong> = '127.0.0.1:4444'</td></tr></table>
|
||||||
|
</body></html>
|
90
apps/sam/python/doc/pydoc/i2p.html
Normal file
90
apps/sam/python/doc/pydoc/i2p.html
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||||
|
<html><head><title>Python: package i2p</title>
|
||||||
|
</head><body bgcolor="#f0f0f8">
|
||||||
|
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||||
|
<tr bgcolor="#7799ee">
|
||||||
|
<td valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong>i2p</strong></big></big></font></td
|
||||||
|
><td align=right valign=bottom
|
||||||
|
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///D|/code/i2p/i2p/__init__.py">d:\code\i2p\i2p\__init__.py</a></font></td></tr></table>
|
||||||
|
<p><tt>i2p -- I2P Python interface</tt></p>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#aa55cc">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="i2p.eep.html">eep</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="i2p.router.html">router</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="i2p.sam.html">sam</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="i2p.samclasses.html">samclasses</a><br>
|
||||||
|
</td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ee77aa">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#ee77aa"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="exceptions.html#Exception">exceptions.Exception</a>
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.html#Error">Error</a>
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.html#RouterError">RouterError</a>
|
||||||
|
</font></dt></dl>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Error">class <strong>Error</strong></a>(<a href="exceptions.html#Exception">exceptions.Exception</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Base class for all I2P errors.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="Error-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Error-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Error-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="RouterError">class <strong>RouterError</strong></a>(<a href="i2p.html#Error">Error</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Could not connect to router.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%"><dl><dt>Method resolution order:</dt>
|
||||||
|
<dd><a href="i2p.html#RouterError">RouterError</a></dd>
|
||||||
|
<dd><a href="i2p.html#Error">Error</a></dd>
|
||||||
|
<dd><a href="exceptions.html#Exception">exceptions.Exception</a></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="RouterError-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="RouterError-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="RouterError-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#55aa55">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><strong>__all__</strong> = ['Error', 'RouterError', 'sam', 'eep', 'router']</td></tr></table>
|
||||||
|
</body></html>
|
38
apps/sam/python/doc/pydoc/i2p.router.html
Normal file
38
apps/sam/python/doc/pydoc/i2p.router.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||||
|
<html><head><title>Python: module i2p.router</title>
|
||||||
|
</head><body bgcolor="#f0f0f8">
|
||||||
|
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||||
|
<tr bgcolor="#7799ee">
|
||||||
|
<td valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong><a href="i2p.html"><font color="#ffffff">i2p</font></a>.router</strong></big></big></font></td
|
||||||
|
><td align=right valign=bottom
|
||||||
|
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///D|/code/i2p/i2p/router.py">d:\code\i2p\i2p\router.py</a></font></td></tr></table>
|
||||||
|
<p><tt>Router Control API for Python</tt></p>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#aa55cc">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#fffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="i2p.html">i2p</a><br>
|
||||||
|
<a href="os.html">os</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="socket.html">socket</a><br>
|
||||||
|
<a href="sys.html">sys</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="threading.html">threading</a><br>
|
||||||
|
<a href="time.html">time</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="urllib2.html">urllib2</a><br>
|
||||||
|
</td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#55aa55">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><strong>check_addrlist</strong> = ['127.0.0.1:7656', '127.0.0.1:4444']<br>
|
||||||
|
<strong>our_router</strong> = False<br>
|
||||||
|
<strong>our_router_lock</strong> = <thread.lock object at 0x008AD0F0><br>
|
||||||
|
<strong>router_config</strong> = 'router.config'</td></tr></table>
|
||||||
|
</body></html>
|
374
apps/sam/python/doc/pydoc/i2p.sam.html
Normal file
374
apps/sam/python/doc/pydoc/i2p.sam.html
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
|
||||||
|
<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||||
|
<html><head><title>Python: module i2p.sam</title>
|
||||||
|
</head><body bgcolor="#f0f0f8">
|
||||||
|
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||||
|
<tr bgcolor="#7799ee">
|
||||||
|
<td valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong><a href="i2p.html"><font color="#ffffff">i2p</font></a>.sam</strong></big></big></font></td
|
||||||
|
><td align=right valign=bottom
|
||||||
|
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///D|/code/i2p/i2p/sam.py">d:\code\i2p\i2p\sam.py</a></font></td></tr></table>
|
||||||
|
<p><tt>SAM Python API</tt></p>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#aa55cc">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#fffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="Queue.html">Queue</a><br>
|
||||||
|
<a href="copy.html">copy</a><br>
|
||||||
|
<a href="i2p.html">i2p</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="select.html">select</a><br>
|
||||||
|
<a href="socket.html">socket</a><br>
|
||||||
|
<a href="i2p.samclasses.html">i2p.samclasses</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="thread.html">thread</a><br>
|
||||||
|
<a href="threading.html">threading</a><br>
|
||||||
|
<a href="time.html">time</a><br>
|
||||||
|
</td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ee77aa">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#ee77aa"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.html#Error">i2p.Error</a>(<a href="exceptions.html#Exception">exceptions.Exception</a>)
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.sam.html#Error">Error</a>
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.sam.html#BlockError">BlockError</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.sam.html#ClosedError">ClosedError</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.sam.html#NetworkError">NetworkError</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.sam.html#Timeout">Timeout</a>
|
||||||
|
</font></dt></dl>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</dd>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.sam.html#Poll">Poll</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.sam.html#Socket">Socket</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.sam.html#Tunnel">Tunnel</a>
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.sam.html#TunnelClient">TunnelClient</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.sam.html#TunnelServer">TunnelServer</a>
|
||||||
|
</font></dt></dl>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="BlockError">class <strong>BlockError</strong></a>(<a href="i2p.sam.html#Error">Error</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt><a href="#Socket">Socket</a> call would have blocked.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%"><dl><dt>Method resolution order:</dt>
|
||||||
|
<dd><a href="i2p.sam.html#BlockError">BlockError</a></dd>
|
||||||
|
<dd><a href="i2p.sam.html#Error">Error</a></dd>
|
||||||
|
<dd><a href="i2p.html#Error">i2p.Error</a></dd>
|
||||||
|
<dd><a href="exceptions.html#Exception">exceptions.Exception</a></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="BlockError-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="BlockError-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="BlockError-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="ClosedError">class <strong>ClosedError</strong></a>(<a href="i2p.sam.html#Error">Error</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>A command was used on a socket that closed gracefully.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%"><dl><dt>Method resolution order:</dt>
|
||||||
|
<dd><a href="i2p.sam.html#ClosedError">ClosedError</a></dd>
|
||||||
|
<dd><a href="i2p.sam.html#Error">Error</a></dd>
|
||||||
|
<dd><a href="i2p.html#Error">i2p.Error</a></dd>
|
||||||
|
<dd><a href="exceptions.html#Exception">exceptions.Exception</a></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="ClosedError-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="ClosedError-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="ClosedError-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Error">class <strong>Error</strong></a>(<a href="i2p.html#Error">i2p.Error</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Base class for all SAM errors.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%"><dl><dt>Method resolution order:</dt>
|
||||||
|
<dd><a href="i2p.sam.html#Error">Error</a></dd>
|
||||||
|
<dd><a href="i2p.html#Error">i2p.Error</a></dd>
|
||||||
|
<dd><a href="exceptions.html#Exception">exceptions.Exception</a></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="Error-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Error-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Error-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="NetworkError">class <strong>NetworkError</strong></a>(<a href="i2p.sam.html#Error">Error</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Network error occurred within I2P.<br>
|
||||||
|
The error object is a 2-tuple: (errtag, errdesc).<br>
|
||||||
|
errtag is a SAM error string,<br>
|
||||||
|
errdesc is a human readable error description.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%"><dl><dt>Method resolution order:</dt>
|
||||||
|
<dd><a href="i2p.sam.html#NetworkError">NetworkError</a></dd>
|
||||||
|
<dd><a href="i2p.sam.html#Error">Error</a></dd>
|
||||||
|
<dd><a href="i2p.html#Error">i2p.Error</a></dd>
|
||||||
|
<dd><a href="exceptions.html#Exception">exceptions.Exception</a></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="NetworkError-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="NetworkError-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="NetworkError-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Poll">class <strong>Poll</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Class implementing poll interface. Works for Python sockets<br>
|
||||||
|
and SAM sockets.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="Poll-__init__"><strong>__init__</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Poll-poll"><strong>poll</strong></a>(self, timeout<font color="#909090">=None</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Poll-register"><strong>register</strong></a>(self, fd, eventmask<font color="#909090">=13</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Poll-unregister"><strong>unregister</strong></a>(self, fd)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Socket">class <strong>Socket</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>A socket object.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="Socket-__deepcopy__"><strong>__deepcopy__</strong></a>(self, memo)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-__init__"><strong>__init__</strong></a>(self, session, type, samaddr<font color="#909090">='127.0.0.1:7656'</font>, **kwargs)</dt><dd><tt>Equivalent to socket().</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-accept"><strong>accept</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-bind"><strong>bind</strong></a>(self, address)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-close"><strong>close</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-connect"><strong>connect</strong></a>(self, address)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-connect_ex"><strong>connect_ex</strong></a>(self, address)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-getpeername"><strong>getpeername</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-getsockname"><strong>getsockname</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-gettimeout"><strong>gettimeout</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-listen"><strong>listen</strong></a>(self, backlog)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-makefile"><strong>makefile</strong></a>(self, mode<font color="#909090">='r'</font>, bufsize<font color="#909090">=-1</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-recv"><strong>recv</strong></a>(self, bufsize, flags<font color="#909090">=0</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-recvfrom"><strong>recvfrom</strong></a>(self, bufsize, flags<font color="#909090">=0</font>)</dt><dd><tt>For a datagram or raw socket, bufsize = -1 indicates that the<br>
|
||||||
|
entire packet should be retrieved.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-send"><strong>send</strong></a>(self, string, flags<font color="#909090">=0</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-sendall"><strong>sendall</strong></a>(self, string, flags<font color="#909090">=0</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-sendto"><strong>sendto</strong></a>(self, string, flags, address)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-setblocking"><strong>setblocking</strong></a>(self, flag)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Socket-settimeout"><strong>settimeout</strong></a>(self, value)</dt></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Properties defined here:<br>
|
||||||
|
<dl><dt><strong>dest</strong></dt>
|
||||||
|
<dd><tt>Local I2P Destination of socket</tt></dd>
|
||||||
|
</dl>
|
||||||
|
<dl><dt><strong>session</strong></dt>
|
||||||
|
<dd><tt>Session name</tt></dd>
|
||||||
|
</dl>
|
||||||
|
<dl><dt><strong>type</strong></dt>
|
||||||
|
<dd><tt><a href="#Socket">Socket</a> type: SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.</tt></dd>
|
||||||
|
</dl>
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Timeout">class <strong>Timeout</strong></a>(<a href="i2p.sam.html#Error">Error</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Time out occurred for a socket which had timeouts enabled<br>
|
||||||
|
via a prior call to settimeout().<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%"><dl><dt>Method resolution order:</dt>
|
||||||
|
<dd><a href="i2p.sam.html#Timeout">Timeout</a></dd>
|
||||||
|
<dd><a href="i2p.sam.html#Error">Error</a></dd>
|
||||||
|
<dd><a href="i2p.html#Error">i2p.Error</a></dd>
|
||||||
|
<dd><a href="exceptions.html#Exception">exceptions.Exception</a></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="exceptions.html#Exception">exceptions.Exception</a>:<br>
|
||||||
|
<dl><dt><a name="Timeout-__getitem__"><strong>__getitem__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Timeout-__init__"><strong>__init__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Timeout-__str__"><strong>__str__</strong></a>(...)</dt></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Tunnel">class <strong>Tunnel</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#ffc8d8"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="Tunnel-__init__"><strong>__init__</strong></a>(self, receive, make_send, nconnect<font color="#909090">=-1</font>, timeout<font color="#909090">=60.0</font>)</dt><dd><tt>A <a href="#Tunnel">Tunnel</a> relays connections from a 'receive' socket to one<br>
|
||||||
|
or more 'send' sockets. The receive socket must be bound<br>
|
||||||
|
and listening. For each incoming connection, a new send<br>
|
||||||
|
socket is created by calling make_send(). Data is then<br>
|
||||||
|
exchanged between the created streams until one socket is<br>
|
||||||
|
closed. nconnect is the maximum number of simultaneous<br>
|
||||||
|
connections (-1 for infinite), and timeout is the time that<br>
|
||||||
|
a single connection can last for (None allows a connection<br>
|
||||||
|
to last forever).<br>
|
||||||
|
<br>
|
||||||
|
Sockets must accept stream traffic and support the Python<br>
|
||||||
|
socket interface. A separate daemonic thread is created to<br>
|
||||||
|
manage the tunnel. For high performance, make_send() should<br>
|
||||||
|
make a socket and connect in non-blocking mode (you should<br>
|
||||||
|
catch and discard the sam.<a href="#BlockError">BlockError</a> or socket.error due to<br>
|
||||||
|
executing connect on a non-blocking socket).<br>
|
||||||
|
<br>
|
||||||
|
Security Note:<br>
|
||||||
|
A firewall is needed to maintain the end user's anonymity.<br>
|
||||||
|
An attacker could keep a tunnel socket open by pinging it<br>
|
||||||
|
regularly. The accepted sockets from 'receive' must prevent<br>
|
||||||
|
this by closing down eventually.<br>
|
||||||
|
<br>
|
||||||
|
<a href="#Socket">Socket</a> errors do not cause the <a href="#Tunnel">Tunnel</a> to shut down.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Tunnel-close"><strong>close</strong></a>(self)</dt><dd><tt>Close all connections made for this tunnel.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="TunnelClient">class <strong>TunnelClient</strong></a>(<a href="i2p.sam.html#Tunnel">Tunnel</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#ffc8d8"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="TunnelClient-__init__"><strong>__init__</strong></a>(self, session, port, dest, samaddr<font color="#909090">='127.0.0.1:7656'</font>, nconnect<font color="#909090">=-1</font>, timeout<font color="#909090">=None</font>, **kwargs)</dt><dd><tt>Tunnels localhost:port --> I2P Destination dest.<br>
|
||||||
|
<br>
|
||||||
|
A session named 'session' is created locally, for purposes<br>
|
||||||
|
of routing to 'dest'. nconnect and timeout are the maximum<br>
|
||||||
|
number of connections and maximum time per connection. All<br>
|
||||||
|
other arguments are passed to sam.socket(). This call blocks<br>
|
||||||
|
until the tunnel is ready.</tt></dd></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Properties defined here:<br>
|
||||||
|
<dl><dt><strong>dest</strong></dt>
|
||||||
|
<dd><strong><em>get</em></strong> = 'Local Destination used for routing.'</dd>
|
||||||
|
</dl>
|
||||||
|
<dl><dt><strong>remotedest</strong></dt>
|
||||||
|
<dd><tt>Remote Destination.</tt></dd>
|
||||||
|
</dl>
|
||||||
|
<dl><dt><strong>session</strong></dt>
|
||||||
|
<dd><strong><em>get</em></strong> = 'Session name for local Destination.'</dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="i2p.sam.html#Tunnel">Tunnel</a>:<br>
|
||||||
|
<dl><dt><a name="TunnelClient-close"><strong>close</strong></a>(self)</dt><dd><tt>Close all connections made for this tunnel.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="TunnelServer">class <strong>TunnelServer</strong></a>(<a href="i2p.sam.html#Tunnel">Tunnel</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#ffc8d8"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="TunnelServer-__init__"><strong>__init__</strong></a>(self, session, port, samaddr<font color="#909090">='127.0.0.1:7656'</font>, nconnect<font color="#909090">=-1</font>, timeout<font color="#909090">=None</font>, **kwargs)</dt><dd><tt>Tunnels incoming SAM streams --> localhost:port.<br>
|
||||||
|
<br>
|
||||||
|
nconnect and timeout are the maximum number of connections<br>
|
||||||
|
and maximum time per connection. All other arguments are<br>
|
||||||
|
passed to sam.socket(). This call blocks until the tunnel<br>
|
||||||
|
is ready.</tt></dd></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Properties defined here:<br>
|
||||||
|
<dl><dt><strong>dest</strong></dt>
|
||||||
|
<dd><tt>I2P Destination of server.</tt></dd>
|
||||||
|
</dl>
|
||||||
|
<dl><dt><strong>session</strong></dt>
|
||||||
|
<dd><tt>Session name for server.</tt></dd>
|
||||||
|
</dl>
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="i2p.sam.html#Tunnel">Tunnel</a>:<br>
|
||||||
|
<dl><dt><a name="TunnelServer-close"><strong>close</strong></a>(self)</dt><dd><tt>Close all connections made for this tunnel.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#55aa55">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><strong>MAX_DGRAM</strong> = 31744<br>
|
||||||
|
<strong>MAX_RAW</strong> = 32768<br>
|
||||||
|
<strong>MSG_DONTWAIT</strong> = 128<br>
|
||||||
|
<strong>MSG_PEEK</strong> = 2<br>
|
||||||
|
<strong>MSG_WAITALL</strong> = 64<br>
|
||||||
|
<strong>POLLERR</strong> = 8<br>
|
||||||
|
<strong>POLLHUP</strong> = 16<br>
|
||||||
|
<strong>POLLIN</strong> = 1<br>
|
||||||
|
<strong>POLLNVAL</strong> = 32<br>
|
||||||
|
<strong>POLLOUT</strong> = 4<br>
|
||||||
|
<strong>POLLPRI</strong> = 1<br>
|
||||||
|
<strong>SOCK_DGRAM</strong> = 2<br>
|
||||||
|
<strong>SOCK_RAW</strong> = 3<br>
|
||||||
|
<strong>SOCK_STREAM</strong> = 1<br>
|
||||||
|
<strong>samaddr</strong> = '127.0.0.1:7656'<br>
|
||||||
|
<strong>samver</strong> = 1.0</td></tr></table>
|
||||||
|
</body></html>
|
314
apps/sam/python/doc/pydoc/i2p.samclasses.html
Normal file
314
apps/sam/python/doc/pydoc/i2p.samclasses.html
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
|
||||||
|
<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||||
|
<html><head><title>Python: module i2p.samclasses</title>
|
||||||
|
</head><body bgcolor="#f0f0f8">
|
||||||
|
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||||
|
<tr bgcolor="#7799ee">
|
||||||
|
<td valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong><a href="i2p.html"><font color="#ffffff">i2p</font></a>.samclasses</strong></big></big></font></td
|
||||||
|
><td align=right valign=bottom
|
||||||
|
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:///D|/code/i2p/i2p/samclasses.py">d:\code\i2p\i2p\samclasses.py</a></font></td></tr></table>
|
||||||
|
<p><tt>Lower-level SAM API, interfaces with SAM Bridge.<br>
|
||||||
|
<br>
|
||||||
|
For internal use only.<br>
|
||||||
|
<br>
|
||||||
|
Use the higher level i2p.sam module for your own programs.<br>
|
||||||
|
<br>
|
||||||
|
For details on SAM, see "Simple Anonymous Messaging (SAM) v1.0,"<br>
|
||||||
|
as published by jrandom.<br>
|
||||||
|
<br>
|
||||||
|
Class Overview:<br>
|
||||||
|
<br>
|
||||||
|
<a href="#SAMTerminal">SAMTerminal</a>: Message sender/reader, talks to SAM Bridge<br>
|
||||||
|
through a single socket.<br>
|
||||||
|
<a href="#StringBuffer">StringBuffer</a>: Queue for character data.<br>
|
||||||
|
<a href="#BaseSession">BaseSession</a>: SAM session classes are derived from this.<br>
|
||||||
|
<a href="#StreamSession">StreamSession</a>: Manipulate a SAM stream session through a<br>
|
||||||
|
threadsafe, high-level interface.<br>
|
||||||
|
<a href="#DatagramSession">DatagramSession</a>: SAM datagram session, threadsafe, high level.<br>
|
||||||
|
<a href="#RawSession">RawSession</a>: SAM raw session, threadsafe, high level.<br>
|
||||||
|
<br>
|
||||||
|
Note that a 'None' timeout is an infinite timeout: it<br>
|
||||||
|
blocks forever if necessary.<br>
|
||||||
|
<br>
|
||||||
|
Todo:<br>
|
||||||
|
* Error handling is a huge mess. Neaten it up.<br>
|
||||||
|
Subclass a ErrorMixin class, then use set_error(e),<br>
|
||||||
|
check_error(), get_error().<br>
|
||||||
|
* Streams are a huge mess. Neaten them up.<br>
|
||||||
|
* This whole interface is a tad confusing. Neaten it up.</tt></p>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#aa55cc">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#fffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="Queue.html">Queue</a><br>
|
||||||
|
<a href="i2p.html">i2p</a><br>
|
||||||
|
<a href="random.html">random</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="shlex.html">shlex</a><br>
|
||||||
|
<a href="socket.html">socket</a><br>
|
||||||
|
<a href="string.html">string</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="sys.html">sys</a><br>
|
||||||
|
<a href="thread.html">thread</a><br>
|
||||||
|
<a href="threading.html">threading</a><br>
|
||||||
|
</td><td width="25%" valign=top><a href="time.html">time</a><br>
|
||||||
|
<a href="traceback.html">traceback</a><br>
|
||||||
|
</td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ee77aa">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#ee77aa"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.samclasses.html#BaseSession">BaseSession</a>
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.samclasses.html#DatagramSession">DatagramSession</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.samclasses.html#RawSession">RawSession</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.samclasses.html#StreamSession">StreamSession</a>
|
||||||
|
</font></dt></dl>
|
||||||
|
</dd>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.samclasses.html#Deque">Deque</a>
|
||||||
|
</font></dt><dd>
|
||||||
|
<dl>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.samclasses.html#StringBuffer">StringBuffer</a>
|
||||||
|
</font></dt></dl>
|
||||||
|
</dd>
|
||||||
|
<dt><font face="helvetica, arial"><a href="i2p.samclasses.html#SAMTerminal">SAMTerminal</a>
|
||||||
|
</font></dt><dt><font face="helvetica, arial"><a href="i2p.samclasses.html#Stream">Stream</a>
|
||||||
|
</font></dt></dl>
|
||||||
|
<p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="BaseSession">class <strong>BaseSession</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Base session, from which <a href="#StreamSession">StreamSession</a>, <a href="#DatagramSession">DatagramSession</a>,<br>
|
||||||
|
and <a href="#RawSession">RawSession</a> are derived.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="BaseSession-__init__"><strong>__init__</strong></a>(self, addr<font color="#909090">=''</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="BaseSession-close"><strong>close</strong></a>(self)</dt><dd><tt>Close the session.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="DatagramSession">class <strong>DatagramSession</strong></a>(<a href="i2p.samclasses.html#BaseSession">BaseSession</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Datagram session. All methods are blocking and threadsafe.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="DatagramSession-__init__"><strong>__init__</strong></a>(self, name, addr<font color="#909090">=''</font>, **kwargs)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="DatagramSession-__len__"><strong>__len__</strong></a>(self)</dt><dd><tt>Number of packets in read buffer.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="DatagramSession-recv"><strong>recv</strong></a>(self, timeout<font color="#909090">=None</font>, peek<font color="#909090">=False</font>)</dt><dd><tt>Get a single packet. Blocks for up to timeout seconds if<br>
|
||||||
|
n > 0 and no packet is available (timeout=None means wait<br>
|
||||||
|
forever). If still no packet is available, raises BlockError<br>
|
||||||
|
or Timeout. Returns the pair (data, address). If peek is<br>
|
||||||
|
True, the data is not removed.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="DatagramSession-send"><strong>send</strong></a>(self, s, dest)</dt><dd><tt>Send packet with contents s to given destination.</tt></dd></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="i2p.samclasses.html#BaseSession">BaseSession</a>:<br>
|
||||||
|
<dl><dt><a name="DatagramSession-close"><strong>close</strong></a>(self)</dt><dd><tt>Close the session.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Deque">class <strong>Deque</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>A double-ended queue.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="Deque-__init__"><strong>__init__</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Deque-__len__"><strong>__len__</strong></a>(self)</dt><dd><tt>Number of items in the deque.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Deque-pop_first"><strong>pop_first</strong></a>(self)</dt><dd><tt>Pop an item off the beginning of the deque, and return it.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Deque-pop_last"><strong>pop_last</strong></a>(self)</dt><dd><tt>Pop an item off the end of the deque, and return it.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Deque-push_first"><strong>push_first</strong></a>(self, obj)</dt><dd><tt>Prepend obj to the beginning of the deque.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Deque-push_last"><strong>push_last</strong></a>(self, obj)</dt><dd><tt>Append obj to the end of the deque.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="RawSession">class <strong>RawSession</strong></a>(<a href="i2p.samclasses.html#BaseSession">BaseSession</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Raw session. All methods are blocking and threadsafe.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="RawSession-__init__"><strong>__init__</strong></a>(self, name, addr<font color="#909090">=''</font>, **kwargs)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="RawSession-__len__"><strong>__len__</strong></a>(self)</dt><dd><tt>Number of packets in read buffer.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="RawSession-recv"><strong>recv</strong></a>(self, timeout<font color="#909090">=None</font>, peek<font color="#909090">=False</font>)</dt><dd><tt>Identical to DatagramSocket.recv. The from address is an<br>
|
||||||
|
empty string.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="RawSession-send"><strong>send</strong></a>(self, s, dest)</dt><dd><tt>Send packet with contents s to given destination.</tt></dd></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="i2p.samclasses.html#BaseSession">BaseSession</a>:<br>
|
||||||
|
<dl><dt><a name="RawSession-close"><strong>close</strong></a>(self)</dt><dd><tt>Close the session.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="SAMTerminal">class <strong>SAMTerminal</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Message-by-message communication with SAM through a single<br>
|
||||||
|
socket. _on_* messages are dispatched to msgobj.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="SAMTerminal-__init__"><strong>__init__</strong></a>(self, addr, msgobj)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="SAMTerminal-check"><strong>check</strong></a>(self)</dt><dd><tt>Raise an error if terminal was closed, otherwise do<br>
|
||||||
|
nothing.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="SAMTerminal-check_message"><strong>check_message</strong></a>(self, kwargs)</dt><dd><tt>Raises an error if kwargs['RESULT'] != 'OK'.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="SAMTerminal-close"><strong>close</strong></a>(self)</dt><dd><tt>Close the SAM terminal.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="SAMTerminal-on_message"><strong>on_message</strong></a>(self, msg, kwargs)</dt><dd><tt>Process a SAM message that was received. Dispatch to<br>
|
||||||
|
_on_MESSAGE_NAME(**kwargs).</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="SAMTerminal-queue_get"><strong>queue_get</strong></a>(self, q)</dt><dd><tt>Identical to q.get() unless a call to <a href="#SAMTerminal-check">check</a>() fails,<br>
|
||||||
|
in which case the waiting is cut short with an error.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="SAMTerminal-send_message"><strong>send_message</strong></a>(self, msg)</dt><dd><tt>Send a message to the SAM bridge. A newline will be<br>
|
||||||
|
automatically added if none is present.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="Stream">class <strong>Stream</strong></a></font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>Receives and sends data for an individual stream.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="Stream-__del__"><strong>__del__</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-__init__"><strong>__init__</strong></a>(self, parent, remotedest, id, didconnect<font color="#909090">=True</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-__len__"><strong>__len__</strong></a>(self)</dt><dd><tt>Current length of read buffer.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-close"><strong>close</strong></a>(self)</dt><dd><tt>Close the stream. Threadsafe.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-on_close"><strong>on_close</strong></a>(self, e)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-on_receive"><strong>on_receive</strong></a>(self, s)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-recv"><strong>recv</strong></a>(self, n, timeout<font color="#909090">=None</font>, peek<font color="#909090">=False</font>, waitall<font color="#909090">=False</font>)</dt><dd><tt>Reads up to n bytes in a manner identical to socket.recv.<br>
|
||||||
|
Blocks for up to timeout seconds if n > 0 and no data is<br>
|
||||||
|
available (timeout=None means wait forever). If still no data<br>
|
||||||
|
is available, raises BlockError or Timeout. For a closed<br>
|
||||||
|
stream, recv will read the data stored in the buffer until<br>
|
||||||
|
EOF, at which point the read data will be truncated. If peek<br>
|
||||||
|
is True, the data is not removed. If waitall is True, reads<br>
|
||||||
|
exactly n bytes, or raises BlockError or Timeout as<br>
|
||||||
|
appropriate. Returns data.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="Stream-send"><strong>send</strong></a>(self, s)</dt><dd><tt>Sends the string s, blocking if necessary.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="StreamSession">class <strong>StreamSession</strong></a>(<a href="i2p.samclasses.html#BaseSession">BaseSession</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt><a href="#Stream">Stream</a> session. All methods are blocking and threadsafe.<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="StreamSession-__init__"><strong>__init__</strong></a>(self, name, addr<font color="#909090">=''</font>, **kwargs)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StreamSession-__len__"><strong>__len__</strong></a>(self)</dt><dd><tt>Unconnected session; has no read data available.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StreamSession-accept"><strong>accept</strong></a>(self, timeout<font color="#909090">=None</font>)</dt><dd><tt>Wait for incoming connection, and return a <a href="#Stream">Stream</a> object<br>
|
||||||
|
for it.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StreamSession-connect"><strong>connect</strong></a>(self, dest, timeout<font color="#909090">=None</font>)</dt><dd><tt>Create a stream connected to remote destination 'dest'. The<br>
|
||||||
|
id is random. If the timeout is exceeded, do NOT raise an<br>
|
||||||
|
error; rather, return a <a href="#Stream">Stream</a> object with .didconnect set<br>
|
||||||
|
to False.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StreamSession-listen"><strong>listen</strong></a>(self, backlog)</dt><dd><tt>Set maximum number of queued connections.</tt></dd></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="i2p.samclasses.html#BaseSession">BaseSession</a>:<br>
|
||||||
|
<dl><dt><a name="StreamSession-close"><strong>close</strong></a>(self)</dt><dd><tt>Close the session.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table> <p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#ffc8d8">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#000000" face="helvetica, arial"><a name="StringBuffer">class <strong>StringBuffer</strong></a>(<a href="i2p.samclasses.html#Deque">Deque</a>)</font></td></tr>
|
||||||
|
|
||||||
|
<tr bgcolor="#ffc8d8"><td rowspan=2><tt> </tt></td>
|
||||||
|
<td colspan=2><tt>A FIFO for characters. Strings can be efficiently<br>
|
||||||
|
appended to the end, and read from the beginning.<br>
|
||||||
|
<br>
|
||||||
|
Example:<br>
|
||||||
|
B = <a href="#StringBuffer">StringBuffer</a>('Hello W')<br>
|
||||||
|
B.<a href="#StringBuffer-append">append</a>('orld!')<br>
|
||||||
|
print B.<a href="#StringBuffer-read">read</a>(5) # 'Hello'<br>
|
||||||
|
print B.<a href="#StringBuffer-read">read</a>() # 'World!'<br> </tt></td></tr>
|
||||||
|
<tr><td> </td>
|
||||||
|
<td width="100%">Methods defined here:<br>
|
||||||
|
<dl><dt><a name="StringBuffer-__init__"><strong>__init__</strong></a>(self, s<font color="#909090">=''</font>)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-__len__"><strong>__len__</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-__repr__"><strong>__repr__</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-__str__"><strong>__str__</strong></a>(self)</dt></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-append"><strong>append</strong></a>(self, s)</dt><dd><tt>Append string data to the end of the buffer.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-peek"><strong>peek</strong></a>(self, n<font color="#909090">=None</font>)</dt><dd><tt>Like <a href="#StringBuffer-read">read</a>(), but do not remove the data that is returned.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-prepend"><strong>prepend</strong></a>(self, s)</dt><dd><tt>Prepend string data to the beginning of the buffer.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-read"><strong>read</strong></a>(self, n<font color="#909090">=None</font>)</dt><dd><tt>Read n bytes of data (or less if less data available) from the<br>
|
||||||
|
beginning of the buffer. The data is removed. If n is<br>
|
||||||
|
omitted, read the entire buffer.</tt></dd></dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
Methods inherited from <a href="i2p.samclasses.html#Deque">Deque</a>:<br>
|
||||||
|
<dl><dt><a name="StringBuffer-pop_first"><strong>pop_first</strong></a>(self)</dt><dd><tt>Pop an item off the beginning of the deque, and return it.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-pop_last"><strong>pop_last</strong></a>(self)</dt><dd><tt>Pop an item off the end of the deque, and return it.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-push_first"><strong>push_first</strong></a>(self, obj)</dt><dd><tt>Prepend obj to the beginning of the deque.</tt></dd></dl>
|
||||||
|
|
||||||
|
<dl><dt><a name="StringBuffer-push_last"><strong>push_last</strong></a>(self, obj)</dt><dd><tt>Append obj to the end of the deque.</tt></dd></dl>
|
||||||
|
|
||||||
|
</td></tr></table></td></tr></table><p>
|
||||||
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||||
|
<tr bgcolor="#55aa55">
|
||||||
|
<td colspan=3 valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
|
||||||
|
|
||||||
|
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||||
|
<td width="100%"><strong>sam_log</strong> = False</td></tr></table>
|
||||||
|
</body></html>
|
36
apps/sam/python/doc/pydoc/makedoc.py
Normal file
36
apps/sam/python/doc/pydoc/makedoc.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# makedoc.py: Make pydoc documentation for Python SAM API
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
import os, inspect
|
||||||
|
import pydoc as pydoc_
|
||||||
|
|
||||||
|
def pydoc(args):
|
||||||
|
"""Run pydoc (command line) with given argument string."""
|
||||||
|
filename = inspect.getsourcefile(pydoc_)
|
||||||
|
os.system('python ' + filename + ' ' + args)
|
||||||
|
|
||||||
|
def move(f1, f2):
|
||||||
|
"""Moves filename f1 to filename f2, overwriting if f2 already exists."""
|
||||||
|
try: os.remove(f2)
|
||||||
|
except: pass
|
||||||
|
os.rename(f1, f2)
|
||||||
|
|
||||||
|
def makedoc():
|
||||||
|
"""Make all HTML documentation for Python I2P library."""
|
||||||
|
modules = ['i2p', 'i2p.sam', 'i2p.eep', 'i2p.router', 'i2p.samclasses']
|
||||||
|
|
||||||
|
origdir = os.getcwd()
|
||||||
|
os.chdir('../..')
|
||||||
|
|
||||||
|
for m in modules:
|
||||||
|
pydoc('-w ' + m)
|
||||||
|
|
||||||
|
os.chdir(origdir)
|
||||||
|
|
||||||
|
for m in modules:
|
||||||
|
move('../../' + m + '.html', './' + m + '.html')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
makedoc()
|
34
apps/sam/python/readme.txt
Normal file
34
apps/sam/python/readme.txt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
Python-I2P v0.9
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Python-I2P is a Python interface to I2P.
|
||||||
|
|
||||||
|
All files in this directory and subdirectories
|
||||||
|
have been placed in the public domain by
|
||||||
|
Connelly Barnes.
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
Quick Start
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
>>> from i2p import sam
|
||||||
|
>>> s = sam.socket('Alice', sam.SOCK_STREAM)
|
||||||
|
>>> s.connect('duck.i2p')
|
||||||
|
>>> s.send('GET / HTTP/1.0\r\n\r\n')
|
||||||
|
>>> s.recv(1000)
|
||||||
|
(Response from duck.i2p)
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
Full Start
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
See the docs directory for HTML documentation.
|
11
apps/sam/python/setup.py
Normal file
11
apps/sam/python/setup.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
from distutils.core import setup
|
||||||
|
|
||||||
|
setup(name="Python I2P API",
|
||||||
|
version="0.9",
|
||||||
|
description="Python Interface to I2P",
|
||||||
|
author="Connelly Barnes",
|
||||||
|
author_email="'Y29ubmVsbHliYXJuZXNAeWFob28uY29t\n'.decode('base64')",
|
||||||
|
url="http://www.i2p.net/",
|
||||||
|
packages=['i2p'],
|
||||||
|
)
|
15
apps/sam/python/src/examples/datagram.py
Normal file
15
apps/sam/python/src/examples/datagram.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# datagram.py: Datagram client
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
dest = sam.resolve('yourserver.i2p')
|
||||||
|
S = sam.socket('Bob', sam.SOCK_DGRAM)
|
||||||
|
S.sendto('Hello packet', 0, dest)
|
||||||
|
|
||||||
|
# Get packet up to 1000 bytes -- the rest is discarded.
|
||||||
|
(data, dest) = S.recvfrom(1000)
|
||||||
|
|
||||||
|
print data
|
20
apps/sam/python/src/examples/datagram_noblock.py
Normal file
20
apps/sam/python/src/examples/datagram_noblock.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
# ---------------------------------------------------
|
||||||
|
# datagram_noblock.py: Non-blocking datagram server
|
||||||
|
# ---------------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
import time
|
||||||
|
|
||||||
|
S = sam.socket('Eve', sam.SOCK_DGRAM)
|
||||||
|
print 'Serving at:', S.dest
|
||||||
|
S.setblocking(False)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
(data, dest) = S.recvfrom(1000) # Read packet
|
||||||
|
print 'Got', data, 'from', dest
|
||||||
|
S.sendto('Hi client!', 0, dest)
|
||||||
|
except sam.BlockError: # No data available
|
||||||
|
pass
|
||||||
|
time.sleep(0.01) # Reduce CPU usage
|
14
apps/sam/python/src/examples/datagram_server.py
Normal file
14
apps/sam/python/src/examples/datagram_server.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# datagram_server.py: Datagram server
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
S = sam.socket('Eve', sam.SOCK_DGRAM)
|
||||||
|
print 'Serving at:', S.dest
|
||||||
|
|
||||||
|
while True:
|
||||||
|
(data, dest) = S.recvfrom(1000) # Read packet
|
||||||
|
print 'Got', data, 'from', dest
|
||||||
|
S.sendto('Hi client!', 0, dest)
|
33
apps/sam/python/src/examples/dos.py
Normal file
33
apps/sam/python/src/examples/dos.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# dos.py: Noneffective denial of service tool
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
import threading, sys
|
||||||
|
|
||||||
|
def dos_stream(dest):
|
||||||
|
"""Perform a DOS attack on a stream server."""
|
||||||
|
dest = sam.resolve(dest)
|
||||||
|
|
||||||
|
# DOS code, runs in n separate threads.
|
||||||
|
def f():
|
||||||
|
while True:
|
||||||
|
S = sam.socket(dest, sam.SOCK_STREAM)
|
||||||
|
S.connect(dest)
|
||||||
|
S.send('GET / HTTP/1.0\r\n\r\n')
|
||||||
|
S.close()
|
||||||
|
|
||||||
|
# Start up the threads.
|
||||||
|
for i in range(128):
|
||||||
|
T = threading.Thread(target=f)
|
||||||
|
T.start()
|
||||||
|
|
||||||
|
def syntax():
|
||||||
|
print "Usage: python dos.py Destination"
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
dos_stream(sys.argv[1])
|
||||||
|
else:
|
||||||
|
syntax()
|
14
apps/sam/python/src/examples/examples.txt
Normal file
14
apps/sam/python/src/examples/examples.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
datagram.py - Datagram client
|
||||||
|
datagram_noblock.py - Non-blocking datagram server
|
||||||
|
datagram_server.py - Blocking datagram server
|
||||||
|
dos.py - Denial of service tool
|
||||||
|
raw.py - Raw client
|
||||||
|
raw_noblock.py - Non-blocking raw server
|
||||||
|
raw_server.py - Raw server
|
||||||
|
stream.py - Stream client
|
||||||
|
stream_eepget.py - Get an eepsite using sockets
|
||||||
|
stream_noblock.py - Non-blocking stream server
|
||||||
|
stream_server.py - Blocking stream server
|
10
apps/sam/python/src/examples/raw.py
Normal file
10
apps/sam/python/src/examples/raw.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# raw.py: Raw client
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
dest = sam.resolve('yourserver.i2p') # Send to dest
|
||||||
|
S = sam.socket('Carol', sam.SOCK_RAW)
|
||||||
|
S.sendto('Hello packet', 0, dest)
|
19
apps/sam/python/src/examples/raw_noblock.py
Normal file
19
apps/sam/python/src/examples/raw_noblock.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
# ---------------------------------------------------
|
||||||
|
# raw_noblock.py: Non-blocking raw server
|
||||||
|
# ---------------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
import time
|
||||||
|
|
||||||
|
S = sam.socket('Eve', sam.SOCK_RAW)
|
||||||
|
print 'Serving at:', S.dest
|
||||||
|
S.setblocking(False)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = S.recv(1000) # Read packet
|
||||||
|
print 'Got', data
|
||||||
|
except sam.BlockError: # No data available
|
||||||
|
pass
|
||||||
|
time.sleep(0.01) # Reduce CPU usage
|
13
apps/sam/python/src/examples/raw_server.py
Normal file
13
apps/sam/python/src/examples/raw_server.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# raw_server.py: Raw server
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
S = sam.socket('Eve', sam.SOCK_RAW)
|
||||||
|
print 'Serving at:', S.dest
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = S.recv(1000) # Read packet
|
||||||
|
print 'Got', data
|
11
apps/sam/python/src/examples/stream.py
Normal file
11
apps/sam/python/src/examples/stream.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# stream.py: Simple stream client
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
S = sam.socket('Alice', sam.SOCK_STREAM)
|
||||||
|
S.connect('duck.i2p')
|
||||||
|
S.send('GET / HTTP/1.0\r\n\r\n') # Send request
|
||||||
|
print S.recv(1000) # Read up to 1000 bytes
|
17
apps/sam/python/src/examples/stream_eepget.py
Normal file
17
apps/sam/python/src/examples/stream_eepget.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# stream_eepget.py: Get an eepsite using sockets
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
S = sam.socket('Alice', sam.SOCK_STREAM)
|
||||||
|
S.connect('duck.i2p')
|
||||||
|
S.send('GET / HTTP/1.0\r\n\r\n') # Send request
|
||||||
|
f = S.makefile() # File object
|
||||||
|
|
||||||
|
while True: # Read header
|
||||||
|
line = f.readline().strip() # Read a line
|
||||||
|
if line == '': break # Content begins
|
||||||
|
|
||||||
|
print f.read() # Read file object
|
39
apps/sam/python/src/examples/stream_noblock.py
Normal file
39
apps/sam/python/src/examples/stream_noblock.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# stream_noblock.py: Non-blocking stream server
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
import i2p
|
||||||
|
from i2p import sam
|
||||||
|
import thread, time
|
||||||
|
|
||||||
|
S = sam.socket('Dave', sam.SOCK_STREAM)
|
||||||
|
S.listen(10) # Queue up to 10 connections
|
||||||
|
S.setblocking(False) # Non-blocking
|
||||||
|
print 'Serving at:', S.dest
|
||||||
|
|
||||||
|
def handle_connection(C):
|
||||||
|
"""Handle a single connection in a thread of its own."""
|
||||||
|
try:
|
||||||
|
f = C.makefile() # File object
|
||||||
|
req = f.readline() # Read HTTP request
|
||||||
|
|
||||||
|
s = '<h1>Hello!</h1>' # String to send back
|
||||||
|
|
||||||
|
f.write('HTTP/1.0 200 OK\r\nContent-Type: text/html' +
|
||||||
|
'\r\nContent-Length: ' + str(int(len(s))) + '\r\n\r\n' + s)
|
||||||
|
|
||||||
|
f.close() # Close file
|
||||||
|
C.close() # Close connection
|
||||||
|
except sam.Error, e:
|
||||||
|
# Recover from SAM errors
|
||||||
|
print 'Warning:', str(e)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
(C, remotedest) = S.accept() # Get a connection
|
||||||
|
thread.start_new_thread(handle_connection, (C,))
|
||||||
|
except sam.BlockError:
|
||||||
|
# Ignore 'command would have blocked' errors
|
||||||
|
pass
|
||||||
|
time.sleep(0.01) # Reduce CPU usage
|
28
apps/sam/python/src/examples/stream_server.py
Normal file
28
apps/sam/python/src/examples/stream_server.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# stream_server.py: Simple stream server
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
import i2p
|
||||||
|
from i2p import sam
|
||||||
|
|
||||||
|
S = sam.socket('Dave', sam.SOCK_STREAM)
|
||||||
|
S.listen(10) # Queue up to 10 connections
|
||||||
|
print 'Serving at:', S.dest
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
(C, remotedest) = S.accept() # Get a connection
|
||||||
|
f = C.makefile() # File object
|
||||||
|
req = f.readline() # Read HTTP request
|
||||||
|
|
||||||
|
s = '<h1>Hello!</h1>' # String to send back
|
||||||
|
|
||||||
|
f.write('HTTP/1.0 200 OK\r\nContent-Type: text/html' +
|
||||||
|
'\r\nContent-Length: ' + str(int(len(s))) + '\r\n\r\n' + s)
|
||||||
|
|
||||||
|
f.close() # Close file
|
||||||
|
C.close() # Close connection
|
||||||
|
except sam.Error, e:
|
||||||
|
# Recover from SAM errors
|
||||||
|
print 'Warning:', str(e)
|
20
apps/sam/python/src/i2p/__init__.py
Normal file
20
apps/sam/python/src/i2p/__init__.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
i2p -- I2P Python interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
__all__ = ['Error', 'RouterError', 'sam', 'eep', 'router']
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
"""Base class for all I2P errors."""
|
||||||
|
|
||||||
|
class RouterError(Error):
|
||||||
|
"""Could not connect to router."""
|
||||||
|
|
||||||
|
import sam
|
||||||
|
import eep
|
||||||
|
import router
|
||||||
|
|
||||||
|
# Internal use only
|
||||||
|
#import samclasses as _samclasses
|
||||||
|
|
47
apps/sam/python/src/i2p/eep.py
Normal file
47
apps/sam/python/src/i2p/eep.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# eep.py: I2P Project -- Eeproxy Python API
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
Eeproxy Python API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
eepaddr = '127.0.0.1:4444' # Default port for eeproxy
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Functions
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def urlopen(url, eepaddr=eepaddr):
|
||||||
|
"""Like urllib2.urlopen(url), but only works for eep-sites.
|
||||||
|
Example: f = urlopen('http://duck.i2p/index.html')"""
|
||||||
|
if url.find('http://') != 0: url = 'http://' + url
|
||||||
|
|
||||||
|
# Handle I2P Destination
|
||||||
|
if len(url) >= 256:
|
||||||
|
suffix = url[len('http://'):]
|
||||||
|
if suffix[:4] != 'i2p/': url = 'http://i2p/' + suffix
|
||||||
|
|
||||||
|
# Add trailing slash
|
||||||
|
if url.find('/', len('http://')) < 0: url = url + '/'
|
||||||
|
|
||||||
|
# Remove http:// and trailing slash from eepaddr.
|
||||||
|
if eepaddr.find('http://') == 0: eepaddr = eepaddr[len('http://'):]
|
||||||
|
eepaddr = eepaddr.rstrip('/')
|
||||||
|
|
||||||
|
proxy = urllib2.ProxyHandler( \
|
||||||
|
{'http': 'http://anonymous:passwd@' + eepaddr})
|
||||||
|
opener = urllib2.build_opener(proxy, \
|
||||||
|
urllib2.HTTPBasicAuthHandler(), urllib2.HTTPHandler)
|
||||||
|
return opener.open(url)
|
||||||
|
|
||||||
|
def urlget(url, eepaddr=eepaddr):
|
||||||
|
"""Get contents of an eepsite.
|
||||||
|
Example: urlget('http://duck.i2p/')."""
|
||||||
|
f = urlopen(url, eepaddr=eepaddr)
|
||||||
|
ans = f.read()
|
||||||
|
f.close()
|
||||||
|
return ans
|
188
apps/sam/python/src/i2p/router.py
Normal file
188
apps/sam/python/src/i2p/router.py
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# router.py: I2P Project -- Router Control API for Python
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
Router Control API for Python
|
||||||
|
"""
|
||||||
|
|
||||||
|
import i2p
|
||||||
|
import i2p.sam
|
||||||
|
import i2p.eep
|
||||||
|
|
||||||
|
import socket as pysocket
|
||||||
|
import os, sys
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
check_addrlist = [i2p.sam.samaddr, i2p.eep.eepaddr]
|
||||||
|
|
||||||
|
router_config = 'router.config' # Router config filename
|
||||||
|
|
||||||
|
# True if our Python program started the router
|
||||||
|
our_router = False
|
||||||
|
our_router_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def find(dir=None):
|
||||||
|
"""Find the absolute path to a locally installed I2P router.
|
||||||
|
|
||||||
|
An I2P installation is located by looking in the
|
||||||
|
environment I2P, then in PATH, then in the dir argument
|
||||||
|
given to the function. It looks for startRouter.sh or
|
||||||
|
startRouter.bat. Raises ValueError if an I2P installation
|
||||||
|
could not be located.
|
||||||
|
"""
|
||||||
|
if sys.platform[:3] == 'win':
|
||||||
|
sep = ';'
|
||||||
|
else:
|
||||||
|
sep = ':'
|
||||||
|
L = []
|
||||||
|
if 'PATH' in os.environ: L += os.environ['PATH'].split(sep)
|
||||||
|
if 'I2P' in os.environ: L += os.environ['I2P'].split(sep)
|
||||||
|
if dir != None and dir != '': L += dir.split(sep)
|
||||||
|
for dirname in L:
|
||||||
|
filename = os.path.join(dirname, 'startRouter.bat')
|
||||||
|
if os.path.exists(filename):
|
||||||
|
return dirname
|
||||||
|
filename = os.path.join(dirname, 'startRouter.sh')
|
||||||
|
if os.path.exists(filename):
|
||||||
|
return dirname
|
||||||
|
raise ValueError('I2P installation not found')
|
||||||
|
|
||||||
|
|
||||||
|
def check(dir=None):
|
||||||
|
"""Checks whether a locally installed router is running. Does
|
||||||
|
nothing if successful, otherwise raises i2p.RouterError.
|
||||||
|
|
||||||
|
An I2P installation is located by using find(dir).
|
||||||
|
The router.config file is parsed for 'router.adminPort'.
|
||||||
|
This port is queried to determine whether the router is
|
||||||
|
running.
|
||||||
|
"""
|
||||||
|
config = _parse_config(os.path.join(find(dir), router_config))
|
||||||
|
port = config.get('router.adminPort', '')
|
||||||
|
try:
|
||||||
|
port = int(port)
|
||||||
|
except:
|
||||||
|
raise ValueError('router.adminPort missing or bad in ' +
|
||||||
|
router_config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s = pysocket.socket(pysocket.AF_INET, pysocket.SOCK_STREAM)
|
||||||
|
s.connect(('127.0.0.1', port))
|
||||||
|
s.close()
|
||||||
|
except pysocket.error:
|
||||||
|
raise i2p.RouterError('could not contact 127.0.0.1:' + str(port))
|
||||||
|
|
||||||
|
def _run_program(filename):
|
||||||
|
"""Runs the given program in a new process and new terminal."""
|
||||||
|
if sys.platform[:3] == 'win':
|
||||||
|
os.startfile(filename)
|
||||||
|
global our_router
|
||||||
|
our_router = True
|
||||||
|
else:
|
||||||
|
# Linux possibilities:
|
||||||
|
# sh -c command
|
||||||
|
# xterm -e command
|
||||||
|
# bash -c command
|
||||||
|
# Try os.spawnl() with the above.
|
||||||
|
raise ValueError('unimplemented')
|
||||||
|
|
||||||
|
def start(dir=None, hidden=False):
|
||||||
|
"""Start a locally installed I2P router. Does nothing if
|
||||||
|
the router has already been started.
|
||||||
|
|
||||||
|
An I2P installation is located by using find(dir).
|
||||||
|
|
||||||
|
If hidden is True, do not show a terminal for the router.
|
||||||
|
"""
|
||||||
|
routerdir = find(dir)
|
||||||
|
router = os.path.join(routerdir, 'startRouter.bat')
|
||||||
|
try:
|
||||||
|
check(dir)
|
||||||
|
return # Already running
|
||||||
|
except:
|
||||||
|
pass # Not yet running
|
||||||
|
|
||||||
|
olddir = os.getcwd()
|
||||||
|
|
||||||
|
if hidden:
|
||||||
|
raise ValueError('unimplemented')
|
||||||
|
|
||||||
|
our_router_lock.acquire()
|
||||||
|
try:
|
||||||
|
os.chdir(routerdir)
|
||||||
|
try:
|
||||||
|
_run_program(router)
|
||||||
|
finally:
|
||||||
|
os.chdir(olddir)
|
||||||
|
finally:
|
||||||
|
our_router_lock.release()
|
||||||
|
|
||||||
|
# Ideas for hidden=True:
|
||||||
|
# Parse startRouter.bat, and run same command with javaw
|
||||||
|
# on Windows to hide command box.
|
||||||
|
# Perhaps use javaw (?) or javaws (j2sdk1.4.2/jre/javaws/javaws)
|
||||||
|
# Perhaps /path-to/program 2>/dev/null 1>/dev/null&
|
||||||
|
|
||||||
|
def _parse_config(filename):
|
||||||
|
"""Return a dict with (name, value) items for the given I2P configuration file."""
|
||||||
|
f = open(filename, 'r')
|
||||||
|
s = f.read()
|
||||||
|
f.close()
|
||||||
|
ans = {}
|
||||||
|
for line in s.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if '#' in line: line = line[:line.find('#')]
|
||||||
|
pair = line.split('=')
|
||||||
|
if len(pair) == 2:
|
||||||
|
ans[pair[0].strip()] = pair[1].strip()
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def stop(dir=None, force=False):
|
||||||
|
"""Stop a locally installed I2P router, if it was started by
|
||||||
|
the current Python program. If force is True, stop the
|
||||||
|
router even if it was started by another process. Do nothing
|
||||||
|
if force is False and the router was started by another program.
|
||||||
|
|
||||||
|
The file 'router.config' is located using the same search
|
||||||
|
process used for find(dir). It is parsed for
|
||||||
|
'router.shutdownPassword' and 'router.adminPort'. The
|
||||||
|
router is shut down through the admin port.
|
||||||
|
|
||||||
|
Raises i2p.RouterError if the I2P router is running but cannot
|
||||||
|
be stopped. You must uncomment the
|
||||||
|
'router.shutdownPassword' line for this command to work.
|
||||||
|
"""
|
||||||
|
if force == False and our_router == False:
|
||||||
|
return
|
||||||
|
|
||||||
|
config = _parse_config(os.path.join(find(dir), router_config))
|
||||||
|
|
||||||
|
password = config.get('router.shutdownPassword', '')
|
||||||
|
if password == '':
|
||||||
|
raise ValueError('router.shutdownPassword not found in ' +
|
||||||
|
router_config)
|
||||||
|
admin_port = config.get('router.adminPort', '')
|
||||||
|
if admin_port == '':
|
||||||
|
raise ValueError('router.adminPort not found in ' + router_config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
admin_port = int(admin_port)
|
||||||
|
except:
|
||||||
|
raise ValueError('invalid router.adminPort in ' + router_config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock = pysocket.socket(pysocket.AF_INET, pysocket.SOCK_STREAM)
|
||||||
|
sock.connect(('127.0.0.1', admin_port))
|
||||||
|
sock.send('GET /shutdown?password=' + password + ' HTTP/1.0\r\n\r\n')
|
||||||
|
time.sleep(0.01)
|
||||||
|
sock.close()
|
||||||
|
except:
|
||||||
|
raise i2p.RouterError('router shutdown failed')
|
||||||
|
|
||||||
|
# Assume shutdown succeeded (it will take 30 seconds).
|
706
apps/sam/python/src/i2p/sam.py
Normal file
706
apps/sam/python/src/i2p/sam.py
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# sam.py: I2P Project -- SAM Python API
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
SAM Python API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import i2p
|
||||||
|
|
||||||
|
import samclasses, threading, time, copy, Queue, thread
|
||||||
|
import socket as pysocket
|
||||||
|
import select as pyselect
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Global variables
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
# Ports
|
||||||
|
samaddr = '127.0.0.1:7656' # Default port for SAM Bridge
|
||||||
|
|
||||||
|
# Flags for recv, recvfrom.
|
||||||
|
MSG_PEEK = 2 # Peek at incoming message
|
||||||
|
MSG_WAITALL = 64 # Wait for data or error
|
||||||
|
MSG_DONTWAIT = 128 # Nonblocking
|
||||||
|
|
||||||
|
# Packet sizes
|
||||||
|
MAX_DGRAM = 31744 # Max size of datagram packet
|
||||||
|
MAX_RAW = 32768 # Max size of raw packet
|
||||||
|
|
||||||
|
# Socket types
|
||||||
|
SOCK_STREAM = 1 # Stream socket
|
||||||
|
SOCK_DGRAM = 2 # Datagram socket
|
||||||
|
SOCK_RAW = 3 # Raw socket
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
samver = 1.0 # SAM version implemented
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Errors
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
class Error(i2p.Error):
|
||||||
|
"""Base class for all SAM errors."""
|
||||||
|
|
||||||
|
class NetworkError(Error):
|
||||||
|
"""Network error occurred within I2P.
|
||||||
|
The error object is a 2-tuple: (errtag, errdesc).
|
||||||
|
errtag is a SAM error string,
|
||||||
|
errdesc is a human readable error description.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class ClosedError(Error):
|
||||||
|
"""A command was used on a socket that closed gracefully."""
|
||||||
|
|
||||||
|
class BlockError(Error):
|
||||||
|
"""Socket call would have blocked."""
|
||||||
|
|
||||||
|
class Timeout(Error):
|
||||||
|
"""Time out occurred for a socket which had timeouts enabled
|
||||||
|
via a prior call to settimeout()."""
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Sockets
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
# Note: socket(), __make_session() and Socket() should have same args
|
||||||
|
def socket(session, type, samaddr=samaddr, **kwargs):
|
||||||
|
r"""Create a new socket. Argument session should be a session
|
||||||
|
name -- if the name has not yet been used, an I2P
|
||||||
|
Destination will be created for it, otherwise, the
|
||||||
|
existing Destination will be re-used. An empty session
|
||||||
|
string causes a transient session to be created. Argument
|
||||||
|
type is one of SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW.
|
||||||
|
|
||||||
|
I2P configuration keyword arguments:
|
||||||
|
|
||||||
|
* in_depth - depth of incoming tunnel (default 2)
|
||||||
|
* out_depth - depth of outgoing tunnel (default 2)
|
||||||
|
|
||||||
|
A single session may be shared by more than one socket, if
|
||||||
|
the sockets are the same type, and if the sockets are
|
||||||
|
created within the same Python process. The socket
|
||||||
|
objects are multithread-safe.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
a = i2p.socket('Alice', i2p.SOCK_STREAM)
|
||||||
|
b = i2p.socket('Bob', i2p.SOCK_DGRAM,
|
||||||
|
in_depth=2, out_depth=5)
|
||||||
|
|
||||||
|
The created object behaves identically to a socket from
|
||||||
|
module socket, with the following exceptions:
|
||||||
|
|
||||||
|
* I2P Destinations are used as address arguments [1].
|
||||||
|
* bind is a no-op: sockets are always bound.
|
||||||
|
* send* methods send all data and are non-blocking.
|
||||||
|
|
||||||
|
A given session name can only be open in a single Python
|
||||||
|
program at a time. If you need to overcome this
|
||||||
|
limitation, consider patching I2P.
|
||||||
|
|
||||||
|
[1]. Alternatively, a host name can be used as an address.
|
||||||
|
It will be resolved using hosts.txt.
|
||||||
|
|
||||||
|
For details on how to use socket objects, see
|
||||||
|
http://www.python.org/doc/current/lib/socket-objects.html
|
||||||
|
|
||||||
|
See the examples directory for code examples.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Socket(session, type, samaddr, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Socket session objects
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
# Global list of session objects.
|
||||||
|
_sessions = {}
|
||||||
|
_session_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _make_session(session, type, samaddr, **kwargs):
|
||||||
|
"""Make a session object (eg samclasses.StreamSession). Same
|
||||||
|
arguments as socket(). Return an existing session object
|
||||||
|
if one has been previously created under the given name.
|
||||||
|
"""
|
||||||
|
# Synchronize
|
||||||
|
_session_lock.acquire()
|
||||||
|
try:
|
||||||
|
if type == SOCK_STREAM: C = samclasses.StreamSession
|
||||||
|
elif type == SOCK_DGRAM: C = samclasses.DatagramSession
|
||||||
|
elif type == SOCK_RAW: C = samclasses.RawSession
|
||||||
|
else: raise ValueError('bad socket type')
|
||||||
|
# Get existing session, if available
|
||||||
|
if session != '' and _sessions.has_key(session):
|
||||||
|
if _sessions[session].__class__ != C:
|
||||||
|
raise ValueError('session ' + repr(session) + ' was ' +
|
||||||
|
'created with a different session type.')
|
||||||
|
return _sessions[session]
|
||||||
|
# Create new session
|
||||||
|
if type == SOCK_STREAM: ans = C(session, samaddr, **kwargs)
|
||||||
|
elif type == SOCK_DGRAM: ans = C(session, samaddr, **kwargs)
|
||||||
|
elif type == SOCK_RAW: ans = C(session, samaddr, **kwargs)
|
||||||
|
if session != '': _sessions[session] = ans
|
||||||
|
return ans
|
||||||
|
finally: _session_lock.release()
|
||||||
|
|
||||||
|
def _wrap_stream(stream, parent_socket):
|
||||||
|
"""Wraps a Socket object around a samclasses.Stream object."""
|
||||||
|
s = Socket('', 0, dummy_socket=True)
|
||||||
|
s.sessobj = stream
|
||||||
|
s.remotedest = stream.remotedest
|
||||||
|
s.dest = parent_socket.dest
|
||||||
|
s.session = parent_socket.session
|
||||||
|
s.type = parent_socket.type
|
||||||
|
s.timeout = None
|
||||||
|
s.samaddr = parent_socket.samaddr
|
||||||
|
s.closed = False
|
||||||
|
return s
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Socket class
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
class Socket:
|
||||||
|
"""A socket object."""
|
||||||
|
|
||||||
|
# Docstrings for pydoc. These variables will be overwritten.
|
||||||
|
dest = property(doc='Local I2P Destination of socket')
|
||||||
|
session = property(doc='Session name')
|
||||||
|
type = property(doc='Socket type: SOCK_STREAM, SOCK_DGRAM,' +
|
||||||
|
' or SOCK_RAW.')
|
||||||
|
# FIXME: Use getsockopt to detect errors.
|
||||||
|
|
||||||
|
def __init__(self, session, type, samaddr=samaddr, **kwargs):
|
||||||
|
"""Equivalent to socket()."""
|
||||||
|
if kwargs.has_key('dummy_socket'): return
|
||||||
|
self.sessobj = _make_session(session, type, samaddr, **kwargs)
|
||||||
|
self.dest = self.sessobj.dest
|
||||||
|
self.session = session
|
||||||
|
self.type = type
|
||||||
|
self.timeout = None # None indicates blocking mode
|
||||||
|
self.samaddr = samaddr
|
||||||
|
self.closed = False # Was current object closed?
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
def _verify_open(self):
|
||||||
|
"""Verify that the socket has not been closed."""
|
||||||
|
if self.closed == True:
|
||||||
|
raise ClosedError('socket closed')
|
||||||
|
|
||||||
|
def _verify_stream(self):
|
||||||
|
"""Raise an error if socket is not a SOCK_STREAM."""
|
||||||
|
if self.type != SOCK_STREAM:
|
||||||
|
raise i2p.Error('operation not supported')
|
||||||
|
# FIXME: Check for errors also.
|
||||||
|
|
||||||
|
def _verify_connected(self, needs_to_be_connected=True):
|
||||||
|
"""Raise an error if socket is not a connected stream socket."""
|
||||||
|
self._verify_stream()
|
||||||
|
if not hasattr(self.sessobj, 'remotedest'):
|
||||||
|
raise i2p.Error('socket is not connected')
|
||||||
|
if needs_to_be_connected and not self.sessobj.didconnect:
|
||||||
|
raise i2p.Error('socket is in the process of connecting')
|
||||||
|
# FIXME: Check for errors also.
|
||||||
|
|
||||||
|
def _verify_not_connected(self):
|
||||||
|
"""Verify that the socket is not currently connected, and is not
|
||||||
|
in the process of connecting."""
|
||||||
|
self._verify_stream()
|
||||||
|
if hasattr(self.sessobj, 'remotedest'):
|
||||||
|
raise i2p.Error('socket is already connected')
|
||||||
|
# FIXME: Check for errors also.
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
self._verify_open()
|
||||||
|
self._verify_not_connected()
|
||||||
|
# Raises BlockError or Timeout if not ready.
|
||||||
|
C = _wrap_stream(self.sessobj.accept(self.timeout), self)
|
||||||
|
return (C, C.remotedest)
|
||||||
|
|
||||||
|
def bind(self, address):
|
||||||
|
self._verify_open()
|
||||||
|
self._verify_not_connected()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self._verify_connected()
|
||||||
|
connected = True
|
||||||
|
except i2p.Error:
|
||||||
|
connected = False
|
||||||
|
if connected:
|
||||||
|
# Close the Stream object.
|
||||||
|
self.sessobj.close()
|
||||||
|
else:
|
||||||
|
# Never close a session object.
|
||||||
|
pass
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def connect(self, address):
|
||||||
|
# Synchronized. Lock prevents two connects from occurring at the
|
||||||
|
# same time in different threads.
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
self._verify_open()
|
||||||
|
if self.type == SOCK_DGRAM or self.type == SOCK_RAW:
|
||||||
|
self.packet_dest = address
|
||||||
|
return
|
||||||
|
|
||||||
|
self._verify_not_connected()
|
||||||
|
address = resolve(address, self.samaddr)
|
||||||
|
|
||||||
|
timeout = self.timeout
|
||||||
|
unwrap = self.sessobj.connect(address, timeout=timeout)
|
||||||
|
w = _wrap_stream(unwrap, self)
|
||||||
|
self.sessobj = w.sessobj
|
||||||
|
self.remotedest = w.remotedest
|
||||||
|
|
||||||
|
if self.sessobj.err != None:
|
||||||
|
raise self.sessobj.err
|
||||||
|
|
||||||
|
# Raise error if not yet connected
|
||||||
|
if not self.sessobj.didconnect:
|
||||||
|
if timeout == 0.0:
|
||||||
|
raise BlockError('command would have blocked. use ' +
|
||||||
|
'select() to find when socket is connected')
|
||||||
|
else: raise Timeout('timed out. use select() to find ' +
|
||||||
|
'when socket is connected')
|
||||||
|
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
def connect_ex(self, address):
|
||||||
|
try: self.connect(address)
|
||||||
|
except i2p.Error, e: return e
|
||||||
|
|
||||||
|
def getpeername(self):
|
||||||
|
self._verify_connected()
|
||||||
|
return self.remotedest
|
||||||
|
|
||||||
|
def getsockname(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
def listen(self, backlog):
|
||||||
|
self._verify_open()
|
||||||
|
self._verify_not_connected()
|
||||||
|
self.sessobj.listen(backlog)
|
||||||
|
|
||||||
|
def makefile(self, mode='r', bufsize=-1):
|
||||||
|
self._verify_open()
|
||||||
|
self._verify_connected()
|
||||||
|
return pysocket._fileobject(self, mode, bufsize)
|
||||||
|
|
||||||
|
def recv(self, bufsize, flags=0):
|
||||||
|
# FIXME: What about recv'ing if connected in asynchronous mode?
|
||||||
|
# It is acceptable to call recv() after a stream has closed
|
||||||
|
# gracefully. It is an error to call recv() after a stream has
|
||||||
|
# closed due to an I2P network error.
|
||||||
|
timeout = self.timeout
|
||||||
|
(peek, waitall, dontwait) = \
|
||||||
|
(flags & MSG_PEEK, flags & MSG_WAITALL, flags & MSG_DONTWAIT)
|
||||||
|
if dontwait: timeout = 0.0
|
||||||
|
|
||||||
|
if self.type == SOCK_STREAM:
|
||||||
|
self._verify_connected()
|
||||||
|
return self.sessobj.recv(bufsize, timeout, peek, waitall)
|
||||||
|
else:
|
||||||
|
return self.recvfrom(bufsize, flags)[0]
|
||||||
|
|
||||||
|
def recvfrom(self, bufsize, flags=0):
|
||||||
|
"""For a datagram or raw socket, bufsize = -1 indicates that the
|
||||||
|
entire packet should be retrieved."""
|
||||||
|
timeout = self.timeout
|
||||||
|
(peek, waitall, dontwait) = \
|
||||||
|
(flags & MSG_PEEK, flags & MSG_WAITALL, flags & MSG_DONTWAIT)
|
||||||
|
if dontwait: timeout = 0.0
|
||||||
|
|
||||||
|
if self.type == SOCK_STREAM:
|
||||||
|
self._verify_connected()
|
||||||
|
if bufsize < 0: raise ValueError('bufsize must be >= 0')
|
||||||
|
return (self.sessobj.recv(bufsize, timeout, peek, waitall), \
|
||||||
|
self.remotedest)
|
||||||
|
else:
|
||||||
|
return self.sessobj.recv(timeout, peek)[:bufsize]
|
||||||
|
|
||||||
|
def send(self, string, flags=0):
|
||||||
|
self._verify_open()
|
||||||
|
if self.type == SOCK_DGRAM or self.type == SOCK_RAW:
|
||||||
|
if not hasattr(self, 'packet_dest'):
|
||||||
|
raise i2p.Error('use connect or sendto to specify a ' +
|
||||||
|
'Destination')
|
||||||
|
self.sendto(string, flags, self.packet_dest)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._verify_connected()
|
||||||
|
if self.closed:
|
||||||
|
raise i2p.Error('send operation on closed socket')
|
||||||
|
# FIXME: What about send'ing if connected in asynchronous mode?
|
||||||
|
self.sessobj.send(string)
|
||||||
|
|
||||||
|
def sendall(self, string, flags=0):
|
||||||
|
self.send(string)
|
||||||
|
|
||||||
|
def sendto(self, string, flags, address):
|
||||||
|
self._verify_open()
|
||||||
|
if not self.type in [SOCK_DGRAM, SOCK_RAW]:
|
||||||
|
raise i2p.Error('operation not supported')
|
||||||
|
if self.closed:
|
||||||
|
raise i2p.Error('sendto operation on closed socket')
|
||||||
|
address = resolve(address, self.samaddr)
|
||||||
|
self.sessobj.send(string, address)
|
||||||
|
|
||||||
|
def setblocking(self, flag):
|
||||||
|
if flag: self.timeout = None
|
||||||
|
else: self.timeout = 0.0
|
||||||
|
|
||||||
|
def settimeout(self, value):
|
||||||
|
self.timeout = value
|
||||||
|
|
||||||
|
def gettimeout(self):
|
||||||
|
return self.timeout
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
return copy.copy(self)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Poll and select
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
POLLIN = 1 # There is data to read
|
||||||
|
POLLPRI = 1 # Same as POLLIN
|
||||||
|
POLLOUT = 4 # Ready for output
|
||||||
|
POLLERR = 8 # Wait for error condition
|
||||||
|
POLLHUP = 16 # Not implemented
|
||||||
|
POLLNVAL = 32 # Not implemented
|
||||||
|
|
||||||
|
class Poll:
|
||||||
|
"""Class implementing poll interface. Works for Python sockets
|
||||||
|
and SAM sockets."""
|
||||||
|
def __init__(self):
|
||||||
|
self.fds = {} # Maps _hash() -> (socket, mask)
|
||||||
|
def _hash(self, fd):
|
||||||
|
if isinstance(fd, int):
|
||||||
|
return fd # Use the fd itself if integer.
|
||||||
|
else:
|
||||||
|
return id(fd) # Use object address (no copies!)
|
||||||
|
def register(self, fd, eventmask=POLLIN|POLLOUT|POLLERR):
|
||||||
|
self.fds[self._hash(fd)] = (fd, eventmask)
|
||||||
|
def unregister(self, fd):
|
||||||
|
del self.fds[self._hash(fd)]
|
||||||
|
def poll(self, timeout=None):
|
||||||
|
readlist, writelist, errlist = [], [], []
|
||||||
|
for F, mask in self.fds:
|
||||||
|
if mask & POLLIN: readlist += [F]
|
||||||
|
if mask & POLLOUT: writelist += [F]
|
||||||
|
if mask & POLLERR: errlist += [F]
|
||||||
|
(Rs, Ws, Es) = select(readlist, writelist, errlist,
|
||||||
|
timeout=timeout)
|
||||||
|
ans = []
|
||||||
|
for R in Rs: ans.append((R, POLLIN))
|
||||||
|
for W in Ws: ans.append((W, POLLOUT))
|
||||||
|
for E in Es: ans.append((E, POLLERR))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def poll():
|
||||||
|
"""Returns a polling object. Works on SAM sockets and Python
|
||||||
|
sockets. See select.poll() in the Python library for more
|
||||||
|
information."""
|
||||||
|
return Poll()
|
||||||
|
|
||||||
|
def select(readlist, writelist, errlist, timeout=None):
|
||||||
|
"""Performs a select call. Works on SAM sockets and Python
|
||||||
|
sockets. See select.select() in the Python library for more
|
||||||
|
information."""
|
||||||
|
Rans = []
|
||||||
|
Wans = []
|
||||||
|
Eans = []
|
||||||
|
if timeout != None: end = time.clock() + timeout
|
||||||
|
while True:
|
||||||
|
# FIXME: Check performance.
|
||||||
|
# Use pysocket.poll for Python sockets, if needed for speed.
|
||||||
|
|
||||||
|
# Check for read availability.
|
||||||
|
for R in readlist:
|
||||||
|
if isinstance(R, int) or hasattr(R, 'fileno'):
|
||||||
|
# Python socket
|
||||||
|
if len(pyselect.select([R], [], [], 0.0)[0]) > 0:
|
||||||
|
Rans.append(R)
|
||||||
|
else:
|
||||||
|
# SAM Socket
|
||||||
|
if R.type == SOCK_STREAM:
|
||||||
|
try:
|
||||||
|
R._verify_connected()
|
||||||
|
Rans.append(R)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if len(R) > 0: Rans.append(R)
|
||||||
|
|
||||||
|
# Check for write availability.
|
||||||
|
for W in writelist:
|
||||||
|
if isinstance(W, int) or hasattr(W, 'fileno'):
|
||||||
|
# Python socket
|
||||||
|
if len(pyselect.select([], [W], [], 0.0)[1]) > 0:
|
||||||
|
Wans.append(W)
|
||||||
|
else:
|
||||||
|
# SAM Socket
|
||||||
|
if W.type == SOCK_STREAM:
|
||||||
|
try:
|
||||||
|
W._verify_connected()
|
||||||
|
Wans.append(W)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
Wans.append(W)
|
||||||
|
|
||||||
|
# Check for error conditions.
|
||||||
|
# These can only be stream errors.
|
||||||
|
for E in errlist:
|
||||||
|
if isinstance(E, int) or hasattr(E, 'fileno'):
|
||||||
|
# Python socket
|
||||||
|
if len(pyselect.select([], [], [E], 0.0)[2]) > 0:
|
||||||
|
Eans.append(E)
|
||||||
|
else:
|
||||||
|
if E.type == SOCK_STREAM:
|
||||||
|
try:
|
||||||
|
# FIXME: Use a ._get_error() function for errors.
|
||||||
|
# Socket can only have an error if it connected.
|
||||||
|
E._verify_connected()
|
||||||
|
if E.sessobj.err != None:
|
||||||
|
Eans.append(E)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if timeout != None and time.clock() >= end: break
|
||||||
|
if len(Rans) != 0 or len(Wans) != 0 or len(Eans) != 0: break
|
||||||
|
|
||||||
|
samclasses.sleep()
|
||||||
|
|
||||||
|
return (Rans, Wans, Eans)
|
||||||
|
|
||||||
|
def resolve(host, samaddr=samaddr):
|
||||||
|
"""Resolve I2P host name --> I2P Destination.
|
||||||
|
Returns the same string if host is already a Destination."""
|
||||||
|
if host.find('http://') == 0: host = host[len('http://'):]
|
||||||
|
host = host.rstrip('/')
|
||||||
|
if len(host) >= 256: return host
|
||||||
|
S = samclasses.BaseSession(samaddr)
|
||||||
|
ans = S._namelookup(host)
|
||||||
|
S.close()
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def _exchange_data(A, B):
|
||||||
|
"""Exchanges data A <-> B between open stream sockets A and B."""
|
||||||
|
# FIXME: There's recv errors that we should be shutting
|
||||||
|
# down sockets for, but this seems to work OK.
|
||||||
|
err = None
|
||||||
|
try:
|
||||||
|
# Send data from A -> B while available.
|
||||||
|
while True:
|
||||||
|
# A -> B.
|
||||||
|
A.setblocking(False)
|
||||||
|
try: s = A.recv(1024)
|
||||||
|
except Exception, e: s = None
|
||||||
|
if s == '': raise ClosedError
|
||||||
|
if s == None:
|
||||||
|
# Stop sending A -> B.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
B.setblocking(True)
|
||||||
|
B.sendall(s)
|
||||||
|
except Exception, e:
|
||||||
|
err = e
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Send data from B -> A while available.
|
||||||
|
while True:
|
||||||
|
# B -> A.
|
||||||
|
B.setblocking(False)
|
||||||
|
try: s = B.recv(1024)
|
||||||
|
except Exception, e: s = None
|
||||||
|
if s == '': raise ClosedError
|
||||||
|
if s == None:
|
||||||
|
# Stop sending B -> A.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
A.setblocking(True)
|
||||||
|
A.sendall(s)
|
||||||
|
except Exception, e:
|
||||||
|
err = e
|
||||||
|
|
||||||
|
# Re-raise error after finishing communications both ways.
|
||||||
|
if err != None: raise err
|
||||||
|
|
||||||
|
def _test_connected(B):
|
||||||
|
"""Raises an error if socket B is not yet connected."""
|
||||||
|
[Rlist, Wlist, Elist] = select([B], [B], [B], 0.0)
|
||||||
|
if len(Wlist) == 0:
|
||||||
|
raise ValueError('socket not yet connected')
|
||||||
|
|
||||||
|
class Tunnel:
|
||||||
|
def __init__(self, receive, make_send, nconnect=-1, timeout=60.0):
|
||||||
|
"""A Tunnel relays connections from a 'receive' socket to one
|
||||||
|
or more 'send' sockets. The receive socket must be bound
|
||||||
|
and listening. For each incoming connection, a new send
|
||||||
|
socket is created by calling make_send(). Data is then
|
||||||
|
exchanged between the created streams until one socket is
|
||||||
|
closed. nconnect is the maximum number of simultaneous
|
||||||
|
connections (-1 for infinite), and timeout is the time that
|
||||||
|
a single connection can last for (None allows a connection
|
||||||
|
to last forever).
|
||||||
|
|
||||||
|
Sockets must accept stream traffic and support the Python
|
||||||
|
socket interface. A separate daemonic thread is created to
|
||||||
|
manage the tunnel. For high performance, make_send() should
|
||||||
|
make a socket and connect in non-blocking mode (you should
|
||||||
|
catch and discard the sam.BlockError or socket.error due to
|
||||||
|
executing connect on a non-blocking socket).
|
||||||
|
|
||||||
|
Security Note:
|
||||||
|
A firewall is needed to maintain the end user's anonymity.
|
||||||
|
An attacker could keep a tunnel socket open by pinging it
|
||||||
|
regularly. The accepted sockets from 'receive' must prevent
|
||||||
|
this by closing down eventually.
|
||||||
|
|
||||||
|
Socket errors do not cause the Tunnel to shut down.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.receive = receive
|
||||||
|
self.make_send = make_send
|
||||||
|
self.receive.setblocking(False)
|
||||||
|
self.alive = True
|
||||||
|
self.nconnect = nconnect
|
||||||
|
self.timeout = timeout
|
||||||
|
T = threading.Thread(target=self._run, args=())
|
||||||
|
T.setDaemon(True)
|
||||||
|
T.start()
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
|
"""Manage the tunnel in a separate thread."""
|
||||||
|
tunnels = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Look for a new connection
|
||||||
|
if self.nconnect < 0 or len(tunnels) < self.nconnect:
|
||||||
|
(A, B) = (None, None)
|
||||||
|
try:
|
||||||
|
(A, ignoredest) = self.receive.accept()
|
||||||
|
A.setblocking(False)
|
||||||
|
B = self.make_send()
|
||||||
|
B.setblocking(False)
|
||||||
|
if self.timeout != None: t = time.time() + self.timeout
|
||||||
|
else: t = None
|
||||||
|
tunnels.append((A, B, t))
|
||||||
|
except Exception, e:
|
||||||
|
try:
|
||||||
|
if A != None:
|
||||||
|
A.setblocking(False); A.close()
|
||||||
|
except Exception, e: pass
|
||||||
|
try:
|
||||||
|
if B != None:
|
||||||
|
B.setblocking(False); B.close()
|
||||||
|
except Exception, e: pass
|
||||||
|
|
||||||
|
# Send data between existing connections
|
||||||
|
new_tunnels = []
|
||||||
|
for (A, B, t) in tunnels:
|
||||||
|
# For each connection pair, send data.
|
||||||
|
try:
|
||||||
|
if t != None: assert time.time() <= t
|
||||||
|
# Test whether B is successfully connected
|
||||||
|
_test_connected(B)
|
||||||
|
|
||||||
|
# Send A <-> B.
|
||||||
|
_exchange_data(A, B)
|
||||||
|
|
||||||
|
if self.timeout != None: t = time.time() + self.timeout
|
||||||
|
else: t = None
|
||||||
|
new_tunnels.append((A, B, t))
|
||||||
|
except Exception, e:
|
||||||
|
# Catch errors. Kill the connection if it's been at
|
||||||
|
# least timeout seconds since last non-erroneous call
|
||||||
|
# to _exchange_data, or if stream was closed. This
|
||||||
|
# allows stream-not-finished-connecting errors to be
|
||||||
|
# dropped within the timeout.
|
||||||
|
time_ok = True
|
||||||
|
if self.timeout != None:
|
||||||
|
if time.time() > t: time_ok = False
|
||||||
|
if time_ok and not isinstance(e, ClosedError):
|
||||||
|
# Don't kill connection yet
|
||||||
|
new_tunnels.append((A, B, t))
|
||||||
|
else:
|
||||||
|
# We've only gotten errors for 'timeout' s.
|
||||||
|
# Drop the connection.
|
||||||
|
try: A.setblocking(False); A.close()
|
||||||
|
except Exception, e: pass
|
||||||
|
try: B.setblocking(False); B.close()
|
||||||
|
except Exception, e: pass
|
||||||
|
tunnels = new_tunnels
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
# Shut down all connections if self.close() was called.
|
||||||
|
if not self.alive:
|
||||||
|
for (A, B, t) in tunnels:
|
||||||
|
try: A.setblocking(False); A.close()
|
||||||
|
except: pass
|
||||||
|
try: B.setblocking(False); B.close()
|
||||||
|
except: pass
|
||||||
|
break
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close all connections made for this tunnel."""
|
||||||
|
self.alive = False
|
||||||
|
|
||||||
|
class TunnelServer(Tunnel):
|
||||||
|
dest = property(doc='I2P Destination of server.')
|
||||||
|
session = property(doc='Session name for server.')
|
||||||
|
def __init__(self, session, port, samaddr=samaddr, nconnect=-1,
|
||||||
|
timeout=None, **kwargs):
|
||||||
|
"""Tunnels incoming SAM streams --> localhost:port.
|
||||||
|
|
||||||
|
nconnect and timeout are the maximum number of connections
|
||||||
|
and maximum time per connection. All other arguments are
|
||||||
|
passed to sam.socket(). This call blocks until the tunnel
|
||||||
|
is ready."""
|
||||||
|
S = socket(session, SOCK_STREAM, samaddr, **kwargs)
|
||||||
|
S.listen(64)
|
||||||
|
self.session = session
|
||||||
|
self.dest = S.dest
|
||||||
|
def make_send():
|
||||||
|
C = pysocket.socket(pysocket.AF_INET, pysocket.SOCK_STREAM)
|
||||||
|
C.setblocking(False)
|
||||||
|
try: C.connect(('127.0.0.1', port))
|
||||||
|
except: pass # Ignore 'would have blocked' error
|
||||||
|
return C
|
||||||
|
Tunnel.__init__(self, S, make_send, nconnect, timeout)
|
||||||
|
|
||||||
|
class TunnelClient(Tunnel):
|
||||||
|
remotedest = property(doc='Remote Destination.')
|
||||||
|
dest = property('Local Destination used for routing.')
|
||||||
|
session = property('Session name for local Destination.')
|
||||||
|
def __init__(self, session, port, dest, samaddr=samaddr,
|
||||||
|
nconnect=-1, timeout=None, **kwargs):
|
||||||
|
"""Tunnels localhost:port --> I2P Destination dest.
|
||||||
|
|
||||||
|
A session named 'session' is created locally, for purposes
|
||||||
|
of routing to 'dest'. nconnect and timeout are the maximum
|
||||||
|
number of connections and maximum time per connection. All
|
||||||
|
other arguments are passed to sam.socket(). This call blocks
|
||||||
|
until the tunnel is ready."""
|
||||||
|
S = pysocket.socket(pysocket.AF_INET, pysocket.SOCK_STREAM)
|
||||||
|
S.bind(('', port))
|
||||||
|
S.listen(4)
|
||||||
|
obj = socket(session, SOCK_STREAM, samaddr, **kwargs)
|
||||||
|
self.session = session
|
||||||
|
self.dest = obj.dest
|
||||||
|
def make_send():
|
||||||
|
C = socket(session, SOCK_STREAM, samaddr, **kwargs)
|
||||||
|
C.setblocking(False)
|
||||||
|
try: C.connect(dest)
|
||||||
|
except: pass # Ignore 'would have blocked' error
|
||||||
|
return C
|
||||||
|
Tunnel.__init__(self, S, make_send, nconnect, timeout)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# End of File
|
||||||
|
# --------------------------------------------------
|
769
apps/sam/python/src/i2p/samclasses.py
Normal file
769
apps/sam/python/src/i2p/samclasses.py
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# samclasses.py: Lower-level SAM API, interfaces with SAM Bridge.
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
Lower-level SAM API, interfaces with SAM Bridge.
|
||||||
|
|
||||||
|
For internal use only.
|
||||||
|
|
||||||
|
Use the higher level i2p.sam module for your own programs.
|
||||||
|
|
||||||
|
For details on SAM, see "Simple Anonymous Messaging (SAM) v1.0,"
|
||||||
|
as published by jrandom.
|
||||||
|
|
||||||
|
Class Overview:
|
||||||
|
|
||||||
|
SAMTerminal: Message sender/reader, talks to SAM Bridge
|
||||||
|
through a single socket.
|
||||||
|
StringBuffer: Queue for character data.
|
||||||
|
BaseSession: SAM session classes are derived from this.
|
||||||
|
StreamSession: Manipulate a SAM stream session through a
|
||||||
|
threadsafe, high-level interface.
|
||||||
|
DatagramSession: SAM datagram session, threadsafe, high level.
|
||||||
|
RawSession: SAM raw session, threadsafe, high level.
|
||||||
|
|
||||||
|
Note that a 'None' timeout is an infinite timeout: it
|
||||||
|
blocks forever if necessary.
|
||||||
|
|
||||||
|
Todo:
|
||||||
|
* Error handling is a huge mess. Neaten it up.
|
||||||
|
Subclass a ErrorMixin class, then use set_error(e),
|
||||||
|
check_error(), get_error().
|
||||||
|
* Streams are a huge mess. Neaten them up.
|
||||||
|
* This whole interface is a tad confusing. Neaten it up.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
import socket, thread, threading, time, string
|
||||||
|
import Queue, traceback, random, sys, shlex
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Import i2p and i2p.sam (for defaults and errors)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
import i2p
|
||||||
|
import i2p.sam
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Functions
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
def sleep(): time.sleep(0.01) # Sleep between thread polls
|
||||||
|
|
||||||
|
sam_log = False # Logging flag. Logs to ./log.txt.
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# SAMTerminal
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
class SAMTerminal:
|
||||||
|
"""Message-by-message communication with SAM through a single
|
||||||
|
socket. _on_* messages are dispatched to msgobj."""
|
||||||
|
|
||||||
|
def __init__(self, addr, msgobj):
|
||||||
|
try: self.host, self.port = addr.split(':')
|
||||||
|
except: raise ValueError('sam port required')
|
||||||
|
self.port = int(self.port)
|
||||||
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.msgobj = msgobj
|
||||||
|
try:
|
||||||
|
self.sock.connect((self.host, self.port))
|
||||||
|
except:
|
||||||
|
raise i2p.RouterError('could not contact SAM bridge on ' +
|
||||||
|
self.host + ':' + str(self.port))
|
||||||
|
thread.start_new_thread(self._poll_loop, ())
|
||||||
|
self.error = None
|
||||||
|
self.lost_error = i2p.RouterError('SAM bridge connection lost')
|
||||||
|
|
||||||
|
def _poll_loop(self):
|
||||||
|
"""Polling loop for incoming messages."""
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Read until newline
|
||||||
|
line = []
|
||||||
|
while True:
|
||||||
|
try: c = self.sock.recv(1)
|
||||||
|
except socket.error, ex: self.error = self.lost_error
|
||||||
|
if c == '': self.error = self.lost_error
|
||||||
|
if self.error != None: return
|
||||||
|
if c == '\n': break
|
||||||
|
if c != '': line += [c]
|
||||||
|
line = ''.join(line)
|
||||||
|
if sam_log:
|
||||||
|
logf = open('log.txt', 'a')
|
||||||
|
logf.write('\n' + line + '\n')
|
||||||
|
logf.close()
|
||||||
|
(msg, kwargs) = self._samdecode(line)
|
||||||
|
# Read N bytes if SIZE=N is present.
|
||||||
|
if 'SIZE' in kwargs:
|
||||||
|
data = []
|
||||||
|
remain = int(kwargs['SIZE'])
|
||||||
|
while True:
|
||||||
|
try: s = self.sock.recv(remain)
|
||||||
|
except socket.error, ex: self.error = self.lost_error
|
||||||
|
if s == '': self.error = self.lost_error
|
||||||
|
if self.error != None: return
|
||||||
|
if s != '': data += [s]
|
||||||
|
remain -= len(s)
|
||||||
|
if remain <= 0: break
|
||||||
|
data = ''.join(data)
|
||||||
|
# Store the read data in kwargs['DATA'].
|
||||||
|
kwargs['DATA'] = data
|
||||||
|
del kwargs['SIZE']
|
||||||
|
# Dispatch the message
|
||||||
|
try: self.on_message(msg, kwargs)
|
||||||
|
except Exception, e:
|
||||||
|
# On exception in on_message, print a warning, keep going.
|
||||||
|
print 'Unhandled exception in polling thread.'
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# Don't need to sleep since recv() blocks.
|
||||||
|
# End of while loop
|
||||||
|
except Exception, e:
|
||||||
|
# For other exceptions, print a fatal error and stop polling.
|
||||||
|
print 'Fatal exception in polling thread'
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
|
||||||
|
def _samdecode(self, s):
|
||||||
|
"""Given a SAM command, returns (a, b), where a is the string at
|
||||||
|
the beginning of the command, and b is a dictionary of name,
|
||||||
|
value pairs for the command."""
|
||||||
|
(args, kwargs) = ([], {})
|
||||||
|
for w in shlex.split(s):
|
||||||
|
if '=' in w: kwargs[w.split('=')[0]] = w.split('=')[1]
|
||||||
|
else: args += [w]
|
||||||
|
return (' '.join(args), kwargs)
|
||||||
|
|
||||||
|
def check_message(self, kwargs):
|
||||||
|
"""Raises an error if kwargs['RESULT'] != 'OK'."""
|
||||||
|
if not kwargs.get('RESULT', '') in ['OK', '']:
|
||||||
|
raise i2p.sam.NetworkError((kwargs['RESULT'],
|
||||||
|
kwargs.get('MESSAGE', '')))
|
||||||
|
|
||||||
|
def on_message(self, msg, kwargs):
|
||||||
|
"""Process a SAM message that was received. Dispatch to
|
||||||
|
self._on_MESSAGE_NAME(**kwargs)."""
|
||||||
|
name = '_on_' + msg.upper().replace(' ', '_')
|
||||||
|
getattr(self.msgobj, name)(**kwargs)
|
||||||
|
|
||||||
|
def send_message(self, msg):
|
||||||
|
"""Send a message to the SAM bridge. A newline will be
|
||||||
|
automatically added if none is present."""
|
||||||
|
self.check()
|
||||||
|
if not '\n' in msg: msg = msg + '\n'
|
||||||
|
if sam_log:
|
||||||
|
logf = open('log.txt', 'a')
|
||||||
|
logf.write('\n' + msg)
|
||||||
|
logf.close()
|
||||||
|
try: self.sock.sendall(msg)
|
||||||
|
except socket.error: self.error = self.lost_error
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
"""Raise an error if terminal was closed, otherwise do
|
||||||
|
nothing."""
|
||||||
|
if self.error != None: raise self.error
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close the SAM terminal."""
|
||||||
|
# If data is sent via STREAM SEND, and the socket is closed
|
||||||
|
# immediately, the data will be lost. Delay 0.01 s to fix this
|
||||||
|
# bug (tested Windows, Linux).
|
||||||
|
time.sleep(0.01)
|
||||||
|
self.error = i2p.sam.ClosedError()
|
||||||
|
self.sock.close()
|
||||||
|
|
||||||
|
def queue_get(self, q):
|
||||||
|
"""Identical to q.get() unless a call to self.check() fails,
|
||||||
|
in which case the waiting is cut short with an error."""
|
||||||
|
while True:
|
||||||
|
try: return q.get_nowait()
|
||||||
|
except Queue.Empty: pass
|
||||||
|
self.check()
|
||||||
|
sleep()
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# StringBuffer: A FIFO for string data.
|
||||||
|
# -------------------------------------------------------
|
||||||
|
|
||||||
|
class Deque:
|
||||||
|
"""A double-ended queue."""
|
||||||
|
def __init__(self):
|
||||||
|
self.a = []
|
||||||
|
self.b = []
|
||||||
|
def push_last(self, obj):
|
||||||
|
"""Append obj to the end of the deque."""
|
||||||
|
self.b.append(obj)
|
||||||
|
def push_first(self, obj):
|
||||||
|
"""Prepend obj to the beginning of the deque."""
|
||||||
|
self.a.append(obj)
|
||||||
|
def _partition(self):
|
||||||
|
if len(self) > 1:
|
||||||
|
self.a.reverse()
|
||||||
|
all = self.a + self.b
|
||||||
|
n = len(all) / 2
|
||||||
|
self.a = all[:n]
|
||||||
|
self.b = all[n:]
|
||||||
|
self.a.reverse()
|
||||||
|
def pop_last(self):
|
||||||
|
"""Pop an item off the end of the deque, and return it."""
|
||||||
|
if not self.b: self._partition()
|
||||||
|
try: return self.b.pop()
|
||||||
|
except: return self.a.pop()
|
||||||
|
def pop_first(self):
|
||||||
|
"""Pop an item off the beginning of the deque, and return it."""
|
||||||
|
if not self.a: self._partition()
|
||||||
|
try: return self.a.pop()
|
||||||
|
except: return self.b.pop()
|
||||||
|
def __len__(self):
|
||||||
|
"""Number of items in the deque."""
|
||||||
|
return len(self.b) + len(self.a)
|
||||||
|
|
||||||
|
class StringBuffer(Deque):
|
||||||
|
"""A FIFO for characters. Strings can be efficiently
|
||||||
|
appended to the end, and read from the beginning.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
B = StringBuffer('Hello W')
|
||||||
|
B.append('orld!')
|
||||||
|
print B.read(5) # 'Hello'
|
||||||
|
print B.read() # 'World!'
|
||||||
|
"""
|
||||||
|
def __init__(self, s=''):
|
||||||
|
Deque.__init__(self)
|
||||||
|
self.length = 0
|
||||||
|
self.append(s)
|
||||||
|
def append(self, s):
|
||||||
|
"""Append string data to the end of the buffer."""
|
||||||
|
n = 128
|
||||||
|
for block in [s[i:i+n] for i in range(0,len(s),n)]:
|
||||||
|
self.push_last(block)
|
||||||
|
self.length += len(s)
|
||||||
|
def prepend(self, s):
|
||||||
|
"""Prepend string data to the beginning of the buffer."""
|
||||||
|
n = 128
|
||||||
|
blocks = [s[i:i+n] for i in range(0,len(s),n)]
|
||||||
|
blocks.reverse()
|
||||||
|
for block in blocks:
|
||||||
|
self.push_first(block)
|
||||||
|
self.length += len(s)
|
||||||
|
def read(self, n=None):
|
||||||
|
"""Read n bytes of data (or less if less data available) from the
|
||||||
|
beginning of the buffer. The data is removed. If n is
|
||||||
|
omitted, read the entire buffer."""
|
||||||
|
if n == None or n > len(self): n = len(self)
|
||||||
|
destlen = len(self) - n
|
||||||
|
ans = []
|
||||||
|
while len(self) > destlen:
|
||||||
|
ans += [self.pop_first()]
|
||||||
|
self.length -= len(ans[-1])
|
||||||
|
ans = ''.join(ans)
|
||||||
|
self.prepend(ans[n:])
|
||||||
|
ans = ans[:n]
|
||||||
|
return ans
|
||||||
|
def peek(self, n=None):
|
||||||
|
"""Like read(), but do not remove the data that is returned."""
|
||||||
|
ans = self.read(n)
|
||||||
|
self.prepend(ans)
|
||||||
|
return ans
|
||||||
|
def __len__(self): return self.length
|
||||||
|
def __str__(self): return self.peek()
|
||||||
|
def __repr__(self): return 'StringBuffer(' + str(self) + ')'
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# BaseSession
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
class BaseSession:
|
||||||
|
"""Base session, from which StreamSession, DatagramSession,
|
||||||
|
and RawSession are derived."""
|
||||||
|
|
||||||
|
def __init__(self, addr=''):
|
||||||
|
if addr == '': addr = i2p.sam.samaddr
|
||||||
|
self.term = SAMTerminal(addr=addr, msgobj=self)
|
||||||
|
self.lock = threading.Lock() # Data lock.
|
||||||
|
self.closed = False
|
||||||
|
self.qhello = Queue.Queue() # Thread messaging, HELLO REPLY.
|
||||||
|
self.qnaming = Queue.Queue() # Thread messaging, NAMING REPLY.
|
||||||
|
self.qsession = Queue.Queue() # Thread messaging, SESSION STATUS.
|
||||||
|
self._hello() # Do handshake with SAM bridge.
|
||||||
|
|
||||||
|
def _hello(self):
|
||||||
|
"""Internal command, handshake with SAM terminal."""
|
||||||
|
self.term.send_message('HELLO VERSION MIN=' +
|
||||||
|
str(i2p.sam.samver) + ' MAX=' + str(i2p.sam.samver))
|
||||||
|
self.term.check_message(self.term.queue_get(self.qhello))
|
||||||
|
|
||||||
|
def _on_HELLO_REPLY(self, **kwargs):
|
||||||
|
"""Internal command, got HELLO REPLY."""
|
||||||
|
self.qhello.put(kwargs) # Pass kwargs back to _hello.
|
||||||
|
|
||||||
|
def _on_SESSION_STATUS(self, **kwargs):
|
||||||
|
"""Internal command, got SESSION STATUS."""
|
||||||
|
self.qsession.put(kwargs) # Pass kwargs back to main thread.
|
||||||
|
|
||||||
|
def _namelookup(self, name):
|
||||||
|
"""Internal command, does a NAMING LOOKUP query."""
|
||||||
|
self.term.send_message('NAMING LOOKUP NAME=' + name)
|
||||||
|
# Read back response, check it, and return X in VALUE=X.
|
||||||
|
kwargs = self.term.queue_get(self.qnaming)
|
||||||
|
self.term.check_message(kwargs)
|
||||||
|
return kwargs['VALUE']
|
||||||
|
|
||||||
|
def _on_NAMING_REPLY(self, **kwargs):
|
||||||
|
"""Internal command, got NAMING REPLY."""
|
||||||
|
self.qnaming.put(kwargs) # Pass kwargs back to _namelookup.
|
||||||
|
|
||||||
|
def _set_properties(self):
|
||||||
|
"""Internal command, call at end of __init__ to set up
|
||||||
|
properties."""
|
||||||
|
self.dest = self._namelookup('ME')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close the session."""
|
||||||
|
# Synchronize
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
# Close the terminal if we're not already closed.
|
||||||
|
if not self.closed: self.term.close()
|
||||||
|
self.closed = True
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
def _encode_kwargs(self, **kwargs):
|
||||||
|
"""Internal command, encode extra kwargs for passing to
|
||||||
|
SESSION CREATE."""
|
||||||
|
ans = ''
|
||||||
|
for k in kwargs:
|
||||||
|
if k == 'in_depth':
|
||||||
|
ans += ' tunnels.depthInbound=' + \
|
||||||
|
str(int(kwargs['in_depth']))
|
||||||
|
elif k == 'out_depth':
|
||||||
|
ans += ' tunnels.depthOutbound=' + \
|
||||||
|
str(int(kwargs['out_depth']))
|
||||||
|
else:
|
||||||
|
raise ValueError('unexpected keyword argument ' + repr(k))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# StreamSession
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
class StreamSession(BaseSession):
|
||||||
|
"""Stream session. All methods are blocking and threadsafe."""
|
||||||
|
|
||||||
|
def __init__(self, name, addr='', **kwargs):
|
||||||
|
if addr == '': addr = i2p.sam.samaddr
|
||||||
|
BaseSession.__init__(self, addr)
|
||||||
|
self.idmap = {} # Maps id to Stream object.
|
||||||
|
self.qaccept = Queue.Queue() # Thread messaging, accept.
|
||||||
|
self.name = name
|
||||||
|
self.max_accept = 0 # Max queued incoming connections.
|
||||||
|
|
||||||
|
# Create stream session.
|
||||||
|
if name == '':
|
||||||
|
name = 'TRANSIENT'
|
||||||
|
|
||||||
|
# DIRECTION=BOTH (the default) is used because we can't know in
|
||||||
|
# advance whether a session will call listen().
|
||||||
|
|
||||||
|
self.term.send_message('SESSION CREATE STYLE=STREAM' +
|
||||||
|
' DESTINATION=' + name + self._encode_kwargs(**kwargs))
|
||||||
|
self.term.check_message(self.term.queue_get(self.qsession))
|
||||||
|
|
||||||
|
self._set_properties()
|
||||||
|
|
||||||
|
def connect(self, dest, timeout=None):
|
||||||
|
"""Create a stream connected to remote destination 'dest'. The
|
||||||
|
id is random. If the timeout is exceeded, do NOT raise an
|
||||||
|
error; rather, return a Stream object with .didconnect set
|
||||||
|
to False."""
|
||||||
|
if not isinstance(dest, type('')): raise TypeError
|
||||||
|
# Synchronize
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
# Pick a positive stream id at random.
|
||||||
|
while True:
|
||||||
|
# 9/10 probability of success per iteration
|
||||||
|
id = random.randrange(1, len(self.idmap) * 10 + 2)
|
||||||
|
if not id in self.idmap:
|
||||||
|
ans = Stream(self, dest, id, didconnect=False)
|
||||||
|
self.idmap[id] = ans
|
||||||
|
break
|
||||||
|
finally: self.lock.release()
|
||||||
|
# Send STREAM CONNECT and wait for reply.
|
||||||
|
self.term.send_message('STREAM CONNECT ID=' + str(id) +
|
||||||
|
' DESTINATION=' + str(dest))
|
||||||
|
|
||||||
|
# Now wait until the stream's .didconnect flag is set to True.
|
||||||
|
if timeout != None: end = time.clock() + timeout
|
||||||
|
while True:
|
||||||
|
self.term.check()
|
||||||
|
if ans.didconnect: break
|
||||||
|
if timeout != None and time.clock() >= end: break
|
||||||
|
sleep()
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def _on_STREAM_STATUS(self, **kwargs):
|
||||||
|
"""Internal command, got STREAM STATUS. Unblocks connect."""
|
||||||
|
# Store error is needed
|
||||||
|
try: self.term.check_message(kwargs)
|
||||||
|
except Exception, e:
|
||||||
|
try: self.idmap[int(kwargs['ID'])].err = e
|
||||||
|
except: pass # Closed too quickly
|
||||||
|
|
||||||
|
# Now set .didconnect flag to True.
|
||||||
|
try: self.idmap[int(kwargs['ID'])].didconnect = True
|
||||||
|
except: pass # Closed too quickly
|
||||||
|
|
||||||
|
def accept(self, timeout=None):
|
||||||
|
"""Wait for incoming connection, and return a Stream object
|
||||||
|
for it."""
|
||||||
|
if self.max_accept <= 0:
|
||||||
|
raise i2p.Error('listen(n) must be called before accept ' +
|
||||||
|
'(n>=1)')
|
||||||
|
if timeout != None: end = time.clock() + timeout
|
||||||
|
while True:
|
||||||
|
self.term.check()
|
||||||
|
# Synchronized
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
# Get Stream object if available.
|
||||||
|
if self.qaccept.qsize() > 0:
|
||||||
|
return self.term.queue_get(self.qaccept)
|
||||||
|
finally: self.lock.release()
|
||||||
|
if timeout != None and time.clock() >= end: break
|
||||||
|
sleep()
|
||||||
|
|
||||||
|
# Handle timeout and blocking errors
|
||||||
|
if timeout == 0.0:
|
||||||
|
raise i2p.sam.BlockError('command would have blocked')
|
||||||
|
else:
|
||||||
|
raise i2p.sam.Timeout('timed out')
|
||||||
|
|
||||||
|
def listen(self, backlog):
|
||||||
|
"""Set maximum number of queued connections."""
|
||||||
|
if self.closed: raise sam.ClosedError()
|
||||||
|
self.max_accept = backlog
|
||||||
|
|
||||||
|
def _on_STREAM_CONNECTED(self, **kwargs):
|
||||||
|
"""Got STREAM CONNECTED command. This is what accept() commands
|
||||||
|
wait for."""
|
||||||
|
# Synchronize
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
# Drop connection if over maximum size.
|
||||||
|
if self.qaccept.qsize() >= self.max_accept:
|
||||||
|
self.term.send_message('STREAM CLOSE ID=' +
|
||||||
|
str(int(kwargs['ID'])))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parse, create Stream, and place on self.qaccept.
|
||||||
|
self.term.check_message(kwargs)
|
||||||
|
# A negative id is chosen for us
|
||||||
|
id = int(kwargs['ID'])
|
||||||
|
self.idmap[id] = Stream(self, kwargs['DESTINATION'], id)
|
||||||
|
# Pass Stream object back to accept.
|
||||||
|
self.qaccept.put(self.idmap[id])
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
def _send_stream(self, id, data):
|
||||||
|
"""Internal command, send data to stream id. Use Stream.send
|
||||||
|
in your code."""
|
||||||
|
self.term.send_message('STREAM SEND ID=' + str(id) + ' SIZE=' +
|
||||||
|
str(len(data)) + '\n' + data)
|
||||||
|
|
||||||
|
def _on_STREAM_CLOSED(self, **kwargs):
|
||||||
|
"""Got STREAM CLOSED command. Call idmap[id].on_close(e) and
|
||||||
|
delete idmap[id]."""
|
||||||
|
id = int(kwargs['ID'])
|
||||||
|
|
||||||
|
# No error is produced for a graceful remote close.
|
||||||
|
e = None
|
||||||
|
try: self.term.check_message(kwargs)
|
||||||
|
except i2p.Error, err: e = err
|
||||||
|
|
||||||
|
# Synchronize
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
# Sent STREAM CLOSE, SAM didn't hear us in time.
|
||||||
|
if not id in self.idmap: return
|
||||||
|
# Pop id from self.idmap, if available.
|
||||||
|
obj = self.idmap[id]
|
||||||
|
del self.idmap[id]
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
# Process on_close message.
|
||||||
|
obj.on_close(None)
|
||||||
|
|
||||||
|
def _on_STREAM_RECEIVED(self, **kwargs):
|
||||||
|
"""Got STREAM RECEIVED command. Dispatch to
|
||||||
|
idmap[id].on_receive(s)."""
|
||||||
|
id = int(kwargs['ID'])
|
||||||
|
if not id in self.idmap:
|
||||||
|
# _on_STREAM_CONNECTED blocks until self.idmap[id] is properly
|
||||||
|
# set up. Therefore, we have received a stream packet despite
|
||||||
|
# closing the stream immediately after _on_STREAM_CONNECTED
|
||||||
|
# (SAM ignored us). So ignore it.
|
||||||
|
return
|
||||||
|
self.idmap[id].on_receive(kwargs['DATA'])
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Unconnected session; has no read data available."""
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class Stream:
|
||||||
|
"""Receives and sends data for an individual stream."""
|
||||||
|
|
||||||
|
def __init__(self, parent, remotedest, id, didconnect=True):
|
||||||
|
self.parent = parent
|
||||||
|
self.buf = StringBuffer()
|
||||||
|
self.localdest = parent.dest
|
||||||
|
self.remotedest = remotedest
|
||||||
|
self.id = id
|
||||||
|
# Data lock. Allow multiple acquire()s by same thread
|
||||||
|
self.lock = threading.RLock()
|
||||||
|
self.closed = False
|
||||||
|
# Error message, on STREAM STATUS, or on STREAM CLOSED.
|
||||||
|
self.err = None
|
||||||
|
# Whether stream got a STREAM CONNECTED message
|
||||||
|
self.didconnect = didconnect
|
||||||
|
|
||||||
|
def send(self, s):
|
||||||
|
"""Sends the string s, blocking if necessary."""
|
||||||
|
id = self.id
|
||||||
|
if self.closed or id == None:
|
||||||
|
if self.err != None: raise self.err
|
||||||
|
raise i2p.sam.ClosedError('stream closed')
|
||||||
|
if len(s) == 0: return
|
||||||
|
nmax = 32768
|
||||||
|
for block in [s[i:i+nmax] for i in range(0,len(s),nmax)]:
|
||||||
|
self.parent._send_stream(id, block)
|
||||||
|
|
||||||
|
def recv(self, n, timeout=None, peek=False, waitall=False):
|
||||||
|
"""Reads up to n bytes in a manner identical to socket.recv.
|
||||||
|
Blocks for up to timeout seconds if n > 0 and no data is
|
||||||
|
available (timeout=None means wait forever). If still no data
|
||||||
|
is available, raises BlockError or Timeout. For a closed
|
||||||
|
stream, recv will read the data stored in the buffer until
|
||||||
|
EOF, at which point the read data will be truncated. If peek
|
||||||
|
is True, the data is not removed. If waitall is True, reads
|
||||||
|
exactly n bytes, or raises BlockError or Timeout as
|
||||||
|
appropriate. Returns data."""
|
||||||
|
|
||||||
|
if n < 0: raise ValueError
|
||||||
|
if n == 0: return ''
|
||||||
|
|
||||||
|
minlen = 1
|
||||||
|
if waitall: minlen = n
|
||||||
|
|
||||||
|
if timeout != None: end = time.clock() + timeout
|
||||||
|
while True:
|
||||||
|
# Synchronized check and read until data available.
|
||||||
|
self.parent.term.check()
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
if len(self.buf) >= minlen:
|
||||||
|
if peek: return self.buf.peek(n)
|
||||||
|
else: return self.buf.read(n)
|
||||||
|
# Graceful close: return as much data as possible
|
||||||
|
# (up to n bytes).
|
||||||
|
if self.closed and self.err == None: return self.buf.read(n)
|
||||||
|
# Ungraceful close: raise an error.
|
||||||
|
if self.err != None: raise self.err
|
||||||
|
finally: self.lock.release()
|
||||||
|
if timeout != None and time.clock() >= end: break
|
||||||
|
sleep()
|
||||||
|
|
||||||
|
# Handle timeout and blocking error
|
||||||
|
if timeout == 0.0:
|
||||||
|
raise i2p.sam.BlockError('command would have blocked')
|
||||||
|
else:
|
||||||
|
raise i2p.sam.Timeout('timed out')
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Current length of read buffer."""
|
||||||
|
return len(self.buf)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close the stream. Threadsafe."""
|
||||||
|
# Synchronize self.parent.
|
||||||
|
self.parent.lock.acquire()
|
||||||
|
try:
|
||||||
|
if not self.closed:
|
||||||
|
self.closed = True
|
||||||
|
id = self.id
|
||||||
|
# Set self.id to None, so we don't close a new stream by
|
||||||
|
# accident.
|
||||||
|
self.id = None
|
||||||
|
if not id in self.parent.idmap: return
|
||||||
|
self.parent.term.send_message('STREAM CLOSE ID=' + str(id))
|
||||||
|
# No error is produced for a locally closed stream
|
||||||
|
self.on_close(None)
|
||||||
|
del self.parent.idmap[id]
|
||||||
|
finally: self.parent.lock.release()
|
||||||
|
|
||||||
|
def on_receive(self, s):
|
||||||
|
# Synchronize
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
self.buf.append(s)
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
def on_close(self, e):
|
||||||
|
self.closed = True
|
||||||
|
self.err = e
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# DatagramSession
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
class DatagramSession(BaseSession):
|
||||||
|
"""Datagram session. All methods are blocking and threadsafe."""
|
||||||
|
|
||||||
|
def __init__(self, name, addr='', **kwargs):
|
||||||
|
if addr == '': addr = i2p.sam.samaddr
|
||||||
|
BaseSession.__init__(self, addr)
|
||||||
|
self.buf = Deque() # FIFO of incoming packets.
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# Create datagram session
|
||||||
|
if name == '': name = 'TRANSIENT'
|
||||||
|
self.term.send_message('SESSION CREATE STYLE=DATAGRAM ' +
|
||||||
|
'DESTINATION=' + name + self._encode_kwargs(**kwargs))
|
||||||
|
self.term.check_message(self.term.queue_get(self.qsession))
|
||||||
|
|
||||||
|
self._set_properties()
|
||||||
|
|
||||||
|
def _on_DATAGRAM_RECEIVED(self, **kwargs):
|
||||||
|
"""Internal method, got DATAGRAM RECEIVED."""
|
||||||
|
# Synchronized
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
self.buf.push_last((kwargs['DATA'], kwargs['DESTINATION']))
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
def send(self, s, dest):
|
||||||
|
"""Send packet with contents s to given destination."""
|
||||||
|
# Raise error if packet is too large.
|
||||||
|
if len(s) > i2p.sam.MAX_DGRAM:
|
||||||
|
raise ValueError('packets must have length <= ' +
|
||||||
|
str(i2p.sam.MAX_DGRAM) + ' bytes')
|
||||||
|
self.term.send_message('DATAGRAM SEND DESTINATION=' + dest +
|
||||||
|
' SIZE=' + str(len(s)) + '\n' + s)
|
||||||
|
|
||||||
|
def recv(self, timeout=None, peek=False):
|
||||||
|
"""Get a single packet. Blocks for up to timeout seconds if
|
||||||
|
n > 0 and no packet is available (timeout=None means wait
|
||||||
|
forever). If still no packet is available, raises BlockError
|
||||||
|
or Timeout. Returns the pair (data, address). If peek is
|
||||||
|
True, the data is not removed."""
|
||||||
|
if timeout != None: end = time.clock() + timeout
|
||||||
|
while True:
|
||||||
|
self.term.check()
|
||||||
|
# Synchronized check and read until data available.
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
if len(self.buf) > 0:
|
||||||
|
if peek:
|
||||||
|
ans = self.buf.pop_first()
|
||||||
|
self.buf.push_first(ans)
|
||||||
|
return ans
|
||||||
|
else:
|
||||||
|
return self.buf.pop_first()
|
||||||
|
finally: self.lock.release()
|
||||||
|
if timeout != None and time.clock() >= end: break
|
||||||
|
sleep()
|
||||||
|
|
||||||
|
# Handle timeout and blocking error
|
||||||
|
if timeout == 0.0:
|
||||||
|
raise i2p.sam.BlockError('command would have blocked')
|
||||||
|
else:
|
||||||
|
raise i2p.sam.Timeout('timed out')
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Number of packets in read buffer."""
|
||||||
|
return len(self.buf)
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# RawSession
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
class RawSession(BaseSession):
|
||||||
|
"""Raw session. All methods are blocking and threadsafe."""
|
||||||
|
|
||||||
|
def __init__(self, name, addr='', **kwargs):
|
||||||
|
if addr == '': addr = i2p.sam.samaddr
|
||||||
|
BaseSession.__init__(self, addr)
|
||||||
|
self.buf = Deque() # FIFO of incoming packets.
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# Create raw session
|
||||||
|
if name == '': name = 'TRANSIENT'
|
||||||
|
self.term.send_message('SESSION CREATE STYLE=RAW DESTINATION=' +
|
||||||
|
name + self._encode_kwargs(**kwargs))
|
||||||
|
self.term.check_message(self.term.queue_get(self.qsession))
|
||||||
|
|
||||||
|
self._set_properties()
|
||||||
|
|
||||||
|
def _on_RAW_RECEIVED(self, **kwargs):
|
||||||
|
"""Internal method, got RAW RECEIVED."""
|
||||||
|
# Synchronized
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
self.buf.push_last((kwargs['DATA'], ''))
|
||||||
|
finally: self.lock.release()
|
||||||
|
|
||||||
|
def send(self, s, dest):
|
||||||
|
"""Send packet with contents s to given destination."""
|
||||||
|
# Raise error if packet is too big
|
||||||
|
if len(s) > i2p.sam.MAX_RAW:
|
||||||
|
raise ValueError('packets must have length <= ' +
|
||||||
|
str(i2p.sam.MAX_RAW) + ' bytes')
|
||||||
|
self.term.send_message('RAW SEND DESTINATION=' + dest +
|
||||||
|
' SIZE=' + str(len(s)) + '\n' + s)
|
||||||
|
|
||||||
|
def recv(self, timeout=None, peek=False):
|
||||||
|
"""Identical to DatagramSocket.recv. The from address is an
|
||||||
|
empty string."""
|
||||||
|
if timeout != None: end = time.clock() + timeout
|
||||||
|
while True:
|
||||||
|
self.term.check()
|
||||||
|
# Synchronized check and read until data available.
|
||||||
|
self.lock.acquire()
|
||||||
|
try:
|
||||||
|
if len(self.buf) > 0:
|
||||||
|
if peek:
|
||||||
|
ans = self.buf.pop_first()
|
||||||
|
self.buf.push_first(ans)
|
||||||
|
return ans
|
||||||
|
else:
|
||||||
|
return self.buf.pop_first()
|
||||||
|
finally: self.lock.release()
|
||||||
|
if timeout != None and time.clock() >= end: break
|
||||||
|
sleep()
|
||||||
|
|
||||||
|
# Handle timeout and blocking error
|
||||||
|
if timeout == 0.0:
|
||||||
|
raise i2p.sam.BlockError('command would have blocked')
|
||||||
|
else:
|
||||||
|
raise i2p.sam.Timeout('timed out')
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Number of packets in read buffer."""
|
||||||
|
return len(self.buf)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# End of file
|
||||||
|
# -----------------------------------------------------
|
6
apps/sam/python/src/i2p/test/readme.txt
Normal file
6
apps/sam/python/src/i2p/test/readme.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
Unit tests for I2P Python interface.
|
||||||
|
|
||||||
|
Note that these aren't all unit tests yet.
|
||||||
|
|
||||||
|
Some are demos, some require manual intervention.
|
37
apps/sam/python/src/i2p/test/test_eep.py
Normal file
37
apps/sam/python/src/i2p/test/test_eep.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# test_eep.py: Unit tests for eep.py.
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
# Make sure we can import i2p
|
||||||
|
import sys; sys.path += ['../../']
|
||||||
|
|
||||||
|
import traceback, sys
|
||||||
|
from i2p import eep, sam, samclasses
|
||||||
|
|
||||||
|
def verify_html(s):
|
||||||
|
"""Raise an error if s does not end with </html>"""
|
||||||
|
assert s.strip().lower()[-7:] == '</html>'
|
||||||
|
|
||||||
|
def eepget_test():
|
||||||
|
try:
|
||||||
|
verify_html(eep.urlget('http://duck.i2p/index.html'))
|
||||||
|
verify_html(eep.urlget('http://duck.i2p/'))
|
||||||
|
verify_html(eep.urlget('http://duck.i2p'))
|
||||||
|
verify_html(eep.urlget('duck.i2p/'))
|
||||||
|
verify_html(eep.urlget('duck.i2p'))
|
||||||
|
except Exception, e:
|
||||||
|
print 'Unit test failed for eepget'
|
||||||
|
print "Note that urllib2.urlopen uses IE's proxy settings " + \
|
||||||
|
"in Windows."
|
||||||
|
print "This may cause " + \
|
||||||
|
"urllib2.urlopen('http://www.google.com/') to fail."
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
print 'eepget: OK'
|
||||||
|
|
||||||
|
def test():
|
||||||
|
eepget_test()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print 'Testing:'
|
||||||
|
test()
|
442
apps/sam/python/src/i2p/test/test_sam.py
Normal file
442
apps/sam/python/src/i2p/test/test_sam.py
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# test_sam.py: Unit tests for sam.py.
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
# Make sure we can import i2p
|
||||||
|
import sys; sys.path += ['../../']
|
||||||
|
|
||||||
|
import traceback, time, thread, threading, random, copy
|
||||||
|
from i2p import eep, sam
|
||||||
|
|
||||||
|
def test_passed(s, msg='OK'):
|
||||||
|
"""Notify user that the given unit test passed."""
|
||||||
|
print ' ' + (s + ':').ljust(50) + msg
|
||||||
|
|
||||||
|
def verify_html(s):
|
||||||
|
"""Raise an error if s does not end with </html>"""
|
||||||
|
assert s.strip().lower()[-7:] == '</html>'
|
||||||
|
|
||||||
|
def stream_client(dest):
|
||||||
|
"""Sub-unit test for sam.socket in SOCK_STREAM mode."""
|
||||||
|
S = sam.socket('Alice', sam.SOCK_STREAM)
|
||||||
|
S.connect(dest)
|
||||||
|
S.send('GET / HTTP/1.0\r\n\r\n') # Send request
|
||||||
|
f = S.makefile() # File object
|
||||||
|
|
||||||
|
while True: # Read header
|
||||||
|
line = f.readline().strip() # Read a line
|
||||||
|
if line == '': break # Content begins
|
||||||
|
|
||||||
|
s = f.read() # Get content
|
||||||
|
f.close()
|
||||||
|
S.close()
|
||||||
|
|
||||||
|
def stream_client_test():
|
||||||
|
"""Unit test for sam.socket in SOCK_STREAM mode."""
|
||||||
|
url = 'morph.i2p'
|
||||||
|
stream_client('http://' + url + '/')
|
||||||
|
stream_client(url)
|
||||||
|
stream_client(url + '/')
|
||||||
|
stream_client('http://' + url)
|
||||||
|
stream_client(sam.resolve('http://' + url + '/'))
|
||||||
|
test_passed('sam.socket stream client')
|
||||||
|
|
||||||
|
def packet_test(raw=True):
|
||||||
|
"""Unit test for sam.socket in SOCK_DGRAM or SOCK_RAW modes."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
multithread_wait_time = 500.0
|
||||||
|
may_need_increase = False
|
||||||
|
|
||||||
|
if raw:
|
||||||
|
C = sam.socket('Carola', sam.SOCK_RAW, in_depth=0, out_depth=0)
|
||||||
|
D = sam.socket('Davey', sam.SOCK_RAW, in_depth=0, out_depth=0)
|
||||||
|
else:
|
||||||
|
C = sam.socket('Carol', sam.SOCK_DGRAM,in_depth=0,out_depth=0)
|
||||||
|
D = sam.socket('Dave', sam.SOCK_DGRAM, in_depth=0, out_depth=0)
|
||||||
|
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock
|
||||||
|
C_recv = [] # Packets C *should* receive
|
||||||
|
D_recv = [] # Packets D *should* receive
|
||||||
|
C_got = [] # Packets C actually got
|
||||||
|
D_got = [] # Packets D actually got
|
||||||
|
|
||||||
|
n = 50 # Create n threads
|
||||||
|
m = 40 # Each thread sends m packets
|
||||||
|
|
||||||
|
global __done_count
|
||||||
|
__done_count = 0
|
||||||
|
__lock = threading.Lock()
|
||||||
|
|
||||||
|
# Use C and D to send and read in many different threads.
|
||||||
|
def f():
|
||||||
|
# This code is run in each separate thread
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock, __done_count
|
||||||
|
for i in range(m):
|
||||||
|
# Random binary string of length 2-80.
|
||||||
|
index_list = range(random.randrange(2, 80))
|
||||||
|
s = ''.join([chr(random.randrange(256)) for j in index_list])
|
||||||
|
if random.randrange(2) == 0:
|
||||||
|
# Send packet from C to D, and log it.
|
||||||
|
C.sendto(s, 0, D.dest)
|
||||||
|
__lock.acquire()
|
||||||
|
D_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
else:
|
||||||
|
# Send packet from D to C, and log it.
|
||||||
|
D.sendto(s, 0, C.dest)
|
||||||
|
__lock.acquire()
|
||||||
|
C_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
time.sleep(0.01*random.uniform(0.0,1.0))
|
||||||
|
# Read any available packets.
|
||||||
|
try: (p, fromaddr) = C.recvfrom(1000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == D.dest
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
try: (p, fromaddr) = D.recvfrom(1000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == C.dest
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
__done_count += 1
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
# Create n threads.
|
||||||
|
for i in range(n):
|
||||||
|
threading.Thread(target=f).start()
|
||||||
|
|
||||||
|
# Wait for them to finish.
|
||||||
|
while __done_count < n: time.sleep(0.01)
|
||||||
|
|
||||||
|
# Read any left-over received packets.
|
||||||
|
end_time = time.clock() + multithread_wait_time
|
||||||
|
while time.clock() < end_time:
|
||||||
|
# Read any available packets.
|
||||||
|
try: (p, fromaddr) = C.recvfrom(1000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == D.dest
|
||||||
|
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
|
||||||
|
try: (p, fromaddr) = D.recvfrom(1000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == C.dest
|
||||||
|
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
if len(C_got) == len(C_recv) and len(D_got) == len(D_recv):
|
||||||
|
break
|
||||||
|
|
||||||
|
if time.clock() >= end_time:
|
||||||
|
may_need_increase = True
|
||||||
|
|
||||||
|
C_got.sort()
|
||||||
|
D_got.sort()
|
||||||
|
C_recv.sort()
|
||||||
|
D_recv.sort()
|
||||||
|
|
||||||
|
assert C_got == C_recv
|
||||||
|
assert D_got == D_recv
|
||||||
|
|
||||||
|
C.close()
|
||||||
|
D.close()
|
||||||
|
except:
|
||||||
|
if raw:
|
||||||
|
print 'Unit test failed for sam.socket (SOCK_RAW).'
|
||||||
|
print 'Raw packets are not reliable.'
|
||||||
|
else:
|
||||||
|
print 'Unit test failed for sam.socket (SOCK_DGRAM).'
|
||||||
|
print 'Datagram packets are not reliable.'
|
||||||
|
|
||||||
|
if may_need_increase:
|
||||||
|
print 'Try increasing multithread_wait_time.'
|
||||||
|
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
|
||||||
|
if raw:
|
||||||
|
test_passed('sam.socket (SOCK_RAW)')
|
||||||
|
else:
|
||||||
|
test_passed('sam.socket (SOCK_RAW)')
|
||||||
|
|
||||||
|
def stream_test():
|
||||||
|
"""Multithreaded unit test for sam.socket (SOCK_STREAM)."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
multithread_wait_time = 200.0
|
||||||
|
may_need_increase = False
|
||||||
|
|
||||||
|
kwargs = {'in_depth':0, 'out_depth':0}
|
||||||
|
C = sam.socket('Carolic', sam.SOCK_STREAM, **kwargs)
|
||||||
|
D = sam.socket('David', sam.SOCK_STREAM, **kwargs)
|
||||||
|
Cout = sam.socket('Carolic', sam.SOCK_STREAM, **kwargs)
|
||||||
|
Dout = sam.socket('David', sam.SOCK_STREAM, **kwargs)
|
||||||
|
|
||||||
|
assert C.dest == Cout.dest
|
||||||
|
assert D.dest == Dout.dest
|
||||||
|
|
||||||
|
C.listen(5)
|
||||||
|
D.listen(5)
|
||||||
|
Cout.connect(D.dest)
|
||||||
|
Dout.connect(C.dest)
|
||||||
|
(Cin, ignoredest) = C.accept()
|
||||||
|
(Din, ignoredest) = D.accept()
|
||||||
|
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock
|
||||||
|
C_recv = [] # String data C *should* receive
|
||||||
|
D_recv = [] # String data D *should* receive
|
||||||
|
C_got = [] # String data C actually got
|
||||||
|
D_got = [] # String data D actually got
|
||||||
|
|
||||||
|
n = 50 # Create n threads
|
||||||
|
m = 40 # Each thread sends m strings
|
||||||
|
|
||||||
|
global __done_count
|
||||||
|
__done_count = 0
|
||||||
|
__lock = threading.Lock()
|
||||||
|
|
||||||
|
# Use C and D to send and read in many different threads.
|
||||||
|
def f():
|
||||||
|
# This code is run in each separate thread
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock, __done_count
|
||||||
|
for i in range(m):
|
||||||
|
# Random binary string of length 2-80.
|
||||||
|
index_list = range(random.randrange(2, 80))
|
||||||
|
s = ''.join([chr(random.randrange(256)) for j in index_list])
|
||||||
|
if random.randrange(2) == 0:
|
||||||
|
# Send packet from C to D, and log it.
|
||||||
|
__lock.acquire()
|
||||||
|
Cout.send(s)
|
||||||
|
D_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
else:
|
||||||
|
# Send packet from D to C, and log it.
|
||||||
|
__lock.acquire()
|
||||||
|
Dout.send(s)
|
||||||
|
C_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
time.sleep(0.01*random.uniform(0.0,1.0))
|
||||||
|
# Read any available string data, non-blocking.
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
try: p = Cin.recv(100000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
try: p = Din.recv(100000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
__done_count += 1
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
# Create n threads.
|
||||||
|
for i in range(n):
|
||||||
|
threading.Thread(target=f).start()
|
||||||
|
|
||||||
|
# Wait for them to finish.
|
||||||
|
while __done_count < n: time.sleep(0.01)
|
||||||
|
|
||||||
|
# Read any left-over received string data.
|
||||||
|
end_time = time.clock() + multithread_wait_time
|
||||||
|
while time.clock() < end_time:
|
||||||
|
# Read any available string data, non-blocking.
|
||||||
|
try: p = Cin.recv(100000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
|
||||||
|
try: p = Din.recv(100000, sam.MSG_DONTWAIT)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
|
||||||
|
if len(''.join(C_got)) == len(''.join(C_recv)) and \
|
||||||
|
len(''.join(D_got)) == len(''.join(D_recv)):
|
||||||
|
break
|
||||||
|
|
||||||
|
if time.clock() >= end_time:
|
||||||
|
may_need_increase = True
|
||||||
|
|
||||||
|
C_got = ''.join(C_got)
|
||||||
|
D_got = ''.join(D_got)
|
||||||
|
C_recv = ''.join(C_recv)
|
||||||
|
D_recv = ''.join(D_recv)
|
||||||
|
assert C_got == C_recv
|
||||||
|
assert D_got == D_recv
|
||||||
|
|
||||||
|
Cin.close()
|
||||||
|
Din.close()
|
||||||
|
Cout.close()
|
||||||
|
Dout.close()
|
||||||
|
C.close()
|
||||||
|
D.close()
|
||||||
|
except:
|
||||||
|
print 'Unit test failed for sam.socket ' + \
|
||||||
|
'(SOCK_STREAM, multithreaded).'
|
||||||
|
|
||||||
|
if may_need_increase:
|
||||||
|
print 'Try increasing multithread_wait_time.'
|
||||||
|
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
|
||||||
|
test_passed('sam.socket (SOCK_STREAM, multithreaded)')
|
||||||
|
|
||||||
|
|
||||||
|
def noblock_stream_test():
|
||||||
|
"""Unit test for non-blocking stream commands and listen."""
|
||||||
|
|
||||||
|
serv = sam.socket('Allison',sam.SOCK_STREAM,in_depth=0,out_depth=0)
|
||||||
|
serv.setblocking(False)
|
||||||
|
serv.listen(100)
|
||||||
|
assert serv.gettimeout() == 0.0
|
||||||
|
|
||||||
|
msg_to_client = 'Hi, client!!!!'
|
||||||
|
msg_to_server = 'Hi, server!'
|
||||||
|
|
||||||
|
nconnects = 5
|
||||||
|
|
||||||
|
global server_done, client_count, client_lock
|
||||||
|
server_done = False
|
||||||
|
client_count = 0
|
||||||
|
client_lock = threading.Lock()
|
||||||
|
|
||||||
|
def serv_func(n = nconnects):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
(C, ignoredest) = serv.accept()
|
||||||
|
C.send(msg_to_client)
|
||||||
|
rmsg = C.recv(len(msg_to_server), sam.MSG_WAITALL)
|
||||||
|
if rmsg != msg_to_server:
|
||||||
|
raise ValueError('message should have been: ' +
|
||||||
|
repr(msg_to_server) + ' was: ' + repr(rmsg))
|
||||||
|
C.close()
|
||||||
|
n -= 1
|
||||||
|
if n == 0: break
|
||||||
|
except sam.BlockError:
|
||||||
|
pass
|
||||||
|
time.sleep(0.01)
|
||||||
|
global server_done
|
||||||
|
server_done = True
|
||||||
|
|
||||||
|
def client_func():
|
||||||
|
# FIXME: i2p.sam.NetworkError('TIMEOUT', '') errors are produced
|
||||||
|
# for our streams if we use '' for all clients. Why?
|
||||||
|
C = sam.socket('Bobb', sam.SOCK_STREAM, in_depth=0, out_depth=0)
|
||||||
|
C.setblocking(False)
|
||||||
|
try:
|
||||||
|
C.connect(serv.dest)
|
||||||
|
except sam.BlockError:
|
||||||
|
# One could also use timeout=0.1 and loop
|
||||||
|
(Rlist, Wlist, Elist) = sam.select([C], [C], [C])
|
||||||
|
if len(Elist) > 0:
|
||||||
|
assert Elist[0] == C
|
||||||
|
raise Elist[0].sessobj.err
|
||||||
|
C.send(msg_to_server)
|
||||||
|
C.setblocking(True)
|
||||||
|
rmsg = C.recv(len(msg_to_client), sam.MSG_WAITALL)
|
||||||
|
if rmsg != msg_to_client:
|
||||||
|
raise ValueError('message should have been: ' +
|
||||||
|
repr(msg_to_client) + ' was: ' + repr(rmsg))
|
||||||
|
C.close()
|
||||||
|
global client_count, client_lock
|
||||||
|
|
||||||
|
# Synchronized
|
||||||
|
client_lock.acquire()
|
||||||
|
try: client_count += 1
|
||||||
|
finally: client_lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
thread.start_new_thread(serv_func, ())
|
||||||
|
|
||||||
|
for i in range(nconnects):
|
||||||
|
thread.start_new_thread(client_func, ())
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if server_done and client_count == nconnects: break
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
test_passed('sam.listen (SOCK_STREAM), and non-blocking IO')
|
||||||
|
|
||||||
|
def tunnel_server_demo():
|
||||||
|
"""Demo for TunnelServer."""
|
||||||
|
|
||||||
|
T = sam.TunnelServer('Alisick', 8080, in_depth=0, out_depth=0)
|
||||||
|
|
||||||
|
print 'Server ready at:'
|
||||||
|
print T.dest
|
||||||
|
while True:
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def tunnel_client_demo():
|
||||||
|
"""Demo for TunnelClient."""
|
||||||
|
|
||||||
|
T = sam.TunnelClient('Alliaha', 8001, 'duck.i2p', \
|
||||||
|
in_depth=0, out_depth=0)
|
||||||
|
|
||||||
|
print 'Serving up duck.i2p at http://127.0.0.1:8001/'
|
||||||
|
while True:
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# select, poll
|
||||||
|
# tunnel_client, tunnel_server
|
||||||
|
# noblocking unit tests
|
||||||
|
|
||||||
|
def multi_stream_test(n):
|
||||||
|
"""See if we can have n streams open at once."""
|
||||||
|
server = None
|
||||||
|
client = [None] * n
|
||||||
|
|
||||||
|
server = sam.socket('Aligi',sam.SOCK_STREAM,in_depth=0,out_depth=0)
|
||||||
|
server.listen(n)
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
client[i] = sam.socket('Bobo', sam.SOCK_STREAM, \
|
||||||
|
in_depth=0, out_depth=0)
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
client[i].connect(server.dest)
|
||||||
|
client[i].send('Hi')
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
client[i].close()
|
||||||
|
server.close()
|
||||||
|
|
||||||
|
test_passed(str(n) + ' streams open at once')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Todo: Write unit tests for TunnelServer, TunnelClient.
|
||||||
|
|
||||||
|
def test():
|
||||||
|
print 'Testing:'
|
||||||
|
print "Comment and uncomment tests manually, if they don't finish."
|
||||||
|
|
||||||
|
# noblock_stream_test()
|
||||||
|
# stream_client_test()
|
||||||
|
# packet_test(raw=True)
|
||||||
|
# stream_test()
|
||||||
|
# multi_stream_test(200)
|
||||||
|
|
||||||
|
# Demos (manual unit tests):
|
||||||
|
# tunnel_server_demo()
|
||||||
|
# tunnel_client_demo() # This fails too
|
||||||
|
|
||||||
|
# Note: The datagram unit test fails, apparently due to a bug in I2P
|
||||||
|
# (packet loss).
|
||||||
|
## packet_test(raw=False)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
446
apps/sam/python/src/i2p/test/test_samclasses.py
Normal file
446
apps/sam/python/src/i2p/test/test_samclasses.py
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# test_samclasses.py: Unit tests for samclasses.py.
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
# Make sure we can import i2p
|
||||||
|
import sys; sys.path += ['../../']
|
||||||
|
|
||||||
|
import traceback, time, thread, threading, random
|
||||||
|
from i2p import eep, sam, samclasses
|
||||||
|
|
||||||
|
def test_passed(s, msg='OK'):
|
||||||
|
"""Notify user that the given unit test passed."""
|
||||||
|
print ' ' + (s + ':').ljust(50) + msg
|
||||||
|
|
||||||
|
def verify_html(s):
|
||||||
|
"""Raise an error if s does not end with </html>"""
|
||||||
|
assert s.strip().lower()[-7:] == '</html>'
|
||||||
|
|
||||||
|
def resolve_test(name='duck.i2p'):
|
||||||
|
"""Unit test for resolve."""
|
||||||
|
try:
|
||||||
|
rname = sam.resolve(name)
|
||||||
|
except:
|
||||||
|
print 'Unit test failed for sam.resolve'
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
|
||||||
|
test_passed('sam.resolve', 'See below')
|
||||||
|
print ' Use hosts.txt to verify that ' + name + '=' + \
|
||||||
|
rname[:15] + '...'
|
||||||
|
|
||||||
|
def raw_test1():
|
||||||
|
"""Unit test for samclasses.RawSession."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
C = samclasses.RawSession('Carol')
|
||||||
|
D = samclasses.RawSession('Dave')
|
||||||
|
|
||||||
|
C.send('Hello!', D.dest)
|
||||||
|
D.send('Hi C!', C.dest)
|
||||||
|
|
||||||
|
(packet, addr) = C.recv(1000)
|
||||||
|
assert packet == 'Hi C!'
|
||||||
|
(packet, addr) = D.recv(1000)
|
||||||
|
assert packet == 'Hello!'
|
||||||
|
C.close()
|
||||||
|
D.close()
|
||||||
|
except:
|
||||||
|
print 'Unit test failed for samclasses.RawSession'
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
test_passed('samclasses.RawSession')
|
||||||
|
|
||||||
|
def datagram_test1():
|
||||||
|
"""Unit test for samclasses.DatagramSession."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
C = samclasses.DatagramSession('Carol')
|
||||||
|
D = samclasses.DatagramSession('Dave')
|
||||||
|
|
||||||
|
C.send('Hello!', D.dest)
|
||||||
|
D.send('Hi C!', C.dest)
|
||||||
|
|
||||||
|
(packet, remotedest) = C.recv(1000)
|
||||||
|
assert str(packet) == 'Hi C!' and remotedest == D.dest
|
||||||
|
(packet, remotedest) = D.recv(1000)
|
||||||
|
assert str(packet) == 'Hello!' and remotedest == C.dest
|
||||||
|
C.close()
|
||||||
|
D.close()
|
||||||
|
except:
|
||||||
|
print 'Unit test failed for samclasses.DatagramSession'
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
test_passed('samclasses.DatagramSession')
|
||||||
|
|
||||||
|
def stream_readline(S):
|
||||||
|
"""Read a line, with a \r\n newline, including trailing \r\n."""
|
||||||
|
ans = []
|
||||||
|
while True:
|
||||||
|
c = S.recv(1)
|
||||||
|
if c == '': break
|
||||||
|
if c == '\n': break
|
||||||
|
ans += [c]
|
||||||
|
return ''.join(ans)
|
||||||
|
|
||||||
|
def stream_http_get(S, dest):
|
||||||
|
"""Get contents of http://dest/ via HTTP/1.0 and
|
||||||
|
samclasses.StreamSession S."""
|
||||||
|
C = S.connect(dest)
|
||||||
|
|
||||||
|
C.send('GET / HTTP/1.0\r\n\r\n')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = stream_readline(C).strip()
|
||||||
|
if line.find('Content-Length: ') == 0:
|
||||||
|
clen = int(line.split()[1])
|
||||||
|
if line == '': break
|
||||||
|
|
||||||
|
s = C.recv(clen, timeout=None)
|
||||||
|
time.sleep(2.0)
|
||||||
|
C.close()
|
||||||
|
return s
|
||||||
|
|
||||||
|
def stream_test1():
|
||||||
|
"""Unit test for samclasses.StreamSession.connect."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
dest = sam.resolve('duck.i2p')
|
||||||
|
S = samclasses.StreamSession('Bob')
|
||||||
|
verify_html(stream_http_get(S, dest))
|
||||||
|
verify_html(stream_http_get(S, dest))
|
||||||
|
verify_html(stream_http_get(S, dest))
|
||||||
|
S.close()
|
||||||
|
|
||||||
|
except:
|
||||||
|
print 'Unit test failed for samclasses.StreamSession'
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
test_passed('samclasses.StreamSession.connect')
|
||||||
|
|
||||||
|
def stream_test2():
|
||||||
|
"""Unit test for samclasses.StreamSession.accept."""
|
||||||
|
global __server_done, __client_done, __err
|
||||||
|
__server_done = False
|
||||||
|
__client_done = False
|
||||||
|
__err = None
|
||||||
|
|
||||||
|
S = samclasses.StreamSession('Bob')
|
||||||
|
S.listen(10)
|
||||||
|
msg = '<h1>Hello!</h1>'
|
||||||
|
|
||||||
|
def serve():
|
||||||
|
try:
|
||||||
|
# Serve 3 connections, then quit.
|
||||||
|
for i in range(3):
|
||||||
|
C = S.accept() # Get a connection.
|
||||||
|
req = stream_readline(C) # Read HTTP request.
|
||||||
|
|
||||||
|
s = msg # Message to send back
|
||||||
|
|
||||||
|
C.send('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n' +
|
||||||
|
'Content-Length: ' + str(int(len(s))) + '\r\n\r\n' + s)
|
||||||
|
|
||||||
|
if i % 2 == 0: C.close() # Close connection
|
||||||
|
S.close()
|
||||||
|
except Exception, e:
|
||||||
|
global __err
|
||||||
|
__err = e
|
||||||
|
global __server_done
|
||||||
|
__server_done = True
|
||||||
|
|
||||||
|
thread.start_new_thread(serve, ())
|
||||||
|
# Wait for accept to kick in (should work without).
|
||||||
|
time.sleep(2.0)
|
||||||
|
|
||||||
|
def client():
|
||||||
|
try:
|
||||||
|
S2 = samclasses.StreamSession('Carol')
|
||||||
|
# Get / on server three times.
|
||||||
|
assert stream_http_get(S2, S.dest) == msg
|
||||||
|
assert stream_http_get(S2, S.dest) == msg
|
||||||
|
assert stream_http_get(S2, S.dest) == msg
|
||||||
|
S2.close()
|
||||||
|
except Exception, e:
|
||||||
|
global __err
|
||||||
|
__err = e
|
||||||
|
global __client_done
|
||||||
|
__client_done = True
|
||||||
|
|
||||||
|
thread.start_new_thread(client, ())
|
||||||
|
|
||||||
|
while not (__client_done and __server_done): time.sleep(0.01)
|
||||||
|
|
||||||
|
if __err != None:
|
||||||
|
print 'Unit test failed for samclasses.StreamSession.accept'
|
||||||
|
raise __err
|
||||||
|
test_passed('samclasses.StreamSession.accept')
|
||||||
|
|
||||||
|
def multithread_packet_test(raw=True):
|
||||||
|
"""If raw: Multithreaded unit test for samclasses.RawSession.
|
||||||
|
Not raw: Multithreaded unit test for samclasses.DatagramSession.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
multithread_wait_time = 200.0
|
||||||
|
may_need_increase = False
|
||||||
|
|
||||||
|
if raw:
|
||||||
|
C = samclasses.RawSession('Carol', in_depth=0, out_depth=0)
|
||||||
|
D = samclasses.RawSession('Dave', in_depth=0, out_depth=0)
|
||||||
|
else:
|
||||||
|
C = samclasses.DatagramSession('Carol',in_depth=0,out_depth=0)
|
||||||
|
D = samclasses.DatagramSession('Dave',in_depth=0,out_depth=0)
|
||||||
|
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock
|
||||||
|
C_recv = [] # Packets C *should* receive
|
||||||
|
D_recv = [] # Packets D *should* receive
|
||||||
|
C_got = [] # Packets C actually got
|
||||||
|
D_got = [] # Packets D actually got
|
||||||
|
|
||||||
|
n = 50 # Create n threads
|
||||||
|
m = 40 # Each thread sends m packets
|
||||||
|
|
||||||
|
global __done_count
|
||||||
|
__done_count = 0
|
||||||
|
__lock = threading.Lock()
|
||||||
|
|
||||||
|
# Use C and D to send and read in many different threads.
|
||||||
|
def f():
|
||||||
|
# This code is run in each separate thread
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock, __done_count
|
||||||
|
for i in range(m):
|
||||||
|
# Random binary string of length 2-80.
|
||||||
|
index_list = range(random.randrange(2, 80))
|
||||||
|
s = ''.join([chr(random.randrange(256)) for j in index_list])
|
||||||
|
if random.randrange(2) == 0:
|
||||||
|
# Send packet from C to D, and log it.
|
||||||
|
C.send(s, D.dest)
|
||||||
|
__lock.acquire()
|
||||||
|
D_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
else:
|
||||||
|
# Send packet from D to C, and log it.
|
||||||
|
D.send(s, C.dest)
|
||||||
|
__lock.acquire()
|
||||||
|
C_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
time.sleep(0.01*random.uniform(0.0,1.0))
|
||||||
|
# Read any available packets.
|
||||||
|
try: (p, fromaddr) = C.recv(timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == D.dest
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
try: (p, fromaddr) = D.recv(timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == C.dest
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
__done_count += 1
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
# Create n threads.
|
||||||
|
for i in range(n):
|
||||||
|
threading.Thread(target=f).start()
|
||||||
|
|
||||||
|
# Wait for them to finish.
|
||||||
|
while __done_count < n: time.sleep(0.01)
|
||||||
|
|
||||||
|
# Read any left-over received packets.
|
||||||
|
end_time = time.clock() + multithread_wait_time
|
||||||
|
while time.clock() < end_time:
|
||||||
|
# Read any available packets.
|
||||||
|
try: (p, fromaddr) = C.recv(timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == D.dest
|
||||||
|
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
|
||||||
|
try: (p, fromaddr) = D.recv(timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None and not raw: assert fromaddr == C.dest
|
||||||
|
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
if len(C_got) == len(C_recv) and len(D_got) == len(D_recv):
|
||||||
|
break
|
||||||
|
|
||||||
|
if time.clock() >= end_time:
|
||||||
|
may_need_increase = True
|
||||||
|
|
||||||
|
C_got.sort()
|
||||||
|
D_got.sort()
|
||||||
|
C_recv.sort()
|
||||||
|
D_recv.sort()
|
||||||
|
assert C_got == C_recv
|
||||||
|
assert D_got == D_recv
|
||||||
|
|
||||||
|
C.close()
|
||||||
|
D.close()
|
||||||
|
except:
|
||||||
|
if raw:
|
||||||
|
print 'Unit test failed for samclasses.RawSession ' + \
|
||||||
|
'(multithreaded).'
|
||||||
|
print 'Raw packets are not reliable.'
|
||||||
|
else:
|
||||||
|
print 'Unit test failed for samclasses.DatagramSession ' + \
|
||||||
|
'(multithreaded).'
|
||||||
|
print 'Datagram packets are not reliable.'
|
||||||
|
|
||||||
|
if may_need_increase:
|
||||||
|
print 'Try increasing multithread_wait_time.'
|
||||||
|
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
if raw:
|
||||||
|
test_passed('samclasses.RawSession (multithreaded)')
|
||||||
|
else:
|
||||||
|
test_passed('samclasses.DatagramSession (multithreaded)')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def multithread_stream_test():
|
||||||
|
"""Multithreaded unit test for samclasses.StreamSession."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
multithread_wait_time = 200.0
|
||||||
|
may_need_increase = False
|
||||||
|
|
||||||
|
C = samclasses.StreamSession('Carol', in_depth=0, out_depth=0)
|
||||||
|
D = samclasses.StreamSession('Dave', in_depth=0, out_depth=0)
|
||||||
|
C.listen(10)
|
||||||
|
D.listen(10)
|
||||||
|
|
||||||
|
Cout = C.connect(D.dest)
|
||||||
|
Dout = D.connect(C.dest)
|
||||||
|
Cin = C.accept()
|
||||||
|
Din = D.accept()
|
||||||
|
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock
|
||||||
|
C_recv = [] # String data C *should* receive
|
||||||
|
D_recv = [] # String data D *should* receive
|
||||||
|
C_got = [] # String data C actually got
|
||||||
|
D_got = [] # String data D actually got
|
||||||
|
|
||||||
|
n = 50 # Create n threads
|
||||||
|
m = 40 # Each thread sends m strings
|
||||||
|
|
||||||
|
global __done_count
|
||||||
|
__done_count = 0
|
||||||
|
__lock = threading.Lock()
|
||||||
|
|
||||||
|
# Use C and D to send and read in many different threads.
|
||||||
|
def f():
|
||||||
|
# This code is run in each separate thread
|
||||||
|
global C_recv, D_recv, C_got, D_got, __lock, __done_count
|
||||||
|
for i in range(m):
|
||||||
|
# Random binary string of length 2-80.
|
||||||
|
index_list = range(random.randrange(2, 80))
|
||||||
|
s = ''.join([chr(random.randrange(256)) for j in index_list])
|
||||||
|
if random.randrange(2) == 0:
|
||||||
|
# Send packet from C to D, and log it.
|
||||||
|
__lock.acquire()
|
||||||
|
Cout.send(s)
|
||||||
|
D_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
else:
|
||||||
|
# Send packet from D to C, and log it.
|
||||||
|
__lock.acquire()
|
||||||
|
Dout.send(s)
|
||||||
|
C_recv += [s]
|
||||||
|
__lock.release()
|
||||||
|
time.sleep(0.01*random.uniform(0.0,1.0))
|
||||||
|
# Read any available string data, non-blocking.
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
try: p = Cin.recv(100000, timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
try: p = Din.recv(100000, timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
__lock.acquire()
|
||||||
|
__done_count += 1
|
||||||
|
__lock.release()
|
||||||
|
|
||||||
|
# Create n threads.
|
||||||
|
for i in range(n):
|
||||||
|
threading.Thread(target=f).start()
|
||||||
|
|
||||||
|
# Wait for them to finish.
|
||||||
|
while __done_count < n: time.sleep(0.01)
|
||||||
|
|
||||||
|
# Read any left-over received string data.
|
||||||
|
end_time = time.clock() + multithread_wait_time
|
||||||
|
while time.clock() < end_time:
|
||||||
|
# Read any available string data, non-blocking.
|
||||||
|
try: p = Cin.recv(100000, timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: C_got += [p]
|
||||||
|
|
||||||
|
try: p = Din.recv(100000, timeout=0.0)
|
||||||
|
except sam.BlockError: p = None
|
||||||
|
if p != None: D_got += [p]
|
||||||
|
|
||||||
|
if len(''.join(C_got)) == len(''.join(C_recv)) and \
|
||||||
|
len(''.join(D_got)) == len(''.join(D_recv)):
|
||||||
|
break
|
||||||
|
|
||||||
|
if time.clock() >= end_time:
|
||||||
|
may_need_increase = True
|
||||||
|
|
||||||
|
C_got = ''.join(C_got)
|
||||||
|
D_got = ''.join(D_got)
|
||||||
|
C_recv = ''.join(C_recv)
|
||||||
|
D_recv = ''.join(D_recv)
|
||||||
|
assert C_got == C_recv
|
||||||
|
assert D_got == D_recv
|
||||||
|
|
||||||
|
Cin.close()
|
||||||
|
Din.close()
|
||||||
|
Cout.close()
|
||||||
|
Dout.close()
|
||||||
|
C.close()
|
||||||
|
D.close()
|
||||||
|
except:
|
||||||
|
print 'Unit test failed for samclasses.StreamSession ' + \
|
||||||
|
'(multithreaded).'
|
||||||
|
|
||||||
|
if may_need_increase:
|
||||||
|
print 'Try increasing multithread_wait_time.'
|
||||||
|
|
||||||
|
traceback.print_exc(); sys.exit()
|
||||||
|
test_passed('samclasses.StreamSession (multithreaded)')
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
print 'Tests may take several minutes each.'
|
||||||
|
print 'If the network is unreliable, tests will fail.'
|
||||||
|
print 'A test only needs to pass once to be considered successful.'
|
||||||
|
print
|
||||||
|
print 'Testing:'
|
||||||
|
|
||||||
|
resolve_test()
|
||||||
|
raw_test1()
|
||||||
|
datagram_test1()
|
||||||
|
stream_test1()
|
||||||
|
stream_test2()
|
||||||
|
multithread_packet_test(raw=True)
|
||||||
|
multithread_stream_test()
|
||||||
|
|
||||||
|
# Note: The datagram unit test fails, but it's apparently I2P's
|
||||||
|
# fault (the code is the same as for raw packets, and the sam
|
||||||
|
# bridge is sent all the relevant data).
|
||||||
|
# Code: multithread_packet_test(raw=False)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
|
|
13
apps/sam/python/todo.txt
Normal file
13
apps/sam/python/todo.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
Todo:
|
||||||
|
|
||||||
|
* Deal with known bugs (see bugs.txt).
|
||||||
|
* Clean up code.
|
||||||
|
See the comments at the top of samclasses.py.
|
||||||
|
* Use a logger framework (perhaps Python module logging).
|
||||||
|
* Deal with FIXME comments.
|
||||||
|
* Unit tests for close then do something errors.
|
||||||
|
Try closing a stream at one end, and make sure the other
|
||||||
|
end gets all the data sent before closing.
|
||||||
|
* Make an event-based socket class.
|
||||||
|
(doesn't need to be asyncore -- but perhaps model it).
|
Reference in New Issue
Block a user