1# encoding: ascii-8bit
2#
3# Licensed to the Apache Software Foundation (ASF) under one
4# or more contributor license agreements. See the NOTICE file
5# distributed with this work for additional information
6# regarding copyright ownership. The ASF licenses this file
7# to you under the Apache License, Version 2.0 (the
8# "License"); you may not use this file except in compliance
9# with the License. You may obtain a copy of the License at
10#
11#   http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing,
14# software distributed under the License is distributed on an
15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16# KIND, either express or implied. See the License for the
17# specific language governing permissions and limitations
18# under the License.
19#
20
21require 'spec_helper'
22
23shared_examples_for 'a binary protocol' do
24  before(:each) do
25    @trans = Thrift::MemoryBufferTransport.new
26    @prot = protocol_class.new(@trans)
27  end
28
29  it "should define the proper VERSION_1, VERSION_MASK AND TYPE_MASK" do
30    expect(protocol_class.const_get(:VERSION_MASK)).to eq(0xffff0000)
31    expect(protocol_class.const_get(:VERSION_1)).to eq(0x80010000)
32    expect(protocol_class.const_get(:TYPE_MASK)).to eq(0x000000ff)
33  end
34
35  it "should make strict_read readable" do
36    expect(@prot.strict_read).to eql(true)
37  end
38
39  it "should make strict_write readable" do
40    expect(@prot.strict_write).to eql(true)
41  end
42
43  it "should write the message header" do
44    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
45    expect(@trans.read(@trans.available)).to eq([protocol_class.const_get(:VERSION_1) | Thrift::MessageTypes::CALL, "testMessage".size, "testMessage", 17].pack("NNa11N"))
46  end
47
48  it "should write the message header without version when writes are not strict" do
49    @prot = protocol_class.new(@trans, true, false) # no strict write
50    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
51    expect(@trans.read(@trans.available)).to eq("\000\000\000\vtestMessage\001\000\000\000\021")
52  end
53
54  it "should write the message header with a version when writes are strict" do
55    @prot = protocol_class.new(@trans) # strict write
56    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
57    expect(@trans.read(@trans.available)).to eq("\200\001\000\001\000\000\000\vtestMessage\000\000\000\021")
58  end
59
60
61  # message footer is a noop
62
63  it "should write the field header" do
64    @prot.write_field_begin('foo', Thrift::Types::DOUBLE, 3)
65    expect(@trans.read(@trans.available)).to eq([Thrift::Types::DOUBLE, 3].pack("cn"))
66  end
67
68  # field footer is a noop
69
70  it "should write the STOP field" do
71    @prot.write_field_stop
72    expect(@trans.read(1)).to eq("\000")
73  end
74
75  it "should write the map header" do
76    @prot.write_map_begin(Thrift::Types::STRING, Thrift::Types::LIST, 17)
77    expect(@trans.read(@trans.available)).to eq([Thrift::Types::STRING, Thrift::Types::LIST, 17].pack("ccN"));
78  end
79
80  # map footer is a noop
81
82  it "should write the list header" do
83    @prot.write_list_begin(Thrift::Types::I16, 42)
84    expect(@trans.read(@trans.available)).to eq([Thrift::Types::I16, 42].pack("cN"))
85  end
86
87  # list footer is a noop
88
89  it "should write the set header" do
90    @prot.write_set_begin(Thrift::Types::I16, 42)
91    expect(@trans.read(@trans.available)).to eq([Thrift::Types::I16, 42].pack("cN"))
92  end
93
94  it "should write a bool" do
95    @prot.write_bool(true)
96    @prot.write_bool(false)
97    expect(@trans.read(@trans.available)).to eq("\001\000")
98  end
99
100  it "should treat a nil bool as false" do
101    @prot.write_bool(nil)
102    expect(@trans.read(1)).to eq("\000")
103  end
104
105  it "should write a byte" do
106    # byte is small enough, let's check -128..127
107    (-128..127).each do |i|
108      @prot.write_byte(i)
109      expect(@trans.read(1)).to eq([i].pack('c'))
110    end
111  end
112
113  it "should clip numbers out of signed range" do
114    (128..255).each do |i|
115      @prot.write_byte(i)
116      expect(@trans.read(1)).to eq([i].pack('c'))
117    end
118  end
119
120  it "errors out with a Bignum" do
121    expect { @prot.write_byte(2**65) }.to raise_error(RangeError)
122  end
123
124  it "should error gracefully when trying to write a nil byte" do
125    expect { @prot.write_byte(nil) }.to raise_error
126  end
127
128  it "should write an i16" do
129    # try a random scattering of values
130    # include the signed i16 minimum/maximum
131    [-2**15, -1024, 17, 0, -10000, 1723, 2**15-1].each do |i|
132      @prot.write_i16(i)
133    end
134    # and try something out of signed range, it should clip
135    @prot.write_i16(2**15 + 5)
136
137    expect(@trans.read(@trans.available)).to eq("\200\000\374\000\000\021\000\000\330\360\006\273\177\377\200\005")
138
139    # a Bignum should error
140    # lambda { @prot.write_i16(2**65) }.should raise_error(RangeError)
141  end
142
143  it "should error gracefully when trying to write a nil i16" do
144    expect { @prot.write_i16(nil) }.to raise_error
145  end
146
147  it "should write an i32" do
148    # try a random scattering of values
149    # include the signed i32 minimum/maximum
150    [-2**31, -123123, -2532, -3, 0, 2351235, 12331, 2**31-1].each do |i|
151      @prot.write_i32(i)
152    end
153    # try something out of signed range, it should clip
154    expect(@trans.read(@trans.available)).to eq("\200\000\000\000" + "\377\376\037\r" + "\377\377\366\034" + "\377\377\377\375" + "\000\000\000\000" + "\000#\340\203" + "\000\0000+" + "\177\377\377\377")
155    [2 ** 31 + 5, 2 ** 65 + 5].each do |i|
156      expect { @prot.write_i32(i) }.to raise_error(RangeError)
157    end
158  end
159
160  it "should error gracefully when trying to write a nil i32" do
161    expect { @prot.write_i32(nil) }.to raise_error
162  end
163
164  it "should write an i64" do
165    # try a random scattering of values
166    # try the signed i64 minimum/maximum
167    [-2**63, -12356123612323, -23512351, -234, 0, 1231, 2351236, 12361236213, 2**63-1].each do |i|
168      @prot.write_i64(i)
169    end
170    # try something out of signed range, it should clip
171    expect(@trans.read(@trans.available)).to eq(["\200\000\000\000\000\000\000\000",
172      "\377\377\364\303\035\244+]",
173      "\377\377\377\377\376\231:\341",
174      "\377\377\377\377\377\377\377\026",
175      "\000\000\000\000\000\000\000\000",
176      "\000\000\000\000\000\000\004\317",
177      "\000\000\000\000\000#\340\204",
178      "\000\000\000\002\340\311~\365",
179      "\177\377\377\377\377\377\377\377"].join(""))
180    expect { @prot.write_i64(2 ** 65 + 5) }.to raise_error(RangeError)
181  end
182
183  it "should error gracefully when trying to write a nil i64" do
184    expect { @prot.write_i64(nil) }.to raise_error
185  end
186
187  it "should write a double" do
188    # try a random scattering of values, including min/max
189    values = [Float::MIN,-1231.15325, -123123.23, -23.23515123, 0, 12351.1325, 523.23, Float::MAX]
190    values.each do |f|
191      @prot.write_double(f)
192      expect(@trans.read(@trans.available)).to eq([f].pack("G"))
193    end
194  end
195
196  it "should error gracefully when trying to write a nil double" do
197    expect { @prot.write_double(nil) }.to raise_error
198  end
199
200  if RUBY_VERSION >= '1.9'
201    it 'should write a string' do
202      str = 'abc'
203      @prot.write_string(str)
204      a = @trans.read(@trans.available)
205      expect(a.encoding).to eq(Encoding::BINARY)
206      expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63])
207    end
208
209    it 'should write a string with unicode characters' do
210      str = "abc \u20AC \u20AD".encode('UTF-8')
211      @prot.write_string(str)
212      a = @trans.read(@trans.available)
213      expect(a.encoding).to eq(Encoding::BINARY)
214      expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x0B, 0x61, 0x62, 0x63, 0x20,
215                                0xE2, 0x82, 0xAC, 0x20, 0xE2, 0x82, 0xAD])
216    end
217
218    it 'should write should write a string with unicode characters and transcoding' do
219      str = "abc \u20AC".encode('ISO-8859-15')
220      @prot.write_string(str)
221      a = @trans.read(@trans.available)
222      expect(a.encoding).to eq(Encoding::BINARY)
223      expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x07, 0x61, 0x62, 0x63, 0x20, 0xE2, 0x82, 0xAC])
224    end
225
226    it 'should write a binary string' do
227      buffer = [0, 1, 2, 3].pack('C*')
228      @prot.write_binary(buffer)
229      a = @trans.read(@trans.available)
230      expect(a.encoding).to eq(Encoding::BINARY)
231      expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03])
232    end
233  else
234    it 'should write a string' do
235      str = 'abc'
236      @prot.write_string(str)
237      a = @trans.read(@trans.available)
238      expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63])
239    end
240
241    it 'should write a binary string' do
242      buffer = [0, 1, 2, 3].pack('C*')
243      @prot.write_binary(buffer)
244      a = @trans.read(@trans.available)
245      expect(a.unpack('C*')).to eq([0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03])
246    end
247  end
248
249  it "should error gracefully when trying to write a nil string" do
250    expect { @prot.write_string(nil) }.to raise_error
251  end
252
253  it "should write the message header without version when writes are not strict" do
254    @prot = protocol_class.new(@trans, true, false) # no strict write
255    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
256    expect(@trans.read(@trans.available)).to eq("\000\000\000\vtestMessage\001\000\000\000\021")
257  end
258
259  it "should write the message header with a version when writes are strict" do
260    @prot = protocol_class.new(@trans) # strict write
261    @prot.write_message_begin('testMessage', Thrift::MessageTypes::CALL, 17)
262    expect(@trans.read(@trans.available)).to eq("\200\001\000\001\000\000\000\vtestMessage\000\000\000\021")
263  end
264
265  # message footer is a noop
266
267  it "should read a field header" do
268    @trans.write([Thrift::Types::STRING, 3].pack("cn"))
269    expect(@prot.read_field_begin).to eq([nil, Thrift::Types::STRING, 3])
270  end
271
272  # field footer is a noop
273
274  it "should read a stop field" do
275    @trans.write([Thrift::Types::STOP].pack("c"));
276    expect(@prot.read_field_begin).to eq([nil, Thrift::Types::STOP, 0])
277  end
278
279  it "should read a map header" do
280    @trans.write([Thrift::Types::DOUBLE, Thrift::Types::I64, 42].pack("ccN"))
281    expect(@prot.read_map_begin).to eq([Thrift::Types::DOUBLE, Thrift::Types::I64, 42])
282  end
283
284  # map footer is a noop
285
286  it "should read a list header" do
287    @trans.write([Thrift::Types::STRING, 17].pack("cN"))
288    expect(@prot.read_list_begin).to eq([Thrift::Types::STRING, 17])
289  end
290
291  # list footer is a noop
292
293  it "should read a set header" do
294    @trans.write([Thrift::Types::STRING, 17].pack("cN"))
295    expect(@prot.read_set_begin).to eq([Thrift::Types::STRING, 17])
296  end
297
298  # set footer is a noop
299
300  it "should read a bool" do
301    @trans.write("\001\000");
302    expect(@prot.read_bool).to eq(true)
303    expect(@prot.read_bool).to eq(false)
304  end
305
306  it "should read a byte" do
307    [-128, -57, -3, 0, 17, 24, 127].each do |i|
308      @trans.write([i].pack("c"))
309      expect(@prot.read_byte).to eq(i)
310    end
311  end
312
313  it "should read an i16" do
314    # try a scattering of values, including min/max
315    [-2**15, -5237, -353, 0, 1527, 2234, 2**15-1].each do |i|
316      @trans.write([i].pack("n"));
317      expect(@prot.read_i16).to eq(i)
318    end
319  end
320
321  it "should read an i32" do
322    # try a scattering of values, including min/max
323    [-2**31, -235125, -6236, 0, 2351, 123123, 2**31-1].each do |i|
324      @trans.write([i].pack("N"))
325      expect(@prot.read_i32).to eq(i)
326    end
327  end
328
329  it "should read an i64" do
330    # try a scattering of values, including min/max
331    [-2**63, -123512312, -6346, 0, 32, 2346322323, 2**63-1].each do |i|
332      @trans.write([i >> 32, i & 0xFFFFFFFF].pack("NN"))
333      expect(@prot.read_i64).to eq(i)
334    end
335  end
336
337  it "should read a double" do
338    # try a random scattering of values, including min/max
339    [Float::MIN, -231231.12351, -323.233513, 0, 123.2351235, 2351235.12351235, Float::MAX].each do |f|
340      @trans.write([f].pack("G"));
341      expect(@prot.read_double).to eq(f)
342    end
343  end
344
345  if RUBY_VERSION >= '1.9'
346    it 'should read a string' do
347      # i32 of value 3, followed by three characters/UTF-8 bytes 'a', 'b', 'c'
348      buffer = [0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63].pack('C*')
349      @trans.write(buffer)
350      a = @prot.read_string
351      expect(a).to eq('abc'.encode('UTF-8'))
352      expect(a.encoding).to eq(Encoding::UTF_8)
353    end
354
355    it 'should read a string containing unicode characters from UTF-8 encoded buffer' do
356      # i32 of value 3, followed by one character U+20AC made up of three bytes
357      buffer = [0x00, 0x00, 0x00, 0x03, 0xE2, 0x82, 0xAC].pack('C*')
358      @trans.write(buffer)
359      a = @prot.read_string
360      expect(a).to eq("\u20AC".encode('UTF-8'))
361      expect(a.encoding).to eq(Encoding::UTF_8)
362    end
363
364    it 'should read a binary string' do
365      buffer = [0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03].pack('C*')
366      @trans.write(buffer)
367      a = @prot.read_binary
368      expect(a).to eq([0x00, 0x01, 0x02, 0x03].pack('C*'))
369      expect(a.encoding).to eq(Encoding::BINARY)
370    end
371  else
372    it 'should read a string' do
373      # i32 of value 3, followed by three characters/UTF-8 bytes 'a', 'b', 'c'
374      buffer = [0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63].pack('C*')
375      @trans.write(buffer)
376      expect(@prot.read_string).to eq('abc')
377    end
378
379    it 'should read a binary string' do
380      buffer = [0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x02, 0x03].pack('C*')
381      @trans.write(buffer)
382      a = @prot.read_binary
383      expect(a).to eq([0x00, 0x01, 0x02, 0x03].pack('C*'))
384    end
385  end
386
387  it "should perform a complete rpc with no args or return" do
388    srv_test(
389      proc {|client| client.send_voidMethod()},
390      proc {|client| expect(client.recv_voidMethod).to eq(nil)}
391    )
392  end
393
394  it "should perform a complete rpc with a primitive return type" do
395    srv_test(
396      proc {|client| client.send_primitiveMethod()},
397      proc {|client| expect(client.recv_primitiveMethod).to eq(1)}
398    )
399  end
400
401  it "should perform a complete rpc with a struct return type" do
402    srv_test(
403      proc {|client| client.send_structMethod()},
404      proc {|client|
405        result = client.recv_structMethod
406        result.set_byte_map = nil
407        result.map_byte_map = nil
408        expect(result).to eq(Fixtures::COMPACT_PROTOCOL_TEST_STRUCT)
409      }
410    )
411  end
412
413  def get_socket_connection
414    server = Thrift::ServerSocket.new("localhost", 9090)
415    server.listen
416
417    clientside = Thrift::Socket.new("localhost", 9090)
418    clientside.open
419    serverside = server.accept
420    [clientside, serverside, server]
421  end
422
423  def srv_test(firstblock, secondblock)
424    clientside, serverside, server = get_socket_connection
425
426    clientproto = protocol_class.new(clientside)
427    serverproto = protocol_class.new(serverside)
428
429    processor = Thrift::Test::Srv::Processor.new(SrvHandler.new)
430
431    client = Thrift::Test::Srv::Client.new(clientproto, clientproto)
432
433    # first block
434    firstblock.call(client)
435
436    processor.process(serverproto, serverproto)
437
438    # second block
439    secondblock.call(client)
440  ensure
441    clientside.close
442    serverside.close
443    server.close
444  end
445
446  class SrvHandler
447    def voidMethod()
448    end
449
450    def primitiveMethod
451      1
452    end
453
454    def structMethod
455      Fixtures::COMPACT_PROTOCOL_TEST_STRUCT
456    end
457  end
458end
459