blob: 06740a16a26dc4b62b12fc15ea136a0214836c2b [file] [log] [blame]
Jake Slack03928ae2014-05-13 18:41:56 -07001//
2// ========================================================================
3// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4// ------------------------------------------------------------------------
5// All rights reserved. This program and the accompanying materials
6// are made available under the terms of the Eclipse Public License v1.0
7// and Apache License v2.0 which accompanies this distribution.
8//
9// The Eclipse Public License is available at
10// http://www.eclipse.org/legal/epl-v10.html
11//
12// The Apache License v2.0 is available at
13// http://www.opensource.org/licenses/apache2.0.php
14//
15// You may elect to redistribute this code under either of these licenses.
16// ========================================================================
17//
18
19package org.eclipse.jetty.websocket;
20
21import java.io.IOException;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
24import java.util.Collections;
25import java.util.List;
26
27import org.eclipse.jetty.io.AbstractConnection;
28import org.eclipse.jetty.io.AsyncEndPoint;
29import org.eclipse.jetty.io.Buffer;
30import org.eclipse.jetty.io.ByteArrayBuffer;
31import org.eclipse.jetty.io.Connection;
32import org.eclipse.jetty.io.EndPoint;
33import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
34import org.eclipse.jetty.util.StringUtil;
35import org.eclipse.jetty.util.log.Log;
36import org.eclipse.jetty.util.log.Logger;
37import org.eclipse.jetty.websocket.WebSocket.OnFrame;
38
39public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection
40{
41 private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class);
42
43 public final static byte LENGTH_FRAME=(byte)0x80;
44 public final static byte SENTINEL_FRAME=(byte)0x00;
45
46 private final WebSocketParser _parser;
47 private final WebSocketGenerator _generator;
48 private final WebSocket _websocket;
49 private final String _protocol;
50 private String _key1;
51 private String _key2;
52 private ByteArrayBuffer _hixieBytes;
53
54 public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
55 throws IOException
56 {
57 super(endpoint,timestamp);
58
59 _endp.setMaxIdleTime(maxIdleTime);
60
61 _websocket = websocket;
62 _protocol=protocol;
63
64 _generator = new WebSocketGeneratorD00(buffers, _endp);
65 _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket));
66 }
67
68 /* ------------------------------------------------------------ */
69 public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
70 {
71 return this;
72 }
73
74
75 /* ------------------------------------------------------------ */
76 public void setHixieKeys(String key1,String key2)
77 {
78 _key1=key1;
79 _key2=key2;
80 _hixieBytes=new IndirectNIOBuffer(16);
81 }
82
83 /* ------------------------------------------------------------ */
84 public Connection handle() throws IOException
85 {
86 try
87 {
88 // handle stupid hixie random bytes
89 if (_hixieBytes!=null)
90 {
91
92 // take any available bytes from the parser buffer, which may have already been read
93 Buffer buffer=_parser.getBuffer();
94 if (buffer!=null && buffer.length()>0)
95 {
96 int l=buffer.length();
97 if (l>(8-_hixieBytes.length()))
98 l=8-_hixieBytes.length();
99 _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
100 buffer.skip(l);
101 }
102
103 // while we are not blocked
104 while(_endp.isOpen())
105 {
106 // do we now have enough
107 if (_hixieBytes.length()==8)
108 {
109 // we have the silly random bytes
110 // so let's work out the stupid 16 byte reply.
111 doTheHixieHixieShake();
112 _endp.flush(_hixieBytes);
113 _hixieBytes=null;
114 _endp.flush();
115 break;
116 }
117
118 // no, then let's fill
119 int filled=_endp.fill(_hixieBytes);
120 if (filled<0)
121 {
122 _endp.flush();
123 _endp.close();
124 break;
125 }
126 else if (filled==0)
127 return this;
128 }
129
130 if (_websocket instanceof OnFrame)
131 ((OnFrame)_websocket).onHandshake(this);
132 _websocket.onOpen(this);
133 return this;
134 }
135
136 // handle the framing protocol
137 boolean progress=true;
138
139 while (progress)
140 {
141 int flushed=_generator.flush();
142 int filled=_parser.parseNext();
143
144 progress = flushed>0 || filled>0;
145
146 _endp.flush();
147
148 if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
149 progress=true;
150 }
151 }
152 catch(IOException e)
153 {
154 LOG.debug(e);
155 try
156 {
157 if (_endp.isOpen())
158 _endp.close();
159 }
160 catch(IOException e2)
161 {
162 LOG.ignore(e2);
163 }
164 throw e;
165 }
166 finally
167 {
168 if (_endp.isOpen())
169 {
170 if (_endp.isInputShutdown() && _generator.isBufferEmpty())
171 _endp.close();
172 else
173 checkWriteable();
174
175 checkWriteable();
176 }
177 }
178 return this;
179 }
180
181 /* ------------------------------------------------------------ */
182 public void onInputShutdown() throws IOException
183 {
184 // TODO
185 }
186
187 /* ------------------------------------------------------------ */
188 private void doTheHixieHixieShake()
189 {
190 byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
191 WebSocketConnectionD00.hixieCrypt(_key1),
192 WebSocketConnectionD00.hixieCrypt(_key2),
193 _hixieBytes.asArray());
194 _hixieBytes.clear();
195 _hixieBytes.put(result);
196 }
197
198 /* ------------------------------------------------------------ */
199 public boolean isOpen()
200 {
201 return _endp!=null&&_endp.isOpen();
202 }
203
204 /* ------------------------------------------------------------ */
205 public boolean isIdle()
206 {
207 return _parser.isBufferEmpty() && _generator.isBufferEmpty();
208 }
209
210 /* ------------------------------------------------------------ */
211 public boolean isSuspended()
212 {
213 return false;
214 }
215
216 /* ------------------------------------------------------------ */
217 public void onClose()
218 {
219 _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
220 }
221
222 /* ------------------------------------------------------------ */
223 /**
224 */
225 public void sendMessage(String content) throws IOException
226 {
227 byte[] data = content.getBytes(StringUtil.__UTF8);
228 _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
229 _generator.flush();
230 checkWriteable();
231 }
232
233 /* ------------------------------------------------------------ */
234 public void sendMessage(byte[] data, int offset, int length) throws IOException
235 {
236 _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
237 _generator.flush();
238 checkWriteable();
239 }
240
241 /* ------------------------------------------------------------ */
242 public boolean isMore(byte flags)
243 {
244 return (flags&0x8) != 0;
245 }
246
247 /* ------------------------------------------------------------ */
248 /**
249 * {@inheritDoc}
250 */
251 public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
252 {
253 }
254
255 /* ------------------------------------------------------------ */
256 public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
257 {
258 _generator.addFrame((byte)0,opcode,content,offset,length);
259 _generator.flush();
260 checkWriteable();
261 }
262
263 /* ------------------------------------------------------------ */
264 public void close(int code, String message)
265 {
266 throw new UnsupportedOperationException();
267 }
268
269 /* ------------------------------------------------------------ */
270 public void disconnect()
271 {
272 close();
273 }
274
275 /* ------------------------------------------------------------ */
276 public void close()
277 {
278 try
279 {
280 _generator.flush();
281 _endp.close();
282 }
283 catch(IOException e)
284 {
285 LOG.ignore(e);
286 }
287 }
288
289 public void shutdown()
290 {
291 close();
292 }
293
294 /* ------------------------------------------------------------ */
295 public void fillBuffersFrom(Buffer buffer)
296 {
297 _parser.fill(buffer);
298 }
299
300
301 /* ------------------------------------------------------------ */
302 private void checkWriteable()
303 {
304 if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
305 ((AsyncEndPoint)_endp).scheduleWrite();
306 }
307
308 /* ------------------------------------------------------------ */
309 static long hixieCrypt(String key)
310 {
311 // Don't ask me what all this is about.
312 // I think it's pretend secret stuff, kind of
313 // like talking in pig latin!
314 long number=0;
315 int spaces=0;
316 for (char c : key.toCharArray())
317 {
318 if (Character.isDigit(c))
319 number=number*10+(c-'0');
320 else if (c==' ')
321 spaces++;
322 }
323 return number/spaces;
324 }
325
326 public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
327 {
328 try
329 {
330 MessageDigest md = MessageDigest.getInstance("MD5");
331 byte [] fodder = new byte[16];
332
333 fodder[0]=(byte)(0xff&(key1>>24));
334 fodder[1]=(byte)(0xff&(key1>>16));
335 fodder[2]=(byte)(0xff&(key1>>8));
336 fodder[3]=(byte)(0xff&key1);
337 fodder[4]=(byte)(0xff&(key2>>24));
338 fodder[5]=(byte)(0xff&(key2>>16));
339 fodder[6]=(byte)(0xff&(key2>>8));
340 fodder[7]=(byte)(0xff&key2);
341 System.arraycopy(key3, 0, fodder, 8, 8);
342 md.update(fodder);
343 return md.digest();
344 }
345 catch (NoSuchAlgorithmException e)
346 {
347 throw new IllegalStateException(e);
348 }
349 }
350
351 public void setMaxTextMessageSize(int size)
352 {
353 }
354
355 public void setMaxIdleTime(int ms)
356 {
357 try
358 {
359 _endp.setMaxIdleTime(ms);
360 }
361 catch(IOException e)
362 {
363 LOG.warn(e);
364 }
365 }
366
367 public void setMaxBinaryMessageSize(int size)
368 {
369 }
370
371 public int getMaxTextMessageSize()
372 {
373 return -1;
374 }
375
376 public int getMaxIdleTime()
377 {
378 return _endp.getMaxIdleTime();
379 }
380
381 public int getMaxBinaryMessageSize()
382 {
383 return -1;
384 }
385
386 public String getProtocol()
387 {
388 return _protocol;
389 }
390
391 protected void onFrameHandshake()
392 {
393 if (_websocket instanceof OnFrame)
394 {
395 ((OnFrame)_websocket).onHandshake(this);
396 }
397 }
398
399 protected void onWebsocketOpen()
400 {
401 _websocket.onOpen(this);
402 }
403
404 static class FrameHandlerD00 implements WebSocketParser.FrameHandler
405 {
406 final WebSocket _websocket;
407
408 FrameHandlerD00(WebSocket websocket)
409 {
410 _websocket=websocket;
411 }
412
413 public void onFrame(byte flags, byte opcode, Buffer buffer)
414 {
415 try
416 {
417 byte[] array=buffer.array();
418
419 if (opcode==0)
420 {
421 if (_websocket instanceof WebSocket.OnTextMessage)
422 ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
423 }
424 else
425 {
426 if (_websocket instanceof WebSocket.OnBinaryMessage)
427 ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
428 }
429 }
430 catch(Throwable th)
431 {
432 LOG.warn(th);
433 }
434 }
435
436 public void close(int code,String message)
437 {
438 }
439 }
440
441 public boolean isMessageComplete(byte flags)
442 {
443 return true;
444 }
445
446 public byte binaryOpcode()
447 {
448 return LENGTH_FRAME;
449 }
450
451 public byte textOpcode()
452 {
453 return SENTINEL_FRAME;
454 }
455
456 public boolean isControl(byte opcode)
457 {
458 return false;
459 }
460
461 public boolean isText(byte opcode)
462 {
463 return (opcode&LENGTH_FRAME)==0;
464 }
465
466 public boolean isBinary(byte opcode)
467 {
468 return (opcode&LENGTH_FRAME)!=0;
469 }
470
471 public boolean isContinuation(byte opcode)
472 {
473 return false;
474 }
475
476 public boolean isClose(byte opcode)
477 {
478 return false;
479 }
480
481 public boolean isPing(byte opcode)
482 {
483 return false;
484 }
485
486 public boolean isPong(byte opcode)
487 {
488 return false;
489 }
490
491 public List<Extension> getExtensions()
492 {
493 return Collections.emptyList();
494 }
495
496 public byte continuationOpcode()
497 {
498 return 0;
499 }
500
501 public byte finMask()
502 {
503 return 0;
504 }
505
506 public void setAllowFrameFragmentation(boolean allowFragmentation)
507 {
508 }
509
510 public boolean isAllowFrameFragmentation()
511 {
512 return false;
513 }
514}