1// 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} 515