54 * with mutability, security, masking/unmasking, readonly status, etc. So 55 * copying greatly simplifies the implementation. 56 * 57 * In the case of memory-sensitive environments an alternative implementation 58 * could use an internal pool of buffers though at the cost of extra complexity 59 * and possible performance degradation. 60 */ 61 abstract class OutgoingMessage { 62 63 // Share per WebSocket? 64 private static final SecureRandom maskingKeys = new SecureRandom(); 65 66 protected ByteBuffer[] frame; 67 protected int offset; 68 69 /* 70 * Performs contextualization. This method is not a part of the constructor 71 * so it would be possible to defer the work it does until the most 72 * convenient moment (up to the point where sentTo is invoked). 73 */ 74 protected void contextualize(Context context) { 75 // masking and charset decoding should be performed here rather than in 76 // the constructor (as of today) 77 if (context.isCloseSent()) { 78 throw new IllegalStateException("Close sent"); 79 } 80 } 81 82 protected boolean sendTo(RawChannel channel) throws IOException { 83 while ((offset = nextUnwrittenIndex()) != -1) { 84 long n = channel.write(frame, offset, frame.length - offset); 85 if (n == 0) { 86 return false; 87 } 88 } 89 return true; 90 } 91 92 private int nextUnwrittenIndex() { 93 for (int i = offset; i < frame.length; i++) { 94 if (frame[i].hasRemaining()) { 95 return i; 96 } 97 } 98 return -1; 99 } 100 101 static final class Text extends OutgoingMessage { 102 103 private final ByteBuffer payload; 104 private final boolean isLast; 105 106 Text(CharSequence characters, boolean isLast) { 107 CharsetEncoder encoder = UTF_8.newEncoder(); // Share per WebSocket? 108 try { 109 payload = encoder.encode(CharBuffer.wrap(characters)); 110 } catch (CharacterCodingException e) { 111 throw new IllegalArgumentException( 112 "Malformed UTF-8 text message"); 113 } 114 this.isLast = isLast; 115 } 116 117 @Override 118 protected void contextualize(Context context) { 119 super.contextualize(context); 120 if (context.isPreviousBinary() && !context.isPreviousLast()) { 121 throw new IllegalStateException("Unexpected text message"); 122 } 123 frame = getDataMessageBuffers( 124 TEXT, context.isPreviousLast(), isLast, payload, payload); 125 context.setPreviousBinary(false); 126 context.setPreviousText(true); 127 context.setPreviousLast(isLast); 128 } 129 } 130 131 static final class Binary extends OutgoingMessage { 132 133 private final ByteBuffer payload; 134 private final boolean isLast; 135 136 Binary(ByteBuffer payload, boolean isLast) { 137 this.payload = requireNonNull(payload); 138 this.isLast = isLast; 139 } 140 141 @Override 142 protected void contextualize(Context context) { 143 super.contextualize(context); 144 if (context.isPreviousText() && !context.isPreviousLast()) { 145 throw new IllegalStateException("Unexpected binary message"); 146 } 147 ByteBuffer newBuffer = ByteBuffer.allocate(payload.remaining()); 148 frame = getDataMessageBuffers( 149 BINARY, context.isPreviousLast(), isLast, payload, newBuffer); 150 context.setPreviousText(false); 151 context.setPreviousBinary(true); 152 context.setPreviousLast(isLast); 153 } 154 } 155 156 static final class Ping extends OutgoingMessage { 157 158 Ping(ByteBuffer payload) { 159 frame = getControlMessageBuffers(PING, payload); 160 } 161 } 162 163 static final class Pong extends OutgoingMessage { 164 165 Pong(ByteBuffer payload) { 166 frame = getControlMessageBuffers(PONG, payload); 167 } 168 } 169 170 static final class Close extends OutgoingMessage { 171 172 Close() { 178 .putChar((char) statusCode); 179 CoderResult result = UTF_8.newEncoder() 180 .encode(CharBuffer.wrap(reason), 181 payload, 182 true); 183 if (result.isOverflow()) { 184 throw new IllegalArgumentException("Long reason"); 185 } else if (result.isError()) { 186 try { 187 result.throwException(); 188 } catch (CharacterCodingException e) { 189 throw new IllegalArgumentException( 190 "Malformed UTF-8 reason", e); 191 } 192 } 193 payload.flip(); 194 frame = getControlMessageBuffers(CLOSE, payload); 195 } 196 197 @Override 198 protected void contextualize(Context context) { 199 super.contextualize(context); 200 context.setCloseSent(); 201 } 202 } 203 204 private static ByteBuffer[] getControlMessageBuffers(Opcode opcode, 205 ByteBuffer payload) { 206 assert opcode.isControl() : opcode; 207 int remaining = payload.remaining(); 208 if (remaining > 125) { 209 throw new IllegalArgumentException 210 ("Long message: " + remaining); 211 } 212 ByteBuffer frame = ByteBuffer.allocate(MAX_HEADER_SIZE_BYTES + remaining); 213 int mask = maskingKeys.nextInt(); 214 new Frame.HeaderWriter() 215 .fin(true) 216 .opcode(opcode) 217 .payloadLen(remaining) 218 .mask(mask) 219 .write(frame); 220 Frame.Masker.transferMasking(payload, frame, mask); | 54 * with mutability, security, masking/unmasking, readonly status, etc. So 55 * copying greatly simplifies the implementation. 56 * 57 * In the case of memory-sensitive environments an alternative implementation 58 * could use an internal pool of buffers though at the cost of extra complexity 59 * and possible performance degradation. 60 */ 61 abstract class OutgoingMessage { 62 63 // Share per WebSocket? 64 private static final SecureRandom maskingKeys = new SecureRandom(); 65 66 protected ByteBuffer[] frame; 67 protected int offset; 68 69 /* 70 * Performs contextualization. This method is not a part of the constructor 71 * so it would be possible to defer the work it does until the most 72 * convenient moment (up to the point where sentTo is invoked). 73 */ 74 protected boolean contextualize(Context context) { 75 // masking and charset decoding should be performed here rather than in 76 // the constructor (as of today) 77 if (context.isCloseSent()) { 78 throw new IllegalStateException("Close sent"); 79 } 80 return true; 81 } 82 83 protected boolean sendTo(RawChannel channel) throws IOException { 84 while ((offset = nextUnwrittenIndex()) != -1) { 85 long n = channel.write(frame, offset, frame.length - offset); 86 if (n == 0) { 87 return false; 88 } 89 } 90 return true; 91 } 92 93 private int nextUnwrittenIndex() { 94 for (int i = offset; i < frame.length; i++) { 95 if (frame[i].hasRemaining()) { 96 return i; 97 } 98 } 99 return -1; 100 } 101 102 static final class Text extends OutgoingMessage { 103 104 private final ByteBuffer payload; 105 private final boolean isLast; 106 107 Text(CharSequence characters, boolean isLast) { 108 CharsetEncoder encoder = UTF_8.newEncoder(); // Share per WebSocket? 109 try { 110 payload = encoder.encode(CharBuffer.wrap(characters)); 111 } catch (CharacterCodingException e) { 112 throw new IllegalArgumentException( 113 "Malformed UTF-8 text message"); 114 } 115 this.isLast = isLast; 116 } 117 118 @Override 119 protected boolean contextualize(Context context) { 120 super.contextualize(context); 121 if (context.isPreviousBinary() && !context.isPreviousLast()) { 122 throw new IllegalStateException("Unexpected text message"); 123 } 124 frame = getDataMessageBuffers( 125 TEXT, context.isPreviousLast(), isLast, payload, payload); 126 context.setPreviousBinary(false); 127 context.setPreviousText(true); 128 context.setPreviousLast(isLast); 129 return true; 130 } 131 } 132 133 static final class Binary extends OutgoingMessage { 134 135 private final ByteBuffer payload; 136 private final boolean isLast; 137 138 Binary(ByteBuffer payload, boolean isLast) { 139 this.payload = requireNonNull(payload); 140 this.isLast = isLast; 141 } 142 143 @Override 144 protected boolean contextualize(Context context) { 145 super.contextualize(context); 146 if (context.isPreviousText() && !context.isPreviousLast()) { 147 throw new IllegalStateException("Unexpected binary message"); 148 } 149 ByteBuffer newBuffer = ByteBuffer.allocate(payload.remaining()); 150 frame = getDataMessageBuffers( 151 BINARY, context.isPreviousLast(), isLast, payload, newBuffer); 152 context.setPreviousText(false); 153 context.setPreviousBinary(true); 154 context.setPreviousLast(isLast); 155 return true; 156 } 157 } 158 159 static final class Ping extends OutgoingMessage { 160 161 Ping(ByteBuffer payload) { 162 frame = getControlMessageBuffers(PING, payload); 163 } 164 } 165 166 static final class Pong extends OutgoingMessage { 167 168 Pong(ByteBuffer payload) { 169 frame = getControlMessageBuffers(PONG, payload); 170 } 171 } 172 173 static final class Close extends OutgoingMessage { 174 175 Close() { 181 .putChar((char) statusCode); 182 CoderResult result = UTF_8.newEncoder() 183 .encode(CharBuffer.wrap(reason), 184 payload, 185 true); 186 if (result.isOverflow()) { 187 throw new IllegalArgumentException("Long reason"); 188 } else if (result.isError()) { 189 try { 190 result.throwException(); 191 } catch (CharacterCodingException e) { 192 throw new IllegalArgumentException( 193 "Malformed UTF-8 reason", e); 194 } 195 } 196 payload.flip(); 197 frame = getControlMessageBuffers(CLOSE, payload); 198 } 199 200 @Override 201 protected boolean contextualize(Context context) { 202 if (context.isCloseSent()) { 203 return false; 204 } else { 205 context.setCloseSent(); 206 return true; 207 } 208 } 209 } 210 211 private static ByteBuffer[] getControlMessageBuffers(Opcode opcode, 212 ByteBuffer payload) { 213 assert opcode.isControl() : opcode; 214 int remaining = payload.remaining(); 215 if (remaining > 125) { 216 throw new IllegalArgumentException 217 ("Long message: " + remaining); 218 } 219 ByteBuffer frame = ByteBuffer.allocate(MAX_HEADER_SIZE_BYTES + remaining); 220 int mask = maskingKeys.nextInt(); 221 new Frame.HeaderWriter() 222 .fin(true) 223 .opcode(opcode) 224 .payloadLen(remaining) 225 .mask(mask) 226 .write(frame); 227 Frame.Masker.transferMasking(payload, frame, mask); |