1 | /* |
2 | Copyright (C) 2002-2005 MySQL AB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of version 2 of the GNU General Public License as |
6 | published by the Free Software Foundation. |
7 | |
8 | There are special exceptions to the terms and conditions of the GPL |
9 | as it is applied to this software. View the full text of the |
10 | exception in file EXCEPTIONS-CONNECTOR-J in the directory of this |
11 | software distribution. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | |
22 | |
23 | |
24 | */ |
25 | package com.mysql.jdbc; |
26 | |
27 | import java.io.UnsupportedEncodingException; |
28 | |
29 | import java.nio.ByteBuffer; |
30 | |
31 | import java.sql.SQLException; |
32 | |
33 | /** |
34 | * Buffer contains code to read and write packets from/to the MySQL server. |
35 | * |
36 | * @version $Id: Buffer.java 5417 2006-06-20 21:33:56 +0000 (Tue, 20 Jun 2006) mmatthews $ |
37 | * @author Mark Matthews |
38 | */ |
39 | class Buffer { |
40 | static final int MAX_BYTES_TO_DUMP = 512; |
41 | |
42 | static final int NO_LENGTH_LIMIT = -1; |
43 | |
44 | static final long NULL_LENGTH = -1; |
45 | |
46 | private int bufLength = 0; |
47 | |
48 | private byte[] byteBuffer; |
49 | |
50 | private int position = 0; |
51 | |
52 | protected boolean wasMultiPacket = false; |
53 | |
54 | Buffer(byte[] buf) { |
55 | this.byteBuffer = buf; |
56 | setBufLength(buf.length); |
57 | } |
58 | |
59 | Buffer(int size) { |
60 | this.byteBuffer = new byte[size]; |
61 | setBufLength(this.byteBuffer.length); |
62 | this.position = MysqlIO.HEADER_LENGTH; |
63 | } |
64 | |
65 | final void clear() { |
66 | this.position = MysqlIO.HEADER_LENGTH; |
67 | } |
68 | |
69 | final void dump() { |
70 | dump(getBufLength()); |
71 | } |
72 | |
73 | final String dump(int numBytes) { |
74 | return StringUtils.dumpAsHex(getBytes(0, |
75 | numBytes > getBufLength() ? getBufLength() : numBytes), |
76 | numBytes > getBufLength() ? getBufLength() : numBytes); |
77 | } |
78 | |
79 | final String dumpClampedBytes(int numBytes) { |
80 | int numBytesToDump = numBytes < MAX_BYTES_TO_DUMP ? numBytes |
81 | : MAX_BYTES_TO_DUMP; |
82 | |
83 | String dumped = StringUtils.dumpAsHex(getBytes(0, |
84 | numBytesToDump > getBufLength() ? getBufLength() |
85 | : numBytesToDump), |
86 | numBytesToDump > getBufLength() ? getBufLength() |
87 | : numBytesToDump); |
88 | |
89 | if (numBytesToDump < numBytes) { |
90 | return dumped + " ....(packet exceeds max. dump length)"; |
91 | } |
92 | |
93 | return dumped; |
94 | } |
95 | |
96 | final void dumpHeader() { |
97 | for (int i = 0; i < MysqlIO.HEADER_LENGTH; i++) { |
98 | String hexVal = Integer.toHexString(readByte(i) & 0xff); |
99 | |
100 | if (hexVal.length() == 1) { |
101 | hexVal = "0" + hexVal; //$NON-NLS-1$ |
102 | } |
103 | |
104 | System.out.print(hexVal + " "); //$NON-NLS-1$ |
105 | } |
106 | } |
107 | |
108 | final void dumpNBytes(int start, int nBytes) { |
109 | StringBuffer asciiBuf = new StringBuffer(); |
110 | |
111 | for (int i = start; (i < (start + nBytes)) && (i < getBufLength()); i++) { |
112 | String hexVal = Integer.toHexString(readByte(i) & 0xff); |
113 | |
114 | if (hexVal.length() == 1) { |
115 | hexVal = "0" + hexVal; //$NON-NLS-1$ |
116 | } |
117 | |
118 | System.out.print(hexVal + " "); //$NON-NLS-1$ |
119 | |
120 | if ((readByte(i) > 32) && (readByte(i) < 127)) { |
121 | asciiBuf.append((char) readByte(i)); |
122 | } else { |
123 | asciiBuf.append("."); //$NON-NLS-1$ |
124 | } |
125 | |
126 | asciiBuf.append(" "); //$NON-NLS-1$ |
127 | } |
128 | |
129 | System.out.println(" " + asciiBuf.toString()); //$NON-NLS-1$ |
130 | } |
131 | |
132 | final void ensureCapacity(int additionalData) throws SQLException { |
133 | if ((this.position + additionalData) > getBufLength()) { |
134 | if ((this.position + additionalData) < this.byteBuffer.length) { |
135 | // byteBuffer.length is != getBufLength() all of the time |
136 | // due to re-using of packets (we don't shrink them) |
137 | // |
138 | // If we can, don't re-alloc, just set buffer length |
139 | // to size of current buffer |
140 | setBufLength(this.byteBuffer.length); |
141 | } else { |
142 | // |
143 | // Otherwise, re-size, and pad so we can avoid |
144 | // allocing again in the near future |
145 | // |
146 | int newLength = (int) (this.byteBuffer.length * 1.25); |
147 | |
148 | if (newLength < (this.byteBuffer.length + additionalData)) { |
149 | newLength = this.byteBuffer.length |
150 | + (int) (additionalData * 1.25); |
151 | } |
152 | |
153 | if (newLength < this.byteBuffer.length) { |
154 | newLength = this.byteBuffer.length + additionalData; |
155 | } |
156 | |
157 | byte[] newBytes = new byte[newLength]; |
158 | |
159 | System.arraycopy(this.byteBuffer, 0, newBytes, 0, |
160 | this.byteBuffer.length); |
161 | this.byteBuffer = newBytes; |
162 | setBufLength(this.byteBuffer.length); |
163 | } |
164 | } |
165 | } |
166 | |
167 | /** |
168 | * Skip over a length-encoded string |
169 | * |
170 | * @return The position past the end of the string |
171 | */ |
172 | public int fastSkipLenString() { |
173 | long len = this.readFieldLength(); |
174 | |
175 | this.position += len; |
176 | |
177 | return (int) len; // this is safe, as this is only |
178 | } |
179 | |
180 | protected final byte[] getBufferSource() { |
181 | return this.byteBuffer; |
182 | } |
183 | |
184 | int getBufLength() { |
185 | return this.bufLength; |
186 | } |
187 | |
188 | /** |
189 | * Returns the array of bytes this Buffer is using to read from. |
190 | * |
191 | * @return byte array being read from |
192 | */ |
193 | public byte[] getByteBuffer() { |
194 | return this.byteBuffer; |
195 | } |
196 | |
197 | final byte[] getBytes(int len) { |
198 | byte[] b = new byte[len]; |
199 | System.arraycopy(this.byteBuffer, this.position, b, 0, len); |
200 | this.position += len; // update cursor |
201 | |
202 | return b; |
203 | } |
204 | |
205 | /* |
206 | * (non-Javadoc) |
207 | * |
208 | * @see com.mysql.jdbc.Buffer#getBytes(int, int) |
209 | */ |
210 | byte[] getBytes(int offset, int len) { |
211 | byte[] dest = new byte[len]; |
212 | System.arraycopy(this.byteBuffer, offset, dest, 0, len); |
213 | |
214 | return dest; |
215 | } |
216 | |
217 | int getCapacity() { |
218 | return this.byteBuffer.length; |
219 | } |
220 | |
221 | public ByteBuffer getNioBuffer() { |
222 | throw new IllegalArgumentException(Messages |
223 | .getString("ByteArrayBuffer.0")); //$NON-NLS-1$ |
224 | } |
225 | |
226 | /** |
227 | * Returns the current position to write to/ read from |
228 | * |
229 | * @return the current position to write to/ read from |
230 | */ |
231 | public int getPosition() { |
232 | return this.position; |
233 | } |
234 | |
235 | // 2000-06-05 Changed |
236 | final boolean isLastDataPacket() { |
237 | return ((getBufLength() < 9) && ((this.byteBuffer[0] & 0xff) == 254)); |
238 | } |
239 | |
240 | final long newReadLength() { |
241 | int sw = this.byteBuffer[this.position++] & 0xff; |
242 | |
243 | switch (sw) { |
244 | case 251: |
245 | return 0; |
246 | |
247 | case 252: |
248 | return readInt(); |
249 | |
250 | case 253: |
251 | return readLongInt(); |
252 | |
253 | case 254: // changed for 64 bit lengths |
254 | return readLongLong(); |
255 | |
256 | default: |
257 | return sw; |
258 | } |
259 | } |
260 | |
261 | final byte readByte() { |
262 | return this.byteBuffer[this.position++]; |
263 | } |
264 | |
265 | final byte readByte(int readAt) { |
266 | return this.byteBuffer[readAt]; |
267 | } |
268 | |
269 | final long readFieldLength() { |
270 | int sw = this.byteBuffer[this.position++] & 0xff; |
271 | |
272 | switch (sw) { |
273 | case 251: |
274 | return NULL_LENGTH; |
275 | |
276 | case 252: |
277 | return readInt(); |
278 | |
279 | case 253: |
280 | return readLongInt(); |
281 | |
282 | case 254: |
283 | return readLongLong(); |
284 | |
285 | default: |
286 | return sw; |
287 | } |
288 | } |
289 | |
290 | // 2000-06-05 Changed |
291 | final int readInt() { |
292 | byte[] b = this.byteBuffer; // a little bit optimization |
293 | |
294 | return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8); |
295 | } |
296 | |
297 | final int readIntAsLong() { |
298 | byte[] b = this.byteBuffer; |
299 | |
300 | return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8) |
301 | | ((b[this.position++] & 0xff) << 16) |
302 | | ((b[this.position++] & 0xff) << 24); |
303 | } |
304 | |
305 | final byte[] readLenByteArray(int offset) { |
306 | long len = this.readFieldLength(); |
307 | |
308 | if (len == NULL_LENGTH) { |
309 | return null; |
310 | } |
311 | |
312 | if (len == 0) { |
313 | return Constants.EMPTY_BYTE_ARRAY; |
314 | } |
315 | |
316 | this.position += offset; |
317 | |
318 | return getBytes((int) len); |
319 | } |
320 | |
321 | final long readLength() { |
322 | int sw = this.byteBuffer[this.position++] & 0xff; |
323 | |
324 | switch (sw) { |
325 | case 251: |
326 | return 0; |
327 | |
328 | case 252: |
329 | return readInt(); |
330 | |
331 | case 253: |
332 | return readLongInt(); |
333 | |
334 | case 254: |
335 | return readLong(); |
336 | |
337 | default: |
338 | return sw; |
339 | } |
340 | } |
341 | |
342 | // 2000-06-05 Fixed |
343 | final long readLong() { |
344 | byte[] b = this.byteBuffer; |
345 | |
346 | return ((long) b[this.position++] & 0xff) |
347 | | (((long) b[this.position++] & 0xff) << 8) |
348 | | ((long) (b[this.position++] & 0xff) << 16) |
349 | | ((long) (b[this.position++] & 0xff) << 24); |
350 | } |
351 | |
352 | // 2000-06-05 Changed |
353 | final int readLongInt() { |
354 | byte[] b = this.byteBuffer; |
355 | |
356 | return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8) |
357 | | ((b[this.position++] & 0xff) << 16); |
358 | } |
359 | |
360 | // 2000-06-05 Fixed |
361 | final long readLongLong() { |
362 | byte[] b = this.byteBuffer; |
363 | |
364 | return (b[this.position++] & 0xff) |
365 | | ((long) (b[this.position++] & 0xff) << 8) |
366 | | ((long) (b[this.position++] & 0xff) << 16) |
367 | | ((long) (b[this.position++] & 0xff) << 24) |
368 | | ((long) (b[this.position++] & 0xff) << 32) |
369 | | ((long) (b[this.position++] & 0xff) << 40) |
370 | | ((long) (b[this.position++] & 0xff) << 48) |
371 | | ((long) (b[this.position++] & 0xff) << 56); |
372 | } |
373 | |
374 | final int readnBytes() { |
375 | int sw = this.byteBuffer[this.position++] & 0xff; |
376 | |
377 | switch (sw) { |
378 | case 1: |
379 | return this.byteBuffer[this.position++] & 0xff; |
380 | |
381 | case 2: |
382 | return this.readInt(); |
383 | |
384 | case 3: |
385 | return this.readLongInt(); |
386 | |
387 | case 4: |
388 | return (int) this.readLong(); |
389 | |
390 | default: |
391 | return 255; |
392 | } |
393 | } |
394 | |
395 | // |
396 | // Read a null-terminated string |
397 | // |
398 | // To avoid alloc'ing a new byte array, we |
399 | // do this by hand, rather than calling getNullTerminatedBytes() |
400 | // |
401 | final String readString() { |
402 | int i = this.position; |
403 | int len = 0; |
404 | int maxLen = getBufLength(); |
405 | |
406 | while ((i < maxLen) && (this.byteBuffer[i] != 0)) { |
407 | len++; |
408 | i++; |
409 | } |
410 | |
411 | String s = new String(this.byteBuffer, this.position, len); |
412 | this.position += (len + 1); // update cursor |
413 | |
414 | return s; |
415 | } |
416 | |
417 | final String readString(String encoding) throws SQLException { |
418 | int i = this.position; |
419 | int len = 0; |
420 | int maxLen = getBufLength(); |
421 | |
422 | while ((i < maxLen) && (this.byteBuffer[i] != 0)) { |
423 | len++; |
424 | i++; |
425 | } |
426 | |
427 | try { |
428 | return new String(this.byteBuffer, this.position, len, encoding); |
429 | } catch (UnsupportedEncodingException uEE) { |
430 | throw SQLError.createSQLException(Messages.getString("ByteArrayBuffer.1") //$NON-NLS-1$ |
431 | + encoding + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$ |
432 | } finally { |
433 | this.position += (len + 1); // update cursor |
434 | } |
435 | } |
436 | |
437 | void setBufLength(int bufLengthToSet) { |
438 | this.bufLength = bufLengthToSet; |
439 | } |
440 | |
441 | /** |
442 | * Sets the array of bytes to use as a buffer to read from. |
443 | * |
444 | * @param byteBuffer |
445 | * the array of bytes to use as a buffer |
446 | */ |
447 | public void setByteBuffer(byte[] byteBufferToSet) { |
448 | this.byteBuffer = byteBufferToSet; |
449 | } |
450 | |
451 | /** |
452 | * Set the current position to write to/ read from |
453 | * |
454 | * @param position |
455 | * the position (0-based index) |
456 | */ |
457 | public void setPosition(int positionToSet) { |
458 | this.position = positionToSet; |
459 | } |
460 | |
461 | /** |
462 | * Sets whether this packet was part of a multipacket |
463 | * |
464 | * @param flag |
465 | * was this packet part of a multipacket? |
466 | */ |
467 | public void setWasMultiPacket(boolean flag) { |
468 | this.wasMultiPacket = flag; |
469 | } |
470 | |
471 | public String toString() { |
472 | return dumpClampedBytes(getPosition()); |
473 | } |
474 | |
475 | public String toSuperString() { |
476 | return super.toString(); |
477 | } |
478 | |
479 | /** |
480 | * Was this packet part of a multipacket? |
481 | * |
482 | * @return was this packet part of a multipacket? |
483 | */ |
484 | public boolean wasMultiPacket() { |
485 | return this.wasMultiPacket; |
486 | } |
487 | |
488 | final void writeByte(byte b) throws SQLException { |
489 | ensureCapacity(1); |
490 | |
491 | this.byteBuffer[this.position++] = b; |
492 | } |
493 | |
494 | // Write a byte array |
495 | final void writeBytesNoNull(byte[] bytes) throws SQLException { |
496 | int len = bytes.length; |
497 | ensureCapacity(len); |
498 | System.arraycopy(bytes, 0, this.byteBuffer, this.position, len); |
499 | this.position += len; |
500 | } |
501 | |
502 | // Write a byte array with the given offset and length |
503 | final void writeBytesNoNull(byte[] bytes, int offset, int length) |
504 | throws SQLException { |
505 | ensureCapacity(length); |
506 | System.arraycopy(bytes, offset, this.byteBuffer, this.position, length); |
507 | this.position += length; |
508 | } |
509 | |
510 | final void writeDouble(double d) throws SQLException { |
511 | long l = Double.doubleToLongBits(d); |
512 | writeLongLong(l); |
513 | } |
514 | |
515 | final void writeFieldLength(long length) throws SQLException { |
516 | if (length < 251) { |
517 | writeByte((byte) length); |
518 | } else if (length < 65536L) { |
519 | ensureCapacity(3); |
520 | writeByte((byte) 252); |
521 | writeInt((int) length); |
522 | } else if (length < 16777216L) { |
523 | ensureCapacity(4); |
524 | writeByte((byte) 253); |
525 | writeLongInt((int) length); |
526 | } else { |
527 | ensureCapacity(9); |
528 | writeByte((byte) 254); |
529 | writeLongLong(length); |
530 | } |
531 | } |
532 | |
533 | final void writeFloat(float f) throws SQLException { |
534 | ensureCapacity(4); |
535 | |
536 | int i = Float.floatToIntBits(f); |
537 | byte[] b = this.byteBuffer; |
538 | b[this.position++] = (byte) (i & 0xff); |
539 | b[this.position++] = (byte) (i >>> 8); |
540 | b[this.position++] = (byte) (i >>> 16); |
541 | b[this.position++] = (byte) (i >>> 24); |
542 | } |
543 | |
544 | // 2000-06-05 Changed |
545 | final void writeInt(int i) throws SQLException { |
546 | ensureCapacity(2); |
547 | |
548 | byte[] b = this.byteBuffer; |
549 | b[this.position++] = (byte) (i & 0xff); |
550 | b[this.position++] = (byte) (i >>> 8); |
551 | } |
552 | |
553 | // Write a String using the specified character |
554 | // encoding |
555 | final void writeLenBytes(byte[] b) throws SQLException { |
556 | int len = b.length; |
557 | ensureCapacity(len + 9); |
558 | writeFieldLength(len); |
559 | System.arraycopy(b, 0, this.byteBuffer, this.position, len); |
560 | this.position += len; |
561 | } |
562 | |
563 | // Write a String using the specified character |
564 | // encoding |
565 | final void writeLenString(String s, String encoding, String serverEncoding, |
566 | SingleByteCharsetConverter converter, boolean parserKnowsUnicode, |
567 | Connection conn) |
568 | throws UnsupportedEncodingException, SQLException { |
569 | byte[] b = null; |
570 | |
571 | if (converter != null) { |
572 | b = converter.toBytes(s); |
573 | } else { |
574 | b = StringUtils.getBytes(s, encoding, serverEncoding, |
575 | parserKnowsUnicode, conn); |
576 | } |
577 | |
578 | int len = b.length; |
579 | ensureCapacity(len + 9); |
580 | writeFieldLength(len); |
581 | System.arraycopy(b, 0, this.byteBuffer, this.position, len); |
582 | this.position += len; |
583 | } |
584 | |
585 | // 2000-06-05 Changed |
586 | final void writeLong(long i) throws SQLException { |
587 | ensureCapacity(4); |
588 | |
589 | byte[] b = this.byteBuffer; |
590 | b[this.position++] = (byte) (i & 0xff); |
591 | b[this.position++] = (byte) (i >>> 8); |
592 | b[this.position++] = (byte) (i >>> 16); |
593 | b[this.position++] = (byte) (i >>> 24); |
594 | } |
595 | |
596 | // 2000-06-05 Changed |
597 | final void writeLongInt(int i) throws SQLException { |
598 | ensureCapacity(3); |
599 | byte[] b = this.byteBuffer; |
600 | b[this.position++] = (byte) (i & 0xff); |
601 | b[this.position++] = (byte) (i >>> 8); |
602 | b[this.position++] = (byte) (i >>> 16); |
603 | } |
604 | |
605 | final void writeLongLong(long i) throws SQLException { |
606 | ensureCapacity(8); |
607 | byte[] b = this.byteBuffer; |
608 | b[this.position++] = (byte) (i & 0xff); |
609 | b[this.position++] = (byte) (i >>> 8); |
610 | b[this.position++] = (byte) (i >>> 16); |
611 | b[this.position++] = (byte) (i >>> 24); |
612 | b[this.position++] = (byte) (i >>> 32); |
613 | b[this.position++] = (byte) (i >>> 40); |
614 | b[this.position++] = (byte) (i >>> 48); |
615 | b[this.position++] = (byte) (i >>> 56); |
616 | } |
617 | |
618 | // Write null-terminated string |
619 | final void writeString(String s) throws SQLException { |
620 | ensureCapacity((s.length() * 2) + 1); |
621 | writeStringNoNull(s); |
622 | this.byteBuffer[this.position++] = 0; |
623 | } |
624 | |
625 | // Write null-terminated string in the given encoding |
626 | final void writeString(String s, String encoding, Connection conn) throws SQLException { |
627 | ensureCapacity((s.length() * 2) + 1); |
628 | try { |
629 | writeStringNoNull(s, encoding, encoding, false, conn); |
630 | } catch (UnsupportedEncodingException ue) { |
631 | throw new SQLException(ue.toString(), SQLError.SQL_STATE_GENERAL_ERROR); |
632 | } |
633 | |
634 | this.byteBuffer[this.position++] = 0; |
635 | } |
636 | |
637 | // Write string, with no termination |
638 | final void writeStringNoNull(String s) throws SQLException { |
639 | int len = s.length(); |
640 | ensureCapacity(len * 2); |
641 | System.arraycopy(s.getBytes(), 0, this.byteBuffer, this.position, len); |
642 | this.position += len; |
643 | |
644 | // for (int i = 0; i < len; i++) |
645 | // { |
646 | // this.byteBuffer[this.position++] = (byte)s.charAt(i); |
647 | // } |
648 | } |
649 | |
650 | // Write a String using the specified character |
651 | // encoding |
652 | final void writeStringNoNull(String s, String encoding, |
653 | String serverEncoding, boolean parserKnowsUnicode, Connection conn) |
654 | throws UnsupportedEncodingException, SQLException { |
655 | byte[] b = StringUtils.getBytes(s, encoding, serverEncoding, |
656 | parserKnowsUnicode, conn); |
657 | |
658 | int len = b.length; |
659 | ensureCapacity(len); |
660 | System.arraycopy(b, 0, this.byteBuffer, this.position, len); |
661 | this.position += len; |
662 | } |
663 | } |