Source code: Freenet/RawMessage.java
1 /*
2 *$Id: RawMessage.java,v 1.19 2000/04/07 20:34:09 hobbex Exp $
3
4 This code is part of the Java Adaptive Network Client by Ian Clarke.
5 It is distributed under the GNU General Public Licence (GPL)
6 version 2. See http://www.gnu.org/ for further details of the GPL.
7 */
8
9 /**
10 * This class represents a raw message in the general format
11 * expected by Freenet. It can be created either from
12 * an InputStream or manually created from scratch. It can
13 * then by piped out to an OutputStream. Methods are provided
14 * for manipulation of normal fields, however the type of the
15 * message and the trailing field should be set by direct
16 * manipulation of fields.
17 *
18 * @author <A HREF="mailto:I.Clarke@strs.co.uk">Ian Clarke</A>
19 * @author <A HREF="mailto:blanu@uts.cc.utexas.edu">Brandon Wiley</A>
20 **/
21 package Freenet;
22
23 import Freenet.support.*;
24 import java.io.*;
25 import java.io.EOFException;
26 import java.util.*;
27
28 public class RawMessage
29 {
30 public static final boolean debug = true;
31
32 /**
33 * Test method
34 **/
35 public static void main(String[] args)
36 throws Exception
37 {
38 RawMessage r = new RawMessage(System.in);
39 InputStream i = r.toInputStream();
40 int t=0;
41 while (t != -1)
42 {
43 t = i.read();
44 System.out.write(t);
45 }
46 }
47
48 // Public Fields
49 /** The type of the message **/
50 public String messageType = null;
51 public Long trailingFieldLength;
52 public long trailingFieldLength() {return trailingFieldLength == null ? 0 :trailingFieldLength.longValue();}
53 public String trailingFieldName = null;
54 public InputStream trailingFieldStream = null;
55
56 // Protected/Private Fields
57 protected Hashtable fields = new Hashtable();
58 protected static final char[] crlf = {'\n', '\r'};
59 protected static final char[] endfn = {'=', '\n', '\r'};
60
61 // Constructors
62 public RawMessage(InputStream i) throws InvalidMessageException, EOFException
63 {
64 try
65 {
66 Logger.log("RawMessage.java","Reading message",Logger.DEBUGGING);
67 PushbackInputStream in = new PushbackInputStream(i);
68
69 // Read message type
70 messageType = readTo(in, crlf);
71
72 // Now read field/data pairs
73 while(true)
74 {
75 String name, data;
76 long length=0;
77 char r;
78
79 // Read field name
80 remCRLF(in);
81 if (testEOF(in))
82 break;
83 name = readTo(in, endfn);
84
85 if (testEOF(in))
86 break;
87 // If the next value is a CR then this is a
88 // trailing field
89 if (testEOF(in))
90 break;
91
92 if ((r = (char)in.read()) == '\n' || r == '\r')
93 {
94 if(name.equals("EndMessage")) {
95 break;
96 }
97 // Removing LF after CR
98 r = (char) in.read();
99 if (r != '\n')
100 in.unread ((int)r);
101
102 Logger.log("RawMessage.java","Found trailing field",Logger.MINOR);
103 trailingFieldName = name;
104 String dl=readField("DataLength");
105 if(dl!=null) {
106 trailingFieldLength = new Long(dl);
107 deleteField("DataLength");
108 }
109 trailingFieldStream=in;
110 break;
111 }
112 else
113 { // This is not a trailing field
114 data = readTo(in, crlf);
115 }
116 setField(name, data);
117 }
118
119 }
120 catch (EOFException e)
121 {
122 if (messageType != null) {
123 // messages must end with "EndMessage"
124 Logger.log("RawMessage.java", "Stream died while reading message of type: " + messageType, Logger.ERROR);
125 }
126 else {
127 // stream closed without getting a new message
128 Logger.log("RawMessage.java", "Stream closed", Logger.DEBUGGING);
129 }
130 throw e;
131 }
132 catch (IOException e)
133 {
134 throw new InvalidMessageException("Could not parse message from stream.");
135 }
136 catch(Exception e)
137 {
138 Logger.log("RawMessage.java", "Exception in RawMessage()", Logger.ERROR);
139 e.printStackTrace();
140 }
141 }
142
143 public RawMessage(String name)
144 {
145 messageType = name;
146 }
147
148 // Public Methods
149
150 public boolean isKeepAlive() {
151 return !(readField("KeepAlive") != null && readField("KeepAlive").equalsIgnoreCase("false"));
152 }
153
154 public void deleteField(String name)
155 {
156 fields.remove(name);
157 }
158
159 public String readField(String name)
160 {
161 return (String) fields.get(name);
162 }
163
164 public void setField(String name, String value)
165 {
166 fields.put(name, value);
167 }
168
169 public Enumeration fieldNames() {
170 return fields.keys();
171 }
172
173
174 /**
175 * @depreaciated This method could lock on large messages. Use
176 * writeMessage instead.
177 **/
178 public InputStream toInputStream() throws IOException
179 {
180 PipedOutputStream po=new PipedOutputStream();
181 BufferedInputStream pi=new BufferedInputStream(new PipedInputStream(po));
182 writeMessage(po);
183 po.flush();
184 po.close();
185
186 return (trailingFieldStream != null) ? (InputStream)(new SequenceInputStream(pi, trailingFieldStream)) : (InputStream)pi;
187 }
188
189 public InputStream getTrailing() {
190 return trailingFieldName != null ? trailingFieldStream : null;
191 }
192
193
194 public void writeMessage(OutputStream out) throws IOException
195 {
196 String key, value;
197
198 PrintWriter writer=new PrintWriter(out);
199
200 // Output message type
201 writer.println(messageType);
202
203 // Output non-trailing fields
204 Enumeration iterator=fields.keys();
205 while(iterator.hasMoreElements()) {
206 key=(String)iterator.nextElement();
207 value=(String)fields.get(key);
208 writer.print(key+"="+value+"\n");
209 }
210
211 // Output trailing field
212
213 if (trailingFieldLength!=null)
214 writer.print("DataLength="+trailingFieldLength+"\n");
215 writer.print(trailingFieldName+"\n");
216
217 writer.flush();
218 }
219
220 public void writeTrailing(OutputStream out)
221 {
222 if (trailingFieldStream!=null && out!=null) {
223 Conduit c = new Conduit(trailingFieldStream,out);
224 c.syncFeed();
225 }
226 }
227
228 public String toString()
229 {
230 return messageType + "\n" + fields.toString();
231 }
232
233 // Protected/Private Methods
234
235 /**
236 * Removes any CRLFs from the part of the stream to be
237 * read next
238 **/
239 protected void remCRLF(PushbackInputStream i)
240 throws IOException
241 {
242 char r;
243 do
244 {
245 r = (char) i.read();
246 } while ((r == '\n') || (r == '\r'));
247 i.unread((int) r);
248 }
249
250 /** Checks non-destructively whether we have received an EOF **/
251 protected boolean testEOF(PushbackInputStream i)
252 throws IOException
253 {
254 int c;
255 c = i.read();
256 if (c==-1)
257 return true;
258 else
259 {
260 i.unread(c);
261 return false;
262 }
263 }
264
265 /**
266 * Will return String from InputStream until one of
267 * the characters in tms is about to be read.
268 **/
269 protected String readTo(PushbackInputStream i, char[] tms)
270 throws IOException, EOFException
271 {
272 StringBuffer tmp = new StringBuffer();
273 char r=' ';
274 int ir;
275 while (true)
276 {
277 ir = i.read();
278 if (ir == -1)
279 throw new EOFException();
280 r = (char) ir;
281 if (r == -1)
282 throw new EOFException();
283 boolean b = false;
284 for (int x=0; x<tms.length; x++)
285 {
286 if (tms[x] == r)
287 {
288 b=true;
289 break;
290 }
291 }
292 if (b)
293 break;
294 tmp.append(r);
295 }
296 if (ir != -1)
297 i.unread((int) r);
298 return tmp.toString();
299 }
300
301 }
302
303 /*
304 *$Log: RawMessage.java,v $
305 *Revision 1.19 2000/04/07 20:34:09 hobbex
306 *Fixed building and running with Kaffe (scripts/kbuild.sh). Added canceling of timer callback and sending of requestfailed on loosing MessageMemory (not too well tested). And fixed a bug in the datastore + some exception catching in requestfailed.
307 *
308 *Revision 1.18 2000/04/05 22:14:08 hobbex
309 *Unknown message fields are passed, and source is now optional for clients
310 *
311 *Revision 1.17 2000/03/31 14:36:13 hobbex
312 *Abstracted returning a message, and made replies set KeepAlive=false if the original message did
313 *
314 *Revision 1.16 2000/03/31 12:51:38 thong
315 *Make "stream died" error more intelligible
316 *
317 *Revision 1.15 2000/03/30 11:32:27 hobbex
318 *Close connections after sending KeepAlive=false message
319 *
320 *Revision 1.14 2000/03/29 00:30:45 hal
321 *Check for CR at end of EndMessage line.
322 *
323 *Revision 1.13 2000/03/27 21:02:32 hobbex
324 *Sustained streams
325 *
326 *Revision 1.12 2000/02/29 06:46:56 blanu
327 *Stray debugging println removed.
328 *
329 *Revision 1.11 2000/02/29 06:35:36 blanu
330 *Handshaking is now in a message-like format. It actually uses RawMessage to construct the handshake.
331 *RawMessage now supports the EndMessage field to end a message instead of EOF.
332 *This is useful for handshaking and also for future Keepalive connections.
333 *
334 *Revision 1.10 2000/02/17 14:12:54 hobbex
335 *catch unknown message types
336 *
337 *Revision 1.9 2000/02/17 12:52:23 hobbex
338 *removed runtime exception on bad connection (annoying)
339 *
340 *Revision 1.8 2000/02/12 13:59:57 hobbex
341 *fixed tunneling on inserts for large files. Britney never sounded so good!
342 *
343 *Revision 1.7 2000/02/11 20:12:01 hobbex
344 *Fixed tunneling to use only one thread. Changed Rawmessage to write to output streams rather than return inputstreams. Fixed releasing of DataStream when a message is dropped.
345 *
346 *Revision 1.6 2000/02/09 23:56:39 hobbex
347 *Preliminary tunneling of DataReplies and DataInserts (for now)
348 *
349 *Revision 1.5 2000/01/27 15:45:39 sanity
350 *Some bug fixes, added localAddress field to Node.java which is used by
351 *DataRequest and DataReply messages to set dataSource field. Also removed
352 *untidy debugging stuff from RawMessage.
353 *
354 *Revision 1.4 2000/01/11 06:32:05 blanu
355 *fixed problem with hex uniqueid and name=value field format
356 *
357 *Revision 1.3 2000/01/11 02:02:51 blanu
358 *changed from -: to = field format
359 *
360 *Revision 1.2 2000/01/03 16:32:07 hobbex
361 *Changed various println to Logger - think I got them all
362 *
363 *Revision 1.1.1.1 1999/12/31 11:53:02 sanity
364 *Initial import from OpenProjects
365 *
366 *Revision 1.4 1999/12/29 07:14:06 michael
367 *Additional LF after CR of trailing field - bug fixed
368 *
369 *
370 */