Skip to content

Commit e38d9a9

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

File tree

4 files changed

+173
-32
lines changed

4 files changed

+173
-32
lines changed

README.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ non-blocking event-driven model (similar to the
1313
Implemented WebSocket protocol versions are:
1414

1515
* [RFC 6455](http://tools.ietf.org/html/rfc6455)
16+
* [RFC 7692](http://tools.ietf.org/html/rfc7692)
1617

1718
[Here](https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts) some more details about protocol versions/drafts.
19+
[PerMessageDeflateExample](https://github.com/TooTallNate/Java-WebSocket/wiki/PerMessageDeflateExample) enable the extension with reference to both a server and client example.
1820

1921

2022
## Getting Started

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
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77
import org.junit.Test;
88

99
import java.nio.ByteBuffer;
10+
import java.util.zip.Deflater;
11+
import java.util.zip.Inflater;
1012

11-
import static org.junit.Assert.*;
13+
import static org.junit.Assert.assertArrayEquals;
14+
import static org.junit.Assert.assertEquals;
15+
import static org.junit.Assert.assertFalse;
16+
import static org.junit.Assert.assertTrue;
17+
import static org.junit.Assert.fail;
1218

1319
public class PerMessageDeflateExtensionTest {
1420

@@ -113,8 +119,67 @@ public void testGetProvidedExtensionAsServer() {
113119
}
114120

115121
@Test
116-
public void testToString() throws Exception {
122+
public void testToString() {
117123
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
118124
assertEquals( "PerMessageDeflateExtension", deflateExtension.toString() );
119125
}
126+
127+
@Test
128+
public void testIsServerNoContextTakeover() {
129+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
130+
assertTrue(deflateExtension.isServerNoContextTakeover());
131+
}
132+
133+
@Test
134+
public void testSetServerNoContextTakeover() {
135+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
136+
deflateExtension.setServerNoContextTakeover(false);
137+
assertFalse(deflateExtension.isServerNoContextTakeover());
138+
}
139+
140+
@Test
141+
public void testIsClientNoContextTakeover() {
142+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
143+
assertFalse(deflateExtension.isClientNoContextTakeover());
144+
}
145+
146+
@Test
147+
public void testSetClientNoContextTakeover() {
148+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
149+
deflateExtension.setClientNoContextTakeover(true);
150+
assertTrue(deflateExtension.isClientNoContextTakeover());
151+
}
152+
153+
@Test
154+
public void testCopyInstance() {
155+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
156+
IExtension newDeflateExtension = deflateExtension.copyInstance();
157+
assertEquals(deflateExtension.toString(), newDeflateExtension.toString());
158+
}
159+
160+
@Test
161+
public void testGetInflater() {
162+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
163+
assertEquals(deflateExtension.getInflater().getRemaining(), new Inflater(true).getRemaining());
164+
}
165+
166+
@Test
167+
public void testSetInflater() {
168+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
169+
deflateExtension.setInflater(new Inflater(false));
170+
assertEquals(deflateExtension.getInflater().getRemaining(), new Inflater(false).getRemaining());
171+
}
172+
173+
@Test
174+
public void testGetDeflater() {
175+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
176+
assertEquals(deflateExtension.getDeflater().finished(), new Deflater(Deflater.DEFAULT_COMPRESSION, true).finished());
177+
}
178+
179+
@Test
180+
public void testSetDeflater() {
181+
PerMessageDeflateExtension deflateExtension = new PerMessageDeflateExtension();
182+
deflateExtension.setDeflater(new Deflater(Deflater.DEFAULT_COMPRESSION, false));
183+
assertEquals(deflateExtension.getDeflater().finished(),new Deflater(Deflater.DEFAULT_COMPRESSION, false).finished());
184+
}
120185
}

0 commit comments

Comments
 (0)