tl1 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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +26 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +675 -0
- data/README.md +188 -0
- data/Rakefile +7 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/tl1.rb +11 -0
- data/lib/tl1/ast.rb +268 -0
- data/lib/tl1/command.rb +113 -0
- data/lib/tl1/input_format.rb +23 -0
- data/lib/tl1/output_format.rb +23 -0
- data/lib/tl1/platforms/bti.rb +129 -0
- data/lib/tl1/session.rb +86 -0
- data/lib/tl1/test_io.rb +31 -0
- data/lib/tl1/version.rb +4 -0
- data/tl1.gemspec +26 -0
- metadata +106 -0
data/lib/tl1/command.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'strscan'
|
3
|
+
|
4
|
+
module TL1
|
5
|
+
# A representation of a CLI interaction, including an input message format and
|
6
|
+
# an output message format.
|
7
|
+
class Command
|
8
|
+
attr_reader :input_format, :output_format
|
9
|
+
|
10
|
+
def initialize(input, output = nil)
|
11
|
+
@input_format = TL1::InputFormat.new(input)
|
12
|
+
@output_format = output && TL1::OutputFormat.new(output)
|
13
|
+
end
|
14
|
+
|
15
|
+
def input(**kwargs)
|
16
|
+
input_format.format(**kwargs) + ';'
|
17
|
+
end
|
18
|
+
|
19
|
+
def record_sources(output)
|
20
|
+
OutputScanner.new(output).records
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_output(output)
|
24
|
+
return output unless output_format
|
25
|
+
|
26
|
+
record_sources(output).map do |record_source|
|
27
|
+
output_format.parse(record_source)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# A helper class to extract records from output messages. Assumes records
|
32
|
+
# are double-quoted and separated by newlines.
|
33
|
+
class OutputScanner
|
34
|
+
attr_reader :message
|
35
|
+
|
36
|
+
def initialize(message)
|
37
|
+
@message = message
|
38
|
+
end
|
39
|
+
|
40
|
+
def records
|
41
|
+
records = []
|
42
|
+
scanner = StringScanner.new(message)
|
43
|
+
scan_begin(scanner)
|
44
|
+
loop do
|
45
|
+
record = scan_next_record(scanner)
|
46
|
+
break unless record
|
47
|
+
records << record
|
48
|
+
end
|
49
|
+
records
|
50
|
+
end
|
51
|
+
|
52
|
+
def scan_begin(scanner)
|
53
|
+
scanner.skip_until(/^M\s+\d+\s+COMPLD$/)
|
54
|
+
end
|
55
|
+
|
56
|
+
def scan_next_record(scanner)
|
57
|
+
scanner.skip(/\s*/)
|
58
|
+
char = scanner.getch
|
59
|
+
case char
|
60
|
+
when '>'
|
61
|
+
scan_begin(scanner)
|
62
|
+
scan_next_record(scanner)
|
63
|
+
when ';'
|
64
|
+
nil
|
65
|
+
when '"'
|
66
|
+
scan_record(scanner)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def scan_characters(scanner)
|
71
|
+
loop do
|
72
|
+
raise 'Unexpected end of message' if scanner.eos?
|
73
|
+
yield scanner.getch
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def scan_record(scanner)
|
78
|
+
record = String.new
|
79
|
+
|
80
|
+
scan_characters(scanner) do |char|
|
81
|
+
case char
|
82
|
+
when '\\'
|
83
|
+
next_char = scanner.getch
|
84
|
+
if next_char == '"'
|
85
|
+
record << "\"#{scan_record_quoted_string(scanner)}"
|
86
|
+
else
|
87
|
+
record << char << next_char
|
88
|
+
end
|
89
|
+
when '"'
|
90
|
+
return record
|
91
|
+
else
|
92
|
+
record << char
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def scan_record_quoted_string(scanner)
|
98
|
+
record = String.new
|
99
|
+
|
100
|
+
scan_characters(scanner) do |char|
|
101
|
+
case char
|
102
|
+
when '\\'
|
103
|
+
next_char = scanner.getch
|
104
|
+
return "#{record}\"" if next_char == '"'
|
105
|
+
record << char << next_char
|
106
|
+
else
|
107
|
+
record << char
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end # class Command
|
113
|
+
end # module TL1
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TL1
|
3
|
+
# A format for an input message.
|
4
|
+
class InputFormat
|
5
|
+
attr_reader :source
|
6
|
+
|
7
|
+
def initialize(source)
|
8
|
+
@source = source
|
9
|
+
end
|
10
|
+
|
11
|
+
def ast
|
12
|
+
@ast ||= AST.parse_message_format(source)
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json
|
16
|
+
ast.as_json
|
17
|
+
end
|
18
|
+
|
19
|
+
def format(**kwargs)
|
20
|
+
ast.format(**kwargs)
|
21
|
+
end
|
22
|
+
end # class OutputFormat
|
23
|
+
end # module TL1
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TL1
|
3
|
+
# A format for records appearing in output messages.
|
4
|
+
class OutputFormat
|
5
|
+
attr_reader :source
|
6
|
+
|
7
|
+
def initialize(source)
|
8
|
+
@source = source
|
9
|
+
end
|
10
|
+
|
11
|
+
def ast
|
12
|
+
@ast ||= AST.parse_message_format(source)
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json
|
16
|
+
ast.as_json
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(record_source)
|
20
|
+
ast.parse(record_source)
|
21
|
+
end
|
22
|
+
end # class OutputFormat
|
23
|
+
end # module TL1
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop: disable Metrics/ModuleLength
|
4
|
+
module TL1
|
5
|
+
module Platforms
|
6
|
+
module BTI
|
7
|
+
ACT_USER = Command.new('ACT-USER::<username>:::<password>')
|
8
|
+
CANC_USER = Command.new('CANC-USER::<username>')
|
9
|
+
|
10
|
+
RTRV_EQPT_ALL = Command.new(
|
11
|
+
'RTRV-EQPT',
|
12
|
+
'<aid>:<type>:ID=<id>,C1=<custom1>,C2=<custom2>,C3=<custom3>:pst,sst'
|
13
|
+
)
|
14
|
+
|
15
|
+
RTRV_INV_ALL = Command.new(
|
16
|
+
'RTRV-INV',
|
17
|
+
[
|
18
|
+
'<aid>,<aidtype>',
|
19
|
+
[
|
20
|
+
'NAME=<name>',
|
21
|
+
'PEC=<pec>',
|
22
|
+
'CLEI=<clei>',
|
23
|
+
'FNAME=<fname>',
|
24
|
+
'SER=<ser>',
|
25
|
+
'HWREV=<hwrev>',
|
26
|
+
'FW=<fw>',
|
27
|
+
'MFGDAT=<mfgdat>',
|
28
|
+
'MFGLOCN=<mfglocn>',
|
29
|
+
'TSTDAT=<tstdat>',
|
30
|
+
'TSTLOCN=<tstlocn>',
|
31
|
+
'WAVELENGTH=<wavelength>',
|
32
|
+
'WAVELENGTHMIN=<wavelengthmin>',
|
33
|
+
'WAVELENGTHMAX=<wavelengthmax>',
|
34
|
+
'WAVELENGTHSPACING=<wavelengthspacing>',
|
35
|
+
'REACH=<reach>',
|
36
|
+
'MINBR=<minbr>',
|
37
|
+
'MAXBR=<maxbr>',
|
38
|
+
'NOMBR=<nombr>',
|
39
|
+
'ENCODING=<encoding>',
|
40
|
+
'CONNTYPE=<conntype>',
|
41
|
+
'VENDORNAME=<vendorname>',
|
42
|
+
'VENDORPN=<vendorpn>',
|
43
|
+
'VENDOROUI=<vendoroui>',
|
44
|
+
'TXFAULTIMP=<txfaultimp>',
|
45
|
+
'TXDISABLEIMP=<txdisableimp>',
|
46
|
+
'LOSIMP=<losimp>',
|
47
|
+
'DDIAGIMP=<ddiagimp>',
|
48
|
+
'MEDIA=<media>',
|
49
|
+
'USI=<usi>',
|
50
|
+
'TEMPHT=<tempht>',
|
51
|
+
'TEMPHTS=<temphts>'
|
52
|
+
].join(',')
|
53
|
+
].join(':')
|
54
|
+
)
|
55
|
+
|
56
|
+
RTRV_ALM_ALL = Command.new(
|
57
|
+
'RTRV-ALM-ALL',
|
58
|
+
[
|
59
|
+
'<aid>,<aidtype>',
|
60
|
+
[
|
61
|
+
'<ntfcncde>',
|
62
|
+
'<condtype>',
|
63
|
+
'<srveff>',
|
64
|
+
'<ocrdat>',
|
65
|
+
'<ocrtim>',
|
66
|
+
'<locn>',
|
67
|
+
'<dirn>',
|
68
|
+
'<tmper>'
|
69
|
+
].join(','),
|
70
|
+
'<conddescr>,<aiddet>,<obsdbhvr>,<exptdbhvr>',
|
71
|
+
'<dgntype>,<tblislt>'
|
72
|
+
].join(':')
|
73
|
+
)
|
74
|
+
|
75
|
+
RTRV_CRS_WCH = Command.new(
|
76
|
+
'RTRV-CRS-WCH',
|
77
|
+
'<from_aid>,<to_aid>::SERVICENAME=<service_name>'
|
78
|
+
)
|
79
|
+
|
80
|
+
RTRV_CRS_XCVR = Command.new(
|
81
|
+
'RTRV-CRS-XCVR',
|
82
|
+
'<src_aid>,<dst_aid>:<ctype>'
|
83
|
+
)
|
84
|
+
|
85
|
+
RTRV_CONN_EQPT = Command.new(
|
86
|
+
'RTRV-CONN-EQPT',
|
87
|
+
'<fromAid>,<toAid>:<type>'
|
88
|
+
)
|
89
|
+
|
90
|
+
RTRV_WDM = Command.new(
|
91
|
+
'RTRV-WDM',
|
92
|
+
[
|
93
|
+
'<aid>',
|
94
|
+
'',
|
95
|
+
[
|
96
|
+
'<ID=<id>',
|
97
|
+
'C1=<custom1>',
|
98
|
+
'C2=<custom2>',
|
99
|
+
'C3=<custom3>',
|
100
|
+
'FIBER=<fiber>',
|
101
|
+
'SPANLEN=<spanlen>',
|
102
|
+
'SPANLOSSSPECMAX=<spanlossspecmax>',
|
103
|
+
'SPANLOSSRX-HT=<spanlossrx-ht>',
|
104
|
+
'NUMCHNLS=<numchnls>',
|
105
|
+
'AINSTMR=<ainstmr>',
|
106
|
+
'ACTAINSTMR=<actainstmr>'
|
107
|
+
].join(','),
|
108
|
+
'<pst>,<sst>'
|
109
|
+
].join(':')
|
110
|
+
)
|
111
|
+
|
112
|
+
RTRV_ROUTE_CONN = Command.new(
|
113
|
+
'RTRV-ROUTE-CONN',
|
114
|
+
[
|
115
|
+
'',
|
116
|
+
'<ipaddr>,<mask>,<nexthop>',
|
117
|
+
[
|
118
|
+
'COST=<cost>',
|
119
|
+
'ADMINDIST=<admindist>',
|
120
|
+
'TYPE=<type>',
|
121
|
+
'PROT=<prot>',
|
122
|
+
'AGE=<age>',
|
123
|
+
'PREFSTAT=<prefstat>'
|
124
|
+
].join(',')
|
125
|
+
].join(':')
|
126
|
+
)
|
127
|
+
end # module BTI
|
128
|
+
end # module Platforms
|
129
|
+
end # module TL1
|
data/lib/tl1/session.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TL1
|
3
|
+
# A wrapper around an IO-like object representing a connection to a
|
4
|
+
# TL1-capable network element.
|
5
|
+
class Session
|
6
|
+
# @param io [IO, Net::SSH::Telnet, Net::Telnet]
|
7
|
+
# An established connection to a TL1 server.
|
8
|
+
#
|
9
|
+
# The connection object must have a `#write` method, and one of two read
|
10
|
+
# methods: `#expect` or `#waitfor`. If you are using Net::Telnet or
|
11
|
+
# Net::SSH::Telnet, `#waitfor` will be used. Otherwise, you should make
|
12
|
+
# sure that your connection object has an `#expect` method that behaves
|
13
|
+
# like `IO#expect` from the standard library.
|
14
|
+
#
|
15
|
+
# @param timeout [Integer]
|
16
|
+
# How long to wait for responses, by default.
|
17
|
+
def initialize(io, timeout = 10)
|
18
|
+
@timeout = timeout
|
19
|
+
@io =
|
20
|
+
if io.respond_to?(:expect)
|
21
|
+
io
|
22
|
+
elsif io.respond_to?(:waitfor)
|
23
|
+
WaitforWrapper.new(io)
|
24
|
+
else
|
25
|
+
raise UnsupportedIOError,
|
26
|
+
"the given IO doesn't respond to expect or waitfor"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Execute a TL1::Command
|
31
|
+
#
|
32
|
+
# @param [TL1::Command]
|
33
|
+
# @return [TL1::AST::Node]
|
34
|
+
def cmd(command, **kwargs)
|
35
|
+
output = raw_cmd(command.input(**kwargs))
|
36
|
+
command.parse_output(output)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Receive data until the given pattern is matched.
|
40
|
+
#
|
41
|
+
# @param pattern [Regexp]
|
42
|
+
# @param timeout [Integer]
|
43
|
+
def expect(pattern, timeout = nil)
|
44
|
+
timeout ||= @timeout
|
45
|
+
@io.expect(pattern, timeout)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Send a string and receive a string back.
|
49
|
+
#
|
50
|
+
# @param message [String]
|
51
|
+
# @param timeout [Integer]
|
52
|
+
# @return [String]
|
53
|
+
def raw_cmd(message, timeout = nil)
|
54
|
+
write(message)
|
55
|
+
expect(COMPLD, timeout)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Send a string.
|
59
|
+
#
|
60
|
+
# @param message [String]
|
61
|
+
# @return [Boolean]
|
62
|
+
def write(message)
|
63
|
+
@io.write(message)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Wraps objects that support `#waitfor` but not `#expect`, such as
|
67
|
+
# Net::Telnet and Net::SSH::Telnet. It is used transparently by
|
68
|
+
# `TL1::Session#initialize` for those classes, so it shouldn't be necessary
|
69
|
+
# to use it directly. If you are defining a new class that responds to
|
70
|
+
# `#waitfor`, you can define your own `#expect` method instead of using
|
71
|
+
# this.
|
72
|
+
class WaitforWrapper
|
73
|
+
def initialize(io)
|
74
|
+
@io = io
|
75
|
+
end
|
76
|
+
|
77
|
+
def expect(pattern, timeout)
|
78
|
+
@io.waitfor('Match' => pattern, 'Timeout' => timeout)
|
79
|
+
end
|
80
|
+
|
81
|
+
def write(*args)
|
82
|
+
@io.write(*args)
|
83
|
+
end
|
84
|
+
end # class WaitforWrapper
|
85
|
+
end # class Session
|
86
|
+
end # module TL1
|
data/lib/tl1/test_io.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TL1
|
3
|
+
# A simple IO-like object that accepts a hash of string inputs and
|
4
|
+
# corresponding outputs, useful for testing.
|
5
|
+
class TestIO
|
6
|
+
def initialize(commands, first_output: '')
|
7
|
+
commands.each_pair do |input, output|
|
8
|
+
next if output =~ COMPLD
|
9
|
+
raise ArgumentError, "incomplete output for #{input}"
|
10
|
+
end
|
11
|
+
|
12
|
+
@commands = commands
|
13
|
+
@next_output = first_output
|
14
|
+
end
|
15
|
+
|
16
|
+
def expect(pattern, _timeout = nil)
|
17
|
+
unless pattern =~ @next_output
|
18
|
+
raise TimeoutError, 'pattern does not match the next output'
|
19
|
+
end
|
20
|
+
|
21
|
+
@next_output
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(message)
|
25
|
+
@next_output = @commands.fetch(message)
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
class TimeoutError < RuntimeError; end
|
30
|
+
end # class TestIO
|
31
|
+
end # module TL1
|
data/lib/tl1/version.rb
ADDED
data/tl1.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tl1/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'tl1'
|
8
|
+
spec.version = Tl1::VERSION
|
9
|
+
spec.authors = ['Ben Miller']
|
10
|
+
spec.email = ['bmiller@rackspace.com']
|
11
|
+
|
12
|
+
spec.summary = 'Define, send, and receive TL1 messages'
|
13
|
+
spec.homepage = 'https://github.com/bjmllr/tl1'
|
14
|
+
spec.license = 'GPL-3.0'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tl1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Miller
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- bmiller@rackspace.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- ".travis.yml"
|
65
|
+
- CODE_OF_CONDUCT.md
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- lib/tl1.rb
|
73
|
+
- lib/tl1/ast.rb
|
74
|
+
- lib/tl1/command.rb
|
75
|
+
- lib/tl1/input_format.rb
|
76
|
+
- lib/tl1/output_format.rb
|
77
|
+
- lib/tl1/platforms/bti.rb
|
78
|
+
- lib/tl1/session.rb
|
79
|
+
- lib/tl1/test_io.rb
|
80
|
+
- lib/tl1/version.rb
|
81
|
+
- tl1.gemspec
|
82
|
+
homepage: https://github.com/bjmllr/tl1
|
83
|
+
licenses:
|
84
|
+
- GPL-3.0
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.6.11
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Define, send, and receive TL1 messages
|
106
|
+
test_files: []
|