lib/stun/src/attribute.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const Type = enum(u16) {
4 mapped_address = 0x0001,
5 change_request = 0x0003,
6 username = 0x0006,
7 message_integrity = 0x0008,
8 error_code = 0x0009,
9 unknown_attributes = 0x000a,
10 realm = 0x0014,
11 nonce = 0x0015,
12 message_integrity_sha256 = 0x001c,
13 password_algorithm = 0x001d,
14 userhash = 0x001e,
15 xor_mapped_address = 0x0020,
16 response_port = 0x0027,
17 password_algorithms = 0x8002,
18 alternate_domain = 0x8003,
19 software = 0x8022,
20 alternate_server = 0x8023,
21 fingerprint = 0x8028,
22 response_origin = 0x802b,
23 other_address = 0x802c,
24 _,
25 };
26
27 pub const DecodeError = error{
28 InvalidAddressFamily,
29 InvalidAttributeLength,
30 InvalidErrorCode,
31 InvalidPasswordAlgorithms,
32 InvalidText,
33 };
34
35 pub const Ipv4Address = struct {
36 address: [4]u8,
37 port: u16,
38 };
39
40 pub const Ipv6Address = struct {
41 address: [16]u8,
42 port: u16,
43 };
44
45 pub const Address = union(enum) {
46 ipv4: Ipv4Address,
47 ipv6: Ipv6Address,
48 };
49
50 pub const ChangeRequest = struct {
51 change_ip: bool,
52 change_port: bool,
53 };
54
55 pub const ErrorCode = struct {
56 code: u10,
57 reason: []const u8,
58 };
59
60 pub const PasswordAlgorithm = enum(u16) {
61 md5 = 0x0001,
62 sha256 = 0x0002,
63 _,
64 };
65
66 pub const PasswordAlgorithmValue = struct {
67 algorithm: PasswordAlgorithm,
68 parameters: []const u8,
69 };
70
71 pub const PasswordAlgorithms = struct {
72 bytes: []const u8,
73
74 pub fn iterator(self: PasswordAlgorithms) PasswordAlgorithmIterator {
75 return .{ .bytes = self.bytes };
76 }
77 };
78
79 pub const PasswordAlgorithmIterator = struct {
80 bytes: []const u8,
81 index: usize = 0,
82
83 pub fn next(self: *PasswordAlgorithmIterator) DecodeError!?PasswordAlgorithmValue {
84 if (self.index == self.bytes.len) return null;
85 if (self.bytes.len - self.index < 4) return error.InvalidPasswordAlgorithms;
86 const header = self.bytes[self.index..][0..4];
87 const parameter_len = std.mem.readInt(u16, header[2..4], .big);
88 const value_end = std.math.add(usize, self.index + 4, parameter_len) catch
89 return error.InvalidPasswordAlgorithms;
90 if (value_end > self.bytes.len) return error.InvalidPasswordAlgorithms;
91 const next_index = std.math.add(usize, value_end, paddingLength(parameter_len)) catch
92 return error.InvalidPasswordAlgorithms;
93 if (next_index > self.bytes.len) return error.InvalidPasswordAlgorithms;
94 const value = PasswordAlgorithmValue{
95 .algorithm = @fromBackingInt(@intCast(std.mem.readInt(u16, header[0..2], .big))),
96 .parameters = self.bytes[self.index + 4 .. value_end],
97 };
98 self.index = next_index;
99 return value;
100 }
101 };
102
103 pub const UnknownAttributes = struct {
104 bytes: []const u8,
105
106 pub fn count(self: UnknownAttributes) usize {
107 return self.bytes.len / 2;
108 }
109
110 pub fn at(self: UnknownAttributes, index: usize) ?u16 {
111 if (index >= self.count()) return null;
112 return std.mem.readInt(u16, self.bytes[index * 2 ..][0..2], .big);
113 }
114 };
115
116 pub const Unknown = struct {
117 bytes: []const u8,
118 comprehension_required: bool,
119 };
120
121 pub const Value = union(enum) {
122 mapped_address: Address,
123 change_request: ChangeRequest,
124 username: []const u8,
125 message_integrity: []const u8,
126 error_code: ErrorCode,
127 unknown_attributes: UnknownAttributes,
128 realm: []const u8,
129 nonce: []const u8,
130 message_integrity_sha256: []const u8,
131 password_algorithm: PasswordAlgorithmValue,
132 userhash: []const u8,
133 xor_mapped_address: Address,
134 response_port: u16,
135 password_algorithms: PasswordAlgorithms,
136 alternate_domain: []const u8,
137 software: []const u8,
138 alternate_server: Address,
139 fingerprint: u32,
140 response_origin: Address,
141 other_address: Address,
142 unknown: Unknown,
143 };
144
145 pub const Attribute = struct {
146 type_code: u16,
147 value: Value,
148 raw_value: []const u8,
149 padding: []const u8,
150
151 pub fn comprehensionRequired(self: Attribute) bool {
152 return self.type_code < 0x8000;
153 }
154 };
155
156 pub fn paddingLength(value_len: usize) usize {
157 return (4 - (value_len & 3)) & 3;
158 }
159
160 pub fn decode(
161 type_code: u16,
162 raw_value: []const u8,
163 padding: []const u8,
164 cookie: u32,
165 transaction_id: [12]u8,
166 ) DecodeError!Attribute {
167 if (padding.len != paddingLength(raw_value.len)) return error.InvalidAttributeLength;
168 const value = try decodeValue(
169 @fromBackingInt(@intCast(type_code)),
170 raw_value,
171 cookie,
172 transaction_id,
173 );
174 return .{
175 .type_code = type_code,
176 .value = value,
177 .raw_value = raw_value,
178 .padding = padding,
179 };
180 }
181
182 fn decodeValue(
183 attribute_type: Type,
184 raw: []const u8,
185 cookie: u32,
186 transaction_id: [12]u8,
187 ) DecodeError!Value {
188 return switch (attribute_type) {
189 .mapped_address => .{ .mapped_address = try decodeAddress(raw, null, transaction_id) },
190 .change_request => .{ .change_request = try decodeChangeRequest(raw) },
191 .username => .{ .username = try decodeText(raw, 763) },
192 .message_integrity => .{ .message_integrity = try exact(raw, 20) },
193 .error_code => .{ .error_code = try decodeErrorCode(raw) },
194 .unknown_attributes => .{ .unknown_attributes = try decodeUnknownAttributes(raw) },
195 .realm => .{ .realm = try decodeText(raw, 763) },
196 .nonce => .{ .nonce = try bounded(raw, 763) },
197 .message_integrity_sha256 => .{
198 .message_integrity_sha256 = try decodeSha256Integrity(raw),
199 },
200 .password_algorithm => .{ .password_algorithm = try decodePasswordAlgorithm(raw) },
201 .userhash => .{ .userhash = try exact(raw, 32) },
202 .xor_mapped_address => .{
203 .xor_mapped_address = try decodeAddress(raw, cookie, transaction_id),
204 },
205 .response_port => .{ .response_port = try decodeResponsePort(raw) },
206 .password_algorithms => .{
207 .password_algorithms = try decodePasswordAlgorithms(raw),
208 },
209 .alternate_domain => .{ .alternate_domain = try decodeAscii(raw, 255) },
210 .software => .{ .software = try decodeText(raw, 763) },
211 .alternate_server => .{
212 .alternate_server = try decodeAddress(raw, null, transaction_id),
213 },
214 .fingerprint => .{ .fingerprint = try decodeFingerprint(raw) },
215 .response_origin => .{
216 .response_origin = try decodeAddress(raw, null, transaction_id),
217 },
218 .other_address => .{ .other_address = try decodeAddress(raw, null, transaction_id) },
219 _ => .{ .unknown = .{
220 .bytes = raw,
221 .comprehension_required = @backingInt(attribute_type) < 0x8000,
222 } },
223 };
224 }
225
226 fn bounded(raw: []const u8, maximum: usize) DecodeError![]const u8 {
227 if (raw.len > maximum) return error.InvalidAttributeLength;
228 return raw;
229 }
230
231 fn exact(raw: []const u8, length: usize) DecodeError![]const u8 {
232 if (raw.len != length) return error.InvalidAttributeLength;
233 return raw;
234 }
235
236 fn decodeText(raw: []const u8, maximum: usize) DecodeError![]const u8 {
237 _ = try bounded(raw, maximum);
238 if (!std.unicode.utf8ValidateSlice(raw)) return error.InvalidText;
239 return raw;
240 }
241
242 fn decodeAscii(raw: []const u8, maximum: usize) DecodeError![]const u8 {
243 _ = try bounded(raw, maximum);
244 for (0..maximum) |index| {
245 if (index == raw.len) break;
246 if (!std.ascii.isAscii(raw[index])) return error.InvalidText;
247 }
248 return raw;
249 }
250
251 fn decodeSha256Integrity(raw: []const u8) DecodeError![]const u8 {
252 if (raw.len < 16) return error.InvalidAttributeLength;
253 if (raw.len > 32) return error.InvalidAttributeLength;
254 if ((raw.len & 3) != 0) return error.InvalidAttributeLength;
255 return raw;
256 }
257
258 fn decodeChangeRequest(raw: []const u8) DecodeError!ChangeRequest {
259 _ = try exact(raw, 4);
260 const flags = std.mem.readInt(u32, raw[0..4], .big);
261 return .{
262 .change_ip = (flags & 0x04) != 0,
263 .change_port = (flags & 0x02) != 0,
264 };
265 }
266
267 fn decodeResponsePort(raw: []const u8) DecodeError!u16 {
268 _ = try exact(raw, 2);
269 return std.mem.readInt(u16, raw[0..2], .big);
270 }
271
272 fn decodeFingerprint(raw: []const u8) DecodeError!u32 {
273 _ = try exact(raw, 4);
274 return std.mem.readInt(u32, raw[0..4], .big);
275 }
276
277 fn decodeErrorCode(raw: []const u8) DecodeError!ErrorCode {
278 if (raw.len < 4) return error.InvalidAttributeLength;
279 const class = raw[2] & 0x07;
280 const number = raw[3];
281 if (class < 3 or class > 6) return error.InvalidErrorCode;
282 if (number > 99) return error.InvalidErrorCode;
283 const reason = try decodeText(raw[4..], 763);
284 return .{
285 .code = @as(u10, class) * 100 + @as(u10, number),
286 .reason = reason,
287 };
288 }
289
290 fn decodeUnknownAttributes(raw: []const u8) DecodeError!UnknownAttributes {
291 if ((raw.len & 1) != 0) return error.InvalidAttributeLength;
292 return .{ .bytes = raw };
293 }
294
295 fn decodePasswordAlgorithm(raw: []const u8) DecodeError!PasswordAlgorithmValue {
296 if (raw.len < 4) return error.InvalidPasswordAlgorithms;
297 const parameter_len = std.mem.readInt(u16, raw[2..4], .big);
298 if (parameter_len != raw.len - 4) return error.InvalidPasswordAlgorithms;
299 return .{
300 .algorithm = @fromBackingInt(@intCast(std.mem.readInt(u16, raw[0..2], .big))),
301 .parameters = raw[4..],
302 };
303 }
304
305 fn decodePasswordAlgorithms(raw: []const u8) DecodeError!PasswordAlgorithms {
306 var iterator = (PasswordAlgorithms{ .bytes = raw }).iterator();
307 const maximum_entries = 16_383;
308 for (0..maximum_entries) |_| {
309 if (try iterator.next() == null) return .{ .bytes = raw };
310 }
311 return error.InvalidPasswordAlgorithms;
312 }
313
314 fn decodeAddress(
315 raw: []const u8,
316 xor_cookie: ?u32,
317 transaction_id: [12]u8,
318 ) DecodeError!Address {
319 if (raw.len < 4) return error.InvalidAttributeLength;
320 const encoded_port = std.mem.readInt(u16, raw[2..4], .big);
321 const port = encoded_port ^ @as(u16, @truncate((xor_cookie orelse 0) >> 16));
322 return switch (raw[1]) {
323 0x01 => decodeIpv4(raw, port, xor_cookie),
324 0x02 => decodeIpv6(raw, port, xor_cookie, transaction_id),
325 else => error.InvalidAddressFamily,
326 };
327 }
328
329 fn decodeIpv4(raw: []const u8, port: u16, xor_cookie: ?u32) DecodeError!Address {
330 if (raw.len != 8) return error.InvalidAttributeLength;
331 var address: [4]u8 = raw[4..8].*;
332 if (xor_cookie) |cookie| {
333 var cookie_bytes: [4]u8 = undefined;
334 std.mem.writeInt(u32, &cookie_bytes, cookie, .big);
335 for (0..4) |index| address[index] ^= cookie_bytes[index];
336 }
337 return .{ .ipv4 = .{ .address = address, .port = port } };
338 }
339
340 fn decodeIpv6(
341 raw: []const u8,
342 port: u16,
343 xor_cookie: ?u32,
344 transaction_id: [12]u8,
345 ) DecodeError!Address {
346 if (raw.len != 20) return error.InvalidAttributeLength;
347 var address: [16]u8 = raw[4..20].*;
348 if (xor_cookie) |cookie| {
349 var cookie_bytes: [4]u8 = undefined;
350 std.mem.writeInt(u32, &cookie_bytes, cookie, .big);
351 for (0..16) |index| {
352 const mask = if (index < 4) cookie_bytes[index] else transaction_id[index - 4];
353 address[index] ^= mask;
354 }
355 }
356 return .{ .ipv6 = .{ .address = address, .port = port } };
357 }
358
359 pub fn encodeAddress(
360 output: *[20]u8,
361 address: Address,
362 xor_cookie: ?u32,
363 transaction_id: [12]u8,
364 ) u5 {
365 output[0] = 0;
366 var cookie_bytes: [4]u8 = undefined;
367 std.mem.writeInt(u32, &cookie_bytes, xor_cookie orelse 0, .big);
368 return switch (address) {
369 .ipv4 => |value| encodeIpv4(output, value, xor_cookie, cookie_bytes),
370 .ipv6 => |value| encodeIpv6(
371 output,
372 value,
373 xor_cookie,
374 cookie_bytes,
375 transaction_id,
376 ),
377 };
378 }
379
380 fn encodeIpv4(
381 output: *[20]u8,
382 value: Ipv4Address,
383 xor_cookie: ?u32,
384 cookie_bytes: [4]u8,
385 ) u5 {
386 output[1] = 0x01;
387 const port = value.port ^ @as(u16, @truncate((xor_cookie orelse 0) >> 16));
388 std.mem.writeInt(u16, output[2..4], port, .big);
389 for (0..4) |index| {
390 const mask = if (xor_cookie == null) 0 else cookie_bytes[index];
391 output[4 + index] = value.address[index] ^ mask;
392 }
393 return 8;
394 }
395
396 fn encodeIpv6(
397 output: *[20]u8,
398 value: Ipv6Address,
399 xor_cookie: ?u32,
400 cookie_bytes: [4]u8,
401 transaction_id: [12]u8,
402 ) u5 {
403 output[1] = 0x02;
404 const port = value.port ^ @as(u16, @truncate((xor_cookie orelse 0) >> 16));
405 std.mem.writeInt(u16, output[2..4], port, .big);
406 for (0..16) |index| {
407 const encoded_mask = if (index < 4) cookie_bytes[index] else transaction_id[index - 4];
408 const mask = if (xor_cookie == null) 0 else encoded_mask;
409 output[4 + index] = value.address[index] ^ mask;
410 }
411 return 20;
412 }