1 /*
2  * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.jndi.dns;
27 
28 import java.io.IOException;
29 import java.net.DatagramSocket;
30 import java.net.DatagramPacket;
31 import java.net.InetAddress;
32 import java.net.Socket;
33 import java.security.SecureRandom;
34 import javax.naming.*;
35 
36 import java.util.Collections;
37 import java.util.Map;
38 import java.util.HashMap;
39 
40 import sun.security.jca.JCAUtil;
41 
42 // Some of this code began life as part of sun.javaos.net.DnsClient
43 // originally by sritchie@eng 1/96.  It was first hacked up for JNDI
44 // use by caveh@eng 6/97.
45 
46 
47 /**
48  * The DnsClient class performs DNS client operations in support of DnsContext.
49  *
50  */
51 
52 public class DnsClient {
53 
54     // DNS packet header field offsets
55     private static final int IDENT_OFFSET = 0;
56     private static final int FLAGS_OFFSET = 2;
57     private static final int NUMQ_OFFSET  = 4;
58     private static final int NUMANS_OFFSET = 6;
59     private static final int NUMAUTH_OFFSET = 8;
60     private static final int NUMADD_OFFSET = 10;
61     private static final int DNS_HDR_SIZE = 12;
62 
63     // DNS response codes
64     private static final int NO_ERROR       = 0;
65     private static final int FORMAT_ERROR   = 1;
66     private static final int SERVER_FAILURE = 2;
67     private static final int NAME_ERROR     = 3;
68     private static final int NOT_IMPL       = 4;
69     private static final int REFUSED        = 5;
70 
71     private static final String[] rcodeDescription = {
72         "No error",
73         "DNS format error",
74         "DNS server failure",
75         "DNS name not found",
76         "DNS operation not supported",
77         "DNS service refused"
78     };
79 
80     private static final int DEFAULT_PORT = 53;
81     private static final int TRANSACTION_ID_BOUND = 0x10000;
82     private static final SecureRandom random = JCAUtil.getSecureRandom();
83     private InetAddress[] servers;
84     private int[] serverPorts;
85     private int timeout;                // initial timeout on UDP queries in ms
86     private int retries;                // number of UDP retries
87 
88     private final Object udpSocketLock = new Object();
89     private static final DNSDatagramSocketFactory factory =
90             new DNSDatagramSocketFactory(random);
91 
92     // Requests sent
93     private Map<Integer, ResourceRecord> reqs;
94 
95     // Responses received
96     private Map<Integer, byte[]> resps;
97 
98     //-------------------------------------------------------------------------
99 
100     /*
101      * Each server is of the form "server[:port]".  IPv6 literal host names
102      * include delimiting brackets.
103      * "timeout" is the initial timeout interval (in ms) for UDP queries,
104      * and "retries" gives the number of retries per server.
105      */
DnsClient(String[] servers, int timeout, int retries)106     public DnsClient(String[] servers, int timeout, int retries)
107             throws NamingException {
108         this.timeout = timeout;
109         this.retries = retries;
110         this.servers = new InetAddress[servers.length];
111         serverPorts = new int[servers.length];
112 
113         for (int i = 0; i < servers.length; i++) {
114 
115             // Is optional port given?
116             int colon = servers[i].indexOf(':',
117                                            servers[i].indexOf(']') + 1);
118 
119             serverPorts[i] = (colon < 0)
120                 ? DEFAULT_PORT
121                 : Integer.parseInt(servers[i].substring(colon + 1));
122             String server = (colon < 0)
123                 ? servers[i]
124                 : servers[i].substring(0, colon);
125             try {
126                 this.servers[i] = InetAddress.getByName(server);
127             } catch (java.net.UnknownHostException e) {
128                 NamingException ne = new ConfigurationException(
129                         "Unknown DNS server: " + server);
130                 ne.setRootCause(e);
131                 throw ne;
132             }
133         }
134         reqs = Collections.synchronizedMap(
135             new HashMap<Integer, ResourceRecord>());
136         resps = Collections.synchronizedMap(new HashMap<Integer, byte[]>());
137     }
138 
getDatagramSocket()139     DatagramSocket getDatagramSocket() throws NamingException {
140         try {
141             return factory.open();
142         } catch (java.net.SocketException e) {
143             NamingException ne = new ConfigurationException();
144             ne.setRootCause(e);
145             throw ne;
146         }
147     }
148 
finalize()149     protected void finalize() {
150         close();
151     }
152 
153     // A lock to access the request and response queues in tandem.
154     private Object queuesLock = new Object();
155 
close()156     public void close() {
157         synchronized (queuesLock) {
158             reqs.clear();
159             resps.clear();
160         }
161     }
162 
163     /*
164      * If recursion is true, recursion is requested on the query.
165      * If auth is true, only authoritative responses are accepted; other
166      * responses throw NameNotFoundException.
167      */
query(DnsName fqdn, int qclass, int qtype, boolean recursion, boolean auth)168     ResourceRecords query(DnsName fqdn, int qclass, int qtype,
169                           boolean recursion, boolean auth)
170             throws NamingException {
171 
172         int xid;
173         Packet pkt;
174         ResourceRecord collision;
175 
176         do {
177             // Generate a random transaction ID
178             xid = random.nextInt(TRANSACTION_ID_BOUND);
179             pkt = makeQueryPacket(fqdn, xid, qclass, qtype, recursion);
180 
181             // enqueue the outstanding request
182             collision = reqs.putIfAbsent(xid, new ResourceRecord(pkt.getData(),
183                 pkt.length(), Header.HEADER_SIZE, true, false));
184 
185         } while (collision != null);
186 
187         Exception caughtException = null;
188         boolean[] doNotRetry = new boolean[servers.length];
189 
190         try {
191             //
192             // The UDP retry strategy is to try the 1st server, and then
193             // each server in order. If no answer, double the timeout
194             // and try each server again.
195             //
196             for (int retry = 0; retry < retries; retry++) {
197 
198                 // Try each name server.
199                 for (int i = 0; i < servers.length; i++) {
200                     if (doNotRetry[i]) {
201                         continue;
202                     }
203 
204                     // send the request packet and wait for a response.
205                     try {
206                         if (debug) {
207                             dprint("SEND ID (" + (retry + 1) + "): " + xid);
208                         }
209 
210                         byte[] msg = null;
211                         msg = doUdpQuery(pkt, servers[i], serverPorts[i],
212                                             retry, xid);
213                         //
214                         // If the matching response is not got within the
215                         // given timeout, check if the response was enqueued
216                         // by some other thread, if not proceed with the next
217                         // server or retry.
218                         //
219                         if (msg == null) {
220                             if (resps.size() > 0) {
221                                 msg = lookupResponse(xid);
222                             }
223                             if (msg == null) { // try next server or retry
224                                 continue;
225                             }
226                         }
227                         Header hdr = new Header(msg, msg.length);
228 
229                         if (auth && !hdr.authoritative) {
230                             caughtException = new NameNotFoundException(
231                                     "DNS response not authoritative");
232                             doNotRetry[i] = true;
233                             continue;
234                         }
235                         if (hdr.truncated) {  // message is truncated -- try TCP
236 
237                             // Try each server, starting with the one that just
238                             // provided the truncated message.
239                             for (int j = 0; j < servers.length; j++) {
240                                 int ij = (i + j) % servers.length;
241                                 if (doNotRetry[ij]) {
242                                     continue;
243                                 }
244                                 try {
245                                     Tcp tcp =
246                                         new Tcp(servers[ij], serverPorts[ij]);
247                                     byte[] msg2;
248                                     try {
249                                         msg2 = doTcpQuery(tcp, pkt);
250                                     } finally {
251                                         tcp.close();
252                                     }
253                                     Header hdr2 = new Header(msg2, msg2.length);
254                                     if (hdr2.query) {
255                                         throw new CommunicationException(
256                                             "DNS error: expecting response");
257                                     }
258                                     checkResponseCode(hdr2);
259 
260                                     if (!auth || hdr2.authoritative) {
261                                         // Got a valid response
262                                         hdr = hdr2;
263                                         msg = msg2;
264                                         break;
265                                     } else {
266                                         doNotRetry[ij] = true;
267                                     }
268                                 } catch (Exception e) {
269                                     // Try next server, or use UDP response
270                                 }
271                             } // servers
272                         }
273                         return new ResourceRecords(msg, msg.length, hdr, false);
274 
275                     } catch (IOException e) {
276                         if (debug) {
277                             dprint("Caught IOException:" + e);
278                         }
279                         if (caughtException == null) {
280                             caughtException = e;
281                         }
282                         // Use reflection to allow pre-1.4 compilation.
283                         // This won't be needed much longer.
284                         if (e.getClass().getName().equals(
285                                 "java.net.PortUnreachableException")) {
286                             doNotRetry[i] = true;
287                         }
288                     } catch (NameNotFoundException e) {
289                         // This is authoritative, so return immediately
290                         throw e;
291                     } catch (CommunicationException e) {
292                         if (caughtException == null) {
293                             caughtException = e;
294                         }
295                     } catch (NamingException e) {
296                         if (caughtException == null) {
297                             caughtException = e;
298                         }
299                         doNotRetry[i] = true;
300                     }
301                 } // servers
302             } // retries
303 
304         } finally {
305             reqs.remove(xid); // cleanup
306         }
307 
308         if (caughtException instanceof NamingException) {
309             throw (NamingException) caughtException;
310         }
311         // A network timeout or other error occurred.
312         NamingException ne = new CommunicationException("DNS error");
313         ne.setRootCause(caughtException);
314         throw ne;
315     }
316 
queryZone(DnsName zone, int qclass, boolean recursion)317     ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)
318             throws NamingException {
319 
320         int xid = random.nextInt(TRANSACTION_ID_BOUND);
321 
322         Packet pkt = makeQueryPacket(zone, xid, qclass,
323                                      ResourceRecord.QTYPE_AXFR, recursion);
324         Exception caughtException = null;
325 
326         // Try each name server.
327         for (int i = 0; i < servers.length; i++) {
328             try {
329                 Tcp tcp = new Tcp(servers[i], serverPorts[i]);
330                 byte[] msg;
331                 try {
332                     msg = doTcpQuery(tcp, pkt);
333                     Header hdr = new Header(msg, msg.length);
334                     // Check only rcode as per
335                     // draft-ietf-dnsext-axfr-clarify-04
336                     checkResponseCode(hdr);
337                     ResourceRecords rrs =
338                         new ResourceRecords(msg, msg.length, hdr, true);
339                     if (rrs.getFirstAnsType() != ResourceRecord.TYPE_SOA) {
340                         throw new CommunicationException(
341                                 "DNS error: zone xfer doesn't begin with SOA");
342                     }
343 
344                     if (rrs.answer.size() == 1 ||
345                             rrs.getLastAnsType() != ResourceRecord.TYPE_SOA) {
346                         // The response is split into multiple DNS messages.
347                         do {
348                             msg = continueTcpQuery(tcp);
349                             if (msg == null) {
350                                 throw new CommunicationException(
351                                         "DNS error: incomplete zone transfer");
352                             }
353                             hdr = new Header(msg, msg.length);
354                             checkResponseCode(hdr);
355                             rrs.add(msg, msg.length, hdr);
356                         } while (rrs.getLastAnsType() !=
357                                  ResourceRecord.TYPE_SOA);
358                     }
359 
360                     // Delete the duplicate SOA record.
361                     rrs.answer.removeElementAt(rrs.answer.size() - 1);
362                     return rrs;
363 
364                 } finally {
365                     tcp.close();
366                 }
367 
368             } catch (IOException e) {
369                 caughtException = e;
370             } catch (NameNotFoundException e) {
371                 throw e;
372             } catch (NamingException e) {
373                 caughtException = e;
374             }
375         }
376         if (caughtException instanceof NamingException) {
377             throw (NamingException) caughtException;
378         }
379         NamingException ne = new CommunicationException(
380                 "DNS error during zone transfer");
381         ne.setRootCause(caughtException);
382         throw ne;
383     }
384 
385 
386     /**
387      * Tries to retreive an UDP packet matching the given xid
388      * received within the timeout.
389      * If a packet with different xid is received, the received packet
390      * is enqueued with the corresponding xid in 'resps'.
391      */
doUdpQuery(Packet pkt, InetAddress server, int port, int retry, int xid)392     private byte[] doUdpQuery(Packet pkt, InetAddress server,
393                                      int port, int retry, int xid)
394             throws IOException, NamingException {
395 
396         int minTimeout = 50; // msec after which there are no retries.
397 
398         synchronized (udpSocketLock) {
399             try (DatagramSocket udpSocket = getDatagramSocket()) {
400                 DatagramPacket opkt = new DatagramPacket(
401                         pkt.getData(), pkt.length(), server, port);
402                 DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
403                 // Packets may only be sent to or received from this server address
404                 udpSocket.connect(server, port);
405                 int pktTimeout = (timeout * (1 << retry));
406                 try {
407                     udpSocket.send(opkt);
408 
409                     // timeout remaining after successive 'receive()'
410                     int timeoutLeft = pktTimeout;
411                     int cnt = 0;
412                     do {
413                         if (debug) {
414                            cnt++;
415                             dprint("Trying RECEIVE(" +
416                                     cnt + ") retry(" + (retry + 1) +
417                                     ") for:" + xid  + "    sock-timeout:" +
418                                     timeoutLeft + " ms.");
419                         }
420                         udpSocket.setSoTimeout(timeoutLeft);
421                         long start = System.currentTimeMillis();
422                         udpSocket.receive(ipkt);
423                         long end = System.currentTimeMillis();
424 
425                         byte[] data = ipkt.getData();
426                         if (isMatchResponse(data, xid)) {
427                             return data;
428                         }
429                         timeoutLeft = pktTimeout - ((int) (end - start));
430                     } while (timeoutLeft > minTimeout);
431 
432                 } finally {
433                     udpSocket.disconnect();
434                 }
435                 return null; // no matching packet received within the timeout
436             }
437         }
438     }
439 
440     /*
441      * Sends a TCP query, and returns the first DNS message in the response.
442      */
doTcpQuery(Tcp tcp, Packet pkt)443     private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
444 
445         int len = pkt.length();
446         // Send 2-byte message length, then send message.
447         tcp.out.write(len >> 8);
448         tcp.out.write(len);
449         tcp.out.write(pkt.getData(), 0, len);
450         tcp.out.flush();
451 
452         byte[] msg = continueTcpQuery(tcp);
453         if (msg == null) {
454             throw new IOException("DNS error: no response");
455         }
456         return msg;
457     }
458 
459     /*
460      * Returns the next DNS message from the TCP socket, or null on EOF.
461      */
continueTcpQuery(Tcp tcp)462     private byte[] continueTcpQuery(Tcp tcp) throws IOException {
463 
464         int lenHi = tcp.in.read();      // high-order byte of response length
465         if (lenHi == -1) {
466             return null;        // EOF
467         }
468         int lenLo = tcp.in.read();      // low-order byte of response length
469         if (lenLo == -1) {
470             throw new IOException("Corrupted DNS response: bad length");
471         }
472         int len = (lenHi << 8) | lenLo;
473         byte[] msg = new byte[len];
474         int pos = 0;                    // next unfilled position in msg
475         while (len > 0) {
476             int n = tcp.in.read(msg, pos, len);
477             if (n == -1) {
478                 throw new IOException(
479                         "Corrupted DNS response: too little data");
480             }
481             len -= n;
482             pos += n;
483         }
484         return msg;
485     }
486 
makeQueryPacket(DnsName fqdn, int xid, int qclass, int qtype, boolean recursion)487     private Packet makeQueryPacket(DnsName fqdn, int xid,
488                                    int qclass, int qtype, boolean recursion) {
489         int qnameLen = fqdn.getOctets();
490         int pktLen = DNS_HDR_SIZE + qnameLen + 4;
491         Packet pkt = new Packet(pktLen);
492 
493         short flags = recursion ? Header.RD_BIT : 0;
494 
495         pkt.putShort(xid, IDENT_OFFSET);
496         pkt.putShort(flags, FLAGS_OFFSET);
497         pkt.putShort(1, NUMQ_OFFSET);
498         pkt.putShort(0, NUMANS_OFFSET);
499         pkt.putInt(0, NUMAUTH_OFFSET);
500 
501         makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
502         pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
503         pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
504 
505         return pkt;
506     }
507 
508     // Builds a query name in pkt according to the RFC spec.
makeQueryName(DnsName fqdn, Packet pkt, int off)509     private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
510 
511         // Loop through labels, least-significant first.
512         for (int i = fqdn.size() - 1; i >= 0; i--) {
513             String label = fqdn.get(i);
514             int len = label.length();
515 
516             pkt.putByte(len, off++);
517             for (int j = 0; j < len; j++) {
518                 pkt.putByte(label.charAt(j), off++);
519             }
520         }
521         if (!fqdn.hasRootLabel()) {
522             pkt.putByte(0, off);
523         }
524     }
525 
526     //-------------------------------------------------------------------------
527 
lookupResponse(Integer xid)528     private byte[] lookupResponse(Integer xid) throws NamingException {
529         //
530         // Check the queued responses: some other thread in between
531         // received the response for this request.
532         //
533         if (debug) {
534             dprint("LOOKUP for: " + xid +
535                 "\tResponse Q:" + resps);
536         }
537         byte[] pkt;
538         if ((pkt = resps.get(xid)) != null) {
539             checkResponseCode(new Header(pkt, pkt.length));
540             synchronized (queuesLock) {
541                 resps.remove(xid);
542                 reqs.remove(xid);
543             }
544 
545             if (debug) {
546                 dprint("FOUND (" + Thread.currentThread() +
547                     ") for:" + xid);
548             }
549         }
550         return pkt;
551     }
552 
553     /*
554      * Checks the header of an incoming DNS response.
555      * Returns true if it matches the given xid and throws a naming
556      * exception, if appropriate, based on the response code.
557      *
558      * Also checks that the domain name, type and class in the response
559      * match those in the original query.
560      */
isMatchResponse(byte[] pkt, int xid)561     private boolean isMatchResponse(byte[] pkt, int xid)
562                 throws NamingException {
563 
564         Header hdr = new Header(pkt, pkt.length);
565         if (hdr.query) {
566             throw new CommunicationException("DNS error: expecting response");
567         }
568 
569         if (!reqs.containsKey(xid)) { // already received, ignore the response
570             return false;
571         }
572 
573         // common case- the request sent matches the subsequent response read
574         if (hdr.xid == xid) {
575             if (debug) {
576                 dprint("XID MATCH:" + xid);
577             }
578             checkResponseCode(hdr);
579             if (!hdr.query && hdr.numQuestions == 1) {
580 
581                 ResourceRecord rr = new ResourceRecord(pkt, pkt.length,
582                     Header.HEADER_SIZE, true, false);
583 
584                 // Retrieve the original query
585                 ResourceRecord query = reqs.get(xid);
586                 int qtype = query.getType();
587                 int qclass = query.getRrclass();
588                 DnsName qname = query.getName();
589 
590                 // Check that the type/class/name in the query section of the
591                 // response match those in the original query
592                 if ((qtype == ResourceRecord.QTYPE_STAR ||
593                     qtype == rr.getType()) &&
594                     (qclass == ResourceRecord.QCLASS_STAR ||
595                     qclass == rr.getRrclass()) &&
596                     qname.equals(rr.getName())) {
597 
598                     if (debug) {
599                         dprint("MATCH NAME:" + qname + " QTYPE:" + qtype +
600                             " QCLASS:" + qclass);
601                     }
602 
603                     // Remove the response for the xid if received by some other
604                     // thread.
605                     synchronized (queuesLock) {
606                         resps.remove(xid);
607                         reqs.remove(xid);
608                     }
609                     return true;
610 
611                 } else {
612                     if (debug) {
613                         dprint("NO-MATCH NAME:" + qname + " QTYPE:" + qtype +
614                             " QCLASS:" + qclass);
615                     }
616                 }
617             }
618             return false;
619         }
620 
621         //
622         // xid mis-match: enqueue the response, it may belong to some other
623         // thread that has not yet had a chance to read its response.
624         // enqueue only the first response, responses for retries are ignored.
625         //
626         synchronized (queuesLock) {
627             if (reqs.containsKey(hdr.xid)) { // enqueue only the first response
628                 resps.put(hdr.xid, pkt);
629             }
630         }
631 
632         if (debug) {
633             dprint("NO-MATCH SEND ID:" +
634                                 xid + " RECVD ID:" + hdr.xid +
635                                 "    Response Q:" + resps +
636                                 "    Reqs size:" + reqs.size());
637         }
638         return false;
639     }
640 
641     /*
642      * Throws an exception if appropriate for the response code of a
643      * given header.
644      */
checkResponseCode(Header hdr)645     private void checkResponseCode(Header hdr) throws NamingException {
646 
647         int rcode = hdr.rcode;
648         if (rcode == NO_ERROR) {
649             return;
650         }
651         String msg = (rcode < rcodeDescription.length)
652             ? rcodeDescription[rcode]
653             : "DNS error";
654         msg += " [response code " + rcode + "]";
655 
656         switch (rcode) {
657         case SERVER_FAILURE:
658             throw new ServiceUnavailableException(msg);
659         case NAME_ERROR:
660             throw new NameNotFoundException(msg);
661         case NOT_IMPL:
662         case REFUSED:
663             throw new OperationNotSupportedException(msg);
664         case FORMAT_ERROR:
665         default:
666             throw new NamingException(msg);
667         }
668     }
669 
670     //-------------------------------------------------------------------------
671 
672     private static final boolean debug = false;
673 
dprint(String mess)674     private static void dprint(String mess) {
675         if (debug) {
676             System.err.println("DNS: " + mess);
677         }
678     }
679 
680 }
681 
682 class Tcp {
683 
684     private Socket sock;
685     java.io.InputStream in;
686     java.io.OutputStream out;
687 
Tcp(InetAddress server, int port)688     Tcp(InetAddress server, int port) throws IOException {
689         sock = new Socket(server, port);
690         sock.setTcpNoDelay(true);
691         out = new java.io.BufferedOutputStream(sock.getOutputStream());
692         in = new java.io.BufferedInputStream(sock.getInputStream());
693     }
694 
close()695     void close() throws IOException {
696         sock.close();
697     }
698 }
699 
700 /*
701  * javaos emulation -cj
702  */
703 class Packet {
704         byte buf[];
705 
Packet(int len)706         Packet(int len) {
707                 buf = new byte[len];
708         }
709 
Packet(byte data[], int len)710         Packet(byte data[], int len) {
711                 buf = new byte[len];
712                 System.arraycopy(data, 0, buf, 0, len);
713         }
714 
putInt(int x, int off)715         void putInt(int x, int off) {
716                 buf[off + 0] = (byte)(x >> 24);
717                 buf[off + 1] = (byte)(x >> 16);
718                 buf[off + 2] = (byte)(x >> 8);
719                 buf[off + 3] = (byte)x;
720         }
721 
putShort(int x, int off)722         void putShort(int x, int off) {
723                 buf[off + 0] = (byte)(x >> 8);
724                 buf[off + 1] = (byte)x;
725         }
726 
putByte(int x, int off)727         void putByte(int x, int off) {
728                 buf[off] = (byte)x;
729         }
730 
putBytes(byte src[], int src_offset, int dst_offset, int len)731         void putBytes(byte src[], int src_offset, int dst_offset, int len) {
732                 System.arraycopy(src, src_offset, buf, dst_offset, len);
733         }
734 
length()735         int length() {
736                 return buf.length;
737         }
738 
getData()739         byte[] getData() {
740                 return buf;
741         }
742 }
743