Adding support for multiple classifiers per gate
[packetcable.git] / packetcable-policy-server / src / test / java / org / opendaylight / controller / packetcable / provider / PCMMServiceTest.java
1 /*
2  * (c) 2015 Cable Television Laboratories, Inc.  All rights reserved.
3  */
4
5 package org.opendaylight.controller.packetcable.provider;
6
7 import static junit.framework.TestCase.assertEquals;
8 import static org.mockito.Mockito.mock;
9 import static org.mockito.Mockito.when;
10
11 import java.io.ByteArrayInputStream;
12 import java.io.ByteArrayOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
16 import java.net.InetAddress;
17 import java.net.Socket;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import org.junit.After;
25 import org.junit.Assert;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber;
31 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ServiceClassName;
32 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ServiceFlowDirection;
33 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.TosByte;
34 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.TpProtocol;
35 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ccap.attributes.AmId;
36 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ccap.attributes.Connection;
37 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ccaps.Ccap;
38 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.classifier.attributes.Classifiers;
39 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.classifier.attributes.classifiers.ClassifierContainer;
40 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.classifier.attributes.classifiers.classifier.container.classifier.choice.QosClassifierChoice;
41 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.classifier.Classifier;
42 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gate.spec.GateSpec;
43 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
44 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.traffic.profile.TrafficProfile;
45 import org.pcmm.PCMMPdpAgent;
46 import org.pcmm.gates.IGateSpec.Direction;
47 import org.pcmm.gates.IPCMMGate;
48 import org.pcmm.rcd.IPCMMClient;
49 import org.pcmm.rcd.impl.CMTS;
50 import org.umu.cops.stack.COPSClientSI;
51 import org.umu.cops.stack.COPSContext;
52 import org.umu.cops.stack.COPSContext.RType;
53 import org.umu.cops.stack.COPSData;
54 import org.umu.cops.stack.COPSDecision;
55 import org.umu.cops.stack.COPSDecision.Command;
56 import org.umu.cops.stack.COPSDecision.DecisionFlag;
57 import org.umu.cops.stack.COPSDecisionMsg;
58 import org.umu.cops.stack.COPSHandle;
59 import org.umu.cops.stack.COPSMsg;
60 import org.umu.cops.stack.COPSMsgParser;
61 import org.umu.cops.stack.COPSObjHeader.CNum;
62 import org.umu.cops.stack.COPSObjHeader.CType;
63
64 /**
65  * Tests the PCMMService's ability to connect to a CMTS. Gate additions will not properly work as there is currently
66  * not any other means to receive acknowledgements. This functionality must be tested by the PCMMService's client
67  * PacketcableProvider.
68  */
69 public class PCMMServiceTest {
70
71     private static final String ccapId = "ccap-1";
72     private static final String gatePath = "testGatePath";
73
74     /**
75      * Denotes whether or not a real CMTS is being tested against.
76      * Ensure the checked-in value is always false else tests will most likely fail.
77      */
78     private static final boolean realCmts = false;
79
80
81     // The test objects/values to use that will be instantiated in @Before
82
83     /**
84      * The mock CMTS running on localhost with a dynamic port assigned.
85      */
86     private CMTS icmts;
87
88     /**
89      * The IP address object for the CMTS to test against
90      */
91     private Ipv4Address cmtsAddr;
92
93     /**
94      * The gate classifier's srcIp value, any valid IP should work.
95      */
96     private Ipv4Address srcAddr;
97
98     /**
99      * The gate classifier's dstIp value, any valid IP should work.
100      */
101     private Ipv4Address dstAddr;
102
103     /**
104      * Defines the CMTS to add to the PCMMService
105      */
106     private Ccap ccap;
107
108     /**
109      * The class under test
110      */
111     private PCMMService service;
112
113     /**
114      * The cable modem IP address to which a gate should be set
115      */
116     private InetAddress cmAddrInet;
117     private InetAddress invalidCmAddrInet;
118
119     @Before
120     public void setup() throws IOException {
121         srcAddr = new Ipv4Address("10.10.10.0");
122         dstAddr = new Ipv4Address("10.32.99.99");
123
124         if (realCmts) {
125             cmAddrInet = InetAddress.getByAddress(new byte[] {10, 32, 110, (byte)172});
126             invalidCmAddrInet = InetAddress.getByAddress(new byte[] {99, 99, 99, 99});
127
128             // Use me when testing against a CMTS or emulator not running in the same JVM
129             cmtsAddr = new Ipv4Address("10.32.10.3");
130             ccap = makeCcapObj(PCMMPdpAgent.WELL_KNOWN_PDP_PORT, cmtsAddr, ccapId);
131         } else {
132             cmAddrInet = InetAddress.getByAddress(new byte[] {10, 32, 110, (byte)180});
133             invalidCmAddrInet = InetAddress.getByAddress(new byte[] {99, 99, 99, 99});
134
135             // Use me for automated testing and the CMTS emulator running in the same JVM
136             cmtsAddr = new Ipv4Address("127.0.0.1");
137
138             final Set<String> upGate = new HashSet<>();
139             upGate.add("extrm_up");
140             final Set<String> dnGate = new HashSet<>();
141             dnGate.add("extrm_dn");
142             final Map<Direction, Set<String>> gates = new HashMap<>();
143             gates.put(Direction.UPSTREAM, upGate);
144             gates.put(Direction.DOWNSTREAM, dnGate);
145
146             final Map<String, Boolean> cmStatus = new HashMap<>();
147             cmStatus.put(cmAddrInet.getHostAddress(), true);
148             cmStatus.put(invalidCmAddrInet.getHostAddress(), false);
149             icmts = new CMTS(gates, cmStatus);
150             icmts.startServer();
151
152             ccap = makeCcapObj(icmts.getPort(), cmtsAddr, ccapId);
153         }
154
155         service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
156     }
157
158     @After
159     public void tearDown() {
160         if (icmts != null) icmts.stopServer();
161         service.disconect();
162     }
163
164     @Test
165     public void testAddCcap() {
166         connectToCmts(service);
167     }
168
169     @Test
170     public void testAddInvalidCcapBadPort() {
171         final int port;
172         if (icmts != null) port = icmts.getPort() + 1;
173         else port = PCMMPdpAgent.WELL_KNOWN_PDP_PORT + 1;
174         ccap = makeCcapObj(port, cmtsAddr, ccapId);
175         service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
176         final String message = service.addCcap();
177         Assert.assertNotNull(message);
178         final String expectedMsg = "404 Not Found - CCAP " + ccapId + " failed to connect @ " + cmtsAddr.getValue()
179                 + ':' + port + " - ";
180         Assert.assertTrue(expectedMsg, message.startsWith(expectedMsg));
181     }
182
183     @Test
184     public void testAddValidUpGateTwice() throws Exception {
185         connectToCmts(service);
186         final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
187         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
188                 expectedMsg1);
189
190         final String expectedMsg2 = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath + " already exists";
191         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
192                 expectedMsg2);
193
194         Assert.assertTrue(deleteGate(service, gatePath));
195     }
196
197     @Test
198     public void testAddTwoValidUpGates() throws Exception {
199         connectToCmts(service);
200
201         final String gatePath1 = "gatePath1";
202         final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath1 + " returned GateId";
203         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath1,
204                 expectedMsg1);
205
206         final String gatePath2 = "gatePath2";
207         final String expectedMsg2 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath2 + " returned GateId";
208         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath2,
209                 expectedMsg2);
210
211         Assert.assertTrue(deleteGate(service, gatePath1));
212         Assert.assertTrue(deleteGate(service, gatePath2));
213     }
214
215     @Test
216     public void testAddValidDownGateTwice() throws Exception {
217         connectToCmts(service);
218         final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
219         addAndValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
220                 expectedMsg1);
221
222         final String expectedMsg2 = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath + " already exists";
223         addAndValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
224                 expectedMsg2);
225
226         Assert.assertTrue(deleteGate(service, gatePath));
227     }
228
229     @Test
230     public void testDeleteNonExistentGate() throws Exception {
231         connectToCmts(service);
232         Assert.assertFalse(deleteGate(service, gatePath));
233     }
234
235     @Test
236     public void testAddAndRemoveValidUpGate() throws Exception {
237         final String expectedMsgStart = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
238         addRemoveValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
239                 expectedMsgStart);
240     }
241
242     @Test
243     public void testAddAndRemoveValidDownGate() throws Exception {
244         final String expectedMsgStart = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
245         addRemoveValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
246                 expectedMsgStart);
247     }
248
249     @Test
250     public void testAddAndRemoveInvalidCmAddrUpGate() throws Exception {
251         // TODO - fix cmts emulator
252         final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
253                 + " returned error - Error Code: 13 Error Subcode : 0  Invalid SubscriberID";
254         addInvalidGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, invalidCmAddrInet, gatePath,
255                 expectedMsgStart);
256     }
257
258     @Test
259     public void testAddInvalidScnUpGate() throws Exception {
260         final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
261                 + " returned error - Error Code: 11 Error Subcode : 0  Undefined Service Class Name";
262         addInvalidGate(service, "extrm_up_invalid", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
263                 expectedMsgStart);
264     }
265
266     @Test
267     public void testAddInvalidScnDownGate() throws Exception {
268         final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
269                 + " returned error - Error Code: 11 Error Subcode : 0  Undefined Service Class Name";
270         addInvalidGate(service, "extrm_dn_invalid", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
271                 expectedMsgStart);
272     }
273
274     /**
275      * This tests the instantiation of a COPSDecisionMsg object that is responsible for setting a gate request,
276      * streams it over a mock Socket object and parses the bytes into a new COPSDecisionMsg object which should
277      * be equivalent
278      * @throws Exception - test will fail should any exception be thrown during execution
279      */
280     @Test
281     public void testGateRequestDecisionMsg() throws Exception {
282         final Socket socket = new MockSocket();
283
284         final ServiceFlowDirection direction = ServiceFlowDirection.Us;
285         final Gate gate = makeGateObj("extrm_up", cmtsAddr, direction, new Ipv4Address("127.0.0.1"));
286         final IPCMMGate gateReq = makeGateRequest(ccap, gate, InetAddress.getByName("localhost"), direction);
287         final byte[] data = gateReq.getData();
288
289         final Set<COPSDecision> decisionSet = new HashSet<>();
290         decisionSet.add(new COPSDecision(CType.DEF, Command.INSTALL, DecisionFlag.REQERROR));
291         final Map<COPSContext, Set<COPSDecision>> decisionMap = new HashMap<>();
292         decisionMap.put(new COPSContext(RType.CONFIG, (short) 0), decisionSet);
293
294         final COPSClientSI clientSD = new COPSClientSI(CNum.DEC, CType.CSI, new COPSData(data, 0, data.length));
295         final COPSDecisionMsg decisionMsg = new COPSDecisionMsg(IPCMMClient.CLIENT_TYPE, new COPSHandle(new COPSData("123")),
296                 decisionMap, null, clientSD);
297         decisionMsg.writeData(socket);
298
299         final COPSMsg msg = COPSMsgParser.parseMessage(socket);
300         Assert.assertNotNull(msg);
301         Assert.assertEquals(decisionMsg, msg);
302     }
303
304     /**
305      * Attempts to create a gate against a CMTS, validates the results then attempts to delete it.
306      * @param service - the service used to connect to a CMTS for issuing requests
307      * @param scnName - the service class name (aka. gate name)
308      * @param srcAddr - the address to the CMTS subnet?
309      * @param dstAddr - the destination address
310      * @param direction - the gate direction
311      * @param cmAddrInet - the address to the cable modem to which the gate will be assigned
312      * @param gatePath - the path to the gate
313      * @param expGateSetMsgStart - the expected start of the gate set return message to be validated against
314      */
315     private void addRemoveValidateGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
316                                        final Ipv4Address dstAddr, final ServiceFlowDirection direction,
317                                        final InetAddress cmAddrInet, final String gatePath,
318                                        final String expGateSetMsgStart) {
319         connectToCmts(service);
320         addAndValidateGate(service, scnName, srcAddr, dstAddr, direction, cmAddrInet, gatePath, expGateSetMsgStart);
321         deleteGate(service, gatePath);
322     }
323
324     private void addInvalidGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
325             final Ipv4Address dstAddr, final ServiceFlowDirection direction,
326             final InetAddress cmAddrInet, final String gatePath,
327             final String expGateSetMsgStart) {
328         connectToCmts(service);
329         final int numRequestsBefore = service.gateRequests.size();
330         addAndValidateGate(service, scnName, srcAddr, dstAddr, direction, cmAddrInet, gatePath, expGateSetMsgStart);
331         assertEquals(numRequestsBefore, service.gateRequests.size());
332     }
333
334     private void connectToCmts(final PCMMService service) {
335         final String message = service.addCcap();
336         Assert.assertNotNull(message);
337         final String expectedMsg = "200 OK - CCAP " + ccapId + " connected @ "
338                 + ccap.getConnection().getIpAddress().getIpv4Address().getValue()
339                 + ":" + ccap.getConnection().getPort().getValue();
340         Assert.assertEquals(expectedMsg, message);
341         Assert.assertNotNull(service.ccapClient.pcmmPdp.getClientHandle());
342     }
343
344     /**
345      * Attempts to create a gate against a CMTS and validates the results.
346      * @param service - the service used to connect to a CMTS for issuing requests
347      * @param scnName - the service class name (aka. gate name)
348      * @param srcAddr - the address to the CMTS subnet?
349      * @param dstAddr - the destination address
350      * @param direction - the gate direction
351      * @param cmAddrInet - the address to the cable modem to which the gate will be assigned
352      * @param gatePath - the path to the gate
353      * @param expGateSetMsgStart - the expected start of the gate set return message to be validated against
354      */
355     private void addAndValidateGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
356                                     final Ipv4Address dstAddr, final ServiceFlowDirection direction,
357                                     final InetAddress cmAddrInet, final String gatePath,
358                                     final String expGateSetMsgStart) {
359         final Gate gate = makeGateObj(scnName, srcAddr, direction, dstAddr);
360
361 //        final String gateSetMsg = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
362 //        Assert.assertNotNull(gateSetMsg);
363 //        Assert.assertTrue(gateSetMsg, gateSetMsg.startsWith(expGateSetMsgStart));
364
365         // TODO update this method for the new GateSetStatus object
366         PCMMService.GateSetStatus status = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
367         Assert.assertNotNull(status);
368         Assert.assertTrue(status.getMessage().startsWith(expGateSetMsgStart));
369
370         // TODO - add validation to the PCMMGateReq contained within the map
371         if (status.didSucceed()) {
372             Assert.assertTrue((service.gateRequests.containsKey(gatePath)));
373             Assert.assertNotNull(service.gateRequests.get(gatePath));
374         }
375     }
376
377     /**
378      * Attempts to delete a gate
379      * @param service - the service used to connect to a CMTS for issuing requests
380      * @param gatePath - the path to the gate
381      */
382     private boolean deleteGate(final PCMMService service, final String gatePath) {
383         final boolean out = service.sendGateDelete(gatePath);
384
385         // Wait up to 1 sec for response to be processed
386         final long start = System.currentTimeMillis();
387         while (1000 < System.currentTimeMillis() - start) {
388             if (service.gateRequests.size() == 0) break;
389         }
390         Assert.assertNull(service.gateRequests.get(gatePath));
391         return out;
392     }
393
394     /**
395      * Creates a mock Ccap object that can be used for connecting to a CMTS
396      * @param inPort - the CMTS port number
397      * @param ipAddr - the CMTS IPv4 address string
398      * @param ccapId - the ID of the CCAP
399      * @return - the mock Ccap object
400      */
401     private Ccap makeCcapObj(final int inPort, final Ipv4Address ipAddr, final String ccapId) {
402         final Ccap ccap = mock(Ccap.class);
403         final Connection conn = mock(Connection.class);
404         when(ccap.getConnection()).thenReturn(conn);
405         final PortNumber port = mock(PortNumber.class);
406         when(conn.getPort()).thenReturn(port);
407         when(port.getValue()).thenReturn(inPort);
408
409         final IpAddress addr = mock(IpAddress.class);
410         when(conn.getIpAddress()).thenReturn(addr);
411         when(addr.getIpv4Address()).thenReturn(ipAddr);
412
413         when(ccap.getCcapId()).thenReturn(ccapId);
414         final AmId amid = mock(AmId.class);
415         when(ccap.getAmId()).thenReturn(amid);
416         when(amid.getAmTag()).thenReturn(0xcada);
417         when(amid.getAmType()).thenReturn(1);
418
419         return ccap;
420     }
421
422     /**
423      * Creates a mock Gate object
424      * @param scnValue - the service class name defined on the CMTS
425      * @param dstAddr - the CM address this gate should be set against
426      * @return - the gate request
427      */
428     private Gate makeGateObj(final String scnValue, final Ipv4Address srcAddr, final ServiceFlowDirection direction,
429                               final Ipv4Address dstAddr) {
430         final Gate gate = mock(Gate.class);
431         final GateSpec gateSpec = mock(GateSpec.class);
432         when(gate.getGateSpec()).thenReturn(gateSpec);
433         when(gateSpec.getDirection()).thenReturn(direction);
434         // TODO - make sure to write a test when this value is not null
435         when(gateSpec.getDscpTosOverwrite()).thenReturn(null);
436         final TrafficProfile trafficProfile = mock(TrafficProfile.class);
437         final ServiceClassName scn = mock(ServiceClassName.class);
438         when(scn.getValue()).thenReturn(scnValue);
439         when(trafficProfile.getServiceClassName()).thenReturn(scn);
440         when(gate.getTrafficProfile()).thenReturn(trafficProfile);
441
442         // TODO - write tests when this is null and ExtClassifier or Ipv6Classifier objects are not null
443         final Classifier classifier = mock(Classifier.class);
444
445         // This is the address of the CM
446         when(classifier.getDstIp()).thenReturn(dstAddr);
447
448         final PortNumber dstPort = new PortNumber(4321);
449         when(classifier.getDstPort()).thenReturn(dstPort);
450         final TpProtocol protocol = new TpProtocol(0);
451         when(classifier.getProtocol()).thenReturn(protocol);
452         when(classifier.getSrcIp()).thenReturn(srcAddr);
453         final PortNumber srcPort = new PortNumber(1234);
454         when(classifier.getSrcPort()).thenReturn(srcPort);
455
456         // TODO - Can this value be any other value than 0 or 1 (See TosByte enumeration)
457         final TosByte tosByte = new TosByte((short)0);
458         when(classifier.getTosByte()).thenReturn(tosByte);
459         final TosByte tosMask = new TosByte((short)224);
460         when(classifier.getTosMask()).thenReturn(tosMask);
461
462         final QosClassifierChoice classifierChoice = mock(QosClassifierChoice.class);
463         when(classifierChoice.getClassifier()).thenReturn(classifier);
464
465         ClassifierContainer classifierContainer = mock(ClassifierContainer.class);
466         when(classifierContainer.getClassifierChoice()).thenReturn(classifierChoice);
467
468         final List<ClassifierContainer> containerList = Collections.singletonList(classifierContainer);
469
470         Classifiers classifiers = mock(Classifiers.class);
471         when(classifiers.getClassifierContainer()).thenReturn(containerList);
472
473         when(gate.getClassifiers()).thenReturn(classifiers);
474
475         return gate;
476     }
477
478     private IPCMMGate makeGateRequest(final Ccap ccap, final Gate gateReq, final InetAddress addrSubId,
479                                      final ServiceFlowDirection direction) {
480         final PCMMGateReqBuilder gateBuilder = new PCMMGateReqBuilder();
481         gateBuilder.setAmId(ccap.getAmId());
482         gateBuilder.setSubscriberId(addrSubId);
483         // force gateSpec.Direction to align with SCN direction
484         final ServiceClassName scn = gateReq.getTrafficProfile().getServiceClassName();
485         if (scn != null) {
486             gateBuilder.setGateSpec(gateReq.getGateSpec(), direction);
487         } else {
488             // not an SCN gate
489             gateBuilder.setGateSpec(gateReq.getGateSpec(), null);
490         }
491         gateBuilder.setTrafficProfile(gateReq.getTrafficProfile());
492
493         gateBuilder.setClassifiers(gateReq.getClassifiers().getClassifierContainer());
494
495         // assemble the final gate request
496         return gateBuilder.build();
497     }
498
499     private class MockSocket extends Socket {
500
501         private ByteArrayOutputStream os = new ByteArrayOutputStream();
502         private ByteArrayInputStream is;
503
504         @Override
505         public OutputStream getOutputStream() {
506             return os;
507         }
508
509         @Override
510         public InputStream getInputStream() {
511             if (is == null) is = new ByteArrayInputStream(os.toByteArray());
512             return is;
513         }
514     }
515
516 }