summaryrefslogtreecommitdiff
path: root/spec/ruby/library/socket/basicsocket/recvmsg_spec.rb
blob: 04ba1d74c768c1afd047e3f9f3a7662ee8ca8a1d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
require_relative '../spec_helper'
require_relative '../fixtures/classes'

describe 'BasicSocket#recvmsg' do
  SocketSpecs.each_ip_protocol do |family, ip_address|
    describe 'using a disconnected socket' do
      before do
        @client = Socket.new(family, :DGRAM)
        @server = Socket.new(family, :DGRAM)
      end

      after do
        @client.close
        @server.close
      end

      platform_is_not :windows do
        describe 'using an unbound socket' do
          it 'blocks the caller' do
            -> { @server.recvmsg }.should block_caller
          end
        end
      end

      describe 'using a bound socket' do
        before do
          @server.bind(Socket.sockaddr_in(0, ip_address))
        end

        describe 'without any data available' do
          it 'blocks the caller' do
            -> { @server.recvmsg }.should block_caller
          end
        end

        describe 'with data available' do
          before do
            @client.connect(@server.getsockname)

            @client.write('hello')
          end

          it 'returns an Array containing the data, an Addrinfo and the flags' do
            @server.recvmsg.should be_an_instance_of(Array)
          end

          describe 'without a maximum message length' do
            it 'reads all the available data' do
              @server.recvmsg[0].should == 'hello'
            end
          end

          describe 'with a maximum message length' do
            it 'reads up to the maximum amount of bytes' do
              @server.recvmsg(2)[0].should == 'he'
            end
          end

          describe 'the returned Array' do
            before do
              @array = @server.recvmsg
            end

            it 'stores the message at index 0' do
              @array[0].should == 'hello'
            end

            it 'stores an Addrinfo at index 1' do
              @array[1].should be_an_instance_of(Addrinfo)
            end

            platform_is_not :windows do
              it 'stores the flags at index 2' do
                @array[2].should be_kind_of(Integer)
              end
            end

            describe 'the returned Addrinfo' do
              before do
                @addr = @array[1]
              end

              it 'uses the IP address of the client' do
                @addr.ip_address.should == @client.local_address.ip_address
              end

              it 'uses the correct address family' do
                @addr.afamily.should == family
              end

              it 'uses the correct protocol family' do
                @addr.pfamily.should == family
              end

              it 'uses the correct socket type' do
                @addr.socktype.should == Socket::SOCK_DGRAM
              end

              it 'uses the port number of the client' do
                @addr.ip_port.should == @client.local_address.ip_port
              end
            end
          end
        end
      end
    end

    platform_is_not :windows do
      describe 'using a connected socket' do
        before do
          @client = Socket.new(family, :STREAM)
          @server = Socket.new(family, :STREAM)

          @server.bind(Socket.sockaddr_in(0, ip_address))
          @server.listen(1)

          @client.connect(@server.getsockname)
        end

        after do
          @client.close
          @server.close
        end

        describe 'without any data available' do
          it 'blocks the caller' do
            socket, _ = @server.accept
            begin
              -> { socket.recvmsg }.should block_caller
            ensure
              socket.close
            end
          end
        end

        describe 'with data available' do
          before do
            @client.write('hello')
            @socket, _ = @server.accept
          end

          after do
            @socket.close
          end

          it 'returns an Array containing the data, an Addrinfo and the flags' do
            @socket.recvmsg.should be_an_instance_of(Array)
          end

          describe 'the returned Array' do
            before do
              @array = @socket.recvmsg
            end

            it 'stores the message at index 0' do
              @array[0].should == 'hello'
            end

            it 'stores an Addrinfo at index 1' do
              @array[1].should be_an_instance_of(Addrinfo)
            end

            it 'stores the flags at index 2' do
              @array[2].should be_kind_of(Integer)
            end

            describe 'the returned Addrinfo' do
              before do
                @addr = @array[1]
              end

              it 'raises when receiving the ip_address message' do
                -> { @addr.ip_address }.should raise_error(SocketError)
              end

              it 'uses the correct address family' do
                @addr.afamily.should == Socket::AF_UNSPEC
              end

              it 'returns 0 for the protocol family' do
                @addr.pfamily.should == 0
              end

              it 'uses the correct socket type' do
                @addr.socktype.should == Socket::SOCK_STREAM
              end

              it 'raises when receiving the ip_port message' do
                -> { @addr.ip_port }.should raise_error(SocketError)
              end
            end
          end
        end
      end
    end
  end
end

describe 'BasicSocket#recvmsg' do
  context "when recvfrom(2) returns 0 (if no messages are available to be received and the peer has performed an orderly shutdown)" do
    describe "stream socket" do
      before :each do
        @server = TCPServer.new('127.0.0.1', 0)
        @port = @server.addr[1]
      end

      after :each do
        @server.close unless @server.closed?
      end

      ruby_version_is ""..."3.3" do
        platform_is_not :windows do
          it "returns an empty String as received data on a closed stream socket" do
            t = Thread.new do
              client = @server.accept
              client.recvmsg(10)
            ensure
              client.close if client
            end

            Thread.pass while t.status and t.status != "sleep"
            t.status.should_not be_nil

            socket = TCPSocket.new('127.0.0.1', @port)
            socket.close

            t.value.should.is_a? Array
            t.value[0].should == ""
          end
        end
      end

      ruby_version_is "3.3" do
        platform_is_not :windows do
          it "returns nil on a closed stream socket" do
            t = Thread.new do
              client = @server.accept
              client.recvmsg(10)
            ensure
              client.close if client
            end

            Thread.pass while t.status and t.status != "sleep"
            t.status.should_not be_nil

            socket = TCPSocket.new('127.0.0.1', @port)
            socket.close

            t.value.should be_nil
          end
        end
      end
    end

    describe "datagram socket" do
      SocketSpecs.each_ip_protocol do |family, ip_address|
        before :each do
          @server = UDPSocket.new(family)
          @client = UDPSocket.new(family)
        end

        after :each do
          @server.close unless @server.closed?
          @client.close unless @client.closed?
        end

        it "returns an empty String as received data" do
          @server.bind(ip_address, 0)
          addr = @server.connect_address
          @client.connect(addr.ip_address, addr.ip_port)

          @client.send('', 0)
          message = @server.recvmsg(1)

          message.should.is_a? Array
          message[0].should == ""
        end
      end
    end
  end
end