simple_nts_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rubocop.yml +24 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +57 -0
  8. data/Rakefile +10 -0
  9. data/exe/simple_nts_client +8 -0
  10. data/lib/nts/cli.rb +83 -0
  11. data/lib/nts/ntske/client.rb +100 -0
  12. data/lib/nts/ntske/message/aead_algorithm_negotiation.rb +47 -0
  13. data/lib/nts/ntske/message/cookie.rb +33 -0
  14. data/lib/nts/ntske/message/end_of_message.rb +30 -0
  15. data/lib/nts/ntske/message/error_record.rb +39 -0
  16. data/lib/nts/ntske/message/nts_next_protocol_negotiation.rb +35 -0
  17. data/lib/nts/ntske/message/ntsv4_port_negotiation.rb +36 -0
  18. data/lib/nts/ntske/message/ntsv4_server_negotiation.rb +36 -0
  19. data/lib/nts/ntske/message/warning_record.rb +34 -0
  20. data/lib/nts/ntske/message.rb +107 -0
  21. data/lib/nts/ntske.rb +4 -0
  22. data/lib/nts/sntp/client.rb +160 -0
  23. data/lib/nts/sntp/extension/nts_authenticator.rb +53 -0
  24. data/lib/nts/sntp/extension/nts_cookie.rb +34 -0
  25. data/lib/nts/sntp/extension/unique_identifier.rb +36 -0
  26. data/lib/nts/sntp/extension/unknown_extension.rb +36 -0
  27. data/lib/nts/sntp/extension.rb +38 -0
  28. data/lib/nts/sntp/message.rb +119 -0
  29. data/lib/nts/sntp.rb +4 -0
  30. data/lib/nts/version.rb +5 -0
  31. data/lib/nts.rb +12 -0
  32. data/simple_nts_client.gemspec +27 -0
  33. data/spec/aead_algorithm_negotiation_spec.rb +28 -0
  34. data/spec/cookie_spec.rb +36 -0
  35. data/spec/end_of_message_spec.rb +26 -0
  36. data/spec/error_record_spec.rb +28 -0
  37. data/spec/extension_spec.rb +28 -0
  38. data/spec/nts_authenticator_spec.rb +64 -0
  39. data/spec/nts_cookie_spec.rb +36 -0
  40. data/spec/nts_next_protocol_negotiation_spec.rb +28 -0
  41. data/spec/ntsv4_port_negotiation_spec.rb +28 -0
  42. data/spec/ntsv4_server_negotiation_spec.rb +28 -0
  43. data/spec/spec_helper.rb +11 -0
  44. data/spec/unique_identifier_spec.rb +36 -0
  45. data/spec/unknown_extension_spec.rb +46 -0
  46. data/spec/warning_record_spec.rb +28 -0
  47. metadata +145 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fc37d0334da875bdc71e5089fc001e0ce29772ab238db3ca93f750f25579d417
4
+ data.tar.gz: c432c7f707cdbe678df8181881900525d39fa6e502190924fb3b6025ad5ca63e
5
+ SHA512:
6
+ metadata.gz: 054d1484e8d6c17b1da9f183edd9c18c79fe83b86747555774a551b6791460a67c9c6aff5dff5ef5b9bda1a47e8613e58ceec5dd2ae3848253dc8718973bcbb2
7
+ data.tar.gz: ee2d152f81b731ab6e9213acb11b8e7af43366cb6565a3369000efbb8f8d005f2f46dbdde95708fa3c1b7e26917a94cc79eafb62834cbaab9042535645ef3503
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ /vendor/
3
+ /pkg/
4
+ /spec/reports/
5
+ /spec/examples.txt
6
+ /tmp/
7
+
8
+ .byebug_history
9
+ /.bundle/
10
+ /vendor/bundle
11
+ /lib/bundler/man/
12
+
13
+ Gemfile.lock
14
+ .ruby-version
15
+ .ruby-gemset
16
+ .rvmrc
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Style/NumericLiterals:
8
+ Enabled: false
9
+
10
+ Style/BlockDelimiters:
11
+ Enabled: false
12
+
13
+ Metrics/AbcSize:
14
+ Max: 30
15
+
16
+ Metrics/MethodLength:
17
+ Max: 30
18
+
19
+ Naming/UncommunicativeMethodParamName:
20
+ MinNameLength: 1
21
+
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - 'spec/*.rb'
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ sudo: false
2
+
3
+ language: ruby
4
+
5
+ rvm:
6
+ - 2.6.3
7
+ - ruby-head
8
+
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: ruby-head
12
+
13
+ before_install:
14
+ - gem install bundler
15
+ - bundle install
16
+
17
+ script: bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'miscreant'
6
+ gem 'rake'
7
+ gem 'tttls1.3', '>= 0.2.7'
8
+
9
+ group :test do
10
+ gem 'pry'
11
+ gem 'pry-byebug'
12
+ gem 'rspec', '3.8.0'
13
+ gem 'rubocop', '0.68.1'
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Tomoya Kuwayama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # simple_nts_client
2
+
3
+ [![Gem Version](https://badge.fury.io/simple_nts_client.svg)](https://badge.fury.io/rb/simple_nts_client)
4
+ [![Build Status](https://travis-ci.org/thekuwayama/simple_nts_client.svg?branch=master)](https://travis-ci.org/thekuwayama/simple_nts_client)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/7b34a4868f1e297af084/maintainability)](https://codeclimate.com/github/thekuwayama/simple_nts_client/maintainability)
6
+
7
+ simple\_nts\_client is CLI that is simple NTS(Network Time Security) Client implementation.
8
+ This CLI prints the now timestamp got with NTS.
9
+ Current implementation is based on:
10
+
11
+ * [draft-ietf-ntp-using-nts-for-ntp-20](https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20)
12
+
13
+
14
+ ## Installation
15
+
16
+ The gem is available at [rubygems.org](https://rubygems.org/gems/simple_nts_client). You can install it the following:
17
+
18
+ ```bash
19
+ $ gem install simple_nts_client
20
+ ```
21
+
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ $ simple_nts_client --help
27
+ Usage: simple_nts_client [options]
28
+ -s, --server VALUE NTS-KE server name (default time.cloudflare.com)
29
+ -p, --port VALUE NTS-KE port number (default 1234)
30
+ -v, --verbose verbose mode (default false)
31
+ ```
32
+
33
+ You can run it the following:
34
+
35
+ ```bash
36
+ $ simple_nts_client
37
+ 2019-07-20 06:00:00 +0900
38
+ ```
39
+
40
+ If you need to access other NTS-KE server or port, you can run it the following:
41
+
42
+ ```bash
43
+ $ simple_nts_client --server YOURSERVER --port YOURPORT
44
+ 2019-07-20 06:00:00 +0900
45
+ ```
46
+
47
+
48
+ ## How "simple" client?
49
+
50
+ * The CLI supports only `AEAD_AES_SIV_CMAC_256` as AEAD algorithms to protect the NTPv4 packet.
51
+ * The CLI sends the first one of the received cookies via the response of the New Cookie for NTPv4 record and discards cookies that didn't send.
52
+ * The CLI just prints the timestamp adjusted by calculated system clock offset.
53
+
54
+
55
+ ## License
56
+
57
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RuboCop::RakeTask.new
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task default: %i[rubocop spec]
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH << __dir__ + '/../lib'
5
+
6
+ require 'nts'
7
+
8
+ Nts::CLI.new.run
data/lib/nts/cli.rb ADDED
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Nts
6
+ class CLI
7
+ # rubocop: disable Metrics/MethodLength
8
+ def parse_options(argv = ARGV)
9
+ op = OptionParser.new
10
+
11
+ # default value
12
+ opts = {
13
+ server: 'time.cloudflare.com',
14
+ port: 1234,
15
+ verbose: false
16
+ }
17
+
18
+ op.on(
19
+ '-s',
20
+ '--server VALUE',
21
+ "NTS-KE server name (default #{opts[:server]})"
22
+ ) do |v|
23
+ opts[:server] = v
24
+ end
25
+
26
+ op.on(
27
+ '-p',
28
+ '--port VALUE',
29
+ "NTS-KE port number (default #{opts[:port]})"
30
+ ) do |v|
31
+ opts[:port] = v
32
+ end
33
+
34
+ op.on(
35
+ '-v',
36
+ '--verbose',
37
+ "verbose mode (default #{opts[:verbose]})"
38
+ ) do |v|
39
+ opts[:verbose] = v
40
+ end
41
+
42
+ begin
43
+ op.parse(argv)
44
+ rescue OptionParser::InvalidOption => e
45
+ puts op.to_s
46
+ puts "error: #{e.message}"
47
+ exit 1
48
+ end
49
+
50
+ opts
51
+ end
52
+ # rubocop: enable Metrics/MethodLength
53
+
54
+ def run
55
+ opts = parse_options
56
+
57
+ # NTS-KE
58
+ ntske_client = Ntske::Client.new(opts[:server], opts[:port])
59
+ hostname, ntp_port, cookies, c2s_key, s2c_key = ntske_client.key_establish
60
+ hostname ||= opts[:server]
61
+ ntp_port ||= 123
62
+
63
+ # verbose mode
64
+ if opts[:verbose]
65
+ puts "# NTPv4 Server => #{hostname}"
66
+ puts "# NTPv4 Port => #{ntp_port}"
67
+ puts "# Cookie for NTPv4(hex) => #{cookies.first.unpack1('H*')}"
68
+ puts "# client-to-server(hex) => #{c2s_key.unpack1('H*')}"
69
+ puts "# server-to-client(hex) => #{s2c_key.unpack1('H*')}"
70
+ end
71
+
72
+ # NTS protected NTP
73
+ ntp_client = Nts::Sntp::Client.new(
74
+ hostname,
75
+ ntp_port,
76
+ cookies.first,
77
+ c2s_key,
78
+ s2c_key
79
+ )
80
+ puts ntp_client.what_time
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module Nts
5
+ module Ntske
6
+ class Client
7
+ ALPN = 'ntske/1'
8
+ private_constant :ALPN
9
+
10
+ KE_LABEL = 'EXPORTER-network-time-security/1'
11
+ private_constant :KE_LABEL
12
+
13
+ # @param hostname [String]
14
+ # @param port [Integer]
15
+ def initialize(hostname, port)
16
+ @hostname = hostname
17
+ @port = port
18
+ end
19
+
20
+ # @return [String | nil] NTPv4 Server
21
+ # @return [Integer | nil] NTPv4 Port
22
+ # @return [Array of String] Cookies for NTPv4
23
+ # @return [String] C2S key
24
+ # @return [String] S2C key
25
+ # rubocop: disable Metrics/AbcSize
26
+ # rubocop: disable Metrics/CyclomaticComplexity
27
+ # rubocop: disable Metrics/MethodLength
28
+ # rubocop: disable Metrics/PerceivedComplexity
29
+ def key_establish
30
+ sock = TCPSocket.new(@hostname, @port)
31
+ client = TTTLS13::Client.new(sock, @hostname, alpn: [ALPN])
32
+ client.connect
33
+ req = [
34
+ NtsNextProtocolNegotiation.new,
35
+ AeadAlgorithmNegotiation.new([AeadAlgorithm::AEAD_AES_SIV_CMAC_256]),
36
+ EndOfMessage.new
37
+ ]
38
+ client.write(req.map(&:serialize))
39
+ res = []
40
+ buffer = ''
41
+ loop do
42
+ read, = IO.select([sock], nil, nil, 1)
43
+ if read.nil?
44
+ warn 'Timeout: receiving for NTS-KE messages'
45
+ exit 1
46
+ else
47
+ r, b = Ntske.response_deserialize(buffer + client.read)
48
+ res += r
49
+ buffer = b
50
+ end
51
+
52
+ # Error
53
+ er = res.find { |m| m.is_a?(ErrorRecord) }
54
+ raise "received Error(#{er.error_code.unpack1('n')})" unless er.nil?
55
+
56
+ # Warning
57
+ wr = res.find { |m| m.is_a?(WarningRecord) }
58
+ raise "received Warning(#{wr.warning_code})" unless wr.nil?
59
+
60
+ # End of Message
61
+ break if res.last&.is_a?(EndOfMessage) &&
62
+ res.count { |m| m.is_a?(EndOfMessage) } == 1 &&
63
+ buffer.empty?
64
+ end
65
+
66
+ # Next Protocol Negotiation
67
+ npn = res.select { |m| m.is_a?(NtsNextProtocolNegotiation) }
68
+ raise Exception if npn.nil? || npn.length != 1
69
+
70
+ # NTPv4 Server
71
+ server = res.find { |m| m.is_a?(Ntsv4ServerNegotiation) }&.server
72
+
73
+ # NTPv4 Port
74
+ port = res.find { |m| m.is_a?(Ntsv4PortNegotiation) }&.port
75
+
76
+ # Cookies for NTPv4
77
+ cookies = res.select { |m| m.is_a?(Cookie) }&.map(&:cookie)
78
+ raise Exception if cookies.empty?
79
+
80
+ # AEAD algorithm => C2S, S2C key
81
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-5.1
82
+ alg = res.find { |m| m.is_a?(AeadAlgorithmNegotiation) }&.algorithms
83
+ &.first
84
+ raise Exception if alg.nil?
85
+
86
+ key_len = 32 # only support AEAD_AES_SIV_CMAC_256
87
+ # NTPv4 context | AEAD Algorithm | C2S / S2C
88
+ # 0x00 0x00 | [refer IANA] | 0x00 / 0x01
89
+ c2s_key = client.exporter(KE_LABEL, "\x00\x00" + alg + "\x00", key_len)
90
+ s2c_key = client.exporter(KE_LABEL, "\x00\x00" + alg + "\x01", key_len)
91
+
92
+ [server, port, cookies, c2s_key, s2c_key]
93
+ end
94
+ # rubocop: enable Metrics/AbcSize
95
+ # rubocop: enable Metrics/CyclomaticComplexity
96
+ # rubocop: enable Metrics/MethodLength
97
+ # rubocop: enable Metrics/PerceivedComplexity
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module Nts
5
+ module Ntske
6
+ # https://www.iana.org/assignments/aead-parameters/aead-parameters.xhtml#aead-parameters-2
7
+ module AeadAlgorithm
8
+ AEAD_AES_SIV_CMAC_256 = "\x00\x0F"
9
+ end
10
+
11
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.5
12
+ class AeadAlgorithmNegotiation < Record
13
+ attr_reader :algorithms
14
+
15
+ # @param algorithms [Array of Nts::Ntske::AeadAlgorithm constants]
16
+ # @param c [Boolean]
17
+ #
18
+ # example:
19
+ # AeadAlgorithmNegotiation.new([
20
+ # AeadAlgorithm::AEAD_AES_SIV_CMAC_256
21
+ # ])
22
+ def initialize(algorithms, c = false)
23
+ super(c, RecordType::AEAD_ALGORITHM_NEGOTIATION)
24
+ @algorithms = algorithms
25
+ end
26
+
27
+ # @param s [String]
28
+ # @param c [Boolean]
29
+ #
30
+ # @raise [Exception]
31
+ #
32
+ # @return [Nts::Ntske::AeadAlgorithmNegotiation]
33
+ def self.deserialize(s, c)
34
+ raise Exception unless (s.length % 2).zero?
35
+
36
+ AeadAlgorithmNegotiation.new(s.scan(/.{2}/), c)
37
+ end
38
+
39
+ private
40
+
41
+ # @return [String]
42
+ def serialize_body
43
+ @algorithms.join
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nts
4
+ module Ntske
5
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.6
6
+ class Cookie < Record
7
+ attr_reader :cookie
8
+
9
+ # @param cookie [String]
10
+ # @param c [Boolean]
11
+ def initialize(cookie, c = false)
12
+ super(c, RecordType::NEW_COOKIE_FOR_NTPV4)
13
+
14
+ @cookie = cookie
15
+ end
16
+
17
+ # @param s [String]
18
+ # @param c [Boolean]
19
+ #
20
+ # @return [Nts::Ntske::Cookie]
21
+ def self.deserialize(s, c)
22
+ Cookie.new(s, c)
23
+ end
24
+
25
+ private
26
+
27
+ # @return [String]
28
+ def serialize_body
29
+ @cookie
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nts
4
+ module Ntske
5
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.1
6
+ class EndOfMessage < Record
7
+ def initialize
8
+ super(true, RecordType::END_OF_MESSAGE)
9
+ end
10
+
11
+ # @param s [String]
12
+ #
13
+ # @raise [Exception]
14
+ #
15
+ # @return [Nts::Ntske::EndOfMessage]
16
+ def self.deserialize(s)
17
+ raise Exception unless s.empty?
18
+
19
+ EndOfMessage.new
20
+ end
21
+
22
+ private
23
+
24
+ # @return [String]
25
+ def serialize_body
26
+ ''
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nts
4
+ module Ntske
5
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.3
6
+ module ErrorCode
7
+ UNRECOGNIZED_CRITICAL_RECORD = "\x00\x00"
8
+ BAD_REQUEST = "\x00\x01"
9
+ end
10
+
11
+ class ErrorRecord < Record
12
+ attr_reader :error_code
13
+
14
+ # @param error_code [Nts::Ntske::ErrorCode constant]
15
+ def initialize(error_code)
16
+ super(true, RecordType::ERROR)
17
+ @error_code = error_code
18
+ end
19
+
20
+ # @param s [String]
21
+ #
22
+ # @raise [Exception]
23
+ #
24
+ # @return [Nts::Ntske::ErrorRecord]
25
+ def self.deserialize(s)
26
+ raise Exception unless s.length == 2
27
+
28
+ ErrorRecord.new(s)
29
+ end
30
+
31
+ private
32
+
33
+ # @return [String]
34
+ def serialize_body
35
+ @error_code
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module Nts
5
+ module Ntske
6
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.2
7
+ class NtsNextProtocolNegotiation < Record
8
+ attr_reader :next_protocol
9
+
10
+ def initialize
11
+ super(false, RecordType::NTS_NEXT_PROTOCOL_NEGOTIATION)
12
+ @next_protocol = "\x00\x00" # Protocol ID 0 (NTPv4)
13
+ end
14
+
15
+ # @param s [String]
16
+ #
17
+ # @raise [Exception]
18
+ #
19
+ # @return [Nts::Ntske::NtsNextProtocolNegotiation]
20
+ def self.deserialize(s)
21
+ # only support NTPv4
22
+ raise Exception unless s == "\x00\x00"
23
+
24
+ NtsNextProtocolNegotiation.new
25
+ end
26
+
27
+ private
28
+
29
+ # @return [String]
30
+ def serialize_body
31
+ @next_protocol
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nts
4
+ module Ntske
5
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.8
6
+ class Ntsv4PortNegotiation < Record
7
+ attr_reader :port
8
+
9
+ # @param port [Integer]
10
+ # @param c [Boolean]
11
+ def initialize(port, c = false)
12
+ super(c, RecordType::NTPV4_PORT_NEGOTIATION)
13
+ @port = port
14
+ end
15
+
16
+ # @param s [String]
17
+ # @param c [Boolean]
18
+ #
19
+ # @raise [Exception]
20
+ #
21
+ # @return [Nts::Ntske::Ntsv4PortNegotiation]
22
+ def self.deserialize(s, c)
23
+ raise Exception unless s.length == 2
24
+
25
+ Ntsv4PortNegotiation.new(s.unpack1('n'), c)
26
+ end
27
+
28
+ private
29
+
30
+ # @return [String]
31
+ def serialize_body
32
+ [@port].pack('n1')
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nts
4
+ module Ntske
5
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.7
6
+ class Ntsv4ServerNegotiation < Record
7
+ attr_reader :server
8
+
9
+ # @param server [String]
10
+ # @param c [Boolean]
11
+ def initialize(server, c = false)
12
+ super(c, RecordType::NTPV4_SERVER_NEGOTIATION)
13
+ @server = server
14
+ end
15
+
16
+ # @param s [String]
17
+ # @param c [Boolean]
18
+ #
19
+ # @raise [Exception]
20
+ #
21
+ # @return [Nts::Ntske::Ntsv4ServerNegotiation]
22
+ def self.deserialize(s, c)
23
+ raise Exception if s.nil? || s.empty?
24
+
25
+ Ntsv4ServerNegotiation.new(s, c)
26
+ end
27
+
28
+ private
29
+
30
+ # @return [String]
31
+ def serialize_body
32
+ @server
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nts
4
+ module Ntske
5
+ # https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-20#section-4.1.4
6
+ class WarningRecord < Record
7
+ attr_reader :warning_code
8
+
9
+ # @param warning_code [Integer]
10
+ def initialize(warning_code)
11
+ super(true, RecordType::WARNING)
12
+ @warning_code = warning_code
13
+ end
14
+
15
+ # @param s [String]
16
+ #
17
+ # @raise [Exception]
18
+ #
19
+ # @return [Nts::Ntske:WarningRecord]
20
+ def self.deserialize(s)
21
+ raise Exception unless s.length == 2
22
+
23
+ WarningRecord.new(s.unpack1('n'))
24
+ end
25
+
26
+ private
27
+
28
+ # @return [String]
29
+ def serialize_body
30
+ [@warning_code].pack('n1')
31
+ end
32
+ end
33
+ end
34
+ end