ruby-watchman 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ext/ruby-watchman/depend +5 -0
- data/ext/ruby-watchman/extconf.rb +24 -0
- data/ext/ruby-watchman/watchman.c +698 -0
- data/ext/ruby-watchman/watchman.h +60 -0
- data/lib/ruby-watchman.rb +3 -0
- data/lib/ruby-watchman/version.rb +5 -0
- data/spec/ruby_watchman_spec.rb +397 -0
- data/spec/spec_helper.rb +5 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d97cec0f4ed9e90d780a524893af100bf3c0d05f
|
4
|
+
data.tar.gz: 7fff355ab24e5b9d8ef7a27ac365a71eb8f88027
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3885a5743a0fcef6ac94f7ef08e9ff0fa4010671fb786e7e2e5b3b07f4fbd4024e04378da32ddebb3be140d0f58951c4b9a957ed8035cd8f69d0b3ea09e9a635
|
7
|
+
data.tar.gz: 714c4f1c4d3cab0c710adb6fec2655a57fdb9b811d5586b8076001a1e9e4a4f3804856bef27b2297d4f4b28d3e08026fbd4f7d2fc6b428257dd5fff0c840657f
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright (c) 2014-present Facebook. All Rights Reserved.
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
def header(item)
|
6
|
+
unless find_header(item)
|
7
|
+
puts "couldn't find #{item} (required)"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# mandatory headers
|
13
|
+
header('ruby.h')
|
14
|
+
header('fcntl.h')
|
15
|
+
header('sys/errno.h')
|
16
|
+
header('sys/socket.h')
|
17
|
+
|
18
|
+
# variable headers
|
19
|
+
have_header('ruby/st.h') # >= 1.9; sets HAVE_RUBY_ST_H
|
20
|
+
have_header('st.h') # 1.8; sets HAVE_ST_H
|
21
|
+
|
22
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
23
|
+
|
24
|
+
create_makefile('ruby-watchman/ext')
|
@@ -0,0 +1,698 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2014, Facebook, Inc.
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
* Neither the name Facebook nor the names of its contributors may be used to
|
16
|
+
endorse or promote products derived from this software without specific
|
17
|
+
prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
*/
|
30
|
+
|
31
|
+
#include "watchman.h"
|
32
|
+
|
33
|
+
#if defined(HAVE_RUBY_ST_H)
|
34
|
+
#include <ruby/st.h>
|
35
|
+
#elif defined(HAVE_ST_H)
|
36
|
+
#include <st.h>
|
37
|
+
#else
|
38
|
+
#error no st.h header found
|
39
|
+
#endif
|
40
|
+
|
41
|
+
#include <fcntl.h> /* for fcntl() */
|
42
|
+
#include <sys/errno.h> /* for errno */
|
43
|
+
#include <sys/socket.h> /* for recv(), MSG_PEEK */
|
44
|
+
|
45
|
+
typedef struct {
|
46
|
+
uint8_t *data; // payload
|
47
|
+
size_t cap; // total capacity
|
48
|
+
size_t len; // current length
|
49
|
+
} watchman_t;
|
50
|
+
|
51
|
+
// Forward declarations:
|
52
|
+
VALUE watchman_load(char **ptr, char *end);
|
53
|
+
void watchman_dump(watchman_t *w, VALUE serializable);
|
54
|
+
|
55
|
+
#define WATCHMAN_DEFAULT_STORAGE 4096
|
56
|
+
|
57
|
+
#define WATCHMAN_BINARY_MARKER "\x00\x01"
|
58
|
+
#define WATCHMAN_ARRAY_MARKER 0x00
|
59
|
+
#define WATCHMAN_HASH_MARKER 0x01
|
60
|
+
#define WATCHMAN_STRING_MARKER 0x02
|
61
|
+
#define WATCHMAN_INT8_MARKER 0x03
|
62
|
+
#define WATCHMAN_INT16_MARKER 0x04
|
63
|
+
#define WATCHMAN_INT32_MARKER 0x05
|
64
|
+
#define WATCHMAN_INT64_MARKER 0x06
|
65
|
+
#define WATCHMAN_FLOAT_MARKER 0x07
|
66
|
+
#define WATCHMAN_TRUE 0x08
|
67
|
+
#define WATCHMAN_FALSE 0x09
|
68
|
+
#define WATCHMAN_NIL 0x0a
|
69
|
+
#define WATCHMAN_TEMPLATE_MARKER 0x0b
|
70
|
+
#define WATCHMAN_SKIP_MARKER 0x0c
|
71
|
+
|
72
|
+
#define WATCHMAN_HEADER \
|
73
|
+
WATCHMAN_BINARY_MARKER \
|
74
|
+
"\x06" \
|
75
|
+
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
76
|
+
|
77
|
+
static const char watchman_array_marker = WATCHMAN_ARRAY_MARKER;
|
78
|
+
static const char watchman_hash_marker = WATCHMAN_HASH_MARKER;
|
79
|
+
static const char watchman_string_marker = WATCHMAN_STRING_MARKER;
|
80
|
+
static const char watchman_true = WATCHMAN_TRUE;
|
81
|
+
static const char watchman_false = WATCHMAN_FALSE;
|
82
|
+
static const char watchman_nil = WATCHMAN_NIL;
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Appends `len` bytes, starting at `data`, to the watchman_t struct `w`
|
86
|
+
*
|
87
|
+
* Will attempt to reallocate the underlying storage if it is not sufficient.
|
88
|
+
*/
|
89
|
+
void watchman_append(watchman_t *w, const char *data, size_t len) {
|
90
|
+
if (w->len + len > w->cap) {
|
91
|
+
w->cap += w->len + WATCHMAN_DEFAULT_STORAGE;
|
92
|
+
REALLOC_N(w->data, uint8_t, w->cap);
|
93
|
+
}
|
94
|
+
memcpy(w->data + w->len, data, len);
|
95
|
+
w->len += len;
|
96
|
+
}
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Allocate a new watchman_t struct
|
100
|
+
*
|
101
|
+
* The struct has a small amount of extra capacity preallocated, and a blank
|
102
|
+
* header that can be filled in later to describe the PDU.
|
103
|
+
*/
|
104
|
+
watchman_t *watchman_init() {
|
105
|
+
watchman_t *w = ALLOC(watchman_t);
|
106
|
+
w->cap = WATCHMAN_DEFAULT_STORAGE;
|
107
|
+
w->len = 0;
|
108
|
+
w->data = ALLOC_N(uint8_t, WATCHMAN_DEFAULT_STORAGE);
|
109
|
+
|
110
|
+
watchman_append(w, WATCHMAN_HEADER, sizeof(WATCHMAN_HEADER) - 1);
|
111
|
+
return w;
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Free a watchman_t struct `w` that was previously allocated with
|
116
|
+
* `watchman_init`
|
117
|
+
*/
|
118
|
+
void watchman_free(watchman_t *w) {
|
119
|
+
xfree(w->data);
|
120
|
+
xfree(w);
|
121
|
+
}
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Encodes and appends the integer `num` to `w`
|
125
|
+
*/
|
126
|
+
void watchman_dump_int(watchman_t *w, int64_t num) {
|
127
|
+
char encoded[1 + sizeof(int64_t)];
|
128
|
+
|
129
|
+
if (num == (int8_t)num) {
|
130
|
+
encoded[0] = WATCHMAN_INT8_MARKER;
|
131
|
+
encoded[1] = (int8_t)num;
|
132
|
+
watchman_append(w, encoded, 1 + sizeof(int8_t));
|
133
|
+
} else if (num == (int16_t)num) {
|
134
|
+
encoded[0] = WATCHMAN_INT16_MARKER;
|
135
|
+
*(int16_t *)(encoded + 1) = (int16_t)num;
|
136
|
+
watchman_append(w, encoded, 1 + sizeof(int16_t));
|
137
|
+
} else if (num == (int32_t)num) {
|
138
|
+
encoded[0] = WATCHMAN_INT32_MARKER;
|
139
|
+
*(int32_t *)(encoded + 1) = (int32_t)num;
|
140
|
+
watchman_append(w, encoded, 1 + sizeof(int32_t));
|
141
|
+
} else {
|
142
|
+
encoded[0] = WATCHMAN_INT64_MARKER;
|
143
|
+
*(int64_t *)(encoded + 1) = (int64_t)num;
|
144
|
+
watchman_append(w, encoded, 1 + sizeof(int64_t));
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Encodes and appends the string `string` to `w`
|
150
|
+
*/
|
151
|
+
void watchman_dump_string(watchman_t *w, VALUE string) {
|
152
|
+
watchman_append(w, &watchman_string_marker, sizeof(watchman_string_marker));
|
153
|
+
watchman_dump_int(w, RSTRING_LEN(string));
|
154
|
+
watchman_append(w, RSTRING_PTR(string), RSTRING_LEN(string));
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Encodes and appends the double `num` to `w`
|
159
|
+
*/
|
160
|
+
void watchman_dump_double(watchman_t *w, double num) {
|
161
|
+
char encoded[1 + sizeof(double)];
|
162
|
+
encoded[0] = WATCHMAN_FLOAT_MARKER;
|
163
|
+
*(double *)(encoded + 1) = num;
|
164
|
+
watchman_append(w, encoded, sizeof(encoded));
|
165
|
+
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Encodes and appends the array `array` to `w`
|
169
|
+
*/
|
170
|
+
void watchman_dump_array(watchman_t *w, VALUE array) {
|
171
|
+
long i;
|
172
|
+
watchman_append(w, &watchman_array_marker, sizeof(watchman_array_marker));
|
173
|
+
watchman_dump_int(w, RARRAY_LEN(array));
|
174
|
+
for (i = 0; i < RARRAY_LEN(array); i++) {
|
175
|
+
watchman_dump(w, rb_ary_entry(array, i));
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Helper method that encodes and appends a key/value pair (`key`, `value`) from
|
181
|
+
* a hash to the watchman_t struct passed in via `data`
|
182
|
+
*/
|
183
|
+
int watchman_dump_hash_iterator(VALUE key, VALUE value, VALUE data) {
|
184
|
+
watchman_t *w = (watchman_t *)data;
|
185
|
+
watchman_dump_string(w, StringValue(key));
|
186
|
+
watchman_dump(w, value);
|
187
|
+
return ST_CONTINUE;
|
188
|
+
}
|
189
|
+
|
190
|
+
/**
|
191
|
+
* Encodes and appends the hash `hash` to `w`
|
192
|
+
*/
|
193
|
+
void watchman_dump_hash(watchman_t *w, VALUE hash) {
|
194
|
+
watchman_append(w, &watchman_hash_marker, sizeof(watchman_hash_marker));
|
195
|
+
watchman_dump_int(w, RHASH_SIZE(hash));
|
196
|
+
rb_hash_foreach(hash, watchman_dump_hash_iterator, (VALUE)w);
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Encodes and appends the serialized Ruby object `serializable` to `w`
|
201
|
+
*
|
202
|
+
* Examples of serializable objects include arrays, hashes, strings, numbers
|
203
|
+
* (integers, floats), booleans, and nil.
|
204
|
+
*/
|
205
|
+
void watchman_dump(watchman_t *w, VALUE serializable) {
|
206
|
+
switch (TYPE(serializable)) {
|
207
|
+
case T_ARRAY:
|
208
|
+
return watchman_dump_array(w, serializable);
|
209
|
+
case T_HASH:
|
210
|
+
return watchman_dump_hash(w, serializable);
|
211
|
+
case T_STRING:
|
212
|
+
return watchman_dump_string(w, serializable);
|
213
|
+
case T_FIXNUM: // up to 63 bits
|
214
|
+
return watchman_dump_int(w, FIX2LONG(serializable));
|
215
|
+
case T_BIGNUM:
|
216
|
+
return watchman_dump_int(w, NUM2LL(serializable));
|
217
|
+
case T_FLOAT:
|
218
|
+
return watchman_dump_double(w, NUM2DBL(serializable));
|
219
|
+
case T_TRUE:
|
220
|
+
return watchman_append(w, &watchman_true, sizeof(watchman_true));
|
221
|
+
case T_FALSE:
|
222
|
+
return watchman_append(w, &watchman_false, sizeof(watchman_false));
|
223
|
+
case T_NIL:
|
224
|
+
return watchman_append(w, &watchman_nil, sizeof(watchman_nil));
|
225
|
+
default:
|
226
|
+
rb_raise(rb_eTypeError, "unsupported type");
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
/**
|
231
|
+
* Extract and return the int encoded at `ptr`
|
232
|
+
*
|
233
|
+
* Moves `ptr` past the extracted int.
|
234
|
+
*
|
235
|
+
* Will raise an ArgumentError if extracting the int would take us beyond the
|
236
|
+
* end of the buffer indicated by `end`, or if there is no int encoded at `ptr`.
|
237
|
+
*
|
238
|
+
* @returns The extracted int
|
239
|
+
*/
|
240
|
+
int64_t watchman_load_int(char **ptr, char *end) {
|
241
|
+
char *val_ptr = *ptr + sizeof(int8_t);
|
242
|
+
int64_t val = 0;
|
243
|
+
|
244
|
+
if (val_ptr >= end) {
|
245
|
+
rb_raise(rb_eArgError, "insufficient int storage");
|
246
|
+
}
|
247
|
+
|
248
|
+
switch (*ptr[0]) {
|
249
|
+
case WATCHMAN_INT8_MARKER:
|
250
|
+
if (val_ptr + sizeof(int8_t) > end) {
|
251
|
+
rb_raise(rb_eArgError, "overrun extracting int8_t");
|
252
|
+
}
|
253
|
+
val = *(int8_t *)val_ptr;
|
254
|
+
*ptr = val_ptr + sizeof(int8_t);
|
255
|
+
break;
|
256
|
+
case WATCHMAN_INT16_MARKER:
|
257
|
+
if (val_ptr + sizeof(int16_t) > end) {
|
258
|
+
rb_raise(rb_eArgError, "overrun extracting int16_t");
|
259
|
+
}
|
260
|
+
val = *(int16_t *)val_ptr;
|
261
|
+
*ptr = val_ptr + sizeof(int16_t);
|
262
|
+
break;
|
263
|
+
case WATCHMAN_INT32_MARKER:
|
264
|
+
if (val_ptr + sizeof(int32_t) > end) {
|
265
|
+
rb_raise(rb_eArgError, "overrun extracting int32_t");
|
266
|
+
}
|
267
|
+
val = *(int32_t *)val_ptr;
|
268
|
+
*ptr = val_ptr + sizeof(int32_t);
|
269
|
+
break;
|
270
|
+
case WATCHMAN_INT64_MARKER:
|
271
|
+
if (val_ptr + sizeof(int64_t) > end) {
|
272
|
+
rb_raise(rb_eArgError, "overrun extracting int64_t");
|
273
|
+
}
|
274
|
+
val = *(int64_t *)val_ptr;
|
275
|
+
*ptr = val_ptr + sizeof(int64_t);
|
276
|
+
break;
|
277
|
+
default:
|
278
|
+
rb_raise(
|
279
|
+
rb_eArgError,
|
280
|
+
"bad integer marker 0x%02x",
|
281
|
+
(unsigned int)*ptr[0]
|
282
|
+
);
|
283
|
+
break;
|
284
|
+
}
|
285
|
+
|
286
|
+
return val;
|
287
|
+
}
|
288
|
+
|
289
|
+
/**
|
290
|
+
* Reads and returns a string encoded in the Watchman binary protocol format,
|
291
|
+
* starting at `ptr` and finishing at or before `end`
|
292
|
+
*/
|
293
|
+
VALUE watchman_load_string(char **ptr, char *end) {
|
294
|
+
if (*ptr >= end) {
|
295
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
296
|
+
}
|
297
|
+
|
298
|
+
if (*ptr[0] != WATCHMAN_STRING_MARKER) {
|
299
|
+
rb_raise(rb_eArgError, "not a number");
|
300
|
+
}
|
301
|
+
|
302
|
+
*ptr += sizeof(int8_t);
|
303
|
+
if (*ptr >= end) {
|
304
|
+
rb_raise(rb_eArgError, "invalid string header");
|
305
|
+
}
|
306
|
+
|
307
|
+
int64_t len = watchman_load_int(ptr, end);
|
308
|
+
if (len == 0) { // special case for zero-length strings
|
309
|
+
return rb_str_new2("");
|
310
|
+
} else if (*ptr + len > end) {
|
311
|
+
rb_raise(rb_eArgError, "insufficient string storage");
|
312
|
+
}
|
313
|
+
|
314
|
+
VALUE string = rb_str_new(*ptr, len);
|
315
|
+
*ptr += len;
|
316
|
+
return string;
|
317
|
+
}
|
318
|
+
|
319
|
+
/**
|
320
|
+
* Reads and returns a double encoded in the Watchman binary protocol format,
|
321
|
+
* starting at `ptr` and finishing at or before `end`
|
322
|
+
*/
|
323
|
+
double watchman_load_double(char **ptr, char *end) {
|
324
|
+
*ptr += sizeof(int8_t); // caller has already verified the marker
|
325
|
+
if (*ptr + sizeof(double) > end) {
|
326
|
+
rb_raise(rb_eArgError, "insufficient double storage");
|
327
|
+
}
|
328
|
+
double val = *(double *)*ptr;
|
329
|
+
*ptr += sizeof(double);
|
330
|
+
return val;
|
331
|
+
}
|
332
|
+
|
333
|
+
/**
|
334
|
+
* Helper method which returns length of the array encoded in the Watchman
|
335
|
+
* binary protocol format, starting at `ptr` and finishing at or before `end`
|
336
|
+
*/
|
337
|
+
int64_t watchman_load_array_header(char **ptr, char *end) {
|
338
|
+
if (*ptr >= end) {
|
339
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
340
|
+
}
|
341
|
+
|
342
|
+
// verify and consume marker
|
343
|
+
if (*ptr[0] != WATCHMAN_ARRAY_MARKER) {
|
344
|
+
rb_raise(rb_eArgError, "not an array");
|
345
|
+
}
|
346
|
+
*ptr += sizeof(int8_t);
|
347
|
+
|
348
|
+
// expect a count
|
349
|
+
if (*ptr + sizeof(int8_t) * 2 > end) {
|
350
|
+
rb_raise(rb_eArgError, "incomplete array header");
|
351
|
+
}
|
352
|
+
return watchman_load_int(ptr, end);
|
353
|
+
}
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Reads and returns an array encoded in the Watchman binary protocol format,
|
357
|
+
* starting at `ptr` and finishing at or before `end`
|
358
|
+
*/
|
359
|
+
VALUE watchman_load_array(char **ptr, char *end) {
|
360
|
+
int64_t count, i;
|
361
|
+
VALUE array;
|
362
|
+
|
363
|
+
count = watchman_load_array_header(ptr, end);
|
364
|
+
array = rb_ary_new2(count);
|
365
|
+
|
366
|
+
for (i = 0; i < count; i++) {
|
367
|
+
rb_ary_push(array, watchman_load(ptr, end));
|
368
|
+
}
|
369
|
+
|
370
|
+
return array;
|
371
|
+
}
|
372
|
+
|
373
|
+
/**
|
374
|
+
* Reads and returns a hash encoded in the Watchman binary protocol format,
|
375
|
+
* starting at `ptr` and finishing at or before `end`
|
376
|
+
*/
|
377
|
+
VALUE watchman_load_hash(char **ptr, char *end) {
|
378
|
+
int64_t count, i;
|
379
|
+
VALUE hash, key, value;
|
380
|
+
|
381
|
+
*ptr += sizeof(int8_t); // caller has already verified the marker
|
382
|
+
|
383
|
+
// expect a count
|
384
|
+
if (*ptr + sizeof(int8_t) * 2 > end) {
|
385
|
+
rb_raise(rb_eArgError, "incomplete hash header");
|
386
|
+
}
|
387
|
+
count = watchman_load_int(ptr, end);
|
388
|
+
|
389
|
+
hash = rb_hash_new();
|
390
|
+
|
391
|
+
for (i = 0; i < count; i++) {
|
392
|
+
key = watchman_load_string(ptr, end);
|
393
|
+
value = watchman_load(ptr, end);
|
394
|
+
rb_hash_aset(hash, key, value);
|
395
|
+
}
|
396
|
+
|
397
|
+
return hash;
|
398
|
+
}
|
399
|
+
|
400
|
+
/**
|
401
|
+
* Reads and returns a templated array encoded in the Watchman binary protocol
|
402
|
+
* format, starting at `ptr` and finishing at or before `end`
|
403
|
+
*
|
404
|
+
* Templated arrays are arrays of hashes which have repetitive key information
|
405
|
+
* pulled out into a separate "headers" prefix.
|
406
|
+
*
|
407
|
+
* @see https://github.com/facebook/watchman/blob/master/BSER.markdown
|
408
|
+
*/
|
409
|
+
VALUE watchman_load_template(char **ptr, char *end) {
|
410
|
+
int64_t header_items_count, i, row_count;
|
411
|
+
VALUE array, hash, header, key, value;
|
412
|
+
|
413
|
+
*ptr += sizeof(int8_t); // caller has already verified the marker
|
414
|
+
|
415
|
+
// process template header array
|
416
|
+
header_items_count = watchman_load_array_header(ptr, end);
|
417
|
+
header = rb_ary_new2(header_items_count);
|
418
|
+
for (i = 0; i < header_items_count; i++) {
|
419
|
+
rb_ary_push(header, watchman_load_string(ptr, end));
|
420
|
+
}
|
421
|
+
|
422
|
+
// process row items
|
423
|
+
row_count = watchman_load_int(ptr, end);
|
424
|
+
array = rb_ary_new2(header_items_count);
|
425
|
+
while (row_count--) {
|
426
|
+
hash = rb_hash_new();
|
427
|
+
for (i = 0; i < header_items_count; i++) {
|
428
|
+
if (*ptr >= end) {
|
429
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
430
|
+
}
|
431
|
+
|
432
|
+
if (*ptr[0] == WATCHMAN_SKIP_MARKER) {
|
433
|
+
*ptr += sizeof(uint8_t);
|
434
|
+
} else {
|
435
|
+
value = watchman_load(ptr, end);
|
436
|
+
key = rb_ary_entry(header, i);
|
437
|
+
rb_hash_aset(hash, key, value);
|
438
|
+
}
|
439
|
+
}
|
440
|
+
rb_ary_push(array, hash);
|
441
|
+
}
|
442
|
+
return array;
|
443
|
+
}
|
444
|
+
|
445
|
+
/**
|
446
|
+
* Reads and returns an object encoded in the Watchman binary protocol format,
|
447
|
+
* starting at `ptr` and finishing at or before `end`
|
448
|
+
*/
|
449
|
+
VALUE watchman_load(char **ptr, char *end) {
|
450
|
+
if (*ptr >= end) {
|
451
|
+
rb_raise(rb_eArgError, "unexpected end of input");
|
452
|
+
}
|
453
|
+
|
454
|
+
switch (*ptr[0]) {
|
455
|
+
case WATCHMAN_ARRAY_MARKER:
|
456
|
+
return watchman_load_array(ptr, end);
|
457
|
+
case WATCHMAN_HASH_MARKER:
|
458
|
+
return watchman_load_hash(ptr, end);
|
459
|
+
case WATCHMAN_STRING_MARKER:
|
460
|
+
return watchman_load_string(ptr, end);
|
461
|
+
case WATCHMAN_INT8_MARKER:
|
462
|
+
case WATCHMAN_INT16_MARKER:
|
463
|
+
case WATCHMAN_INT32_MARKER:
|
464
|
+
case WATCHMAN_INT64_MARKER:
|
465
|
+
return LL2NUM(watchman_load_int(ptr, end));
|
466
|
+
case WATCHMAN_FLOAT_MARKER:
|
467
|
+
return rb_float_new(watchman_load_double(ptr, end));
|
468
|
+
case WATCHMAN_TRUE:
|
469
|
+
*ptr += 1;
|
470
|
+
return Qtrue;
|
471
|
+
case WATCHMAN_FALSE:
|
472
|
+
*ptr += 1;
|
473
|
+
return Qfalse;
|
474
|
+
case WATCHMAN_NIL:
|
475
|
+
*ptr += 1;
|
476
|
+
return Qnil;
|
477
|
+
case WATCHMAN_TEMPLATE_MARKER:
|
478
|
+
return watchman_load_template(ptr, end);
|
479
|
+
default:
|
480
|
+
rb_raise(rb_eTypeError, "unsupported type");
|
481
|
+
}
|
482
|
+
|
483
|
+
return Qnil; // keep the compiler happy
|
484
|
+
}
|
485
|
+
|
486
|
+
/**
|
487
|
+
* RubyWatchman.load(serialized)
|
488
|
+
*
|
489
|
+
* Converts the binary object, `serialized`, from the Watchman binary protocol
|
490
|
+
* format into a normal Ruby object.
|
491
|
+
*/
|
492
|
+
VALUE RubyWatchman_load(VALUE self, VALUE serialized) {
|
493
|
+
serialized = StringValue(serialized);
|
494
|
+
long len = RSTRING_LEN(serialized);
|
495
|
+
char *ptr = RSTRING_PTR(serialized);
|
496
|
+
char *end = ptr + len;
|
497
|
+
|
498
|
+
// expect at least the binary marker and a int8_t length counter
|
499
|
+
if ((size_t)len < sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) * 2) {
|
500
|
+
rb_raise(rb_eArgError, "undersized header");
|
501
|
+
}
|
502
|
+
|
503
|
+
int mismatched =
|
504
|
+
memcmp(ptr, WATCHMAN_BINARY_MARKER, sizeof(WATCHMAN_BINARY_MARKER) - 1);
|
505
|
+
if (mismatched) {
|
506
|
+
rb_raise(rb_eArgError, "missing binary marker");
|
507
|
+
}
|
508
|
+
|
509
|
+
// get size marker
|
510
|
+
ptr += sizeof(WATCHMAN_BINARY_MARKER) - 1;
|
511
|
+
uint64_t payload_size = watchman_load_int(&ptr, end);
|
512
|
+
if (!payload_size) {
|
513
|
+
rb_raise(rb_eArgError, "empty payload");
|
514
|
+
}
|
515
|
+
|
516
|
+
// sanity check length
|
517
|
+
if (ptr + payload_size != end) {
|
518
|
+
rb_raise(
|
519
|
+
rb_eArgError,
|
520
|
+
"payload size mismatch (%lu)",
|
521
|
+
(unsigned long)(end - (ptr + payload_size))
|
522
|
+
);
|
523
|
+
}
|
524
|
+
|
525
|
+
VALUE loaded = watchman_load(&ptr, end);
|
526
|
+
|
527
|
+
// one more sanity check
|
528
|
+
if (ptr != end) {
|
529
|
+
rb_raise(
|
530
|
+
rb_eArgError,
|
531
|
+
"payload termination mismatch (%lu)",
|
532
|
+
(unsigned long)(end - ptr)
|
533
|
+
);
|
534
|
+
}
|
535
|
+
|
536
|
+
return loaded;
|
537
|
+
}
|
538
|
+
|
539
|
+
/**
|
540
|
+
* RubyWatchman.dump(serializable)
|
541
|
+
*
|
542
|
+
* Converts the Ruby object, `serializable`, into a binary string in the
|
543
|
+
* Watchman binary protocol format.
|
544
|
+
*
|
545
|
+
* Examples of serializable objects include arrays, hashes, strings, numbers
|
546
|
+
* (integers, floats), booleans, and nil.
|
547
|
+
*/
|
548
|
+
VALUE RubyWatchman_dump(VALUE self, VALUE serializable) {
|
549
|
+
watchman_t *w = watchman_init();
|
550
|
+
watchman_dump(w, serializable);
|
551
|
+
|
552
|
+
// update header with final length information
|
553
|
+
uint64_t *len =
|
554
|
+
(uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1);
|
555
|
+
*len = w->len - sizeof(WATCHMAN_HEADER) + 1;
|
556
|
+
|
557
|
+
// prepare final return value
|
558
|
+
VALUE serialized = rb_str_buf_new(w->len);
|
559
|
+
rb_str_buf_cat(serialized, (const char*)w->data, w->len);
|
560
|
+
watchman_free(w);
|
561
|
+
return serialized;
|
562
|
+
}
|
563
|
+
|
564
|
+
// How far we have to look to figure out the size of the PDU header
|
565
|
+
#define WATCHMAN_SNIFF_BUFFER_SIZE \
|
566
|
+
sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t)
|
567
|
+
|
568
|
+
// How far we have to peek, at most, to figure out the size of the PDU itself
|
569
|
+
#define WATCHMAN_PEEK_BUFFER_SIZE \
|
570
|
+
sizeof(WATCHMAN_BINARY_MARKER) - 1 + \
|
571
|
+
sizeof(WATCHMAN_INT64_MARKER) + \
|
572
|
+
sizeof(int64_t)
|
573
|
+
|
574
|
+
/**
|
575
|
+
* RubyWatchman.query(query, socket)
|
576
|
+
*
|
577
|
+
* Converts `query`, a Watchman query comprising Ruby objects, into the Watchman
|
578
|
+
* binary protocol format, transmits it over socket, and unserializes and
|
579
|
+
* returns the result.
|
580
|
+
*/
|
581
|
+
VALUE RubyWatchman_query(VALUE self, VALUE query, VALUE socket) {
|
582
|
+
VALUE error = Qnil;
|
583
|
+
VALUE errorClass = Qnil;
|
584
|
+
VALUE loaded = Qnil;
|
585
|
+
void *buffer = NULL;
|
586
|
+
int fileno = NUM2INT(rb_funcall(socket, rb_intern("fileno"), 0));
|
587
|
+
|
588
|
+
// do blocking I/O to simplify the following logic
|
589
|
+
int flags = fcntl(fileno, F_GETFL);
|
590
|
+
if (
|
591
|
+
!(flags & O_NONBLOCK) &&
|
592
|
+
fcntl(fileno, F_SETFL, flags & ~O_NONBLOCK) == -1
|
593
|
+
) {
|
594
|
+
error = rb_str_new2("unable to clear O_NONBLOCK flag");
|
595
|
+
goto cleanup;
|
596
|
+
}
|
597
|
+
|
598
|
+
// send the message
|
599
|
+
VALUE serialized = RubyWatchman_dump(self, query);
|
600
|
+
long query_len = RSTRING_LEN(serialized);
|
601
|
+
ssize_t sent = send(fileno, RSTRING_PTR(serialized), query_len, 0);
|
602
|
+
if (sent == -1) {
|
603
|
+
goto system_call_fail;
|
604
|
+
} else if (sent != query_len) {
|
605
|
+
error = rb_str_new2("sent byte count mismatch");
|
606
|
+
goto cleanup;
|
607
|
+
}
|
608
|
+
|
609
|
+
// sniff to see how large the header is
|
610
|
+
int8_t peek[WATCHMAN_PEEK_BUFFER_SIZE];
|
611
|
+
ssize_t received =
|
612
|
+
recv(fileno, peek, WATCHMAN_SNIFF_BUFFER_SIZE, MSG_PEEK | MSG_WAITALL);
|
613
|
+
if (received == -1) {
|
614
|
+
goto system_call_fail;
|
615
|
+
} else if (received != WATCHMAN_SNIFF_BUFFER_SIZE) {
|
616
|
+
error = rb_str_new2("failed to sniff PDU header");
|
617
|
+
goto cleanup;
|
618
|
+
}
|
619
|
+
|
620
|
+
// peek at size of PDU
|
621
|
+
int8_t sizes[] = { 0, 0, 0, 1, 2, 4, 8 };
|
622
|
+
int8_t sizes_idx = peek[sizeof(WATCHMAN_BINARY_MARKER) - 1];
|
623
|
+
if (sizes_idx < WATCHMAN_INT8_MARKER || sizes_idx > WATCHMAN_INT64_MARKER) {
|
624
|
+
error = rb_str_new2("bad PDU size marker");
|
625
|
+
goto cleanup;
|
626
|
+
}
|
627
|
+
ssize_t peek_size = sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) +
|
628
|
+
sizes[sizes_idx];
|
629
|
+
|
630
|
+
received = recv(fileno, peek, peek_size, MSG_PEEK);
|
631
|
+
if (received == -1) {
|
632
|
+
goto system_call_fail;
|
633
|
+
} else if (received != peek_size) {
|
634
|
+
error = rb_str_new2("failed to peek at PDU header");
|
635
|
+
goto cleanup;
|
636
|
+
}
|
637
|
+
int8_t *pdu_size_ptr =
|
638
|
+
peek + sizeof(WATCHMAN_BINARY_MARKER) - sizeof(int8_t);
|
639
|
+
int64_t payload_size =
|
640
|
+
peek_size +
|
641
|
+
watchman_load_int((char **)&pdu_size_ptr, (char *)peek + peek_size);
|
642
|
+
|
643
|
+
// actually read the PDU
|
644
|
+
buffer = xmalloc(payload_size);
|
645
|
+
if (!buffer) {
|
646
|
+
errorClass = rb_eNoMemError;
|
647
|
+
error = rb_str_new2("failed to allocate");
|
648
|
+
goto cleanup;
|
649
|
+
}
|
650
|
+
received = recv(fileno, buffer, payload_size, MSG_WAITALL);
|
651
|
+
if (received == -1) {
|
652
|
+
goto system_call_fail;
|
653
|
+
} else if (received != payload_size) {
|
654
|
+
error = rb_str_new2("failed to load PDU");
|
655
|
+
goto cleanup;
|
656
|
+
}
|
657
|
+
|
658
|
+
if (!(flags & O_NONBLOCK) && fcntl(fileno, F_SETFL, flags) == -1) {
|
659
|
+
error = rb_str_new2("unable to restore fnctl flags");
|
660
|
+
goto cleanup;
|
661
|
+
}
|
662
|
+
|
663
|
+
char *payload = buffer + peek_size;
|
664
|
+
loaded = watchman_load(&payload, payload + payload_size);
|
665
|
+
goto cleanup;
|
666
|
+
|
667
|
+
system_call_fail:
|
668
|
+
errorClass = rb_eSystemCallError;
|
669
|
+
error = INT2FIX(errno);
|
670
|
+
|
671
|
+
cleanup:
|
672
|
+
if (buffer) {
|
673
|
+
xfree(buffer);
|
674
|
+
}
|
675
|
+
|
676
|
+
if (!(flags & O_NONBLOCK) && fcntl(fileno, F_SETFL, flags) == -1) {
|
677
|
+
rb_raise(rb_eRuntimeError, "unable to restore fnctl flags");
|
678
|
+
}
|
679
|
+
|
680
|
+
if (NIL_P(errorClass)) {
|
681
|
+
errorClass = rb_eRuntimeError;
|
682
|
+
}
|
683
|
+
|
684
|
+
if (!NIL_P(error)) {
|
685
|
+
rb_exc_raise(rb_class_new_instance(1, &error, errorClass));
|
686
|
+
}
|
687
|
+
|
688
|
+
return loaded;
|
689
|
+
}
|
690
|
+
|
691
|
+
VALUE mRubyWatchman = 0; // module RubyWatchman
|
692
|
+
|
693
|
+
void Init_ext() {
|
694
|
+
mRubyWatchman = rb_define_module("RubyWatchman");
|
695
|
+
rb_define_singleton_method(mRubyWatchman, "load", RubyWatchman_load, 1);
|
696
|
+
rb_define_singleton_method(mRubyWatchman, "dump", RubyWatchman_dump, 1);
|
697
|
+
rb_define_singleton_method(mRubyWatchman, "query", RubyWatchman_query, 2);
|
698
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2014, Facebook, Inc.
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright notice,
|
9
|
+
this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
* Neither the name Facebook nor the names of its contributors may be used to
|
16
|
+
endorse or promote products derived from this software without specific
|
17
|
+
prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
*/
|
30
|
+
|
31
|
+
#include <ruby.h>
|
32
|
+
|
33
|
+
/**
|
34
|
+
* @module RubyWatchman
|
35
|
+
*
|
36
|
+
* Methods for working with the Watchman binary protocol
|
37
|
+
*
|
38
|
+
* @see https://github.com/facebook/watchman/blob/master/BSER.markdown
|
39
|
+
*/
|
40
|
+
extern VALUE mRubyWatchman;
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Convert an object serialized using the Watchman binary protocol[0] into an
|
44
|
+
* unpacked Ruby object
|
45
|
+
*/
|
46
|
+
extern VALUE RubyWatchman_load(VALUE self, VALUE serialized);
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Serialize a Ruby object into the Watchman binary protocol format
|
50
|
+
*/
|
51
|
+
extern VALUE RubyWatchman_dump(VALUE self, VALUE serializable);
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Issue `query` to the Watchman instance listening on `socket` (a `UNIXSocket`
|
55
|
+
* instance) and return the result
|
56
|
+
*
|
57
|
+
* The query is serialized following the Watchman binary protocol and the
|
58
|
+
* result is converted to native Ruby objects before returning to the caller.
|
59
|
+
*/
|
60
|
+
extern VALUE RubyWatchman_query(VALUE self, VALUE query, VALUE socket);
|
@@ -0,0 +1,397 @@
|
|
1
|
+
# Copyright (c) 2014-present Facebook. All Rights Reserved.
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RubyWatchman do
|
6
|
+
def binary(str)
|
7
|
+
if str.respond_to?(:force_encoding) # Ruby >= 1.9
|
8
|
+
str.force_encoding('ASCII-8BIT')
|
9
|
+
else
|
10
|
+
str
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def little_endian?
|
15
|
+
byte = [0xff00].pack('s')[0]
|
16
|
+
if byte.is_a?(Fixnum) # ie. Ruby 1.8
|
17
|
+
byte.zero?
|
18
|
+
elsif byte.is_a?(String) # ie. Ruby >= 1.9
|
19
|
+
byte == "\x00"
|
20
|
+
else
|
21
|
+
raise 'unable to determine endianness'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def roundtrip(value)
|
26
|
+
described_class.load(described_class.dump(value))
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'roundtrips arrays' do
|
30
|
+
value = [1, 2, ['three', false]]
|
31
|
+
expect(roundtrip(value)).to eq(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'roundtrips hashes' do
|
35
|
+
value = {
|
36
|
+
'foo' => 1,
|
37
|
+
'bar' => {
|
38
|
+
'baz' => 'bing',
|
39
|
+
}
|
40
|
+
}
|
41
|
+
expect(roundtrip(value)).to eq(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'roundtrips strings' do
|
45
|
+
expect(roundtrip('')).to eq('')
|
46
|
+
expect(roundtrip('/foo/bar/baz')).to eq('/foo/bar/baz')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'roundtrips uint8_t integers' do
|
50
|
+
expect(roundtrip(0)).to eq(0)
|
51
|
+
expect(roundtrip(1)).to eq(1)
|
52
|
+
expect(roundtrip(0xff)).to eq(0xff)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'roundtrips uint16_t integers' do
|
56
|
+
expect(roundtrip(0x1234)).to eq(0x1234)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'roundtrips uint32_t integers' do
|
60
|
+
expect(roundtrip(0x12345678)).to eq(0x12345678)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'roundtrips uint64_t integers' do
|
64
|
+
expect(roundtrip(0x12345678abcdef00)).to eq(0x12345678abcdef00)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'roundtrips floats' do
|
68
|
+
expect(roundtrip(1234.5678)).to eq(1234.5678)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'roundtrips `true` booleans' do
|
72
|
+
expect(roundtrip(true)).to be_true
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'roundtrips `false` booleans' do
|
76
|
+
expect(roundtrip(false)).to be_false
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'roundtrips nil' do
|
80
|
+
expect(roundtrip(nil)).to be_nil
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.load' do
|
84
|
+
it 'rejects undersized input' do
|
85
|
+
expect { described_class.load('') }.
|
86
|
+
to raise_error(ArgumentError, /undersized/i)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'rejects input without a binary marker' do
|
90
|
+
expect { described_class.load('gibberish') }.
|
91
|
+
to raise_error(ArgumentError, /missing/i)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'rejects a missing payload header' do
|
95
|
+
# binary protocol marker, but nothing else
|
96
|
+
input = binary("\x00\x01")
|
97
|
+
expect { described_class.load(input) }.
|
98
|
+
to raise_error(ArgumentError, /undersized/i)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'rejects empty payloads' do
|
102
|
+
# uint8_t size marker of zero
|
103
|
+
input = binary("\x00\x01\x03\x00")
|
104
|
+
expect { described_class.load(input) }.
|
105
|
+
to raise_error(ArgumentError, /empty/i)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'rejects unrecognized payload markers' do
|
109
|
+
# 0x10 is not a valid integer marker
|
110
|
+
input = binary("\x00\x01\x10\x00")
|
111
|
+
expect { described_class.load(input) }.
|
112
|
+
to raise_error(ArgumentError, /bad integer/i)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'rejects undersized payload markers' do
|
116
|
+
# int16_t marker, but only storage for int8_t
|
117
|
+
input = binary("\x00\x01\x04\x00")
|
118
|
+
expect { described_class.load(input) }.
|
119
|
+
to raise_error(ArgumentError, /overrun\b.+\bint16_t/i)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'loads array values' do
|
123
|
+
input = binary(
|
124
|
+
"\x00\x01\x03\x16\x00\x03\x05\x03\x01\x02\x03" \
|
125
|
+
"\x06foobar\x08\x09\x00\x03\x02\x03\x0a\x0a"
|
126
|
+
)
|
127
|
+
expect(described_class.load(input)).
|
128
|
+
to eq([1, 'foobar', true, false, [10, nil]])
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'handles empty arrays' do
|
132
|
+
input = binary("\x00\x01\x03\x03\x00\x03\x00")
|
133
|
+
expect(described_class.load(input)).to eq([])
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'rejects arrays with incomplete headers' do
|
137
|
+
input = binary("\x00\x01\x03\x02\x00\x03")
|
138
|
+
expect { described_class.load(input) }.
|
139
|
+
to raise_error(ArgumentError, /incomplete array header/i)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'rejects arrays with incomplete entries' do
|
143
|
+
input = binary("\x00\x01\x03\x05\x00\x03\x10\x0a\x0a")
|
144
|
+
expect { described_class.load(input) }.
|
145
|
+
to raise_error(ArgumentError, /unexpected end/i)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'loads hash values' do
|
149
|
+
input = binary(
|
150
|
+
"\x00\x01\x03\x1a\x01\x03\x02\x02\x03\x03foo\x0a" \
|
151
|
+
"\x02\x03\x03bar\x01\x03\x01\x02\x03\x03baz\x08"
|
152
|
+
)
|
153
|
+
expected = {
|
154
|
+
'foo' => nil,
|
155
|
+
'bar' => {
|
156
|
+
'baz' => true,
|
157
|
+
}
|
158
|
+
}
|
159
|
+
expect(described_class.load(input)).to eq(expected)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'handles empty hashes' do
|
163
|
+
input = binary("\x00\x01\x03\x03\x01\x03\x00")
|
164
|
+
expect(described_class.load(input)).to eq({})
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'rejects hashes with incomplete headers' do
|
168
|
+
input = binary("\x00\x01\x03\x02\x01\x03")
|
169
|
+
expect { described_class.load(input) }.
|
170
|
+
to raise_error(ArgumentError, /incomplete hash header/i)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'rejects hashes with invalid keys' do
|
174
|
+
# keys must be strings; this one uses uses a number instead
|
175
|
+
input = binary("\x00\x01\x03\x05\x01\x03\x01\x03\x00")
|
176
|
+
expect { described_class.load(input) }.
|
177
|
+
to raise_error(ArgumentError, /not a number/i)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'rejects hashes with missing keys' do
|
181
|
+
input = binary("\x00\x01\x03\x03\x01\x03\x01")
|
182
|
+
expect { described_class.load(input) }.
|
183
|
+
to raise_error(ArgumentError, /unexpected end/i)
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'rejects hashes with missing values' do
|
187
|
+
input = binary("\x00\x01\x03\x09\x01\x03\x01\x02\x03\x03foo")
|
188
|
+
expect { described_class.load(input) }.
|
189
|
+
to raise_error(ArgumentError, /unexpected end/i)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'loads string values' do
|
193
|
+
input = binary("\x00\x01\x03\x06\x02\x03\x03foo")
|
194
|
+
expect(described_class.load(input)).to eq('foo')
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'handles empty strings' do
|
198
|
+
input = binary("\x00\x01\x03\x03\x02\x03\x00")
|
199
|
+
expect(described_class.load(input)).to eq('')
|
200
|
+
end
|
201
|
+
|
202
|
+
if String.new.respond_to?(:encoding) # ie. Ruby >= 1.9
|
203
|
+
it 'loads string values as ASCII-8BIT encoded strings' do
|
204
|
+
input = binary("\x00\x01\x03\x06\x02\x03\x03foo")
|
205
|
+
expect(described_class.load(input).encoding.to_s).to eq('ASCII-8BIT')
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'rejects string values with incomplete headers' do
|
210
|
+
input = binary("\x00\x01\x03\x01\x02")
|
211
|
+
expect { described_class.load(input) }.
|
212
|
+
to raise_error(ArgumentError, /invalid string header/i)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'rejects string values with invalid headers' do
|
216
|
+
# expect a number indicating the string length, get a boolean instead
|
217
|
+
input = binary("\x00\x01\x03\x05\x02\x08foo")
|
218
|
+
expect { described_class.load(input) }.
|
219
|
+
to raise_error(ArgumentError, /bad integer/i)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'rejects string values with insufficient storage' do
|
223
|
+
# expect 3 bytes, get 2 instead
|
224
|
+
input = binary("\x00\x01\x03\x05\x02\x03\x03fo")
|
225
|
+
expect { described_class.load(input) }.
|
226
|
+
to raise_error(ArgumentError, /insufficient string storage/i)
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'loads uint8_t values' do
|
230
|
+
input = binary("\x00\x01\x03\x02\x03\x12")
|
231
|
+
expect(described_class.load(input)).to eq(0x12)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'loads uint16_t values' do
|
235
|
+
if little_endian?
|
236
|
+
input = binary("\x00\x01\x03\x03\x04\x34\x12")
|
237
|
+
else
|
238
|
+
input = binary("\x00\x01\x03\x03\x04\x12\x34")
|
239
|
+
end
|
240
|
+
|
241
|
+
expect(described_class.load(input)).to eq(0x1234)
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'loads uint32_t values' do
|
245
|
+
if little_endian?
|
246
|
+
input = binary("\x00\x01\x03\x05\x05\x78\x56\x34\x12")
|
247
|
+
else
|
248
|
+
input = binary("\x00\x01\x03\x05\x05\x12\x34\x56\x78")
|
249
|
+
end
|
250
|
+
|
251
|
+
expect(described_class.load(input)).to eq(0x12345678)
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'loads int uint64_t values' do
|
255
|
+
if little_endian?
|
256
|
+
input = binary("\x00\x01\x03\x09\x06\xef\xcd\xab\x90\x78\x56\x34\x12")
|
257
|
+
else
|
258
|
+
input = binary("\x00\x01\x03\x09\x06\x12\x34\x56\x78\x90\xab\xcd\xef")
|
259
|
+
end
|
260
|
+
expect(described_class.load(input)).to eq(0x1234567890abcdef)
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'rejects int markers with missing values' do
|
264
|
+
# expect an integer, but hit the end of the buffer
|
265
|
+
input = binary("\x00\x01\x03\x01\x05")
|
266
|
+
expect { described_class.load(input) }.
|
267
|
+
to raise_error(ArgumentError, /insufficient int storage/i)
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'rejects double markers with insufficient storage' do
|
271
|
+
# double with 7 bytes of storage instead of the required 8 bytes
|
272
|
+
input = binary("\x00\x01\x03\x08\x07\x00\x00\x00\x00\x00\x00\x00")
|
273
|
+
expect { described_class.load(input) }.
|
274
|
+
to raise_error(ArgumentError, /insufficient double storage/i)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'loads boolean `true` values' do
|
278
|
+
input = binary("\x00\x01\x03\x01\x08")
|
279
|
+
expect(described_class.load(input)).to be_true
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'loads boolean `false` values' do
|
283
|
+
input = binary("\x00\x01\x03\x01\x09")
|
284
|
+
expect(described_class.load(input)).to be_false
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'loads nil' do
|
288
|
+
input = binary("\x00\x01\x03\x01\x0a")
|
289
|
+
expect(described_class.load(input)).to be_nil
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'loads templates' do
|
293
|
+
# this example includes a "skip" marker
|
294
|
+
input = binary(
|
295
|
+
"\x00\x01\x03\x28\x0b\x00\x03\x02\x02\x03\x04name" \
|
296
|
+
"\x02\x03\x03age\x03\x03\x02\x03\x04fred\x03" \
|
297
|
+
"\x14\x02\x03\x04pete\x03\x1e\x0c\x03\x19"
|
298
|
+
)
|
299
|
+
expected = [
|
300
|
+
{ 'name' => 'fred', 'age' => 20 },
|
301
|
+
{ 'name' => 'pete', 'age' => 30 },
|
302
|
+
{ 'age' => 25 },
|
303
|
+
]
|
304
|
+
expect(described_class.load(input)).to eq(expected)
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'handles empty templates' do
|
308
|
+
input = binary(
|
309
|
+
"\x00\x01\x03\x12\x0b\x00\x03\x02\x02" \
|
310
|
+
"\x03\x03foo\x02\x03\x03bar\x03\x00"
|
311
|
+
)
|
312
|
+
expect(described_class.load(input)).to eq([])
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'rejects templates without a header array' do
|
316
|
+
input = binary("\x00\x01\x03\x01\x0b")
|
317
|
+
expect { described_class.load(input) }.
|
318
|
+
to raise_error(ArgumentError, /unexpected end/i)
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'rejects templates without a row items array' do
|
322
|
+
input = binary(
|
323
|
+
"\x00\x01\x03\x10\x0b\x00\x03\x02\x02" \
|
324
|
+
"\x03\x03foo\x02\x03\x03bar"
|
325
|
+
)
|
326
|
+
expect { described_class.load(input) }.
|
327
|
+
to raise_error(ArgumentError, /insufficient/i)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'rejects templates with non-string header items' do
|
331
|
+
input = binary(
|
332
|
+
"\x00\x01\x03\x0e\x0b\x00\x03\x02\x02" \
|
333
|
+
"\x03\x03foo\x03\x03\x03\x00"
|
334
|
+
)
|
335
|
+
expect { described_class.load(input) }.
|
336
|
+
to raise_error(ArgumentError, /not a number/)
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'rejects templates with a header item array count mismatch' do
|
340
|
+
input = binary(
|
341
|
+
"\x00\x01\x03\x0a\x0b\x00\x03\x02\x02" \
|
342
|
+
"\x03\x03foo"
|
343
|
+
)
|
344
|
+
expect { described_class.load(input) }.
|
345
|
+
to raise_error(ArgumentError, /unexpected end/)
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'rejects templates with a row item count mismatch' do
|
349
|
+
input = binary(
|
350
|
+
"\x00\x01\x03\x25\x0b\x00\x03\x02\x02\x03\x04name" \
|
351
|
+
"\x02\x03\x03age\x03\x03\x02\x03\x04fred\x03" \
|
352
|
+
"\x14\x02\x03\x04pete\x03\x1e"
|
353
|
+
)
|
354
|
+
expect { described_class.load(input) }.
|
355
|
+
to raise_error(ArgumentError, /unexpected end/)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
describe '.dump' do
|
360
|
+
let(:query) do
|
361
|
+
# typical kind of query that Command-T issues
|
362
|
+
['query', '/some/path', {
|
363
|
+
'expression' => ['type', 'f'],
|
364
|
+
'fields' => ['name'],
|
365
|
+
}]
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'serializes' do
|
369
|
+
expect { described_class.dump(query) }.to_not raise_error
|
370
|
+
end
|
371
|
+
|
372
|
+
if String.new.respond_to?(:encoding) # ie. Ruby >= 1.9
|
373
|
+
it 'serializes to an ASCII-8BIT string' do
|
374
|
+
expect(described_class.dump(query).encoding.to_s).to eq('ASCII-8BIT')
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'generates a correct serialization' do
|
379
|
+
# in Ruby 1.8, hashes aren't ordered, so two serializations are possible
|
380
|
+
expected = [
|
381
|
+
binary(
|
382
|
+
"\x00\x01\x06\x49\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x02\x03" \
|
383
|
+
"\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x0aexpression" \
|
384
|
+
"\x00\x03\x02\x02\x03\x04type\x02\x03\x01f\x02\x03\x06fields\x00" \
|
385
|
+
"\x03\x01\x02\x03\x04name"
|
386
|
+
),
|
387
|
+
binary(
|
388
|
+
"\x00\x01\x06\x49\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x02\x03" \
|
389
|
+
"\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x06fields\x00" \
|
390
|
+
"\x03\x01\x02\x03\x04name\x02\x03\x0aexpression\x00\x03\x02\x02" \
|
391
|
+
"\x03\x04type\x02\x03\x01f"
|
392
|
+
)
|
393
|
+
]
|
394
|
+
expect(expected).to include(described_class.dump(query))
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-watchman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Hurrell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: |
|
56
|
+
A fast implementation of the Watchman binary protocol written in C.
|
57
|
+
email:
|
58
|
+
- glh@fb.com
|
59
|
+
executables: []
|
60
|
+
extensions:
|
61
|
+
- ext/ruby-watchman/extconf.rb
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ext/ruby-watchman/watchman.c
|
65
|
+
- ext/ruby-watchman/watchman.h
|
66
|
+
- ext/ruby-watchman/extconf.rb
|
67
|
+
- ext/ruby-watchman/depend
|
68
|
+
- lib/ruby-watchman/version.rb
|
69
|
+
- lib/ruby-watchman.rb
|
70
|
+
- spec/ruby_watchman_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
homepage: https://github.com/facebook/watchman/tree/master/ruby/ruby-watchman
|
73
|
+
licenses:
|
74
|
+
- BSD
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.0.14
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Ruby implementation of Watchman binary protocol
|
96
|
+
test_files:
|
97
|
+
- spec/ruby_watchman_spec.rb
|
98
|
+
- spec/spec_helper.rb
|