ruby-watchman 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|