netsnmp 0.2.0 → 0.5.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,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mib/parser"
4
+
5
+ module NETSNMP
6
+ module MIB
7
+ using IsNumericExtensions
8
+
9
+ OIDREGEX = /^[\d\.]*$/
10
+
11
+ module_function
12
+
13
+ MIBDIRS = ENV.fetch("MIBDIRS", File.join("/usr", "share", "snmp", "mibs")).split(":")
14
+ PARSER = Parser.new
15
+ @parser_mutex = Mutex.new
16
+ @modules_loaded = []
17
+ @object_identifiers = {}
18
+
19
+ # Translates na identifier, such as "sysDescr", into an OID
20
+ def oid(identifier)
21
+ prefix, *suffix = case identifier
22
+ when Array
23
+ identifier
24
+ else
25
+ identifier.split(".", 2)
26
+ end
27
+
28
+ # early exit if it's an OID already
29
+ unless prefix.integer?
30
+ load_defaults
31
+ # load module if need be
32
+ idx = prefix.index("::")
33
+ if idx
34
+ mod = prefix[0..(idx - 1)]
35
+ type = prefix[(idx + 2)..-1]
36
+ unless module_loaded?(mod)
37
+ return unless load(mod)
38
+ end
39
+ else
40
+ type = prefix
41
+ end
42
+
43
+ return if type.nil? || type.empty?
44
+
45
+ prefix = @object_identifiers[type] ||
46
+ raise(Error, "can't convert #{type} to OID")
47
+
48
+ end
49
+
50
+ [prefix, *suffix].join(".")
51
+ end
52
+
53
+ def identifier(oid)
54
+ @object_identifiers.select do |_, ids_oid|
55
+ oid.start_with?(ids_oid)
56
+ end.sort_by(&:size).first
57
+ end
58
+
59
+ #
60
+ # Loads a MIB. Can be called multiple times, as it'll load it once.
61
+ #
62
+ # Accepts the MIB name in several ways:
63
+ #
64
+ # MIB.load("SNMPv2-MIB")
65
+ # MIB.load("SNMPv2-MIB.txt")
66
+ # MIB.load("/path/to/SNMPv2-MIB.txt")
67
+ #
68
+ def load(mod)
69
+ unless File.file?(mod)
70
+ moddir = nil
71
+ MIBDIRS.each do |mibdir|
72
+ if File.exist?(File.join(mibdir, mod))
73
+ moddir = File.join(mibdir, mod)
74
+ break
75
+ elsif File.extname(mod).empty? && File.exist?(File.join(mibdir, "#{mod}.txt"))
76
+ moddir = File.join(mibdir, "#{mod}.txt")
77
+ break
78
+ end
79
+ end
80
+ return false unless moddir
81
+ mod = moddir
82
+ end
83
+ return true if @modules_loaded.include?(mod)
84
+ do_load(mod)
85
+ @modules_loaded << mod
86
+ true
87
+ end
88
+
89
+ def module_loaded?(mod)
90
+ if File.file?(mod)
91
+ @modules_loaded.include?(mod)
92
+ else
93
+ @modules_loaded.map { |path| File.basename(path, ".*") }.include?(mod)
94
+ end
95
+ end
96
+
97
+ TYPES = ["OBJECT IDENTIFIER", "OBJECT-TYPE", "MODULE-IDENTITY"].freeze
98
+
99
+ STATIC_MIB_TO_OID = {
100
+ "iso" => "1"
101
+ }.freeze
102
+
103
+ #
104
+ # Loads the MIB all the time, where +mod+ is the absolute path to the MIB.
105
+ #
106
+ def do_load(mod)
107
+ data = @parser_mutex.synchronize { PARSER.parse(File.read(mod)) }
108
+
109
+ imports = load_imports(data[:imports])
110
+
111
+ declarations = Hash[
112
+ data[:declarations].reject { |dec| !dec.key?(:name) || !TYPES.include?(dec[:type]) }
113
+ .map { |dec| [String(dec[:name]), String(dec[:value]).split(/ +/)] }
114
+ ]
115
+
116
+ declarations.each do |nme, value|
117
+ store_oid_in_identifiers(nme, value, imports: imports, declarations: declarations)
118
+ end
119
+ end
120
+
121
+ def store_oid_in_identifiers(nme, value, imports:, declarations:)
122
+ oid = value.flat_map do |cp|
123
+ if cp.integer?
124
+ cp
125
+ elsif @object_identifiers.key?(cp)
126
+ @object_identifiers[cp]
127
+ elsif declarations.key?(cp)
128
+ store_oid_in_identifiers(cp, declarations[cp], imports: imports, declarations: declarations)
129
+ @object_identifiers[cp]
130
+ else
131
+ STATIC_MIB_TO_OID[cp] || begin
132
+ imported_mod, = imports.find do |_, identifiers|
133
+ identifiers.include?(cp)
134
+ end
135
+
136
+ raise Error, "didn't find a module to import \"#{cp}\" from" unless imported_mod
137
+
138
+ load(imported_mod)
139
+
140
+ @object_identifiers[cp]
141
+ end
142
+ end
143
+ end.join(".")
144
+
145
+ @object_identifiers[nme] = oid
146
+ end
147
+
148
+ #
149
+ # Reformats the import lists into an hash indexed by module name, to a list of
150
+ # imported names
151
+ #
152
+ def load_imports(imports)
153
+ return unless imports
154
+
155
+ imports = [imports] unless imports.respond_to?(:to_ary)
156
+ imports.each_with_object({}) do |import, imp|
157
+ imp[String(import[:name])] = case import[:ids]
158
+ when Hash
159
+ [String(import[:ids][:name])]
160
+ else
161
+ import[:ids].map { |id| String(id[:name]) }
162
+ end
163
+ end
164
+ end
165
+
166
+ def load_defaults
167
+ # loading the defaults MIBS
168
+ load("SNMPv2-MIB")
169
+ load("IF-MIB")
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,750 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module NETSNMP::MIB
6
+ class Parser < Parslet::Parser
7
+ root :mibfile
8
+
9
+ def spaced(character = nil)
10
+ if character.nil? && block_given?
11
+ yield >> space.repeat
12
+ else
13
+ str(character) >> space.repeat
14
+ end
15
+ end
16
+
17
+ def curly(atom)
18
+ str("{") >> space.repeat >> atom >> space.repeat >> str("}")
19
+ end
20
+
21
+ def bracketed(atom)
22
+ str("(") >> space.repeat >> atom >> space.repeat >> str(")")
23
+ end
24
+
25
+ def square_bracketed(atom)
26
+ str("[") >> space.repeat >> atom >> space.repeat >> str("]")
27
+ end
28
+
29
+ def with_separator(atom, separator = nil)
30
+ if separator
31
+ sep = if separator.is_a?(String)
32
+ space.repeat >> str(separator) >> space.repeat
33
+ else
34
+ separator
35
+ end
36
+
37
+ atom >> (sep >> atom).repeat
38
+ else
39
+ atom >> (space.repeat >> atom).repeat
40
+ end
41
+ end
42
+
43
+ rule(:mibfile) do
44
+ space.repeat >> modules.maybe
45
+ end
46
+
47
+ rule(:modules) do
48
+ with_separator(mod)
49
+ end
50
+
51
+ rule(:mod) do
52
+ spaced { module_name.as(:name) } >> module_oid >>
53
+ spaced("DEFINITIONS") >> colon_colon_part >>
54
+ spaced("BEGIN") >>
55
+ exports_part.as(:exports) >>
56
+ linkage_part.as(:imports) >>
57
+ declaration_part.as(:declarations) >>
58
+ spaced("END")
59
+ end
60
+
61
+ rule(:module_name) { uppercase_identifier }
62
+
63
+ rule(:module_oid) do
64
+ spaced { curly(object_identifier) }.maybe
65
+ end
66
+
67
+ rule(:declaration_part) do
68
+ spaced { declarations }.maybe
69
+ end
70
+
71
+ rule(:exports_part) do
72
+ spaced { exports_clause }.maybe
73
+ end
74
+
75
+ rule(:exports_clause) do
76
+ spaced("EXPORTS") >> spaced { import_identifiers } >> str(";")
77
+ end
78
+
79
+ rule(:linkage_part) do
80
+ spaced { linkage_clause }.maybe
81
+ end
82
+
83
+ rule(:linkage_clause) do
84
+ spaced("IMPORTS") >> spaced { import_part } >> str(";")
85
+ end
86
+
87
+ rule(:import_part) do
88
+ imports.maybe
89
+ end
90
+
91
+ rule(:imports) { with_separator(import) }
92
+
93
+ rule(:import) do
94
+ spaced { import_identifiers.as(:ids) } >> spaced("FROM") >> module_name.as(:name)
95
+ end
96
+
97
+ rule(:import_identifiers) { with_separator(import_identifier.as(:name), ",") }
98
+
99
+ rule(:import_identifier) do
100
+ lowercase_identifier | uppercase_identifier | imported_keyword
101
+ end
102
+
103
+ rule(:imported_keyword) do
104
+ imported_smi_keyword |
105
+ str("BITS") |
106
+ str("Integer32") |
107
+ str("IpAddress") |
108
+ str("MANDATORY-GROUPS") |
109
+ str("MODULE-COMPLIANCE") |
110
+ str("MODULE-IDENTITY") |
111
+ str("OBJECT-GROUP") |
112
+ str("OBJECT-IDENTITY") |
113
+ str("OBJECT-TYPE") |
114
+ str("Opaque") |
115
+ str("TEXTUAL-CONVENTION") |
116
+ str("TimeTicks") |
117
+ str("Unsigned32")
118
+ end
119
+
120
+ rule(:imported_smi_keyword) do
121
+ str("AGENT-CAPABILITIES") |
122
+ str("Counter32") |
123
+ str("Counter64") |
124
+ str("Gauge32") |
125
+ str("NOTIFICATION-GROUP") |
126
+ str("NOTIFICATION-TYPE") |
127
+ str("TRAP-TYPE")
128
+ end
129
+
130
+ rule(:declarations) do
131
+ with_separator(declaration)
132
+ end
133
+
134
+ rule(:declaration) do
135
+ type_declaration |
136
+ value_declaration |
137
+ object_identity_clause |
138
+ object_type_clause |
139
+ traptype_clause |
140
+ notification_type_clause |
141
+ module_identity_clause |
142
+ module_compliance_clause |
143
+ object_group_clause |
144
+ notification_group_clause |
145
+ agent_capabilities_clause |
146
+ macro_clause
147
+ end
148
+
149
+ rule(:macro_clause) do
150
+ spaced { macro_name.as(:name) } >> spaced { str("MACRO").as(:type) } >> colon_colon_part >>
151
+ spaced("BEGIN") >>
152
+ # ignoring macro clauses
153
+ match("^(?!END)").repeat >>
154
+ spaced("END")
155
+ end
156
+
157
+ rule(:macro_name) do
158
+ str("MODULE-IDENTITY") |
159
+ str("OBJECT-TYPE") |
160
+ str("TRAP-TYPE") |
161
+ str("NOTIFICATION-TYPE") |
162
+ str("OBJECT-IDENTITY") |
163
+ str("TEXTUAL-CONVENTION") |
164
+ str("OBJECT-GROUP") |
165
+ str("NOTIFICATION-GROUP") |
166
+ str("MODULE-COMPLIANCE") |
167
+ str("AGENT-CAPABILITIES")
168
+ end
169
+
170
+ rule(:agent_capabilities_clause) do
171
+ spaced { lowercase_identifier.as(:name) } >>
172
+ spaced { str("AGENT-CAPABILITIES").as(:type) } >>
173
+ spaced("PRODUCT-RELEASE") >> spaced { text } >>
174
+ spaced("STATUS") >> spaced { status } >>
175
+ spaced("DESCRIPTION") >> spaced { text } >>
176
+ spaced { refer_part }.maybe >>
177
+ spaced { module_part_capabilities }.maybe >>
178
+ colon_colon_part >> curly(object_identifier)
179
+ end
180
+
181
+ rule(:module_part_capabilities) do
182
+ modules_capabilities
183
+ end
184
+
185
+ rule(:modules_capabilities) do
186
+ with_separator(module_capabilities)
187
+ end
188
+
189
+ rule(:module_capabilities) do
190
+ spaced("SUPPORTS") >>
191
+ module_name_capabilities >>
192
+ spaced("INCLUDES") >> curly(capabilities_groups) >>
193
+ spaced { variation_part }.maybe
194
+ end
195
+
196
+ rule(:module_name_capabilities) do
197
+ spaced { uppercase_identifier } >> object_identifier | uppercase_identifier
198
+ end
199
+
200
+ rule(:capabilities_groups) do
201
+ with_separator(capabilities_group)
202
+ end
203
+
204
+ rule(:capabilities_group) { objectIdentifier }
205
+
206
+ rule(:variation_part) do
207
+ variations
208
+ end
209
+
210
+ rule(:variations) do
211
+ with_separator(variation)
212
+ end
213
+
214
+ rule(:variation) do
215
+ spaced("VARIATION") >> object_identifier >>
216
+ spaced { syntax_part }.maybe >>
217
+ spaced { write_syntax_part } >>
218
+ spaced { variation_access_part }.maybe >>
219
+ spaced { creation_part }.maybe >>
220
+ spaced { def_val_part }.maybe >>
221
+ spaced("DESCRIPTION") >> text
222
+ end
223
+
224
+ rule(:variation_access_part) do
225
+ spaced("ACCESS") >> variation_access
226
+ end
227
+
228
+ rule(:variation_access) { lowercase_identifier }
229
+
230
+ rule(:creation_part) do
231
+ spaced("CREATION-REQUIRES") >> curly(cells)
232
+ end
233
+
234
+ rule(:cells) { with_separator(cell, ",") }
235
+
236
+ rule(:cell) { object_identifier }
237
+
238
+ rule(:notification_group_clause) do
239
+ spaced { lowercase_identifier.as(:name) } >>
240
+ spaced { str("NOTIFICATION-GROUP").as(:type) } >>
241
+ spaced { notifications_part } >>
242
+ spaced("STATUS") >> spaced { status } >>
243
+ spaced("DESCRIPTION") >> spaced { text } >>
244
+ spaced { refer_part }.maybe >>
245
+ colon_colon_part >> curly(object_identifier)
246
+ end
247
+
248
+ rule(:notifications_part) do
249
+ spaced("NOTIFICATIONS") >> curly(notifications)
250
+ end
251
+
252
+ rule(:notifications) do
253
+ with_separator(notification, ",")
254
+ end
255
+
256
+ rule(:notification) do
257
+ notification_name
258
+ end
259
+
260
+ rule(:object_group_clause) do
261
+ spaced { lowercase_identifier.as(:name) } >>
262
+ spaced { str("OBJECT-GROUP").as(:type) } >>
263
+ spaced { object_group_objects_part } >>
264
+ spaced("STATUS") >> spaced { status } >>
265
+ spaced("DESCRIPTION") >> spaced { text } >>
266
+ spaced { refer_part }.maybe >>
267
+ colon_colon_part >> curly(object_identifier)
268
+ end
269
+
270
+ rule(:object_group_objects_part) do
271
+ spaced("OBJECTS") >> curly(objects)
272
+ end
273
+
274
+ rule(:module_compliance_clause) do
275
+ spaced { lowercase_identifier.as(:name) } >>
276
+ spaced { str("MODULE-COMPLIANCE").as(:type) } >>
277
+ spaced("STATUS") >> spaced { status } >>
278
+ spaced("DESCRIPTION") >> spaced { text } >>
279
+ spaced { refer_part }.maybe >>
280
+ spaced { compliance_modules } >>
281
+ colon_colon_part >> curly(object_identifier)
282
+ end
283
+
284
+ rule(:compliance_modules) do
285
+ with_separator(compliance_module)
286
+ end
287
+
288
+ rule(:compliance_module) do
289
+ spaced { str("MODULE") >> (space_in_line.repeat(1) >> compliance_module_name).maybe } >>
290
+ spaced { mandatory_part }.maybe >>
291
+ compliances.maybe
292
+ end
293
+
294
+ rule(:compliance_module_name) do
295
+ uppercase_identifier
296
+ end
297
+
298
+ rule(:mandatory_part) do
299
+ spaced("MANDATORY-GROUPS") >> curly(mandatory_groups)
300
+ end
301
+
302
+ rule(:compliances) do
303
+ with_separator(compliance).as(:compliances)
304
+ end
305
+
306
+ rule(:compliance) do
307
+ compliance_group | compliance_object
308
+ end
309
+
310
+ rule(:compliance_group) do
311
+ spaced { str("GROUP").as(:type) } >> spaced { object_identifier.as(:name) } >>
312
+ spaced("DESCRIPTION") >> text
313
+ end
314
+
315
+ rule(:compliance_object) do
316
+ spaced { str("OBJECT").as(:type) } >>
317
+ spaced { object_identifier.as(:name) } >>
318
+ spaced { syntax_part }.maybe >>
319
+ spaced { write_syntax_part }.maybe >>
320
+ spaced { access_part }.maybe >>
321
+ spaced("DESCRIPTION") >> text
322
+ end
323
+
324
+ rule(:syntax_part) do
325
+ spaced("SYNTAX") >> syntax.as(:syntax)
326
+ end
327
+
328
+ rule(:write_syntax_part) do
329
+ (spaced("WRITE-SYNTAX") >> spaced { syntax }).maybe
330
+ end
331
+
332
+ rule(:access_part) do
333
+ (spaced("MIN-ACCESS") >> spaced { access }).maybe
334
+ end
335
+
336
+ rule(:mandatory_groups) do
337
+ with_separator(mandatory_group, ",").as(:groups)
338
+ end
339
+
340
+ rule(:mandatory_group) { object_identifier.as(:name) }
341
+
342
+ rule(:module_identity_clause) do
343
+ spaced { lowercase_identifier.as(:name) } >>
344
+ spaced { str("MODULE-IDENTITY").as(:type) } >>
345
+ (spaced("SUBJECT-CATEGORIES") >> curly(category_ids)).maybe >>
346
+ spaced("LAST-UPDATED") >> spaced { ext_utc_time } >>
347
+ spaced("ORGANIZATION") >> spaced { text.as(:organization) } >>
348
+ spaced("CONTACT-INFO") >> spaced { text.as(:contact_info) } >>
349
+ spaced("DESCRIPTION") >> spaced { text } >>
350
+ spaced { revisions }.maybe >>
351
+ colon_colon_part >> curly(object_identifier.as(:value))
352
+ end
353
+
354
+ rule(:ext_utc_time) { text }
355
+
356
+ rule(:revisions) do
357
+ with_separator(revision)
358
+ end
359
+
360
+ rule(:revision) do
361
+ spaced("REVISION") >> spaced { ext_utc_time } >>
362
+ spaced("DESCRIPTION") >>
363
+ text
364
+ end
365
+
366
+ rule(:category_ids) do
367
+ with_separator(category_id, ",")
368
+ end
369
+
370
+ rule(:category_id) do
371
+ spaced { lowercase_identifier } >> bracketed(number) | lowercase_identifier
372
+ end
373
+
374
+ rule(:notification_type_clause) do
375
+ spaced { lowercase_identifier.as(:name) } >>
376
+ spaced { str("NOTIFICATION-TYPE").as(:type) } >>
377
+ spaced { notification_objects_part }.maybe >>
378
+ spaced("STATUS") >> spaced { status } >>
379
+ spaced("DESCRIPTION") >> spaced { text } >>
380
+ spaced { refer_part }.maybe >>
381
+ colon_colon_part >> curly(notification_name)
382
+ end
383
+
384
+ rule(:notification_objects_part) do
385
+ spaced("OBJECTS") >> curly(objects)
386
+ end
387
+
388
+ rule(:objects) do
389
+ with_separator(object, ",")
390
+ end
391
+
392
+ rule(:object) do
393
+ object_identifier
394
+ end
395
+
396
+ rule(:notification_name) do
397
+ object_identifier
398
+ end
399
+
400
+ rule(:traptype_clause) do
401
+ spaced { fuzzy_lowercase_identifier.as(:name) } >>
402
+ spaced { str("TRAP-TYPE").as(:type) } >> spaced { enterprise_part } >>
403
+ spaced { var_part }.maybe >>
404
+ spaced { descr_part }.maybe >>
405
+ spaced { refer_part }.maybe >>
406
+ colon_colon_part >> number
407
+ end
408
+
409
+ rule(:enterprise_part) do
410
+ spaced("ENTERPRISE") >> object_identifier |
411
+ spaced("ENTERPRISE") >> curly(object_identifier)
412
+ end
413
+
414
+ rule(:var_part) do
415
+ spaced("VARIABLES") >> curly(var_types)
416
+ end
417
+
418
+ rule(:var_types) do
419
+ with_separator(var_type, ",")
420
+ end
421
+
422
+ rule(:var_type) { object_identifier }
423
+
424
+ rule(:descr_part) do
425
+ spaced("DESCRIPTION") >> text
426
+ end
427
+
428
+ rule(:object_type_clause) do
429
+ spaced { lowercase_identifier.as(:name) } >>
430
+ spaced { str("OBJECT-TYPE").as(:type) } >>
431
+ spaced { syntax_part }.maybe >>
432
+ spaced { units_part }.maybe >>
433
+ spaced { max_access_part }.maybe >>
434
+ (spaced("STATUS") >> spaced { status }).maybe >>
435
+ spaced { description_clause }.maybe >>
436
+ spaced { refer_part }.maybe >>
437
+ spaced { index_part }.maybe >>
438
+ spaced { mib_index }.maybe >>
439
+ spaced { def_val_part }.maybe >>
440
+ colon_colon_part >> curly(object_identifier.as(:value))
441
+ end
442
+
443
+ rule(:object_identity_clause) do
444
+ spaced { lowercase_identifier.as(:name) } >>
445
+ spaced { str("OBJECT-IDENTITY").as(:type) } >>
446
+ spaced("STATUS") >> spaced { status } >>
447
+ spaced("DESCRIPTION") >> spaced { text } >>
448
+ spaced { refer_part }.maybe >>
449
+ colon_colon_part >> curly(object_identifier)
450
+ end
451
+
452
+ rule(:units_part) do
453
+ spaced("UNITS") >> text.as(:units)
454
+ end
455
+
456
+ rule(:max_access_part) do
457
+ spaced("MAX-ACCESS") >> access | spaced("ACCESS") >> access
458
+ end
459
+
460
+ rule(:access) { lowercase_identifier }
461
+
462
+ rule(:description_clause) do
463
+ spaced("DESCRIPTION") >> text
464
+ end
465
+
466
+ rule(:index_part) do
467
+ spaced("AUGMENTS") >> curly(entry)
468
+ end
469
+
470
+ rule(:mib_index) do
471
+ spaced("INDEX") >> curly(index_types)
472
+ end
473
+
474
+ rule(:def_val_part) do
475
+ spaced("DEFVAL") >> curly(valueof_simple_syntax)
476
+ end
477
+
478
+ rule(:valueof_simple_syntax) do
479
+ value | lowercase_identifier | text | curly(object_identifiers_defval)
480
+ end
481
+
482
+ rule(:object_identifiers_defval) do
483
+ with_separator(object_identifier_defval)
484
+ end
485
+
486
+ rule(:object_identifier_defval) do
487
+ spaced { lowercase_identifier } >> bracketed(number) |
488
+ number
489
+ end
490
+
491
+ rule(:index_types) do
492
+ with_separator(index_type, ",")
493
+ end
494
+
495
+ rule(:index_type) do
496
+ spaced("IMPLIED") >> idx | idx
497
+ end
498
+
499
+ rule(:idx) do
500
+ object_identifier
501
+ end
502
+
503
+ rule(:entry) do
504
+ object_identifier
505
+ end
506
+
507
+ rule(:value_declaration) do
508
+ spaced { fuzzy_lowercase_identifier.as(:name) } >>
509
+ spaced { str("OBJECT IDENTIFIER").as(:type) } >>
510
+ colon_colon_part >> curly(object_identifier.as(:value))
511
+ end
512
+
513
+ rule(:fuzzy_lowercase_identifier) do
514
+ lowercase_identifier | uppercase_identifier
515
+ end
516
+
517
+ rule(:object_identifier) do
518
+ sub_identifiers
519
+ end
520
+
521
+ rule(:sub_identifiers) do
522
+ with_separator(sub_identifier, space_in_line.repeat)
523
+ end
524
+
525
+ rule(:sub_identifier) do
526
+ fuzzy_lowercase_identifier |
527
+ number |
528
+ spaced { lowercase_identifier } >> bracketed(number)
529
+ end
530
+
531
+ rule(:type_declaration) do
532
+ spaced { type_name.as(:vartype) } >> colon_colon_part >> type_declaration_rhs
533
+ end
534
+
535
+ rule(:type_name) do
536
+ uppercase_identifier | type_smi
537
+ end
538
+
539
+ rule(:type_smi) do
540
+ type_smi_and_sppi | type_smi_only
541
+ end
542
+
543
+ rule(:type_declaration_rhs) do
544
+ spaced { choice_clause } |
545
+ spaced { str("TEXTUAL-CONVENTION") } >>
546
+ spaced { display_part }.maybe >>
547
+ spaced("STATUS") >> spaced { status } >>
548
+ spaced("DESCRIPTION") >> spaced { text } >>
549
+ spaced { refer_part }.maybe >>
550
+ spaced("SYNTAX") >> syntax |
551
+ syntax
552
+ end
553
+
554
+ rule(:refer_part) do
555
+ spaced("REFERENCE") >> text
556
+ end
557
+
558
+ rule(:choice_clause) do
559
+ # Ignoring choice syntax
560
+ spaced { str("CHOICE").as(:type) } >> curly(match("[^\}]").repeat)
561
+ end
562
+
563
+ rule(:syntax) do
564
+ object_syntax | spaced("BITS").as(:type) >> curly(named_bits)
565
+ end
566
+
567
+ rule(:display_part) do
568
+ spaced("DISPLAY-HINT") >> text
569
+ end
570
+
571
+ rule(:named_bits) do
572
+ with_separator(named_bit, ",")
573
+ end
574
+
575
+ rule(:named_bit) do
576
+ spaced { lowercase_identifier } >> bracketed(number)
577
+ end
578
+
579
+ rule(:object_syntax) do
580
+ conceptual_table |
581
+ entry_type |
582
+ simple_syntax |
583
+ application_syntax |
584
+ type_tag >> simple_syntax |
585
+ row.as(:value)
586
+ end
587
+
588
+ rule(:simple_syntax) do
589
+ spaced { str("INTEGER").as(:type) } >> (integer_subtype | enum_spec).maybe |
590
+ spaced { str("Integer32").as(:type) >> space } >> integer_subtype.maybe |
591
+ spaced { str("OCTET STRING").as(:type) } >> octetstring_subtype.maybe |
592
+ spaced { str("OBJECT IDENTIFIER").as(:type) } >> any_subtype |
593
+ spaced { uppercase_identifier.as(:type) } >> (integer_subtype | enum_spec | octetstring_subtype)
594
+ end
595
+
596
+ rule(:application_syntax) do
597
+ spaced { str("IpAddress").as(:type) >> space } >> any_subtype |
598
+ spaced { str("NetworkAddress").as(:type) >> space } >> any_subtype |
599
+ spaced { str("Counter32").as(:type) >> space } >> integer_subtype.maybe |
600
+ spaced { str("Gauge32").as(:type) >> space } >> integer_subtype.maybe |
601
+ spaced { str("Unsigned32").as(:type) >> space } >> integer_subtype.maybe |
602
+ spaced { str("TimeTicks").as(:type) >> space } >> any_subtype |
603
+ spaced { str("Opaque").as(:type) >> space } >> octetstring_subtype.maybe |
604
+ spaced { str("Counter64").as(:type) >> space } >> integer_subtype.maybe
605
+ end
606
+
607
+ rule(:conceptual_table) do
608
+ spaced { str("SEQUENCE OF").as(:type) } >> row.as(:value)
609
+ end
610
+
611
+ rule(:entry_type) do
612
+ spaced { str("SEQUENCE").as(:type) } >> curly(sequence_items)
613
+ end
614
+
615
+ rule(:type_tag) do
616
+ spaced { square_bracketed(spaced("APPLICATION") >> number.as(:application_type)) } >> spaced("IMPLICIT") |
617
+ spaced { square_bracketed(spaced("UNIVERSAL") >> number.as(:universal_type)) } >> spaced("IMPLICIT")
618
+ end
619
+
620
+ rule(:sequence_items) do
621
+ with_separator(sequence_item, ",")
622
+ end
623
+
624
+ rule(:sequence_item) do
625
+ spaced { lowercase_identifier } >> spaced { sequence_syntax }
626
+ end
627
+
628
+ rule(:sequence_syntax) do
629
+ str("BITS") |
630
+ sequence_object_syntax |
631
+ spaced { uppercase_identifier } >> any_subtype
632
+ end
633
+
634
+ rule(:sequence_object_syntax) do
635
+ sequence_simple_syntax | sequence_application_syntax
636
+ end
637
+
638
+ rule(:sequence_simple_syntax) do
639
+ spaced("INTEGER") >> any_subtype |
640
+ spaced("Integer32") >> any_subtype |
641
+ spaced("OCTET STRING") >> any_subtype |
642
+ spaced("OBJECT IDENTIFIER") >> any_subtype
643
+ end
644
+
645
+ rule(:sequence_application_syntax) do
646
+ spaced { str("IpAddress") >> space } >> any_subtype |
647
+ spaced { str("COUNTER32") } >> any_subtype |
648
+ spaced { str("Gauge32") >> space } >> any_subtype |
649
+ spaced { str("Unsigned32") >> space } >> any_subtype |
650
+ spaced { str("TimeTicks") >> space } >> any_subtype |
651
+ str("Opaque") |
652
+ spaced { str("Counter64") >> space } >> any_subtype
653
+ end
654
+
655
+ rule(:row) { uppercase_identifier }
656
+
657
+ rule(:integer_subtype) { bracketed(ranges) }
658
+
659
+ rule(:octetstring_subtype) do
660
+ bracketed(spaced("SIZE") >> bracketed(ranges))
661
+ end
662
+
663
+ rule(:any_subtype) do
664
+ (integer_subtype | octetstring_subtype | enum_spec).maybe
665
+ end
666
+
667
+ rule(:enum_spec) { curly(enum_items) }
668
+
669
+ rule(:enum_items) do
670
+ with_separator(enum_item.as(:enum), ",")
671
+ end
672
+
673
+ rule(:enum_item) do
674
+ fuzzy_lowercase_identifier.as(:name) >> space.repeat >> bracketed(number.as(:value))
675
+ end
676
+
677
+ rule(:ranges) do
678
+ with_separator(range.as(:range), "|")
679
+ end
680
+
681
+ rule(:range) do
682
+ value.as(:min) >> space.repeat >> (str("..") >> space.repeat >> value.as(:max)).maybe
683
+ end
684
+
685
+ rule(:value) do
686
+ number | hexstring | binstring
687
+ end
688
+
689
+ rule(:status) { lowercase_identifier }
690
+
691
+ rule(:uppercase_identifier) do
692
+ match("[A-Z]") >> match("[A-Za-z0-9\-]").repeat
693
+ end
694
+
695
+ rule(:lowercase_identifier) do
696
+ match("[a-z]") >> match("[A-Za-z0-9\-]").repeat
697
+ end
698
+
699
+ rule(:type_smi_and_sppi) do
700
+ str("IpAddress") | str("TimeTicks") | str("Opaque") | str("Integer32") | str("Unsigned32")
701
+ end
702
+
703
+ rule(:type_smi_only) do
704
+ str("Counter") | str("Gauge32") | str("Counter64")
705
+ end
706
+
707
+ rule(:colon_colon_part) { spaced("::=") }
708
+
709
+ rule(:space_in_line) { match('[ \t]').repeat(1) }
710
+ rule(:cr) { match("\n") }
711
+ rule(:space) do
712
+ # this rule match all not important text
713
+ (match('[ \t\r\n]') | comment_line).repeat(1)
714
+ end
715
+
716
+ rule(:comment_line) do
717
+ (match('\-\-') >> match('[^\n]').repeat >> match('\n'))
718
+ end
719
+
720
+ rule(:space?) { space.maybe }
721
+ rule(:digit) { match["0-9"] }
722
+ rule(:hexchar) { match("[0-9a-fA-F]") }
723
+ rule(:empty) { str("") }
724
+ rule(:number) do
725
+ (
726
+ str("-").maybe >> (
727
+ str("0") | (match("[1-9]") >> digit.repeat)
728
+ ) >> (
729
+ str(".") >> digit.repeat(1)
730
+ ).maybe >> (
731
+ match("[eE]") >> (str("+") | str("-")).maybe >> digit.repeat(1)
732
+ ).maybe
733
+ ).repeat(1)
734
+ end
735
+
736
+ rule(:hexstring) do
737
+ str("'") >> hexchar.repeat >> str("'") >> match("[hH]")
738
+ end
739
+
740
+ rule(:binstring) do
741
+ str("'") >> match["0-1"].repeat >> str("'")
742
+ end
743
+
744
+ rule(:text) do
745
+ str('"') >> (
746
+ str('\\') >> any | str('"').absent? >> any
747
+ ).repeat >> str('"')
748
+ end
749
+ end
750
+ end