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