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 }