sdp 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.infinity_test +4 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/ChangeLog.rdoc +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +111 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +141 -0
- data/Rakefile +39 -0
- data/features/sdp_create.feature +15 -0
- data/features/sdp_get.feature +35 -0
- data/features/step_definitions/sdp_create_steps.rb +46 -0
- data/features/step_definitions/sdp_get_steps.rb +23 -0
- data/features/support/env.rb +3 -0
- data/features/support/sdp_file.txt +12 -0
- data/gemspec.yml +15 -0
- data/lib/sdp/description.rb +172 -0
- data/lib/sdp/parser.rb +116 -0
- data/lib/sdp/session_template.erb +41 -0
- data/lib/sdp/version.rb +4 -0
- data/lib/sdp.rb +23 -0
- data/sdp.gemspec +114 -0
- data/spec/sdp/description_spec.rb +370 -0
- data/spec/sdp/parser_spec.rb +167 -0
- data/spec/sdp_spec.rb +181 -0
- data/spec/spec_helper.rb +3 -0
- data/tasks/metrics.rake +25 -0
- data/tasks/roodi_config.yml +14 -0
- data/tasks/stats.rake +13 -0
- metadata +143 -0
data/.document
ADDED
data/.infinity_test
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup rdoc --title "sdp Documentation" --protected
|
data/ChangeLog.rdoc
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source :rubygems
|
2
|
+
|
3
|
+
#gemspec
|
4
|
+
gem 'parslet', '~> 1.0.0'
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem 'rake', '~> 0.8.7'
|
8
|
+
gem 'ore-core', '~> 0.1.0'
|
9
|
+
gem 'jeweler', '~> 1.5.0'
|
10
|
+
gem 'ore-tasks', '~> 0.3.0'
|
11
|
+
gem 'rspec', '~> 2.3.0'
|
12
|
+
gem 'yard', '~> 0.6.0'
|
13
|
+
gem 'infinity_test'
|
14
|
+
gem 'metric_fu'
|
15
|
+
gem 'code_statistics', '~> 0.2.13'
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
Saikuro (1.1.0)
|
5
|
+
abstract (1.0.0)
|
6
|
+
activesupport (3.0.3)
|
7
|
+
arrayfields (4.7.4)
|
8
|
+
blankslate (2.1.2.3)
|
9
|
+
chronic (0.2.3)
|
10
|
+
hoe (>= 1.2.1)
|
11
|
+
churn (0.0.13)
|
12
|
+
chronic (>= 0.2.3)
|
13
|
+
hirb
|
14
|
+
json_pure
|
15
|
+
main
|
16
|
+
ruby_parser (~> 2.0.4)
|
17
|
+
sexp_processor (~> 3.0.3)
|
18
|
+
code_statistics (0.2.13)
|
19
|
+
colored (1.2)
|
20
|
+
diff-lcs (1.1.2)
|
21
|
+
erubis (2.6.6)
|
22
|
+
abstract (>= 1.0.0)
|
23
|
+
fattr (2.2.0)
|
24
|
+
flay (1.4.1)
|
25
|
+
ruby_parser (~> 2.0)
|
26
|
+
sexp_processor (~> 3.0)
|
27
|
+
flog (2.5.0)
|
28
|
+
ruby_parser (~> 2.0)
|
29
|
+
sexp_processor (~> 3.0)
|
30
|
+
git (1.2.5)
|
31
|
+
haml (3.0.25)
|
32
|
+
hirb (0.3.6)
|
33
|
+
hoe (2.8.0)
|
34
|
+
rake (>= 0.8.7)
|
35
|
+
i18n (0.5.0)
|
36
|
+
infinity_test (1.0.2)
|
37
|
+
notifiers (>= 1.1.0)
|
38
|
+
watchr (>= 0.7)
|
39
|
+
jeweler (1.5.2)
|
40
|
+
bundler (~> 1.0.0)
|
41
|
+
git (>= 1.2.5)
|
42
|
+
rake
|
43
|
+
json_pure (1.4.6)
|
44
|
+
main (4.4.0)
|
45
|
+
arrayfields (>= 4.7.4)
|
46
|
+
fattr (>= 2.1.0)
|
47
|
+
metric_fu (2.0.1)
|
48
|
+
Saikuro (>= 1.1.0)
|
49
|
+
activesupport (>= 2.0.0)
|
50
|
+
chronic (~> 0.2.3)
|
51
|
+
churn (>= 0.0.7)
|
52
|
+
flay (>= 1.2.1)
|
53
|
+
flog (>= 2.2.0)
|
54
|
+
rails_best_practices (>= 0.3.16)
|
55
|
+
rcov (>= 0.8.3.3)
|
56
|
+
reek (>= 1.2.6)
|
57
|
+
roodi (>= 2.1.0)
|
58
|
+
notifiers (1.1.0)
|
59
|
+
ore-core (0.1.1)
|
60
|
+
ore-tasks (0.3.0)
|
61
|
+
ore-core (~> 0.1.0)
|
62
|
+
parslet (1.0.1)
|
63
|
+
blankslate (~> 2.1.2.3)
|
64
|
+
rails_best_practices (0.6.5)
|
65
|
+
activesupport
|
66
|
+
colored (~> 1.2)
|
67
|
+
erubis (~> 2.6.6)
|
68
|
+
haml (~> 3.0.18)
|
69
|
+
i18n
|
70
|
+
ruby-progressbar (~> 0.0.9)
|
71
|
+
ruby_parser (~> 2.0.4)
|
72
|
+
rake (0.8.7)
|
73
|
+
rcov (0.9.9)
|
74
|
+
reek (1.2.8)
|
75
|
+
ruby2ruby (~> 1.2)
|
76
|
+
ruby_parser (~> 2.0)
|
77
|
+
sexp_processor (~> 3.0)
|
78
|
+
roodi (2.1.0)
|
79
|
+
ruby_parser
|
80
|
+
rspec (2.3.0)
|
81
|
+
rspec-core (~> 2.3.0)
|
82
|
+
rspec-expectations (~> 2.3.0)
|
83
|
+
rspec-mocks (~> 2.3.0)
|
84
|
+
rspec-core (2.3.1)
|
85
|
+
rspec-expectations (2.3.0)
|
86
|
+
diff-lcs (~> 1.1.2)
|
87
|
+
rspec-mocks (2.3.0)
|
88
|
+
ruby-progressbar (0.0.9)
|
89
|
+
ruby2ruby (1.2.5)
|
90
|
+
ruby_parser (~> 2.0)
|
91
|
+
sexp_processor (~> 3.0)
|
92
|
+
ruby_parser (2.0.5)
|
93
|
+
sexp_processor (~> 3.0)
|
94
|
+
sexp_processor (3.0.5)
|
95
|
+
watchr (0.7)
|
96
|
+
yard (0.6.4)
|
97
|
+
|
98
|
+
PLATFORMS
|
99
|
+
ruby
|
100
|
+
|
101
|
+
DEPENDENCIES
|
102
|
+
code_statistics (~> 0.2.13)
|
103
|
+
infinity_test
|
104
|
+
jeweler (~> 1.5.0)
|
105
|
+
metric_fu
|
106
|
+
ore-core (~> 0.1.0)
|
107
|
+
ore-tasks (~> 0.3.0)
|
108
|
+
parslet (~> 1.0.0)
|
109
|
+
rake (~> 0.8.7)
|
110
|
+
rspec (~> 2.3.0)
|
111
|
+
yard (~> 0.6.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 sloveless
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
= sdp
|
2
|
+
|
3
|
+
* {Homepage}[http://rubygems.org/gems/sdp]
|
4
|
+
* {Documentation}[http://rubydoc.info/gems/sdp]
|
5
|
+
* {Email}[steve.loveless at gmail.com]
|
6
|
+
|
7
|
+
== Description
|
8
|
+
|
9
|
+
SDP is used by a number of protocols for describing multimedia sessions; protocols
|
10
|
+
include SIP (Session Initiation Protocol), SAP (Session Announcement Protocol),
|
11
|
+
and RTSP (Real Time Streaming Protocol).
|
12
|
+
|
13
|
+
This gem has two purposes:
|
14
|
+
1. To use as a client to parse SDP descriptions into useful objects
|
15
|
+
2. To allow for creating an SDP description from scratch
|
16
|
+
|
17
|
+
Since SDP descriptions contain a lot of useful data pieces, but due to the
|
18
|
+
design of SDP descriptions, not only is it tough to know what's what when
|
19
|
+
you visually look at that description, but it's tough to extract out the piece of
|
20
|
+
data that you need to work with (ex. session ID). The SDP.#parse method takes
|
21
|
+
a description and parses it in to a Hash-like SDP::Description object, which has
|
22
|
+
accessor methods (that mimic the RFC 4566 document) for easily getting and setting
|
23
|
+
values.
|
24
|
+
|
25
|
+
Similarly, since it's difficult to remember all the ins and outs of building
|
26
|
+
an SDP description string that meets the requirements of RFC 4566, the
|
27
|
+
SDP::Description class has a #to_s method that takes the fields that you've
|
28
|
+
populated (from using the accessors) and turns those in to a string that's suitable
|
29
|
+
to use for an SDP description.
|
30
|
+
|
31
|
+
== Features
|
32
|
+
|
33
|
+
* Parse an SDP description from String to Ruby object
|
34
|
+
* Create new SDP description
|
35
|
+
|
36
|
+
== Examples
|
37
|
+
|
38
|
+
=== Creating an SDP description
|
39
|
+
|
40
|
+
require 'sdp/description'
|
41
|
+
|
42
|
+
sdp = SDP::Description.new
|
43
|
+
sdp.inspect # => {:session_section=>{
|
44
|
+
# :time_zones=>[], :attributes=>[], :protocol_version=>0
|
45
|
+
# },
|
46
|
+
# :media_sections=>[]
|
47
|
+
# }
|
48
|
+
sdp.to_s # => "v=0\no= \ns=\nc= \nt= \n\n"
|
49
|
+
sdp.username = "elvis"
|
50
|
+
sdp.media_sections << { :media => "video", :port => 9000, :format => 0, :protocol => "RTP/AVP", :attributes => [{ :attribute => "recvonly" }] }
|
51
|
+
sdp.media_sections << { :media => "audio", :port => 9100, :format => 33, :protocol => "RTP/AVP", :attributes => [{ :attribute => "rtpmap", :value => "99 h263-1998/90000" }] }
|
52
|
+
|
53
|
+
# Fields are stored in a Hash, where the session information goes in :session_section,
|
54
|
+
# and each media section goes in an Array at :media_sections:
|
55
|
+
sdp.inspect # => {:session_section=>{:time_zones=>[], :attributes=>[], :protocol_version=>0, :username=>"elvis"}, :media_sections=>[{:media=>"video", :port=>9000, :format=>0, :protocol=>"RTP/AVP", :attributes=>[{:attribute=>"recvonly"}]}, {:media=>"audio", :port=>9100, :format=>33, :protocol=>"RTP/AVP", :attributes=>[{:attribute=>"rtpmap", :value=>"99 h263-1998/90000"}]}]}
|
56
|
+
sdp.valid? # => false (Require fields haven't yet been given)
|
57
|
+
sdp.id = 1
|
58
|
+
sdp.version = 123
|
59
|
+
sdp.network_type = :IN
|
60
|
+
sdp.address_type = :IP4
|
61
|
+
sdp.name = " "
|
62
|
+
sdp.start_time = 1
|
63
|
+
sdp.stop_time = 10
|
64
|
+
sdp.unicast_address = "127.0.0.1"
|
65
|
+
sdp.valid? # => true
|
66
|
+
sdp.to_s # => "v=0\no=elvis 1 123 IN IP4 127.0.0.1\ns= \nc=IN IP4 \nt=1 10\nm=video 9000 RTP/AVP 0\na=recvonlym=audio 9100 RTP/AVP 33\na=rtpmap:99 h263-1998/90000\n"
|
67
|
+
|
68
|
+
|
69
|
+
=== Parsing an SDP description
|
70
|
+
|
71
|
+
sdp_string = <<-EOF
|
72
|
+
v=0
|
73
|
+
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
|
74
|
+
s=SDP Seminar
|
75
|
+
i=A Seminar on the session description protocol
|
76
|
+
u=http://www.example.com/seminars/sdp.pdf
|
77
|
+
e=j.doe@example.com (Jane Doe)
|
78
|
+
p=+1 617 555-6011
|
79
|
+
c=IN IP4 224.2.17.12/127
|
80
|
+
b=CT:1000
|
81
|
+
t=2873397496 2873404696
|
82
|
+
r=604800 3600 0 90000
|
83
|
+
z=2882844526 -1h
|
84
|
+
k=clear:password
|
85
|
+
a=recvonly
|
86
|
+
a=type:test
|
87
|
+
m=audio 49170 RTP/AVP 0
|
88
|
+
m=video 51372 RTP/AVP 99
|
89
|
+
a=rtpmap:99 h263-1998/90000
|
90
|
+
EOF
|
91
|
+
|
92
|
+
session = SDP.parse sdp_string
|
93
|
+
|
94
|
+
session.class # => SDP::Description
|
95
|
+
session.protocol_version # => 0
|
96
|
+
session.media_sections # => [{:media=>"audio", :port=>"49170", :protocol=>"RTP/AVP", :format=>"0", :attributes=>""}, {:media=>"video", :port=>"51372", :protocol=>"RTP/AVP", :format=>"99", :attributes=>[{:attribute=>"rtpmap", :value=>"99 h263-1998/90000"}]}]
|
97
|
+
session.username # => "jdoe"
|
98
|
+
session.id # => "2890844526"
|
99
|
+
session.version # => "2890842807"
|
100
|
+
session.network_type # => "IN"
|
101
|
+
session.address_type # => "IP4"
|
102
|
+
session.unicast_address # => "10.47.16.5"
|
103
|
+
session.name # => "SDP Seminar"
|
104
|
+
session.information # => "A Seminar on the session description protocol"
|
105
|
+
session.uri # => "http://www.example.com/seminars/sdp.pdf"
|
106
|
+
session.email_address # => "j.doe@example.com (Jane Doe)"
|
107
|
+
session.connection_address # => "224.2.17.12/127"
|
108
|
+
session.start_time # => 2873397496
|
109
|
+
session.stop_time # => 2873404696
|
110
|
+
session.attributes # => [{:attribute=>"recvonly"}, {:attribute=>"type", :value=>"test"}]
|
111
|
+
|
112
|
+
# Put it back to a string...
|
113
|
+
session.to_s # => "v=0\r\n
|
114
|
+
# o=elvis 2890844526 2890842807 IN IP4 10.47.16.5\r\n
|
115
|
+
# s=SDP Seminar\r\n
|
116
|
+
# i=A Seminar on the session description protocol\r\n
|
117
|
+
# u=http://www.example.com/seminars/sdp.pdf\r\n
|
118
|
+
# e=j.doe@example.com (Jane Doe)\r\n
|
119
|
+
# c=IN IP4 224.2.17.12/127\r\n
|
120
|
+
# t=2873397496 2873404696\r\n
|
121
|
+
# a=recvonly\r\n
|
122
|
+
# m=audio 49170 RTP/AVP 0\r\n
|
123
|
+
# m=video\r\n"
|
124
|
+
# a=rtpmap:99 h263-1998/90000\r\n"
|
125
|
+
|
126
|
+
== Requirements
|
127
|
+
|
128
|
+
* Rubies (tested, at least):
|
129
|
+
* 1.8.7
|
130
|
+
* 1.9.1
|
131
|
+
* 1.9.2
|
132
|
+
|
133
|
+
== Install
|
134
|
+
|
135
|
+
$ gem install sdp
|
136
|
+
|
137
|
+
== Copyright
|
138
|
+
|
139
|
+
Copyright (c) 2011 Steve Loveless
|
140
|
+
|
141
|
+
See LICENSE.txt for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler'
|
5
|
+
rescue LoadError => e
|
6
|
+
STDERR.puts e.message
|
7
|
+
STDERR.puts "Run `gem install bundler` to install Bundler."
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
Bundler.setup(:development)
|
13
|
+
rescue Bundler::BundlerError => e
|
14
|
+
STDERR.puts e.message
|
15
|
+
STDERR.puts "Run `bundle install` to install missing gems."
|
16
|
+
exit e.status_code
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake'
|
20
|
+
|
21
|
+
require 'ore/specification'
|
22
|
+
require 'jeweler'
|
23
|
+
Jeweler::Tasks.new(Ore::Specification.new)
|
24
|
+
|
25
|
+
require 'ore/tasks'
|
26
|
+
Ore::Tasks.new
|
27
|
+
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
30
|
+
t.ruby_opts = "-w"
|
31
|
+
t.rspec_opts = ['--format', 'documentation', '--color']
|
32
|
+
end
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
require 'yard'
|
36
|
+
YARD::Rake::YardocTask.new
|
37
|
+
|
38
|
+
# Load all extra rake tasks
|
39
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].each { |ext| load ext }
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: Programmatically create an SDP file
|
2
|
+
As a utility using SDP to describe a multimedia session
|
3
|
+
I want to be able to turn Ruby code in to an SDP description,
|
4
|
+
as specified in RFC 4566
|
5
|
+
So that I can use Ruby to describe the multimedia session
|
6
|
+
|
7
|
+
Scenario: Create an SDP file from a Ruby object
|
8
|
+
Given I know what the SDP file should look like
|
9
|
+
When I build the Ruby object with the appropriate fields
|
10
|
+
Then the resulting file should look like the intended description
|
11
|
+
|
12
|
+
Scenario: Create a basic SDP object
|
13
|
+
Given I create an SDP object with no parameters
|
14
|
+
When I convert it to a String
|
15
|
+
Then it should have :version set to 0
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Feature: Get SDP file fields and marshall into Ruby data types
|
2
|
+
As an RTSP consumer
|
3
|
+
I want to be able to be able to read SDP files into Ruby data types
|
4
|
+
So that it's easy to determine how to work with the RTSP stream
|
5
|
+
|
6
|
+
Scenario: Parse the RFC 4566 example
|
7
|
+
Given the RFC 4566 SDP example in a file
|
8
|
+
When I parse the file
|
9
|
+
Then the <value> for <field> is accessible via the SDP object
|
10
|
+
| field | value |
|
11
|
+
| protocol_version | 0 |
|
12
|
+
| username | jdoe |
|
13
|
+
| id | 2890844526 |
|
14
|
+
| version | 2890842807 |
|
15
|
+
| network_type | IN |
|
16
|
+
| address_type | IP4 |
|
17
|
+
| unicast_address | 10.47.16.5 |
|
18
|
+
| name | SDP Seminar |
|
19
|
+
| information | A Seminar on the session description protocol |
|
20
|
+
| uri | http://www.example.com/seminars/sdp.pdf |
|
21
|
+
| email_address | j.doe@example.com (Jane Doe) |
|
22
|
+
| connection_address | 224.2.17.12/127 |
|
23
|
+
| start_time | 2873397496 |
|
24
|
+
| stop_time | 2873404696 |
|
25
|
+
| attributes | recvonly |
|
26
|
+
# | m[0][1] | 49170 |
|
27
|
+
# | m[0][2] | "RTP/AVP" |
|
28
|
+
# | m[0][3] | 0 |
|
29
|
+
# | m[1][1] | 51372 |
|
30
|
+
# | m[1][2] | "RTP/AVP" |
|
31
|
+
# | m[1][3] | 99 |
|
32
|
+
# | a[1][1] | 99 |
|
33
|
+
# | a[1][2] | "h263-1998" |
|
34
|
+
# | a[1][3] | 90000 |
|
35
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sdp/description'
|
2
|
+
|
3
|
+
Given /^I know what the SDP file should look like$/ do
|
4
|
+
@example_sdp_file = File.read(File.dirname(__FILE__) + "/../support/sdp_file.txt")
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^I build the Ruby object with the appropriate fields$/ do
|
8
|
+
@session = SDP::Description.new
|
9
|
+
@session.protocol_version = 0
|
10
|
+
@session.username = "jdoe"
|
11
|
+
@session.id = 2890844526
|
12
|
+
@session.version = 2890842807
|
13
|
+
@session.network_type = :IN
|
14
|
+
@session.address_type = :IP4
|
15
|
+
@session.unicast_address = "10.47.16.5"
|
16
|
+
@session.name = "SDP Seminar"
|
17
|
+
@session.information = "A Seminar on the session description protocol"
|
18
|
+
@session.uri = "http://www.example.com/seminars/sdp.pdf"
|
19
|
+
@session.email_address = "j.doe@example.com (Jane Doe)"
|
20
|
+
@session.connection_address = "224.2.17.12/127"
|
21
|
+
@session.start_time = 2873397496
|
22
|
+
@session.stop_time = 2873404696
|
23
|
+
@session.attributes << { :attribute => "recvonly" }
|
24
|
+
@session.media_sections <<
|
25
|
+
{ :media => "audio", :port => 49170, :protocol => "RTP/AVP", :format => 0 }
|
26
|
+
@session.media_sections <<
|
27
|
+
{ :media => "video", :port => 51372, :protocol => "RTP/AVP", :format => 99,
|
28
|
+
:attributes => [{ :attribute => "rtpmap", :value => "99 h263-1998/90000" }]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
Then /^the resulting file should look like the intended description$/ do
|
33
|
+
@session.to_s.should == @example_sdp_file
|
34
|
+
end
|
35
|
+
|
36
|
+
Given /^I create an SDP object with no parameters$/ do
|
37
|
+
@session = SDP::Description.new
|
38
|
+
end
|
39
|
+
|
40
|
+
When /^I convert it to a String$/ do
|
41
|
+
@sdp_string = @session.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
Then /^it should have :version set to (\d+)$/ do |value|
|
45
|
+
@sdp_string.should match /v=#{value}/
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Given /^the RFC 4566 SDP example in a file$/ do
|
2
|
+
@sdp_file = File.open(File.dirname(__FILE__) + '/../support/sdp_file.txt', 'r').read
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I parse the file$/ do
|
6
|
+
@sdp = SDP.parse @sdp_file
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^the <value> for <field> is accessible via the SDP object$/ do |table|
|
10
|
+
# table is a Cucumber::Ast::Table
|
11
|
+
table.hashes.each do |sdp_field|
|
12
|
+
field_type = sdp_field["field"].to_sym
|
13
|
+
value = sdp_field["value"]
|
14
|
+
|
15
|
+
actual_value = @sdp.send(field_type)
|
16
|
+
|
17
|
+
if field_type == :attributes
|
18
|
+
actual_value.first[:attribute].should == value
|
19
|
+
else
|
20
|
+
actual_value.to_s.should == value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
v=0
|
2
|
+
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
|
3
|
+
s=SDP Seminar
|
4
|
+
i=A Seminar on the session description protocol
|
5
|
+
u=http://www.example.com/seminars/sdp.pdf
|
6
|
+
e=j.doe@example.com (Jane Doe)
|
7
|
+
c=IN IP4 224.2.17.12/127
|
8
|
+
t=2873397496 2873404696
|
9
|
+
a=recvonly
|
10
|
+
m=audio 49170 RTP/AVP 0
|
11
|
+
m=video 51372 RTP/AVP 99
|
12
|
+
a=rtpmap:99 h263-1998/90000
|
data/gemspec.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
name: sdp
|
2
|
+
summary: "Parse and create SDP (Session Description Protocol) text based on RFC4566."
|
3
|
+
description: "This gem allows for parsing SDP (Session Description Protocol) information in to a Ruby object, making it easy to read and work with that data. It also allows for easily creating SDP objects that can be converted to text using #to_s."
|
4
|
+
license: MIT
|
5
|
+
authors: sloveless
|
6
|
+
email: steve.loveless@gmail.com
|
7
|
+
homepage: http://rubygems.org/gems/sdp
|
8
|
+
has_yard: true
|
9
|
+
|
10
|
+
dependencies:
|
11
|
+
parslet: ~> 1.0.0
|
12
|
+
|
13
|
+
development_dependencies:
|
14
|
+
bundler: ~> 1.0.0
|
15
|
+
yard: ~> 0.6.0
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class SDP
|
4
|
+
PROTOCOL_VERSION = 0
|
5
|
+
|
6
|
+
# Represents an SDP description as defined in RFC 4566. This class allows
|
7
|
+
# for creating an object so you can, in turn, create a String that
|
8
|
+
# represents an SDP description. The String, then can be used by
|
9
|
+
# other protocols that depend on an SDP description.
|
10
|
+
#
|
11
|
+
# SDP::Description objects are initialized empty (i.e. no fields are
|
12
|
+
# defined), putting the onus on you to add fields in the proper order.
|
13
|
+
# After building the description up, call #to_s to render it. This
|
14
|
+
# will render the String with fields in order that they were added
|
15
|
+
# to the object, so be sure to add them according to spec!
|
16
|
+
class Description < Hash
|
17
|
+
class << self
|
18
|
+
|
19
|
+
# Class macro to access the different fields that make up the
|
20
|
+
# description.
|
21
|
+
#
|
22
|
+
# @param [Symbol] field_type
|
23
|
+
def field(field_type)
|
24
|
+
define_read_field_method(field_type)
|
25
|
+
define_write_field_method(field_type)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates read accessor for the field type. This simply reads
|
29
|
+
# the correct Hash value and returns that.
|
30
|
+
#
|
31
|
+
# @param [Symbol] field_type
|
32
|
+
# @return [] Returns whatever type the value is that's stored
|
33
|
+
# in the Hash key.
|
34
|
+
def define_read_field_method(field_type)
|
35
|
+
define_method field_type do
|
36
|
+
if field_type == :media_sections
|
37
|
+
self[:media_sections]
|
38
|
+
else
|
39
|
+
self[:session_section][field_type]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates write accessor for the field type. This simply writes
|
45
|
+
# the correct Hash value and returns that.
|
46
|
+
#
|
47
|
+
# @param [Symbol] field_type
|
48
|
+
def define_write_field_method(field_type)
|
49
|
+
case field_type
|
50
|
+
when :media_sections
|
51
|
+
define_method ":media_sections<<" do |value|
|
52
|
+
self[:media_sections] << value
|
53
|
+
end
|
54
|
+
when :time_zones || :attributes
|
55
|
+
define_method "#{field_type}<<" do |value|
|
56
|
+
self[:session_section][field_type] << value
|
57
|
+
end
|
58
|
+
else
|
59
|
+
define_method "#{field_type}=" do |value|
|
60
|
+
self[:session_section][field_type] = value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
FIELDS = [
|
67
|
+
:protocol_version,
|
68
|
+
:username,
|
69
|
+
:id,
|
70
|
+
:version,
|
71
|
+
:network_type,
|
72
|
+
:address_type,
|
73
|
+
:unicast_address,
|
74
|
+
:name,
|
75
|
+
:information,
|
76
|
+
:uri,
|
77
|
+
:email_address,
|
78
|
+
:phone_number,
|
79
|
+
:connection_address,
|
80
|
+
:bandwidth_type,
|
81
|
+
:bandwidth,
|
82
|
+
:start_time,
|
83
|
+
:stop_time,
|
84
|
+
:repeat_interval,
|
85
|
+
:active_duration,
|
86
|
+
:offsets_from_start_time,
|
87
|
+
:time_zones,
|
88
|
+
:encryption_method,
|
89
|
+
:encryption_key,
|
90
|
+
:attributes,
|
91
|
+
:media_sections
|
92
|
+
]
|
93
|
+
|
94
|
+
FIELDS.each do |field_type|
|
95
|
+
field field_type
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Hash] session_as_hash Pass this in to use these values instead
|
99
|
+
# of building your own from scratch.
|
100
|
+
def initialize(session_as_hash=nil)
|
101
|
+
if session_as_hash.nil?
|
102
|
+
self[:session_section] = {}
|
103
|
+
self[:session_section][:time_zones] = []
|
104
|
+
self[:session_section][:attributes] = []
|
105
|
+
self[:media_sections] = []
|
106
|
+
|
107
|
+
self.send :protocol_version=, SDP::PROTOCOL_VERSION
|
108
|
+
else
|
109
|
+
begin
|
110
|
+
unless validate_init_value(session_as_hash)
|
111
|
+
self.replace session_as_hash
|
112
|
+
end
|
113
|
+
rescue SDP::RuntimeError => ex
|
114
|
+
puts ex.message
|
115
|
+
raise
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
# Turns the current SDP::Description object into the SDP description,
|
123
|
+
# ready to be used.
|
124
|
+
#
|
125
|
+
# @return [String] The SDP description.
|
126
|
+
def to_s
|
127
|
+
template = File.read(File.dirname(__FILE__) + "/session_template.erb")
|
128
|
+
|
129
|
+
sdp = ERB.new(template, 0, "%<>")
|
130
|
+
sdp.result(get_binding)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Checks to see if the fields set in the current object will yield an SDP
|
134
|
+
# description that meets the RFC 4566 spec.
|
135
|
+
#
|
136
|
+
# @return [Boolean] true if the object will meet spec; false if not.
|
137
|
+
def valid?
|
138
|
+
return false unless protocol_version && username && id && version &&
|
139
|
+
network_type && address_type && unicast_address && name &&
|
140
|
+
start_time && stop_time && !media_sections.empty?
|
141
|
+
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
#--------------------------------------------------------------------------
|
146
|
+
# PRIVATES!
|
147
|
+
private
|
148
|
+
|
149
|
+
# @return [Binding] Values for this object for ERB to use.
|
150
|
+
def get_binding
|
151
|
+
binding
|
152
|
+
end
|
153
|
+
|
154
|
+
# @raise [SDP::RuntimeError] If not given a Hash.
|
155
|
+
def validate_init_value value
|
156
|
+
unless value.class == Hash
|
157
|
+
message = "Must pass a Hash in on initialize. You passed in a #{value.class}."
|
158
|
+
raise SDP::RuntimeError, message
|
159
|
+
end
|
160
|
+
|
161
|
+
bad_keys = []
|
162
|
+
value.each_key do |key|
|
163
|
+
bad_keys << key unless (FIELDS.include?(key) || key == :session_section)
|
164
|
+
end
|
165
|
+
|
166
|
+
unless bad_keys.empty?
|
167
|
+
message = "Invalid key value passed in on initialize: #{bad_keys}"
|
168
|
+
raise SDP::RuntimeError, message
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|