hrr_rb_netconf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +28 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +32 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE +201 -0
  8. data/README.md +220 -0
  9. data/Rakefile +6 -0
  10. data/demo/server.rb +54 -0
  11. data/demo/server_over_ssh.rb +97 -0
  12. data/demo/server_with_session-oriented-database.rb +134 -0
  13. data/demo/server_with_sessionless-database.rb +81 -0
  14. data/hrr_rb_netconf.gemspec +28 -0
  15. data/lib/hrr_rb_netconf.rb +10 -0
  16. data/lib/hrr_rb_netconf/logger.rb +56 -0
  17. data/lib/hrr_rb_netconf/server.rb +109 -0
  18. data/lib/hrr_rb_netconf/server/capabilities.rb +75 -0
  19. data/lib/hrr_rb_netconf/server/capability.rb +206 -0
  20. data/lib/hrr_rb_netconf/server/capability/base_1_0.rb +183 -0
  21. data/lib/hrr_rb_netconf/server/capability/base_1_1.rb +247 -0
  22. data/lib/hrr_rb_netconf/server/capability/candidate_1_0.rb +34 -0
  23. data/lib/hrr_rb_netconf/server/capability/confirmed_commit_1_0.rb +24 -0
  24. data/lib/hrr_rb_netconf/server/capability/confirmed_commit_1_1.rb +24 -0
  25. data/lib/hrr_rb_netconf/server/capability/rollback_on_error_1_0.rb +16 -0
  26. data/lib/hrr_rb_netconf/server/capability/startup_1_0.rb +22 -0
  27. data/lib/hrr_rb_netconf/server/capability/url_1_0.rb +23 -0
  28. data/lib/hrr_rb_netconf/server/capability/validate_1_0.rb +25 -0
  29. data/lib/hrr_rb_netconf/server/capability/validate_1_1.rb +25 -0
  30. data/lib/hrr_rb_netconf/server/capability/writable_running_1_0.rb +17 -0
  31. data/lib/hrr_rb_netconf/server/capability/xpath_1_0.rb +14 -0
  32. data/lib/hrr_rb_netconf/server/datastore.rb +30 -0
  33. data/lib/hrr_rb_netconf/server/datastore/oper_handler.rb +29 -0
  34. data/lib/hrr_rb_netconf/server/datastore/session.rb +52 -0
  35. data/lib/hrr_rb_netconf/server/error.rb +52 -0
  36. data/lib/hrr_rb_netconf/server/error/access_denied.rb +19 -0
  37. data/lib/hrr_rb_netconf/server/error/bad_attribute.rb +20 -0
  38. data/lib/hrr_rb_netconf/server/error/bad_element.rb +20 -0
  39. data/lib/hrr_rb_netconf/server/error/data_exists.rb +19 -0
  40. data/lib/hrr_rb_netconf/server/error/data_missing.rb +19 -0
  41. data/lib/hrr_rb_netconf/server/error/in_use.rb +19 -0
  42. data/lib/hrr_rb_netconf/server/error/invalid_value.rb +19 -0
  43. data/lib/hrr_rb_netconf/server/error/lock_denied.rb +19 -0
  44. data/lib/hrr_rb_netconf/server/error/malformed_message.rb +19 -0
  45. data/lib/hrr_rb_netconf/server/error/missing_attribute.rb +19 -0
  46. data/lib/hrr_rb_netconf/server/error/missing_element.rb +19 -0
  47. data/lib/hrr_rb_netconf/server/error/operation_failed.rb +19 -0
  48. data/lib/hrr_rb_netconf/server/error/operation_not_supported.rb +19 -0
  49. data/lib/hrr_rb_netconf/server/error/partial_operation.rb +19 -0
  50. data/lib/hrr_rb_netconf/server/error/resource_denied.rb +19 -0
  51. data/lib/hrr_rb_netconf/server/error/rollback_failed.rb +19 -0
  52. data/lib/hrr_rb_netconf/server/error/rpc_errorable.rb +138 -0
  53. data/lib/hrr_rb_netconf/server/error/too_big.rb +19 -0
  54. data/lib/hrr_rb_netconf/server/error/unknown_attribute.rb +19 -0
  55. data/lib/hrr_rb_netconf/server/error/unknown_element.rb +19 -0
  56. data/lib/hrr_rb_netconf/server/error/unknown_namespace.rb +19 -0
  57. data/lib/hrr_rb_netconf/server/errors.rb +28 -0
  58. data/lib/hrr_rb_netconf/server/filter.rb +48 -0
  59. data/lib/hrr_rb_netconf/server/filter/subtree.rb +135 -0
  60. data/lib/hrr_rb_netconf/server/filter/xpath.rb +59 -0
  61. data/lib/hrr_rb_netconf/server/model.rb +123 -0
  62. data/lib/hrr_rb_netconf/server/model/node.rb +19 -0
  63. data/lib/hrr_rb_netconf/server/operation.rb +92 -0
  64. data/lib/hrr_rb_netconf/server/session.rb +177 -0
  65. data/lib/hrr_rb_netconf/version.rb +6 -0
  66. metadata +149 -0
@@ -0,0 +1,109 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'thread'
5
+ require 'hrr_rb_netconf/logger'
6
+ require 'hrr_rb_netconf/server/error'
7
+ require 'hrr_rb_netconf/server/errors'
8
+ require 'hrr_rb_netconf/server/datastore'
9
+ require 'hrr_rb_netconf/server/capabilities'
10
+ require 'hrr_rb_netconf/server/session'
11
+
12
+ module HrrRbNetconf
13
+ class Server
14
+ SESSION_ID_UNALLOCATED = "UNALLOCATED"
15
+ SESSION_ID_MIN = 1
16
+ SESSION_ID_MAX = 2**32 - 1
17
+ SESSION_ID_MODULO = SESSION_ID_MAX - SESSION_ID_MIN + 1
18
+
19
+ def initialize datastore, capabilities: nil, strict_capabilities: false
20
+ @logger = Logger.new self.class.name
21
+ @datastore = datastore
22
+ @capabilities = capabilities || Capabilities.new
23
+ @strict_capabilities = strict_capabilities
24
+ @mutex = Mutex.new
25
+ @sessions = Hash.new
26
+ @locks = Hash.new
27
+ @lock_mutex = Mutex.new
28
+ @last_session_id = SESSION_ID_MIN - 1
29
+ end
30
+
31
+ def allocate_session_id
32
+ session_id = (SESSION_ID_MODULO).times.lazy.map{ |idx| (@last_session_id + 1 + idx - SESSION_ID_MIN) % SESSION_ID_MODULO + SESSION_ID_MIN }.reject{ |sid| @sessions.has_key? sid }.first
33
+ unless session_id
34
+ @logger.error { "Failed allocating Session ID" }
35
+ raise "Failed allocating Session ID"
36
+ end
37
+ @last_session_id = session_id
38
+ end
39
+
40
+ def delete_session session_id
41
+ if @sessions.has_key? session_id
42
+ @sessions.delete session_id
43
+ end
44
+ end
45
+
46
+ def start_session io
47
+ @logger.info { "Starting session" }
48
+ session_id = SESSION_ID_UNALLOCATED
49
+ begin
50
+ @mutex.synchronize do
51
+ session_id = allocate_session_id
52
+ @logger.info { "Session ID: #{session_id}" }
53
+ @sessions[session_id] = Session.new self, @capabilities, @datastore, session_id, io, @strict_capabilities
54
+ end
55
+ t = Thread.new {
56
+ @sessions[session_id].start
57
+ }
58
+ @logger.info { "Session started: Session ID: #{session_id}" }
59
+ t.join
60
+ rescue => e
61
+ @logger.error { "Session terminated: Session ID: #{session_id}" }
62
+ raise
63
+ else
64
+ @logger.info { "Session closed: Session ID: #{session_id}" }
65
+ ensure
66
+ @lock_mutex.synchronize do
67
+ @locks.delete_if{ |tgt, sid| sid == session_id }
68
+ end
69
+ @mutex.synchronize do
70
+ delete_session session_id
71
+ end
72
+ end
73
+ end
74
+
75
+ def close_session session_id
76
+ @logger.info { "Close session: Session ID: #{session_id}" }
77
+ @sessions[session_id].close
78
+ end
79
+
80
+ def lock target, session_id
81
+ @logger.info { "Lock: Target: #{target}, Session ID: #{session_id}" }
82
+ @lock_mutex.synchronize do
83
+ if @locks.has_key? target
84
+ @logger.info { "Lock failed, lock is already held by session-id: #{@locks[target]}" }
85
+ raise Error['lock-denied'].new('protocol', 'error', info: {'session-id' => @locks[target].to_s}, message: 'Lock failed, lock is already held')
86
+ else
87
+ @locks[target] = session_id
88
+ end
89
+ end
90
+ end
91
+
92
+ def unlock target, session_id
93
+ @logger.info { "Unlock: Target: #{target}, Session ID: #{session_id}" }
94
+ @lock_mutex.synchronize do
95
+ if @locks.has_key? target
96
+ if @locks[target] == session_id
97
+ @locks.delete target
98
+ else
99
+ @logger.info { "Unlock failed, lock is held by session-id: #{@locks[target]}" }
100
+ raise Error['operation-failed'].new('protocol', 'error')
101
+ end
102
+ else
103
+ @logger.info { "Unlock failed, lock is not held" }
104
+ raise Error['operation-failed'].new('protocol', 'error')
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,75 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'tsort'
5
+ require 'hrr_rb_netconf/server/capability'
6
+
7
+ module HrrRbNetconf
8
+ class Server
9
+ class Capabilities
10
+ def initialize features=nil, capabilities_h=nil
11
+ @features = features
12
+ unless capabilities_h
13
+ @caps = Capability.list.inject({}){ |a, b| a.merge({b => Capability[b].new}) }
14
+ else
15
+ @caps = capabilities_h
16
+ end
17
+ end
18
+
19
+ def negotiate remote_capabilities
20
+ filtered_by_features = @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }
21
+ capabilities_h = filtered_by_features.values.group_by{ |c| c.keyword }.map{ |k, cs|
22
+ cs.map{ |c|
23
+ remote_capabilities.lazy.map{ |rc| c.negotiate rc }.select{ |nc| nc }.first
24
+ }.compact.max
25
+ }.compact.inject({}){ |a, c|
26
+ a.merge({c.uri => c})
27
+ }
28
+ features = if @features.nil? then nil else @features.dup end
29
+ Capabilities.new features, capabilities_h
30
+ end
31
+
32
+ def register_capability name, &blk
33
+ cap = Capability.new(name)
34
+ blk.call cap
35
+ @caps[name] = cap
36
+ end
37
+
38
+ def unregister_capability name
39
+ @caps.delete name
40
+ end
41
+
42
+ def list_all
43
+ @caps.keys
44
+ end
45
+
46
+ def list_supported
47
+ @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }.map{ |k, v| v.id }
48
+ end
49
+
50
+ def list_loadable
51
+ filtered_by_features = @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }
52
+ @filtered_by_dependencies = filtered_by_features.select{ |k, v| v.dependencies.all?{ |d| filtered_by_features.has_key? d } }
53
+ tsort.map{ |k| @filtered_by_dependencies[k].id }
54
+ end
55
+
56
+ def each_loadable
57
+ filtered_by_features = @caps.select{ |k, v| if @features.nil? then true else (v.if_features - @features).empty? end }
58
+ @filtered_by_dependencies = filtered_by_features.select{ |k, v| v.dependencies.all?{ |d| filtered_by_features.has_key? d } }
59
+ tsort.each do|k|
60
+ yield @filtered_by_dependencies[k]
61
+ end
62
+ end
63
+
64
+ include TSort
65
+
66
+ def tsort_each_node &blk
67
+ @filtered_by_dependencies.each_key(&blk)
68
+ end
69
+
70
+ def tsort_each_child node, &blk
71
+ @filtered_by_dependencies[node].dependencies.each(&blk)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,206 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'uri'
5
+
6
+ module HrrRbNetconf
7
+ class Server
8
+ class Capability
9
+ @subclass_list = Array.new
10
+
11
+ class << self
12
+ def inherited klass
13
+ @subclass_list.push klass if @subclass_list
14
+ end
15
+
16
+ def [] key
17
+ __subclass_list__(__method__).find{ |klass| klass::ID == key }
18
+ end
19
+
20
+ def list
21
+ __subclass_list__(__method__).map{ |klass| klass::ID }
22
+ end
23
+
24
+ def __subclass_list__ method_name
25
+ send(:method_missing, method_name) unless @subclass_list
26
+ @subclass_list
27
+ end
28
+
29
+ private :__subclass_list__
30
+ end
31
+
32
+ class << self
33
+ def oper_procs
34
+ @oper_procs || {}
35
+ end
36
+
37
+ def oper_proc oper_name, &blk
38
+ @oper_procs ||= Hash.new
39
+ @oper_procs[oper_name] = blk
40
+ end
41
+
42
+ def models
43
+ @models || []
44
+ end
45
+
46
+ def model oper_name, path, stmt=nil, attrs={}
47
+ @models ||= Array.new
48
+ @models.push [oper_name, path, stmt, attrs]
49
+ end
50
+
51
+ private :oper_proc, :model
52
+ end
53
+
54
+ attr_accessor :if_features, :dependencies, :queries
55
+
56
+ def initialize id=nil
57
+ @id = id || self.class::ID
58
+ @queries = (self.class::QUERIES rescue {})
59
+ @if_features = (self.class::IF_FEATURES rescue [])
60
+ @dependencies = (self.class::DEPENDENCIES rescue [])
61
+ @oper_procs = self.class.oper_procs.dup
62
+ @models = (self.class.models rescue [])
63
+ @uri_proc = Proc.new { |id| id.split('?').first }
64
+ @keyword_proc = Proc.new { |id| id.split('?').first.match(/^((?:.+(?:\/|:))+.+)(?:\/|:)(.+)$/)[1] }
65
+ @version_proc = Proc.new { |id| id.split('?').first.match(/^((?:.+(?:\/|:))+.+)(?:\/|:)(.+)$/)[2] }
66
+ @decode_queries_proc = Proc.new { |qs_s| URI.decode_www_form(qs_s).inject({}){|a,(k,v)| a.merge({k=>(v.split(','))})} }
67
+ @encode_queries_proc = Proc.new { |qs_h| URI.encode_www_form(qs_h.map{|k,v| [k, v.join(',')]}) }
68
+ end
69
+
70
+ def oper_procs
71
+ @oper_procs
72
+ end
73
+
74
+ def oper_proc oper_name, &blk
75
+ if blk
76
+ @oper_procs[oper_name] = blk
77
+ end
78
+ @oper_procs[oper_name]
79
+ end
80
+
81
+ def models
82
+ @models
83
+ end
84
+
85
+ def model oper_name, path, stmt=nil, attrs={}
86
+ @models.push [oper_name, path, stmt, attrs]
87
+ end
88
+
89
+ def id
90
+ if @queries.empty?
91
+ @id
92
+ else
93
+ [@id, @encode_queries_proc.call(@queries)].join('?')
94
+ end
95
+ end
96
+
97
+ def uri
98
+ @uri_proc.call id
99
+ end
100
+
101
+ def keyword
102
+ @keyword_proc.call id
103
+ end
104
+
105
+ def version
106
+ @version_proc.call id
107
+ end
108
+
109
+ def uri_proc &blk
110
+ if blk
111
+ @uri_proc = blk
112
+ else
113
+ raise ArgumentError, "block not given"
114
+ end
115
+ end
116
+
117
+ def keyword_proc &blk
118
+ if blk
119
+ @keyword_proc = blk
120
+ else
121
+ raise ArgumentError, "block not given"
122
+ end
123
+ end
124
+
125
+ def version_proc &blk
126
+ if blk
127
+ @version_proc = blk
128
+ else
129
+ raise ArgumentError, "block not given"
130
+ end
131
+ end
132
+
133
+ def decode_queries_proc &blk
134
+ if blk
135
+ @decode_queries_proc = blk
136
+ else
137
+ raise ArgumentError, "block not given"
138
+ end
139
+ end
140
+
141
+ def encode_queries_proc &blk
142
+ if blk
143
+ @encode_queries_proc = blk
144
+ else
145
+ raise ArgumentError, "block not given"
146
+ end
147
+ end
148
+
149
+ def negotiate other_id
150
+ other_keyword = @keyword_proc.call(other_id)
151
+ unless keyword == other_keyword
152
+ nil
153
+ else
154
+ other_version = @version_proc.call(other_id)
155
+ case version <=> other_version
156
+ when 0
157
+ c = self.dup
158
+ c.queries = negotiate_queries(other_id)
159
+ c
160
+ else
161
+ nil
162
+ end
163
+ end
164
+ end
165
+
166
+ def negotiate_queries other_id
167
+ other_queries = @decode_queries_proc.call((other_id.split('?') + [''])[1])
168
+ queries.inject({}){ |a, (k, v)|
169
+ if other_queries.has_key?(k)
170
+ values = v & other_queries[k]
171
+ if values.empty?
172
+ a
173
+ else
174
+ a.merge({k => values})
175
+ end
176
+ else
177
+ a
178
+ end
179
+ }
180
+ end
181
+
182
+ include Comparable
183
+
184
+ def <=> other
185
+ unless keyword == other.keyword
186
+ nil
187
+ else
188
+ version <=> other.version
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ require 'hrr_rb_netconf/server/capability/base_1_0'
196
+ require 'hrr_rb_netconf/server/capability/base_1_1'
197
+ require 'hrr_rb_netconf/server/capability/writable_running_1_0'
198
+ require 'hrr_rb_netconf/server/capability/candidate_1_0'
199
+ require 'hrr_rb_netconf/server/capability/confirmed_commit_1_0'
200
+ require 'hrr_rb_netconf/server/capability/confirmed_commit_1_1'
201
+ require 'hrr_rb_netconf/server/capability/rollback_on_error_1_0'
202
+ require 'hrr_rb_netconf/server/capability/startup_1_0'
203
+ require 'hrr_rb_netconf/server/capability/validate_1_0'
204
+ require 'hrr_rb_netconf/server/capability/validate_1_1'
205
+ require 'hrr_rb_netconf/server/capability/url_1_0'
206
+ require 'hrr_rb_netconf/server/capability/xpath_1_0'
@@ -0,0 +1,183 @@
1
+ # coding: utf-8
2
+ # vim: et ts=2 sw=2
3
+
4
+ require 'rexml/document'
5
+ require 'hrr_rb_netconf/logger'
6
+
7
+ module HrrRbNetconf
8
+ class Server
9
+ class Capability
10
+ class Base_1_0 < Capability
11
+ ID = 'urn:ietf:params:netconf:base:1.0'
12
+ DEPENDENCIES = []
13
+ IF_FEATURES = []
14
+
15
+ oper_proc('get'){ |session, datastore, input_e|
16
+ datastore.run 'get', input_e
17
+ }
18
+
19
+ oper_proc('get-config'){ |session, datastore, input_e|
20
+ datastore.run 'get-config', input_e
21
+ }
22
+
23
+ oper_proc('edit-config'){ |session, datastore, input_e|
24
+ datastore.run 'edit-config', input_e
25
+ '<ok />'
26
+ }
27
+
28
+ oper_proc('copy-config'){ |session, datastore, input_e|
29
+ datastore.run 'copy-config', input_e
30
+ '<ok />'
31
+ }
32
+
33
+ oper_proc('delete-config'){ |session, datastore, input_e|
34
+ datastore.run 'delete-config', input_e
35
+ '<ok />'
36
+ }
37
+
38
+ oper_proc('lock'){ |session, datastore, input_e|
39
+ target = input_e.elements['target'].elements[1].name
40
+ session.lock target
41
+ begin
42
+ datastore.run 'lock', input_e
43
+ '<ok />'
44
+ rescue
45
+ session.unlock target
46
+ raise
47
+ end
48
+ }
49
+
50
+ oper_proc('unlock'){ |session, datastore, input_e|
51
+ datastore.run 'unlock', input_e
52
+ target = input_e.elements['target'].elements[1].name
53
+ session.unlock target
54
+ '<ok />'
55
+ }
56
+
57
+ oper_proc('close-session'){ |session, datastore, input_e|
58
+ datastore.run 'close-session', input_e
59
+ session.close
60
+ '<ok />'
61
+ }
62
+
63
+ oper_proc('kill-session'){ |session, datastore, input_e|
64
+ session.close_other Integer(input_e.elements['session-id'].text)
65
+ '<ok />'
66
+ }
67
+
68
+ model 'get', ['filter'], 'leaf', 'type' => 'anyxml'
69
+ model 'get-config', ['source'], 'container'
70
+ model 'get-config', ['source', 'config-source'], 'choice', 'mandatory' => true
71
+ model 'get-config', ['source', 'config-source', 'running'], 'leaf', 'type' => 'empty'
72
+ model 'get-config', ['filter'], 'leaf', 'type' => 'anyxml'
73
+ model 'edit-config', ['target'], 'container'
74
+ model 'edit-config', ['target', 'config-target'], 'choice', 'mandatory' => true
75
+ model 'edit-config', ['default-operation'], 'leaf', 'type' => 'enumeration', 'enum' => ['merge', 'replace', 'none'], 'default' => 'merge'
76
+ model 'edit-config', ['error-option'], 'leaf', 'type' => 'enumeration', 'enum' => ['stop-on-error', 'continue-on-error', 'rollback-on-error'], 'default' => 'stop-on-error'
77
+ model 'edit-config', ['edit-content'], 'choice', 'mandatory' => true
78
+ model 'edit-config', ['edit-content', 'config'], 'leaf', 'type' => 'anyxml'
79
+ model 'copy-config', ['target'], 'container'
80
+ model 'copy-config', ['target', 'config-target'], 'choice', 'mandatory' => true
81
+ model 'copy-config', ['source'], 'container'
82
+ model 'copy-config', ['source', 'config-source'], 'choice', 'mandatory' => true
83
+ model 'copy-config', ['source', 'config-source', 'running'], 'leaf', 'type' => 'empty'
84
+ model 'copy-config', ['source', 'config-source', 'config'], 'leaf', 'type' => 'anyxml'
85
+ model 'delete-config', ['target'], 'container'
86
+ model 'delete-config', ['target', 'config-target'], 'choice', 'mandatory' => true
87
+ model 'lock', ['target'], 'container'
88
+ model 'lock', ['target', 'config-target'], 'choice', 'mandatory' => true
89
+ model 'lock', ['target', 'config-target', 'running'], 'leaf', 'type' => 'empty'
90
+ model 'unlock', ['target'], 'container'
91
+ model 'unlock', ['target', 'config-target'], 'choice', 'mandatory' => true
92
+ model 'unlock', ['target', 'config-target', 'running'], 'leaf', 'type' => 'empty'
93
+ model 'close-session', []
94
+ model 'kill-session', ['session-id'], 'leaf', 'type' => 'integer', 'range' => [1, 2**32-1]
95
+
96
+ class Sender
97
+ def initialize io_w
98
+ @logger = Logger.new self.class.name
99
+ @io_w = io_w
100
+ @formatter = REXML::Formatters::Pretty.new(2)
101
+ @formatter.compact = true
102
+ end
103
+
104
+ def send_message msg
105
+ buf = String.new
106
+ case msg
107
+ when String
108
+ begin
109
+ @formatter.write(REXML::Document.new(msg, {:ignore_whitespace_nodes => :all}).root, buf)
110
+ rescue => e
111
+ @logger.error { "Invalid sending message: #{msg.inspect}: #{e.message}" }
112
+ raise "Invalid sending message: #{msg.inspect}: #{e.message}"
113
+ end
114
+ when REXML::Document
115
+ @formatter.write(msg.root, buf)
116
+ when REXML::Element
117
+ @formatter.write(msg, buf)
118
+ else
119
+ @logger.error { "Unexpected sending message: #{msg.inspect}" }
120
+ raise ArgumentError, "Unexpected sending message: #{msg.inspect}"
121
+ end
122
+ @logger.debug { "Sending message: #{buf.inspect}" }
123
+ begin
124
+ @io_w.write "#{buf}\n]]>]]>\n"
125
+ rescue => e
126
+ @logger.info { "Sender IO closed: #{e.class}: #{e.message}" }
127
+ raise IOError, "Sender IO closed: #{e.class}: #{e.message}"
128
+ end
129
+ end
130
+ end
131
+
132
+ class Receiver
133
+ def initialize io_r
134
+ @logger = Logger.new self.class.name
135
+ @io_r = io_r
136
+ end
137
+
138
+ def receive_message
139
+ buf = String.new
140
+ loop do
141
+ begin
142
+ tmp = @io_r.read(1)
143
+ rescue => e
144
+ @logger.info { "Receiver IO closed: #{e.class}: #{e.message}" }
145
+ return nil
146
+ end
147
+ if tmp.nil?
148
+ @logger.info { "Receiver IO closed" }
149
+ return nil
150
+ end
151
+ buf += tmp
152
+ if buf[-6..-1] == ']]>]]>'
153
+ break
154
+ end
155
+ end
156
+ @logger.debug { "Received message: #{buf[0..-7].inspect}" }
157
+ begin
158
+ received_msg = REXML::Document.new(buf[0..-7], {:ignore_whitespace_nodes => :all}).root
159
+ validate_received_msg received_msg
160
+ received_msg
161
+ rescue => e
162
+ info = "Invalid received message: #{e.message.split("\n").first}: #{buf[0..-7].inspect}"
163
+ @logger.info { info }
164
+ raise info
165
+ end
166
+ end
167
+
168
+ def validate_received_msg received_msg
169
+ unless received_msg
170
+ raise "No valid root tag interpreted"
171
+ end
172
+ unless "rpc" == received_msg.name
173
+ raise "Invalid message: expected #{"rpc".inspect}, but got #{received_msg.name.inspect}"
174
+ end
175
+ unless "urn:ietf:params:xml:ns:netconf:base:1.0" == received_msg.namespace
176
+ raise "Invalid namespace: expected #{"urn:ietf:params:xml:ns:netconf:base:1.0".inspect}, but got #{received_msg.namespace.inspect}"
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end