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 }