1 /* 2 * Copyright (c) 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 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 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 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 package java.net.http; 26 27 import java.net.http.WSFrame.HeaderBuilder; 28 import java.net.http.WSFrame.Masker; 29 import java.net.http.WSOutgoingMessage.Binary; 30 import java.net.http.WSOutgoingMessage.Close; 31 import java.net.http.WSOutgoingMessage.Ping; 32 import java.net.http.WSOutgoingMessage.Pong; 33 import java.net.http.WSOutgoingMessage.Text; 34 import java.net.http.WSOutgoingMessage.Visitor; 35 import java.nio.ByteBuffer; 36 import java.nio.CharBuffer; 37 import java.nio.charset.CharacterCodingException; 38 import java.security.SecureRandom; 39 import java.util.function.Consumer; 40 41 import static java.net.http.WSFrame.MAX_HEADER_SIZE_BYTES; 42 import static java.net.http.WSFrame.Opcode.BINARY; 43 import static java.net.http.WSFrame.Opcode.CLOSE; 44 import static java.net.http.WSFrame.Opcode.CONTINUATION; 45 import static java.net.http.WSFrame.Opcode.PING; 46 import static java.net.http.WSFrame.Opcode.PONG; 47 import static java.net.http.WSFrame.Opcode.TEXT; 48 import static java.util.Objects.requireNonNull; 49 50 /* 51 * A Sender of outgoing messages. Given a message, 52 * 53 * 1) constructs the frame 54 * 2) initiates the channel write 55 * 3) notifies when the message has been sent 56 */ 57 final class WSMessageSender { 58 59 private final Visitor frameBuilderVisitor; 60 private final Consumer<Throwable> completionEventConsumer; 61 private final WSWriter writer; 62 private final ByteBuffer[] buffers = new ByteBuffer[2]; 63 64 WSMessageSender(RawChannel channel, Consumer<Throwable> completionEventConsumer) { 65 // Single reusable buffer that holds a header 66 this.buffers[0] = ByteBuffer.allocateDirect(MAX_HEADER_SIZE_BYTES); 67 this.frameBuilderVisitor = new FrameBuilderVisitor(); 68 this.completionEventConsumer = completionEventConsumer; 69 this.writer = new WSWriter(channel, this.completionEventConsumer); 70 } 71 72 /* 73 * Tries to send the given message fully. Invoked once per message. 74 */ 75 boolean trySendFully(WSOutgoingMessage m) { 76 requireNonNull(m); 77 synchronized (this) { 78 try { 79 return sendNow(m); 80 } catch (Exception e) { 81 completionEventConsumer.accept(e); 82 return false; 83 } 84 } 85 } 86 87 private boolean sendNow(WSOutgoingMessage m) { 88 buffers[0].clear(); 89 m.accept(frameBuilderVisitor); 90 buffers[0].flip(); 91 return writer.tryWriteFully(buffers); 92 } 93 94 /* 95 * Builds and initiates a write of a frame, from a given message. 96 */ 97 class FrameBuilderVisitor implements Visitor { 98 99 private final SecureRandom random = new SecureRandom(); 100 private final WSCharsetToolkit.Encoder encoder = new WSCharsetToolkit.Encoder(); 101 private final Masker masker = new Masker(); 102 private final HeaderBuilder headerBuilder = new HeaderBuilder(); 103 private boolean previousIsLast = true; 104 105 @Override 106 public void visit(Text message) { 107 try { 108 buffers[1] = encoder.encode(CharBuffer.wrap(message.characters)); 109 } catch (CharacterCodingException e) { 110 completionEventConsumer.accept(e); 111 return; 112 } 113 int mask = random.nextInt(); 114 maskAndRewind(buffers[1], mask); 115 headerBuilder 116 .fin(message.isLast) 117 .opcode(previousIsLast ? TEXT : CONTINUATION) 118 .payloadLen(buffers[1].remaining()) 119 .mask(mask) 120 .build(buffers[0]); 121 previousIsLast = message.isLast; 122 } 123 124 @Override 125 public void visit(Binary message) { 126 buffers[1] = message.bytes; 127 int mask = random.nextInt(); 128 maskAndRewind(buffers[1], mask); 129 headerBuilder 130 .fin(message.isLast) 131 .opcode(previousIsLast ? BINARY : CONTINUATION) 132 .payloadLen(message.bytes.remaining()) 133 .mask(mask) 134 .build(buffers[0]); 135 previousIsLast = message.isLast; 136 } 137 138 @Override 139 public void visit(Ping message) { 140 buffers[1] = message.bytes; 141 int mask = random.nextInt(); 142 maskAndRewind(buffers[1], mask); 143 headerBuilder 144 .fin(true) 145 .opcode(PING) 146 .payloadLen(message.bytes.remaining()) 147 .mask(mask) 148 .build(buffers[0]); 149 } 150 151 @Override 152 public void visit(Pong message) { 153 buffers[1] = message.bytes; 154 int mask = random.nextInt(); 155 maskAndRewind(buffers[1], mask); 156 headerBuilder 157 .fin(true) 158 .opcode(PONG) 159 .payloadLen(message.bytes.remaining()) 160 .mask(mask) 161 .build(buffers[0]); 162 } 163 164 @Override 165 public void visit(Close message) { 166 buffers[1] = message.bytes; 167 int mask = random.nextInt(); 168 maskAndRewind(buffers[1], mask); 169 headerBuilder 170 .fin(true) 171 .opcode(CLOSE) 172 .payloadLen(buffers[1].remaining()) 173 .mask(mask) 174 .build(buffers[0]); 175 } 176 177 private void maskAndRewind(ByteBuffer b, int mask) { 178 int oldPos = b.position(); 179 masker.mask(mask).applyMask(b, b); 180 b.position(oldPos); 181 } 182 } 183 }