hrr_rb_netconf 0.1.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.
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