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