sctp-socket 0.1.4 → 0.2.1

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.1'
4
4
  spec.author = 'Daniel Berger'
5
5
  spec.email = 'djberg96@gmail.com'
6
6
  spec.summary = 'Ruby bindings for SCTP sockets'
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.cert_chain = ['certs/djberg96_pub.pem']
10
10
 
11
11
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
12
- f.match(%r{^(test|spec|features)/})
12
+ f.match(%r{^(test|spec|features|.github)/})
13
13
  end
14
14
 
15
15
  spec.extensions = ['ext/sctp/extconf.rb']
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.1
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-17 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: bundler
@@ -103,8 +103,6 @@ extensions:
103
103
  - ext/sctp/extconf.rb
104
104
  extra_rdoc_files: []
105
105
  files:
106
- - ".github/FUNDING.yml"
107
- - ".github/workflows/ruby.yml"
108
106
  - ".gitignore"
109
107
  - CHANGES.md
110
108
  - Gemfile
@@ -114,12 +112,13 @@ files:
114
112
  - Rakefile
115
113
  - certs/djberg96_pub.pem
116
114
  - examples/client_example.rb
115
+ - examples/client_for_server_test.rb
116
+ - examples/sctp_server_example.rb
117
117
  - examples/server_example.rb
118
+ - examples/server_using_sctp_server.rb
118
119
  - ext/sctp/extconf.rb
119
120
  - ext/sctp/socket.c
120
- - ext/sctp/socket.h
121
- - ext/sctp/socket/client.c
122
- - ext/sctp/socket/server.c
121
+ - lib/sctp/server.rb
123
122
  - sctp-socket.gemspec
124
123
  homepage: https://github.com/djberg96/sctp-socket
125
124
  licenses:
metadata.gz.sig CHANGED
Binary file
data/.github/FUNDING.yml DELETED
@@ -1,4 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: djberg96
4
- open_collective: daniel-berger
@@ -1,59 +0,0 @@
1
- name: Ruby
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- paths-ignore:
7
- - '**/*.md'
8
- pull_request:
9
- branches: [ main ]
10
- paths-ignore:
11
- - '**/*.md'
12
- workflow_dispatch:
13
-
14
- jobs:
15
- test:
16
- runs-on: ubuntu-latest
17
- strategy:
18
- matrix:
19
- ruby-version: ['3.2', '3.3']
20
- steps:
21
- - uses: actions/checkout@v4
22
- - name: Install SCTP headers
23
- run: |
24
- sudo apt-get install libsctp-dev lksctp-tools
25
- sudo ip link add dummy1 type dummy
26
- sudo ip link add dummy2 type dummy
27
- sudo ip addr add 1.1.1.1/24 dev dummy1
28
- sudo ip addr add 1.1.1.2/24 dev dummy2
29
- sudo ip link set dummy1 up
30
- sudo ip link set dummy2 up
31
- - name: Setup Ruby
32
- uses: ruby/setup-ruby@v1
33
- with:
34
- ruby-version: ${{ matrix.ruby-version }}
35
- bundler-cache: true
36
- - name: Run Specs
37
- run: bundle exec rake
38
- freebsd:
39
- runs-on: ubuntu-latest
40
- steps:
41
- - uses: actions/checkout@v4
42
- - name: Test in FreeBSD
43
- id: test
44
- uses: vmactions/freebsd-vm@v1
45
- with:
46
- usesh: true
47
- prepare: |
48
- pkg install -y llvm ruby devel/ruby-gems sctplib git-tiny
49
-
50
- run: |
51
- git config --global --add safe.directory /home/runner/work/sctp-socket/sctp-socket
52
- kldload sctp
53
- ifconfig lo1 create
54
- ifconfig lo1 1.1.1.1/24 up
55
- ifconfig lo2 create
56
- ifconfig lo2 1.1.1.2/24 up
57
- gem install bundler --no-document
58
- bundle install --quiet
59
- bundle exec rake
@@ -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