1 /*
2  * Copyright (c) 2000, 2017, 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 
149     @SuppressWarnings("deprecation")
finalize()150     protected void finalize() {
151         close();
152     }
153 
154     // A lock to access the request and response queues in tandem.
155     private Object queuesLock = new Object();
156 
close()157     public void close() {
158         synchronized (queuesLock) {
159             reqs.clear();
160             resps.clear();
161         }
162     }
163 
164     /*
165      * If recursion is true, recursion is requested on the query.
166      * If auth is true, only authoritative responses are accepted; other
167      * responses throw NameNotFoundException.
168      */
query(DnsName fqdn, int qclass, int qtype, boolean recursion, boolean auth)169     ResourceRecords query(DnsName fqdn, int qclass, int qtype,
170                           boolean recursion, boolean auth)
171             throws NamingException {
172 
173         int xid;
174         Packet pkt;
175         ResourceRecord collision;
176 
177         do {
178             // Generate a random transaction ID
179             xid = random.nextInt(TRANSACTION_ID_BOUND);
180             pkt = makeQueryPacket(fqdn, xid, qclass, qtype, recursion);
181 
182             // enqueue the outstanding request
183             collision = reqs.putIfAbsent(xid, new ResourceRecord(pkt.getData(),
184                 pkt.length(), Header.HEADER_SIZE, true, false));
185 
186         } while (collision != null);
187 
188         Exception caughtException = null;
189         boolean[] doNotRetry = new boolean[servers.length];
190 
191         try {
192             //
193             // The UDP retry strategy is to try the 1st server, and then
194             // each server in order. If no answer, double the timeout
195             // and try each server again.
196             //
197             for (int retry = 0; retry < retries; retry++) {
198 
199                 // Try each name server.
200                 for (int i = 0; i < servers.length; i++) {
201                     if (doNotRetry[i]) {
202                         continue;
203                     }
204 
205                     // send the request packet and wait for a response.
206                     try {
207                         if (debug) {
208                             dprint("SEND ID (" + (retry + 1) + "): " + xid);
209                         }
210 
211                         byte[] msg = null;
212                         msg = doUdpQuery(pkt, servers[i], serverPorts[i],
213                                             retry, xid);
214                         //
215                         // If the matching response is not got within the
216                         // given timeout, check if the response was enqueued
217                         // by some other thread, if not proceed with the next
218                         // server or retry.
219                         //
220                         if (msg == null) {
221                             if (resps.size() > 0) {
222                                 msg = lookupResponse(xid);
223                             }
224                             if (msg == null) { // try next server or retry
225                                 continue;
226                             }
227                         }
228                         Header hdr = new Header(msg, msg.length);
229 
230                         if (auth && !hdr.authoritative) {
231                             caughtException = new NameNotFoundException(
232                                     "DNS response not authoritative");
233                             doNotRetry[i] = true;
234                             continue;
235                         }
236                         if (hdr.truncated) {  // message is truncated -- try TCP
237 
238                             // Try each server, starting with the one that just
239                             // provided the truncated message.
240                             for (int j = 0; j < servers.length; j++) {
241                                 int ij = (i + j) % servers.length;
242                                 if (doNotRetry[ij]) {
243                                     continue;
244                                 }
245                                 try {
246                                     Tcp tcp =
247                                         new Tcp(servers[ij], serverPorts[ij]);
248                                     byte[] msg2;
249                                     try {
250                                         msg2 = doTcpQuery(tcp, pkt);
251                                     } finally {
252                                         tcp.close();
253                                     }
254                                     Header hdr2 = new Header(msg2, msg2.length);
255                                     if (hdr2.query) {
256                                         throw new CommunicationException(
257                                             "DNS error: expecting response");
258                                     }
259                                     checkResponseCode(hdr2);
260 
261                                     if (!auth || hdr2.authoritative) {
262                                         // Got a valid response
263                                         hdr = hdr2;
264                                         msg = msg2;
265                                         break;
266                                     } else {
267                                         doNotRetry[ij] = true;
268                                     }
269                                 } catch (Exception e) {
270                                     // Try next server, or use UDP response
271                                 }
272                             } // servers
273                         }
274                         return new ResourceRecords(msg, msg.length, hdr, false);
275 
276                     } catch (IOException e) {
277                         if (debug) {
278                             dprint("Caught IOException:" + e);
279                         }
280                         if (caughtException == null) {
281                             caughtException = e;
282                         }
283                         // Use reflection to allow pre-1.4 compilation.
284                         // This won't be needed much longer.
285                         if (e.getClass().getName().equals(
286                                 "java.net.PortUnreachableException")) {
287                             doNotRetry[i] = true;
288                         }
289                     } catch (NameNotFoundException e) {
290                         // This is authoritative, so return immediately
291                         throw e;
292                     } catch (CommunicationException e) {
293                         if (caughtException == null) {
294                             caughtException = e;
295                         }
296                     } catch (NamingException e) {
297                         if (caughtException == null) {
298                             caughtException = e;
299                         }
300                         doNotRetry[i] = true;
301                     }
302                 } // servers
303             } // retries
304 
305         } finally {
306             reqs.remove(xid); // cleanup
307         }
308 
309         if (caughtException instanceof NamingException) {
310             throw (NamingException) caughtException;
311         }
312         // A network timeout or other error occurred.
313         NamingException ne = new CommunicationException("DNS error");
314         ne.setRootCause(caughtException);
315         throw ne;
316     }
317 
queryZone(DnsName zone, int qclass, boolean recursion)318     ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)
319             throws NamingException {
320 
321         int xid = random.nextInt(TRANSACTION_ID_BOUND);
322 
323         Packet pkt = makeQueryPacket(zone, xid, qclass,
324                                      ResourceRecord.QTYPE_AXFR, recursion);
325         Exception caughtException = null;
326 
327         // Try each name server.
328         for (int i = 0; i < servers.length; i++) {
329             try {
330                 Tcp tcp = new Tcp(servers[i], serverPorts[i]);
331                 byte[] msg;
332                 try {
333                     msg = doTcpQuery(tcp, pkt);
334                     Header hdr = new Header(msg, msg.length);
335                     // Check only rcode as per
336                     // draft-ietf-dnsext-axfr-clarify-04
337                     checkResponseCode(hdr);
338                     ResourceRecords rrs =
339                         new ResourceRecords(msg, msg.length, hdr, true);
340                     if (rrs.getFirstAnsType() != ResourceRecord.TYPE_SOA) {
341                         throw new CommunicationException(
342                                 "DNS error: zone xfer doesn't begin with SOA");
343                     }
344 
345                     if (rrs.answer.size() == 1 ||
346                             rrs.getLastAnsType() != ResourceRecord.TYPE_SOA) {
347                         // The response is split into multiple DNS messages.
348                         do {
349                             msg = continueTcpQuery(tcp);
350                             if (msg == null) {
351                                 throw new CommunicationException(
352                                         "DNS error: incomplete zone transfer");
353                             }
354                             hdr = new Header(msg, msg.length);
355                             checkResponseCode(hdr);
356                             rrs.add(msg, msg.length, hdr);
357                         } while (rrs.getLastAnsType() !=
358                                  ResourceRecord.TYPE_SOA);
359                     }
360 
361                     // Delete the duplicate SOA record.
362                     rrs.answer.removeElementAt(rrs.answer.size() - 1);
363                     return rrs;
364 
365                 } finally {
366                     tcp.close();
367                 }
368 
369             } catch (IOException e) {
370                 caughtException = e;
371             } catch (NameNotFoundException e) {
372                 throw e;
373             } catch (NamingException e) {
374                 caughtException = e;
375             }
376         }
377         if (caughtException instanceof NamingException) {
378             throw (NamingException) caughtException;
379         }
380         NamingException ne = new CommunicationException(
381                 "DNS error during zone transfer");
382         ne.setRootCause(caughtException);
383         throw ne;
384     }
385 
386 
387     /**
388      * Tries to retrieve a UDP packet matching the given xid
389      * received within the timeout.
390      * If a packet with different xid is received, the received packet
391      * is enqueued with the corresponding xid in 'resps'.
392      */
doUdpQuery(Packet pkt, InetAddress server, int port, int retry, int xid)393     private byte[] doUdpQuery(Packet pkt, InetAddress server,
394                                      int port, int retry, int xid)
395             throws IOException, NamingException {
396 
397         int minTimeout = 50; // msec after which there are no retries.
398 
399         synchronized (udpSocketLock) {
400             try (DatagramSocket udpSocket = getDatagramSocket()) {
401                 DatagramPacket opkt = new DatagramPacket(
402                         pkt.getData(), pkt.length(), server, port);
403                 DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
404                 // Packets may only be sent to or received from this server address
405                 udpSocket.connect(server, port);
406                 int pktTimeout = (timeout * (1 << retry));
407                 try {
408                     udpSocket.send(opkt);
409 
410                     // timeout remaining after successive 'receive()'
411                     int timeoutLeft = pktTimeout;
412                     int cnt = 0;
413                     do {
414                         if (debug) {
415                            cnt++;
416                             dprint("Trying RECEIVE(" +
417                                     cnt + ") retry(" + (retry + 1) +
418                                     ") for:" + xid  + "    sock-timeout:" +
419                                     timeoutLeft + " ms.");
420                         }
421                         udpSocket.setSoTimeout(timeoutLeft);
422                         long start = System.currentTimeMillis();
423                         udpSocket.receive(ipkt);
424                         long end = System.currentTimeMillis();
425 
426                         byte[] data = ipkt.getData();
427                         if (isMatchResponse(data, xid)) {
428                             return data;
429                         }
430                         timeoutLeft = pktTimeout - ((int) (end - start));
431                     } while (timeoutLeft > minTimeout);
432 
433                 } finally {
434                     udpSocket.disconnect();
435                 }
436                 return null; // no matching packet received within the timeout
437             }
438         }
439     }
440 
441     /*
442      * Sends a TCP query, and returns the first DNS message in the response.
443      */
doTcpQuery(Tcp tcp, Packet pkt)444     private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
445 
446         int len = pkt.length();
447         // Send 2-byte message length, then send message.
448         tcp.out.write(len >> 8);
449         tcp.out.write(len);
450         tcp.out.write(pkt.getData(), 0, len);
451         tcp.out.flush();
452 
453         byte[] msg = continueTcpQuery(tcp);
454         if (msg == null) {
455             throw new IOException("DNS error: no response");
456         }
457         return msg;
458     }
459 
460     /*
461      * Returns the next DNS message from the TCP socket, or null on EOF.
462      */
continueTcpQuery(Tcp tcp)463     private byte[] continueTcpQuery(Tcp tcp) throws IOException {
464 
465         int lenHi = tcp.in.read();      // high-order byte of response length
466         if (lenHi == -1) {
467             return null;        // EOF
468         }
469         int lenLo = tcp.in.read();      // low-order byte of response length
470         if (lenLo == -1) {
471             throw new IOException("Corrupted DNS response: bad length");
472         }
473         int len = (lenHi << 8) | lenLo;
474         byte[] msg = new byte[len];
475         int pos = 0;                    // next unfilled position in msg
476         while (len > 0) {
477             int n = tcp.in.read(msg, pos, len);
478             if (n == -1) {
479                 throw new IOException(
480                         "Corrupted DNS response: too little data");
481             }
482             len -= n;
483             pos += n;
484         }
485         return msg;
486     }
487 
makeQueryPacket(DnsName fqdn, int xid, int qclass, int qtype, boolean recursion)488     private Packet makeQueryPacket(DnsName fqdn, int xid,
489                                    int qclass, int qtype, boolean recursion) {
490         int qnameLen = fqdn.getOctets();
491         int pktLen = DNS_HDR_SIZE + qnameLen + 4;
492         Packet pkt = new Packet(pktLen);
493 
494         short flags = recursion ? Header.RD_BIT : 0;
495 
496         pkt.putShort(xid, IDENT_OFFSET);
497         pkt.putShort(flags, FLAGS_OFFSET);
498         pkt.putShort(1, NUMQ_OFFSET);
499         pkt.putShort(0, NUMANS_OFFSET);
500         pkt.putInt(0, NUMAUTH_OFFSET);
501 
502         makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
503         pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
504         pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
505 
506         return pkt;
507     }
508 
509     // Builds a query name in pkt according to the RFC spec.
makeQueryName(DnsName fqdn, Packet pkt, int off)510     private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
511 
512         // Loop through labels, least-significant first.
513         for (int i = fqdn.size() - 1; i >= 0; i--) {
514             String label = fqdn.get(i);
515             int len = label.length();
516 
517             pkt.putByte(len, off++);
518             for (int j = 0; j < len; j++) {
519                 pkt.putByte(label.charAt(j), off++);
520             }
521         }
522         if (!fqdn.hasRootLabel()) {
523             pkt.putByte(0, off);
524         }
525     }
526 
527     //-------------------------------------------------------------------------
528 
lookupResponse(Integer xid)529     private byte[] lookupResponse(Integer xid) throws NamingException {
530         //
531         // Check the queued responses: some other thread in between
532         // received the response for this request.
533         //
534         if (debug) {
535             dprint("LOOKUP for: " + xid +
536                 "\tResponse Q:" + resps);
537         }
538         byte[] pkt;
539         if ((pkt = resps.get(xid)) != null) {
540             checkResponseCode(new Header(pkt, pkt.length));
541             synchronized (queuesLock) {
542                 resps.remove(xid);
543                 reqs.remove(xid);
544             }
545 
546             if (debug) {
547                 dprint("FOUND (" + Thread.currentThread() +
548                     ") for:" + xid);
549             }
550         }
551         return pkt;
552     }
553 
554     /*
555      * Checks the header of an incoming DNS response.
556      * Returns true if it matches the given xid and throws a naming
557      * exception, if appropriate, based on the response code.
558      *
559      * Also checks that the domain name, type and class in the response
560      * match those in the original query.
561      */
isMatchResponse(byte[] pkt, int xid)562     private boolean isMatchResponse(byte[] pkt, int xid)
563                 throws NamingException {
564 
565         Header hdr = new Header(pkt, pkt.length);
566         if (hdr.query) {
567             throw new CommunicationException("DNS error: expecting response");
568         }
569 
570         if (!reqs.containsKey(xid)) { // already received, ignore the response
571             return false;
572         }
573 
574         // common case- the request sent matches the subsequent response read
575         if (hdr.xid == xid) {
576             if (debug) {
577                 dprint("XID MATCH:" + xid);
578             }
579             checkResponseCode(hdr);
580             if (!hdr.query && hdr.numQuestions == 1) {
581 
582                 ResourceRecord rr = new ResourceRecord(pkt, pkt.length,
583                     Header.HEADER_SIZE, true, false);
584 
585                 // Retrieve the original query
586                 ResourceRecord query = reqs.get(xid);
587                 int qtype = query.getType();
588                 int qclass = query.getRrclass();
589                 DnsName qname = query.getName();
590 
591                 // Check that the type/class/name in the query section of the
592                 // response match those in the original query
593                 if ((qtype == ResourceRecord.QTYPE_STAR ||
594                     qtype == rr.getType()) &&
595                     (qclass == ResourceRecord.QCLASS_STAR ||
596                     qclass == rr.getRrclass()) &&
597                     qname.equals(rr.getName())) {
598 
599                     if (debug) {
600                         dprint("MATCH NAME:" + qname + " QTYPE:" + qtype +
601                             " QCLASS:" + qclass);
602                     }
603 
604                     // Remove the response for the xid if received by some other
605                     // thread.
606                     synchronized (queuesLock) {
607                         resps.remove(xid);
608                         reqs.remove(xid);
609                     }
610                     return true;
611 
612                 } else {
613                     if (debug) {
614                         dprint("NO-MATCH NAME:" + qname + " QTYPE:" + qtype +
615                             " QCLASS:" + qclass);
616                     }
617                 }
618             }
619             return false;
620         }
621 
622         //
623         // xid mis-match: enqueue the response, it may belong to some other
624         // thread that has not yet had a chance to read its response.
625         // enqueue only the first response, responses for retries are ignored.
626         //
627         synchronized (queuesLock) {
628             if (reqs.containsKey(hdr.xid)) { // enqueue only the first response
629                 resps.put(hdr.xid, pkt);
630             }
631         }
632 
633         if (debug) {
634             dprint("NO-MATCH SEND ID:" +
635                                 xid + " RECVD ID:" + hdr.xid +
636                                 "    Response Q:" + resps +
637                                 "    Reqs size:" + reqs.size());
638         }
639         return false;
640     }
641 
642     /*
643      * Throws an exception if appropriate for the response code of a
644      * given header.
645      */
checkResponseCode(Header hdr)646     private void checkResponseCode(Header hdr) throws NamingException {
647 
648         int rcode = hdr.rcode;
649         if (rcode == NO_ERROR) {
650             return;
651         }
652         String msg = (rcode < rcodeDescription.length)
653             ? rcodeDescription[rcode]
654             : "DNS error";
655         msg += " [response code " + rcode + "]";
656 
657         switch (rcode) {
658         case SERVER_FAILURE:
659             throw new ServiceUnavailableException(msg);
660         case NAME_ERROR:
661             throw new NameNotFoundException(msg);
662         case NOT_IMPL:
663         case REFUSED:
664             throw new OperationNotSupportedException(msg);
665         case FORMAT_ERROR:
666         default:
667             throw new NamingException(msg);
668         }
669     }
670 
671     //-------------------------------------------------------------------------
672 
673     private static final boolean debug = false;
674 
dprint(String mess)675     private static void dprint(String mess) {
676         if (debug) {
677             System.err.println("DNS: " + mess);
678         }
679     }
680 
681 }
682 
683 class Tcp {
684 
685     private Socket sock;
686     java.io.InputStream in;
687     java.io.OutputStream out;
688 
Tcp(InetAddress server, int port)689     Tcp(InetAddress server, int port) throws IOException {
690         sock = new Socket(server, port);
691         sock.setTcpNoDelay(true);
692         out = new java.io.BufferedOutputStream(sock.getOutputStream());
693         in = new java.io.BufferedInputStream(sock.getInputStream());
694     }
695 
close()696     void close() throws IOException {
697         sock.close();
698     }
699 }
700 
701 /*
702  * javaos emulation -cj
703  */
704 class Packet {
705         byte buf[];
706 
Packet(int len)707         Packet(int len) {
708                 buf = new byte[len];
709         }
710 
Packet(byte data[], int len)711         Packet(byte data[], int len) {
712                 buf = new byte[len];
713                 System.arraycopy(data, 0, buf, 0, len);
714         }
715 
putInt(int x, int off)716         void putInt(int x, int off) {
717                 buf[off + 0] = (byte)(x >> 24);
718                 buf[off + 1] = (byte)(x >> 16);
719                 buf[off + 2] = (byte)(x >> 8);
720                 buf[off + 3] = (byte)x;
721         }
722 
putShort(int x, int off)723         void putShort(int x, int off) {
724                 buf[off + 0] = (byte)(x >> 8);
725                 buf[off + 1] = (byte)x;
726         }
727 
putByte(int x, int off)728         void putByte(int x, int off) {
729                 buf[off] = (byte)x;
730         }
731 
putBytes(byte src[], int src_offset, int dst_offset, int len)732         void putBytes(byte src[], int src_offset, int dst_offset, int len) {
733                 System.arraycopy(src, src_offset, buf, dst_offset, len);
734         }
735 
length()736         int length() {
737                 return buf.length;
738         }
739 
getData()740         byte[] getData() {
741                 return buf;
742         }
743 }
744