Skip to content

Commit fbefbbb

Browse files
authored
Merge pull request #28 from n-rodriguez/feat/p5-idiomatic
P5a: idiomatic-Crystal cleanups
2 parents 5c94aea + ab5dfa9 commit fbefbbb

12 files changed

Lines changed: 101 additions & 114 deletions

File tree

spec/snmp_spec.cr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ describe SNMP do
274274
socket = UDPSocket.new
275275
socket.connect(TEST_SNMP_SERVER, 161)
276276
socket.sync = false
277-
socket.read_timeout = 3
277+
socket.read_timeout = 3.seconds
278278

279279
# Setup session
280280
session = SNMP::V3::Session.new("usr-md5-none", "authkey1")
@@ -301,7 +301,7 @@ describe SNMP do
301301
socket = UDPSocket.new
302302
socket.connect(TEST_SNMP_SERVER, 161)
303303
socket.sync = false
304-
socket.read_timeout = 3
304+
socket.read_timeout = 3.seconds
305305

306306
# Setup session
307307
session = SNMP::V3::Session.new("usr-md5-aes", "authkey1", "privkey1", priv_protocol: SNMP::V3::Security::PrivacyProtocol::AES)
@@ -328,7 +328,7 @@ describe SNMP do
328328
socket = UDPSocket.new
329329
socket.connect(TEST_SNMP_SERVER, 161)
330330
socket.sync = false
331-
socket.read_timeout = 3
331+
socket.read_timeout = 3.seconds
332332

333333
# Setup session
334334
session = SNMP::V3::Session.new("usr-md5-des", "authkey1", "privkey1")
@@ -353,7 +353,7 @@ describe SNMP do
353353
socket = UDPSocket.new
354354
socket.connect(TEST_SNMP_SERVER, 161)
355355
socket.sync = false
356-
socket.read_timeout = 3
356+
socket.read_timeout = 3.seconds
357357

358358
# Make request
359359
session = SNMP::Session.new
@@ -371,7 +371,7 @@ describe SNMP do
371371
# socket.connect("localhost", 32771)
372372
socket.connect(TEST_SNMP_SERVER, 161)
373373
socket.sync = false
374-
socket.read_timeout = 3
374+
socket.read_timeout = 3.seconds
375375

376376
# Make request
377377
session = SNMP::Session.new("public")

src/snmp.cr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ require "bindata/asn1"
33
class SNMP
44
alias UniversalTags = ASN1::BER::UniversalTags
55

6+
# Range for a randomly-generated PDU request-id / v3 message-id.
7+
REQUEST_ID_RANGE = 1..Int32::MAX
8+
69
module V3
710
# SNMPv3 message flags describing the features used
811
@[Flags]

src/snmp/client.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SNMP::Client
3232
private def build_socket : UDPSocket
3333
socket = UDPSocket.new
3434
socket.sync = false
35-
socket.read_timeout = timeout
35+
socket.read_timeout = timeout.seconds
3636
socket
3737
end
3838

src/snmp/message.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class SNMP::Message
2121
end
2222
end
2323

24-
def initialize(@community, @request, varbind : VarBind? | Array(VarBind) = nil, request_id = rand(2147483647), error_status = ErrorStatus::NoError, error_index = 0, @version = Version::V2C)
24+
def initialize(@community, @request, varbind : VarBind? | Array(VarBind) = nil, request_id = rand(REQUEST_ID_RANGE), error_status = ErrorStatus::NoError, error_index = 0, @version = Version::V2C)
2525
@pdu = PDU.new(request_id, varbind, error_status, error_index)
2626
end
2727

src/snmp/pdu.cr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ class SNMP::PDU
99
end
1010
end
1111

12-
def initialize(@request_id = rand(2147483647), @varbinds : Array(VarBind) = [] of VarBind, @error_status = ErrorStatus::NoError, @error_index = 0)
12+
def initialize(@request_id = rand(REQUEST_ID_RANGE), @varbinds : Array(VarBind) = [] of VarBind, @error_status = ErrorStatus::NoError, @error_index = 0)
1313
end
1414

15-
def initialize(@request_id = rand(2147483647), varbind : VarBind? = nil, @error_status = ErrorStatus::NoError, @error_index = 0)
15+
def initialize(@request_id = rand(REQUEST_ID_RANGE), varbind : VarBind? = nil, @error_status = ErrorStatus::NoError, @error_index = 0)
1616
if varbind
1717
@varbinds = [varbind]
1818
else
@@ -31,7 +31,7 @@ class SNMP::PDU
3131
property max_repetitions : Int32 = 0
3232

3333
def new_request_id
34-
@request_id = rand(2147483647)
34+
@request_id = rand(REQUEST_ID_RANGE)
3535
end
3636

3737
# shortcut for `.varbinds[0].oid`

src/snmp/session.cr

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class SNMP::Session
1010
end
1111

1212
def engine_validation_probe : V3::Message
13-
raise "engine probes are not required for SNMP V2C"
13+
raise SNMP::Error.new("engine probes are not required for SNMP V2C")
1414
end
1515

1616
def validate(message : V3::Message)
@@ -44,24 +44,24 @@ class SNMP::Session
4444
SNMP::Message.new(snmp)
4545
end
4646

47-
def get(oid, request_id = rand(2147483647))
47+
def get(oid, request_id = rand(REQUEST_ID_RANGE))
4848
SNMP::Message.new(@community, Request::Get, VarBind.new(oid), request_id)
4949
end
5050

5151
# Multi-varbind Get: one GetRequest carrying every OID (RFC 3416 allows a PDU
5252
# to bind several variables), answered by a single Response with N varbinds.
53-
def get(oids : Enumerable(String), request_id = rand(2147483647))
53+
def get(oids : Enumerable(String), request_id = rand(REQUEST_ID_RANGE))
5454
varbinds = oids.map { |oid| VarBind.new(oid) }.to_a
5555
SNMP::Message.new(@community, Request::Get, varbinds, request_id)
5656
end
5757

58-
def get_next(oid, request_id = rand(2147483647))
58+
def get_next(oid, request_id = rand(REQUEST_ID_RANGE))
5959
message = get(oid, request_id)
6060
message.request = Request::GetNext
6161
message
6262
end
6363

64-
def get_next(oids : Enumerable(String), request_id = rand(2147483647))
64+
def get_next(oids : Enumerable(String), request_id = rand(REQUEST_ID_RANGE))
6565
message = get(oids, request_id)
6666
message.request = Request::GetNext
6767
message
@@ -70,35 +70,35 @@ class SNMP::Session
7070
# GetBulk (RFC 3416): retrieve up to *max_repetitions* successors for each
7171
# repeating varbind in one round-trip. The first *non_repeaters* OIDs are
7272
# treated as plain GetNext, the rest as repeaters.
73-
def get_bulk(oids : Enumerable(String), non_repeaters = 0, max_repetitions = 10, request_id = rand(2147483647))
73+
def get_bulk(oids : Enumerable(String), non_repeaters = 0, max_repetitions = 10, request_id = rand(REQUEST_ID_RANGE))
7474
varbinds = oids.map { |oid| VarBind.new(oid) }.to_a
7575
message = SNMP::Message.new(@community, Request::GetBulk, varbinds, request_id)
7676
message.non_repeaters = non_repeaters
7777
message.max_repetitions = max_repetitions
7878
message
7979
end
8080

81-
def set(oid, value, request_id = rand(2147483647))
82-
SNMP::Message.new(@community, Request::Set, to_varbind(oid, value), request_id)
81+
def set(oid, value, request_id = rand(REQUEST_ID_RANGE))
82+
SNMP::Message.new(@community, Request::Set, VarBind.from_value(oid, value), request_id)
8383
end
8484

8585
# Standard first two varbinds of an SNMPv2 notification (RFC 3416 4.2.6).
8686
SYS_UPTIME_OID = "1.3.6.1.2.1.1.3.0"
8787
SNMP_TRAP_OID_OID = "1.3.6.1.6.3.1.1.4.1.0"
8888

8989
# Build an SNMPv2-Trap: sysUpTime.0 + snmpTrapOID.0 followed by *varbinds*.
90-
def trap_v2(oid, uptime = 0, varbinds : Array(VarBind) = [] of VarBind, request_id = rand(2147483647))
90+
def trap_v2(oid, uptime = 0, varbinds : Array(VarBind) = [] of VarBind, request_id = rand(REQUEST_ID_RANGE))
9191
SNMP::Message.new(@community, Request::V2_Trap, notification_varbinds(oid, uptime, varbinds), request_id)
9292
end
9393

9494
# Build an Inform (same shape as a v2 trap, but confirmed by the receiver).
95-
def inform(oid, uptime = 0, varbinds : Array(VarBind) = [] of VarBind, request_id = rand(2147483647))
95+
def inform(oid, uptime = 0, varbinds : Array(VarBind) = [] of VarBind, request_id = rand(REQUEST_ID_RANGE))
9696
SNMP::Message.new(@community, Request::Inform, notification_varbinds(oid, uptime, varbinds), request_id)
9797
end
9898

9999
# Build an RFC 1157 SNMPv1 Trap (its own wire structure). *enterprise* is the
100100
# enterprise OID, *agent_address* a dotted-quad IPv4 string.
101-
def trap_v1(enterprise, agent_address, generic_trap : GenericTrap, specific_trap = 0, uptime = 0, varbinds : Array(VarBind) = [] of VarBind, request_id = rand(2147483647))
101+
def trap_v1(enterprise, agent_address, generic_trap : GenericTrap, specific_trap = 0, uptime = 0, varbinds : Array(VarBind) = [] of VarBind, request_id = rand(REQUEST_ID_RANGE))
102102
pdu = V1Trap.new(agent_address, generic_trap, specific_trap.to_i32,
103103
oid: enterprise, time_ticks: uptime.to_u32, varbinds: varbinds, request_id: request_id)
104104
SNMP::Message.new(@community, Request::V1_Trap, pdu, version: Version::V1)
@@ -116,36 +116,8 @@ class SNMP::Session
116116

117117
# Multi-varbind Set: one SetRequest assigning every OID => value pair. The Hash
118118
# keeps insertion order, so the varbinds go out in the order they were given.
119-
def set(values : Hash(String, _), request_id = rand(2147483647))
120-
varbinds = values.map { |oid, value| to_varbind(oid, value) }
119+
def set(values : Hash(String, _), request_id = rand(REQUEST_ID_RANGE))
120+
varbinds = values.map { |oid, value| VarBind.from_value(oid, value) }
121121
SNMP::Message.new(@community, Request::Set, varbinds, request_id)
122122
end
123-
124-
# Encode a single OID => value assignment into a VarBind. Accepts the typed
125-
# SNMP values (`TypedValue`), the Crystal primitives, a raw `ASN1::BER`, or a
126-
# pre-built `VarBind`.
127-
private def to_varbind(oid, value) : VarBind
128-
data = value.is_a?(VarBind) ? value : VarBind.new(oid)
129-
130-
case value
131-
when TypedValue
132-
data.value = value.to_ber
133-
when String
134-
data.value.set_string(value)
135-
when Int
136-
data.value.set_integer(value)
137-
when Bool
138-
data.value.set_boolean(value)
139-
when Nil
140-
data.value.tag_number = UniversalTags::Null
141-
when ASN1::BER
142-
data.value = value
143-
when VarBind
144-
data.oid = oid
145-
else
146-
raise ArgumentError.new("unsupported varbind value. For complex values pass a pre-constructed `ASN1::BER`")
147-
end
148-
149-
data
150-
end
151123
end

src/snmp/v3/message.cr

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ class SNMP::V3::Message < SNMP::Message
5252
@pdu = @scoped_pdu.pdu
5353
end
5454

55-
def initialize(@scoped_pdu : ScopedPDU, @security_params : SecurityParams, security : Security? = nil, @security_model = SecurityModel::USM, @id = rand(2147483647))
55+
def initialize(@scoped_pdu : ScopedPDU, @security_params : SecurityParams, security : Security? = nil, @security_model = SecurityModel::USM, @id = rand(REQUEST_ID_RANGE))
5656
@version = Version::V3
57-
@max_size = 65507
57+
@max_size = MAX_MESSAGE_SIZE
5858
if security
5959
@flags = security.security_level | MessageFlags::Reportable
6060
else
@@ -116,11 +116,15 @@ class SNMP::V3::Message < SNMP::Message
116116

117117
def new_request_id
118118
@pdu.new_request_id
119-
@id = rand(2147483647)
119+
@id = rand(REQUEST_ID_RANGE)
120120
end
121121

122+
# Largest UDP payload (65535 - 20-byte IP - 8-byte UDP header) — advertised as
123+
# msgMaxSize, the biggest response this engine will accept.
124+
MAX_MESSAGE_SIZE = 65507
125+
122126
PRIVNONE = ASN1::BER.new.set_string("", tag: UniversalTags::OctetString)
123-
MSG_MAX_SIZE = ASN1::BER.new.set_integer(65507)
127+
MSG_MAX_SIZE = ASN1::BER.new.set_integer(MAX_MESSAGE_SIZE)
124128
MSG_VERSION = ASN1::BER.new.set_integer(Version::V3.to_i)
125129

126130
def verify(security, scoped_pdu = @scoped_pdu.to_ber)

src/snmp/v3/security.cr

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,20 +219,24 @@ class SNMP::V3::Security
219219
@digest.final
220220
end
221221

222+
# RFC 3414 A.2 expands the password to 2^20 octets, hashed in 64-byte chunks.
223+
PASSKEY_EXPANSION_SIZE = 1 << 20
224+
PASSKEY_CHUNK_SIZE = 64
225+
222226
def passkey(password)
223227
@digest.reset
224228

225-
# RFC 3414 A.2 expands the password (a sequence of octets) to 2^20 bytes by
226-
# cycling through it, then hashes the stream. Feed it in a single reused
227-
# 64-byte chunk indexed cyclically, instead of allocating two Strings per
228-
# iteration (the old rotated/buffer concats) × 16384 iterations.
229+
# Expand the password (a sequence of octets) to PASSKEY_EXPANSION_SIZE by
230+
# cycling through it, then hash the stream. Feed it in a single reused chunk
231+
# indexed cyclically, instead of allocating two Strings per iteration (the
232+
# old rotated/buffer concats) × 16384 iterations.
229233
bytes = password.to_slice
230234
length = bytes.size
231-
buffer = Bytes.new(64)
235+
buffer = Bytes.new(PASSKEY_CHUNK_SIZE)
232236
offset = 0
233237

234-
(1048576 // 64).times do
235-
64.times do |i|
238+
(PASSKEY_EXPANSION_SIZE // PASSKEY_CHUNK_SIZE).times do
239+
PASSKEY_CHUNK_SIZE.times do |i|
236240
buffer[i] = bytes[offset]
237241
offset += 1
238242
offset = 0 if offset == length

src/snmp/v3/security/aes.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class SNMP::V3::Security::AES
3636

3737
def decrypt(encrypted_data : Bytes, salt : Bytes, engine_boots, engine_time)
3838
# 3.3.2.1
39-
raise "invalid privacy salt received" unless (salt.size % 8).zero?
39+
raise SNMP::ParseError.new("invalid privacy salt received") unless (salt.size % 8).zero?
4040

4141
cipher = OpenSSL::Cipher.new(cipher_name)
4242
cipher.padding = false

src/snmp/v3/security/des.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ class SNMP::V3::Security::DES
3737
end
3838

3939
def decrypt(encrypted_data : Bytes, salt : Bytes, engine_boots = nil, engine_time = nil)
40-
raise "invalid priv salt received" unless (salt.size % 8).zero?
41-
raise "invalid encrypted PDU received" unless (encrypted_data.size % 8).zero?
40+
raise SNMP::ParseError.new("invalid priv salt received") unless (salt.size % 8).zero?
41+
raise SNMP::ParseError.new("invalid encrypted PDU received") unless (encrypted_data.size % 8).zero?
4242

4343
cipher = OpenSSL::Cipher.new("des-cbc")
4444
cipher.padding = false

0 commit comments

Comments
 (0)