2 * (c) 2015 Cable Television Laboratories, Inc. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.packetcable.provider;
11 import static junit.framework.TestCase.assertEquals;
13 import static org.hamcrest.CoreMatchers.startsWith;
14 import static org.hamcrest.MatcherAssert.assertThat;
15 import static org.mockito.Mockito.mock;
16 import static org.mockito.Mockito.when;
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.net.InetAddress;
24 import java.net.Socket;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
31 import org.junit.After;
32 import org.junit.Assert;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
38 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.ServiceClassName;
39 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.ServiceFlowDirection;
40 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.TosByte;
41 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.TpProtocol;
42 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.ccap.attributes.AmId;
43 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.ccap.attributes.Connection;
44 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.ccaps.Ccap;
45 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.classifier.attributes.Classifiers;
46 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.classifier.attributes.classifiers.ClassifierContainer;
47 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.classifier.attributes.classifiers.classifier.container.classifier.choice.QosClassifierChoice;
48 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.classifier.Classifier;
49 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.gate.spec.GateSpec;
50 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
51 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.flow.spec.profile.FlowSpecProfile;
52 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.TrafficProfile;
53 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.TrafficProfileBuilder;
54 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.traffic.profile.TrafficProfileChoice;
55 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.traffic.profile.traffic.profile.choice.FlowSpecChoice;
56 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.traffic.profile.traffic.profile.choice.FlowSpecChoiceBuilder;
57 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.traffic.profile.traffic.profile.choice.ServiceClassNameChoice;
58 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.qos.traffic.profile.traffic.profile.traffic.profile.choice.ServiceClassNameChoiceBuilder;
59 import org.opendaylight.yang.gen.v1.urn.packetcable.rev161219.pcmm.serviceclass.name.profile.ServiceClassNameProfile;
60 import org.pcmm.PCMMPdpAgent;
61 import org.pcmm.gates.IPCMMGate;
62 import org.pcmm.rcd.IPCMMClient;
63 import org.pcmm.rcd.impl.CMTS;
64 import org.pcmm.rcd.impl.CMTSConfig;
65 import org.umu.cops.stack.COPSClientSI;
66 import org.umu.cops.stack.COPSContext;
67 import org.umu.cops.stack.COPSContext.RType;
68 import org.umu.cops.stack.COPSData;
69 import org.umu.cops.stack.COPSDecision;
70 import org.umu.cops.stack.COPSDecision.Command;
71 import org.umu.cops.stack.COPSDecision.DecisionFlag;
72 import org.umu.cops.stack.COPSDecisionMsg;
73 import org.umu.cops.stack.COPSHandle;
74 import org.umu.cops.stack.COPSMsg;
75 import org.umu.cops.stack.COPSMsgParser;
76 import org.umu.cops.stack.COPSObjHeader.CNum;
77 import org.umu.cops.stack.COPSObjHeader.CType;
80 * Tests the PCMMService's ability to connect to a CMTS. Gate additions will not properly work as there is currently
81 * not any other means to receive acknowledgements. This functionality must be tested by the PCMMService's client
82 * PacketcableProvider.
84 public class PCMMServiceTest {
86 private static final String ccapId = "ccap-1";
87 private static final String gatePath = "testGatePath";
90 * Denotes whether or not a real CMTS is being tested against.
91 * Ensure the checked-in value is always false else tests will most likely fail.
93 private static final boolean realCmts = false;
96 // The test objects/values to use that will be instantiated in @Before
99 * The mock CMTS running on localhost with a dynamic port assigned.
104 * The IP address object for the CMTS to test against
106 private Ipv4Address cmtsAddr;
109 * The gate classifier's srcIp value, any valid IP should work.
111 private Ipv4Address srcAddr;
114 * The gate classifier's dstIp value, any valid IP should work.
116 private Ipv4Address dstAddr;
119 * Defines the CMTS to add to the PCMMService
124 * The class under test
126 private PCMMService service;
129 * The cable modem IP address to which a gate should be set
131 private InetAddress cmAddrInet;
132 private InetAddress invalidCmAddrInet;
135 public void setup() throws IOException {
136 srcAddr = new Ipv4Address("10.10.10.0");
137 dstAddr = new Ipv4Address("10.32.99.99");
138 invalidCmAddrInet = InetAddress.getByAddress(new byte[] {99, 99, 99, 99});
141 cmAddrInet = InetAddress.getByAddress(new byte[] {10, 32, 110, (byte)172});
144 // Use me when testing against a CMTS or emulator not running in the same JVM
145 cmtsAddr = new Ipv4Address("10.32.10.3");
146 ccap = makeCcapObj(PCMMPdpAgent.WELL_KNOWN_PDP_PORT, cmtsAddr, ccapId);
148 cmAddrInet = InetAddress.getByAddress(new byte[] {10, 32, 110, (byte)180});
150 // Use me for automated testing and the CMTS emulator running in the same JVM
151 cmtsAddr = new Ipv4Address("127.0.0.1");
153 final Set<String> upSCN = new HashSet<>();
154 upSCN.add("extrm_up");
155 final Set<String> dnSCN = new HashSet<>();
156 dnSCN.add("extrm_dn");
158 final Map<String, Boolean> cmStatus = new HashMap<>();
159 cmStatus.put(cmAddrInet.getHostAddress(), true);
160 cmStatus.put(invalidCmAddrInet.getHostAddress(), false);
162 CMTSConfig config = new CMTSConfig(0, (short)4, upSCN, dnSCN, cmStatus);
164 icmts = new CMTS(config);
167 ccap = makeCcapObj(icmts.getPort(), cmtsAddr, ccapId);
170 service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
174 public void tearDown() {
175 if (icmts != null) icmts.stopServer();
180 public void testAddCcap() {
181 connectToCmts(service);
185 public void testAddInvalidCcapBadPort() {
187 if (icmts != null) port = icmts.getPort() + 1;
188 else port = PCMMPdpAgent.WELL_KNOWN_PDP_PORT + 1;
189 ccap = makeCcapObj(port, cmtsAddr, ccapId);
190 service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
191 final String message = service.addCcap();
192 Assert.assertNotNull(message);
193 final String expectedMsg = "404 Not Found - CCAP " + ccapId + " failed to connect @ " + cmtsAddr.getValue()
194 + ':' + port + " - ";
195 Assert.assertTrue(expectedMsg, message.startsWith(expectedMsg));
199 public void testAddValidUpGateTwice() throws Exception {
200 connectToCmts(service);
201 final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
202 addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
205 final String expectedMsg2 = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath + " already exists";
206 addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
209 Assert.assertTrue(deleteGate(service, gatePath));
213 public void testAddTwoValidUpGates() throws Exception {
214 connectToCmts(service);
216 final String gatePath1 = "gatePath1";
217 final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath1 + " returned GateId";
218 addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath1,
221 final String gatePath2 = "gatePath2";
222 final String expectedMsg2 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath2 + " returned GateId";
223 addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath2,
226 Assert.assertTrue(deleteGate(service, gatePath1));
227 Assert.assertTrue(deleteGate(service, gatePath2));
231 public void testAddValidDownGateTwice() throws Exception {
232 connectToCmts(service);
233 final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
234 addAndValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
237 final String expectedMsg2 = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath + " already exists";
238 addAndValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
241 Assert.assertTrue(deleteGate(service, gatePath));
245 public void testDeleteNonExistentGate() throws Exception {
246 connectToCmts(service);
247 Assert.assertFalse(deleteGate(service, gatePath));
251 public void testAddAndRemoveValidUpGate() throws Exception {
252 final String expectedMsgStart = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
253 addRemoveValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
258 public void testAddAndRemoveValidDownGate() throws Exception {
259 final String expectedMsgStart = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
260 addRemoveValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
265 public void testAddAndRemoveInvalidCmAddrUpGate() throws Exception {
266 // TODO - fix cmts emulator
267 final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
268 + " returned error - Error Code: 13 Subcode: 0 Invalid SubscriberID";
269 addInvalidGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, invalidCmAddrInet, gatePath,
274 public void testAddInvalidScnUpGate() throws Exception {
275 final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
276 + " returned error - Error Code: 11 Subcode: 0 Undefined Service Class Name";
277 addInvalidGate(service, "extrm_up_invalid", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
282 public void testAddInvalidScnDownGate() throws Exception {
283 final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
284 + " returned error - Error Code: 11 Subcode: 0 Undefined Service Class Name";
285 addInvalidGate(service, "extrm_dn_invalid", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
290 * This tests the instantiation of a COPSDecisionMsg object that is responsible for setting a gate request,
291 * streams it over a mock Socket object and parses the bytes into a new COPSDecisionMsg object which should
293 * @throws Exception - test will fail should any exception be thrown during execution
296 public void testGateRequestDecisionMsg() throws Exception {
297 final Socket socket = new MockSocket();
299 final ServiceFlowDirection direction = ServiceFlowDirection.Us;
300 final Gate gate = makeGateObj("extrm_up", cmtsAddr, direction, new Ipv4Address("127.0.0.1"));
301 final IPCMMGate gateReq = makeGateRequest(ccap, gate, InetAddress.getByName("localhost"), direction);
302 final byte[] data = gateReq.getData();
304 final Set<COPSDecision> decisionSet = new HashSet<>();
305 decisionSet.add(new COPSDecision(CType.DEF, Command.INSTALL, DecisionFlag.REQERROR));
306 final Map<COPSContext, Set<COPSDecision>> decisionMap = new HashMap<>();
307 decisionMap.put(new COPSContext(RType.CONFIG, (short) 0), decisionSet);
309 final COPSClientSI clientSD = new COPSClientSI(CNum.DEC, CType.CSI, new COPSData(data, 0, data.length));
310 final COPSDecisionMsg decisionMsg = new COPSDecisionMsg(IPCMMClient.CLIENT_TYPE, new COPSHandle(new COPSData("123")),
311 decisionMap, null, clientSD);
312 decisionMsg.writeData(socket);
314 final COPSMsg msg = COPSMsgParser.parseMessage(socket);
315 Assert.assertNotNull(msg);
316 Assert.assertEquals(decisionMsg, msg);
320 * Attempts to create a gate against a CMTS, validates the results then attempts to delete it.
321 * @param service - the service used to connect to a CMTS for issuing requests
322 * @param scnName - the service class name (aka. gate name)
323 * @param srcAddr - the address to the CMTS subnet?
324 * @param dstAddr - the destination address
325 * @param direction - the gate direction
326 * @param cmAddrInet - the address to the cable modem to which the gate will be assigned
327 * @param gatePath - the path to the gate
328 * @param expGateSetMsgStart - the expected start of the gate set return message to be validated against
330 private void addRemoveValidateGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
331 final Ipv4Address dstAddr, final ServiceFlowDirection direction,
332 final InetAddress cmAddrInet, final String gatePath,
333 final String expGateSetMsgStart) {
334 connectToCmts(service);
335 addAndValidateGate(service, scnName, srcAddr, dstAddr, direction, cmAddrInet, gatePath, expGateSetMsgStart);
336 deleteGate(service, gatePath);
339 private void addInvalidGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
340 final Ipv4Address dstAddr, final ServiceFlowDirection direction,
341 final InetAddress cmAddrInet, final String gatePath,
342 final String expGateSetMsgStart) {
343 connectToCmts(service);
344 final int numRequestsBefore = service.gateRequests.size();
345 addAndValidateGate(service, scnName, srcAddr, dstAddr, direction, cmAddrInet, gatePath, expGateSetMsgStart);
346 assertEquals(numRequestsBefore, service.gateRequests.size());
349 private void connectToCmts(final PCMMService service) {
350 final String message = service.addCcap();
351 Assert.assertNotNull(message);
352 final String expectedMsg = "200 OK - CCAP " + ccapId + " connected @ "
353 + ccap.getConnection().getIpAddress().getIpv4Address().getValue()
354 + ":" + ccap.getConnection().getPort().getValue();
355 Assert.assertEquals(expectedMsg, message);
356 Assert.assertNotNull(service.ccapClient.pcmmPdp.getClientHandle());
360 * Attempts to create a gate against a CMTS and validates the results.
361 * @param service - the service used to connect to a CMTS for issuing requests
362 * @param scnName - the service class name (aka. gate name)
363 * @param srcAddr - the address to the CMTS subnet?
364 * @param dstAddr - the destination address
365 * @param direction - the gate direction
366 * @param cmAddrInet - the address to the cable modem to which the gate will be assigned
367 * @param gatePath - the path to the gate
368 * @param expGateSetMsgStart - the expected start of the gate set return message to be validated against
370 private void addAndValidateGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
371 final Ipv4Address dstAddr, final ServiceFlowDirection direction,
372 final InetAddress cmAddrInet, final String gatePath,
373 final String expGateSetMsgStart) {
374 final Gate gate = makeGateObj(scnName, srcAddr, direction, dstAddr);
376 // final String gateSetMsg = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
377 // Assert.assertNotNull(gateSetMsg);
378 // Assert.assertTrue(gateSetMsg, gateSetMsg.startsWith(expGateSetMsgStart));
380 // TODO update this method for the new GateSendStatus object
381 PCMMService.GateSendStatus status = service.sendGateSet(gatePath, cmAddrInet, gate);
382 Assert.assertNotNull(status);
383 assertThat(status.getMessage(), startsWith(expGateSetMsgStart));
386 // TODO - add validation to the PCMMGateReq contained within the map
387 if (status.didSucceed()) {
388 Assert.assertTrue((service.gateRequests.containsKey(gatePath)));
389 Assert.assertNotNull(service.gateRequests.get(gatePath));
394 * Attempts to delete a gate
395 * @param service - the service used to connect to a CMTS for issuing requests
396 * @param gatePath - the path to the gate
398 private boolean deleteGate(final PCMMService service, final String gatePath) {
399 final boolean out = service.sendGateDelete(gatePath);
401 // Wait up to 1 sec for response to be processed
402 final long start = System.currentTimeMillis();
403 while (1000 < System.currentTimeMillis() - start) {
404 if (service.gateRequests.size() == 0) break;
406 Assert.assertNull(service.gateRequests.get(gatePath));
411 * Creates a mock Ccap object that can be used for connecting to a CMTS
412 * @param inPort - the CMTS port number
413 * @param ipAddr - the CMTS IPv4 address string
414 * @param ccapId - the ID of the CCAP
415 * @return - the mock Ccap object
417 private Ccap makeCcapObj(final int inPort, final Ipv4Address ipAddr, final String ccapId) {
418 final Ccap ccap = mock(Ccap.class);
419 final Connection conn = mock(Connection.class);
420 when(ccap.getConnection()).thenReturn(conn);
421 final PortNumber port = mock(PortNumber.class);
422 when(conn.getPort()).thenReturn(port);
423 when(port.getValue()).thenReturn(inPort);
425 final IpAddress addr = mock(IpAddress.class);
426 when(conn.getIpAddress()).thenReturn(addr);
427 when(addr.getIpv4Address()).thenReturn(ipAddr);
429 when(ccap.getCcapId()).thenReturn(ccapId);
430 final AmId amid = mock(AmId.class);
431 when(ccap.getAmId()).thenReturn(amid);
432 when(amid.getAmTag()).thenReturn(0xcada);
433 when(amid.getAmType()).thenReturn(1);
439 * Creates a mock Gate object
440 * @param scnValue - the service class name defined on the CMTS
441 * @param dstAddr - the CM address this gate should be set against
442 * @return - the gate request
444 private Gate makeGateObj(final String scnValue, final Ipv4Address srcAddr, final ServiceFlowDirection direction,
445 final Ipv4Address dstAddr) {
446 final Gate gate = mock(Gate.class);
447 final GateSpec gateSpec = mock(GateSpec.class);
448 when(gate.getGateSpec()).thenReturn(gateSpec);
449 when(gateSpec.getDirection()).thenReturn(direction);
450 // TODO - make sure to write a test when this value is not null
451 when(gateSpec.getDscpTosOverwrite()).thenReturn(null);
453 // TODO - refactor to add flowspec profile testing as well
454 final TrafficProfile trafficProfile = mock(TrafficProfile.class);
455 final ServiceClassNameChoice serviceClassNameChoice = mock(ServiceClassNameChoice.class);
456 final ServiceClassNameProfile serviceClassNameProfile = mock(ServiceClassNameProfile.class);
457 final ServiceClassName serviceClassName = mock(ServiceClassName.class);
458 when(serviceClassName.getValue()).thenReturn(scnValue);
459 when(serviceClassNameProfile.getServiceClassName()).thenReturn(serviceClassName);
460 when(serviceClassNameChoice.getServiceClassNameProfile()).thenReturn(serviceClassNameProfile);
461 when(trafficProfile.getTrafficProfileChoice()).thenReturn(serviceClassNameChoice);
462 when(gate.getTrafficProfile()).thenReturn(trafficProfile);
464 // TODO - write tests when this is null and ExtClassifier or Ipv6Classifier objects are not null
465 final Classifier classifier = mock(Classifier.class);
467 // This is the address of the CM
468 when(classifier.getDstIp()).thenReturn(dstAddr);
470 final PortNumber dstPort = new PortNumber(4321);
471 when(classifier.getDstPort()).thenReturn(dstPort);
472 final TpProtocol protocol = new TpProtocol(0);
473 when(classifier.getProtocol()).thenReturn(protocol);
474 when(classifier.getSrcIp()).thenReturn(srcAddr);
475 final PortNumber srcPort = new PortNumber(1234);
476 when(classifier.getSrcPort()).thenReturn(srcPort);
478 // TODO - Can this value be any other value than 0 or 1 (See TosByte enumeration)
479 final TosByte tosByte = new TosByte((short)0);
480 when(classifier.getTosByte()).thenReturn(tosByte);
481 final TosByte tosMask = new TosByte((short)224);
482 when(classifier.getTosMask()).thenReturn(tosMask);
484 final QosClassifierChoice classifierChoice = mock(QosClassifierChoice.class);
485 when(classifierChoice.getClassifier()).thenReturn(classifier);
487 ClassifierContainer classifierContainer = mock(ClassifierContainer.class);
488 when(classifierContainer.getClassifierChoice()).thenReturn(classifierChoice);
490 final List<ClassifierContainer> containerList = Collections.singletonList(classifierContainer);
492 Classifiers classifiers = mock(Classifiers.class);
493 when(classifiers.getClassifierContainer()).thenReturn(containerList);
495 when(gate.getClassifiers()).thenReturn(classifiers);
500 private IPCMMGate makeGateRequest(final Ccap ccap, final Gate gateReq, final InetAddress addrSubId,
501 final ServiceFlowDirection direction) {
502 final PCMMGateReqBuilder gateBuilder = new PCMMGateReqBuilder();
503 gateBuilder.setAmId(ccap.getAmId());
504 gateBuilder.setSubscriberId(addrSubId);
505 // force gateSpec.Direction to align with SCN direction
506 final ServiceClassName scn =
507 ((ServiceClassNameChoice)gateReq.getTrafficProfile().getTrafficProfileChoice()).getServiceClassNameProfile().getServiceClassName();
509 gateBuilder.setGateSpec(gateReq.getGateSpec(), direction);
512 gateBuilder.setGateSpec(gateReq.getGateSpec(), null);
514 gateBuilder.setTrafficProfile(gateReq.getTrafficProfile());
516 gateBuilder.setClassifiers(gateReq.getClassifiers().getClassifierContainer());
518 // assemble the final gate request
519 return gateBuilder.build();
522 private class MockSocket extends Socket {
524 private ByteArrayOutputStream os = new ByteArrayOutputStream();
525 private ByteArrayInputStream is;
528 public OutputStream getOutputStream() {
533 public InputStream getInputStream() {
534 if (is == null) is = new ByteArrayInputStream(os.toByteArray());