source: apps/sam/code.leo @ 2df4370

Last change on this file since 2df4370 was 4cdd42f, checked in by zzz <zzz@…>, 17 years ago

Fixed build.xml to detect os, and launch 'jythonc' or 'jythonc.bat'
according to whether we're running on *nix or windoze. build.xml
should now work on your platform, as long as you have jython installed
and jython is on your execution path.

Got SAM STREAMs working - test code added to i2psamclient.py
as function demoSTREAM()

  • Property mode set to 100644
File size: 119.1 KB
Line 
1<?xml version="1.0" encoding="UTF-8"?>
2<leo_file>
3<leo_header file_format="2" tnodes="0" max_tnode_index="217" clone_windows="0"/>
4<globals body_outline_ratio="0.35262008733624456">
5        <global_window_position top="70" left="219" height="649" width="978"/>
6        <global_log_window_position top="0" left="0" height="0" width="0"/>
7</globals>
8<preferences>
9</preferences>
10<find_panel_settings>
11        <find_string></find_string>
12        <change_string></change_string>
13</find_panel_settings>
14<vnodes>
15<v t="davidmcnab.041004143447" a="E"><vh>I2P SAM Server and Client</vh>
16<v t="davidmcnab.041004144338" a="E" tnodeList="davidmcnab.041004144338,davidmcnab.041004144338.1,davidmcnab.041004144338.2,davidmcnab.041004144338.4,davidmcnab.041004144338.5,davidmcnab.041004144338.6,davidmcnab.041004144338.8,davidmcnab.041004144338.9,davidmcnab.041004144338.10,davidmcnab.041004144338.11,davidmcnab.041004144338.12,davidmcnab.041004144338.13,davidmcnab.041004144338.14,davidmcnab.041004144338.15,davidmcnab.041004144338.17,davidmcnab.041004144338.18,davidmcnab.041004144338.19,davidmcnab.041004144338.20,davidmcnab.041004144338.21,davidmcnab.041004144338.22,davidmcnab.041004144338.23,davidmcnab.041004144338.24,davidmcnab.041004144338.26,davidmcnab.041004144338.27,davidmcnab.041004144338.29,davidmcnab.041004144338.30,davidmcnab.041004144338.31,davidmcnab.041004144338.32,davidmcnab.041004144338.33,davidmcnab.041004144338.34,davidmcnab.041004144338.35,davidmcnab.041004144338.36,davidmcnab.041004144338.37,davidmcnab.041004144338.38,davidmcnab.041004144338.39,davidmcnab.041004144338.40,davidmcnab.041004144338.41,davidmcnab.041004144338.42,davidmcnab.041004144338.43,davidmcnab.041004144338.44,davidmcnab.041004144338.45,davidmcnab.041004144338.46,davidmcnab.041004144338.47,davidmcnab.041004144338.49,davidmcnab.041004144338.50,davidmcnab.041004144338.51,davidmcnab.041004144338.52,davidmcnab.041004144338.53,davidmcnab.041004144338.54,davidmcnab.041004144338.55,davidmcnab.041004144338.56,davidmcnab.041004144338.57,davidmcnab.041004144338.58,davidmcnab.041004144338.59,davidmcnab.041004144338.60,davidmcnab.041004144338.62,davidmcnab.041004144338.63,davidmcnab.041004144338.64,davidmcnab.041004144338.65,davidmcnab.041004144338.66,davidmcnab.041004144338.67,davidmcnab.041004144338.68,davidmcnab.041004144338.69,davidmcnab.041004144338.70,davidmcnab.041004144338.71,davidmcnab.041004144338.72,davidmcnab.041004144338.73,davidmcnab.041004144338.74,davidmcnab.041004144338.75,davidmcnab.041004144338.76,davidmcnab.041004144338.77,davidmcnab.041004144338.78,davidmcnab.041004144338.79,davidmcnab.041004144338.80,davidmcnab.041004144338.81,davidmcnab.041004144338.82,davidmcnab.041004144338.83,davidmcnab.041004144338.84,davidmcnab.041304205426,davidmcnab.041004144338.85,davidmcnab.041004144338.86,davidmcnab.041004144338.87,davidmcnab.041004144338.88,davidmcnab.041004144338.89,davidmcnab.041004144338.90,davidmcnab.041004144338.92,davidmcnab.041004144338.93,davidmcnab.041004144338.94,davidmcnab.041004144338.95,davidmcnab.041004144338.96,davidmcnab.041004144338.97,davidmcnab.041004144338.98,davidmcnab.041004144338.99,davidmcnab.041004144338.100,davidmcnab.041004144338.101,davidmcnab.041004144338.105,davidmcnab.041004144338.106,davidmcnab.041004144338.107,davidmcnab.041004144338.108,davidmcnab.041004144338.102,davidmcnab.041004144338.103,davidmcnab.041004144338.109"><vh>@file jython/src/i2psam.py</vh>
17<v t="davidmcnab.041004144338.1" a="M"><vh>imports</vh></v>
18<v t="davidmcnab.041004144338.2" a="V"><vh>globals</vh></v>
19<v t="davidmcnab.041004144338.3" a="E"><vh>I2CP Interface Classes</vh>
20<v t="davidmcnab.041004144338.4"><vh>class JavaWrapper</vh></v>
21<v t="davidmcnab.041004144338.5" a="E"><vh>class I2PDestination</vh>
22<v t="davidmcnab.041004144338.6"><vh>__init__</vh></v>
23<v t="davidmcnab.041004144338.7" a="E"><vh>Exporting Methods</vh>
24<v t="davidmcnab.041004144338.8"><vh>toBin</vh></v>
25<v t="davidmcnab.041004144338.9"><vh>toBinFile</vh></v>
26<v t="davidmcnab.041004144338.10"><vh>toBinPrivate</vh></v>
27<v t="davidmcnab.041004144338.11"><vh>toBinFilePrivate</vh></v>
28<v t="davidmcnab.041004144338.12"><vh>toBase64</vh></v>
29<v t="davidmcnab.041004144338.13"><vh>toBase64Private</vh></v>
30<v t="davidmcnab.041004144338.14"><vh>toBase64File</vh></v>
31<v t="davidmcnab.041004144338.15"><vh>toBase64FilePrivate</vh></v>
32</v>
33<v t="davidmcnab.041004144338.16" a="E"><vh>Importing Methods</vh>
34<v t="davidmcnab.041004144338.17"><vh>fromBin</vh></v>
35<v t="davidmcnab.041004144338.18"><vh>fromBinFile</vh></v>
36<v t="davidmcnab.041004144338.19"><vh>fromBinPrivate</vh></v>
37<v t="davidmcnab.041004144338.20"><vh>fromBinFilePrivate</vh></v>
38<v t="davidmcnab.041004144338.21"><vh>fromBase64</vh></v>
39<v t="davidmcnab.041004144338.22"><vh>fromBase64File</vh></v>
40<v t="davidmcnab.041004144338.23"><vh>fromBase64Private</vh></v>
41<v t="davidmcnab.041004144338.24"><vh>fromBase64PrivateFile</vh></v>
42</v>
43<v t="davidmcnab.041004144338.25" a="E"><vh>Signature Methods</vh>
44<v t="davidmcnab.041004144338.26"><vh>sign</vh></v>
45<v t="davidmcnab.041004144338.27"><vh>verify</vh></v>
46</v>
47<v t="davidmcnab.041004144338.28" a="E"><vh>Sanity Methods</vh>
48<v t="davidmcnab.041004144338.29"><vh>hasPrivate</vh></v>
49</v>
50</v>
51<v t="davidmcnab.041004144338.30" a="E"><vh>class I2PClient</vh>
52<v t="davidmcnab.041004144338.31" a="E"><vh>__init__</vh></v>
53<v t="davidmcnab.041004144338.32"><vh>createDestination</vh></v>
54<v t="davidmcnab.041004144338.33"><vh>createSession</vh></v>
55</v>
56<v t="davidmcnab.041004144338.34" a="E"><vh>class I2PSession</vh>
57<v t="davidmcnab.041004144338.35"><vh>attributes</vh></v>
58<v t="davidmcnab.041004144338.36"><vh>__init__</vh></v>
59<v t="davidmcnab.041004144338.37"><vh>sendMessage</vh></v>
60<v t="davidmcnab.041004144338.38"><vh>numMessages</vh></v>
61<v t="davidmcnab.041004144338.39"><vh>getMessage</vh></v>
62<v t="davidmcnab.041004144338.40"><vh>setSessionListener</vh></v>
63<v t="davidmcnab.041004144338.41"><vh>destroySession</vh></v>
64<v t="davidmcnab.041004144338.42" a="E"><vh>CALLBACKS</vh>
65<v t="davidmcnab.041004144338.43"><vh>on_message</vh></v>
66<v t="davidmcnab.041004144338.44"><vh>on_abuse</vh></v>
67<v t="davidmcnab.041004144338.45"><vh>on_disconnected</vh></v>
68<v t="davidmcnab.041004144338.46"><vh>on_error</vh></v>
69</v>
70</v>
71<v t="davidmcnab.041004144338.47"><vh>class I2PSessionListener</vh></v>
72</v>
73<v t="davidmcnab.041004144338.48" a="E"><vh>Streaming Interface Classes</vh>
74<v t="davidmcnab.041004144338.49" a="E"><vh>class I2PSocket</vh>
75<v t="davidmcnab.041004144338.50"><vh>attributes</vh></v>
76<v t="davidmcnab.041004144338.51"><vh>__init__</vh></v>
77<v t="davidmcnab.041004144338.52"><vh>bind</vh></v>
78<v t="davidmcnab.041004144338.53"><vh>listen</vh></v>
79<v t="davidmcnab.041004144338.54"><vh>accept</vh></v>
80<v t="davidmcnab.041004144338.55"><vh>connect</vh></v>
81<v t="davidmcnab.041004144338.56"><vh>recv</vh></v>
82<v t="davidmcnab.041004144338.57"><vh>send</vh></v>
83<v t="davidmcnab.041004144338.58"><vh>available</vh></v>
84<v t="davidmcnab.041004144338.59"><vh>close</vh></v>
85<v t="davidmcnab.041004144338.60"><vh>_createSockmgr</vh></v>
86</v>
87</v>
88<v t="davidmcnab.041004144338.61" a="E"><vh>I2P SAM Server</vh>
89<v t="davidmcnab.041004144338.62" a="E"><vh>class I2PSamServer</vh>
90<v t="davidmcnab.041004144338.63"><vh>attributes</vh></v>
91<v t="davidmcnab.041004144338.64"><vh>__init__</vh></v>
92<v t="davidmcnab.041004144338.65"><vh>run</vh></v>
93<v t="davidmcnab.041004144338.66"><vh>finish_request</vh></v>
94<v t="davidmcnab.041004144338.67"><vh>samAllocId</vh></v>
95</v>
96<v t="davidmcnab.041004144338.68" a="E"><vh>class I2PSamClientHandler</vh>
97<v t="davidmcnab.041004144338.69"><vh>handle</vh></v>
98<v t="davidmcnab.041004144338.70"><vh>on_genkeys</vh></v>
99<v t="davidmcnab.041004144338.71"><vh>on_createsession</vh></v>
100<v t="davidmcnab.041004144338.72"><vh>on_destroysession</vh></v>
101<v t="davidmcnab.041004144338.73"><vh>on_send</vh></v>
102<v t="davidmcnab.041004144338.74"><vh>on_receive</vh></v>
103<v t="davidmcnab.041004144338.75"><vh>on_HELLO</vh></v>
104<v t="davidmcnab.041004144338.76"><vh>on_SESSION</vh></v>
105<v t="davidmcnab.041004144338.77"><vh>on_SESSION_CREATE</vh></v>
106<v t="davidmcnab.041004144338.78"><vh>on_STREAM</vh></v>
107<v t="davidmcnab.041004144338.79"><vh>on_DATAGRAM</vh></v>
108<v t="davidmcnab.041004144338.80"><vh>on_RAW</vh></v>
109<v t="davidmcnab.041004144338.81"><vh>on_NAMING</vh></v>
110<v t="davidmcnab.041004144338.82"><vh>on_DEST</vh></v>
111<v t="davidmcnab.041004144338.83"><vh>on_message</vh></v>
112<v t="davidmcnab.041004144338.84"><vh>threadSocketListener</vh></v>
113<v t="davidmcnab.041304205426"><vh>threadSocketReceiver</vh></v>
114<v t="davidmcnab.041004144338.85"><vh>samParse</vh></v>
115<v t="davidmcnab.041004144338.86"><vh>samSend</vh></v>
116<v t="davidmcnab.041004144338.87"><vh>samCreateArgsList</vh></v>
117<v t="davidmcnab.041004144338.88"><vh>_sendbytes</vh></v>
118<v t="davidmcnab.041004144338.89"><vh>_recvbytes</vh></v>
119</v>
120</v>
121<v t="davidmcnab.041004144338.90"><vh>Exceptions</vh></v>
122<v t="davidmcnab.041004144338.91" a="E"><vh>Functions</vh>
123<v t="davidmcnab.041004144338.92"><vh>shahash</vh></v>
124<v t="davidmcnab.041004144338.93"><vh>base64enc</vh></v>
125<v t="davidmcnab.041004144338.94"><vh>base64dec</vh></v>
126<v t="davidmcnab.041004144338.95"><vh>str2bytearray</vh></v>
127<v t="davidmcnab.041004144338.96"><vh>bytearray2str</vh></v>
128<v t="davidmcnab.041004144338.97"><vh>byteoutstream2str</vh></v>
129<v t="davidmcnab.041004144338.98"><vh>dict2props</vh></v>
130<v t="davidmcnab.041004144338.99"><vh>takeKey</vh></v>
131<v t="davidmcnab.041004144338.100"><vh>log</vh></v>
132<v t="davidmcnab.041004144338.101"><vh>logException</vh></v>
133<v t="davidmcnab.041004144338.104" a="E"><vh>Tests</vh>
134<v t="davidmcnab.041004144338.105" tnodeList="davidmcnab.041004144338.105"><vh>testdests</vh></v>
135<v t="davidmcnab.041004144338.106"><vh>testsigs</vh></v>
136<v t="davidmcnab.041004144338.107"><vh>testsession</vh></v>
137<v t="davidmcnab.041004144338.108"><vh>testsocket</vh></v>
138</v>
139<v t="davidmcnab.041004144338.102"><vh>usage</vh></v>
140<v t="davidmcnab.041004144338.103"><vh>main</vh></v>
141</v>
142<v t="davidmcnab.041004144338.109"><vh>MAINLINE</vh></v>
143</v>
144<v t="davidmcnab.041004144551" a="E" tnodeList="davidmcnab.041004144551,davidmcnab.041004144551.1,davidmcnab.041004144551.2,davidmcnab.041004144551.3,davidmcnab.041004144551.4,davidmcnab.041004144551.5,davidmcnab.041004144551.6,davidmcnab.041004144551.7,davidmcnab.041004144551.8,davidmcnab.041004144551.9,davidmcnab.041004144551.10,davidmcnab.041004144551.12,davidmcnab.041004144551.13,davidmcnab.041004144551.14,davidmcnab.041004144551.15,davidmcnab.041004144551.16,davidmcnab.041004144551.17,davidmcnab.041004144551.18,davidmcnab.041004144551.19,davidmcnab.041004144551.20,davidmcnab.041204020513,davidmcnab.041204204235,davidmcnab.041204044735,davidmcnab.041204050339,davidmcnab.041004144551.21,davidmcnab.041004144551.22,davidmcnab.041004144551.23,davidmcnab.041004144551.24,davidmcnab.041004144551.26,davidmcnab.041004144551.27,davidmcnab.041004144551.28,davidmcnab.041004144551.29,davidmcnab.041004144551.30,davidmcnab.041004144551.31,davidmcnab.041004144551.32,davidmcnab.041004144551.33,davidmcnab.041204042212,davidmcnab.041004144551.35,davidmcnab.041004144551.36,davidmcnab.041004144551.37,davidmcnab.041004144551.38,davidmcnab.041204042212.1,davidmcnab.041204042212.2,davidmcnab.041204044735.1,davidmcnab.041204050339.1,davidmcnab.041304235615,davidmcnab.041204050339.2,davidmcnab.041204050511,davidmcnab.041204044135,davidmcnab.041004144551.39,davidmcnab.041004144551.40,davidmcnab.041004144551.41,davidmcnab.041004144551.42,davidmcnab.041004144551.43,davidmcnab.041004144551.45,davidmcnab.041004144551.46,davidmcnab.041004144551.47,davidmcnab.041004144551.48,davidmcnab.041004144551.49,davidmcnab.041004144551.50,davidmcnab.041204203651,davidmcnab.041004144551.51,davidmcnab.041004144551.52"><vh>@file python/src/i2psamclient.py</vh>
145<v t="davidmcnab.041004144551.1"><vh>imports</vh></v>
146<v t="davidmcnab.041004144551.2"><vh>globals</vh></v>
147<v t="davidmcnab.041004144551.3"><vh>exceptions</vh></v>
148<v t="davidmcnab.041004144551.4" a="E"><vh>class I2PSamClient</vh>
149<v t="davidmcnab.041004144551.5"><vh>attributes</vh></v>
150<v t="davidmcnab.041004144551.6"><vh>__init__</vh></v>
151<v t="davidmcnab.041004144551.7"><vh>createSession</vh></v>
152<v t="davidmcnab.041004144551.8"><vh>destroySession</vh></v>
153<v t="davidmcnab.041004144551.9"><vh>send</vh></v>
154<v t="davidmcnab.041004144551.10"><vh>receive</vh></v>
155<v t="davidmcnab.041004144551.11" a="E"><vh>SAM methods</vh>
156<v t="davidmcnab.041004144551.12"><vh>samHello</vh></v>
157<v t="davidmcnab.041004144551.13"><vh>samSessionCreate</vh></v>
158<v t="davidmcnab.041004144551.14"><vh>samDestGenerate</vh></v>
159<v t="davidmcnab.041004144551.15"><vh>samRawSend</vh></v>
160<v t="davidmcnab.041004144551.16"><vh>samRawCheck</vh></v>
161<v t="davidmcnab.041004144551.17"><vh>samRawReceive</vh></v>
162<v t="davidmcnab.041004144551.18"><vh>samDatagramSend</vh></v>
163<v t="davidmcnab.041004144551.19"><vh>samDatagramCheck</vh></v>
164<v t="davidmcnab.041004144551.20"><vh>samDatagramReceive</vh></v>
165<v t="davidmcnab.041204020513"><vh>samStreamConnect</vh></v>
166<v t="davidmcnab.041204204235"><vh>samStreamAccept</vh></v>
167<v t="davidmcnab.041204044735"><vh>samStreamSend</vh></v>
168<v t="davidmcnab.041204050339"><vh>samStreamClose</vh></v>
169<v t="davidmcnab.041004144551.21"><vh>samNamingLookup</vh></v>
170<v t="davidmcnab.041004144551.22"><vh>samParse</vh></v>
171<v t="davidmcnab.041004144551.23"><vh>samSend</vh></v>
172<v t="davidmcnab.041004144551.24"><vh>samCreateArgsList</vh></v>
173</v>
174<v t="davidmcnab.041004144551.25" a="E"><vh>Receiver Side</vh>
175<v t="davidmcnab.041004144551.26"><vh>threadRx</vh></v>
176<v t="davidmcnab.041004144551.27"><vh>on_HELLO</vh></v>
177<v t="davidmcnab.041004144551.28"><vh>on_SESSION</vh></v>
178<v t="davidmcnab.041004144551.29"><vh>on_STREAM</vh></v>
179<v t="davidmcnab.041004144551.30"><vh>on_DATAGRAM</vh></v>
180<v t="davidmcnab.041004144551.31"><vh>on_RAW</vh></v>
181<v t="davidmcnab.041004144551.32"><vh>on_NAMING</vh></v>
182<v t="davidmcnab.041004144551.33"><vh>on_DEST</vh></v>
183</v>
184<v t="davidmcnab.041004144551.34" a="E"><vh>Utility Methods</vh>
185<v t="davidmcnab.041204042212"><vh>samAllocId</vh></v>
186<v t="davidmcnab.041004144551.35"><vh>_recvline</vh></v>
187<v t="davidmcnab.041004144551.36"><vh>_recvbytes</vh></v>
188<v t="davidmcnab.041004144551.37"><vh>_sendbytes</vh></v>
189<v t="davidmcnab.041004144551.38"><vh>_sendline</vh></v>
190</v>
191</v>
192<v t="davidmcnab.041204042212.1" a="E"><vh>class I2PSAMStream</vh>
193<v t="davidmcnab.041204042212.2"><vh>__init__</vh></v>
194<v t="davidmcnab.041204044735.1"><vh>send</vh></v>
195<v t="davidmcnab.041204050339.1"><vh>recv</vh></v>
196<v t="davidmcnab.041304235615"><vh>readline</vh></v>
197<v t="davidmcnab.041204050339.2"><vh>close</vh></v>
198<v t="davidmcnab.041204050511"><vh>__del__</vh></v>
199<v t="davidmcnab.041204044135"><vh>_notifyIncomingData</vh></v>
200</v>
201<v t="davidmcnab.041004144551.39" a="E"><vh>class I2PRemoteSession</vh>
202<v t="davidmcnab.041004144551.40"><vh>__init__</vh></v>
203<v t="davidmcnab.041004144551.41"><vh>send</vh></v>
204<v t="davidmcnab.041004144551.42"><vh>recv</vh></v>
205<v t="davidmcnab.041004144551.43"><vh>destroy</vh></v>
206</v>
207<v t="davidmcnab.041004144551.44" a="E"><vh>Functions</vh>
208<v t="davidmcnab.041004144551.45"><vh>log</vh></v>
209<v t="davidmcnab.041004144551.46"><vh>logException</vh></v>
210<v t="davidmcnab.041004144551.47"><vh>demoNAMING</vh></v>
211<v t="davidmcnab.041004144551.48"><vh>demoRAW</vh></v>
212<v t="davidmcnab.041004144551.49"><vh>demoDATAGRAM</vh></v>
213<v t="davidmcnab.041004144551.50"><vh>demoSTREAM</vh></v>
214<v t="davidmcnab.041204203651"><vh>demoSTREAM_thread</vh></v>
215<v t="davidmcnab.041004144551.51"><vh>demo</vh></v>
216</v>
217<v t="davidmcnab.041004144551.52"><vh>MAINLINE</vh></v>
218</v>
219</v>
220</vnodes>
221<tnodes>
222<t tx="davidmcnab.041004143447"></t>
223<t tx="davidmcnab.041004144338">@first #!/usr/bin/env jython
224r"""
225Implements I2P SAM Server. (refer U{http://drupal.i2p.net/node/view/144})
226
227Also contains useful classes for jython programs,
228which wrap the I2P java classes into more python-compatible
229paradigms.
230
231If you run this module (or the i2psam.jar file created from it)
232without arguments, it'll run an I2P SAM server bridge, listening
233on port 7656.
234
235The file i2psamclient.py contains python client classes and a
236demo program.
237
238Latest vers of this file is available from U{http://www.freenet.org.nz/i2p/i2psam.py}
239Latest epydoc-generated doco at U{http://www.freenet.org.nz/i2p/i2pjyDoc}
240
241The i2psam.jar file is built from this module with the following
242command (requires jython and java 1.4.x+ to be installed)::
243
244  CLASSPATH=/path/to/i2p.jar:/path/to/mstreaming.jar \
245          jythonc -jar i2psam.jar --all -A net.invisiblenet i2psam.py
246
247"""
248
249@others
250
251
252</t>
253<t tx="davidmcnab.041004144338.1"># python imports
254import sys, os, time, Queue, thread, threading, StringIO, traceback, getopt
255from SocketServer import ThreadingTCPServer, StreamRequestHandler
256
257# java imports
258import java
259
260# i2p-specific imports
261import net.i2p
262import net.i2p.client # to shut up epydoc
263#import net.i2p.client.I2PClient
264#import net.i2p.client.I2PClientFactory
265#import net.i2p.client.I2PSessionListener
266import net.i2p.client.naming
267import net.i2p.client.streaming
268import net.i2p.crypto
269import net.i2p.data
270
271# handy shorthand refs
272i2p = net.i2p
273jI2PClient = i2p.client.I2PClient
274
275# import my own helper hack module
276#import I2PHelper
277
278</t>
279<t tx="davidmcnab.041004144338.2">clientFactory = i2p.client.I2PClientFactory
280
281#i2phelper = I2PHelper()
282
283PROP_RELIABILITY_BEST_EFFORT = i2p.client.I2PClient.PROP_RELIABILITY_BEST_EFFORT
284PROP_RELIABILITY_GUARANTEED = i2p.client.I2PClient.PROP_RELIABILITY_GUARANTEED
285
286version = "0.1.0"
287
288# host/port that our socketserver listens on
289i2psamhost = "127.0.0.1"
290i2psamport = 7656
291
292# host/port that I2P's I2CP listens on
293i2cpHost = "127.0.0.1"
294i2cpPort = 7654
295
296#print "i2cpPort=%s" % repr(i2cpPort)
297
298# ------------------------------------------
299# logging settings
300
301# 1=v.quiet, 2=normal, 3=verbose, 4=debug, 5=painful
302verbosity = 2
303
304# change to a filename to log there instead
305logfile = sys.stdout
306
307# when set to 1, and when logfile != sys.stdout, log msgs are written
308# both to logfile and console stdout
309log2console = 1
310
311# don't touch this!
312loglock = threading.Lock()
313
314
315</t>
316<t tx="davidmcnab.041004144338.3"></t>
317<t tx="davidmcnab.041004144338.4">class JavaWrapper:
318    """
319    Wraps a java object as attribute '_item', and forwards
320    __getattr__ to it.
321   
322    All the classes here derive from this
323    """
324    def __init__(self, item):
325        self._item = item
326   
327    def __getattr__(self, attr):
328        return getattr(self._item, attr)
329   
330
331</t>
332<t tx="davidmcnab.041004144338.5">class I2PDestination(JavaWrapper):
333    """
334    Wraps java I2P destination objects, with a big difference - these
335    objects store the private parts.
336    """
337    @others
338
339</t>
340<t tx="davidmcnab.041004144338.6">def __init__(self, **kw):
341    """
342    Versatile constructor
343   
344    Keywords (choose only one option):
345        - (none) - create a whole new dest
346        - dest, private - wrap an existing I2P java dest with private stream
347          (private is a byte array)
348        - bin - reconstitute a public-only dest from a binary string
349        - binfile - reconstitute public-only from a binary file
350        - binprivate - reconsistitute private dest from binary string
351        - binfileprivate - reconsistitute private dest from binary file pathname
352        - base64 - reconstitute public-only from base64 string
353        - base64file - reconstitute public-only from file containing base64
354        - base64private - reconstitute private from string containing base64
355        - base64fileprivate - reconstitute private from file containing base64
356
357    also:
358        - client - a java net.i2p.client.I2PClient object
359          (avoids need for temporary client object when creating new dests)
360    """
361    dest = i2p.data.Destination()
362    JavaWrapper.__init__(self, dest)
363    self._private = None
364
365    if kw.has_key('dest'):
366        self._item = kw['dest']
367        if kw.has_key('private'):
368            self._private = kw['private']
369
370    elif kw.has_key('bin'):
371        self.fromBin(kw['bin'])
372
373    elif kw.has_key('binfile'):
374        self.fromBinFilePrivate(kw['binfile'])
375
376    elif kw.has_key('binprivate'):
377        self.fromBinPrivate(kw['binprivate'])
378
379    elif kw.has_key('binfileprivate'):
380        self.fromBinFilePrivate(kw['binfileprivate'])
381
382    elif kw.has_key('base64'):
383        self.fromBase64(kw['base64'])
384
385    elif kw.has_key('base64file'):
386        self.fromBase64File(kw['base64file'])
387   
388    elif kw.has_key('base64private'):
389        self.fromBase64Private(kw['base64private'])
390
391    elif kw.has_key('base64fileprivate'):
392        self.fromBase64FilePrivate(kw['base64fileprivate'])
393
394    else:
395        # create a whole new one, with a temporary client object (if needed)
396        if kw.has_key('client'):
397            client = kw['client']
398        else:
399            client = clientFactory.createClient()
400        bytestream = java.io.ByteArrayOutputStream()
401        self._item = client.createDestination(bytestream)
402        self._private = bytestream.toByteArray()
403
404</t>
405<t tx="davidmcnab.041004144338.7"></t>
406<t tx="davidmcnab.041004144338.8">def toBin(self):
407    """
408    Returns a binary string of dest
409    """
410    return bytearray2str(self.toByteArray())
411
412</t>
413<t tx="davidmcnab.041004144338.9">def toBinFile(self, path):
414    """
415    Writes out public binary to a file
416    """
417    f = open(path, "wb")
418    f.write(self.toBin())
419    f.flush()
420    f.close()
421
422</t>
423<t tx="davidmcnab.041004144338.10">def toBinPrivate(self):
424    """
425    Returns the private key string as binary
426    """
427    if self._private == None:
428        raise NoPrivateKey
429    return bytearray2str(self._private)
430
431</t>
432<t tx="davidmcnab.041004144338.11">def toBinFilePrivate(self, path):
433    """
434    Writes out a binary file with the dest info
435    """
436    f = open(path, "wb")
437    f.write(self.toBinPrivate())
438    f.flush()
439    f.close()
440
441</t>
442<t tx="davidmcnab.041004144338.12">def toBase64(self):
443    """
444    Returns base64 string of public part
445    """
446    return self._item.toBase64()
447
448</t>
449<t tx="davidmcnab.041004144338.13">def toBase64Private(self):
450    """
451    Exports dest as base64, including private stuff
452    """
453    if self._private == None:
454        raise NoPrivateKey
455    return i2p.data.Base64.encode(self._private)
456
457</t>
458<t tx="davidmcnab.041004144338.14">def toBase64File(self, path):
459    """
460    Exports dest to file as base64
461    """
462    f = open(path, "wb")
463    f.write(self.toBase64())
464    f.flush()
465    f.close()
466
467</t>
468<t tx="davidmcnab.041004144338.15">def toBase64FilePrivate(self, path):
469    """
470    Writes out a base64 file with the private dest info
471    """
472    f = open(path, "wb")
473    f.write(self.toBase64Private())
474    f.flush()
475    f.close()
476
477</t>
478<t tx="davidmcnab.041004144338.16"></t>
479<t tx="davidmcnab.041004144338.17">def fromBin(self, bin):
480    """
481    Loads this dest from a binary string
482    """
483    self._item.fromByteArray(str2bytearray(bin))
484    self._private = None
485
486</t>
487<t tx="davidmcnab.041004144338.18">def fromBinFile(self, path):
488    """
489    Loads public part from file containing binary
490    """
491    f = open(path, "rb")
492    self.fromBin(f.read())
493    f.close()
494
495</t>
496<t tx="davidmcnab.041004144338.19">def fromBinPrivate(self, s):
497    """
498    Loads this dest object from a base64 private key string
499    """
500    bytes = str2bytearray(s)
501    self._private = bytes
502    stream = java.io.ByteArrayInputStream(bytes)
503    self._item.readBytes(stream)
504
505</t>
506<t tx="davidmcnab.041004144338.20">def fromBinFilePrivate(self, path):
507    """
508    Loads this dest object, given the pathname of a file containing
509    a binary destkey
510    """
511    self.fromBinPrivate(open(path, "rb").read())
512
513</t>
514<t tx="davidmcnab.041004144338.21">def fromBase64(self, b64):
515    """
516    Loads this dest from a base64 string
517    """
518    self._item.fromBase64(b64)
519    self._private = None
520
521</t>
522<t tx="davidmcnab.041004144338.22">def fromBase64File(self, path):
523    """
524    Loads public part from file containing base64
525    """
526    f = open(path, "rb")
527    self.fromBase64(f.read())
528    f.close()
529
530</t>
531<t tx="davidmcnab.041004144338.23">def fromBase64Private(self, s):
532    """
533    Loads this dest object from a base64 private key string
534    """
535    bytes = i2p.data.Base64.decode(s)
536    self._private = bytes
537    stream = java.io.ByteArrayInputStream(bytes)
538    self._item.readBytes(stream)
539
540</t>
541<t tx="davidmcnab.041004144338.24">def fromBase64FilePrivate(self, path):
542    """
543    Loads this dest from a base64 file containing private key
544    """
545    self.fromBase64Private(open(path, "rb").read())
546
547</t>
548<t tx="davidmcnab.041004144338.25"></t>
549<t tx="davidmcnab.041004144338.26">def sign(self, s):
550    """
551    Signs a string using this dest's priv key
552    """
553    # get byte stream
554    bytes = str2bytearray(s)
555
556    # stream up our private bytes
557    stream = java.io.ByteArrayInputStream(self._private)
558
559    # temporary dest object
560    d = i2p.data.Destination()
561
562    # suck the public part off the stream
563    d.readBytes(stream)
564
565    # temporary private key object
566    privkey = i2p.data.PrivateKey()
567    privkey.readBytes(stream)
568   
569    # now we should just have the signing key portion left in the stream
570    signingkey = i2p.data.SigningPrivateKey()
571    signingkey.readBytes(stream)
572   
573    # create DSA engine
574    dsa = i2p.crypto.DSAEngine()
575   
576    sig = dsa.sign(bytes, signingkey)
577
578    rawsig = bytearray2str(sig.getData())
579
580    return rawsig
581
582</t>
583<t tx="davidmcnab.041004144338.27">def verify(self, s, sig):
584    """
585    Verifies a string against this dest, to test if it was actually
586    signed by whoever has the dest privkey
587    """
588    # get byte stream from data
589    databytes = str2bytearray(s)
590
591    # get signature stream from sig
592    sigstream = java.io.ByteArrayInputStream(str2bytearray(sig))
593
594    # make a signature object
595    signature = i2p.data.Signature()
596    signature.readBytes(sigstream)
597
598    # get signature verify key
599    pubkey = self.getSigningPublicKey()
600
601        #log(4, "databytes=%s, pubkey=%s" % (repr(databytes), repr(pubkey)))
602   
603    # now get a verification
604    dsa = i2p.crypto.DSAEngine()
605    result = dsa.verifySignature(signature, databytes, pubkey)
606
607    return result
608
609
610
611</t>
612<t tx="davidmcnab.041004144338.28"></t>
613<t tx="davidmcnab.041004144338.29">def hasPrivate(self):
614    """
615    Returns True if this dest has private parts, False if not
616    """
617
618    if self._private:
619        return 1
620    else:
621        return 0
622</t>
623<t tx="davidmcnab.041004144338.30">class I2PClient(JavaWrapper):
624    """
625    jython-comfortable wrapper for java I2P client class
626    """
627    @others
628
629</t>
630<t tx="davidmcnab.041004144338.31">def __init__(self, **kw):
631    """
632    I2PClient constructor
633   
634    No args or keywords as yet
635    """
636    client = clientFactory.createClient()
637    JavaWrapper.__init__(self, client)
638
639</t>
640<t tx="davidmcnab.041004144338.32">def createDestination(self, **kw):
641    """
642    Creates a destination, either a new one, or from a bin or base64 file
643   
644    Keywords:
645        - see L{I2PDestination} constructor
646    """
647    return I2PDestination(**kw)
648
649</t>
650<t tx="davidmcnab.041004144338.33">def createSession(self, dest, sessionClass=None, **kw):
651    """
652    Create a session
653
654    Arguments:
655        - dest - an L{I2PDestination} object which MUST contain a private portion
656        - sessionClass - if given, this should be a subclass
657          of I2PSession. This allows you to implement your own handlers.
658
659    Keywords:
660        - session options (refer javadocs)
661    """
662    if sessionClass is None:
663        sessionClass = I2PSession
664
665    if not dest.hasPrivate():
666        raise NoPrivateKey("Dest object has no private key")
667
668    #print kw
669    #session = self._item.createSession(destStream, dict2props(kw))
670    session = sessionClass(client=self, dest=dest, **kw)
671    return session
672    #return sessionClass(session=session)
673
674</t>
675<t tx="davidmcnab.041004144338.34">class I2PSession(JavaWrapper):
676    """
677    Wraps an I2P client session
678
679    You can subclass this, overriding the on_* handler callbacks,
680    and pass it as an argument to I2PClient.createSession
681
682    In the default 'on_message' callback, message retrieval is
683    synchronous - inbound messages get written to an internal queue,
684    which you can checked with numMessages() and retrieved from via
685    getMessage(). You may override on_message() if you
686    want to handle incoming messages asynchronously yourself.
687
688    Note - as far as I can tell, this class should be thread-safe.
689    """
690    @others
691</t>
692<t tx="davidmcnab.041004144338.35">host = i2cpHost
693port = i2cpPort
694</t>
695<t tx="davidmcnab.041004144338.36">def __init__(self, **kw):
696    """
697    I2PSession constructor
698
699    Keywords:
700        - either:
701            - session - a java i2p session object
702        - or:
703            - client - an L{I2PClient} object
704            - dest - an L{I2PDestination} object
705    Also:
706        - listener - an L{I2PSessionListener} object.
707
708    Router-level options:
709        - reliability - one of 'guaranteed' and 'besteffort' (default 'besteffort')
710        - host - host on which router is running
711        - port - port on which router is listening
712    """
713    #
714    # grab options destined for java class
715    #
716    options = {}
717
718    reliability = takeKey(kw, 'reliability', 'besteffort')
719    if reliability == 'guaranteed':
720        reliability = jI2PClient.PROP_RELIABILITY_GUARANTEED
721    else:
722        reliability = jI2PClient.PROP_RELIABILITY_BEST_EFFORT
723    options[jI2PClient.PROP_RELIABILITY] = reliability
724
725    host = takeKey(kw, 'host', self.host)
726    options[jI2PClient.PROP_TCP_HOST] = host
727
728    port = takeKey(kw, 'port', self.port)
729    options[jI2PClient.PROP_TCP_PORT] = str(port)
730
731    if kw.has_key('reliability'):
732        reliability = kw['reliability']
733
734    if kw.has_key('listener'):
735        listener = kw['listener']
736        del kw['listener']
737    else:
738        listener = I2PSessionListener()
739
740    #print options
741
742    #
743    # other keywords handled locally
744    #
745    if kw.has_key('session'):
746        session = kw['session']
747        del kw['session']
748        JavaWrapper.__init__(self, session)
749    elif kw.has_key('client') and kw.has_key('dest'):
750        client = kw['client']
751        dest = kw['dest']
752        del kw['client']
753        del kw['dest']
754        destStream = java.io.ByteArrayInputStream(dest._private)
755        session = self._item = client._item.createSession(destStream, dict2props(options))
756        #client.createSession(dest, dict2props(options))
757    else:
758        raise Exception("implementation incomplete")
759
760    # set up a listener
761    self.setSessionListener(listener)
762
763    # set up a queue for inbound msgs
764    self.qInbound = Queue.Queue()
765    self.lockInbound = threading.Lock()
766    self.nInboundMessages = 0
767
768    self.lockOutbound = threading.Lock()
769
770
771
772</t>
773<t tx="davidmcnab.041004144338.37">def sendMessage(self, dest, payload):
774    """
775    Sends a message to another dest
776   
777    Arguments:
778        - dest - an L{I2PDestination} object
779        - payload - a string to send
780    """
781    dest = dest._item
782    payload = str2bytearray(payload)
783    self.lockOutbound.acquire()
784    try:
785        res = self._item.sendMessage(dest, payload)
786    except:
787        self.lockOutbound.release()
788        raise
789    self.lockOutbound.release()
790    return res
791</t>
792<t tx="davidmcnab.041004144338.38">def numMessages(self):
793    """
794    Returns the number of unretrieved inbound messages
795    """
796    self.lockInbound.acquire()
797    n = self.nInboundMessages
798    self.lockInbound.release()
799    return n
800</t>
801<t tx="davidmcnab.041004144338.39">def getMessage(self, blocking=1):
802    """
803    Returns the next available inbound message.
804   
805    If blocking is set to 1 (default), blocks
806    till another message comes in.
807   
808    If blocking is set to 0, returns None if there
809    are no available messages.
810    """
811    if blocking:
812        msg = self.qInbound.get()
813        #print "getMessage: acquiring lock"
814        self.lockInbound.acquire()
815        #print "getMessage: got lock"
816        self.nInboundMessages -= 1
817    else:
818        #print "getMessage: acquiring lock"
819        self.lockInbound.acquire()
820        #print "getMessage: got lock"
821        if self.nInboundMessages &gt; 0:
822            msg = self.qInbound.get()
823            self.nInboundMessages -= 1
824        else:
825            msg = None
826    self.lockInbound.release()
827    #print "getMessage: released lock"
828    return msg
829
830</t>
831<t tx="davidmcnab.041004144338.40">def setSessionListener(self, listener):
832    """
833    Designates an L{I2PSessionListener} object to listen to this session
834    """
835    self.listener = listener
836    listener.addSession(self)
837    self._item.setSessionListener(listener)
838
839
840</t>
841<t tx="davidmcnab.041004144338.41">def destroySession(self):
842    """
843    Destroys an existing session
844
845    Note that due to a jython quirk, calls to destroySession might
846    trigger a TypeError relating to arg mismatch - we ignore such
847    errors here because by the time the exception happens, the
848    session has already been successfully closed
849    """
850    try:
851        self._item.destroySession()
852    except TypeError:
853        pass
854
855</t>
856<t tx="davidmcnab.041004144338.42">#
857# handler methods which you should override
858#
859
860@others
861</t>
862<t tx="davidmcnab.041004144338.43">def on_message(self, msg):
863    """
864    Callback for when a message arrives.
865
866    Appends the message to the inbound queue, which you can check
867    with the numMessages() method, and read with getMessage()
868
869    You should override this if you want to handle inbound messages
870    asynchronously.
871   
872    Arguments:
873        - msg - a string that was sent by peer
874    """
875    #print "on_message: msg=%s" % msg
876    self.lockInbound.acquire()
877    #print "on_message: got lock"
878    self.qInbound.put(msg)
879    self.nInboundMessages += 1
880    self.lockInbound.release()
881    #print "on_message: released lock"
882
883</t>
884<t tx="davidmcnab.041004144338.44">def on_abuse(self, severity):
885    """
886    Callback indicating abuse is happening
887   
888    Arguments:
889        - severity - an int of abuse level, 1-100
890    """
891    print "on_abuse: severity=%s" % severity
892
893</t>
894<t tx="davidmcnab.041004144338.45">def on_disconnected(self):
895    """
896    Callback indicating remote peer disconnected
897    """
898    print "on_disconnected"
899
900</t>
901<t tx="davidmcnab.041004144338.46">def on_error(self, message, error):
902    """
903    Callback indicating an error occurred
904    """
905    print "on_error: message=%s error=%s" % (message, error)
906
907</t>
908<t tx="davidmcnab.041004144338.47">class I2PSessionListener(i2p.client.I2PSessionListener):
909    """
910    Wraps a java i2p.client.I2PSessionListener object
911    """
912    def __init__(self, *sessions):
913        self.sessions = list(sessions)
914
915    def addSession(self, session):
916        """
917        Adds an L{I2PSession} object to the list of sessions to listen on
918       
919        Note - you must also invoke the session's setSessionListener() method
920        (see I2PSession.setSessionListener)
921        """
922        if session not in self.sessions:
923            self.sessions.append(session)
924   
925    def delSession(self, session):
926        """
927        Stop listening to a given session
928        """
929        if session in self.sessions:
930            del self.sessions.index[session]
931
932    def messageAvailable(self, session, msgId, size):
933        """
934        Callback from java::
935            public void messageAvailable(
936                I2PSession session,
937                int msgId,
938                long size)
939        """
940        #print "listener - messageAvailable"
941
942        # try to find session in our sessions table
943        sessions = filter(lambda s, session=session: s._item == session, self.sessions)
944        if sessions:
945            #print "compare to self.session-&gt;%s" % (session == self.session._item)
946
947            # found a matching session - retrieve it
948            session = sessions[0]
949
950            # retrieve message and pass to callback
951            msg = session.receiveMessage(msgId)
952            msgStr = bytearray2str(msg)
953            session.on_message(msgStr)
954        else:
955            print "messageAvailable: unknown session=%s msgId=%s size=%s" % (session, msgId, size)
956
957    def reportAbuse(self, session, severity):
958        """
959        Callback from java::
960            public void reportAbuse(
961                I2PSession session,
962                int severity)
963        """
964        if self.session:
965            self.session.on_abuse(severity)
966        else:
967            print "reportAbuse: unknown session=%s severity=%s" % (session, severity)
968   
969    def disconnected(self, session):
970        """
971        Callback from java::
972            public void disconnected(I2PSession session)
973        """
974        if self.session:
975            self.session.on_disconnected()
976        else:
977            print "disconnected: unknown session=%s" % session
978
979    def errorOccurred(session, message, error):
980        """
981        Callback from java::
982            public void errorOccurred(
983                I2PSession session,
984                java.lang.String message,
985                java.lang.Throwable error)
986        """
987        if self.session:
988            self.session.on_error(message, error)
989        else:
990            print "errorOccurred: message=%s error=%s" % (message, error)
991
992</t>
993<t tx="davidmcnab.041004144338.48"></t>
994<t tx="davidmcnab.041004144338.49">class I2PSocket:
995    """
996    Wraps I2P streaming API into a form resembling python sockets
997    """
998    @others
999</t>
1000<t tx="davidmcnab.041004144338.50">host = i2cpHost
1001port = i2cpPort
1002
1003</t>
1004<t tx="davidmcnab.041004144338.51">def __init__(self, dest=None, **kw):
1005    """
1006    Create an I2P streaming socket
1007
1008    Arguments:
1009        - dest - a private destination to associate with this socket
1010
1011    Keywords:
1012        - host - hostname on which i2cp is listening (default self.host)
1013        - port - port on which i2cp listens (default self.port)
1014
1015    Internally used keywords (used for wrapping an accept()ed connection):
1016        - dest
1017        - remdest
1018        - sock
1019        - instream
1020        - outstream
1021    """
1022    # set up null attribs
1023    self.sockmgr = None
1024    self.instream = None
1025    self.outstream = None
1026    self.sock = None
1027    self._connected = 0
1028    self._blocking = 1
1029
1030    # save dest (or lack thereof)
1031    self.dest = dest
1032
1033    if kw.has_key('sock') \
1034            and kw.has_key('remdest') \
1035            and kw.has_key('instream') \
1036            and kw.has_key('outstream'):
1037
1038        # wrapping an accept()'ed connection
1039        log(4, "accept()'ed a connection, wrapping...")
1040
1041        self.sock = kw['sock']
1042        self.dest = dest
1043        self.remdest = kw['remdest']
1044        self.instream = kw['instream']
1045        self.outstream = kw['outstream']
1046    else:
1047        log(4, "creating new I2PSocket %s" % dest)
1048
1049        # process keywords
1050        self.host = kw.get('host', self.host)
1051        self.port = int(kw.get('port', self.port))
1052
1053        # we need a factory, don't we?
1054        self.sockmgrFact = i2p.client.streaming.I2PSocketManagerFactory()
1055
1056</t>
1057<t tx="davidmcnab.041004144338.52">def bind(self, dest=None):
1058    """
1059    'binds' the socket to a dest
1060
1061    dest is an I2PDestination object, which you may specify in the constructor
1062    instead of here. However, we give you the option of specifying here for
1063    some semantic compatibility with python sockets.
1064    """
1065    if dest is not None:
1066        self.dest = dest
1067    elif not self.dest:
1068        # create new dest, client should interrogate it at some time
1069        log(4, "bind: socket has no dest, creating one")
1070        self.dest = I2PDestination()
1071</t>
1072<t tx="davidmcnab.041004144338.53">def listen(self, *args, **kw):
1073    """
1074    Sets up the object to receive connections
1075    """
1076    # sanity checks
1077    if self.sockmgr:
1078        raise I2PSocketError(".sockmgr already present - have you already called listen?")
1079    if not self.dest:
1080        raise I2PSocketError("socket is not bound to a destination")
1081
1082    log(4, "listening on socket")
1083   
1084    # create the socket manager
1085    self._createSockmgr()
1086    </t>
1087<t tx="davidmcnab.041004144338.54">def accept(self):
1088    """
1089    Waits for incoming connections, and returns a new I2PSocket object
1090    with the connection
1091    """
1092    # sanity check
1093    if not self.sockmgr:
1094        raise I2PSocketError(".listen() has not been called on this socket")
1095
1096    # accept a conn and get its streams
1097    sock = self.sockmgr.getServerSocket().accept()
1098    instream = sock.getInputStream()
1099    outstream = sock.getOutputStream()
1100    remdest = I2PDestination(dest=sock.getPeerDestination())
1101
1102    # wrap it and return it
1103    sockobj = I2PSocket(dest=self.dest,
1104                        remdest=remdest,
1105                        sock=sock,
1106                        instream=instream,
1107                        outstream=outstream)
1108    self._connected = 1
1109    return sockobj
1110
1111</t>
1112<t tx="davidmcnab.041004144338.55">def connect(self, remdest):
1113    """
1114    Connects to a remote destination
1115
1116    This has one totally major difference from the normal socket
1117    paradigm, and that is that you can have n outbound connections
1118    to different dests.
1119    """
1120    # sanity check
1121    if self.sockmgr:
1122        raise I2PSocketError(".sockmgr already present - have you already called listen/connect?")
1123
1124    # create whole new dest if none was provided to constructor
1125    if self.dest is None:
1126        log(4, "connect: creating whole new dest")
1127        self.dest = I2PDestination()
1128
1129    # create the socket manager
1130    self._createSockmgr()
1131
1132    # do the connect
1133    #print "remdest._item = %s" % repr(remdest._item)
1134
1135    opts = net.i2p.client.streaming.I2PSocketOptions()
1136    try:
1137        log(4, "trying to connect to %s" % remdest.toBase64())
1138        sock = self.sock = self.sockmgr.connect(remdest._item, opts)
1139        self.remdest = remdest
1140    except:
1141        logException(2, "apparent exception, continuing...")
1142
1143    self.instream = sock.getInputStream()
1144    self.outstream = sock.getOutputStream()
1145
1146    sockobj = I2PSocket(dest=self.dest,
1147                        remdest=remdest,
1148                        sock=sock,
1149                        instream=self.instream,
1150                        outstream=self.outstream)
1151    self._connected = 1
1152    return sockobj
1153</t>
1154<t tx="davidmcnab.041004144338.56">def recv(self, nbytes):
1155    """
1156    Reads nbytes of data from socket
1157    """
1158    # sanity check
1159    if not self.instream:
1160        raise I2PSocketError("Socket is not connected")
1161   
1162    # for want of better methods, read bytewise
1163    chars = []
1164    while nbytes &gt; 0:
1165        byte = self.instream.read()
1166        if byte &lt; 0:
1167            break # got all we're gonna get
1168        char = chr(byte)
1169        chars.append(char)
1170        #print "read: got a byte %s (%s)" % (byte, repr(char))
1171        nbytes -= 1
1172       
1173    # got it all
1174    buf = "".join(chars)
1175    #print "recv: buf=%s" % repr(buf)
1176    return buf
1177
1178
1179</t>
1180<t tx="davidmcnab.041004144338.57">def send(self, buf):
1181    """
1182    Sends buf thru socket
1183    """
1184    # sanity check
1185    if not self.outstream:
1186        raise I2PSocketError("Socket is not connected")
1187
1188    # and write it out
1189    log(4, "send: writing '%s' to outstream..." % repr(buf))
1190    outstream = self.outstream
1191    for c in buf:
1192        outstream.write(ord(c))
1193
1194    # flush just in case
1195    log(4, "send: flushing...")
1196    self.outstream.flush()
1197
1198    log(4, "send: done")
1199
1200</t>
1201<t tx="davidmcnab.041004144338.58">def available(self):
1202    """
1203    Returns the number of bytes available for recv()
1204    """
1205    #print "available: sock is %s" % repr(self.sock)
1206
1207    return self.instream.available()
1208
1209
1210</t>
1211<t tx="davidmcnab.041004144338.59">def close(self):
1212    """
1213    Closes the socket
1214    """
1215    # sanity check
1216    #if not self._connected:
1217    #    raise I2PSocketError("Socket is not connected")
1218
1219    # shut up everything
1220    try:
1221        self.instream.close()
1222    except:
1223        pass
1224    try:
1225        self.outstream.close()
1226    except:
1227        pass
1228    try:
1229        self.sock.close()
1230    except:
1231        pass
1232</t>
1233<t tx="davidmcnab.041004144338.60">def _createSockmgr(self):
1234
1235    if getattr(self, 'sockmgr', None):
1236        return
1237
1238    #options = {jI2PClient.PROP_TCP_HOST: self.host,
1239    #           jI2PClient.PROP_TCP_PORT: self.port}
1240    options = {}
1241    props = dict2props(options)
1242
1243    # get a java stream thing from dest
1244    stream = java.io.ByteArrayInputStream(self.dest._private)
1245   
1246    # create socket manager thing
1247    self.sockmgr = self.sockmgrFact.createManager(stream, self.host, self.port, props)
1248</t>
1249<t tx="davidmcnab.041004144338.61"></t>
1250<t tx="davidmcnab.041004144338.62">class I2PSamServer(ThreadingTCPServer):
1251    """
1252    A server which makes I2CP available via a socket
1253    """
1254        @others
1255</t>
1256<t tx="davidmcnab.041004144338.63">host = i2psamhost
1257port = i2psamport
1258
1259i2cphost = i2cpHost
1260i2cpport = i2cpPort
1261
1262version = version
1263
1264
1265</t>
1266<t tx="davidmcnab.041004144338.64">def __init__(self, i2pclient=None, **kw):
1267    """
1268    Create the client listener object
1269   
1270    Arguments:
1271        - i2pclient - an I2PClient object - optional - if not
1272          given, one will be created
1273   
1274    Keywords:
1275        - host - host to listen on for client conns (default self.host ('127.0.0.1')
1276        - port - port to listen on for client conns (default self.port (7656)
1277        - i2cphost - host to talk to i2cp on (default self.i2cphost ('127.0.0.1'))
1278        - i2cpport - port to talk to i2cp on (default self.i2cphost ('127.0.0.1'))
1279    """
1280
1281    # create an I2PClient object if none given
1282    if i2pclient is None:
1283        i2pclient = I2PClient()
1284    self.i2pclient = i2pclient
1285
1286    # get optional host/port for client and i2cp
1287    self.host = kw.get('host', self.host)
1288    self.port = int(kw.get('port', self.port))
1289    self.i2cphost = kw.get('i2cphost', self.i2cphost)
1290    self.i2cpport = int(kw.get('i2cpport', self.i2cpport))
1291
1292    # create record of current sessions, and a lock for it
1293    self.sessions = {}
1294    self.sessionsLock = threading.Lock()
1295    self.streams = {}
1296    self.streamsLock = threading.Lock()
1297    self.samNextId = 1
1298    self.samNextIdLock = threading.Lock()
1299
1300    # and create the server
1301    try:
1302        ThreadingTCPServer.__init__(
1303            self,
1304            (self.host, self.port),
1305            I2PSamClientHandler)
1306    except:
1307        log(4, "crashed with host=%s, port=%s" % (self.host, self.port))
1308        raise
1309
1310</t>
1311<t tx="davidmcnab.041004144338.65">def run(self):
1312    """
1313    Run the SAM server.
1314
1315    when connections come in, they are automatically
1316    accepted, and an L{I2PClientHandler} object created,
1317    and its L{handle} method invoked.
1318    """
1319    log(4, "Listening for client requests on %s:%s" % (self.host, self.port))
1320    self.serve_forever()
1321
1322
1323</t>
1324<t tx="davidmcnab.041004144338.66">def finish_request(self, request, client_address):
1325    """Finish one request by instantiating RequestHandlerClass."""
1326    try:
1327        self.RequestHandlerClass(request, client_address, self)
1328    except:
1329        pass
1330    log(3, "Client session terminated")
1331</t>
1332<t tx="davidmcnab.041004144338.67">def samAllocId(self):
1333    """
1334    Allocates a new unique id as required by SAM protocol
1335    """
1336    self.samNextIdLock.acquire()
1337    id = self.samNextId
1338    self.samNextId += 1
1339    self.samNextIdLock.release()
1340    return id
1341</t>
1342<t tx="davidmcnab.041004144338.68">class I2PSamClientHandler(StreamRequestHandler):
1343    r"""
1344    Manages a single socket connection from a client.
1345   
1346    When a client connects to the SAM server, the I2PSamServer
1347    object creates an instance of this class, and invokes its
1348    handle method. See L{handle}.
1349
1350    Note that if a client terminates its connection to the server, the server
1351    will destroy all current connections initiated by that client
1352   
1353    Size values are decimal
1354    Connection is persistent
1355    """
1356        @others</t>
1357<t tx="davidmcnab.041004144338.69">def handle(self):
1358    """
1359    Reads command/data messages from SAM Client, executes these,
1360    and sends back responses.
1361   
1362    Plants callback hooks into I2PSession objects, so that when
1363    data arrives via I2P, it can be immediately sent to the client.
1364    """
1365    self.localsessions = {}
1366    self.globalsessions = self.server.sessions
1367
1368    self.localstreams = {} # keyed by sam stream id
1369    self.globalstreams = self.server.streams
1370
1371    self.samSessionIsOpen = 0
1372    self.samSessionStyle = ''
1373
1374    # localise the id allocator
1375    self.samAllocId = self.server.samAllocId
1376
1377    # need a local sending lock
1378    self.sendLock = threading.Lock()
1379
1380    log(5, "Got req from %s" % repr(self.client_address))
1381
1382    try:
1383        self.namingService = i2p.client.naming.HostsTxtNamingService()
1384    except:
1385        logException(2, "Failed to create naming service object")
1386
1387    try:
1388        while 1:
1389            # get req
1390            req = self.rfile.readline().strip()
1391            flds = [s.strip() for s in req.split(" ")]
1392            cmd = flds[0]
1393            if cmd in ['HELLO', 'SESSION', 'STREAM', 'DATAGRAM', 'RAW', 'NAMING', 'DEST']:
1394                topic, subtopic, args = self.samParse(flds)
1395                method = getattr(self, "on_"+cmd, None)
1396                method(topic, subtopic, args)
1397            else:
1398                method = getattr(self, "on_"+cmd, None)
1399                if method:
1400                    method(flds)
1401                else:
1402                    # bad shit
1403                    self.wfile.write("error unknown command '%s'\n" % cmd)
1404
1405    except IOError:
1406        log(3, "Client connection terminated")
1407    except ValueError:
1408        pass
1409    except:
1410        logException(4, "Client req handler crashed")
1411        self.wfile.write("error\n")
1412
1413    # clean up sessions
1414    for dest in self.localsessions.keys():
1415        if dest in self.globalsessions.keys():
1416            log(4, "forgetting global dest %s" % dest[:30])
1417            del self.globalsessions[dest]
1418
1419    self.finish()
1420    #thread.exit()
1421
1422</t>
1423<t tx="davidmcnab.041004144338.70">def on_genkeys(self, flds):
1424
1425    log(4, "entered")
1426
1427    server = self.server
1428    client = server.i2pclient
1429    globalsessions = server.sessions
1430    sessionsLock = server.sessionsLock
1431
1432    read = self.rfile.read
1433    readline = self.rfile.readline
1434    write = self.wfile.write
1435    flush = self.wfile.flush
1436
1437    # genkeys
1438    try:
1439        dest = I2PDestination()
1440        priv = dest.toBase64Private()
1441        pub = dest.toBase64()
1442        write("ok %s %s\n" % (pub, priv))
1443    except:
1444        write("error exception\n")
1445</t>
1446<t tx="davidmcnab.041004144338.71">def on_createsession(self, flds):
1447
1448    log(4, "entered")
1449
1450    server = self.server
1451    client = server.i2pclient
1452    globalsessions = server.sessions
1453    sessionsLock = server.sessionsLock
1454
1455    read = self.rfile.read
1456    readline = self.rfile.readline
1457    write = self.wfile.write
1458    flush = self.wfile.flush
1459
1460    sessionsLock.acquire()
1461
1462    try:
1463        b64priv = flds[1]
1464
1465        # spit if someone else already has this dest
1466        if b64priv in globalsessions.keys():
1467            write("error dest in use\n")
1468        elif b64priv in self.localsessions.keys():
1469            # duh, already open locally, treat as ok
1470            write("ok\n")
1471        else:
1472            # whole new session - set it up
1473            dest = I2PDestination(base64private=b64priv)
1474            log(4, "Creating session on dest '%s'" % b64priv[:40])
1475            session = client.createSession(dest)
1476            log(4, "Connecting session on dest '%s'" % b64priv[:40])
1477            session.connect()
1478            log(4, "Session on dest '%s' now live" % b64priv[:40])
1479           
1480            # and remember it
1481            self.localsessions[b64priv] = session
1482            globalsessions[b64priv] = session
1483           
1484            # and tell the client the good news
1485            write("ok\n")
1486    except:
1487        logException(4, "createsession fail")
1488        write("error exception\n")
1489
1490    sessionsLock.release()
1491</t>
1492<t tx="davidmcnab.041004144338.72">def on_destroysession(self, flds):
1493
1494    log(4, "entered")
1495
1496    server = self.server
1497    client = server.i2pclient
1498    globalsessions = server.sessions
1499    sessionsLock = server.sessionsLock
1500
1501    read = self.rfile.read
1502    readline = self.rfile.readline
1503    write = self.wfile.write
1504    flush = self.wfile.flush
1505
1506    sessionsLock.acquire()
1507
1508    try:
1509        b64priv = flds[1]
1510       
1511        # spit if session not known
1512        if not globalsessions.has_key(b64priv):
1513            # no such session presently exists anywhere
1514            write("error nosuchsession\n")
1515        elif not self.localsessions.has_key(b64priv):
1516            # session exists, but another client owns it
1517            write("error notyoursession\n")
1518        else:
1519            # session exists and we own it
1520            session = self.localsessions[b64priv]
1521            del self.localsessions[b64priv]
1522            del globalsessions[b64priv]
1523            try:
1524                session.destroySession()
1525                write("ok\n")
1526            except:
1527                raise
1528    except:
1529        logException(4, "destroy session failed")
1530        write("error exception\n")
1531
1532    sessionsLock.release()
1533
1534    log(4, "done")
1535
1536</t>
1537<t tx="davidmcnab.041004144338.73">def on_send(self, flds):
1538
1539    #log(4, "entered: %s" % repr(flds))
1540    log(4, "entered")
1541
1542    server = self.server
1543    client = server.i2pclient
1544    globalsessions = server.sessions
1545    sessionsLock = server.sessionsLock
1546
1547    read = self.rfile.read
1548    readline = self.rfile.readline
1549    write = self.wfile.write
1550    flush = self.wfile.flush
1551
1552    sessionsLock.acquire()
1553
1554    session = None
1555    try:
1556        size = int(flds[1])
1557        b64priv = flds[2]
1558        b64peer = flds[3]
1559        msg = self._recvbytes(size)
1560
1561        # spit if session not known
1562        if not globalsessions.has_key(b64priv):
1563            # no such session presently exists anywhere
1564            log(4, "no such session")
1565            write("error nosuchsession\n")
1566        elif not self.localsessions.has_key(b64priv):
1567            # session exists, but another client owns it
1568            write("error notyoursession\n")
1569        else:
1570            session = self.localsessions[b64priv]
1571    except:
1572        logException(2, "Send exception")
1573        write("error exception on send command\n")
1574
1575    sessionsLock.release()
1576
1577    if not session:
1578        return
1579   
1580    # now get/instantiate the remote dest
1581    try:
1582        peerDest = I2PDestination(base64=b64peer)
1583    except:
1584        peerDest = None
1585        logException(2, "Send: bad remote dest")
1586        write("error bad remote dest\n")
1587    if not peerDest:
1588        return
1589
1590    # and do the send
1591    try:
1592        res = session.sendMessage(peerDest, msg)
1593    except:
1594        logException(2, "Send: failed")
1595        write("error exception on send\n")
1596        res = None
1597
1598    if res is None:
1599        return
1600
1601    # report result
1602    if res:
1603        write("ok\n")
1604    else:
1605        write("error send failed\n")
1606
1607    log(4, "done")
1608
1609</t>
1610<t tx="davidmcnab.041004144338.74">def on_receive(self, flds):
1611
1612    log(4, "entered")
1613
1614    server = self.server
1615    client = server.i2pclient
1616    globalsessions = server.sessions
1617    sessionsLock = server.sessionsLock
1618
1619    read = self.rfile.read
1620    readline = self.rfile.readline
1621    write = self.wfile.write
1622    flush = self.wfile.flush
1623
1624    sessionsLock.acquire()
1625
1626    session = None
1627    try:
1628        b64priv = flds[1]
1629
1630        # spit if session not known
1631        if not globalsessions.has_key(b64priv):
1632            # no such session presently exists anywhere
1633            write("error nosuchsession\n")
1634        elif not self.localsessions.has_key(b64priv):
1635            # session exists, but another client owns it
1636            write("error notyoursession\n")
1637        else:
1638            session = self.localsessions[b64priv]
1639    except:
1640        logException(4, "receive command error")
1641        write("error exception on receive command\n")
1642    sessionsLock.release()
1643
1644    if not session:
1645        log(4, "no session matching privdest %s" % b64priv[:30])
1646        return
1647   
1648    # does this session have any received data?
1649    if session.numMessages() &gt; 0:
1650        msg = session.getMessage()
1651        write("ok %s\n%s" % (len(msg), msg))
1652    else:
1653        write("ok 0\n")
1654
1655    log(4, "done")
1656
1657    return
1658
1659</t>
1660<t tx="davidmcnab.041004144338.75">def on_HELLO(self, topic, subtopic, args):
1661    """
1662    Responds to client PING
1663    """
1664    log(4, "entered")
1665    self.samSend("HELLO", "PONG")
1666    log(4, "responded to HELLO")
1667
1668</t>
1669<t tx="davidmcnab.041004144338.76">def on_SESSION(self, topic, subtopic, args):
1670
1671    log(4, "entered")
1672
1673    server = self.server
1674    client = server.i2pclient
1675    globalsessions = server.sessions
1676    localsessions = self.localsessions
1677    sessionsLock = server.sessionsLock
1678
1679    read = self.rfile.read
1680    readline = self.rfile.readline
1681    write = self.wfile.write
1682    flush = self.wfile.flush
1683
1684    if subtopic == 'CREATE':
1685       
1686        if self.samSessionIsOpen:
1687            self.samSend("SESSION", "STATUS",
1688                         RESULT="I2P_ERROR",
1689                         MESSAGE="Session_already_created",
1690                         )
1691            return
1692
1693        # get/validate STYLE arg
1694        style = self.samSessionStyle = args.get('STYLE', None)
1695        if style is None:
1696            self.samSend("SESSION", "STATUS",
1697                         RESULT="I2P_ERROR",
1698                         MESSAGE="Missing_STYLE_argument",
1699                         )
1700            return
1701        elif style not in ['STREAM', 'DATAGRAM', 'RAW']:
1702            self.samSend("SESSION", "STATUS",
1703                         RESULT="I2P_ERROR",
1704                         MESSAGE="Invalid_STYLE_argument_'%s'" % style,
1705                         )
1706            return
1707
1708        # get/validate DESTINATION arg
1709        dest = args.get('DESTINATION', None)
1710        if dest == 'TRANSIENT':
1711            # create new temporary dest
1712            dest = self.samDest = I2PDestination()
1713            destb64 = dest.toBase64Private()
1714        else:
1715            # make sure dest isn't globally or locally known
1716            if dest in globalsessions.keys() or dest in localsessions.keys():
1717                self.samSend("SESSION", "STATUS",
1718                             RESULT="DUPLICATED_DEST",
1719                             MESSAGE="Destination_'%s...'_already_in_use" % dest[:20],
1720                             )
1721                return
1722
1723            # try to reconstitute dest from given base64
1724            try:
1725                destb64 = dest
1726                dest = I2PDestination(base64private=dest)
1727            except:
1728                self.samSend("SESSION", "STATUS",
1729                             RESULT="INVALID_KEY",
1730                             MESSAGE="Bad_destination_base64_string_'%s...'" % destb64[:20],
1731                             )
1732                return
1733
1734        # got valid dest now
1735        self.dest = dest
1736        self.samDestPub = dest.toBase64()
1737
1738        if style in ['RAW', 'DATAGRAM']:
1739
1740            if style == 'DATAGRAM':
1741                # we need to know how big binary pub dests and sigs
1742                self.samDestPubBin = dest.toBin()
1743                self.samDestPubBinLen = len(self.samDestPubBin)
1744                self.samSigLen = len(self.dest.sign("nothing"))
1745               
1746                log(4, "binary pub dests are %s bytes, sigs are %s bytes" % (
1747                    self.samDestPubBinLen, self.samSigLen))
1748
1749            i2cpHost = args.get('I2CP.HOST', server.i2cphost)
1750            i2cpPort = int(args.get('I2CP.PORT', server.i2cpport))
1751
1752            # both these styles require an I2PSession object
1753            session = client.createSession(dest, host=i2cpHost, port=i2cpPort)
1754           
1755            # plug in our inbound message handler
1756            session.on_message = self.on_message
1757
1758            log(4, "Connecting session on dest '%s'" % destb64[:40])
1759            try:
1760                session.connect()
1761            except net.i2p.client.I2PSessionException:
1762                self.samSend("SESSION", "STATUS",
1763                             RESULT="I2P_ERROR",
1764                             MESSAGE="Failed_to_connect_to_i2cp_port",
1765                             )
1766                logException(3, "Failed to connect I2PSession")
1767                return
1768               
1769            log(4, "Session on dest '%s' now live" % destb64[:40])
1770           
1771            # and remember it
1772            localsessions[destb64] = session
1773            globalsessions[destb64] = session
1774            self.samSession = session
1775
1776        else: # STREAM
1777            # no need to create session object, because we're using streaming api
1778            log(4, "Creating STREAM session")
1779           
1780            # what kind of stream?
1781            direction = args.get('DIRECTION', 'BOTH')
1782            if direction not in ['BOTH', 'RECEIVE', 'CREATE']:
1783                self.samSend("SESSION", "STATUS",
1784                    RESULT="I2P_ERROR",
1785                    MESSAGE="Illegal_direction_keyword_%s" % direction.replace(" ","_"),
1786                    )
1787                return
1788
1789            if direction == 'BOTH':
1790                self.canConnect = 1
1791                self.canAccept = 1
1792            elif direction == 'RECEIVE':
1793                self.canConnect = 0
1794                self.canAccept = 1
1795            elif direction == 'CREATE':
1796                self.canConnect = 1
1797                self.canAccept = 0
1798
1799            # but we do need to mark it as being in use
1800            localsessions[destb64] = globalsessions[destb64] = None
1801
1802            # make a local socket
1803            sock = self.samSock = I2PSocket(dest)
1804
1805            # and we also need to fire up a socket listener, if not CREATE-only
1806            if self.canAccept:
1807                thread.start_new_thread(self.threadSocketListener, (sock, dest))
1808
1809        # finally, we can reply with the good news
1810        self.samSend("SESSION", "STATUS",
1811                     RESULT="OK",
1812                     )
1813
1814    else: # subtopic != CREATE
1815        self.samSend("SESSION", "STATUS",
1816                     RESULT="I2P_ERROR",
1817                     MESSAGE="Invalid_command_'SESSION_%s'" % subtopic,
1818                     )
1819        return
1820
1821</t>
1822<t tx="davidmcnab.041004144338.77">def on_SESSION_CREATE(self, topic, subtopic, args):
1823
1824    log(4, "entered")
1825
1826    server = self.server
1827    client = server.i2pclient
1828    globalsessions = server.sessions
1829    localsessions = self.localsessions
1830    sessionsLock = server.sessionsLock
1831
1832    read = self.rfile.read
1833    readline = self.rfile.readline
1834    write = self.wfile.write
1835    flush = self.wfile.flush
1836
1837</t>
1838<t tx="davidmcnab.041004144338.78">def on_STREAM(self, topic, subtopic, args):
1839
1840    log(4, "entered")
1841
1842    server = self.server
1843    client = server.i2pclient
1844    globalsessions = server.sessions
1845    sessionsLock = server.sessionsLock
1846
1847    read = self.rfile.read
1848    readline = self.rfile.readline
1849    write = self.wfile.write
1850    flush = self.wfile.flush
1851
1852    if subtopic == 'CONNECT':
1853        # who are we connecting to again?
1854        remdest = I2PDestination(base64=args['DESTINATION'])
1855        id = int(args['ID'])
1856   
1857        try:
1858            log(4, "Trying to connect to remote peer %s..." % args['DESTINATION'])
1859            sock = self.samSock.connect(remdest)
1860            log(4, "Connected to remote peer %s..." % args['DESTINATION'])
1861            self.localstreams[id] = sock
1862            self.samSend("STREAM", "STATUS",
1863                         RESULT='OK',
1864                         ID=id,
1865                         )
1866            thread.start_new_thread(self.threadSocketReceiver, (sock, id))
1867
1868        except:
1869            log(4, "Failed to connect to remote peer %s..." % args['DESTINATION'])
1870            self.samSend("STREAM", "STATUS",
1871                         RESULT='I2P_ERROR',
1872                         MESSAGE='exception_on_connect',
1873                         ID=id,
1874                         )
1875
1876    elif subtopic == 'SEND':
1877        # send to someone
1878        id = int(args['ID'])
1879        try:
1880            sock = self.localstreams[id]
1881            sock.send(args['DATA'])
1882        except:
1883            logException(4, "send failed")
1884
1885
1886
1887
1888</t>
1889<t tx="davidmcnab.041004144338.79">def on_DATAGRAM(self, topic, subtopic, args):
1890    r"""
1891    DATAGRAM SEND
1892    DESTINATION=$base64key
1893    SIZE=$numBytes\n[$numBytes of data]
1894
1895    All datagram messages have a signature/hash header, formatted as:
1896        - sender's binary public dest
1897        - S(H(sender_bin_pubdest + recipient_bin_pubdest + msg))
1898    """
1899    log(4, "entered")
1900
1901    # at this stage of things, we don't know how to handle anything except SEND
1902    if subtopic != 'SEND':
1903        log(3, "Got illegal subtopic '%s' in DATAGRAM command" % subtopic)
1904        return
1905
1906    # get the details
1907    peerdestb64 = args['DESTINATION']
1908    peerdest = I2PDestination(base64=peerdestb64)
1909    peerdestBin = base64dec(peerdestb64)
1910    data = args['DATA']
1911
1912    # make up the header
1913    log(4, "samDestPubBin (%s) %s" % (type(self.samDestPubBin), repr(self.samDestPubBin)))
1914    log(4, "peerdestBin (%s) %s" % (type(peerdestBin), repr(peerdestBin)))
1915    log(4, "data (%s) %s" % (type(data), repr(data)))
1916
1917    hashed = shahash(self.samDestPubBin + peerdestBin + data)
1918    log(4, "hashed=%s" % repr(hashed))
1919
1920    sig = self.dest.sign(hashed)
1921    log(4, "sig=%s" % repr(sig))
1922    hdr = self.samDestPubBin + sig
1923   
1924    # send the thing
1925    self.samSession.sendMessage(peerdest, hdr + data)
1926
1927</t>
1928<t tx="davidmcnab.041004144338.80">def on_RAW(self, topic, subtopic, args):
1929    r"""
1930    RAW SEND
1931    DESTINATION=$base64key
1932    SIZE=$numBytes\n[$numBytes of data]
1933    """
1934    log(4, "entered")
1935
1936    # at this stage of things, we don't know how to handle anything except SEND
1937    if subtopic != 'SEND':
1938        return
1939
1940    # get the details
1941    peerdest = I2PDestination(base64=args['DESTINATION'])
1942    msg = args['DATA']
1943
1944    # send the thing
1945    self.samSession.sendMessage(peerdest, msg)
1946</t>
1947<t tx="davidmcnab.041004144338.81">def on_NAMING(self, topic, subtopic, args):
1948
1949    log(4, "entered: %s %s %s" % (repr(topic), repr(subtopic), repr(args)))
1950
1951    # at this stage of things, we don't know how to handle anything except LOOKUP
1952    if subtopic != 'LOOKUP':
1953        return
1954
1955    # get the details
1956    host = args['NAME']
1957
1958    log(4, "looking up host %s" % host)
1959   
1960    # try to lookup
1961    jdest = self.namingService.lookup(host)
1962
1963    if not jdest:
1964        log(4, "host %s not found" % host)
1965        self.samSend("NAMING", "REPLY",
1966                     RESULT="KEY_NOT_FOUND",
1967                     NAME=host,
1968                     )
1969        return
1970
1971    try:
1972        b64 = I2PDestination(dest=jdest).toBase64()
1973        self.samSend("NAMING", "REPLY",
1974                     RESULT="OK",
1975                     NAME=host,
1976                     VALUE=b64,
1977                     )
1978        log(4, "host %s found and valid key returned" % host)
1979        return
1980    except:
1981        log(4, "host %s found but key invalid" % host)
1982        self.samSend("NAMING", "REPLY",
1983                     RESULT="INVALID_KEY",
1984                     NAME=host,
1985                     )
1986
1987</t>
1988<t tx="davidmcnab.041004144338.82">def on_DEST(self, topic, subtopic, args):
1989
1990    log(4, "Generating dest")
1991
1992    dest = I2PDestination()
1993    priv = dest.toBase64Private()
1994    pub = dest.toBase64()
1995
1996    log(4, "Sending dest to client")
1997
1998    self.samSend("DEST", "REPLY", PUB=pub, PRIV=priv)
1999
2000    log(4, "done")
2001</t>
2002<t tx="davidmcnab.041004144338.83">def on_message(self, msg):
2003    """
2004    This callback gets plugged into the I2PSession object,
2005    so we can asychronously notify our client when stuff arrives
2006    """
2007    if self.samSessionStyle == 'RAW':
2008        self.samSend("RAW", "RECEIVE", msg)
2009
2010    elif self.samSessionStyle == 'DATAGRAM':
2011        # ain't so simple, we gotta rip and validate the header
2012        remdestBin = msg[:self.samDestPubBinLen]
2013        log(4, "remdestBin=%s" % repr(remdestBin))
2014
2015        sig = msg[self.samDestPubBinLen:self.samDestPubBinLen+self.samSigLen]
2016        log(4, "sig=%s" % repr(sig))
2017
2018        data = msg[self.samDestPubBinLen+self.samSigLen:]
2019        log(4, "data=%s" % repr(data))
2020       
2021        # now try to verify
2022        hashed = shahash(remdestBin + self.samDestPubBin + data)
2023        log(4, "hashed=%s" % repr(hashed))
2024
2025        remdest = I2PDestination(bin=remdestBin)
2026        if remdest.verify(hashed, sig):
2027            # fine - very good, pass it on
2028            log(4, "sig from peer is valid")
2029            self.samSend("DATAGRAM", "RECEIVE", data,
2030                         DESTINATION=remdest.toBase64(),
2031                         )
2032        else:
2033            log(4, "DATAGRAM sig from peer is invalid")
2034</t>
2035<t tx="davidmcnab.041004144338.84">def threadSocketListener(self, sock, dest):
2036    """
2037    Listens for incoming socket connections, and
2038    notifies the client accordingly
2039    """
2040    destb64 = dest.toBase64()
2041
2042    log(4, "Listening for connections to %s..." % destb64)
2043
2044    sock.bind()
2045    sock.listen()
2046
2047    while 1:
2048        log(4, "Awaiting next connection to %s..." % destb64)
2049        newsock = sock.accept()
2050        log(4, "Got connection to %s..." % destb64)
2051
2052        # need an id, negative
2053        id = - self.server.samAllocId()
2054
2055        # register it in local and global streams
2056        self.localstreams[id] = self.globalstreams[id] = newsock
2057
2058        # fire up the receiver thread
2059        thread.start_new_thread(self.threadSocketReceiver, (newsock, id))
2060       
2061        # who is connected to us?
2062        remdest = newsock.remdest
2063        remdest_b64 = remdest.toBase64()
2064       
2065        # and notify the client
2066        self.samSend("STREAM", "CONNECTED",
2067                     DESTINATION=remdest_b64,
2068                     ID=id)
2069
2070</t>
2071<t tx="davidmcnab.041004144338.85">def samParse(self, flds):
2072    """
2073    carves up a SAM command, returns it as a 3-tuple:
2074        - cmd - command string
2075        - subcmd - subcommand string
2076        - dargs - dict of args
2077    """
2078    cmd = flds[0]
2079    subcmd = flds[1]
2080    args = flds[2:]
2081   
2082    dargs = {}
2083    for arg in args:
2084        try:
2085            name, val = arg.split("=", 1)
2086        except:
2087            logException(3, "failed to process %s in %s" % (repr(arg), repr(flds)))
2088            raise
2089        dargs[name] = val
2090
2091    # read and add data if any
2092    if dargs.has_key('SIZE'):
2093        size = dargs['SIZE'] = int(dargs['SIZE'])
2094        dargs['DATA'] = self._recvbytes(size)
2095
2096    #log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v[:40])) for k,v in dargs.items()]))
2097    log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v)) for k,v in dargs.items()]))
2098
2099    return cmd, subcmd, dargs
2100
2101
2102
2103</t>
2104<t tx="davidmcnab.041004144338.86">def samSend(self, topic, subtopic, data=None, **kw):
2105    """
2106    Sends a SAM message (reply?) back to client
2107   
2108    Arguments:
2109        - topic - the first word in the reply, eg 'STREAM'
2110        - subtopic - the second word of the reply, eg 'CONNECTED'
2111        - data - a string of raw data to send back (optional)
2112    Keywords:
2113        - extra 'name=value' items to pass back.
2114   
2115    Notes:
2116        1. SIZE is not required. If sending back data, it will
2117           be sized and a SIZE arg inserted automatically.
2118        2. a dict of values can be passed to the 'args' keyword, in lieu
2119           of direct keywords. This allows for cases where arg names would
2120           cause python syntax clashes, eg 'tunnels.depthInbound'
2121    """
2122    items = [topic, subtopic]
2123
2124    # stick in SIZE if needed
2125    if data is not None:
2126        kw['SIZE'] = str(len(data))
2127    else:
2128        data = '' # for later
2129
2130    self.samCreateArgsList(kw, items)
2131   
2132    # and whack it together
2133    buf = " ".join(items) + '\n' + data
2134
2135    # and ship it
2136    self.sendLock.acquire()
2137    try:
2138        self._sendbytes(buf)
2139    except:
2140        self.sendLock.release()
2141        raise
2142    self.sendLock.release()
2143
2144</t>
2145<t tx="davidmcnab.041004144338.87">def samCreateArgsList(self, kw1, lst):
2146    for k,v in kw1.items():
2147        if k == 'args':
2148            self.samCreateArgsList(v, lst)
2149        else:
2150            lst.append("=".join([str(k), str(v)]))
2151</t>
2152<t tx="davidmcnab.041004144338.88">def _sendbytes(self, raw):
2153
2154    self.wfile.write(raw)
2155    self.wfile.flush()
2156</t>
2157<t tx="davidmcnab.041004144338.89">def _recvbytes(self, count):
2158    """
2159    Does a guaranteed read of n bytes
2160    """
2161    read = self.rfile.read
2162
2163    chunks = []
2164    needed = count
2165    while needed &gt; 0:
2166        chunk = read(needed)
2167        chunklen = len(chunk)
2168        needed -= chunklen
2169        chunks.append(chunk)
2170    raw = "".join(chunks)
2171
2172    # done
2173    return raw
2174
2175</t>
2176<t tx="davidmcnab.041004144338.90">class NoPrivateKey(Exception):
2177    """Destination object has no private key"""
2178
2179class I2PSocketError(Exception):
2180    """Error working with I2PSocket objects"""
2181</t>
2182<t tx="davidmcnab.041004144338.91"></t>
2183<t tx="davidmcnab.041004144338.92">def shahash(s):
2184    """
2185    Calculates SHA Hash of a string, as a string, using
2186    I2P hashing facility
2187    """
2188    h = net.i2p.crypto.SHA256Generator().calculateHash(s)
2189    h = bytearray2str(h.getData())
2190    return h
2191</t>
2192<t tx="davidmcnab.041004144338.93">def base64enc(s):
2193    return net.i2p.data.Base64.encode(s)
2194</t>
2195<t tx="davidmcnab.041004144338.94">def base64dec(s):
2196    return bytearray2str(net.i2p.data.Base64.decode(s))
2197
2198</t>
2199<t tx="davidmcnab.041004144338.95">def str2bytearray(s):
2200    """
2201    Convenience - converts python string to java-friendly byte array
2202    """
2203    a = []
2204    for c in s:
2205        n = ord(c)
2206        if n &gt;= 128:
2207            n = n - 256
2208        a.append(n)
2209    return a
2210
2211</t>
2212<t tx="davidmcnab.041004144338.96">def bytearray2str(a):
2213    """
2214    Convenience - converts java-friendly byte array to python string
2215    """
2216    chars = []
2217    for n in a:
2218        if n &lt; 0:
2219            n += 256
2220        chars.append(chr(n))
2221    return "".join(chars)
2222
2223</t>
2224<t tx="davidmcnab.041004144338.97">def byteoutstream2str(bs):
2225    """
2226    Convenience - converts java-friendly byteoutputstream to python string
2227    """
2228    chars = []
2229    while 1:
2230        c = bs.read()
2231        if c &gt;= 0:
2232            chars.append(chr(c))
2233        else:
2234            break
2235    return "".join(chars)
2236
2237</t>
2238<t tx="davidmcnab.041004144338.98">def dict2props(d):
2239    """
2240    Converts a python dict d into a java.util.Properties object
2241    """
2242    props = java.util.Properties()
2243    for k,v in d.items():
2244        props[k] = str(v)
2245    return props
2246
2247
2248</t>
2249<t tx="davidmcnab.041004144338.99">def takeKey(somedict, keyname, default=None):
2250    """
2251    Utility function to destructively read a key from a given dict.
2252    Same as the dict's 'takeKey' method, except that the key (if found)
2253    sill be deleted from the dictionary.
2254    """
2255    if somedict.has_key(keyname):
2256        val = somedict[keyname]
2257        del somedict[keyname]
2258    else:
2259        val = default
2260    return val
2261</t>
2262<t tx="davidmcnab.041004144338.100">def log(level, msg, nPrev=0):
2263
2264    # ignore messages that are too trivial for chosen verbosity
2265    if level &gt; verbosity:
2266        return
2267
2268    loglock.acquire()
2269    try:
2270        # rip the stack
2271        caller = traceback.extract_stack()[-(2+nPrev)]
2272        path, line, func = caller[:3]
2273        path = os.path.split(path)[1]
2274        full = "%s:%s:%s():\n* %s" % (
2275            path,
2276            line,
2277            func,
2278            msg.replace("\n", "\n   + "))
2279        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
2280        msg = "%s %s\n" % (now, full)
2281   
2282        if logfile == sys.stdout:
2283            print msg
2284        else:
2285            file(logfile, "a").write(msg+"\n")
2286    except:
2287        s = StringIO.StringIO()
2288        traceback.print_exc(file=s)
2289        print s.getvalue()
2290        print "Logger crashed"
2291    loglock.release()</t>
2292<t tx="davidmcnab.041004144338.101">def logException(level, msg=''):
2293    s = StringIO.StringIO()
2294    traceback.print_exc(file=s)
2295    log(level, "%s\n%s" % (s.getvalue(), msg), 1)
2296</t>
2297<t tx="davidmcnab.041004144338.102">def usage(detailed=0):
2298   
2299    print "Usage: %s &lt;options&gt; [&lt;command&gt;]" % sys.argv[0]
2300    if not detailed:
2301        print "Run with '-h' to get detailed help"
2302        sys.exit(0)
2303
2304    print "I2PSAM is a bridge that allows I2P client programs to access the"
2305    print "I2P network by talking over a plaintext socket connection."
2306    print "References:"
2307    print "   - http://www.freenet.org.nz/i2p - source, doco, downloadables"
2308    print "   - http://drupal.i2p.net/node/view/144 - I2P SAM specification"
2309    print
2310    print "Options:"
2311    print "  -h, -?, --help        - display this help"
2312    print "  -v, --version         - print program version"
2313    print "  -V, --verbosity=n     - set verbosity to n, default 2, 1==quiet, 4==noisy"
2314    print "  -H, --listenhost=host - specify host to listen on for client connections"
2315    print "  -P, --listenport=port - port to listen on for client connections"
2316    print "      --i2cphost=host   - hostname of I2P router's I2CP interface"
2317    print "      --i2cpport=port   - port of I2P router's I2CP interface"
2318    print
2319    print "Commands:"
2320    print "     (run with no commands to launch SAM server)"
2321    print "     samserver - runs as a SAM server"
2322    print "     test - run a suite of self-tests"
2323    print "     testsocket - run only the socket test"
2324    print "     testbidirsocket - run socket test in bidirectional mode"
2325    print
2326   
2327    sys.exit(0)
2328</t>
2329<t tx="davidmcnab.041004144338.103">def main():
2330
2331    argv = sys.argv
2332    argc = len(argv)
2333
2334    # -------------------------------------------------
2335    # do the getopt command line parsing
2336
2337    try:
2338        opts, args = getopt.getopt(sys.argv[1:],
2339                                   "h?vV:H:P:",
2340                                   ['help', 'version', 'verbosity=',
2341                                    'listenhost=', 'listenport=',
2342                                    'i2cphost=', 'i2cpport=',
2343                                    ])
2344    except:
2345        traceback.print_exc(file=sys.stdout)
2346        usage("You entered an invalid option")
2347
2348    #print "args=%s" % args
2349
2350    serveropts = {}
2351    for opt, val in opts:
2352        if opt in ['-h', '-?', '--help']:
2353            usage(1)
2354        elif opt in ['-v', '--version']:
2355            print "I2P SAM version %s" % version
2356            sys.exit(0)
2357        elif opt in ['-V', '--verbosity']:
2358            serveropts['verbosity'] = int(val)
2359        elif opt in ['-H', '--listenhost']:
2360            serveropts['host'] = val
2361        elif opt in ['-P', '--listenport']:
2362            serveropts['port'] = int(val)
2363        elif opt in ['--i2cphost']:
2364            serveropts['i2cphost'] = val
2365        elif opt in ['--i2cpport']:
2366            serveropts['i2cpport'] = int(val)
2367        else:
2368            usage(0)
2369
2370    # --------------------------------------------------
2371    # now run in required mode, default is 'samserver'
2372
2373    if len(args) == 0:
2374        cmd = 'samserver'
2375    else:
2376        cmd = args[0]
2377
2378    if cmd == 'samserver':
2379
2380        log(2, "Running I2P SAM Server...")
2381        server = I2PSamServer(**serveropts)
2382        server.run()
2383
2384    elif cmd == 'test':
2385       
2386        print "RUNNING full I2PSAM Jython TEST SUITE"
2387        testsigs()
2388        testdests()
2389        testsession()
2390        testsocket()
2391
2392    elif cmd == 'testsocket':
2393       
2394        print "RUNNING SOCKET TEST"
2395        testsocket(0)
2396
2397    elif cmd == 'testbidirsocket':
2398        print "RUNNING BIDIRECTIONAL SOCKET TEST"
2399        testsocket(1)
2400
2401    else:
2402        # spit at unrecognised option
2403        usage(0)
2404
2405</t>
2406<t tx="davidmcnab.041004144338.104"></t>
2407<t tx="davidmcnab.041004144338.105">def testdests():
2408    """
2409    Demo function which tests out dest generation and import/export
2410    """
2411    print
2412    print "********************************************"
2413    print "Testing I2P destination create/export/import"
2414    print "********************************************"
2415    print
2416
2417    print "Generating a destination"
2418    d1 = I2PDestination()
2419
2420    print "Exporting and importing dest1 in several forms"
2421
2422    print "public binary string..."
2423    d1_bin = d1.toBin()
2424    d2_bin = I2PDestination(bin=d1_bin)
2425
2426    print "public binary file..."
2427    d1.toBinFile("temp-d1-bin")
2428    d2_binfile = I2PDestination(binfile="temp-d1-bin")
2429
2430    print "private binary string..."
2431    d1_binprivate = d1.toBinPrivate()
2432    d2_binprivate = I2PDestination(binprivate=d1_binprivate)
2433
2434    print "private binary file..."
2435    d1.toBinFilePrivate("temp-d1-bin-private")
2436    d2_binfileprivate = I2PDestination(binfileprivate="temp-d1-bin-private")
2437
2438    print "public base64 string..."
2439    d1_b64 = d1.toBase64()
2440    d2_b64 = I2PDestination(base64=d1_b64)
2441
2442    print "public base64 file..."
2443    d1.toBase64File("temp-d1-b64")
2444    d2_b64file = I2PDestination(base64file="temp-d1-b64")
2445
2446    print "private base64 string..."
2447    d1_base64private = d1.toBase64Private()
2448    d2_b64private = I2PDestination(base64private=d1_base64private)
2449
2450    print "private base64 file..."
2451    d1.toBase64FilePrivate("temp-d1-b64-private")
2452    d2_b64fileprivate = I2PDestination(base64fileprivate="temp-d1-b64-private")
2453
2454    print "All destination creation/import/export tests passed!"
2455
2456
2457</t>
2458<t tx="davidmcnab.041004144338.106">def testsigs():
2459    global d1, d1pub, d1sig, d1res
2460   
2461    print
2462    print "********************************************"
2463    print "Testing I2P dest-based signatures"
2464    print "********************************************"
2465    print
2466   
2467    print "Creating dest..."
2468    d1 = I2PDestination()
2469
2470    s_good = "original stuff that we're signing"
2471    s_bad = "non-original stuff we're trying to forge"
2472   
2473    print "Signing some shit against d1..."
2474    d1sig = d1.sign(s_good)
2475
2476    print "Creating public dest d1pub"
2477    d1pub = I2PDestination(bin=d1.toBin())
2478
2479    print "Verifying original data with d1pub"
2480    res = d1pub.verify(s_good, d1sig)
2481    print "Result: %s (should be 1)" % repr(res)
2482   
2483    print "Trying to verify on a different string"
2484    res1 = d1pub.verify(s_bad, d1sig)
2485    print "Result: %s (should be 0)" % repr(res1)
2486   
2487    if res and not res1:
2488        print "signing/verifying test passed"
2489    else:
2490        print "SIGNING/VERIFYING TEST FAILED"
2491
2492</t>
2493<t tx="davidmcnab.041004144338.107">def testsession():
2494
2495    global c, d1, d2, s1, s2
2496
2497    print
2498    print "********************************************"
2499    print "Testing I2P dest-&gt;dest messaging"
2500    print "********************************************"
2501    print
2502   
2503    print "Creating I2P client..."
2504    c = I2PClient()
2505
2506    print "Creating destination d1..."
2507    d1 = c.createDestination()
2508
2509    print "Creating destination d2..."
2510    d2 = c.createDestination()
2511
2512    print "Creating destination d3..."
2513    d3 = c.createDestination()
2514
2515    print "Creating session s1 on dest d1..."
2516    s1 = c.createSession(d1, host='localhost', port=7654)
2517
2518    print "Creating session s2 on dest d2..."
2519    s2 = c.createSession(d2)
2520
2521    print "Connecting session s1..."
2522    s1.connect()
2523
2524    print "Connecting session s2..."
2525    s2.connect()
2526
2527    print "Sending message from s1 to d2..."
2528    s1.sendMessage(d2, "Hi there, s2!!")
2529
2530    print "Retrieving message from s2..."
2531    print "got: %s" % repr(s2.getMessage())
2532
2533    print "Sending second message from s1 to d2..."
2534    s1.sendMessage(d2, "Hi there again, s2!!")
2535
2536    print "Retrieving message from s2..."
2537    print "got: %s" % repr(s2.getMessage())
2538
2539    print "Sending message from s1 to d3 (should take ages then fail)..."
2540    res = s1.sendMessage(d3, "This is futile!!")
2541    print "result of that send was %s (should have been 0)" % res
2542
2543    print "Destroying session s1..."
2544    s1.destroySession()
2545
2546    print "Destroying session s2..."
2547    s2.destroySession()
2548
2549    print "session tests passed!"
2550</t>
2551<t tx="davidmcnab.041004144338.108">def testsocket(bidirectional=0):
2552
2553    global d1, d2, s1, s2
2554
2555    print
2556    print "********************************************"
2557    print "Testing I2P streaming interface"
2558    print "********************************************"
2559    print
2560   
2561    print "Creating destinations..."
2562    dServer = I2PDestination()
2563    dClient = I2PDestination()
2564
2565    print "Creating sockets..."
2566    sServer = I2PSocket(dServer)
2567    sClient = I2PSocket(dClient)
2568
2569    # server thread which simply reads a line at a time, then echoes
2570    # that line back to the client
2571    def servThread(s):
2572        print "server: binding socket"
2573        s.bind()
2574        print "server: setting socket to listen"
2575        s.listen()
2576        print "server: awaiting connection"
2577        sock = s.accept()
2578        print "server: got connection"
2579
2580        sock.send("Hello, echoing...\n")
2581        buf = ''
2582        while 1:
2583            c = sock.recv(1)
2584            if c == '':
2585                sock.close()
2586                print "server: socket closed"
2587                break
2588
2589            buf += c
2590            if c == '\n':
2591                sock.send("SERVER: "+buf)
2592                buf = ''
2593
2594    # client thread which reads lines and prints them to stdout
2595    def clientThread(s):
2596        buf = ''
2597        while 1:
2598            c = s.recv(1)
2599            if c == '':
2600                s.close()
2601                print "client: socket closed"
2602                break
2603            buf += c
2604            if c == '\n':
2605                print "client: got %s" % repr(buf)
2606                buf = ''
2607
2608    print "launching server thread..."
2609    thread.start_new_thread(servThread, (sServer,))
2610
2611    if bidirectional:
2612        # dummy thread which accepts connections TO client socket
2613        def threadDummy(s):
2614            print "dummy: listening"
2615            s.listen()
2616            print "dummy: accepting"
2617   
2618            sock = s.accept()
2619            print "dummy: got connection"
2620   
2621        print "test - launching dummy client accept thread"
2622        thread.start_new_thread(threadDummy, (sClient,))
2623
2624    print "client: trying to connect"
2625    sClient.connect(dServer)
2626
2627    print "client: connected, launching rx thread"
2628    thread.start_new_thread(clientThread, (sClient,))
2629
2630    while 1:
2631        line = raw_input("Enter something (q to quit)&gt; ")
2632        if line == 'q':
2633            print "closing client socket"
2634            sClient.close()
2635            break
2636        sClient.send(line+"\n")
2637
2638    print "I2PSocket test apparently succeeded"
2639
2640
2641</t>
2642<t tx="davidmcnab.041004144338.109">if __name__ == '__main__':
2643    main()
2644
2645</t>
2646<t tx="davidmcnab.041004144551">@first #!/usr/bin/env python
2647"""
2648Implements a client API for I2CP messaging via SAM
2649
2650Very simple I2P messaging interface, which should prove easy
2651to reimplement in your language of choice
2652
2653This module can be used from cpython or jython
2654
2655Run this module without arguments to see a demo in action
2656(requires SAM server to be already running)
2657"""
2658@others
2659
2660</t>
2661<t tx="davidmcnab.041004144551.1">import sys, os, socket, thread, threading, Queue, traceback, StringIO, time
2662
2663from pdb import set_trace
2664
2665</t>
2666<t tx="davidmcnab.041004144551.2"># -----------------------------------------
2667# server access settings
2668
2669i2psamhost = '127.0.0.1'
2670i2psamport = 7656
2671
2672# ------------------------------------------
2673# logging settings
2674
2675# 1=v.quiet, 2=normal, 3=verbose, 4=debug, 5=painful
2676verbosity = 5
2677
2678# change to a filename to log there instead
2679logfile = sys.stdout
2680
2681# when set to 1, and when logfile != sys.stdout, log msgs are written
2682# both to logfile and console stdout
2683log2console = 1
2684
2685# don't touch this!
2686loglock = threading.Lock()
2687
2688</t>
2689<t tx="davidmcnab.041004144551.3">class I2PServerFail(Exception):
2690    """
2691    A failure in connecting to the I2CP server
2692    """
2693
2694class I2PCommandFail(Exception):
2695    """
2696    A failure in an I2CP command
2697    """
2698    pass
2699
2700class I2PStreamClosed(Exception):
2701    """
2702    Stream is not open
2703    """
2704</t>
2705<t tx="davidmcnab.041004144551.4">class I2PSamClient:
2706    """
2707    Implements a reference client for accessing I2CP via i2psam
2708   
2709    Connects to i2psam's I2PSamServer, sends commands
2710    and receives results
2711
2712    The primitives should be reasonably self-explanatory
2713
2714    Usage summary:
2715        1. create one or more I2PSamClient instances per process (1 should be fine)
2716        2. invoke the L{genkeys} method to create destination keypairs
2717        3. create sessions objects via the L{createSession} method
2718        4. use these session objects to send and receive data
2719        5. destroy the session objects when you're done
2720   
2721    Refer to the function L{demo} for a simple example
2722    """
2723    @others
2724</t>
2725<t tx="davidmcnab.041004144551.5"># server host/port settings exist here in case you might
2726# have a reason for overriding in a subclass
2727
2728host = i2psamhost
2729port = i2psamport
2730
2731i2cpHost = None
2732i2cpPort = None
2733
2734</t>
2735<t tx="davidmcnab.041004144551.6">def __init__(self, **kw):
2736    """
2737    Creates a client connection to i2psam listener
2738   
2739    Keywords:
2740        - host - host to connect to (default 127.0.0.1)
2741        - port - port to connect to (default 7656)
2742    """
2743    # get optional host/port
2744    log(4, "entered")
2745
2746    self.host = kw.get('host', self.host)
2747    self.port = int(kw.get('port', self.port))
2748
2749    self.cmdLock = threading.Lock()
2750
2751    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2752
2753    self.lockHello = threading.Lock()
2754    self.sendLock = threading.Lock()
2755    self.qNewDests = Queue.Queue()
2756    self.qSession = Queue.Queue()
2757    self.qDatagrams = Queue.Queue()
2758    self.qRawMessages = Queue.Queue()
2759
2760    self.namingReplies = {}
2761    self.namingCache = {}
2762
2763    self.streams = {} # currently open streams, keyed by id
2764    self.streamConnectReplies = {} # holds queues awaiting connect resp, keyed by id
2765    self.qNewStreams = Queue.Queue() # incoming connections
2766
2767    self.samNextIdLock = threading.Lock()
2768    self.samNextId = 1
2769
2770    self.isRunning = 1
2771
2772
2773    log(4, "trying connection to SAM server...")
2774    try:
2775        self.sock.connect((self.host, self.port))
2776    except:
2777        raise I2PServerFail(
2778            "Connection to i2psam server failed\n"
2779            "(are you sure your I2P router is running, and\n"
2780            "listening for I2CP connections on %s:%s?)" % (self.host, self.port)
2781            )
2782
2783    # fire up receiver thread
2784    thread.start_new_thread(self.threadRx, ())
2785
2786    # ping the server
2787    try:
2788        log(4, "trying to ping SAM server...")
2789        self.samHello()
2790    except:
2791        logException(4, "Exception on handshaking")
2792        raise I2PServerFail("Failed to handshake with i2psam server")
2793
2794    # connected fine
2795    log(2, "I2CP Client successfully connected")
2796</t>
2797<t tx="davidmcnab.041004144551.7">def createSession(self, privdest):
2798    """
2799    DEPRECATED - use sam* methods instead!
2800
2801    Creates a session using private destkey
2802    """
2803    #3. createsession:
2804    #    - client-&gt;server:
2805    #        - createsession &lt;base64private&gt;\n
2806    #    - server-&gt;client:
2807    #        - ok\n  OR
2808    #        - error[ &lt;reason&gt;]\n
2809
2810    self.cmdLock.acquire()
2811    try:
2812        self._sendline("createsession %s" % privdest)
2813        respitems = self._recvline().split(" ", 1)
2814        if respitems[0] == 'ok':
2815            res = None
2816        else:
2817            res = respitems[1]
2818    except:
2819        logException(2, "createsession fail")
2820        self.cmdLock.release()
2821        raise
2822
2823    self.cmdLock.release()
2824
2825    if res:
2826        raise I2PCommandFail("createsession fail: "+res)
2827
2828    return I2PRemoteSession(self, privdest)
2829
2830</t>
2831<t tx="davidmcnab.041004144551.8">def destroySession(self, privdest):
2832    """
2833    DEPRECATED - use sam* methods instead!
2834
2835    Destrlys a session using private destkey
2836    """
2837    #4. destroysession:
2838    #    - client-&gt;server:
2839    #        - destroysession &lt;base64private&gt;\n
2840    #    - server-&gt;client:
2841    #        - ok\n OR
2842    #        - error[ &lt;reason&gt;]\n
2843
2844    self.cmdLock.acquire()
2845    try:
2846        self._sendline("destroysession %s" % privdest)
2847        respitems = self._recvline().split(" ", 1)
2848        if respitems[0] == 'ok':
2849            res = None
2850        else:
2851            res = respitems[1]
2852    except:
2853        logException(2, "destroysession fail")
2854        self.cmdLock.release()
2855        raise
2856
2857    self.cmdLock.release()
2858
2859    if res:
2860        raise I2PCommandFail("destroysession fail: " + res)
2861
2862    return res
2863
2864</t>
2865<t tx="davidmcnab.041004144551.9">def send(self, privdest, peerdest, msg):
2866    """
2867    DEPRECATED - use sam* methods instead!
2868
2869    Sends a block of data from local dest to remote dest
2870    """
2871    #5. send:
2872    #    - client-&gt;server:
2873    #        - send &lt;size&gt; &lt;localbase64private&gt; &lt;remotebase64dest&gt;\ndata
2874    #    - server-&gt;client:
2875    #        - ok\n OR
2876    #        - error[ &lt;reason&gt;]\n
2877
2878    self.cmdLock.acquire()
2879    try:
2880        self._sendline("send %s %s %s" % (len(msg), privdest, peerdest))
2881        self._sendbytes(msg)
2882        line = self._recvline()
2883        #print "** %s" % line
2884        respitems = line.split(" ", 1)
2885        if respitems[0] == 'ok':
2886            res = None
2887        else:
2888            res = " ".join(respitems[1:])
2889    except:
2890        logException(2, "send fail")
2891        self.cmdLock.release()
2892        raise
2893
2894    self.cmdLock.release()
2895
2896    if res:
2897        raise I2PCommandFail("send fail: " + res)
2898
2899    return res
2900
2901</t>
2902<t tx="davidmcnab.041004144551.10">def receive(self, privdest):
2903    """
2904    DEPRECATED - use sam* methods instead!
2905
2906    receives a block of data, returning string, or None if no data available
2907    """
2908    #6. receive:
2909    #    - client-&gt;server:
2910    #        - receive &lt;localbase64private&gt;\n
2911    #    - server-&gt;client:
2912    #        - ok &lt;size&gt;\ndata OR
2913    #        - error[ &lt;reason&gt;]\n
2914
2915    self.cmdLock.acquire()
2916    try:
2917        self._sendline("receive %s" % privdest)
2918        respitems = self._recvline().split(" ", 1)
2919        if respitems[0] == 'ok':
2920            res = None
2921            size = int(respitems[1])
2922            msg = self._recvbytes(size)
2923            res = None
2924        else:
2925            res = respitems[1]
2926    except:
2927        logException(2, "receive fail")
2928        self.cmdLock.release()
2929        raise
2930
2931    self.cmdLock.release()
2932
2933    if res:
2934        raise I2PCommandFail("destroysession fail: " + res)
2935
2936    return msg
2937</t>
2938<t tx="davidmcnab.041004144551.11"></t>
2939<t tx="davidmcnab.041004144551.12">def samHello(self):
2940    """
2941    Sends a quick HELLO PING to SAM server and awaits response
2942    Arguments:
2943        - none
2944
2945    Keywords:
2946        - none
2947   
2948    Returns:
2949        - nothing (None) if ping sent and pong received, or raises an exception if
2950          failed
2951    """
2952    self.lockHello.acquire()
2953    self.samSend("HELLO", "PING")
2954    self.lockHello.acquire()
2955    self.lockHello.release()
2956</t>
2957<t tx="davidmcnab.041004144551.13">def samSessionCreate(self, style, dest, **kw):
2958    """
2959    Creates a SAM session
2960   
2961    Arguments:
2962        - style - one of 'STREAM', 'DATAGRAM' or 'RAW'
2963        - dest - base64 private destination
2964   
2965    Keywords:
2966        - direction - only used for STREAM sessions, can be RECEIVE,
2967          CREATE or BOTH (default BOTH)
2968        - i2cphost - hostname for the SAM bridge to contact i2p router on
2969        - i2cpport - port for the SAM bridge to contact i2p router on
2970   
2971    Returns:
2972        - 'OK' if session was created successfully, or a tuple
2973          (keyword, message) if not
2974    """
2975    kw1 = dict(kw)
2976    kw1['STYLE'] = self.samStyle = style
2977    kw1['DESTINATION'] = dest
2978    if style == 'STREAM':
2979        direction = kw.get('direction', 'BOTH')
2980        kw1['DIRECTION'] = direction
2981        if direction == 'BOTH':
2982            self.canAccept = 1
2983            self.canConnect = 1
2984        elif direction == 'RECEIVE':
2985            self.canAccept = 1
2986            self.canConnect = 0
2987        elif direction == 'CREATE':
2988            self.canAccept = 0
2989            self.canConnect = 1
2990        else:
2991            raise I2PCommandFail("direction keyword must be one of RECEIVE, CREATE or BOTH")
2992
2993    # stick in i2cp host/port if specified
2994    if kw.has_key('i2cphost'):
2995        kw1['I2CP.HOST'] = kw['i2cphost']
2996    if kw.has_key('i2cpport'):
2997        kw1['I2CP.PORT'] = kw['i2cpport']
2998   
2999    self.samSend("SESSION", "CREATE",
3000                 **kw1)
3001    subtopic, args = self.qSession.get()
3002
3003    if args['RESULT'] == 'OK':
3004        return 'OK'
3005    else:
3006        return (args['RESULT'], args['MESSAGE'])
3007</t>
3008<t tx="davidmcnab.041004144551.14">def samDestGenerate(self):
3009    """
3010    Creates a whole new dest and returns an tuple pub, priv as
3011    base64 public and private destination keys
3012    """
3013    self.samSend("DEST", "GENERATE")
3014    pub, priv = self.qNewDests.get()
3015    return pub, priv
3016</t>
3017<t tx="davidmcnab.041004144551.15">def samRawSend(self, peerdest, msg):
3018    """
3019    Sends a raw anon message to another peer
3020   
3021    peerdest is the public base64 destination key of the peer
3022    """
3023    self.samSend("RAW", "SEND", msg,
3024                 DESTINATION=peerdest,
3025                 )
3026</t>
3027<t tx="davidmcnab.041004144551.16">def samRawCheck(self):
3028    """
3029    Returns 1 if there are received raw messages available, 0 if not
3030    """
3031    return not self.qRawMessages.empty()
3032</t>
3033<t tx="davidmcnab.041004144551.17">def samRawReceive(self, blocking=1):
3034    """
3035    Returns the next raw message available,
3036    blocking if none is available and the blocking arg is set to 0
3037
3038    If blocking is 0, and no messages are available, returns None.
3039   
3040    Remember that you can check for availability with
3041    the .samRawCheck() method
3042    """
3043    if not blocking:
3044        if self.qRawMessages.empty():
3045            return None
3046    return self.qRawMessages.get()
3047    </t>
3048<t tx="davidmcnab.041004144551.18">def samDatagramSend(self, peerdest, msg):
3049    """
3050    Sends a repliable datagram message to another peer
3051
3052    peerdest is the public base64 destination key of the peer
3053    """
3054    self.samSend("DATAGRAM", "SEND", msg,
3055                 DESTINATION=peerdest,
3056                 )
3057</t>
3058<t tx="davidmcnab.041004144551.19">def samDatagramCheck(self):
3059    """
3060    Returns 1 if there are datagram messages received messages available, 0 if not
3061    """
3062    return not self.qDatagrams.empty()
3063</t>
3064<t tx="davidmcnab.041004144551.20">def samDatagramReceive(self, blocking=1):
3065    """
3066    Returns the next datagram message available,
3067    blocking if none is available.
3068
3069    If blocking is set to 0, and no messages are available,
3070    returns None.
3071   
3072    Remember that you can check for availability with
3073    the .samRawCheck() method
3074   
3075    Returns 2-tuple: dest, msg
3076    where dest is the base64 destination of the peer from
3077    whom the message was received
3078    """
3079    if not blocking:
3080        if self.qDatagrams.empty():
3081            return None
3082    return self.qDatagrams.get()
3083</t>
3084<t tx="davidmcnab.041004144551.21">def samNamingLookup(self, host):
3085    """
3086    Looks up a host in hosts.txt
3087    """
3088    # try the cache first
3089    if self.namingCache.has_key(host):
3090        log(4, "found host %s in cache" % host)
3091        return self.namingCache[host]
3092
3093    # make a queue for reply
3094    q = self.namingReplies[host] = Queue.Queue()
3095   
3096    # send off req
3097    self.samSend("NAMING", "LOOKUP",
3098                 NAME=host,
3099                 )
3100
3101    # get resp
3102    resp = q.get()
3103
3104    result = resp.get('RESULT', 'none')
3105    if result == 'OK':
3106        log(4, "adding host %s to cache" % host)
3107        val = resp['VALUE']
3108        self.namingCache[host] = val
3109        return val
3110    else:
3111        raise I2PCommandFail("Error looking up '%s': %s %s" % (
3112            host, result, resp.get('MESSAGE', '')))
3113
3114</t>
3115<t tx="davidmcnab.041004144551.22">def samParse(self, flds):
3116    """
3117    carves up a SAM command, returns it as a 3-tuple:
3118        - cmd - command string
3119        - subcmd - subcommand string
3120        - dargs - dict of args
3121    """
3122    cmd = flds[0]
3123    subcmd = flds[1]
3124    args = flds[2:]
3125   
3126    dargs = {}
3127    for arg in args:
3128        try:
3129            name, val = arg.split("=", 1)
3130        except:
3131            logException(3, "failed to process %s in %s" % (repr(arg), repr(flds)))
3132            raise
3133        dargs[name] = val
3134
3135    # read and add data if any
3136    if dargs.has_key('SIZE'):
3137        size = dargs['SIZE'] = int(dargs['SIZE'])
3138        dargs['DATA'] = self._recvbytes(size)
3139
3140    #log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v[:40])) for k,v in dargs.items()]))
3141    log(4, "\n".join([cmd+" "+subcmd] + [("%s=%s (...)" % (k,v)) for k,v in dargs.items()]))
3142
3143    return cmd, subcmd, dargs
3144
3145
3146
3147
3148
3149</t>
3150<t tx="davidmcnab.041004144551.23">def samSend(self, topic, subtopic, data=None, **kw):
3151    """
3152    Sends a SAM message (reply?) back to client
3153   
3154    Arguments:
3155        - topic - the first word in the reply, eg 'STREAM'
3156        - subtopic - the second word of the reply, eg 'CONNECTED'
3157        - data - a string of raw data to send back (optional)
3158    Keywords:
3159        - extra 'name=value' items to pass back.
3160   
3161    Notes:
3162        1. SIZE is not required. If sending back data, it will
3163           be sized and a SIZE arg inserted automatically.
3164        2. a dict of values can be passed to the 'args' keyword, in lieu
3165           of direct keywords. This allows for cases where arg names would
3166           cause python syntax clashes, eg 'tunnels.depthInbound'
3167    """
3168    items = [topic, subtopic]
3169
3170    # stick in SIZE if needed
3171    if data is not None:
3172        kw['SIZE'] = str(len(data))
3173    else:
3174        data = '' # for later
3175
3176    self.samCreateArgsList(kw, items)
3177   
3178    # and whack it together
3179    buf = " ".join(items) + '\n' + data
3180
3181    # and ship it
3182    self.sendLock.acquire()
3183    try:
3184        self._sendbytes(buf)
3185    except:
3186        self.sendLock.release()
3187        raise
3188    self.sendLock.release()
3189
3190</t>
3191<t tx="davidmcnab.041004144551.24">def samCreateArgsList(self, kw1, lst):
3192    for k,v in kw1.items():
3193        if k == 'args':
3194            self.samCreateArgsList(v, lst)
3195        else:
3196            lst.append("=".join([str(k), str(v)]))
3197</t>
3198<t tx="davidmcnab.041004144551.25"></t>
3199<t tx="davidmcnab.041004144551.26">def threadRx(self):
3200    """
3201    Handles all incoming stuff from SAM, storing in
3202    local queues as appropriate
3203    """
3204    while self.isRunning:
3205        try:
3206            log(4, "Awaiting next message from server")
3207            line = self._recvline()
3208            if line == '':
3209                log(3, "I2P server socket closed")
3210                return
3211            flds = line.split(" ")
3212            topic, subtopic, args = self.samParse(flds)
3213            log(4, "Got %s %s %s" % (topic, subtopic, args))
3214            handleMsg = getattr(self, "on_"+topic, None)
3215            if handleMsg:
3216                handleMsg(topic, subtopic, args)
3217            else:
3218                log(2, "No handler for '%s' message" % topic)
3219        except:
3220            #logException(3, "Exception handling %s %s\n%s" % (topic, subtopic, args))
3221            logException(3, "Exception handling %s" % repr(line))
3222</t>
3223<t tx="davidmcnab.041004144551.27">def on_HELLO(self, topic, subtopic, args):
3224    """
3225    Handles HELLO PONG messages from server
3226    """
3227    # just wake up the caller
3228    log(4, "got HELLO")
3229    self.lockHello.release()
3230</t>
3231<t tx="davidmcnab.041004144551.28">def on_SESSION(self, topic, subtopic, args):
3232    """
3233    Handles SESSION messages from server
3234    """
3235    # just stick whatever on the queue and wake up the caller
3236    res = subtopic, args
3237    self.qSession.put(res)
3238</t>
3239<t tx="davidmcnab.041004144551.29">def on_STREAM(self, topic, subtopic, args):
3240    """
3241    Handles STREAM messages from server
3242
3243    STREAM STATUS
3244    RESULT=$result
3245    ID=$id
3246    [MESSAGE=...]
3247
3248    STREAM CONNECTED
3249    DESTINATION=$base64key
3250    ID=$id
3251
3252    STREAM RECEIVED
3253    ID=$id
3254    SIZE=$numBytes\n[$numBytes of data]
3255
3256    STREAM CLOSED
3257    RESULT=$result
3258    ID=$id
3259    [MESSAGE=...]
3260    """
3261    log(4, "got %s %s %s" % (topic, subtopic, args))
3262
3263    # which stream?
3264    id = int(args['ID'])
3265
3266    # result of prior connection attempt
3267    if subtopic == 'STATUS':
3268        # stick it on the queue that the caller is waiting on and let the
3269        # caller interpret the result
3270        self.streamConnectReplies[id].put(args)
3271        return
3272
3273    # notice of incoming connection
3274    if subtopic == 'CONNECTED':
3275
3276        # grab details
3277        dest = args['DESTINATION']
3278
3279        # wrap it in a stream obj
3280        conn = I2PSAMStream(self, id, dest)
3281        self.streams[id] = conn
3282
3283        # and put it there for anyone calling samStreamAccept()
3284        self.qNewStreams.put(conn)
3285
3286        # done
3287        return
3288
3289    # notice of received data
3290    elif subtopic == 'RECEIVED':
3291        # grab details
3292        data = args['DATA']
3293
3294        # lookup the connection
3295        conn = self.streams.get(id, None)
3296        if not conn:
3297            # conn not known, just ditch
3298            log(2, "got data, but don't recall any conn with id %s" % id)
3299            return
3300
3301        # and post the received data
3302        conn._notifyIncomingData(data)
3303
3304        log(4, "wrote data to conn's inbound queue")
3305       
3306        # done
3307        return
3308
3309    elif subtopic == 'CLOSED':
3310        # lookup the connection
3311        conn = self.streams.get(id, None)
3312        if not conn:
3313            # conn not known, just ditch
3314            return
3315
3316        # mark conn as closed and forget it
3317        conn._notifyIncomingData("") # special signal to close
3318        conn.isOpen = 0
3319        del self.streams[id]
3320
3321        # done
3322        return
3323
3324
3325
3326</t>
3327<t tx="davidmcnab.041004144551.30">def on_DATAGRAM(self, topic, subtopic, args):
3328    """
3329    Handles DATAGRAM messages from server
3330    """
3331    remdest = args['DESTINATION']
3332    data = args['DATA']
3333   
3334    self.qDatagrams.put((remdest, data))
3335</t>
3336<t tx="davidmcnab.041004144551.31">def on_RAW(self, topic, subtopic, args):
3337    """
3338    Handles RAW messages from server
3339    """
3340    data = args['DATA']
3341
3342    log(3, "Got anonymous datagram %s" % repr(data))
3343    self.qRawMessages.put(data)
3344</t>
3345<t tx="davidmcnab.041004144551.32">def on_NAMING(self, topic, subtopic, args):
3346    """
3347    Handles NAMING messages from server
3348    """
3349    # just find out hostname, and stick it on resp q
3350    host = args['NAME']
3351    self.namingReplies[host].put(args)
3352</t>
3353<t tx="davidmcnab.041004144551.33">def on_DEST(self, topic, subtopic, args):
3354    """
3355    Handles DEST messages from server
3356    """
3357    pubkey = args['PUB']
3358    privkey = args['PRIV']
3359    res = pubkey, privkey
3360    self.qNewDests.put(res)
3361</t>
3362<t tx="davidmcnab.041004144551.34"></t>
3363<t tx="davidmcnab.041004144551.35">def _recvline(self):
3364    """
3365    Guaranteed read of a full line
3366    """
3367    chars = []
3368    while 1:
3369        c = self.sock.recv(1)
3370        if c in ['', '\n']:
3371            break
3372        chars.append(c)
3373    return "".join(chars)
3374</t>
3375<t tx="davidmcnab.041004144551.36">def _recvbytes(self, num):
3376    """
3377    Guaranteed read of num bytes
3378    """
3379    if num &lt;= 0:
3380        return ""
3381
3382    reqd = num
3383    chunks = []
3384    while reqd &gt; 0:
3385        chunk = self.sock.recv(reqd)
3386        if not chunk:
3387            raise I2PServerFail("Buffer read fail")
3388        chunks.append(chunk)
3389        reqd -= len(chunk)
3390    return "".join(chunks)
3391</t>
3392<t tx="davidmcnab.041004144551.37">def _sendbytes(self, buf):
3393    """
3394    Guaranteed complete send of a buffer
3395    """
3396    reqd = len(buf)
3397    while reqd &gt; 0:
3398        nsent = self.sock.send(buf)
3399        if nsent == 0:
3400            raise I2PServerFail("Send to server failed")
3401        buf = buf[nsent:]
3402        reqd -= nsent
3403</t>
3404<t tx="davidmcnab.041004144551.38">def _sendline(self, line):
3405    """
3406    just tacks on a newline and sends
3407    """
3408    self._sendbytes(line+"\n")
3409</t>
3410<t tx="davidmcnab.041004144551.39">class I2PRemoteSession:
3411    """
3412    DEPRECATED
3413
3414    Wrapper for I2CP connections
3415   
3416    Do not instantiate this directly - it gets created by
3417    I2PSamClient.createSession()
3418    """   
3419    @others
3420</t>
3421<t tx="davidmcnab.041004144551.40">def __init__(self, client, dest):
3422    """
3423    Do not instantiate this directly
3424    """
3425    self.client = client
3426    self.dest = dest
3427</t>
3428<t tx="davidmcnab.041004144551.41">def send(self, peerdest, msg):
3429    """
3430    """
3431    return self.client.send(self.dest, peerdest, msg)
3432</t>
3433<t tx="davidmcnab.041004144551.42">def receive(self):
3434   
3435    return self.client.receive(self.dest)
3436</t>
3437<t tx="davidmcnab.041004144551.43">def destroy(self):
3438   
3439    return self.client.destroySession(self.dest)
3440
3441</t>
3442<t tx="davidmcnab.041004144551.44"></t>
3443<t tx="davidmcnab.041004144551.45">def log(level, msg, nPrev=0):
3444
3445    # ignore messages that are too trivial for chosen verbosity
3446    if level &gt; verbosity:
3447        return
3448
3449    loglock.acquire()
3450    try:
3451        # rip the stack
3452        caller = traceback.extract_stack()[-(2+nPrev)]
3453        path, line, func = caller[:3]
3454        path = os.path.split(path)[1]
3455        full = "%s:%s:%s():\n* %s" % (
3456            path,
3457            line,
3458            func,
3459            msg.replace("\n", "\n   + "))
3460        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
3461        msg = "%s %s\n" % (now, full)
3462   
3463        if logfile == sys.stdout:
3464            print msg
3465        else:
3466            file(logfile, "a").write(msg+"\n")
3467    except:
3468        s = StringIO.StringIO()
3469        traceback.print_exc(file=s)
3470        print s.getvalue()
3471        print "Logger crashed"
3472    loglock.release()</t>
3473<t tx="davidmcnab.041004144551.46">def logException(level, msg=''):
3474    s = StringIO.StringIO()
3475    traceback.print_exc(file=s)
3476    log(level, "%s\n%s" % (s.getvalue(), msg), 1)
3477</t>
3478<t tx="davidmcnab.041004144551.47">def demoNAMING():
3479    """
3480    Demonstrates the NAMING service
3481    """
3482    print "Starting SAM NAMING demo..."
3483    print
3484
3485    print "Instantiating client connection..."
3486    c0 = I2PSamClient()
3487    print "Client connection created"
3488
3489    for host in ['duck.i2p', 'nonexistent.i2p']:
3490        print "Sending query for host '%s'..." % host
3491        try:
3492            res = c0.samNamingLookup(host)
3493            print "query for %s returned:" % host
3494            print repr(res)
3495        except I2PCommandFail, e:
3496            print "got exception: %s" % repr(e.args)
3497   
3498    print
3499    print "---------------------------------"
3500    print "NAMING service tests succeeded"
3501    print "---------------------------------"
3502    print
3503
3504
3505</t>
3506<t tx="davidmcnab.041004144551.48">def demoRAW():
3507    """
3508    Runs a demo of SAM RAW messaging
3509    """
3510    print "Starting SAM RAW demo..."
3511    print
3512
3513    print "Instantiating client connections..."
3514    c1 = I2PSamClient()
3515    c2 = I2PSamClient()
3516
3517    print "Creating dests via SAM"
3518    pub1, priv1 = c1.samDestGenerate()
3519    pub2, priv2 = c2.samDestGenerate()
3520    print "SAM Dests generated ok"
3521   
3522    print "Creating SAM RAW SESSION on connection c1..."
3523    res = c1.samSessionCreate("RAW", priv1)
3524    if res != 'OK':
3525        print "Failed to create session on connection c1: %s" % repr(res)
3526        return
3527    print "Session on connection c1 created successfully"
3528
3529    print "Creating SAM SESSION on connection c2..."
3530    res = c2.samSessionCreate("RAW", priv2)
3531    if res != 'OK':
3532        print "Failed to create session on connection c2: %s" % repr(res)
3533        return
3534    print "Session on connection c2 created successfully"
3535
3536    msg = "Hi there!"
3537    print "sending from c1 to c2: %s" % repr(msg)
3538    c1.samRawSend(pub2, msg)
3539
3540    print "now try to receive from c2 (will block)..."
3541    msg1 = c2.samRawReceive()
3542    print "Connection c2 got %s" % repr(msg1)
3543
3544    print
3545    print "---------------------------------"
3546    print "RAW data transfer tests succeeded"
3547    print "---------------------------------"
3548    print
3549
3550</t>
3551<t tx="davidmcnab.041004144551.49">def demoDATAGRAM():
3552    """
3553    Runs a demo of SAM DATAGRAM messaging
3554    """
3555    print "Starting SAM DATAGRAM demo..."
3556    print
3557
3558    print "Instantiating 2 more client connections..."
3559    c3 = I2PSamClient()
3560    c4 = I2PSamClient()
3561
3562    print "Creating more dests via SAM"
3563    pub3, priv3 = c3.samDestGenerate()
3564    pub4, priv4 = c4.samDestGenerate()
3565
3566    print "Creating SAM DATAGRAM SESSION on connection c3..."
3567    res = c3.samSessionCreate("DATAGRAM", priv3)
3568    if res != 'OK':
3569        print "Failed to create DATAGRAM session on connection c3: %s" % repr(res)
3570        return
3571    print "DATAGRAM Session on connection c3 created successfully"
3572
3573    print "Creating SAM DATAGRAM SESSION on connection c4..."
3574    res = c4.samSessionCreate("DATAGRAM", priv4)
3575    if res != 'OK':
3576        print "Failed to create DATAGRAM session on connection c4: %s" % repr(res)
3577        return
3578    print "Session on connection c4 created successfully"
3579
3580    msg = "Hi there, this is a datagram!"
3581    print "sending from c3 to c4: %s" % repr(msg)
3582    c3.samDatagramSend(pub4, msg)
3583
3584    print "now try to receive from c4 (will block)..."
3585    remdest, msg1 = c4.samDatagramReceive()
3586    print "Connection c4 got %s from %s..." % (repr(msg1), repr(remdest))
3587
3588
3589    print
3590    print "--------------------------------------"
3591    print "DATAGRAM data transfer tests succeeded"
3592    print "--------------------------------------"
3593    print
3594
3595</t>
3596<t tx="davidmcnab.041004144551.50">def demoSTREAM():
3597    """
3598    Runs a demo of SAM STREAM messaging
3599    """
3600    print "Starting SAM STREAM demo..."
3601    print
3602
3603    print "Instantiating client c6..."
3604    c6 = I2PSamClient()
3605
3606    print "Creating dest for c6"
3607    pub6, priv6 = c6.samDestGenerate()
3608
3609    print "Creating SAM STREAM SESSION on connection c6..."
3610    res = c6.samSessionCreate("STREAM", priv6, direction="RECEIVE")
3611    if res != 'OK':
3612        print "Failed to create STREAM session on connection c6: %s" % repr(res)
3613        return
3614    print "STREAM Session on connection c6 created successfully"
3615
3616    print "Launching acceptor thread..."
3617    thread.start_new_thread(demoSTREAM_thread, (c6,))
3618
3619    #print "sleep a while and give the server a chance..."
3620    #time.sleep(10)
3621
3622    print "----------------------------------------"
3623
3624    print "Instantiating client c5..."
3625    c5 = I2PSamClient()
3626
3627    print "Creating dest for c5"
3628    pub5, priv5 = c5.samDestGenerate()
3629
3630    print "Creating SAM STREAM SESSION on connection c5..."
3631    res = c5.samSessionCreate("STREAM", priv5, direction="CREATE")
3632    if res != 'OK':
3633        print "Failed to create STREAM session on connection c5: %s" % repr(res)
3634        return
3635    print "STREAM Session on connection c5 created successfully"
3636
3637    print "----------------------------------------"
3638
3639    print "Making connection from c5 to c6..."
3640
3641    #set_trace()
3642
3643    try:
3644        conn_c5 = c5.samStreamConnect(pub6)
3645    except:
3646        print "Stream Connection failed"
3647        return
3648    print "Stream connect succeeded"
3649   
3650    print "Receiving from c5..."
3651    buf = conn_c5.readline()
3652    print "Got %s" % repr(buf)
3653
3654    #print "Try to accept connection on c6..."
3655    #conn_c6 = c6.sam
3656
3657    print
3658    print "--------------------------------------"
3659    print "DATAGRAM data transfer tests succeeded"
3660    print "--------------------------------------"
3661    print
3662
3663
3664
3665
3666
3667</t>
3668<t tx="davidmcnab.041004144551.51">def demo():
3669    """
3670    This is a simple and straightforward demo of talking to
3671    the i2psam server socket via the I2PSamClient class.
3672   
3673    Read the source, Luke, it's never been so easy...
3674    """
3675    print
3676    print "-----------------------------------------"
3677    print "Running i2psamclient demo..."
3678    print "-----------------------------------------"
3679    print
3680
3681    #demoNAMING()
3682    #demoRAW()
3683    #demoDATAGRAM()
3684    demoSTREAM()
3685
3686    print
3687    print "-----------------------------------------"
3688    print "Demo Finished"
3689    print "-----------------------------------------"
3690
3691    return
3692</t>
3693<t tx="davidmcnab.041004144551.52">if __name__ == '__main__':
3694
3695    demo()
3696</t>
3697<t tx="davidmcnab.041204020513">def samStreamConnect(self, dest):
3698    """
3699    Makes a STREAM connection to a remote dest
3700
3701    STREAM STATUS
3702    RESULT=$result
3703    ID=$id
3704    [MESSAGE=...]
3705    """
3706    # need an ID
3707    id = self.samAllocId()
3708   
3709    # create queue for connect reply
3710    q = self.streamConnectReplies[id] = Queue.Queue()
3711
3712    # send req
3713    self.samSend("STREAM", "CONNECT",
3714                 ID=id,
3715                 DESTINATION=dest,
3716                 )
3717
3718    # await reply - comes back as a dict
3719    resp = q.get()
3720
3721    # ditch queue
3722    del self.streamConnectReplies[id]
3723    del q
3724   
3725    # check out response
3726    result = resp['RESULT']
3727    if result == 'OK':
3728        conn = I2PSAMStream(self, id, dest)
3729        self.streams[id] = conn
3730        return conn
3731    else:
3732        msg = resp.get('MESSAGE', '')
3733        raise I2PCommandFail(result, msg, "STREAM CONNECT")
3734
3735</t>
3736<t tx="davidmcnab.041204042212">def samAllocId(self):
3737    """
3738    Allocates a new unique id as required by SAM protocol
3739    """
3740    self.samNextIdLock.acquire()
3741    id = self.samNextId
3742    self.samNextId += 1
3743    self.samNextIdLock.release()
3744    return id
3745</t>
3746<t tx="davidmcnab.041204042212.1">class I2PSAMStream:
3747    """
3748    Wrapper for a stream object
3749    """
3750    @others
3751</t>
3752<t tx="davidmcnab.041204042212.2">def __init__(self, client, id, dest):
3753    """
3754    """
3755    self.client = client
3756    self.id = id
3757    self.dest = dest
3758
3759    self.qIncomingData = Queue.Queue()
3760
3761    self.inbuf = ''
3762    self.isOpen = 1
3763</t>
3764<t tx="davidmcnab.041204044135">def _notifyIncomingData(self, data):
3765    """
3766    Called by client receiver to notify incoming data
3767    """
3768    log(4, "got %s" % repr(data))
3769    self.qIncomingData.put(data)
3770</t>
3771<t tx="davidmcnab.041204044735">def samStreamSend(self, conn, data):
3772    """
3773    DO NOT CALL THIS DIRECTLY
3774   
3775    Invoked by an I2PSAMStream object to transfer data
3776    Use the object's .send() method instead.
3777   
3778    conn is the I2PSAMStream
3779
3780    STREAM SEND
3781    ID=$id
3782    SIZE=$numBytes\n[$numBytes of data]
3783    """
3784    # dispatch
3785    self.samSend("STREAM", "SEND", data, ID=conn.id)
3786
3787    # useless, but mimics socket paradigm
3788    return len(data)
3789
3790</t>
3791<t tx="davidmcnab.041204044735.1">def send(self, data):
3792    """
3793    Sends data to a stream connection
3794    """
3795    # barf if stream not open
3796    if not self.isOpen:
3797        raise I2PStreamClosed
3798
3799    # can send
3800    return self.client.samStreamSend(self, data)
3801</t>
3802<t tx="davidmcnab.041204050339">def samStreamClose(self, conn):
3803    """
3804    DO NOT CALL DIRECTLY
3805   
3806    Invoked by I2PSAMStream to close stream
3807    Use the object's .send() method instead.
3808   
3809    STREAM CLOSE
3810    ID=$id
3811    """
3812    self.samSend("STREAM", "CLOSE", ID=conn.id)
3813    del self.streams[conn.id]
3814
3815</t>
3816<t tx="davidmcnab.041204050339.1">def recv(self, size):
3817    """
3818    Retrieves n bytes from peer
3819    """
3820    chunks = []
3821
3822    while self.isOpen and size &gt; 0:
3823        # try internal buffer first
3824        if self.inbuf:
3825            chunk = self.inbuf[:size]
3826            chunklen = len(chunk)
3827            self.inbuf = self.inbuf[chunklen:]
3828            chunks.append(chunk)
3829            size -= chunklen
3830        else:
3831            # replenish input buffer
3832            log(4, "I2PSAMStream.recv: replenishing input buffer")
3833            buf = self.qIncomingData.get()
3834            if buf == '':
3835                # connection closed by peer
3836                self.isOpen = 0
3837                break
3838            else:
3839                # got more data
3840                log(4, "I2PSAMStream: queue returned %s" % repr(buf))
3841                self.inbuf += buf
3842
3843    # return whatever we've got, hopefully all
3844    return "".join(chunks)
3845
3846
3847</t>
3848<t tx="davidmcnab.041204050339.2">def close(self):
3849    """
3850    close this stream connection
3851    """
3852    log(4, "closing stream")
3853    self.client.samStreamClose(self)
3854    log(4, "stream closed")
3855    self.isOpen = 0
3856
3857    # and just to make sure...
3858    self.qIncomingData.put("") # busts out of recv() loops
3859
3860</t>
3861<t tx="davidmcnab.041204050511">def __del__(self):
3862    """
3863    Dropping last ref to this object closes stream
3864    """
3865    self.close()
3866</t>
3867<t tx="davidmcnab.041204203651">def demoSTREAM_thread(sess):
3868   
3869    while 1:
3870        sock = sess.samStreamAccept()
3871        log(4, "got incoming connection")
3872
3873        print "**ACCEPTOR SLEEPING 10 secs BEFORE SENDING"
3874       
3875        time.sleep(10)
3876
3877        sock.send("Hi there, what do you want?\n")
3878
3879        print "**ACCEPTOR SLEEPING 5 MINS BEFORE CLOSING"
3880        time.sleep(300)
3881        print "**ACCEPTOR CLOSING STREAM"
3882
3883        sock.close()
3884
3885</t>
3886<t tx="davidmcnab.041204204235">def samStreamAccept(self):
3887    """
3888    Waits for an incoming connection, returning a wrapped conn obj
3889    """
3890    log(4, "waiting for connection")
3891    conn = self.qNewStreams.get()
3892    log(4, "got connection")
3893    return conn
3894</t>
3895<t tx="davidmcnab.041304205426">def threadSocketReceiver(self, sock, id):
3896    """
3897    One of these gets launched each time a new stream connection
3898    is created. Due to the lack of callback mechanism within the
3899    ministreaming API, we have to actively poll for and send back
3900    received data
3901    """
3902    while 1:
3903        #avail = sock.available()
3904        #if avail &lt;= 0:
3905        #    print "threadSocketReceiver: waiting for data on %s (%s avail)..." % (id, avail)
3906        #    time.sleep(5)
3907        #    continue
3908        #log(4, "reading a byte")
3909
3910        try:
3911            buf = sock.recv(1)
3912        except:
3913            logException(4, "Exception reading first byte")
3914       
3915        if buf == '':
3916            log(4, "stream closed")
3917
3918            # notify a close
3919            self.samSend("STREAM", "CLOSED",
3920                         ID=id)
3921            return
3922
3923        # grab more if there's any available
3924        navail = sock.available()
3925        if navail &gt; 0:
3926            #log(4, "%d more bytes available, reading..." % navail)
3927            rest = sock.recv(navail)
3928            buf += rest
3929       
3930        # send if off
3931        log(4, "got from peer: %s" % repr(buf))
3932       
3933        self.samSend("STREAM", "RECEIVED", buf,
3934                     ID=id,
3935                     )
3936
3937
3938
3939
3940</t>
3941<t tx="davidmcnab.041304235615">def readline(self):
3942    """
3943    Read a line of text from stream, return the line without trailing newline
3944   
3945    This method really shouldn't exist in a class that's trying to look a bit
3946    like a socket object, but what the hell!
3947    """
3948    chars = []
3949    while 1:
3950        char = self.recv(1)
3951        if char in ['', '\n']:
3952            break
3953        chars.append(char)
3954    return "".join(chars)
3955</t>
3956</tnodes>
3957</leo_file>
Note: See TracBrowser for help on using the repository browser.