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 }