1 /* 2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.net.http; 27 28 import java.net.http.WSFrame.HeaderBuilder; 29 import java.net.http.WSFrame.Masker; 30 import java.net.http.WSOutgoingMessage.Binary; 31 import java.net.http.WSOutgoingMessage.Close; 32 import java.net.http.WSOutgoingMessage.Ping; 33 import java.net.http.WSOutgoingMessage.Pong; 34 import java.net.http.WSOutgoingMessage.Text; 35 import java.net.http.WSOutgoingMessage.Visitor; 36 import java.nio.ByteBuffer; 37 import java.nio.CharBuffer; 38 import java.nio.charset.CharacterCodingException; 39 import java.security.SecureRandom; 40 import java.util.function.Consumer; 41 42 import static java.net.http.WSFrame.MAX_HEADER_SIZE_BYTES; 43 import static java.net.http.WSFrame.Opcode.BINARY; 44 import static java.net.http.WSFrame.Opcode.CLOSE; 45 import static java.net.http.WSFrame.Opcode.CONTINUATION; 46 import static java.net.http.WSFrame.Opcode.PING; 47 import static java.net.http.WSFrame.Opcode.PONG; 48 import static java.net.http.WSFrame.Opcode.TEXT; 49 import static java.util.Objects.requireNonNull; 50 51 /* 52 * A Sender of outgoing messages. Given a message, 53 * 54 * 1) constructs the frame 55 * 2) initiates the channel write 56 * 3) notifies when the message has been sent 57 */ 58 final class WSMessageSender { 59 60 private final Visitor frameBuilderVisitor; 61 private final Consumer<Throwable> completionEventConsumer; 62 private final WSWriter writer; 63 private final ByteBuffer[] buffers = new ByteBuffer[2]; 64 65 WSMessageSender(RawChannel channel, Consumer<Throwable> completionEventConsumer) { 66 // Single reusable buffer that holds a header 67 this.buffers[0] = ByteBuffer.allocateDirect(MAX_HEADER_SIZE_BYTES); 68 this.frameBuilderVisitor = new FrameBuilderVisitor(); 69 this.completionEventConsumer = completionEventConsumer; 70 this.writer = new WSWriter(channel, this.completionEventConsumer); 71 } 72 73 /* 74 * Tries to send the given message fully. Invoked once per message. 75 */ 76 boolean trySendFully(WSOutgoingMessage m) { 77 requireNonNull(m); 78 synchronized (this) { 79 try { 80 return sendNow(m); 81 } catch (Exception e) { 82 completionEventConsumer.accept(e); 83 return false; 84 } 85 } 86 } 87 88 private boolean sendNow(WSOutgoingMessage m) { 89 buffers[0].clear(); 90 m.accept(frameBuilderVisitor); 91 buffers[0].flip(); 92 return writer.tryWriteFully(buffers); 93 } 94 95 /* 96 * Builds and initiates a write of a frame, from a given message. 97 */ 98 class FrameBuilderVisitor implements Visitor { 99 100 private final SecureRandom random = new SecureRandom(); 101 private final WSCharsetToolkit.Encoder encoder = new WSCharsetToolkit.Encoder(); 102 private final Masker masker = new Masker(); 103 private final HeaderBuilder headerBuilder = new HeaderBuilder(); 104 private boolean previousIsLast = true; 105 106 @Override 107 public void visit(Text message) { 108 try { 109 buffers[1] = encoder.encode(CharBuffer.wrap(message.characters)); 110 } catch (CharacterCodingException e) { 111 completionEventConsumer.accept(e); 112 return; 113 } 114 int mask = random.nextInt(); 115 maskAndRewind(buffers[1], mask); 116 headerBuilder 117 .fin(message.isLast) 118 .opcode(previousIsLast ? TEXT : CONTINUATION) 119 .payloadLen(buffers[1].remaining()) 120 .mask(mask) 121 .build(buffers[0]); 122 previousIsLast = message.isLast; 123 } 124 125 @Override 126 public void visit(Binary message) { 127 buffers[1] = message.bytes; 128 int mask = random.nextInt(); 129 maskAndRewind(buffers[1], mask); 130 headerBuilder 131 .fin(message.isLast) 132 .opcode(previousIsLast ? BINARY : CONTINUATION) 133 .payloadLen(message.bytes.remaining()) 134 .mask(mask) 135 .build(buffers[0]); 136 previousIsLast = message.isLast; 137 } 138 139 @Override 140 public void visit(Ping message) { 141 buffers[1] = message.bytes; 142 int mask = random.nextInt(); 143 maskAndRewind(buffers[1], mask); 144 headerBuilder 145 .fin(true) 146 .opcode(PING) 147 .payloadLen(message.bytes.remaining()) 148 .mask(mask) 149 .build(buffers[0]); 150 } 151 152 @Override 153 public void visit(Pong message) { 154 buffers[1] = message.bytes; 155 int mask = random.nextInt(); 156 maskAndRewind(buffers[1], mask); 157 headerBuilder 158 .fin(true) 159 .opcode(PONG) 160 .payloadLen(message.bytes.remaining()) 161 .mask(mask) 162 .build(buffers[0]); 163 } 164 165 @Override 166 public void visit(Close message) { 167 buffers[1] = message.bytes; 168 int mask = random.nextInt(); 169 maskAndRewind(buffers[1], mask); 170 headerBuilder 171 .fin(true) 172 .opcode(CLOSE) 173 .payloadLen(buffers[1].remaining()) 174 .mask(mask) 175 .build(buffers[0]); 176 } 177 178 private void maskAndRewind(ByteBuffer b, int mask) { 179 int oldPos = b.position(); 180 masker.mask(mask).applyMask(b, b); 181 b.position(oldPos); 182 } 183 } 184 }