├── .gitignore ├── .mailmap ├── .npmignore ├── AUTHORS ├── BoyerMoore.h ├── LICENSE ├── README.md ├── binding.gyp ├── buffertools.cc ├── buffertools.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /buffertools.node 2 | /.lock-wscript 3 | /build/ 4 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # update AUTHORS with: 2 | # git log --all --reverse --format='%aN <%aE>' | perl -ne 'BEGIN{print "# Authors ordered by first contribution.\n"} print unless $h{$_}; $h{$_} = 1' > AUTHORS 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | buffertools.node 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution. 2 | Ben Noordhuis 3 | Stefan Thomas 4 | Nathan Rajlich 5 | Dane Springmeyer 6 | Barret Schloerke 7 | -------------------------------------------------------------------------------- /BoyerMoore.h: -------------------------------------------------------------------------------- 1 | /* adapted from http://en.wikipedia.org/wiki/Boyer–Moore_string_search_algorithm */ 2 | #ifndef BOYER_MOORE_H 3 | #define BOYER_MOORE_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define ALPHABET_SIZE (1 << CHAR_BIT) 10 | 11 | static void compute_prefix(const uint8_t* str, size_t size, int result[]) { 12 | size_t q; 13 | int k; 14 | result[0] = 0; 15 | 16 | k = 0; 17 | for (q = 1; q < size; q++) { 18 | while (k > 0 && str[k] != str[q]) 19 | k = result[k-1]; 20 | 21 | if (str[k] == str[q]) 22 | k++; 23 | 24 | result[q] = k; 25 | } 26 | } 27 | 28 | static void prepare_badcharacter_heuristic(const uint8_t *str, size_t size, int result[ALPHABET_SIZE]) { 29 | size_t i; 30 | 31 | for (i = 0; i < ALPHABET_SIZE; i++) 32 | result[i] = -1; 33 | 34 | for (i = 0; i < size; i++) 35 | result[str[i]] = i; 36 | } 37 | 38 | void prepare_goodsuffix_heuristic(const uint8_t *normal, const size_t size, int result[]) { 39 | const uint8_t *left = normal; 40 | const uint8_t *right = left + size; 41 | uint8_t * reversed = new uint8_t[size+1]; 42 | uint8_t *tmp = reversed + size; 43 | size_t i; 44 | 45 | /* reverse string */ 46 | *tmp = 0; 47 | while (left < right) 48 | *(--tmp) = *(left++); 49 | 50 | int * prefix_normal = new int[size]; 51 | int * prefix_reversed = new int[size]; 52 | 53 | compute_prefix(normal, size, prefix_normal); 54 | compute_prefix(reversed, size, prefix_reversed); 55 | 56 | for (i = 0; i <= size; i++) { 57 | result[i] = size - prefix_normal[size-1]; 58 | } 59 | 60 | for (i = 0; i < size; i++) { 61 | const int j = size - prefix_reversed[i]; 62 | const int k = i - prefix_reversed[i]+1; 63 | 64 | if (result[j] > k) 65 | result[j] = k; 66 | } 67 | 68 | delete[] reversed; 69 | delete[] prefix_normal; 70 | delete[] prefix_reversed; 71 | } 72 | 73 | /* 74 | * Boyer-Moore search algorithm 75 | */ 76 | const uint8_t *boyermoore_search(const uint8_t *haystack, size_t haystack_len, const uint8_t *needle, size_t needle_len) { 77 | /* 78 | * Simple checks 79 | */ 80 | if(haystack_len == 0) 81 | return NULL; 82 | if(needle_len == 0) 83 | return NULL; 84 | if(needle_len > haystack_len) 85 | return NULL; 86 | 87 | /* 88 | * Initialize heuristics 89 | */ 90 | int badcharacter[ALPHABET_SIZE]; 91 | int * goodsuffix = new int[needle_len+1]; 92 | 93 | prepare_badcharacter_heuristic(needle, needle_len, badcharacter); 94 | prepare_goodsuffix_heuristic(needle, needle_len, goodsuffix); 95 | 96 | /* 97 | * Boyer-Moore search 98 | */ 99 | size_t s = 0; 100 | while(s <= (haystack_len - needle_len)) 101 | { 102 | size_t j = needle_len; 103 | while(j > 0 && needle[j-1] == haystack[s+j-1]) 104 | j--; 105 | 106 | if(j > 0) 107 | { 108 | int k = badcharacter[haystack[s+j-1]]; 109 | int m; 110 | if(k < (int)j && (m = j-k-1) > goodsuffix[j]) 111 | s+= m; 112 | else 113 | s+= goodsuffix[j]; 114 | } 115 | else 116 | { 117 | delete[] goodsuffix; 118 | return haystack + s; 119 | } 120 | } 121 | 122 | delete[] goodsuffix; 123 | /* not found */ 124 | return NULL; 125 | } 126 | 127 | #endif /* BoyerMoore.h */ 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Ben Noordhuis 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-buffertools 2 | 3 | Utilities for manipulating buffers. 4 | 5 | **Deprecation warning: this module is no longer maintained 6 | and doesn't work with newer versions of Node.js. 7 | 8 | Most functionality is now available through the native `Buffer` class.** 9 | 10 | ## Installing the module 11 | 12 | Easy! With [npm](http://npmjs.org/): 13 | 14 | npm install buffertools 15 | 16 | From source: 17 | 18 | node-gyp configure 19 | node-gyp build 20 | 21 | Now you can include the module in your project. 22 | 23 | require('buffertools').extend(); // extend Buffer.prototype 24 | var buf = new Buffer(42); // create a 42 byte buffer 25 | buf.clear(); // clear it! 26 | 27 | If you don't want to extend the Buffer class's prototype (recommended): 28 | 29 | var buffertools = require('buffertools'); 30 | var buf = new Buffer(42); 31 | buffertools.clear(buf); 32 | 33 | ## Methods 34 | 35 | Note that most methods that take a buffer as an argument, will also accept a string. 36 | 37 | ### buffertools.extend([object], [object...]) 38 | 39 | Extend the arguments with the buffertools methods. If called without arguments, 40 | defaults to `[Buffer.prototype, SlowBuffer.prototype]`. Extending prototypes 41 | only makes sense for classes that derive from `Buffer`. 42 | 43 | buffertools v1.x extended the `Buffer` prototype by default. In v2.x, it is 44 | opt-in. The reason for that is that buffertools was originally developed for 45 | node.js v0.3 (or maybe v0.2, I don't remember exactly when buffers were added) 46 | where the `Buffer` class was devoid of any useful methods. Over the years, it 47 | has grown a number of utility methods, some of which conflict with the 48 | buffertools methods of the same name, like `Buffer#fill()`. 49 | 50 | ### Buffer#clear() 51 | ### buffertools.clear(buffer) 52 | 53 | Clear the buffer. This is equivalent to `Buffer#fill(0)`. 54 | Returns the buffer object so you can chain method calls. 55 | 56 | ### Buffer#compare(buffer|string) 57 | ### buffertools.compare(buffer, buffer|string) 58 | 59 | Lexicographically compare two buffers. Returns a number less than zero 60 | if a < b, zero if a == b or greater than zero if a > b. 61 | 62 | Buffers are considered equal when they are of the same length and contain 63 | the same binary data. 64 | 65 | Smaller buffers are considered to be less than larger ones. Some buffers 66 | find this hurtful. 67 | 68 | ### Buffer#concat(a, b, c, ...) 69 | ### buffertools.concat(a, b, c, ...) 70 | 71 | Concatenate two or more buffers/strings and return the result. Example: 72 | 73 | // identical to new Buffer('foobarbaz') 74 | a = new Buffer('foo'); 75 | b = new Buffer('bar'); 76 | c = a.concat(b, 'baz'); 77 | console.log(a, b, c); // "foo bar foobarbaz" 78 | 79 | // static variant 80 | buffertools.concat('foo', new Buffer('bar'), 'baz'); 81 | 82 | ### Buffer#equals(buffer|string) 83 | ### buffertools.equals(buffer, buffer|string) 84 | 85 | Returns true if this buffer equals the argument, false otherwise. 86 | 87 | Buffers are considered equal when they are of the same length and contain 88 | the same binary data. 89 | 90 | Caveat emptor: If your buffers contain strings with different character encodings, 91 | they will most likely *not* be equal. 92 | 93 | ### Buffer#fill(integer|string|buffer) 94 | ### buffertools.fill(buffer, integer|string|buffer) 95 | 96 | Fill the buffer (repeatedly if necessary) with the argument. 97 | Returns the buffer object so you can chain method calls. 98 | 99 | ### Buffer#fromHex() 100 | ### buffertools.fromHex(buffer) 101 | 102 | Assumes this buffer contains hexadecimal data (packed, no whitespace) 103 | and decodes it into binary data. Returns a new buffer with the decoded 104 | content. Throws an exception if non-hexadecimal data is encountered. 105 | 106 | ### Buffer#indexOf(buffer|string, [start=0]) 107 | ### buffertools.indexOf(buffer, buffer|string, [start=0]) 108 | 109 | Search this buffer for the first occurrence of the argument, starting at 110 | offset `start`. Returns the zero-based index or -1 if there is no match. 111 | 112 | ### Buffer#reverse() 113 | ### buffertools.reverse(buffer) 114 | 115 | Reverse the content of the buffer in place. Example: 116 | 117 | b = new Buffer('live'); 118 | b.reverse(); 119 | console.log(b); // "evil" 120 | 121 | ### Buffer#toHex() 122 | ### buffertools.toHex(buffer) 123 | 124 | Returns the contents of this buffer encoded as a hexadecimal string. 125 | 126 | ## Classes 127 | 128 | Singular, actually. To wit: 129 | 130 | ## WritableBufferStream 131 | 132 | This is a regular node.js [writable stream](http://nodejs.org/docs/v0.3.4/api/streams.html#writable_Stream) 133 | that accumulates the data it receives into a buffer. 134 | 135 | Example usage: 136 | 137 | // slurp stdin into a buffer 138 | process.stdin.resume(); 139 | ostream = new WritableBufferStream(); 140 | util.pump(process.stdin, ostream); 141 | console.log(ostream.getBuffer()); 142 | 143 | The stream never emits 'error' or 'drain' events. 144 | 145 | ### WritableBufferStream.getBuffer() 146 | 147 | Return the data accumulated so far as a buffer. 148 | 149 | ## TODO 150 | 151 | * Logical operations on buffers (AND, OR, XOR). 152 | * Add lastIndexOf() functions. 153 | 154 | ## License 155 | 156 | Copyright (c) 2010, Ben Noordhuis 157 | 158 | Permission to use, copy, modify, and/or distribute this software for any 159 | purpose with or without fee is hereby granted, provided that the above 160 | copyright notice and this permission notice appear in all copies. 161 | 162 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 163 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 164 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 165 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 166 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 167 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 168 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 169 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010, Ben Noordhuis 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | { 16 | 'targets': [ 17 | { 18 | 'target_name': 'buffertools', 19 | 'sources': [ 'buffertools.cc' ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /buffertools.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010, Ben Noordhuis 2 | * 3 | * Permission to use, copy, modify, and/or distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include "BoyerMoore.h" 17 | #include "node.h" 18 | #include "node_buffer.h" 19 | #include "node_version.h" 20 | #include "v8.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace { 30 | 31 | using v8::Exception; 32 | using v8::Handle; 33 | using v8::Local; 34 | using v8::Object; 35 | using v8::String; 36 | using v8::Value; 37 | 38 | #if NODE_MAJOR_VERSION > 0 || NODE_MINOR_VERSION > 10 39 | # define UNI_BOOLEAN_NEW(value) \ 40 | v8::Boolean::New(args.GetIsolate(), value) 41 | # if NODE_MAJOR_VERSION >= 3 42 | # define UNI_BUFFER_NEW(size) \ 43 | node::Buffer::New(args.GetIsolate(), size).ToLocalChecked() 44 | # else 45 | # define UNI_BUFFER_NEW(size) \ 46 | node::Buffer::New(args.GetIsolate(), size) 47 | # endif // NODE_MAJOR_VERSION >= 3 48 | # define UNI_CONST_ARGUMENTS(name) \ 49 | const v8::FunctionCallbackInfo& name 50 | # define UNI_ESCAPE(value) \ 51 | return handle_scope.Escape(value) 52 | # define UNI_ESCAPABLE_HANDLESCOPE() \ 53 | v8::EscapableHandleScope handle_scope(args.GetIsolate()) 54 | # define UNI_FUNCTION_CALLBACK(name) \ 55 | void name(const v8::FunctionCallbackInfo& args) 56 | # define UNI_HANDLESCOPE() \ 57 | v8::HandleScope handle_scope(args.GetIsolate()) 58 | # define UNI_INTEGER_NEW(value) \ 59 | v8::Integer::New(args.GetIsolate(), value) 60 | # define UNI_RETURN(value) \ 61 | args.GetReturnValue().Set(value) 62 | # define UNI_STRING_EMPTY() \ 63 | v8::String::Empty(args.GetIsolate()) 64 | # define UNI_STRING_NEW(string, size) \ 65 | v8::String::NewFromUtf8(args.GetIsolate(), \ 66 | string, \ 67 | v8::String::kNormalString, \ 68 | size) 69 | # define UNI_THROW_AND_RETURN(type, message) \ 70 | do { \ 71 | args.GetIsolate()->ThrowException( \ 72 | type(v8::String::NewFromUtf8(args.GetIsolate(), message))); \ 73 | return; \ 74 | } while (0) 75 | # define UNI_THROW_EXCEPTION(type, message) \ 76 | args.GetIsolate()->ThrowException( \ 77 | type(v8::String::NewFromUtf8(args.GetIsolate(), message))); 78 | #else // NODE_MAJOR_VERSION > 0 || NODE_MINOR_VERSION > 10 79 | # define UNI_BOOLEAN_NEW(value) \ 80 | v8::Local::New(v8::Boolean::New(value)) 81 | # define UNI_BUFFER_NEW(size) \ 82 | v8::Local::New(node::Buffer::New(size)->handle_) 83 | # define UNI_CONST_ARGUMENTS(name) \ 84 | const v8::Arguments& name 85 | # define UNI_ESCAPE(value) \ 86 | return handle_scope.Close(value) 87 | # define UNI_ESCAPABLE_HANDLESCOPE() \ 88 | v8::HandleScope handle_scope 89 | # define UNI_FUNCTION_CALLBACK(name) \ 90 | v8::Handle name(const v8::Arguments& args) 91 | # define UNI_HANDLESCOPE() \ 92 | v8::HandleScope handle_scope 93 | # define UNI_INTEGER_NEW(value) \ 94 | v8::Integer::New(value) 95 | # define UNI_RETURN(value) \ 96 | return handle_scope.Close(value) 97 | # define UNI_STRING_EMPTY() \ 98 | v8::String::Empty() 99 | # define UNI_STRING_NEW(string, size) \ 100 | v8::String::New(string, size) 101 | # define UNI_THROW_AND_RETURN(type, message) \ 102 | return v8::ThrowException(v8::String::New(message)) 103 | # define UNI_THROW_EXCEPTION(type, message) \ 104 | v8::ThrowException(v8::String::New(message)) 105 | #endif // NODE_MAJOR_VERSION > 0 || NODE_MINOR_VERSION > 10 106 | 107 | #if defined(_WIN32) 108 | // Emulate snprintf() on windows, _snprintf() doesn't zero-terminate 109 | // the buffer on overflow. 110 | inline int snprintf(char* buf, size_t size, const char* fmt, ...) { 111 | va_list ap; 112 | va_start(ap, fmt); 113 | const int len = _vsprintf_p(buf, size, fmt, ap); 114 | va_end(ap); 115 | if (len < 0) { 116 | abort(); 117 | } 118 | if (static_cast(len) >= size && size > 0) { 119 | buf[size - 1] = '\0'; 120 | } 121 | return len; 122 | } 123 | #endif 124 | 125 | // this is an application of the Curiously Recurring Template Pattern 126 | template struct UnaryAction { 127 | Local apply(Local buffer, 128 | UNI_CONST_ARGUMENTS(args), 129 | uint32_t args_start); 130 | 131 | Local operator()(UNI_CONST_ARGUMENTS(args)) { 132 | UNI_ESCAPABLE_HANDLESCOPE(); 133 | 134 | uint32_t args_start = 0; 135 | Local target = args.This(); 136 | if (node::Buffer::HasInstance(target)) { 137 | // Invoked as prototype method, no action required. 138 | } else if (node::Buffer::HasInstance(args[0])) { 139 | // First argument is the target buffer. 140 | args_start = 1; 141 | target = args[0]->ToObject(); 142 | } else { 143 | UNI_THROW_EXCEPTION(Exception::TypeError, 144 | "Argument should be a buffer object."); 145 | return Local(); 146 | } 147 | 148 | UNI_ESCAPE(static_cast(this)->apply(target, args, args_start)); 149 | } 150 | }; 151 | 152 | template struct BinaryAction { 153 | Local apply(Local buffer, 154 | const uint8_t* data, 155 | size_t size, 156 | UNI_CONST_ARGUMENTS(args), 157 | uint32_t args_start); 158 | 159 | Local operator()(UNI_CONST_ARGUMENTS(args)) { 160 | UNI_ESCAPABLE_HANDLESCOPE(); 161 | 162 | uint32_t args_start = 0; 163 | Local target = args.This(); 164 | if (node::Buffer::HasInstance(target)) { 165 | // Invoked as prototype method, no action required. 166 | } else if (node::Buffer::HasInstance(args[0])) { 167 | // First argument is the target buffer. 168 | args_start = 1; 169 | target = args[0]->ToObject(); 170 | } else { 171 | UNI_THROW_EXCEPTION(Exception::TypeError, 172 | "Argument should be a buffer object."); 173 | return Local(); 174 | } 175 | 176 | if (args[args_start]->IsString()) { 177 | String::Utf8Value s(args[args_start]); 178 | UNI_ESCAPE(static_cast(this)->apply( 179 | target, 180 | (const uint8_t*) *s, 181 | s.length(), 182 | args, 183 | args_start)); 184 | } 185 | 186 | if (node::Buffer::HasInstance(args[args_start])) { 187 | Local other = args[args_start]->ToObject(); 188 | UNI_ESCAPE(static_cast(this)->apply( 189 | target, 190 | (const uint8_t*) node::Buffer::Data(other), 191 | node::Buffer::Length(other), 192 | args, 193 | args_start)); 194 | } 195 | 196 | UNI_THROW_EXCEPTION(Exception::TypeError, 197 | "Second argument must be a string or a buffer."); 198 | return Local(); 199 | } 200 | }; 201 | 202 | // 203 | // helper functions 204 | // 205 | Local clear(Local buffer, int c) { 206 | size_t length = node::Buffer::Length(buffer); 207 | uint8_t* data = (uint8_t*) node::Buffer::Data(buffer); 208 | memset(data, c, length); 209 | return buffer; 210 | } 211 | 212 | Local fill(Local buffer, void* pattern, size_t size) { 213 | size_t length = node::Buffer::Length(buffer); 214 | uint8_t* data = (uint8_t*) node::Buffer::Data(buffer); 215 | 216 | if (size >= length) { 217 | memcpy(data, pattern, length); 218 | } else { 219 | const int n_copies = length / size; 220 | const int remainder = length % size; 221 | for (int i = 0; i < n_copies; i++) { 222 | memcpy(data + size * i, pattern, size); 223 | } 224 | memcpy(data + size * n_copies, pattern, remainder); 225 | } 226 | 227 | return buffer; 228 | } 229 | 230 | int compare(Local buffer, const uint8_t* data2, size_t length2) { 231 | size_t length = node::Buffer::Length(buffer); 232 | if (length != length2) { 233 | return length > length2 ? 1 : -1; 234 | } 235 | 236 | const uint8_t* data = (const uint8_t*) node::Buffer::Data(buffer); 237 | return memcmp(data, data2, length); 238 | } 239 | 240 | // 241 | // actions 242 | // 243 | struct ClearAction: UnaryAction { 244 | Local apply(Local buffer, 245 | UNI_CONST_ARGUMENTS(args), 246 | uint32_t args_start) { 247 | return clear(buffer, 0); 248 | } 249 | }; 250 | 251 | struct FillAction: UnaryAction { 252 | Local apply(Local buffer, 253 | UNI_CONST_ARGUMENTS(args), 254 | uint32_t args_start) { 255 | if (args[args_start]->IsInt32()) { 256 | int c = args[args_start]->Int32Value(); 257 | return clear(buffer, c); 258 | } 259 | 260 | if (args[args_start]->IsString()) { 261 | String::Utf8Value s(args[args_start]); 262 | return fill(buffer, *s, s.length()); 263 | } 264 | 265 | if (node::Buffer::HasInstance(args[args_start])) { 266 | Local other = args[args_start]->ToObject(); 267 | size_t length = node::Buffer::Length(other); 268 | uint8_t* data = (uint8_t*) node::Buffer::Data(other); 269 | return fill(buffer, data, length); 270 | } 271 | 272 | UNI_THROW_EXCEPTION(Exception::TypeError, 273 | "Second argument should be either a string, a buffer " 274 | "or an integer."); 275 | return Local(); 276 | } 277 | }; 278 | 279 | struct ReverseAction: UnaryAction { 280 | // O(n/2) for all cases which is okay, might be optimized some more with whole-word swaps 281 | // XXX won't this trash the L1 cache something awful? 282 | Local apply(Local buffer, 283 | UNI_CONST_ARGUMENTS(args), 284 | uint32_t args_start) { 285 | uint8_t* head = (uint8_t*) node::Buffer::Data(buffer); 286 | uint8_t* tail = head + node::Buffer::Length(buffer); 287 | 288 | while (head < tail) { 289 | --tail; 290 | uint8_t t = *head; 291 | *head = *tail; 292 | *tail = t; 293 | ++head; 294 | } 295 | 296 | return buffer; 297 | } 298 | }; 299 | 300 | struct EqualsAction: BinaryAction { 301 | Local apply(Local buffer, 302 | const uint8_t* data, 303 | size_t size, 304 | UNI_CONST_ARGUMENTS(args), 305 | uint32_t args_start) { 306 | return UNI_BOOLEAN_NEW(compare(buffer, data, size) == 0); 307 | } 308 | }; 309 | 310 | struct CompareAction: BinaryAction { 311 | Local apply(Local buffer, 312 | const uint8_t* data, 313 | size_t size, 314 | UNI_CONST_ARGUMENTS(args), 315 | uint32_t args_start) { 316 | return UNI_INTEGER_NEW(compare(buffer, data, size)); 317 | } 318 | }; 319 | 320 | struct IndexOfAction: BinaryAction { 321 | Local apply(Local buffer, 322 | const uint8_t* data2, 323 | size_t size2, 324 | UNI_CONST_ARGUMENTS(args), 325 | uint32_t args_start) { 326 | const uint8_t* data = (const uint8_t*) node::Buffer::Data(buffer); 327 | const size_t size = node::Buffer::Length(buffer); 328 | 329 | int32_t start = args[args_start + 1]->Int32Value(); 330 | 331 | if (start < 0) 332 | start = size - std::min(size, -start); 333 | else if (static_cast(start) > size) 334 | start = size; 335 | 336 | const uint8_t* p = boyermoore_search( 337 | data + start, size - start, data2, size2); 338 | 339 | const ptrdiff_t offset = p ? (p - data) : -1; 340 | return UNI_INTEGER_NEW(offset); 341 | } 342 | }; 343 | 344 | static char toHexTable[] = "0123456789abcdef"; 345 | 346 | // CHECKME is this cache efficient? 347 | static signed char fromHexTable[] = { 348 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 349 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1, 350 | 10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 351 | 10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 352 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 353 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 354 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 355 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 356 | }; 357 | 358 | inline Local decodeHex(const uint8_t* const data, 359 | const size_t size, 360 | UNI_CONST_ARGUMENTS(args), 361 | uint32_t args_start) { 362 | if (size & 1) { 363 | UNI_THROW_EXCEPTION(Exception::Error, 364 | "Odd string length, this is not hexadecimal data."); 365 | return Local(); 366 | } 367 | 368 | if (size == 0) { 369 | return UNI_STRING_EMPTY(); 370 | } 371 | 372 | Local buffer = UNI_BUFFER_NEW(size / 2); 373 | uint8_t *src = (uint8_t *) data; 374 | uint8_t *dst = (uint8_t *) (const uint8_t*) node::Buffer::Data(buffer); 375 | 376 | for (size_t i = 0; i < size; i += 2) { 377 | int a = fromHexTable[*src++]; 378 | int b = fromHexTable[*src++]; 379 | 380 | if (a == -1 || b == -1) { 381 | UNI_THROW_EXCEPTION(Exception::Error, "This is not hexadecimal data."); 382 | return Local(); 383 | } 384 | 385 | *dst++ = b | (a << 4); 386 | } 387 | 388 | return buffer; 389 | } 390 | 391 | struct FromHexAction: UnaryAction { 392 | Local apply(Local buffer, 393 | UNI_CONST_ARGUMENTS(args), 394 | uint32_t args_start) { 395 | const uint8_t* data = (const uint8_t*) node::Buffer::Data(buffer); 396 | size_t length = node::Buffer::Length(buffer); 397 | return decodeHex(data, length, args, args_start); 398 | } 399 | }; 400 | 401 | struct ToHexAction: UnaryAction { 402 | Local apply(Local buffer, 403 | UNI_CONST_ARGUMENTS(args), 404 | uint32_t args_start) { 405 | const size_t size = node::Buffer::Length(buffer); 406 | const uint8_t* data = (const uint8_t*) node::Buffer::Data(buffer); 407 | 408 | if (size == 0) { 409 | return UNI_STRING_EMPTY(); 410 | } 411 | 412 | std::string s(size * 2, 0); 413 | for (size_t i = 0; i < size; ++i) { 414 | const uint8_t c = (uint8_t) data[i]; 415 | s[i * 2] = toHexTable[c >> 4]; 416 | s[i * 2 + 1] = toHexTable[c & 15]; 417 | } 418 | 419 | return UNI_STRING_NEW(s.c_str(), s.size()); 420 | } 421 | }; 422 | 423 | // 424 | // V8 function callbacks 425 | // 426 | #define V(name) \ 427 | UNI_FUNCTION_CALLBACK(name) { \ 428 | UNI_HANDLESCOPE(); \ 429 | UNI_RETURN(name ## Action()(args)); \ 430 | } 431 | V(Clear) 432 | V(Compare) 433 | V(Equals) 434 | V(Fill) 435 | V(FromHex) 436 | V(IndexOf) 437 | V(Reverse) 438 | V(ToHex) 439 | #undef V 440 | 441 | UNI_FUNCTION_CALLBACK(Concat) { 442 | UNI_HANDLESCOPE(); 443 | 444 | size_t size = 0; 445 | for (int index = 0, length = args.Length(); index < length; ++index) { 446 | Local arg = args[index]; 447 | if (arg->IsString()) { 448 | // Utf8Length() because we need the length in bytes, not characters 449 | size += arg->ToString()->Utf8Length(); 450 | } 451 | else if (node::Buffer::HasInstance(arg)) { 452 | size += node::Buffer::Length(arg->ToObject()); 453 | } 454 | else { 455 | char errmsg[256]; 456 | snprintf(errmsg, 457 | sizeof(errmsg), 458 | "Argument #%lu is neither a string nor a buffer object.", 459 | static_cast(index)); 460 | UNI_THROW_AND_RETURN(Exception::TypeError, errmsg); 461 | } 462 | } 463 | 464 | Local buffer = UNI_BUFFER_NEW(size); 465 | uint8_t* s = (uint8_t*) node::Buffer::Data(buffer); 466 | 467 | for (int index = 0, length = args.Length(); index < length; ++index) { 468 | Local arg = args[index]; 469 | if (arg->IsString()) { 470 | String::Utf8Value v(arg); 471 | memcpy(s, *v, v.length()); 472 | s += v.length(); 473 | } 474 | else if (node::Buffer::HasInstance(arg)) { 475 | Local b = arg->ToObject(); 476 | const uint8_t* data = (const uint8_t*) node::Buffer::Data(b); 477 | size_t length = node::Buffer::Length(b); 478 | memcpy(s, data, length); 479 | s += length; 480 | } 481 | else { 482 | UNI_THROW_AND_RETURN(Exception::Error, 483 | "Congratulations! You have run into a bug: argument " 484 | "is neither a string nor a buffer object. Please " 485 | "make the world a better place and report it."); 486 | } 487 | } 488 | 489 | UNI_RETURN(buffer); 490 | } 491 | 492 | void RegisterModule(Handle target) { 493 | NODE_SET_METHOD(target, "clear", Clear); 494 | NODE_SET_METHOD(target, "compare", Compare); 495 | NODE_SET_METHOD(target, "concat", Concat); 496 | NODE_SET_METHOD(target, "equals", Equals); 497 | NODE_SET_METHOD(target, "fill", Fill); 498 | NODE_SET_METHOD(target, "fromHex", FromHex); 499 | NODE_SET_METHOD(target, "indexOf", IndexOf); 500 | NODE_SET_METHOD(target, "reverse", Reverse); 501 | NODE_SET_METHOD(target, "toHex", ToHex); 502 | } 503 | 504 | } // anonymous namespace 505 | 506 | NODE_MODULE(buffertools, RegisterModule) 507 | -------------------------------------------------------------------------------- /buffertools.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010, Ben Noordhuis 2 | * 3 | * Permission to use, copy, modify, and/or distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | var SlowBuffer = require('buffer').SlowBuffer; 17 | var Buffer = require('buffer').Buffer; 18 | 19 | // requires node 3.1 20 | var events = require('events'); 21 | var util = require('util'); 22 | 23 | try { 24 | var buffertools = require('./build/Release/buffertools.node'); 25 | } catch (e) { 26 | if (e.code !== 'MODULE_NOT_FOUND') throw e; 27 | var buffertools = require('./build/Debug/buffertools.node'); 28 | } 29 | 30 | exports.extend = function() { 31 | var receivers; 32 | if (arguments.length > 0) { 33 | receivers = Array.prototype.slice.call(arguments); 34 | } else if (typeof SlowBuffer === 'function') { 35 | receivers = [Buffer.prototype, SlowBuffer.prototype]; 36 | } else { 37 | receivers = [Buffer.prototype]; 38 | } 39 | for (var i = 0, n = receivers.length; i < n; i += 1) { 40 | var receiver = receivers[i]; 41 | for (var key in buffertools) { 42 | receiver[key] = buffertools[key]; 43 | } 44 | if (receiver !== exports) { 45 | receiver.concat = function() { 46 | var args = [this].concat(Array.prototype.slice.call(arguments)); 47 | return buffertools.concat.apply(buffertools, args); 48 | }; 49 | } 50 | } 51 | }; 52 | exports.extend(exports); 53 | 54 | // 55 | // WritableBufferStream 56 | // 57 | // - never emits 'error' 58 | // - never emits 'drain' 59 | // 60 | function WritableBufferStream() { 61 | this.writable = true; 62 | this.buffer = null; 63 | } 64 | 65 | util.inherits(WritableBufferStream, events.EventEmitter); 66 | 67 | WritableBufferStream.prototype._append = function(buffer, encoding) { 68 | if (!this.writable) { 69 | throw new Error('Stream is not writable.'); 70 | } 71 | 72 | if (Buffer.isBuffer(buffer)) { 73 | // no action required 74 | } 75 | else if (typeof buffer == 'string') { 76 | // TODO optimize 77 | buffer = new Buffer(buffer, encoding || 'utf8'); 78 | } 79 | else { 80 | throw new Error('Argument should be either a buffer or a string.'); 81 | } 82 | 83 | // FIXME optimize! 84 | if (this.buffer) { 85 | this.buffer = buffertools.concat(this.buffer, buffer); 86 | } 87 | else { 88 | this.buffer = new Buffer(buffer.length); 89 | buffer.copy(this.buffer); 90 | } 91 | }; 92 | 93 | WritableBufferStream.prototype.write = function(buffer, encoding) { 94 | this._append(buffer, encoding); 95 | 96 | // signal that it's safe to immediately write again 97 | return true; 98 | }; 99 | 100 | WritableBufferStream.prototype.end = function(buffer, encoding) { 101 | if (buffer) { 102 | this._append(buffer, encoding); 103 | } 104 | 105 | this.emit('close'); 106 | 107 | this.writable = false; 108 | }; 109 | 110 | WritableBufferStream.prototype.getBuffer = function() { 111 | if (this.buffer) { 112 | return this.buffer; 113 | } 114 | return new Buffer(0); 115 | }; 116 | 117 | WritableBufferStream.prototype.toString = function() { 118 | return this.getBuffer().toString(); 119 | }; 120 | 121 | exports.WritableBufferStream = WritableBufferStream; 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buffertools", 3 | "main": "buffertools", 4 | "version": "2.1.7", 5 | "keywords": ["buffer", "buffers"], 6 | "description": "Working with node.js buffers made easy.", 7 | "homepage": "https://github.com/bnoordhuis/node-buffertools", 8 | "author": { 9 | "name": "Ben Noordhuis", 10 | "email": "info@bnoordhuis.nl", 11 | "url": "http://bnoordhuis.nl/" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/bnoordhuis/node-buffertools.git" 16 | }, 17 | "engines": { 18 | "node": ">=0.3.0" 19 | }, 20 | "scripts": { 21 | "test": "node test.js" 22 | }, 23 | "license": "ISC", 24 | "readmeFilename": "README.md" 25 | } 26 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010, Ben Noordhuis 2 | * 3 | * Permission to use, copy, modify, and/or distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | var buffertools = require('./buffertools'); 17 | var Buffer = require('buffer').Buffer; 18 | var assert = require('assert'); 19 | 20 | var WritableBufferStream = buffertools.WritableBufferStream; 21 | 22 | // Extend Buffer.prototype and SlowBuffer.prototype. 23 | buffertools.extend(); 24 | 25 | // these trigger the code paths for UnaryAction and BinaryAction 26 | assert.throws(function() { buffertools.clear({}); }); 27 | assert.throws(function() { buffertools.equals({}, {}); }); 28 | 29 | var a = new Buffer('abcd'), b = new Buffer('abcd'), c = new Buffer('efgh'); 30 | assert.ok(a.equals(b)); 31 | assert.ok(!a.equals(c)); 32 | assert.ok(a.equals('abcd')); 33 | assert.ok(!a.equals('efgh')); 34 | 35 | assert.ok(a.compare(a) == 0); 36 | assert.ok(a.compare(c) < 0); 37 | assert.ok(c.compare(a) > 0); 38 | 39 | assert.ok(a.compare('abcd') == 0); 40 | assert.ok(a.compare('efgh') < 0); 41 | assert.ok(c.compare('abcd') > 0); 42 | 43 | b = new Buffer('****'); 44 | assert.equal(b, b.clear()); 45 | assert.equal(b.inspect(), ''); // FIXME brittle test 46 | 47 | b = new Buffer(4); 48 | assert.equal(b, b.fill(42)); 49 | assert.equal(b.inspect(), ''); 50 | 51 | b = new Buffer(4); 52 | assert.equal(b, b.fill('*')); 53 | assert.equal(b.inspect(), ''); 54 | 55 | b = new Buffer(4); 56 | assert.equal(b, b.fill('ab')); 57 | assert.equal(b.inspect(), ''); 58 | 59 | b = new Buffer(4); 60 | assert.equal(b, b.fill('abcd1234')); 61 | assert.equal(b.inspect(), ''); 62 | 63 | b = new Buffer('Hello, world!'); 64 | assert.equal(-1, b.indexOf(new Buffer('foo'))); 65 | assert.equal(0, b.indexOf(new Buffer('Hell'))); 66 | assert.equal(7, b.indexOf(new Buffer('world'))); 67 | assert.equal(7, b.indexOf(new Buffer('world!'))); 68 | assert.equal(-1, b.indexOf('foo')); 69 | assert.equal(0, b.indexOf('Hell')); 70 | assert.equal(7, b.indexOf('world')); 71 | assert.equal(-1, b.indexOf('')); 72 | assert.equal(-1, b.indexOf('x')); 73 | assert.equal(7, b.indexOf('w')); 74 | assert.equal(0, b.indexOf('Hello, world!')); 75 | assert.equal(-1, b.indexOf('Hello, world!1')); 76 | assert.equal(7, b.indexOf('world', 7)); 77 | assert.equal(-1, b.indexOf('world', 8)); 78 | assert.equal(7, b.indexOf('world', -256)); 79 | assert.equal(7, b.indexOf('world', -6)); 80 | assert.equal(-1, b.indexOf('world', -5)); 81 | assert.equal(-1, b.indexOf('world', 256)); 82 | assert.equal(-1, b.indexOf('', 256)); 83 | 84 | b = new Buffer("\t \r\n"); 85 | assert.equal('09200d0a', b.toHex()); 86 | assert.equal(b.toString(), new Buffer('09200d0a').fromHex().toString()); 87 | 88 | // https://github.com/bnoordhuis/node-buffertools/pull/9 89 | b = new Buffer(4); 90 | b[0] = 0x98; 91 | b[1] = 0x95; 92 | b[2] = 0x60; 93 | b[3] = 0x2f; 94 | assert.equal('9895602f', b.toHex()); 95 | 96 | assert.equal('', buffertools.concat()); 97 | assert.equal('', buffertools.concat('')); 98 | assert.equal('foobar', new Buffer('foo').concat('bar')); 99 | assert.equal('foobarbaz', buffertools.concat(new Buffer('foo'), 'bar', new Buffer('baz'))); 100 | assert.throws(function() { buffertools.concat('foo', 123, 'baz'); }); 101 | // assert that the buffer is copied, not returned as-is 102 | a = new Buffer('For great justice.'), b = buffertools.concat(a); 103 | assert.equal(a.toString(), b.toString()); 104 | assert.notEqual(a, b); 105 | 106 | assert.equal('', new Buffer('').reverse()); 107 | assert.equal('For great justice.', new Buffer('.ecitsuj taerg roF').reverse()); 108 | 109 | // bug fix, see http://github.com/bnoordhuis/node-buffertools/issues#issue/5 110 | var endOfHeader = new Buffer('\r\n\r\n'); 111 | assert.equal(0, endOfHeader.indexOf(endOfHeader)); 112 | assert.equal(0, endOfHeader.indexOf('\r\n\r\n')); 113 | 114 | // feature request, see https://github.com/bnoordhuis/node-buffertools/issues#issue/8 115 | var closed = false; 116 | var stream = new WritableBufferStream(); 117 | 118 | stream.on('close', function() { closed = true; }); 119 | stream.write('Hello,'); 120 | stream.write(' '); 121 | stream.write('world!'); 122 | stream.end(); 123 | 124 | assert.equal(true, closed); 125 | assert.equal(false, stream.writable); 126 | assert.equal('Hello, world!', stream.toString()); 127 | assert.equal('Hello, world!', stream.getBuffer().toString()); 128 | 129 | // closed stream should throw 130 | assert.throws(function() { stream.write('ZIG!'); }); 131 | 132 | // GH-10 indexOf sometimes incorrectly returns -1 133 | for (var i = 0; i < 100; i++) { 134 | var buffer = new Buffer('9A8B3F4491734D18DEFC6D2FA96A2D3BC1020EECB811F037F977D039B4713B1984FBAB40FCB4D4833D4A31C538B76EB50F40FA672866D8F50D0A1063666721B8D8322EDEEC74B62E5F5B959393CD3FCE831CC3D1FA69D79C758853AFA3DC54D411043263596BAD1C9652970B80869DD411E82301DF93D47DCD32421A950EF3E555152E051C6943CC3CA71ED0461B37EC97C5A00EBACADAA55B9A7835F148DEF8906914617C6BD3A38E08C14735FC2EFE075CC61DFE5F2F9686AB0D0A3926604E320160FDC1A4488A323CB4308CDCA4FD9701D87CE689AF999C5C409854B268D00B063A89C2EEF6673C80A4F4D8D0A00163082EDD20A2F1861512F6FE9BB479A22A3D4ACDD2AA848254BA74613190957C7FCD106BF7441946D0E1A562DA68BC37752B1551B8855C8DA08DFE588902D44B2CAB163F3D7D7706B9CC78900D0AFD5DAE5492535A17DB17E24389F3BAA6F5A95B9F6FE955193D40932B5988BC53E49CAC81955A28B81F7B36A1EDA3B4063CBC187B0488FCD51FAE71E4FBAEE56059D847591B960921247A6B7C5C2A7A757EC62A2A2A2A2A2A2A25552591C03EF48994BD9F594A5E14672F55359EF1B38BF2976D1216C86A59847A6B7C4A5C585A0D0A2A6D9C8F8B9E999C2A836F786D577A79816F7C577A797D7E576B506B57A05B5B8C4A8D99989E8B8D9E644A6B9D9D8F9C9E4A504A6B968B93984A93984A988FA19D919C999F9A4A8B969E588C93988B9C938F9D588D8B9C9E9999989D58909C8F988D92588E0D0A3D79656E642073697A653D373035393620706172743D31207063726333323D33616230646235300D0A2E0D0A').fromHex(); 135 | assert.equal(551, buffer.indexOf('=yend')); 136 | } 137 | --------------------------------------------------------------------------------