Issue #207 - Support javax.websocket version 1.1

WIP
This commit is contained in:
Joakim Erdfelt 2016-04-27 15:05:05 -07:00
parent 31328b8f66
commit 8784a1f850
21 changed files with 183 additions and 88 deletions

View File

@ -299,6 +299,8 @@ public class WebSocketPolicy
*/
public void setIdleTimeout(long ms)
{
if(ms < -1) return; // no change (likely came from annotation)
boolean dirty = (this.idleTimeout != ms);
assertGreaterThan("IdleTimeout",ms,0);
this.idleTimeout = ms;
@ -314,6 +316,8 @@ public class WebSocketPolicy
*/
public void setInputBufferSize(int size)
{
if(size < 0) return; // no change (likely came from annotation)
boolean dirty = (this.inputBufferSize != size);
assertGreaterThan("InputBufferSize",size,1);
assertLessThan("InputBufferSize",size,"MaxTextMessageBufferSize",maxTextMessageBufferSize);
@ -352,6 +356,8 @@ public class WebSocketPolicy
*/
public void setMaxBinaryMessageSize(int size)
{
if(size < 0) return; // no change (likely came from annotation)
boolean dirty = (this.maxBinaryMessageSize != size);
assertGreaterThan("MaxBinaryMessageSize",size,1);
@ -388,6 +394,8 @@ public class WebSocketPolicy
*/
public void setMaxTextMessageSize(int size)
{
if(size < 0) return; // no change (likely came from annotation)
boolean dirty = (this.maxTextMessageSize != size);
assertGreaterThan("MaxTextMessageSize",size,1);

View File

@ -36,42 +36,29 @@ import org.eclipse.jetty.websocket.api.StatusCode;
{ ElementType.TYPE })
public @interface WebSocket
{
/* NOTE TO OTHER DEVELOPERS:
* If you change any of these default values,
* make sure you sync the values with WebSocketPolicy
*/
/**
* The size of the buffer used to read from the network layer.
* <p>
* Default: 4096 (4 K)
* The size of the buffer (in bytes) used to read from the network layer.
*/
int inputBufferSize() default 4 * 1024;
int inputBufferSize() default -2;
/**
* The maximum size of a binary message during parsing/generating.
* The maximum size of a binary message (in bytes) during parsing/generating.
* <p>
* Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
* <p>
* Default: 65536 (64 K)
*/
int maxBinaryMessageSize() default 64 * 1024;
int maxBinaryMessageSize() default -2;
/**
* The time in ms (milliseconds) that a websocket may be idle before closing.
* <p>
* Default: 300000 (ms)
*/
int maxIdleTime() default 300_000;
int maxIdleTime() default -2;
/**
* The maximum size of a text message during parsing/generating.
* <p>
* Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
* <p>
* Default: 65536 (64 K)
*/
int maxTextMessageSize() default 64 * 1024;
int maxTextMessageSize() default -2;
/**
* The output frame buffering mode.

View File

@ -0,0 +1,46 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.jetty.websocket.api.WebSocketException;
public class FunctionCallException extends WebSocketException
{
public FunctionCallException(String message, Throwable cause)
{
super(message, cause);
}
public FunctionCallException(Throwable cause)
{
super(cause);
}
public Throwable getInvokedCause()
{
Throwable cause = getCause();
if (cause instanceof InvocationTargetException)
{
return cause.getCause();
}
return cause;
}
}

View File

@ -367,6 +367,11 @@ public class Generator
}
}
public WebSocketBehavior getBehavior()
{
return behavior;
}
public ByteBufferPool getBufferPool()
{
return bufferPool;

View File

@ -595,8 +595,9 @@ public class Parser
buffer.limit(limit);
buffer.position(buffer.position() + window.remaining());
if (LOG.isDebugEnabled()) {
LOG.debug("{} Window: {}",policy.getBehavior(),BufferUtil.toDetailString(window));
if (LOG.isDebugEnabled())
{
LOG.debug("{} Raw Payload: {}",policy.getBehavior(),BufferUtil.toDetailString(window));
}
maskProcessor.process(window);

View File

@ -94,6 +94,7 @@ import org.eclipse.jetty.websocket.common.message.ReaderMessageSink;
import org.eclipse.jetty.websocket.common.message.StringMessageSink;
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
import org.eclipse.jetty.websocket.common.scopes.WebSocketSessionScope;
import org.eclipse.jetty.websocket.common.util.DynamicArgsException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
@ManagedObject("A Jetty WebSocket Session")
@ -254,7 +255,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
policy.setMaxBinaryMessageSize(websocket.maxBinaryMessageSize());
policy.setMaxTextMessageSize(websocket.maxTextMessageSize());
policy.setIdleTimeout(websocket.maxIdleTime());
this.batchmode = websocket.batchMode();
Method onmethod = null;
@ -510,6 +511,22 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
return connection.getMaxIdleTimeout();
}
private Throwable getInvokedCause(Throwable t)
{
if (t instanceof FunctionCallException)
{
return ((FunctionCallException) t).getInvokedCause();
}
else if (t instanceof DynamicArgsException)
{
Throwable cause = ((DynamicArgsException) t).getInvokedCause();
if (cause != null)
return cause;
}
return t;
}
@Override
public InetSocketAddress getLocalAddress()
{
@ -691,6 +708,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
}
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Discarding post EOF frame - {}", frame);
}
}
catch (NotUtf8Exception e)
{
@ -703,25 +725,27 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
}
catch (Throwable t)
{
LOG.warn("Unhandled Error (closing connection)",t);
Throwable cause = getInvokedCause(t);
notifyError(t);
LOG.warn("Unhandled Error (closing connection)",cause);
notifyError(cause);
// Unhandled Error, close the connection.
switch (policy.getBehavior())
{
case SERVER:
close(StatusCode.SERVER_ERROR,t.getClass().getSimpleName());
close(StatusCode.SERVER_ERROR,cause.getClass().getSimpleName());
break;
case CLIENT:
close(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName());
close(StatusCode.POLICY_VIOLATION,cause.getClass().getSimpleName());
break;
}
}
finally
{
// Unset active MessageSink if this was a fin frame
if (frame.isFin() && activeMessageSink != null)
if (frame.getType().isData() && frame.isFin() && activeMessageSink != null)
activeMessageSink = null;
Thread.currentThread().setContextClassLoader(old);
@ -823,7 +847,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
public void open()
{
if (LOG_OPEN.isDebugEnabled())
LOG_OPEN.debug("[{}] {}.open()",policy.getBehavior(),this.getClass().getSimpleName());
LOG_OPEN.debug("[{}] {}.open()", policy.getBehavior(), this.getClass().getSimpleName());
if (remote != null)
{
@ -837,9 +861,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
connection.getIOState().onConnected();
// Connect remote
remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
remote = new WebSocketRemoteEndpoint(connection, outgoingHandler, getBatchMode());
if (LOG_OPEN.isDebugEnabled())
LOG_OPEN.debug("[{}] {}.open() remote={}",policy.getBehavior(),this.getClass().getSimpleName(),remote);
LOG_OPEN.debug("[{}] {}.open() remote={}", policy.getBehavior(), this.getClass().getSimpleName(), remote);
// Open WebSocket
if (onOpenFunction != null)
@ -850,17 +874,21 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (LOG.isDebugEnabled())
{
LOG.debug("open -> {}",dump());
LOG.debug("open -> {}", dump());
}
}
catch (CloseException ce)
{
LOG.warn(ce);
close(ce.getStatusCode(),ce.getMessage());
notifyError(ce.getCause());
close(ce.getStatusCode(), ce.getMessage());
}
catch (Throwable t)
{
LOG.warn(t);
Throwable cause = getInvokedCause(t);
LOG.warn(cause);
notifyError(cause);
// Exception on end-user WS-Endpoint.
// Fast-fail & close connection with reason.
int statusCode = StatusCode.SERVER_ERROR;
@ -868,7 +896,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
{
statusCode = StatusCode.POLICY_VIOLATION;
}
close(statusCode,t.getMessage());
close(statusCode,cause.getMessage());
}
}

View File

@ -23,9 +23,9 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -93,7 +93,7 @@ public class OnByteArrayFunction implements Function<byte[], Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -24,9 +24,9 @@ import java.nio.ByteBuffer;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -90,7 +90,7 @@ public class OnByteBufferFunction implements Function<ByteBuffer, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -23,10 +23,10 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -83,7 +83,7 @@ public class OnCloseFunction implements Function<CloseInfo, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call close method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call close method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -23,9 +23,9 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -79,7 +79,7 @@ public class OnErrorFunction implements Function<Throwable, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call error method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call error method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -23,10 +23,10 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
@ -82,7 +82,7 @@ public class OnFrameFunction implements Function<Frame, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call frame method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call frame method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -24,9 +24,9 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -91,7 +91,7 @@ public class OnInputStreamFunction implements Function<InputStream, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -23,9 +23,9 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -76,7 +76,7 @@ public class OnOpenFunction implements Function<Session, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -24,9 +24,9 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -90,7 +90,7 @@ public class OnReaderFunction implements Function<Reader, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -23,9 +23,9 @@ import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
@ -89,7 +89,7 @@ public class OnTextFunction implements Function<String, Void>
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}

View File

@ -423,7 +423,7 @@ public class FrameFlusher
public String toString()
{
ByteBuffer aggregate = flusher.aggregate;
return String.format("%s[queueSize=%d,aggregateSize=%d,failure=%s]",getClass().getSimpleName(),queue.size(),aggregate == null?0:aggregate.position(),
return String.format("%s[%s,queueSize=%d,aggregateSize=%d,failure=%s]",getClass().getSimpleName(),generator.getBehavior(),queue.size(),aggregate == null?0:aggregate.position(),
failure);
}
}

View File

@ -21,11 +21,15 @@ package org.eclipse.jetty.websocket.common.message;
import java.nio.ByteBuffer;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
public class StringMessageSink implements MessageSink
{
private static final Logger LOG = Log.getLogger(StringMessageSink.class);
private final WebSocketPolicy policy;
private final Function<String, Void> onMessageFunction;
private Utf8StringBuilder utf;
@ -42,33 +46,31 @@ public class StringMessageSink implements MessageSink
@Override
public void accept(ByteBuffer payload, Boolean fin)
{
try
if (payload != null)
{
if (payload != null)
{
policy.assertValidTextMessageSize(size + payload.remaining());
size += payload.remaining();
policy.assertValidTextMessageSize(size + payload.remaining());
size += payload.remaining();
if (utf == null)
utf = new Utf8StringBuilder(1024);
if (utf == null)
utf = new Utf8StringBuilder(1024);
// allow for fast fail of BAD utf (incomplete utf will trigger on messageComplete)
utf.append(payload);
}
if(LOG.isDebugEnabled())
LOG.debug("Raw Payload {}", BufferUtil.toDetailString(payload));
// allow for fast fail of BAD utf (incomplete utf will trigger on messageComplete)
utf.append(payload);
}
finally
if (fin)
{
if (fin)
{
// notify event
if (utf != null)
onMessageFunction.apply(utf.toString());
else
onMessageFunction.apply("");
// reset
size = 0;
utf = null;
}
// notify event
if (utf != null)
onMessageFunction.apply(utf.toString());
else
onMessageFunction.apply("");
// reset
size = 0;
utf = null;
}
}
}

View File

@ -18,16 +18,31 @@
package org.eclipse.jetty.websocket.common.util;
import java.lang.reflect.InvocationTargetException;
@SuppressWarnings("serial")
public class DynamicArgsException extends RuntimeException
{
public DynamicArgsException(String message, Throwable cause)
{
super(message,cause);
super(message, cause);
}
public DynamicArgsException(String message)
{
super(message);
}
public Throwable getInvokedCause()
{
Throwable cause = getCause();
if (cause == null)
return null;
if (cause instanceof InvocationTargetException)
{
return cause.getCause();
}
return cause;
}
}

View File

@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
@ -40,14 +41,18 @@ import org.junit.Test;
public class IdleTimeoutTest
{
@WebSocket(maxIdleTime = 500)
public static class FastTimeoutRFCSocket extends RFCSocket
{
}
@SuppressWarnings("serial")
public static class TimeoutServlet extends WebSocketServlet
{
@Override
public void configure(WebSocketServletFactory factory)
{
factory.getPolicy().setIdleTimeout(500);
factory.register(RFCSocket.class);
factory.register(FastTimeoutRFCSocket.class);
}
}

View File

@ -557,7 +557,7 @@ public class TestABCase5 extends AbstractABCase
}
/**
* Send text fragmented in 2 packets, with ping between them (framewise)
* Send text fragmented in 2 packets, with ping between them (frame wise)
* @throws Exception on test failure
*/
@Test

View File

@ -46,7 +46,7 @@ public class TestABCase6_BadUTF extends AbstractABCase
{
private static final Logger LOG = Log.getLogger(TestABCase6_BadUTF.class);
@Parameters
@Parameters(name = "{0} - {1}")
public static Collection<String[]> data()
{
// The various Good UTF8 sequences as a String (hex form)
@ -163,15 +163,13 @@ public class TestABCase6_BadUTF extends AbstractABCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
try (Fuzzer fuzzer = new Fuzzer(this))
try (Fuzzer fuzzer = new Fuzzer(this);
StacklessLogging ignored = new StacklessLogging(Parser.class) )
{
try (StacklessLogging supress = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
}
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
}
}
}