RocketAMF-ouvrages 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +47 -0
- data/Rakefile +59 -0
- data/RocketAMF.gemspec +20 -0
- data/benchmark.rb +74 -0
- data/ext/rocketamf_ext/class_mapping.c +484 -0
- data/ext/rocketamf_ext/constants.h +52 -0
- data/ext/rocketamf_ext/deserializer.c +770 -0
- data/ext/rocketamf_ext/deserializer.h +27 -0
- data/ext/rocketamf_ext/extconf.rb +18 -0
- data/ext/rocketamf_ext/remoting.c +184 -0
- data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
- data/ext/rocketamf_ext/serializer.c +834 -0
- data/ext/rocketamf_ext/serializer.h +29 -0
- data/ext/rocketamf_ext/utility.h +4 -0
- data/lib/rocketamf.rb +216 -0
- data/lib/rocketamf/class_mapping.rb +237 -0
- data/lib/rocketamf/constants.rb +50 -0
- data/lib/rocketamf/ext.rb +28 -0
- data/lib/rocketamf/extensions.rb +22 -0
- data/lib/rocketamf/pure.rb +24 -0
- data/lib/rocketamf/pure/deserializer.rb +455 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +117 -0
- data/lib/rocketamf/pure/serializer.rb +474 -0
- data/lib/rocketamf/remoting.rb +196 -0
- data/lib/rocketamf/values/messages.rb +214 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/spec/class_mapping_spec.rb +110 -0
- data/spec/deserializer_spec.rb +449 -0
- data/spec/fast_class_mapping_spec.rb +144 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +1 -0
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-time.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +1 -0
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
- data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
- data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
- data/spec/fixtures/objects/amf3-false.bin +1 -0
- data/spec/fixtures/objects/amf3-float.bin +0 -0
- data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/amf3-max.bin +1 -0
- data/spec/fixtures/objects/amf3-min.bin +0 -0
- data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
- data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
- data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/fixtures/request/acknowledge-response.bin +0 -0
- data/spec/fixtures/request/amf0-error-response.bin +0 -0
- data/spec/fixtures/request/blaze-response.bin +0 -0
- data/spec/fixtures/request/commandMessage.bin +0 -0
- data/spec/fixtures/request/flex-request.bin +0 -0
- data/spec/fixtures/request/multiple-simple-request.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-request.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
- data/spec/messages_spec.rb +39 -0
- data/spec/remoting_spec.rb +196 -0
- data/spec/serializer_spec.rb +503 -0
- data/spec/spec_helper.rb +55 -0
- metadata +167 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#ifdef HAVE_RB_STR_ENCODE
|
3
|
+
#include <ruby/encoding.h>
|
4
|
+
#endif
|
5
|
+
|
6
|
+
typedef struct {
|
7
|
+
int version;
|
8
|
+
VALUE class_mapper;
|
9
|
+
VALUE src;
|
10
|
+
char* stream;
|
11
|
+
unsigned long pos;
|
12
|
+
unsigned long size;
|
13
|
+
VALUE obj_cache;
|
14
|
+
VALUE str_cache;
|
15
|
+
VALUE trait_cache;
|
16
|
+
} AMF_DESERIALIZER;
|
17
|
+
|
18
|
+
char des_read_byte(AMF_DESERIALIZER *des);
|
19
|
+
int des_read_uint16(AMF_DESERIALIZER *des);
|
20
|
+
unsigned int des_read_uint32(AMF_DESERIALIZER *des);
|
21
|
+
double des_read_double(AMF_DESERIALIZER *des);
|
22
|
+
int des_read_int(AMF_DESERIALIZER *des);
|
23
|
+
VALUE des_read_string(AMF_DESERIALIZER *des, unsigned int len);
|
24
|
+
VALUE des_read_sym(AMF_DESERIALIZER *des, unsigned int len);
|
25
|
+
void des_set_src(AMF_DESERIALIZER *des, VALUE src);
|
26
|
+
|
27
|
+
VALUE des_deserialize(VALUE self, VALUE ver, VALUE src);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
# Disable the native extension by creating an empty Makefile on JRuby
|
4
|
+
if defined? JRUBY_VERSION
|
5
|
+
message "Generating phony Makefile for JRuby so the gem installs"
|
6
|
+
mfile = File.join(File.dirname(__FILE__), 'Makefile')
|
7
|
+
File.open(mfile, 'w') {|f| f.write dummy_makefile(File.dirname(__FILE__)) }
|
8
|
+
exit 0
|
9
|
+
end
|
10
|
+
|
11
|
+
if enable_config("sort-props", false)
|
12
|
+
$defs.push("-DSORT_PROPS") unless $defs.include? "-DSORT_PROPS"
|
13
|
+
end
|
14
|
+
have_func('rb_str_encode')
|
15
|
+
|
16
|
+
$CFLAGS += " -Wall"
|
17
|
+
|
18
|
+
create_makefile('rocketamf_ext')
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#include "deserializer.h"
|
2
|
+
#include "serializer.h"
|
3
|
+
#include "constants.h"
|
4
|
+
|
5
|
+
extern VALUE mRocketAMF;
|
6
|
+
extern VALUE mRocketAMFExt;
|
7
|
+
extern VALUE cDeserializer;
|
8
|
+
extern VALUE cSerializer;
|
9
|
+
VALUE cRocketAMFHeader;
|
10
|
+
VALUE cRocketAMFMessage;
|
11
|
+
VALUE cRocketAMFAbstractMessage;
|
12
|
+
ID id_amf_version;
|
13
|
+
ID id_headers;
|
14
|
+
ID id_messages;
|
15
|
+
ID id_data;
|
16
|
+
|
17
|
+
/*
|
18
|
+
* call-seq:
|
19
|
+
* env.populate_from_stream(stream, class_mapper=nil)
|
20
|
+
*
|
21
|
+
* Included into RocketAMF::Envelope, this method handles deserializing an AMF
|
22
|
+
* request/response into the envelope
|
23
|
+
*/
|
24
|
+
static VALUE env_populate_from_stream(int argc, VALUE *argv, VALUE self) {
|
25
|
+
static VALUE cClassMapper = 0;
|
26
|
+
if(cClassMapper == 0) cClassMapper = rb_const_get(mRocketAMF, rb_intern("ClassMapper"));
|
27
|
+
|
28
|
+
// Parse args
|
29
|
+
VALUE src;
|
30
|
+
VALUE class_mapper;
|
31
|
+
rb_scan_args(argc, argv, "11", &src, &class_mapper);
|
32
|
+
if(class_mapper == Qnil) class_mapper = rb_class_new_instance(0, NULL, cClassMapper);
|
33
|
+
|
34
|
+
// Create AMF0 deserializer
|
35
|
+
VALUE args[3];
|
36
|
+
args[0] = class_mapper;
|
37
|
+
VALUE des_rb = rb_class_new_instance(1, args, cDeserializer);
|
38
|
+
AMF_DESERIALIZER *des;
|
39
|
+
Data_Get_Struct(des_rb, AMF_DESERIALIZER, des);
|
40
|
+
des_set_src(des, src);
|
41
|
+
|
42
|
+
// Read amf version
|
43
|
+
int amf_ver = des_read_uint16(des);
|
44
|
+
|
45
|
+
// Read headers
|
46
|
+
VALUE headers = rb_hash_new();
|
47
|
+
int header_cnt = des_read_uint16(des);
|
48
|
+
int i;
|
49
|
+
for(i = 0; i < header_cnt; i++) {
|
50
|
+
VALUE name = des_read_string(des, des_read_uint16(des));
|
51
|
+
VALUE must_understand = des_read_byte(des) != 0 ? Qtrue : Qfalse;
|
52
|
+
des_read_uint32(des); // Length is ignored
|
53
|
+
VALUE data = des_deserialize(des_rb, INT2FIX(0), Qnil);
|
54
|
+
|
55
|
+
args[0] = name;
|
56
|
+
args[1] = must_understand;
|
57
|
+
args[2] = data;
|
58
|
+
rb_hash_aset(headers, name, rb_class_new_instance(3, args, cRocketAMFHeader));
|
59
|
+
}
|
60
|
+
|
61
|
+
// Read messages
|
62
|
+
VALUE messages = rb_ary_new();
|
63
|
+
int message_cnt = des_read_uint16(des);
|
64
|
+
for(i = 0; i < message_cnt; i++) {
|
65
|
+
VALUE target_uri = des_read_string(des, des_read_uint16(des));
|
66
|
+
VALUE response_uri = des_read_string(des, des_read_uint16(des));
|
67
|
+
des_read_uint32(des); // Length is ignored
|
68
|
+
VALUE data = des_deserialize(des_rb, INT2FIX(0), Qnil);
|
69
|
+
|
70
|
+
// If they're using the flex remoting APIs, remove array wrapper
|
71
|
+
if(TYPE(data) == T_ARRAY && RARRAY_LEN(data) == 1 && rb_obj_is_kind_of(RARRAY_PTR(data)[0], cRocketAMFAbstractMessage) == Qtrue) {
|
72
|
+
data = RARRAY_PTR(data)[0];
|
73
|
+
}
|
74
|
+
|
75
|
+
args[0] = target_uri;
|
76
|
+
args[1] = response_uri;
|
77
|
+
args[2] = data;
|
78
|
+
rb_ary_push(messages, rb_class_new_instance(3, args, cRocketAMFMessage));
|
79
|
+
}
|
80
|
+
|
81
|
+
// Populate remoting object
|
82
|
+
rb_ivar_set(self, id_amf_version, INT2FIX(amf_ver));
|
83
|
+
rb_ivar_set(self, id_headers, headers);
|
84
|
+
rb_ivar_set(self, id_messages, messages);
|
85
|
+
|
86
|
+
return self;
|
87
|
+
}
|
88
|
+
|
89
|
+
/*
|
90
|
+
* call-seq:
|
91
|
+
* env.serialize(class_mapper=nil)
|
92
|
+
*
|
93
|
+
* Included into RocketAMF::Envelope, this method handles serializing an AMF
|
94
|
+
* request/response into a string
|
95
|
+
*/
|
96
|
+
static VALUE env_serialize(int argc, VALUE *argv, VALUE self) {
|
97
|
+
static VALUE cClassMapper = 0;
|
98
|
+
if(cClassMapper == 0) cClassMapper = rb_const_get(mRocketAMF, rb_intern("ClassMapper"));
|
99
|
+
|
100
|
+
// Parse args
|
101
|
+
VALUE class_mapper;
|
102
|
+
rb_scan_args(argc, argv, "01", &class_mapper);
|
103
|
+
if(class_mapper == Qnil) class_mapper = rb_class_new_instance(0, NULL, cClassMapper);
|
104
|
+
|
105
|
+
// Get instance variables
|
106
|
+
long amf_ver = FIX2LONG(rb_ivar_get(self, id_amf_version));
|
107
|
+
VALUE headers = rb_funcall(rb_ivar_get(self, id_headers), rb_intern("values"), 0); // Get array of header values
|
108
|
+
VALUE messages = rb_ivar_get(self, id_messages);
|
109
|
+
|
110
|
+
// Create AMF0 serializer
|
111
|
+
VALUE args[1] = {class_mapper};
|
112
|
+
VALUE ser_rb = rb_class_new_instance(1, args, cSerializer);
|
113
|
+
AMF_SERIALIZER *ser;
|
114
|
+
Data_Get_Struct(ser_rb, AMF_SERIALIZER, ser);
|
115
|
+
|
116
|
+
// Write version
|
117
|
+
ser_write_uint16(ser, amf_ver);
|
118
|
+
|
119
|
+
// Write headers
|
120
|
+
long header_cnt = RARRAY_LEN(headers);
|
121
|
+
ser_write_uint16(ser, header_cnt);
|
122
|
+
int i;
|
123
|
+
char *str;
|
124
|
+
long str_len;
|
125
|
+
for(i = 0; i < header_cnt; i++) {
|
126
|
+
VALUE header = RARRAY_PTR(headers)[i];
|
127
|
+
|
128
|
+
// Write header name
|
129
|
+
ser_get_string(rb_funcall(header, rb_intern("name"), 0), Qtrue, &str, &str_len);
|
130
|
+
ser_write_uint16(ser, str_len);
|
131
|
+
rb_str_buf_cat(ser->stream, str, str_len);
|
132
|
+
|
133
|
+
// Write understand flag
|
134
|
+
ser_write_byte(ser, rb_funcall(header, rb_intern("must_understand"), 0) == Qtrue ? 1 : 0);
|
135
|
+
|
136
|
+
// Serialize data
|
137
|
+
ser_write_uint32(ser, -1); // length of data - -1 if you don't know
|
138
|
+
ser_serialize(ser_rb, INT2FIX(0), rb_funcall(header, id_data, 0));
|
139
|
+
}
|
140
|
+
|
141
|
+
// Write messages
|
142
|
+
long message_cnt = RARRAY_LEN(messages);
|
143
|
+
ser_write_uint16(ser, message_cnt);
|
144
|
+
for(i = 0; i < message_cnt; i++) {
|
145
|
+
VALUE message = RARRAY_PTR(messages)[i];
|
146
|
+
|
147
|
+
// Write target_uri
|
148
|
+
ser_get_string(rb_funcall(message, rb_intern("target_uri"), 0), Qtrue, &str, &str_len);
|
149
|
+
ser_write_uint16(ser, str_len);
|
150
|
+
rb_str_buf_cat(ser->stream, str, str_len);
|
151
|
+
|
152
|
+
// Write response_uri
|
153
|
+
ser_get_string(rb_funcall(message, rb_intern("response_uri"), 0), Qtrue, &str, &str_len);
|
154
|
+
ser_write_uint16(ser, str_len);
|
155
|
+
rb_str_buf_cat(ser->stream, str, str_len);
|
156
|
+
|
157
|
+
// Serialize data
|
158
|
+
ser_write_uint32(ser, -1); // length of data - -1 if you don't know
|
159
|
+
if(amf_ver == 3) {
|
160
|
+
ser_write_byte(ser, AMF0_AMF3_MARKER);
|
161
|
+
ser_serialize(ser_rb, INT2FIX(3), rb_funcall(message, id_data, 0));
|
162
|
+
} else {
|
163
|
+
ser_serialize(ser_rb, INT2FIX(0), rb_funcall(message, id_data, 0));
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
return ser->stream;
|
168
|
+
}
|
169
|
+
|
170
|
+
|
171
|
+
void Init_rocket_amf_remoting() {
|
172
|
+
VALUE mEnvelope = rb_define_module_under(mRocketAMFExt, "Envelope");
|
173
|
+
rb_define_method(mEnvelope, "populate_from_stream", env_populate_from_stream, -1);
|
174
|
+
rb_define_method(mEnvelope, "serialize", env_serialize, -1);
|
175
|
+
|
176
|
+
// Get refs to commonly used symbols and ids
|
177
|
+
id_amf_version = rb_intern("@amf_version");
|
178
|
+
id_headers = rb_intern("@headers");
|
179
|
+
id_messages = rb_intern("@messages");
|
180
|
+
id_data = rb_intern("data");
|
181
|
+
cRocketAMFHeader = rb_const_get(mRocketAMF, rb_intern("Header"));
|
182
|
+
cRocketAMFMessage = rb_const_get(mRocketAMF, rb_intern("Message"));
|
183
|
+
cRocketAMFAbstractMessage = rb_const_get(rb_const_get(mRocketAMF, rb_intern("Values")), rb_intern("AbstractMessage"));
|
184
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
VALUE mRocketAMF;
|
4
|
+
VALUE mRocketAMFExt;
|
5
|
+
VALUE cDeserializer;
|
6
|
+
VALUE cSerializer;
|
7
|
+
VALUE cStringIO;
|
8
|
+
VALUE cDate;
|
9
|
+
VALUE cDateTime;
|
10
|
+
VALUE sym_class_name;
|
11
|
+
VALUE sym_members;
|
12
|
+
VALUE sym_externalizable;
|
13
|
+
VALUE sym_dynamic;
|
14
|
+
|
15
|
+
void Init_rocket_amf_deserializer();
|
16
|
+
void Init_rocket_amf_serializer();
|
17
|
+
void Init_rocket_amf_fast_class_mapping();
|
18
|
+
void Init_rocket_amf_remoting();
|
19
|
+
|
20
|
+
void Init_rocketamf_ext() {
|
21
|
+
mRocketAMF = rb_define_module("RocketAMF");
|
22
|
+
mRocketAMFExt = rb_define_module_under(mRocketAMF, "Ext");
|
23
|
+
|
24
|
+
// Set up classes
|
25
|
+
Init_rocket_amf_deserializer();
|
26
|
+
Init_rocket_amf_serializer();
|
27
|
+
Init_rocket_amf_fast_class_mapping();
|
28
|
+
Init_rocket_amf_remoting();
|
29
|
+
|
30
|
+
// Get refs to commonly used symbols and ids
|
31
|
+
cStringIO = rb_const_get(rb_cObject, rb_intern("StringIO"));
|
32
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
33
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
34
|
+
sym_class_name = ID2SYM(rb_intern("class_name"));
|
35
|
+
sym_members = ID2SYM(rb_intern("members"));
|
36
|
+
sym_externalizable = ID2SYM(rb_intern("externalizable"));
|
37
|
+
sym_dynamic = ID2SYM(rb_intern("dynamic"));
|
38
|
+
}
|
@@ -0,0 +1,834 @@
|
|
1
|
+
#include "serializer.h"
|
2
|
+
#include "constants.h"
|
3
|
+
#include "utility.h"
|
4
|
+
|
5
|
+
extern VALUE mRocketAMF;
|
6
|
+
extern VALUE mRocketAMFExt;
|
7
|
+
extern VALUE cSerializer;
|
8
|
+
extern VALUE cStringIO;
|
9
|
+
extern VALUE cDate;
|
10
|
+
extern VALUE cDateTime;
|
11
|
+
extern VALUE sym_class_name;
|
12
|
+
extern VALUE sym_members;
|
13
|
+
extern VALUE sym_externalizable;
|
14
|
+
extern VALUE sym_dynamic;
|
15
|
+
VALUE cArrayCollection;
|
16
|
+
ID id_haskey;
|
17
|
+
ID id_encode_amf;
|
18
|
+
ID id_is_array_collection;
|
19
|
+
ID id_use_array_collection;
|
20
|
+
ID id_get_as_class_name;
|
21
|
+
ID id_props_for_serialization;
|
22
|
+
ID id_utc;
|
23
|
+
ID id_to_f;
|
24
|
+
ID id_is_integer;
|
25
|
+
|
26
|
+
static VALUE ser0_serialize(VALUE self, VALUE obj);
|
27
|
+
static VALUE ser3_serialize(VALUE self, VALUE obj);
|
28
|
+
|
29
|
+
void ser_write_byte(AMF_SERIALIZER *ser, char byte) {
|
30
|
+
char bytes[2] = {byte, '\0'};
|
31
|
+
rb_str_buf_cat(ser->stream, bytes, 1);
|
32
|
+
}
|
33
|
+
|
34
|
+
void ser_write_int(AMF_SERIALIZER *ser, int num) {
|
35
|
+
char tmp[4];
|
36
|
+
int tmp_len;
|
37
|
+
|
38
|
+
num &= 0x1fffffff;
|
39
|
+
if (num < 0x80) {
|
40
|
+
tmp_len = 1;
|
41
|
+
tmp[0] = num;
|
42
|
+
} else if (num < 0x4000) {
|
43
|
+
tmp_len = 2;
|
44
|
+
tmp[0] = (num >> 7 & 0x7f) | 0x80;
|
45
|
+
tmp[1] = num & 0x7f;
|
46
|
+
} else if (num < 0x200000) {
|
47
|
+
tmp_len = 3;
|
48
|
+
tmp[0] = (num >> 14 & 0x7f) | 0x80;
|
49
|
+
tmp[1] = (num >> 7 & 0x7f) | 0x80;
|
50
|
+
tmp[2] = num & 0x7f;
|
51
|
+
} else if (num < 0x40000000) {
|
52
|
+
tmp_len = 4;
|
53
|
+
tmp[0] = (num >> 22 & 0x7f) | 0x80;
|
54
|
+
tmp[1] = (num >> 15 & 0x7f) | 0x80;
|
55
|
+
tmp[2] = (num >> 8 & 0x7f) | 0x80;
|
56
|
+
tmp[3] = (num & 0xff);
|
57
|
+
} else {
|
58
|
+
rb_raise(rb_eRangeError, "int %d out of range", num);
|
59
|
+
}
|
60
|
+
|
61
|
+
rb_str_buf_cat(ser->stream, tmp, tmp_len);
|
62
|
+
}
|
63
|
+
|
64
|
+
void ser_write_uint16(AMF_SERIALIZER *ser, long num) {
|
65
|
+
if(num > 0xffff) rb_raise(rb_eRangeError, "int %ld out of range", num);
|
66
|
+
char tmp[2] = {(num >> 8) & 0xff, num & 0xff};
|
67
|
+
rb_str_buf_cat(ser->stream, tmp, 2);
|
68
|
+
}
|
69
|
+
|
70
|
+
void ser_write_uint32(AMF_SERIALIZER *ser, long num) {
|
71
|
+
if(num > 0xffffffff) rb_raise(rb_eRangeError, "int %ld out of range", num);
|
72
|
+
char tmp[4] = {(num >> 24) & 0xff, (num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff};
|
73
|
+
rb_str_buf_cat(ser->stream, tmp, 4);
|
74
|
+
}
|
75
|
+
|
76
|
+
void ser_write_double(AMF_SERIALIZER *ser, double num) {
|
77
|
+
union aligned {
|
78
|
+
double dval;
|
79
|
+
char cval[8];
|
80
|
+
} d;
|
81
|
+
const char *number = d.cval;
|
82
|
+
d.dval = num;
|
83
|
+
|
84
|
+
#ifdef WORDS_BIGENDIAN
|
85
|
+
rb_str_buf_cat(ser->stream, number, 8);
|
86
|
+
#else
|
87
|
+
char netnum[8] = {number[7],number[6],number[5],number[4],number[3],number[2],number[1],number[0]};
|
88
|
+
rb_str_buf_cat(ser->stream, netnum, 8);
|
89
|
+
#endif
|
90
|
+
}
|
91
|
+
|
92
|
+
void ser_get_string(VALUE obj, VALUE encode, char** str, long* len) {
|
93
|
+
int type = TYPE(obj);
|
94
|
+
if(type == T_STRING) {
|
95
|
+
#ifdef HAVE_RB_STR_ENCODE
|
96
|
+
if(encode == Qtrue) {
|
97
|
+
rb_encoding *enc = rb_enc_get(obj);
|
98
|
+
if (enc != rb_ascii8bit_encoding()) {
|
99
|
+
rb_encoding *utf8 = rb_utf8_encoding();
|
100
|
+
if (enc != utf8) obj = rb_str_encode(obj, rb_enc_from_encoding(utf8), 0, Qnil);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
#endif
|
104
|
+
*str = RSTRING_PTR(obj);
|
105
|
+
*len = RSTRING_LEN(obj);
|
106
|
+
} else if(type == T_SYMBOL) {
|
107
|
+
*str = (char*)rb_id2name(SYM2ID(obj));
|
108
|
+
*len = strlen(*str);
|
109
|
+
} else if(obj == Qnil) {
|
110
|
+
*len = 0;
|
111
|
+
} else {
|
112
|
+
rb_raise(rb_eArgError, "Invalid type in ser_get_string: %d", type);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
/*
|
117
|
+
* Write the given array in AMF0 notation
|
118
|
+
*/
|
119
|
+
static void ser0_write_array(VALUE self, VALUE ary) {
|
120
|
+
AMF_SERIALIZER *ser;
|
121
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
122
|
+
|
123
|
+
// Cache it
|
124
|
+
st_add_direct(ser->obj_cache, ary, LONG2FIX(ser->obj_index));
|
125
|
+
ser->obj_index++;
|
126
|
+
|
127
|
+
// Write it out
|
128
|
+
long i, len = RARRAY_LEN(ary);
|
129
|
+
ser_write_byte(ser, AMF0_STRICT_ARRAY_MARKER);
|
130
|
+
ser_write_uint32(ser, len);
|
131
|
+
for(i = 0; i < len; i++) {
|
132
|
+
ser0_serialize(self, RARRAY_PTR(ary)[i]);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
/*
|
137
|
+
* Supports writing strings and symbols. For hash keys, strings all have 16 bit
|
138
|
+
* lengths, so writing a type marker is unnecessary. In that case the third
|
139
|
+
* parameter should be set to Qfalse instead of Qtrue.
|
140
|
+
*/
|
141
|
+
static void ser0_write_string(AMF_SERIALIZER *ser, VALUE obj, VALUE write_marker) {
|
142
|
+
// Extract char array and length from object
|
143
|
+
char* str;
|
144
|
+
long len;
|
145
|
+
ser_get_string(obj, Qtrue, &str, &len);
|
146
|
+
|
147
|
+
// Write string
|
148
|
+
if(len > 0xffff) {
|
149
|
+
if(write_marker == Qtrue) ser_write_byte(ser, AMF0_LONG_STRING_MARKER);
|
150
|
+
ser_write_uint32(ser, len);
|
151
|
+
} else {
|
152
|
+
if(write_marker == Qtrue) ser_write_byte(ser, AMF0_STRING_MARKER);
|
153
|
+
ser_write_uint16(ser, len);
|
154
|
+
}
|
155
|
+
rb_str_buf_cat(ser->stream, str, len);
|
156
|
+
}
|
157
|
+
|
158
|
+
/*
|
159
|
+
* Hash iterator for object properties that writes the key and then serializes
|
160
|
+
* the value
|
161
|
+
*/
|
162
|
+
static int ser0_hash_iter(VALUE key, VALUE val, const VALUE args[1]) {
|
163
|
+
AMF_SERIALIZER *ser;
|
164
|
+
Data_Get_Struct(args[0], AMF_SERIALIZER, ser);
|
165
|
+
|
166
|
+
// Write key and value
|
167
|
+
ser0_write_string(ser, key, Qfalse); // Technically incorrect if key length is longer than a 16 bit string, but if you run into that you're screwed anyways
|
168
|
+
ser0_serialize(args[0], val);
|
169
|
+
|
170
|
+
return ST_CONTINUE;
|
171
|
+
}
|
172
|
+
|
173
|
+
/*
|
174
|
+
* Used for both hashes and objects. Takes the object and the props hash or Qnil,
|
175
|
+
* which forces a call to the class mapper for props for serialization. Prop
|
176
|
+
* sorting must be enabled by an explicit call to extconf.rb, so the tests will
|
177
|
+
* not pass typically on Ruby 1.8.
|
178
|
+
*/
|
179
|
+
static void ser0_write_object(VALUE self, VALUE obj, VALUE props) {
|
180
|
+
AMF_SERIALIZER *ser;
|
181
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
182
|
+
|
183
|
+
// Cache it
|
184
|
+
st_add_direct(ser->obj_cache, obj, LONG2FIX(ser->obj_index));
|
185
|
+
ser->obj_index++;
|
186
|
+
|
187
|
+
// Make a request for props hash unless we already have it
|
188
|
+
if(props == Qnil) {
|
189
|
+
props = rb_funcall(ser->class_mapper, id_props_for_serialization, 1, obj);
|
190
|
+
}
|
191
|
+
|
192
|
+
// Write header
|
193
|
+
VALUE class_name = rb_funcall(ser->class_mapper, id_get_as_class_name, 1, obj);
|
194
|
+
if(class_name != Qnil) {
|
195
|
+
ser_write_byte(ser, AMF0_TYPED_OBJECT_MARKER);
|
196
|
+
ser0_write_string(ser, class_name, Qfalse);
|
197
|
+
} else {
|
198
|
+
ser_write_byte(ser, AMF0_OBJECT_MARKER);
|
199
|
+
}
|
200
|
+
|
201
|
+
// Write out data
|
202
|
+
VALUE args[1] = {self};
|
203
|
+
#ifdef SORT_PROPS
|
204
|
+
// Sort is required prior to Ruby 1.9 to pass all the tests, as Ruby 1.8 hashes don't store insert order
|
205
|
+
VALUE sorted_props = rb_funcall(props, rb_intern("sort"), 0);
|
206
|
+
long i, len = RARRAY_LEN(sorted_props);
|
207
|
+
for(i = 0; i < len; i++) {
|
208
|
+
VALUE pair = RARRAY_PTR(sorted_props)[i];
|
209
|
+
ser0_hash_iter(RARRAY_PTR(pair)[0], RARRAY_PTR(pair)[1], args);
|
210
|
+
}
|
211
|
+
#else
|
212
|
+
rb_hash_foreach(props, ser0_hash_iter, (st_data_t)args);
|
213
|
+
#endif
|
214
|
+
|
215
|
+
ser_write_uint16(ser, 0);
|
216
|
+
ser_write_byte(ser, AMF0_OBJECT_END_MARKER);
|
217
|
+
}
|
218
|
+
|
219
|
+
static void ser0_write_time(VALUE self, VALUE time) {
|
220
|
+
AMF_SERIALIZER *ser;
|
221
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
222
|
+
|
223
|
+
ser_write_byte(ser, AMF0_DATE_MARKER);
|
224
|
+
|
225
|
+
// Write time
|
226
|
+
time = rb_obj_dup(time);
|
227
|
+
rb_funcall(time, id_utc, 0);
|
228
|
+
double tmp_num = NUM2DBL(rb_funcall(time, id_to_f, 0)) * 1000;
|
229
|
+
ser_write_double(ser, tmp_num);
|
230
|
+
ser_write_uint16(ser, 0); // Time zone
|
231
|
+
}
|
232
|
+
|
233
|
+
static void ser0_write_date(VALUE self, VALUE date) {
|
234
|
+
AMF_SERIALIZER *ser;
|
235
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
236
|
+
|
237
|
+
ser_write_byte(ser, AMF0_DATE_MARKER);
|
238
|
+
|
239
|
+
// Write time
|
240
|
+
double tmp_num = rb_str_to_dbl(rb_funcall(date, rb_intern("strftime"), 1, rb_str_new2("%Q")), Qfalse);
|
241
|
+
ser_write_double(ser, tmp_num);
|
242
|
+
ser_write_uint16(ser, 0); // Time zone
|
243
|
+
}
|
244
|
+
|
245
|
+
/*
|
246
|
+
* Serializes the object to a string and returns that string
|
247
|
+
*/
|
248
|
+
static VALUE ser0_serialize(VALUE self, VALUE obj) {
|
249
|
+
AMF_SERIALIZER *ser;
|
250
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
251
|
+
|
252
|
+
int type = TYPE(obj);
|
253
|
+
VALUE klass = Qnil;
|
254
|
+
if(type == T_OBJECT || type == T_DATA) {
|
255
|
+
klass = CLASS_OF(obj);
|
256
|
+
}
|
257
|
+
|
258
|
+
VALUE obj_index;
|
259
|
+
if(st_lookup(ser->obj_cache, obj, &obj_index)) {
|
260
|
+
ser_write_byte(ser, AMF0_REFERENCE_MARKER);
|
261
|
+
ser_write_uint16(ser, FIX2LONG(obj_index));
|
262
|
+
} else if(rb_respond_to(obj, id_encode_amf)) {
|
263
|
+
rb_funcall(obj, id_encode_amf, 1, self);
|
264
|
+
} else if(type == T_STRING || type == T_SYMBOL) {
|
265
|
+
ser0_write_string(ser, obj, Qtrue);
|
266
|
+
} else if(rb_obj_is_kind_of(obj, rb_cNumeric)) {
|
267
|
+
ser_write_byte(ser, AMF0_NUMBER_MARKER);
|
268
|
+
ser_write_double(ser, RFLOAT_VALUE(rb_Float(obj)));
|
269
|
+
} else if(type == T_NIL) {
|
270
|
+
ser_write_byte(ser, AMF0_NULL_MARKER);
|
271
|
+
} else if(type == T_TRUE || type == T_FALSE) {
|
272
|
+
ser_write_byte(ser, AMF0_BOOLEAN_MARKER);
|
273
|
+
ser_write_byte(ser, type == T_TRUE ? 1 : 0);
|
274
|
+
} else if(type == T_ARRAY) {
|
275
|
+
ser0_write_array(self, obj);
|
276
|
+
} else if(klass == rb_cTime) {
|
277
|
+
ser0_write_time(self, obj);
|
278
|
+
} else if(klass == cDate || klass == cDateTime) {
|
279
|
+
ser0_write_date(self, obj);
|
280
|
+
} else if(type == T_HASH || type == T_OBJECT) {
|
281
|
+
ser0_write_object(self, obj, Qnil);
|
282
|
+
}
|
283
|
+
|
284
|
+
return ser->stream;
|
285
|
+
}
|
286
|
+
|
287
|
+
/*
|
288
|
+
* Writes an AMF3 style string. Accepts strings, symbols, and nil, and handles
|
289
|
+
* all the necessary encoding and caching.
|
290
|
+
*/
|
291
|
+
static void ser3_write_utf8vr(AMF_SERIALIZER *ser, VALUE obj) {
|
292
|
+
// Extract char array and length from object
|
293
|
+
char* str;
|
294
|
+
long len;
|
295
|
+
ser_get_string(obj, Qtrue, &str, &len);
|
296
|
+
|
297
|
+
// Write string
|
298
|
+
VALUE str_index;
|
299
|
+
if(len == 0) {
|
300
|
+
ser_write_byte(ser, AMF3_EMPTY_STRING);
|
301
|
+
} else if(st_lookup(ser->str_cache, (st_data_t)str, &str_index)) {
|
302
|
+
ser_write_int(ser, FIX2INT(str_index) << 1);
|
303
|
+
} else {
|
304
|
+
st_add_direct(ser->str_cache, (st_data_t)strdup(str), LONG2FIX(ser->str_index));
|
305
|
+
ser->str_index++;
|
306
|
+
|
307
|
+
ser_write_int(ser, ((int)len) << 1 | 1);
|
308
|
+
rb_str_buf_cat(ser->stream, str, len);
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
/*
|
313
|
+
* Writes Numeric conforming object using AMF3 notation
|
314
|
+
*/
|
315
|
+
static void ser3_write_numeric(AMF_SERIALIZER *ser, VALUE num) {
|
316
|
+
// Is it an integer in range?
|
317
|
+
if(rb_funcall(num, id_is_integer, 0) == Qtrue) {
|
318
|
+
// It's an integer internally, so now we need to check if it's in range
|
319
|
+
VALUE int_obj = rb_Integer(num);
|
320
|
+
if(TYPE(int_obj) == T_FIXNUM) {
|
321
|
+
long long_val = FIX2LONG(int_obj);
|
322
|
+
if(long_val < MIN_INTEGER || long_val > MAX_INTEGER) {
|
323
|
+
// Outside range, but we have a value already, so just cast to double
|
324
|
+
ser_write_byte(ser, AMF3_DOUBLE_MARKER);
|
325
|
+
ser_write_double(ser, (double)long_val);
|
326
|
+
} else {
|
327
|
+
// Inside valid integer range
|
328
|
+
ser_write_byte(ser, AMF3_INTEGER_MARKER);
|
329
|
+
ser_write_int(ser, (int)long_val);
|
330
|
+
}
|
331
|
+
return;
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
// It's either not an integer or out of range, so write as a double
|
336
|
+
ser_write_byte(ser, AMF3_DOUBLE_MARKER);
|
337
|
+
ser_write_double(ser, RFLOAT_VALUE(rb_Float(num)));
|
338
|
+
}
|
339
|
+
|
340
|
+
/*
|
341
|
+
* Writes the given array using AMF3 notation
|
342
|
+
*/
|
343
|
+
static void ser3_write_array(VALUE self, VALUE ary) {
|
344
|
+
AMF_SERIALIZER *ser;
|
345
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
346
|
+
|
347
|
+
// Is it an array collection?
|
348
|
+
VALUE is_ac = Qfalse;
|
349
|
+
if(rb_respond_to(ary, id_is_array_collection)) {
|
350
|
+
is_ac = rb_funcall(ary, id_is_array_collection, 0);
|
351
|
+
} else {
|
352
|
+
is_ac = rb_funcall(ser->class_mapper, id_use_array_collection, 0);
|
353
|
+
}
|
354
|
+
|
355
|
+
// Write type marker
|
356
|
+
ser_write_byte(ser, is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER);
|
357
|
+
|
358
|
+
// Write object ref, or cache it
|
359
|
+
VALUE obj_index;
|
360
|
+
if(st_lookup(ser->obj_cache, ary, &obj_index)) {
|
361
|
+
ser_write_int(ser, FIX2INT(obj_index) << 1);
|
362
|
+
return;
|
363
|
+
} else {
|
364
|
+
st_add_direct(ser->obj_cache, ary, LONG2FIX(ser->obj_index));
|
365
|
+
ser->obj_index++;
|
366
|
+
if(is_ac) ser->obj_index++; // The array collection source array
|
367
|
+
}
|
368
|
+
|
369
|
+
// Write out traits and array marker if it's an array collection
|
370
|
+
if(is_ac) {
|
371
|
+
VALUE trait_index;
|
372
|
+
char array_collection_name[34] = "flex.messaging.io.ArrayCollection";
|
373
|
+
if(st_lookup(ser->trait_cache, (st_data_t)array_collection_name, &trait_index)) {
|
374
|
+
ser_write_int(ser, FIX2INT(trait_index) << 2 | 0x01);
|
375
|
+
} else {
|
376
|
+
st_add_direct(ser->trait_cache, (st_data_t)strdup(array_collection_name), LONG2FIX(ser->trait_index));
|
377
|
+
ser->trait_index++;
|
378
|
+
ser_write_byte(ser, 0x07); // Trait header
|
379
|
+
ser3_write_utf8vr(ser, rb_str_new2(array_collection_name));
|
380
|
+
}
|
381
|
+
ser_write_byte(ser, AMF3_ARRAY_MARKER);
|
382
|
+
}
|
383
|
+
|
384
|
+
// Write header
|
385
|
+
int header = ((int)RARRAY_LEN(ary)) << 1 | 1;
|
386
|
+
ser_write_int(ser, header);
|
387
|
+
ser_write_byte(ser, AMF3_CLOSE_DYNAMIC_ARRAY);
|
388
|
+
|
389
|
+
// Write contents
|
390
|
+
long i, len = RARRAY_LEN(ary);
|
391
|
+
for(i = 0; i < len; i++) {
|
392
|
+
ser3_serialize(self, RARRAY_PTR(ary)[i]);
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
/*
|
397
|
+
* AMF3 property hash write iterator. Checks the args->extra hash, if given,
|
398
|
+
* and skips properties that are keys in that hash.
|
399
|
+
*/
|
400
|
+
static int ser3_hash_iter(VALUE key, VALUE val, const VALUE args[2]) {
|
401
|
+
AMF_SERIALIZER *ser;
|
402
|
+
Data_Get_Struct(args[0], AMF_SERIALIZER, ser);
|
403
|
+
|
404
|
+
if(args[1] == Qnil || rb_funcall(args[1], id_haskey, 1, key) == Qfalse) {
|
405
|
+
// Write key and value
|
406
|
+
ser3_write_utf8vr(ser, key);
|
407
|
+
ser3_serialize(args[0], val);
|
408
|
+
}
|
409
|
+
return ST_CONTINUE;
|
410
|
+
}
|
411
|
+
|
412
|
+
/*
|
413
|
+
* Used for both hashes and objects. Takes the object and the props hash or Qnil,
|
414
|
+
* which forces a call to the class mapper for props for serialization. Prop
|
415
|
+
* sorting must be enabled by an explicit call to extconf.rb, so the tests will
|
416
|
+
* not pass typically on Ruby 1.8. If you need to have specific traits, you can
|
417
|
+
* also pass that in, or pass Qnil to use the default traits - dynamic with no
|
418
|
+
* defined members.
|
419
|
+
*/
|
420
|
+
static void ser3_write_object(VALUE self, VALUE obj, VALUE props, VALUE traits) {
|
421
|
+
AMF_SERIALIZER *ser;
|
422
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
423
|
+
long i;
|
424
|
+
|
425
|
+
// Write type marker
|
426
|
+
ser_write_byte(ser, AMF3_OBJECT_MARKER);
|
427
|
+
|
428
|
+
// Write object ref, or cache it
|
429
|
+
VALUE obj_index;
|
430
|
+
if(st_lookup(ser->obj_cache, obj, &obj_index)) {
|
431
|
+
ser_write_int(ser, FIX2INT(obj_index) << 1);
|
432
|
+
return;
|
433
|
+
} else {
|
434
|
+
st_add_direct(ser->obj_cache, obj, LONG2FIX(ser->obj_index));
|
435
|
+
ser->obj_index++;
|
436
|
+
}
|
437
|
+
|
438
|
+
// Extract traits data, or use defaults
|
439
|
+
VALUE is_default = Qfalse;
|
440
|
+
VALUE class_name = Qnil;
|
441
|
+
VALUE members = Qnil;
|
442
|
+
long members_len = 0;
|
443
|
+
VALUE dynamic = Qtrue;
|
444
|
+
VALUE externalizable = Qfalse;
|
445
|
+
if(traits == Qnil) {
|
446
|
+
class_name = rb_funcall(ser->class_mapper, id_get_as_class_name, 1, obj);
|
447
|
+
if(class_name == Qnil) is_default = Qtrue;
|
448
|
+
} else {
|
449
|
+
class_name = rb_hash_aref(traits, sym_class_name);
|
450
|
+
members = rb_hash_aref(traits, sym_members);
|
451
|
+
if(members != Qnil) members_len = RARRAY_LEN(members);
|
452
|
+
dynamic = rb_hash_aref(traits, sym_dynamic);
|
453
|
+
externalizable = rb_hash_aref(traits, sym_externalizable);
|
454
|
+
}
|
455
|
+
|
456
|
+
// Handle trait caching
|
457
|
+
int did_ref = 0;
|
458
|
+
VALUE trait_index;
|
459
|
+
if(is_default == Qtrue || class_name != Qnil) {
|
460
|
+
const char *ref_class_name = is_default == Qtrue ? "__default__" : RSTRING_PTR(class_name);
|
461
|
+
if(st_lookup(ser->trait_cache, (st_data_t)ref_class_name, &trait_index)) {
|
462
|
+
ser_write_int(ser, FIX2INT(trait_index) << 2 | 0x01);
|
463
|
+
did_ref = 1;
|
464
|
+
} else {
|
465
|
+
st_add_direct(ser->trait_cache, (st_data_t)strdup(ref_class_name), LONG2FIX(ser->trait_index));
|
466
|
+
ser->trait_index++;
|
467
|
+
}
|
468
|
+
}
|
469
|
+
|
470
|
+
// Write traits outs if didn't write reference
|
471
|
+
if(!did_ref) {
|
472
|
+
// Write out trait header
|
473
|
+
int header = 0x03;
|
474
|
+
if(dynamic == Qtrue) header |= 0x02 << 2;
|
475
|
+
if(externalizable == Qtrue) header |= 0x01 << 2;
|
476
|
+
header |= ((int)members_len) << 4;
|
477
|
+
ser_write_int(ser, header);
|
478
|
+
|
479
|
+
// Write class name
|
480
|
+
ser3_write_utf8vr(ser, class_name);
|
481
|
+
|
482
|
+
// Write out members
|
483
|
+
for(i = 0; i < members_len; i++) {
|
484
|
+
ser3_write_utf8vr(ser, RARRAY_PTR(members)[i]);
|
485
|
+
}
|
486
|
+
}
|
487
|
+
|
488
|
+
// Raise exception if marked externalizable
|
489
|
+
if(externalizable == Qtrue) {
|
490
|
+
rb_funcall(obj, rb_intern("write_external"), 1, self);
|
491
|
+
return;
|
492
|
+
}
|
493
|
+
|
494
|
+
// Make a request for props hash unless we already have it
|
495
|
+
if(props == Qnil) {
|
496
|
+
props = rb_funcall(ser->class_mapper, id_props_for_serialization, 1, obj);
|
497
|
+
}
|
498
|
+
|
499
|
+
// Write sealed members
|
500
|
+
VALUE skipped_members = members_len ? rb_hash_new() : Qnil;
|
501
|
+
for(i = 0; i < members_len; i++) {
|
502
|
+
ser3_serialize(self, rb_hash_aref(props, RARRAY_PTR(members)[i]));
|
503
|
+
rb_hash_aset(skipped_members, RARRAY_PTR(members)[i], Qtrue);
|
504
|
+
}
|
505
|
+
|
506
|
+
// Write dynamic properties
|
507
|
+
if(dynamic == Qtrue) {
|
508
|
+
VALUE args[2] = {self, skipped_members};
|
509
|
+
#ifdef SORT_PROPS
|
510
|
+
// Sort is required prior to Ruby 1.9 to pass all the tests, as Ruby 1.8 hashes don't store insert order
|
511
|
+
VALUE sorted_props = rb_funcall(props, rb_intern("sort"), 0);
|
512
|
+
for(i = 0; i < RARRAY_LEN(sorted_props); i++) {
|
513
|
+
VALUE pair = RARRAY_PTR(sorted_props)[i];
|
514
|
+
ser3_hash_iter(RARRAY_PTR(pair)[0], RARRAY_PTR(pair)[1], args);
|
515
|
+
}
|
516
|
+
#else
|
517
|
+
rb_hash_foreach(props, ser3_hash_iter, (st_data_t)args);
|
518
|
+
#endif
|
519
|
+
|
520
|
+
ser_write_byte(ser, AMF3_CLOSE_DYNAMIC_OBJECT);
|
521
|
+
}
|
522
|
+
}
|
523
|
+
|
524
|
+
static void ser3_write_time(VALUE self, VALUE time_obj) {
|
525
|
+
AMF_SERIALIZER *ser;
|
526
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
527
|
+
|
528
|
+
ser_write_byte(ser, AMF3_DATE_MARKER);
|
529
|
+
|
530
|
+
// Write object ref, or cache it
|
531
|
+
VALUE obj_index;
|
532
|
+
if(st_lookup(ser->obj_cache, time_obj, &obj_index)) {
|
533
|
+
ser_write_int(ser, FIX2INT(obj_index) << 1);
|
534
|
+
return;
|
535
|
+
} else {
|
536
|
+
st_add_direct(ser->obj_cache, time_obj, LONG2FIX(ser->obj_index));
|
537
|
+
ser->obj_index++;
|
538
|
+
}
|
539
|
+
|
540
|
+
// Write time
|
541
|
+
ser_write_byte(ser, AMF3_NULL_MARKER); // Ref header
|
542
|
+
time_obj = rb_obj_dup(time_obj);
|
543
|
+
rb_funcall(time_obj, id_utc, 0);
|
544
|
+
double tmp_num = NUM2DBL(rb_funcall(time_obj, id_to_f, 0)) * 1000;
|
545
|
+
ser_write_double(ser, tmp_num);
|
546
|
+
}
|
547
|
+
|
548
|
+
static void ser3_write_date(VALUE self, VALUE date) {
|
549
|
+
AMF_SERIALIZER *ser;
|
550
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
551
|
+
|
552
|
+
ser_write_byte(ser, AMF3_DATE_MARKER);
|
553
|
+
|
554
|
+
// Write object ref, or cache it
|
555
|
+
VALUE obj_index;
|
556
|
+
if(st_lookup(ser->obj_cache, date, &obj_index)) {
|
557
|
+
ser_write_int(ser, FIX2INT(obj_index) << 1);
|
558
|
+
return;
|
559
|
+
} else {
|
560
|
+
st_add_direct(ser->obj_cache, date, LONG2FIX(ser->obj_index));
|
561
|
+
ser->obj_index++;
|
562
|
+
}
|
563
|
+
|
564
|
+
// Write time
|
565
|
+
ser_write_byte(ser, AMF3_NULL_MARKER); // Ref header
|
566
|
+
double tmp_num = rb_str_to_dbl(rb_funcall(date, rb_intern("strftime"), 1, rb_str_new2("%Q")), Qfalse);
|
567
|
+
ser_write_double(ser, tmp_num);
|
568
|
+
}
|
569
|
+
|
570
|
+
static void ser3_write_byte_array(VALUE self, VALUE ba) {
|
571
|
+
AMF_SERIALIZER *ser;
|
572
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
573
|
+
|
574
|
+
ser_write_byte(ser, AMF3_BYTE_ARRAY_MARKER);
|
575
|
+
|
576
|
+
// Write object ref, or cache it
|
577
|
+
VALUE obj_index;
|
578
|
+
if(st_lookup(ser->obj_cache, ba, &obj_index)) {
|
579
|
+
ser_write_int(ser, FIX2INT(obj_index) << 1);
|
580
|
+
return;
|
581
|
+
} else {
|
582
|
+
st_add_direct(ser->obj_cache, ba, LONG2FIX(ser->obj_index));
|
583
|
+
ser->obj_index++;
|
584
|
+
}
|
585
|
+
|
586
|
+
// Write byte array
|
587
|
+
VALUE str = rb_funcall(ba, rb_intern("string"), 0);
|
588
|
+
int len = (int)(RSTRING_LEN(str) << 1); // Explicitly cast to int to avoid compiler warning
|
589
|
+
ser_write_int(ser, len | 1);
|
590
|
+
rb_str_buf_cat(ser->stream, RSTRING_PTR(str), RSTRING_LEN(str));
|
591
|
+
}
|
592
|
+
|
593
|
+
/*
|
594
|
+
* Serializes the object to a string and returns that string
|
595
|
+
*/
|
596
|
+
static VALUE ser3_serialize(VALUE self, VALUE obj) {
|
597
|
+
AMF_SERIALIZER *ser;
|
598
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
599
|
+
|
600
|
+
int type = TYPE(obj);
|
601
|
+
VALUE klass = Qnil;
|
602
|
+
if(type == T_OBJECT || type == T_DATA || type == T_ARRAY) {
|
603
|
+
klass = CLASS_OF(obj);
|
604
|
+
}
|
605
|
+
|
606
|
+
if(rb_respond_to(obj, id_encode_amf)) {
|
607
|
+
rb_funcall(obj, id_encode_amf, 1, self);
|
608
|
+
} else if(type == T_STRING || type == T_SYMBOL) {
|
609
|
+
ser_write_byte(ser, AMF3_STRING_MARKER);
|
610
|
+
ser3_write_utf8vr(ser, obj);
|
611
|
+
} else if(rb_obj_is_kind_of(obj, rb_cNumeric)) {
|
612
|
+
ser3_write_numeric(ser, obj);
|
613
|
+
} else if(type == T_NIL) {
|
614
|
+
ser_write_byte(ser, AMF3_NULL_MARKER);
|
615
|
+
} else if(type == T_TRUE) {
|
616
|
+
ser_write_byte(ser, AMF3_TRUE_MARKER);
|
617
|
+
} else if(type == T_FALSE) {
|
618
|
+
ser_write_byte(ser, AMF3_FALSE_MARKER);
|
619
|
+
} else if(type == T_ARRAY) {
|
620
|
+
ser3_write_array(self, obj);
|
621
|
+
} else if(type == T_HASH) {
|
622
|
+
ser3_write_object(self, obj, Qnil, Qnil);
|
623
|
+
} else if(klass == rb_cTime) {
|
624
|
+
ser3_write_time(self, obj);
|
625
|
+
} else if(klass == cDate || klass == cDateTime) {
|
626
|
+
ser3_write_date(self, obj);
|
627
|
+
} else if(klass == cStringIO) {
|
628
|
+
ser3_write_byte_array(self, obj);
|
629
|
+
} else if(type == T_OBJECT) {
|
630
|
+
ser3_write_object(self, obj, Qnil, Qnil);
|
631
|
+
}
|
632
|
+
|
633
|
+
return ser->stream;
|
634
|
+
}
|
635
|
+
|
636
|
+
/*
|
637
|
+
* Mark ruby objects for GC
|
638
|
+
*/
|
639
|
+
static void ser_mark(AMF_SERIALIZER *ser) {
|
640
|
+
if(!ser) return;
|
641
|
+
rb_gc_mark(ser->class_mapper);
|
642
|
+
rb_gc_mark(ser->stream);
|
643
|
+
}
|
644
|
+
|
645
|
+
/*
|
646
|
+
* Free cache tables, stream and the struct itself
|
647
|
+
*/
|
648
|
+
int ser_free_strtable_key(st_data_t key, st_data_t value, st_data_t ignored)
|
649
|
+
{
|
650
|
+
xfree((void *)key);
|
651
|
+
|
652
|
+
return ST_DELETE;
|
653
|
+
}
|
654
|
+
inline void ser_free_cache(AMF_SERIALIZER *ser) {
|
655
|
+
if(ser->str_cache) {
|
656
|
+
st_foreach(ser->str_cache, ser_free_strtable_key, 0);
|
657
|
+
st_free_table(ser->str_cache);
|
658
|
+
ser->str_cache = NULL;
|
659
|
+
}
|
660
|
+
if(ser->trait_cache) {
|
661
|
+
st_foreach(ser->trait_cache, ser_free_strtable_key, 0);
|
662
|
+
st_free_table(ser->trait_cache);
|
663
|
+
ser->trait_cache = NULL;
|
664
|
+
}
|
665
|
+
if(ser->obj_cache) {
|
666
|
+
st_free_table(ser->obj_cache);
|
667
|
+
ser->obj_cache = NULL;
|
668
|
+
}
|
669
|
+
}
|
670
|
+
static void ser_free(AMF_SERIALIZER *ser) {
|
671
|
+
ser_free_cache(ser);
|
672
|
+
xfree(ser);
|
673
|
+
}
|
674
|
+
|
675
|
+
/*
|
676
|
+
* Create new struct and wrap with class
|
677
|
+
*/
|
678
|
+
static VALUE ser_alloc(VALUE klass) {
|
679
|
+
// Allocate struct
|
680
|
+
AMF_SERIALIZER *ser = ALLOC(AMF_SERIALIZER);
|
681
|
+
memset(ser, 0, sizeof(AMF_SERIALIZER));
|
682
|
+
return Data_Wrap_Struct(klass, ser_mark, ser_free, ser);
|
683
|
+
}
|
684
|
+
|
685
|
+
/*
|
686
|
+
* Initializer
|
687
|
+
*/
|
688
|
+
static VALUE ser_initialize(VALUE self, VALUE class_mapper) {
|
689
|
+
AMF_SERIALIZER *ser;
|
690
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
691
|
+
|
692
|
+
ser->class_mapper = class_mapper;
|
693
|
+
ser->depth = 0;
|
694
|
+
ser->stream = rb_str_buf_new(0);
|
695
|
+
|
696
|
+
return self;
|
697
|
+
}
|
698
|
+
|
699
|
+
/*
|
700
|
+
* call-seq:
|
701
|
+
* ser.version => int
|
702
|
+
*
|
703
|
+
* Returns the serializer version number, so that a custom encode_amf method
|
704
|
+
* knows which version to encode for
|
705
|
+
*/
|
706
|
+
static VALUE ser_version(VALUE self) {
|
707
|
+
AMF_SERIALIZER *ser;
|
708
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
709
|
+
return INT2FIX(ser->version);
|
710
|
+
}
|
711
|
+
|
712
|
+
/*
|
713
|
+
* call-seq:
|
714
|
+
* ser.stream => string
|
715
|
+
*
|
716
|
+
* Returns the string that the serializer is writing to
|
717
|
+
*/
|
718
|
+
static VALUE ser_stream(VALUE self) {
|
719
|
+
AMF_SERIALIZER *ser;
|
720
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
721
|
+
return ser->stream;
|
722
|
+
}
|
723
|
+
|
724
|
+
/*
|
725
|
+
* call-seq:
|
726
|
+
* ser.serialize(amf_ver, obj) => string
|
727
|
+
*
|
728
|
+
* Serialize the given object to the current stream and returns the stream
|
729
|
+
*/
|
730
|
+
VALUE ser_serialize(VALUE self, VALUE ver, VALUE obj) {
|
731
|
+
AMF_SERIALIZER *ser;
|
732
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
733
|
+
|
734
|
+
// Process version
|
735
|
+
int int_ver = FIX2INT(ver);
|
736
|
+
if(int_ver != 0 && int_ver != 3) rb_raise(rb_eArgError, "unsupported version %d", int_ver);
|
737
|
+
ser->version = int_ver;
|
738
|
+
|
739
|
+
// Initialize caches
|
740
|
+
if(ser->depth == 0) {
|
741
|
+
ser->obj_cache = st_init_numtable();
|
742
|
+
ser->obj_index = 0;
|
743
|
+
if(ser->version == 3) {
|
744
|
+
ser->str_cache = st_init_strtable();
|
745
|
+
ser->str_index = 0;
|
746
|
+
ser->trait_cache = st_init_strtable();
|
747
|
+
ser->trait_index = 0;
|
748
|
+
}
|
749
|
+
}
|
750
|
+
ser->depth++;
|
751
|
+
|
752
|
+
// Perform serialization
|
753
|
+
if(ser->version == 0) {
|
754
|
+
ser0_serialize(self, obj);
|
755
|
+
} else {
|
756
|
+
ser3_serialize(self, obj);
|
757
|
+
}
|
758
|
+
|
759
|
+
// Clean up
|
760
|
+
ser->depth--;
|
761
|
+
if(ser->depth == 0) ser_free_cache(ser);
|
762
|
+
|
763
|
+
return ser->stream;
|
764
|
+
}
|
765
|
+
|
766
|
+
/*
|
767
|
+
* call-seq:
|
768
|
+
* ser.write_array(ary) => ser
|
769
|
+
*
|
770
|
+
* Serializes the given array to the serializer stream
|
771
|
+
*/
|
772
|
+
static VALUE ser_write_array(VALUE self, VALUE ary) {
|
773
|
+
AMF_SERIALIZER *ser;
|
774
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
775
|
+
if(ser->version == 0) {
|
776
|
+
ser0_write_array(self, ary);
|
777
|
+
} else {
|
778
|
+
ser3_write_array(self, ary);
|
779
|
+
}
|
780
|
+
return self;
|
781
|
+
}
|
782
|
+
|
783
|
+
/*
|
784
|
+
* call-seq:
|
785
|
+
* ser.write_object(obj, props=nil) => ser
|
786
|
+
* ser.write_object(obj, props=nil, traits=nil) => ser
|
787
|
+
*
|
788
|
+
* Serializes the given object or hash to the serializer stream using
|
789
|
+
* the proper serializer version. If given a props hash, uses that
|
790
|
+
* instead of using the class mapper to calculate it. If given a traits
|
791
|
+
* hash for AMF3, uses that instead of the default dynamic traits with
|
792
|
+
* the mapped class name.
|
793
|
+
*/
|
794
|
+
static VALUE ser_write_object(int argc, VALUE *argv, VALUE self) {
|
795
|
+
AMF_SERIALIZER *ser;
|
796
|
+
Data_Get_Struct(self, AMF_SERIALIZER, ser);
|
797
|
+
|
798
|
+
// Check args and call implementation
|
799
|
+
VALUE obj;
|
800
|
+
VALUE props = Qnil;
|
801
|
+
VALUE traits = Qnil;
|
802
|
+
if(ser->version == 0) {
|
803
|
+
rb_scan_args(argc, argv, "11", &obj, &props);
|
804
|
+
ser0_write_object(self, obj, props);
|
805
|
+
} else {
|
806
|
+
rb_scan_args(argc, argv, "12", &obj, &props, &traits);
|
807
|
+
ser3_write_object(self, obj, props, traits);
|
808
|
+
}
|
809
|
+
|
810
|
+
return self;
|
811
|
+
}
|
812
|
+
|
813
|
+
void Init_rocket_amf_serializer() {
|
814
|
+
// Define Serializer
|
815
|
+
cSerializer = rb_define_class_under(mRocketAMFExt, "Serializer", rb_cObject);
|
816
|
+
rb_define_alloc_func(cSerializer, ser_alloc);
|
817
|
+
rb_define_method(cSerializer, "initialize", ser_initialize, 1);
|
818
|
+
rb_define_method(cSerializer, "version", ser_version, 0);
|
819
|
+
rb_define_method(cSerializer, "stream", ser_stream, 0);
|
820
|
+
rb_define_method(cSerializer, "serialize", ser_serialize, 2);
|
821
|
+
rb_define_method(cSerializer, "write_array", ser_write_array, 1);
|
822
|
+
rb_define_method(cSerializer, "write_object", ser_write_object, -1);
|
823
|
+
|
824
|
+
// Get refs to commonly used symbols and ids
|
825
|
+
id_haskey = rb_intern("has_key?");
|
826
|
+
id_encode_amf = rb_intern("encode_amf");
|
827
|
+
id_is_array_collection = rb_intern("is_array_collection?");
|
828
|
+
id_use_array_collection = rb_intern("use_array_collection");
|
829
|
+
id_get_as_class_name = rb_intern("get_as_class_name");
|
830
|
+
id_props_for_serialization = rb_intern("props_for_serialization");
|
831
|
+
id_utc = rb_intern("utc");
|
832
|
+
id_to_f = rb_intern("to_f");
|
833
|
+
id_is_integer = rb_intern("integer?");
|
834
|
+
}
|