sctp-socket 0.1.4 → 0.2.0

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.
@@ -0,0 +1,207 @@
1
+ require 'socket'
2
+ require 'sctp/socket'
3
+
4
+ module SCTP
5
+ # SCTP::Server provides a TCPServer-like interface for SCTP sockets.
6
+ #
7
+ # Unlike TCP which uses accept() to create new sockets for each connection,
8
+ # SCTP can operate in one-to-many mode where a single socket handles
9
+ # multiple associations. This server class provides both modes:
10
+ #
11
+ # 1. One-to-many mode (default): All connections share the server socket
12
+ # 2. One-to-one mode: Uses peeloff() to create individual sockets per association
13
+ #
14
+ # Example usage:
15
+ #
16
+ # # Basic one-to-many server
17
+ # server = SCTP::Server.new(['127.0.0.1'], 9999)
18
+ # loop do
19
+ # data, info = server.recvmsg
20
+ # puts "Received: #{data} from association #{info.association_id}"
21
+ # server.sendmsg("Echo: #{data}", association_id: info.association_id)
22
+ # end
23
+ #
24
+ # # One-to-one server with peeloff
25
+ # server = SCTP::Server.new(['127.0.0.1'], 9999, one_to_one: true)
26
+ # loop do
27
+ # client = server.accept # Returns a new SCTP::Socket for the association
28
+ # Thread.new(client) do |c|
29
+ # data, info = c.recvmsg
30
+ # c.sendmsg("Echo: #{data}")
31
+ # c.close
32
+ # end
33
+ # end
34
+ #
35
+ class Server
36
+ attr_reader :socket, :addresses, :port, :one_to_one
37
+
38
+ # Create a new SCTP server.
39
+ #
40
+ # @param addresses [Array<String>, String, nil] IP addresses to bind to.
41
+ # If nil, binds to all available addresses.
42
+ # @param port [Integer] Port number to bind to
43
+ # @param one_to_one [Boolean] If true, uses one-to-one mode with peeloff.
44
+ # If false (default), uses one-to-many mode.
45
+ # @param backlog [Integer] Listen backlog (default: 128)
46
+ # @param socket_options [Hash] Additional socket options
47
+ def initialize(addresses = nil, port = 0, one_to_one: false, backlog: 128, **socket_options)
48
+ @addresses = Array(addresses) if addresses
49
+ @port = port
50
+ @one_to_one = one_to_one
51
+ @backlog = backlog
52
+ @socket_options = socket_options
53
+ @pending_associations = {}
54
+
55
+ # Create the main server socket
56
+ if one_to_one
57
+ @socket = SCTP::Socket.new(2, 1) # AF_INET, SOCK_STREAM
58
+ else
59
+ @socket = SCTP::Socket.new(2, 5) # AF_INET, SOCK_SEQPACKET
60
+ end
61
+
62
+ setup_socket
63
+ bind_and_listen
64
+ end
65
+
66
+ # Accept a new association (one-to-one mode only).
67
+ #
68
+ # In one-to-many mode, this method is not used. Instead, use recvmsg
69
+ # to receive data from any association.
70
+ #
71
+ # @return [SCTP::Socket] A new socket for the accepted association
72
+ # @raise [RuntimeError] If not in one-to-one mode
73
+ def accept
74
+ raise "accept() only available in one-to-one mode" unless @one_to_one
75
+
76
+ # Wait for a message to establish an association
77
+ data, info = @socket.recvmsg
78
+ association_id = info.association_id
79
+
80
+ # Check if we already have a peeled-off socket for this association
81
+ if @pending_associations[association_id]
82
+ client_socket = @pending_associations.delete(association_id)
83
+ else
84
+ # Peeloff a new socket for this association
85
+ client_socket = @socket.peeloff(association_id)
86
+ end
87
+
88
+ # Store the initial message in the client socket for retrieval
89
+ client_socket.instance_variable_set(:@initial_message, [data, info])
90
+
91
+ # Add a method to retrieve the initial message
92
+ def client_socket.initial_message
93
+ @initial_message
94
+ end
95
+
96
+ client_socket
97
+ end
98
+
99
+ # Receive a message from any association (one-to-many mode).
100
+ #
101
+ # @param flags [Integer] Receive flags (default: 0)
102
+ # @return [Array<String, Struct>] Message data and info struct
103
+ def recvmsg(flags = 0)
104
+ @socket.recvmsg(flags)
105
+ end
106
+
107
+ # Send a message to a specific association (one-to-many mode).
108
+ #
109
+ # @param data [String] Data to send
110
+ # @param options [Hash] Send options including :association_id
111
+ # @return [Integer] Number of bytes sent
112
+ def sendmsg(data, **options)
113
+ @socket.sendmsg(options.merge(message: data))
114
+ end
115
+
116
+ # Get local addresses bound to this server.
117
+ #
118
+ # @return [Array<String>] Local addresses
119
+ def addr
120
+ @socket.getlocalnames
121
+ end
122
+
123
+ # Get the local port number.
124
+ #
125
+ # @return [Integer] Port number
126
+ def local_port
127
+ @socket.port
128
+ end
129
+
130
+ # Check if the server is closed.
131
+ #
132
+ # @return [Boolean] true if closed, false otherwise
133
+ def closed?
134
+ @socket.closed?
135
+ end
136
+
137
+ # Close the server socket.
138
+ #
139
+ # @param options [Hash] Close options (e.g., linger: seconds)
140
+ def close(**options)
141
+ @socket.close(options) unless closed?
142
+ end
143
+
144
+ # Get server socket information.
145
+ #
146
+ # @return [String] String representation of the server
147
+ def to_s
148
+ if closed?
149
+ "#<SCTP::Server:closed>"
150
+ else
151
+ mode = @one_to_one ? "one-to-one" : "one-to-many"
152
+ "#<SCTP::Server:#{addr.join(',')}:#{local_port} (#{mode})>"
153
+ end
154
+ end
155
+
156
+ alias_method :inspect, :to_s
157
+
158
+ private
159
+
160
+ def setup_socket
161
+ # Apply any socket options provided
162
+ @socket_options.each do |option, value|
163
+ case option
164
+ when :reuse_addr
165
+ # SCTP typically doesn't need SO_REUSEADDR like TCP
166
+ # but we can support it for compatibility
167
+ when :autoclose
168
+ @socket.autoclose = value
169
+ when :nodelay
170
+ @socket.nodelay = value
171
+ when :init_msg
172
+ @socket.set_initmsg(value)
173
+ when :subscriptions
174
+ @socket.subscribe(value)
175
+ else
176
+ # For any other options, try to call them as methods
177
+ if @socket.respond_to?("#{option}=")
178
+ @socket.send("#{option}=", value)
179
+ end
180
+ end
181
+ end
182
+
183
+ # Set up default subscriptions for server operation
184
+ @socket.subscribe(
185
+ data_io: true,
186
+ association: true,
187
+ address: true,
188
+ send_failure: true,
189
+ shutdown: true
190
+ )
191
+ end
192
+
193
+ def bind_and_listen
194
+ if @addresses && !@addresses.empty?
195
+ @socket.bindx(port: @port, addresses: @addresses, reuse_addr: true)
196
+ else
197
+ # Bind to all available addresses
198
+ @socket.bindx(port: @port, reuse_addr: true)
199
+ end
200
+
201
+ @socket.listen(@backlog)
202
+
203
+ # Update port if it was auto-assigned (port 0)
204
+ @port = @socket.port if @port == 0
205
+ end
206
+ end
207
+ end
data/sctp-socket.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = 'sctp-socket'
3
- spec.version = '0.1.4'
3
+ spec.version = '0.2.0'
4
4
  spec.author = 'Daniel Berger'
5
5
  spec.email = 'djberg96@gmail.com'
6
6
  spec.summary = 'Ruby bindings for SCTP sockets'
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sctp-socket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Berger
@@ -35,7 +35,7 @@ cert_chain:
35
35
  ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
36
36
  WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
37
37
  -----END CERTIFICATE-----
38
- date: 2025-02-01 00:00:00.000000000 Z
38
+ date: 2025-08-04 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: bundler
@@ -114,12 +114,13 @@ files:
114
114
  - Rakefile
115
115
  - certs/djberg96_pub.pem
116
116
  - examples/client_example.rb
117
+ - examples/client_for_server_test.rb
118
+ - examples/sctp_server_example.rb
117
119
  - examples/server_example.rb
120
+ - examples/server_using_sctp_server.rb
118
121
  - ext/sctp/extconf.rb
119
122
  - ext/sctp/socket.c
120
- - ext/sctp/socket.h
121
- - ext/sctp/socket/client.c
122
- - ext/sctp/socket/server.c
123
+ - lib/sctp/server.rb
123
124
  - sctp-socket.gemspec
124
125
  homepage: https://github.com/djberg96/sctp-socket
125
126
  licenses:
metadata.gz.sig CHANGED
Binary file
@@ -1,7 +0,0 @@
1
- #include <sctp/socket.h>
2
-
3
- VALUE cClient;
4
-
5
- void Init_client(){
6
- cClient = rb_define_class_under(cSocket, "Client", rb_cObject);
7
- }
@@ -1,7 +0,0 @@
1
- #include <sctp/socket.h>
2
-
3
- VALUE cServer;
4
-
5
- void Init_server(){
6
- cServer = rb_define_class_under(cSocket, "Server", rb_cObject);
7
- }
data/ext/sctp/socket.h DELETED
@@ -1,18 +0,0 @@
1
- #ifndef SCTP_SOCKET_INCLUDED
2
- #define SCTP_SOCKET_INCLUDED
3
-
4
- #include <ruby.h>
5
- #include <netinet/sctp.h>
6
-
7
- VALUE rb_hash_aref2(VALUE, const char*);
8
-
9
- void Init_socket();
10
- void Init_server();
11
- void Init_client();
12
-
13
- extern VALUE mSCTP;
14
- extern VALUE cSocket;
15
- extern VALUE cClient;
16
- extern VALUE cServer;
17
-
18
- #endif