byte.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. 'use strict';
  2. const Long = require('long');
  3. const debug = require('debug')('byte');
  4. const numbers = require('./number');
  5. const utility = require('utility');
  6. const DEFAULT_SIZE = 1024;
  7. const BIG_ENDIAN = 1;
  8. const LITTLE_ENDIAN = 2;
  9. const MAX_INT_31 = Math.pow(2, 31);
  10. const ONE_HUNDRED_MB = 100 * 1024 * 1024;
  11. function ByteBuffer(options) {
  12. options = options || {};
  13. this._order = options.order || BIG_ENDIAN;
  14. this._size = options.size || DEFAULT_SIZE;
  15. this._offset = 0;
  16. this._limit = this._size;
  17. const array = options.array;
  18. if (array) {
  19. this._bytes = array;
  20. } else {
  21. this._bytes = Buffer.alloc(this._size);
  22. }
  23. }
  24. ByteBuffer.BIG_ENDIAN = BIG_ENDIAN;
  25. ByteBuffer.LITTLE_ENDIAN = LITTLE_ENDIAN;
  26. ByteBuffer.allocate = function(capacity) {
  27. return new ByteBuffer({ size: capacity });
  28. };
  29. ByteBuffer.wrap = function(array, offset, length) {
  30. if (offset) {
  31. const end = offset + (length || array.length);
  32. array = array.slice(offset, end);
  33. }
  34. return new ByteBuffer({ array, size: array.length });
  35. };
  36. ByteBuffer.prototype.reset = function() {
  37. this._offset = 0;
  38. };
  39. ByteBuffer.prototype.order = function(order) {
  40. this._order = order;
  41. return this;
  42. };
  43. ByteBuffer.prototype._checkSize = function(afterSize) {
  44. if (this._size >= afterSize) {
  45. return;
  46. }
  47. const old = this._size;
  48. this._size = afterSize * 2;
  49. this._limit = this._size;
  50. debug('allocate new Buffer: from %d to %d bytes', old, this._size);
  51. const bytes = Buffer.alloc(this._size);
  52. this._bytes.copy(bytes, 0);
  53. this._bytes = bytes;
  54. };
  55. ByteBuffer.prototype.put = function(src, offset, length) {
  56. // byte
  57. // bytes, offset, length
  58. // index, byte
  59. if (typeof src !== 'number') {
  60. // bytes, offset, length
  61. const index = this._offset;
  62. offset = offset || 0;
  63. length = length || src.length;
  64. this._offset += length;
  65. this._checkSize(this._offset);
  66. src.copy(this._bytes, index, offset, offset + length);
  67. return this;
  68. }
  69. if (offset === undefined) {
  70. // byte
  71. offset = src;
  72. src = this._offset;
  73. this._offset++;
  74. this._checkSize(this._offset);
  75. }
  76. // index, byte
  77. this._bytes[src] = offset;
  78. return this;
  79. };
  80. ByteBuffer.prototype.get = function(index, length) {
  81. if (index == null && length == null) {
  82. return this._bytes[this._offset++];
  83. }
  84. if (typeof index === 'number' && length == null) {
  85. return this._bytes[index];
  86. }
  87. if (typeof index === 'number' && typeof length === 'number') {
  88. // offset, length => Buffer
  89. return this._copy(index, index + length);
  90. }
  91. const dst = index;
  92. const offset = length || 0;
  93. length = dst.length;
  94. this.checkArraySize(dst.length, offset, length);
  95. this.checkForUnderflow(length);
  96. if (Buffer.isBuffer(dst)) {
  97. this._bytes.copy(dst, offset, this._offset, (this._offset + length));
  98. } else {
  99. for (let i = offset; i < (offset + length); i++) {
  100. dst[i] = this._bytes[i];
  101. }
  102. }
  103. this._offset += length;
  104. return this;
  105. };
  106. ByteBuffer.prototype.read = function(size) {
  107. const index = this._offset;
  108. this._offset += size;
  109. return this._bytes.slice(index, this._offset);
  110. };
  111. ByteBuffer.prototype.putChar = function(index, value) {
  112. // char
  113. // int, char
  114. if (value === undefined) {
  115. // char
  116. value = charCode(index);
  117. index = this._offset;
  118. this._offset++;
  119. this._checkSize(this._offset);
  120. } else {
  121. // int, char
  122. value = charCode(value);
  123. }
  124. this._bytes[index] = value;
  125. return this;
  126. };
  127. function charCode(c) {
  128. return typeof c === 'string' ? c.charCodeAt(0) : c;
  129. }
  130. ByteBuffer.prototype.getChar = function(index) {
  131. const b = this.get(index);
  132. return String.fromCharCode(b);
  133. };
  134. Object.keys(numbers).forEach(function(type) {
  135. const putMethod = 'put' + type;
  136. const getMethod = 'get' + type;
  137. const handles = numbers[type];
  138. const size = handles.size;
  139. ByteBuffer.prototype[putMethod] = function(index, value) {
  140. // index, value
  141. // value
  142. if (value === undefined) {
  143. // index, value
  144. value = index;
  145. index = this._offset;
  146. this._offset += size;
  147. this._checkSize(this._offset);
  148. }
  149. const handle = this._order === BIG_ENDIAN ?
  150. handles.writeBE :
  151. handles.writeLE;
  152. this._bytes[handle](value, index);
  153. return this;
  154. };
  155. ByteBuffer.prototype[getMethod] = function(index) {
  156. if (typeof index !== 'number') {
  157. index = this._offset;
  158. this._offset += size;
  159. }
  160. const handle = this._order === BIG_ENDIAN ?
  161. handles.readBE :
  162. handles.readLE;
  163. return this._bytes[handle](index);
  164. };
  165. });
  166. ByteBuffer.prototype._putZero = function(index) {
  167. this._bytes[index] = 0;
  168. this._bytes[index + 1] = 0;
  169. this._bytes[index + 2] = 0;
  170. this._bytes[index + 3] = 0;
  171. };
  172. ByteBuffer.prototype._putFF = function(index) {
  173. this._bytes[index] = 0xff;
  174. this._bytes[index + 1] = 0xff;
  175. this._bytes[index + 2] = 0xff;
  176. this._bytes[index + 3] = 0xff;
  177. };
  178. ByteBuffer.prototype.putLong = function(index, value) {
  179. // long
  180. // int, long
  181. let offset = 0;
  182. if (value === undefined) {
  183. // long
  184. offset = this._offset;
  185. this._offset += 8;
  186. this._checkSize(this._offset);
  187. value = index;
  188. } else {
  189. // int, long
  190. offset = index;
  191. }
  192. // get the offset
  193. let highOffset = offset;
  194. let lowOffset = offset + 4;
  195. if (this._order !== BIG_ENDIAN) {
  196. highOffset = offset + 4;
  197. lowOffset = offset;
  198. }
  199. let isNumber = typeof value === 'number';
  200. // convert safe number string to number
  201. if (!isNumber && utility.isSafeNumberString(value)) {
  202. isNumber = true;
  203. value = Number(value);
  204. }
  205. // int
  206. if (isNumber &&
  207. value < MAX_INT_31 &&
  208. value >= -MAX_INT_31) {
  209. // put high
  210. value < 0 ?
  211. this._putFF(highOffset) :
  212. this._putZero(highOffset);
  213. if (this._order === BIG_ENDIAN) {
  214. this._bytes.writeInt32BE(value, lowOffset);
  215. } else {
  216. this._bytes.writeInt32LE(value, lowOffset);
  217. }
  218. return this;
  219. }
  220. // long number or string, make it a Long Object
  221. // TODO: Long object's performence has big problem
  222. if (typeof value.low !== 'number' ||
  223. typeof value.high !== 'number') {
  224. // not Long instance, must be Number or String
  225. value = isNumber ?
  226. Long.fromNumber(value) :
  227. Long.fromString(value);
  228. }
  229. // write
  230. if (this._order === BIG_ENDIAN) {
  231. this._bytes.writeInt32BE(value.high, highOffset);
  232. this._bytes.writeInt32BE(value.low, lowOffset);
  233. } else {
  234. this._bytes.writeInt32LE(value.high, highOffset);
  235. this._bytes.writeInt32LE(value.low, lowOffset);
  236. }
  237. return this;
  238. };
  239. ByteBuffer.prototype.putInt64 = ByteBuffer.prototype.putLong;
  240. ByteBuffer.prototype.getLong = function(index) {
  241. if (typeof index !== 'number') {
  242. index = this._offset;
  243. this._offset += 8;
  244. }
  245. if (this._order === BIG_ENDIAN) {
  246. return new Long(
  247. this._bytes.readInt32BE(index + 4), // low, high
  248. this._bytes.readInt32BE(index)
  249. );
  250. }
  251. return new Long(
  252. this._bytes.readInt32LE(index),
  253. this._bytes.readInt32LE(index + 4)
  254. );
  255. };
  256. ByteBuffer.prototype.getInt64 = ByteBuffer.prototype.getLong;
  257. ByteBuffer.prototype._putString = function(index, value, format) {
  258. if (!value || value.length === 0) {
  259. // empty string
  260. if (index === null || index === undefined) {
  261. index = this._offset;
  262. this._offset += 4;
  263. this._checkSize(this._offset);
  264. } else {
  265. this._checkSize(index + 4);
  266. }
  267. return this.putInt(index, 0);
  268. }
  269. const isBuffer = Buffer.isBuffer(value);
  270. let length = isBuffer ?
  271. value.length :
  272. Buffer.byteLength(value);
  273. if (format === 'c') {
  274. length++;
  275. }
  276. if (index === null || index === undefined) {
  277. index = this._offset;
  278. this._offset += length + 4;
  279. this._checkSize(this._offset);
  280. } else {
  281. this._checkSize(index + length + 4);
  282. }
  283. this.putInt(index, length);
  284. const valueOffset = index + 4;
  285. if (isBuffer) {
  286. value.copy(this._bytes, valueOffset);
  287. } else {
  288. this._bytes.write(value, valueOffset);
  289. }
  290. if (format === 'c') {
  291. this.put(valueOffset + length, 0);
  292. }
  293. return this;
  294. };
  295. // Prints a string to the Buffer, encoded as CESU-8
  296. ByteBuffer.prototype.putRawString = function(index, str) {
  297. if (typeof index === 'string') {
  298. // putRawString(str)
  299. str = index;
  300. index = this._offset;
  301. // Note that an UTF-8 encoder will encode a character that is outside BMP
  302. // as 4 bytes, yet a CESU-8 encoder will encode as 6 bytes, ergo 6 / 4 = 1.5
  303. // @see https://en.wikipedia.org/wiki/CESU-8
  304. // this._checkSize(this._offset + Math.ceil(Buffer.byteLength(str) * 1.5));
  305. // use big memory to exchange better performence
  306. // one char => max bytes is 3
  307. let maxIncreaseSize = str.length * 3;
  308. if (maxIncreaseSize > ONE_HUNDRED_MB) {
  309. maxIncreaseSize = Math.ceil(Buffer.byteLength(str) * 1.5);
  310. }
  311. this._checkSize(this._offset + maxIncreaseSize);
  312. }
  313. // CESU-8 Bit Distribution
  314. // @see http://www.unicode.org/reports/tr26/
  315. //
  316. // UTF-16 Code Unit | 1st Byte | 2nd Byte | 3rd Byte
  317. // 000000000xxxxxxx (0x0000 ~ 0x007f) | 0xxxxxxx (0x00 ~ 0x7f) | |
  318. // 00000yyyyyxxxxxx (0x0080 ~ 0x07ff) | 110yyyyy (0xc0 ~ 0xdf) | 10xxxxxx (0x80 ~ 0xbf) |
  319. // zzzzyyyyyyxxxxxx (0x0800 ~ 0xffff) | 1110zzzz (0xe0 ~ 0xef) | 10yyyyyy (0x80 ~ 0xbf) | 10xxxxxx (0x80 ~ 0xbf)
  320. const len = str && str.length;
  321. if (!len) {
  322. return this;
  323. }
  324. for (let i = 0; i < len; i++) {
  325. const ch = str.charCodeAt(i);
  326. // 0x80: 128
  327. if (ch < 0x80) {
  328. this._bytes[index++] = ch;
  329. } else if (ch < 0x800) {
  330. // 0x800: 2048
  331. this._bytes[index++] = (0xc0 + ((ch >> 6) & 0x1f)) >>> 32;
  332. this._bytes[index++] = (0x80 + (ch & 0x3f)) >>> 32;
  333. } else {
  334. this._bytes[index++] = (0xe0 + ((ch >> 12) & 0xf)) >>> 32;
  335. this._bytes[index++] = (0x80 + ((ch >> 6) & 0x3f)) >>> 32;
  336. this._bytes[index++] = (0x80 + (ch & 0x3f)) >>> 32;
  337. }
  338. }
  339. // index is now probably less than @_offset and reflects the real length
  340. this._offset = index;
  341. return this;
  342. };
  343. ByteBuffer.prototype._copy = function(start, end) {
  344. // magic number here..
  345. // @see benchmark/buffer_slice_and_copy.js
  346. // if (end - start > 2048) {
  347. // return this._bytes.slice(start, end);
  348. // }
  349. const buf = Buffer.alloc(end - start);
  350. this._bytes.copy(buf, 0, start, end);
  351. return buf;
  352. };
  353. ByteBuffer.prototype.getRawStringByStringLength = function(index, length) {
  354. let needUpdateOffset = false;
  355. if (arguments.length === 1) {
  356. length = index;
  357. index = this._offset;
  358. needUpdateOffset = true;
  359. }
  360. const data = [];
  361. let bufLength = 0;
  362. while (length--) {
  363. let pos = index + bufLength;
  364. const ch = this._bytes[pos];
  365. if (ch < 0x80) {
  366. data.push(ch);
  367. bufLength += 1;
  368. } else if ((ch & 0xe0) === 0xc0) {
  369. const ch1 = this._bytes[++pos];
  370. const v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
  371. data.push(v);
  372. bufLength += 2;
  373. } else if ((ch & 0xf0) === 0xe0) {
  374. const ch1 = this._bytes[++pos];
  375. const ch2 = this._bytes[++pos];
  376. const v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
  377. data.push(v);
  378. bufLength += 3;
  379. } else {
  380. throw new Error('string is not valid UTF-8 encode');
  381. }
  382. }
  383. if (needUpdateOffset) this._offset += bufLength;
  384. return String.fromCharCode.apply(String, data);
  385. };
  386. ByteBuffer.prototype.getRawString = function(index, length) {
  387. if (typeof index !== 'number') {
  388. // getRawString() => current offset char string
  389. index = this._offset++;
  390. } else if (typeof length === 'number') {
  391. const data = [];
  392. for (let pos = index, end = index + length; pos < end; pos++) {
  393. const ch = this._bytes[pos];
  394. if (ch < 0x80) {
  395. data.push(ch);
  396. } else if ((ch & 0xe0) === 0xc0) {
  397. const ch1 = this._bytes[++pos];
  398. const v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
  399. data.push(v);
  400. } else if ((ch & 0xf0) === 0xe0) {
  401. const ch1 = this._bytes[++pos];
  402. const ch2 = this._bytes[++pos];
  403. const v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
  404. data.push(v);
  405. }
  406. }
  407. return String.fromCharCode.apply(String, data);
  408. }
  409. return String.fromCharCode(this._bytes[index]);
  410. };
  411. ByteBuffer.prototype.readRawString = function(index, length) {
  412. if (arguments.length === 2) {
  413. // readRawString(index, length)
  414. } else {
  415. // readRawString(length);
  416. length = index;
  417. index = this._offset;
  418. this._offset += length;
  419. }
  420. return this._bytes.toString('utf8', index, index + length);
  421. };
  422. [ 'getString', 'getCString' ].forEach(function(method) {
  423. ByteBuffer.prototype[method] = function(index) {
  424. let moveOffset = false;
  425. if (index === null || index === undefined) {
  426. index = this._offset;
  427. moveOffset = true;
  428. }
  429. let length = this.getInt(index);
  430. index += 4;
  431. if (moveOffset) {
  432. this._offset += 4 + length;
  433. }
  434. if (length === 0) {
  435. // empty string
  436. return '';
  437. }
  438. if (method === 'getCString') {
  439. length--;
  440. }
  441. return this._bytes.toString('utf8', index, index + length);
  442. };
  443. });
  444. ByteBuffer.prototype.putString = function(index, value) {
  445. if (typeof value === 'undefined') {
  446. value = index;
  447. index = null;
  448. }
  449. return this._putString(index, value, 'java');
  450. };
  451. ByteBuffer.prototype.putCString = function(index, value) {
  452. if (typeof value === 'undefined') {
  453. value = index;
  454. index = null;
  455. }
  456. return this._putString(index, value, 'c');
  457. };
  458. ByteBuffer.prototype.toString = function() {
  459. let s = '<ByteBuffer';
  460. for (let i = 0; i < this._offset; i++) {
  461. let c = this._bytes[i].toString('16');
  462. if (c.length === 1) {
  463. c = '0' + c;
  464. }
  465. s += ' ' + c;
  466. }
  467. s += '>';
  468. return s;
  469. };
  470. ByteBuffer.prototype.copy = ByteBuffer.prototype.array = function(start, end) {
  471. if (arguments.length === 0) {
  472. start = 0;
  473. end = this._offset;
  474. } else if (arguments.length === 1) {
  475. end = this._offset;
  476. }
  477. if (end > this._offset) {
  478. end = this._offset;
  479. }
  480. return this._copy(start, end);
  481. };
  482. ByteBuffer.prototype.memcpy = function(dest, start, end) {
  483. if (arguments.length === 1) {
  484. start = 0;
  485. end = this._offset;
  486. } else if (arguments.length === 2) {
  487. end = this._offset;
  488. }
  489. if (end > this._offset) {
  490. end = this._offset;
  491. }
  492. if (end - start > dest.length) {
  493. end = start + dest.length;
  494. }
  495. let j = 0;
  496. for (let i = start; i < end; i++) {
  497. dest[j++] = this._bytes[i];
  498. }
  499. return end - start;
  500. };
  501. ByteBuffer.prototype.position = function(newPosition) {
  502. if (typeof newPosition === 'number') {
  503. this._offset = newPosition;
  504. // make `bytes.position(1).read();` chain
  505. return this;
  506. }
  507. return this._offset;
  508. };
  509. ByteBuffer.prototype.skip = function(size) {
  510. this._offset += size;
  511. };
  512. ByteBuffer.prototype.flip = function() {
  513. // switch to read mode
  514. this.limit(this.position());
  515. this.position(0);
  516. return this;
  517. };
  518. ByteBuffer.prototype.clear = function() {
  519. this._limit = this._size;
  520. this._offset = 0;
  521. this._bytes = Buffer.alloc(this._size);
  522. return this;
  523. };
  524. ByteBuffer.prototype.limit = function(newLimit) {
  525. if (typeof newLimit === 'number') {
  526. if ((newLimit < 0) || (newLimit > this._size)) {
  527. throw new Error('IllegalArgumentException');
  528. }
  529. if (this._offset > newLimit) {
  530. this._offset = newLimit;
  531. }
  532. this._limit = newLimit;
  533. return this;
  534. }
  535. return this._limit;
  536. };
  537. ByteBuffer.prototype.capacity = function() {
  538. return this._size;
  539. };
  540. ByteBuffer.prototype.remaining = function() {
  541. return this.limit() - this.position();
  542. };
  543. ByteBuffer.prototype.hasRemaining = function() {
  544. return this.remaining() > 0;
  545. };
  546. ByteBuffer.prototype.checkArraySize = function(arrayLength, offset, length) {
  547. if ((offset < 0) || (length < 0) || (arrayLength < length + offset)) {
  548. throw new Error('IndexOutOfBoundsException');
  549. }
  550. };
  551. ByteBuffer.prototype.checkForUnderflow = function(length) {
  552. length = length || 0;
  553. if (this.remaining() < length) {
  554. throw new Error('BufferOverflowException');
  555. }
  556. };
  557. module.exports = ByteBuffer;