Merge pull request #1048 from dota17/UpdatePMDE · java-codehunger/Java-WebSocket@e38d9a9 · GitHub
Skip to content

Commit e38d9a9

Browse files
authored
Merge pull request TooTallNate#1048 from dota17/UpdatePMDE
2 parents b7e2c94 + 7022fca commit e38d9a9

4 files changed

Lines changed: 173 additions & 32 deletions

File tree

README.markdown

Lines changed: 2 additions & 0 deletions

src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java

Lines changed: 101 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
import org.java_websocket.extensions.CompressionExtension;
77
import org.java_websocket.extensions.ExtensionRequestData;
88
import org.java_websocket.extensions.IExtension;
9-
import org.java_websocket.framing.*;
9+
import org.java_websocket.framing.BinaryFrame;
10+
import org.java_websocket.framing.CloseFrame;
11+
import org.java_websocket.framing.ContinuousFrame;
12+
import org.java_websocket.framing.DataFrame;
13+
import org.java_websocket.framing.Framedata;
14+
import org.java_websocket.framing.FramedataImpl1;
15+
import org.java_websocket.framing.TextFrame;
1016

1117
import java.io.ByteArrayOutputStream;
1218
import java.nio.ByteBuffer;
@@ -16,6 +22,12 @@
1622
import java.util.zip.Deflater;
1723
import java.util.zip.Inflater;
1824

25+
/**
26+
* PerMessage Deflate Extension (<a href="https://tools.ietf.org/html/rfc7692#section-7">7&#46; The "permessage-deflate" Extension</a> in
27+
* <a href="https://tools.ietf.org/html/rfc7692">RFC 7692</a>).
28+
*
29+
* @see <a href="https://tools.ietf.org/html/rfc7692#section-7">7&#46; The "permessage-deflate" Extension in RFC 7692</a>
30+
*/
1931
public class PerMessageDeflateExtension extends CompressionExtension {
2032

2133
// Name of the extension as registered by IETF https://tools.ietf.org/html/rfc7692#section-9.
@@ -28,7 +40,7 @@ public class PerMessageDeflateExtension extends CompressionExtension {
2840
private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits";
2941
private static final int serverMaxWindowBits = 1 << 15;
3042
private static final int clientMaxWindowBits = 1 << 15;
31-
private static final byte[] TAIL_BYTES = {0x00, 0x00, (byte)0xFF, (byte)0xFF};
43+
private static final byte[] TAIL_BYTES = { (byte)0x00, (byte)0x00, (byte)0xFF, (byte)0xFF };
3244
private static final int BUFFER_SIZE = 1 << 10;
3345

3446
private boolean serverNoContextTakeover = true;
@@ -37,9 +49,60 @@ public class PerMessageDeflateExtension extends CompressionExtension {
3749
// For WebSocketServers, this variable holds the extension parameters that the peer client has requested.
3850
// For WebSocketClients, this variable holds the extension parameters that client himself has requested.
3951
private Map<String, String> requestedParameters = new LinkedHashMap<String, String>();
52+
4053
private Inflater inflater = new Inflater(true);
4154
private Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
4255

56+
public Inflater getInflater() {
57+
return inflater;
58+
}
59+
60+
public void setInflater(Inflater inflater) {
61+
this.inflater = inflater;
62+
}
63+
64+
public Deflater getDeflater() {
65+
return deflater;
66+
}
67+
68+
public void setDeflater(Deflater deflater) {
69+
this.deflater = deflater;
70+
}
71+
72+
/**
73+
*
74+
* @return serverNoContextTakeover
75+
*/
76+
public boolean isServerNoContextTakeover()
77+
{
78+
return serverNoContextTakeover;
79+
}
80+
81+
/**
82+
*
83+
* @param serverNoContextTakeover
84+
*/
85+
public void setServerNoContextTakeover(boolean serverNoContextTakeover) {
86+
this.serverNoContextTakeover = serverNoContextTakeover;
87+
}
88+
89+
/**
90+
*
91+
* @return clientNoContextTakeover
92+
*/
93+
public boolean isClientNoContextTakeover()
94+
{
95+
return clientNoContextTakeover;
96+
}
97+
98+
/**
99+
*
100+
* @param clientNoContextTakeover
101+
*/
102+
public void setClientNoContextTakeover(boolean clientNoContextTakeover) {
103+
this.clientNoContextTakeover = clientNoContextTakeover;
104+
}
105+
43106
/*
44107
An endpoint uses the following algorithm to decompress a message.
45108
1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
@@ -50,11 +113,11 @@ public class PerMessageDeflateExtension extends CompressionExtension {
50113
@Override
51114
public void decodeFrame(Framedata inputFrame) throws InvalidDataException {
52115
// Only DataFrames can be decompressed.
53-
if(!(inputFrame instanceof DataFrame))
116+
if (!(inputFrame instanceof DataFrame))
54117
return;
55118

56119
// RSV1 bit must be set only for the first frame.
57-
if(inputFrame.getOpcode() == Opcode.CONTINUOUS && inputFrame.isRSV1())
120+
if (inputFrame.getOpcode() == Opcode.CONTINUOUS && inputFrame.isRSV1())
58121
throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "RSV1 bit can only be set for the first frame.");
59122

60123
// Decompressed output buffer.
@@ -70,47 +133,53 @@ We can check the getRemaining() method to see whether the data we supplied has b
70133
And if not, we just reset the inflater and decompress again.
71134
Note that this behavior doesn't occur if the message is "first compressed and then fragmented".
72135
*/
73-
if(inflater.getRemaining() > 0){
136+
if (inflater.getRemaining() > 0) {
74137
inflater = new Inflater(true);
75138
decompress(inputFrame.getPayloadData().array(), output);
76139
}
77140

78-
if(inputFrame.isFin()) {
141+
if (inputFrame.isFin()) {
79142
decompress(TAIL_BYTES, output);
80143
// If context takeover is disabled, inflater can be reset.
81-
if(clientNoContextTakeover)
144+
if (clientNoContextTakeover)
82145
inflater = new Inflater(true);
83146
}
84147
} catch (DataFormatException e) {
85148
throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, e.getMessage());
86149
}
87150

88151
// RSV1 bit must be cleared after decoding, so that other extensions don't throw an exception.
89-
if(inputFrame.isRSV1())
152+
if (inputFrame.isRSV1())
90153
((DataFrame) inputFrame).setRSV1(false);
91154

92155
// Set frames payload to the new decompressed data.
93156
((FramedataImpl1) inputFrame).setPayload(ByteBuffer.wrap(output.toByteArray(), 0, output.size()));
94157
}
95158

96-
private void decompress(byte[] data, ByteArrayOutputStream outputBuffer) throws DataFormatException{
159+
/**
160+
*
161+
* @param data the bytes of data
162+
* @param outputBuffer the output stream
163+
* @throws DataFormatException
164+
*/
165+
private void decompress(byte[] data, ByteArrayOutputStream outputBuffer) throws DataFormatException {
97166
inflater.setInput(data);
98167
byte[] buffer = new byte[BUFFER_SIZE];
99168

100169
int bytesInflated;
101-
while((bytesInflated = inflater.inflate(buffer)) > 0){
170+
while ((bytesInflated = inflater.inflate(buffer)) > 0) {
102171
outputBuffer.write(buffer, 0, bytesInflated);
103172
}
104173
}
105174

106175
@Override
107176
public void encodeFrame(Framedata inputFrame) {
108177
// Only DataFrames can be decompressed.
109-
if(!(inputFrame instanceof DataFrame))
178+
if (!(inputFrame instanceof DataFrame))
110179
return;
111180

112181
// Only the first frame's RSV1 must be set.
113-
if(!(inputFrame instanceof ContinuousFrame))
182+
if (!(inputFrame instanceof ContinuousFrame))
114183
((DataFrame) inputFrame).setRSV1(true);
115184

116185
deflater.setInput(inputFrame.getPayloadData().array());
@@ -119,7 +188,7 @@ public void encodeFrame(Framedata inputFrame) {
119188
// Temporary buffer to hold compressed output.
120189
byte[] buffer = new byte[1024];
121190
int bytesCompressed;
122-
while((bytesCompressed = deflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH)) > 0) {
191+
while ((bytesCompressed = deflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH)) > 0) {
123192
output.write(buffer, 0, bytesCompressed);
124193
}
125194

@@ -132,11 +201,11 @@ public void encodeFrame(Framedata inputFrame) {
132201
To simulate removal, we just pass 4 bytes less to the new payload
133202
if the frame is final and outputBytes ends with 0x00 0x00 0xff 0xff.
134203
*/
135-
if(inputFrame.isFin()) {
136-
if(endsWithTail(outputBytes))
204+
if (inputFrame.isFin()) {
205+
if (endsWithTail(outputBytes))
137206
outputLength -= TAIL_BYTES.length;
138207

139-
if(serverNoContextTakeover) {
208+
if (serverNoContextTakeover) {
140209
deflater.end();
141210
deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
142211
}
@@ -146,13 +215,18 @@ public void encodeFrame(Framedata inputFrame) {
146215
((FramedataImpl1) inputFrame).setPayload(ByteBuffer.wrap(outputBytes, 0, outputLength));
147216
}
148217

149-
private boolean endsWithTail(byte[] data){
150-
if(data.length < 4)
218+
/**
219+
*
220+
* @param data the bytes of data
221+
* @return true if the data is OK
222+
*/
223+
private boolean endsWithTail(byte[] data) {
224+
if (data.length < 4)
151225
return false;
152226

153227
int length = data.length;
154-
for(int i = 0; i < TAIL_BYTES.length; i++){
155-
if(TAIL_BYTES[i] != data[length - TAIL_BYTES.length + i])
228+
for (int i = 0; i < TAIL_BYTES.length; i++) {
229+
if (TAIL_BYTES[i] != data[length - TAIL_BYTES.length + i])
156230
return false;
157231
}
158232

@@ -162,15 +236,15 @@ private boolean endsWithTail(byte[] data){
162236
@Override
163237
public boolean acceptProvidedExtensionAsServer(String inputExtension) {
164238
String[] requestedExtensions = inputExtension.split(",");
165-
for(String extension : requestedExtensions) {
239+
for (String extension : requestedExtensions) {
166240
ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension);
167-
if(!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName()))
241+
if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName()))
168242
continue;
169243

170244
// Holds parameters that peer client has sent.
171245
Map<String, String> headers = extensionData.getExtensionParameters();
172246
requestedParameters.putAll(headers);
173-
if(requestedParameters.containsKey(CLIENT_NO_CONTEXT_TAKEOVER))
247+
if (requestedParameters.containsKey(CLIENT_NO_CONTEXT_TAKEOVER))
174248
clientNoContextTakeover = true;
175249

176250
return true;
@@ -182,9 +256,9 @@ public boolean acceptProvidedExtensionAsServer(String inputExtension) {
182256
@Override
183257
public boolean acceptProvidedExtensionAsClient(String inputExtension) {
184258
String[] requestedExtensions = inputExtension.split(",");
185-
for(String extension : requestedExtensions) {
259+
for (String extension : requestedExtensions) {
186260
ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension);
187-
if(!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName()))
261+
if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName()))
188262
continue;
189263

190264
// Holds parameters that are sent by the server, as a response to our initial extension request.
@@ -222,9 +296,9 @@ public IExtension copyInstance() {
222296
*/
223297
@Override
224298
public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
225-
if((inputFrame instanceof TextFrame || inputFrame instanceof BinaryFrame) && !inputFrame.isRSV1())
299+
if ((inputFrame instanceof TextFrame || inputFrame instanceof BinaryFrame) && !inputFrame.isRSV1())
226300
throw new InvalidFrameException("RSV1 bit must be set for DataFrames.");
227-
if((inputFrame instanceof ContinuousFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3()))
301+
if ((inputFrame instanceof ContinuousFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3()))
228302
throw new InvalidFrameException( "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + inputFrame.isRSV3() );
229303
super.isFrameValid(inputFrame);
230304
}

src/main/java/org/java_websocket/framing/FramedataImpl1.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public void setFin(boolean fin) {
183183
/**
184184
* Set the rsv1 of this frame to the provided boolean
185185
*
186-
* @param rsv1 true if fin has to be set
186+
* @param rsv1 true if rsv1 has to be set
187187
*/
188188
public void setRSV1(boolean rsv1) {
189189
this.rsv1 = rsv1;
@@ -192,7 +192,7 @@ public void setRSV1(boolean rsv1) {
192192
/**
193193
* Set the rsv2 of this frame to the provided boolean
194194
*
195-
* @param rsv2 true if fin has to be set
195+
* @param rsv2 true if rsv2 has to be set
196196
*/
197197
public void setRSV2(boolean rsv2) {
198198
this.rsv2 = rsv2;
@@ -201,7 +201,7 @@ public void setRSV2(boolean rsv2) {
201201
/**
202202
* Set the rsv3 of this frame to the provided boolean
203203
*
204-
* @param rsv3 true if fin has to be set
204+
* @param rsv3 true if rsv3 has to be set
205205
*/
206206
public void setRSV3(boolean rsv3) {
207207
this.rsv3 = rsv3;

src/test/java/org/java_websocket/extensions/PerMessageDeflateExtensionTest.java

Lines changed: 67 additions & 2 deletions

0 commit comments

Comments
 (0)