├── src ├── test │ ├── query.bin │ └── response.bin ├── main.zig ├── iterative.zig └── dns.zig ├── .gitmodules ├── LICENSE └── README.md /src/test/query.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantecatalfamo/zig-dns/HEAD/src/test/query.bin -------------------------------------------------------------------------------- /src/test/response.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantecatalfamo/zig-dns/HEAD/src/test/response.bin -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zig-network"] 2 | path = zig-network 3 | url = https://github.com/MasterQ32/zig-network 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dante Catalfamo 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Dante Catalfamo 2 | // SPDX-License-Identifier: MIT 3 | 4 | const std = @import("std"); 5 | const network = @import("network"); 6 | const dns = @import("dns.zig"); 7 | 8 | pub fn usage() noreturn { 9 | std.debug.print("Usage: zig-dns \n", .{}); 10 | std.os.exit(1); 11 | } 12 | 13 | pub fn main() anyerror!void { 14 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 15 | defer _ = gpa.deinit(); 16 | var allocator = gpa.allocator(); 17 | try network.init(); 18 | defer network.deinit(); 19 | 20 | var args = try std.process.argsWithAllocator(allocator); 21 | defer args.deinit(); 22 | 23 | _ = args.next(); 24 | const dns_server = args.next() orelse usage(); 25 | const domain = args.next() orelse usage(); 26 | const query_type = args.next() orelse usage(); 27 | 28 | const sock = try network.connectToHost(allocator, dns_server, 53, .udp); 29 | defer sock.close(); 30 | const writer = sock.writer(); 31 | 32 | const message = try dns.createQuery(allocator, domain, std.meta.stringToEnum(dns.QType, query_type) orelse usage()); 33 | defer message.deinit(); 34 | 35 | const message_bytes = try message.to_bytes(allocator); 36 | defer allocator.free(message_bytes); 37 | 38 | std.debug.print("Sending bytes: {any}\n", .{message_bytes}); 39 | std.debug.print("Query:\n {}", .{message}); 40 | 41 | try writer.writeAll(message_bytes); 42 | var recv = [_]u8{0} ** 1024; 43 | const recv_size = try sock.receive(&recv); 44 | const response_bytes = recv[0..recv_size]; 45 | 46 | std.debug.print("Recv: {any}\n", .{response_bytes}); 47 | const response = try dns.Message.from_bytes(allocator, response_bytes); 48 | defer response.deinit(); 49 | std.debug.print("Response:\n{any}\n", .{response}); 50 | } 51 | -------------------------------------------------------------------------------- /src/iterative.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const debug = std.debug; 4 | const testing = std.testing; 5 | const dns = @import("dns.zig"); 6 | const network = @import("network"); 7 | 8 | const root_server = "a.root-servers.net"; 9 | 10 | pub fn main() anyerror!void { 11 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 12 | defer _ = gpa.deinit(); 13 | var allocator = gpa.allocator(); 14 | try network.init(); 15 | defer network.deinit(); 16 | 17 | var args = try std.process.argsWithAllocator(allocator); 18 | defer args.deinit(); 19 | 20 | _ = args.next(); 21 | const domain = args.next() orelse { 22 | std.debug.print("usage: iterative \n", .{}); 23 | std.os.exit(1); 24 | }; 25 | const qtype = std.meta.stringToEnum(dns.QType, args.next() orelse "A") orelse .A; 26 | 27 | std.debug.print("querying: {s}\n", .{root_server}); 28 | var query = try makeNonRecursiveQuery(allocator, root_server, domain, qtype); 29 | 30 | var resolved = false; 31 | while (!resolved) { 32 | if (query.answers.len != 0) { 33 | resolved = true; 34 | } else if (query.authorities.len != 0) { 35 | switch (query.authorities[0].resource_data) { 36 | .ns => { 37 | const ns = try query.authorities[0].resource_data.ns.nsdname.to_string(allocator); 38 | defer allocator.free(ns); 39 | query.deinit(); 40 | std.debug.print("querying: {s}\n", .{ns}); 41 | query = try makeNonRecursiveQuery(allocator, ns, domain, qtype); 42 | }, 43 | else => { 44 | std.debug.print("No record found\n", .{}); 45 | query.deinit(); 46 | return; 47 | }, 48 | } 49 | } else { 50 | std.debug.print("No record found\n", .{}); 51 | query.deinit(); 52 | return; 53 | } 54 | } 55 | 56 | for (query.answers) |answer| { 57 | std.debug.print("{}\n", .{answer.resource_data}); 58 | } 59 | query.deinit(); 60 | } 61 | 62 | pub fn makeNonRecursiveQuery(allocator: mem.Allocator, name_server: []const u8, domain: []const u8, query_type: dns.QType) !dns.Message { 63 | const sock = try network.connectToHost(allocator, name_server, 53, .udp); 64 | defer sock.close(); 65 | const writer = sock.writer(); 66 | 67 | var message = try dns.createQuery(allocator, domain, query_type); 68 | defer message.deinit(); 69 | message.header.recursion_desired = false; 70 | 71 | const message_bytes = try message.to_bytes(allocator); 72 | defer allocator.free(message_bytes); 73 | 74 | try writer.writeAll(message_bytes); 75 | var recv = [_]u8{0} ** 2048; 76 | const recv_size = try sock.receive(&recv); 77 | const response_bytes = recv[0..recv_size]; 78 | 79 | const response = try dns.Message.from_bytes(allocator, response_bytes); 80 | 81 | return response; 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-dns 2 | 3 | Experimental DNS library implemented in zig. 4 | 5 | So far implements [RFC 1035](https://www.rfc-editor.org/rfc/rfc1035.html) plus some updates. 6 | 7 | The library itself has no dependencies, the CLI example uses [`zig-network`](https://github.com/MasterQ32/zig-network) to send and receive packets over the network. 8 | 9 | * Library: `src/dns.zig` 10 | * CLI test: `src/main.zig` 11 | 12 | ### Features 13 | * Streaming interface 14 | * Parse DNS packets 15 | * Generate DNS packets 16 | * Print packet contents 17 | * Label compression 18 | 19 | ### Currently supported record types 20 | 21 | * A - A host address 22 | * NS - An authoritative name server 23 | * MD - A mail destination (Obsolete) 24 | * MF - A mail forwarder (Obsolete) 25 | * CNAME - The canonical name for an alias 26 | * SOA - Marks the start of a zone of authority 27 | * MB - A mailbox domain name (Experimental) 28 | * MG - A mail group member (Experimental) 29 | * MR - A mail rename domain name (Experimental) 30 | * NULL - A byte array (Experimental) 31 | * WKS - A well known service description 32 | * PTR - A domain name pointer 33 | * HINFO - Host information 34 | * MINFO - Mailbox or mail list information 35 | * MX - Mail exchange 36 | * TXT - Text strings 37 | * RP - Responsible Person [RFC 1183](https://www.rfc-editor.org/rfc/rfc1183) 38 | * AAAA - An IPv6 host address [RFC 3596](https://www.rfc-editor.org/rfc/rfc3596) 39 | * LOC - Location information [RFC 1876](https://datatracker.ietf.org/doc/html/rfc1876) 40 | * SRV - Service locator [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782) 41 | * SSHFP - SSH Fingerprint [RFC 4255](https://www.rfc-editor.org/rfc/rfc4255), [RFC 6594](https://www.rfc-editor.org/rfc/rfc6594) 42 | * URI - Uniform Resource Identifier [RFC 7553](https://www.rfc-editor.org/rfc/rfc7553.html) 43 | 44 | ### Interactive 45 | 46 | For testing and development purposes you can call the library interactively from the command line. 47 | 48 | ``` 49 | Usage: zig-dns 50 | ``` 51 | 52 | ### Interactive Example 53 | 54 | ``` 55 | $ zig-dns 1.1.1.1 www.lambda.cx A 56 | Sending bytes: { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 119, 119, 119, 6, 108, 97, 109, 98, 100, 97, 2, 99, 120, 0, 0, 1, 0, 1 } 57 | Query: 58 | Message { 59 | Header { 60 | ID: 1 61 | Response: false 62 | OpCode: query 63 | Authoritative Answer: false 64 | Truncation: false 65 | Recursion Desired: true 66 | Recursion Available: false 67 | Z: 0 68 | Response Code: no_error 69 | } 70 | Questions { 71 | Question { 72 | Name: www.lambda.cx. 73 | QType: A 74 | QClass: IN 75 | } 76 | } 77 | Ansewrs { 78 | } 79 | Authorities { 80 | } 81 | Additional { 82 | } 83 | } 84 | Recv: { 0, 1, 129, 128, 0, 1, 0, 2, 0, 0, 0, 0, 3, 119, 119, 119, 6, 108, 97, 109, 98, 100, 97, 2, 99, 120, 0, 0, 1, 0, 1, 192, 12, 0, 5, 0, 1, 0, 0, 7, 8, 0, 2, 192, 16, 192, 16, 0, 1, 0, 1, 0, 0, 7, 8, 0, 4, 155, 138, 137, 134 } 85 | Response: 86 | Message { 87 | Header { 88 | ID: 1 89 | Response: true 90 | OpCode: query 91 | Authoritative Answer: false 92 | Truncation: false 93 | Recursion Desired: true 94 | Recursion Available: true 95 | Z: 0 96 | Response Code: no_error 97 | } 98 | Questions { 99 | Question { 100 | Name: www.lambda.cx. 101 | QType: A 102 | QClass: IN 103 | } 104 | } 105 | Ansewrs { 106 | Resource Record { 107 | Name: www.lambda.cx. 108 | Type: CNAME 109 | Class: IN 110 | TTL: 1800 111 | Resource Data Length: 2 112 | Resource Data: lambda.cx. 113 | } 114 | Resource Record { 115 | Name: lambda.cx. 116 | Type: A 117 | Class: IN 118 | TTL: 1800 119 | Resource Data Length: 4 120 | Resource Data: 155.138.137.134 121 | } 122 | } 123 | Authorities { 124 | } 125 | Additional { 126 | } 127 | } 128 | ``` 129 | 130 | ### Example Code 131 | 132 | ```zig 133 | const std = @import("std"); 134 | const io = std.io; 135 | const network = @import("network"); 136 | const dns = @import("zig-dns/src/dns.zig"); 137 | 138 | // [...] Main function, allocator, etc. 139 | 140 | try network.init(); 141 | defer network.deinit(); 142 | const sock = try network.connectToHost(allocator, "8.8.8.8", 53, .udp); 143 | defer sock.close(); 144 | const writer = sock.writer(); 145 | 146 | const message = try dns.createQuery(allocator, "lambda.cx", .A); 147 | defer message.deinit(); 148 | 149 | var message_bytes = try message.to_bytes(); 150 | try writer.writeAll(message_bytes); 151 | 152 | var recv = [_]u8{0} ** 1024; 153 | const recv_size = try sock.receive(&recv); 154 | 155 | const response = try dns.Message.from_bytes(allocator, recv[0..recv_size]); 156 | defer response.deinit(); 157 | 158 | std.debug.print("Response:\n{any}\n", .{ response }); 159 | ``` 160 | 161 | Output: 162 | 163 | ``` 164 | Response: 165 | Message { 166 | Header { 167 | ID: 1 168 | Response: true 169 | OpCode: query 170 | Authoritative Answer: false 171 | Truncation: false 172 | Recursion Desired: true 173 | Recursion Available: true 174 | Z: 0 175 | Response Code: no_error 176 | } 177 | Questions { 178 | Question { 179 | Name: www.lambda.cx. 180 | QType: A 181 | QClass: IN 182 | } 183 | } 184 | Ansewrs { 185 | Resource Record { 186 | Name: www.lambda.cx. 187 | Type: CNAME 188 | Class: IN 189 | TTL: 1800 190 | Resource Data Length: 2 191 | Resource Data: lambda.cx. 192 | } 193 | Resource Record { 194 | Name: lambda.cx. 195 | Type: A 196 | Class: IN 197 | TTL: 1800 198 | Resource Data Length: 4 199 | Resource Data: 155.138.137.134 200 | } 201 | } 202 | Authorities { 203 | } 204 | Additional { 205 | } 206 | } 207 | ``` 208 | 209 | ### Iterative Example 210 | See [iterative.zig](src/iterative.zig) as an example for how to use this library iteratively. 211 | -------------------------------------------------------------------------------- /src/dns.zig: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Dante Catalfamo 2 | // SPDX-License-Identifier: MIT 3 | 4 | const std = @import("std"); 5 | const mem = std.mem; 6 | const io = std.io; 7 | const testing = std.testing; 8 | const builtin = @import("builtin"); 9 | 10 | const StrList = std.ArrayList([]const u8); 11 | const LabelList = std.ArrayList(DomainName.Label); 12 | const QuestionList = std.ArrayList(Question); 13 | const ResourceRecordList = std.ArrayList(ResourceRecord); 14 | 15 | /// Creates a DNS query message with common defaults. 16 | pub fn createQuery(allocator: mem.Allocator, address: []const u8, qtype: QType) !Message { 17 | const header = Header{ 18 | .id = 1, 19 | .response = false, 20 | .opcode = .query, 21 | .authoritative_answer = false, 22 | .truncation = false, 23 | .recursion_desired = true, 24 | .recursion_available = false, 25 | .z = 0, 26 | .response_code = .no_error, 27 | .question_count = 1, 28 | .answer_count = 0, 29 | .name_server_count = 0, 30 | .additional_record_count = 0, 31 | }; 32 | 33 | const domain = try DomainName.from_string(allocator, address); 34 | 35 | const question = Question{ 36 | .qname = domain, 37 | .qtype = qtype, 38 | .qclass = @enumFromInt(@intFromEnum(Class.IN)), 39 | }; 40 | var questions = try allocator.alloc(Question, 1); 41 | questions[0] = question; 42 | 43 | const message = Message{ 44 | .allocator = allocator, 45 | .header = header, 46 | .questions = questions, 47 | .answers = &.{}, 48 | .authorities = &.{}, 49 | .additional = &.{} 50 | }; 51 | 52 | return message; 53 | } 54 | 55 | /// DNS Message. All communications inside of the domain protocol are 56 | /// carried in a single format called a message. 57 | pub const Message = struct { 58 | allocator: mem.Allocator, 59 | /// Contains information about the message. The header section is 60 | /// always present 61 | header: Header, 62 | /// The question(s) being asked in the query. This section usually 63 | /// contains one question. 64 | questions: []const Question, 65 | /// ResourceRecords answering the question. 66 | answers: []const ResourceRecord, 67 | /// ResourceRecords pointing toward an authority. 68 | authorities: []const ResourceRecord, 69 | /// ResourceRecords holding additional information. 70 | additional: []const ResourceRecord, 71 | 72 | pub fn to_writer(self: *const Message, writer: anytype) !void { 73 | try writer.writeAll(&self.header.to_bytes()); 74 | for (self.questions) |question| { 75 | try question.to_writer(writer); 76 | } 77 | for (self.answers) |answer| { 78 | try answer.to_writer(writer); 79 | } 80 | for (self.authorities) |authority| { 81 | try authority.to_writer(writer); 82 | } 83 | for (self.additional) |addition| { 84 | try addition.to_writer(writer); 85 | } 86 | } 87 | 88 | /// Not decompressed 89 | pub fn from_reader(allocator: mem.Allocator, reader: anytype) !Message { 90 | const header = try Header.from_reader(reader); 91 | 92 | var questions = QuestionList.init(allocator); 93 | errdefer listDeinit(questions); 94 | 95 | var q_idx: usize = 0; 96 | while (q_idx < header.question_count) : (q_idx += 1) { 97 | const question = try Question.from_reader(allocator, reader); 98 | try questions.append(question); 99 | } 100 | 101 | var answers = ResourceRecordList.init(allocator); 102 | errdefer listDeinit(answers); 103 | 104 | var ans_idx: usize = 0; 105 | while (ans_idx < header.answer_count) : (ans_idx += 1) { 106 | const answer = try ResourceRecord.from_reader(allocator, reader); 107 | try answers.append(answer); 108 | } 109 | 110 | var authorities = ResourceRecordList.init(allocator); 111 | errdefer listDeinit(authorities); 112 | 113 | var auth_idx: usize = 0; 114 | while (auth_idx < header.name_server_count) : (auth_idx += 1) { 115 | const authority = try ResourceRecord.from_reader(allocator, reader); 116 | try authorities.append(authority); 117 | } 118 | 119 | var additional = ResourceRecordList.init(allocator); 120 | errdefer listDeinit(additional); 121 | 122 | var add_idx: usize = 0; 123 | while (add_idx < header.additional_record_count) : (add_idx += 1) { 124 | const addit = try ResourceRecord.from_reader(allocator, reader); 125 | try additional.append(addit); 126 | } 127 | 128 | return Message{ 129 | .allocator = allocator, 130 | .header = header, 131 | .questions = try questions.toOwnedSlice(), 132 | .answers = try answers.toOwnedSlice(), 133 | .authorities = try authorities.toOwnedSlice(), 134 | .additional = try additional.toOwnedSlice(), 135 | }; 136 | } 137 | 138 | /// Automatically decompressed 139 | pub fn from_bytes(allocator: mem.Allocator, bytes: []const u8) !Message { 140 | var buffer = io.fixedBufferStream(bytes); 141 | const reader = buffer.reader(); 142 | const message = try Message.from_reader(allocator, reader); 143 | defer message.deinit(); 144 | const decompressed = try message.decompress(allocator, bytes); 145 | return decompressed; 146 | } 147 | 148 | pub fn to_bytes(self: *const Message, allocator: mem.Allocator) ![]const u8 { 149 | var bytes = std.ArrayList(u8).init(allocator); 150 | const writer = bytes.writer(); 151 | try self.to_writer(writer); 152 | return bytes.toOwnedSlice(); 153 | } 154 | 155 | pub fn deinit(self: *const Message) void { 156 | for (self.questions) |question| { 157 | question.deinit(); 158 | } 159 | self.allocator.free(self.questions); 160 | for (self.answers) |answer| { 161 | answer.deinit(); 162 | } 163 | self.allocator.free(self.answers); 164 | for (self.authorities) |authority| { 165 | authority.deinit(); 166 | } 167 | self.allocator.free(self.authorities); 168 | for (self.additional) |addit| { 169 | addit.deinit(); 170 | } 171 | self.allocator.free(self.additional); 172 | } 173 | 174 | pub fn format(self: *const Message, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 175 | _ = fmt; 176 | _ = options; 177 | try writer.print("Message {{\n", .{}); 178 | try writer.print("{any}", .{self.header}); 179 | try writer.print(" Questions {{\n", .{}); 180 | for (self.questions) |question| { 181 | try writer.print("{any}", .{question}); 182 | } 183 | try writer.print(" }}\n", .{}); 184 | try writer.print(" Answers {{\n", .{}); 185 | for (self.answers) |answer| { 186 | try writer.print("{any}", .{answer}); 187 | } 188 | try writer.print(" }}\n", .{}); 189 | try writer.print(" Authorities {{\n", .{}); 190 | for (self.authorities) |authority| { 191 | try writer.print("{any}", .{authority}); 192 | } 193 | try writer.print(" }}\n", .{}); 194 | try writer.print(" Additional {{\n", .{}); 195 | for (self.additional) |addition| { 196 | try writer.print("{any}", .{addition}); 197 | } 198 | try writer.print(" }}\n", .{}); 199 | try writer.print("}}\n", .{}); 200 | } 201 | 202 | /// Creates a deep copy of the original message, with all message 203 | /// pointers resolved 204 | pub fn decompress(self: *const Message, allocator: mem.Allocator, packet: []const u8) !Message { 205 | var questions = QuestionList.init(allocator); 206 | errdefer listDeinit(questions); 207 | var answers = ResourceRecordList.init(allocator); 208 | errdefer listDeinit(answers); 209 | var authorities = ResourceRecordList.init(allocator); 210 | errdefer listDeinit(authorities); 211 | var additional = ResourceRecordList.init(allocator); 212 | errdefer listDeinit(additional); 213 | 214 | for (self.questions) |question| { 215 | const new_question = try question.decompress(allocator, packet); 216 | errdefer new_question.deinit(); 217 | try questions.append(new_question); 218 | } 219 | 220 | for (self.answers) |answer| { 221 | const new_answer = try answer.decompress(allocator, packet); 222 | errdefer new_answer.deinit(); 223 | try answers.append(new_answer); 224 | } 225 | 226 | for (self.authorities) |authority| { 227 | const new_authority = try authority.decompress(allocator, packet); 228 | errdefer new_authority.deinit(); 229 | try authorities.append(new_authority); 230 | } 231 | 232 | for (self.additional) |addition| { 233 | const new_addition = try addition.decompress(allocator, packet); 234 | errdefer new_addition.deinit(); 235 | try additional.append(new_addition); 236 | } 237 | 238 | const message = Message{ 239 | .allocator = self.allocator, 240 | .header = self.header, 241 | .questions = try questions.toOwnedSlice(), 242 | .answers = try answers.toOwnedSlice(), 243 | .authorities = try authorities.toOwnedSlice(), 244 | .additional = try additional.toOwnedSlice(), 245 | }; 246 | 247 | return message; 248 | } 249 | }; 250 | 251 | /// DNS message header. Contains information about the message. 252 | pub const Header = packed struct(u96) { 253 | /// An identifier assigned by the program that generates any kind 254 | /// of query. This identifier is copied the corresponding reply 255 | /// and can be used by the requester to match up replies to 256 | /// outstanding queries. 257 | id: u16, 258 | 259 | // Flags section. Fields are ordered this way because zig has 260 | // little endian bit order for bit fields. 261 | 262 | // Byte one 263 | 264 | /// Directs the name server to pursue the query recursively. 265 | /// Recursive query support is optional. 266 | recursion_desired: bool, 267 | /// If the message was truncated 268 | truncation: bool, 269 | /// The responding name server is an authority for the domain name 270 | /// in question section. 271 | authoritative_answer: bool, 272 | /// Kind of query in this message. This value is set by the 273 | /// originator of a query and copied into the response. 274 | opcode: Opcode, 275 | /// Specifies whether this message is a query (false), or a 276 | /// response (true). 277 | response: bool, 278 | 279 | // Byte two 280 | 281 | /// Set as part of responses. 282 | response_code: ResponseCode, 283 | /// Reserved. Must be zero 284 | z: u3 = 0, 285 | /// Set or cleared in a response, and denotes whether recursive 286 | /// query support is available in the name server. 287 | recursion_available: bool, 288 | 289 | // End of flag section. 290 | 291 | /// The number of entries in the question section. 292 | question_count: u16, 293 | /// The number of resource records in the answer section. 294 | answer_count: u16, 295 | /// The number of name server resource records in the authority 296 | /// records section. 297 | name_server_count: u16, 298 | /// The number of resource records in the additional records 299 | /// section. 300 | additional_record_count: u16, 301 | 302 | pub const Opcode = enum(u4) { 303 | query = 0, 304 | inverse_query = 1, 305 | status_request = 2, 306 | _, 307 | }; 308 | 309 | pub const ResponseCode = enum(u4) { 310 | no_error = 0, 311 | /// The name server was unable to interpret the query. 312 | format_error = 1, 313 | /// The name server was unable to process this query due to a 314 | /// problem with the name server. 315 | server_failure = 2, 316 | /// Meaningful only for responses from an authoritative name 317 | /// server, this code signifies that the domain name 318 | /// referenced in the query does not exist. 319 | name_error = 3, 320 | /// The name server does not support the requested kind of 321 | /// query. 322 | not_implemented = 4, 323 | /// The name server refuses to perform the specified operation 324 | /// for policy reasons. 325 | refused = 5, 326 | _, 327 | }; 328 | 329 | pub fn from_reader(reader: anytype) !Header { 330 | var bytes = [_]u8{0} ** 12; 331 | const bytes_read = try reader.readAll(&bytes); 332 | if (bytes_read < 12) { 333 | return error.NotEnoughBytes; 334 | } 335 | var header: Header = @bitCast(bytes); 336 | if (builtin.cpu.arch.endian() == .big) { 337 | return header; 338 | } 339 | header.id = @byteSwap(header.id); 340 | header.question_count = @byteSwap(header.question_count); 341 | header.answer_count = @byteSwap(header.answer_count); 342 | header.name_server_count = @byteSwap(header.name_server_count); 343 | header.additional_record_count = @byteSwap(header.additional_record_count); 344 | return header; 345 | } 346 | 347 | pub fn to_bytes(self: *const Header) [12]u8 { 348 | var header = self.*; 349 | if (builtin.cpu.arch.endian() == .big) { 350 | return @bitCast(header); 351 | } 352 | header.id = @byteSwap(header.id); 353 | header.question_count = @byteSwap(header.question_count); 354 | header.answer_count = @byteSwap(header.answer_count); 355 | header.name_server_count = @byteSwap(header.name_server_count); 356 | header.additional_record_count = @byteSwap(header.additional_record_count); 357 | return @as([12]u8, @bitCast(header)); 358 | } 359 | 360 | pub fn to_writer(self: *const Header, writer: anytype) !void { 361 | const header = self.to_bytes(); 362 | try writer.writeAll(header); 363 | } 364 | 365 | pub fn format(self: *const Header, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 366 | _ = fmt; 367 | _ = options; 368 | try writer.print(" Header {{\n", .{}); 369 | try writer.print(" ID: {d}\n", .{self.id}); 370 | try writer.print(" Response: {}\n", .{self.response}); 371 | try writer.print(" OpCode: {s}\n", .{@tagName(self.opcode)}); 372 | try writer.print(" Authoritative Answer: {}\n", .{self.authoritative_answer}); 373 | try writer.print(" Truncation: {}\n", .{self.truncation}); 374 | try writer.print(" Recursion Desired: {}\n", .{self.recursion_desired}); 375 | try writer.print(" Recursion Available: {}\n", .{self.recursion_available}); 376 | try writer.print(" Z: {d}\n", .{self.z}); 377 | try writer.print(" Response Code: {s}\n", .{@tagName(self.response_code)}); 378 | try writer.print(" }}\n", .{}); 379 | } 380 | }; 381 | 382 | test "Header.parse simple request" { 383 | return error.SkipZigTest; 384 | // const pkt = @embedFile("test/query.bin"); 385 | // const header = Header.parse(pkt[0..@sizeOf(Header)]); 386 | // try std.testing.expectEqual(@as(u16, 23002), header.id); 387 | // try std.testing.expectEqual(false, header.response); 388 | // try std.testing.expectEqual(Header.Opcode.query, header.opcode); 389 | // try std.testing.expectEqual(false, header.authoritative_answer); 390 | // try std.testing.expectEqual(false, header.truncation); 391 | // try std.testing.expectEqual(@as(u16, 1), header.question_count); 392 | // try std.testing.expectEqual(@as(u16, 0), header.name_server_count); 393 | } 394 | 395 | test "Header.to_bytes reverses parse" { 396 | return error.SkipZigTest; 397 | // const pkt = @embedFile("test/query.bin"); 398 | // const header = Header.parse(pkt[0..@sizeOf(Header)]); 399 | // const bytes = header.to_bytes(); 400 | // var orig = [_]u8{0} ** @sizeOf(Header); 401 | // mem.copy(u8, &orig, &bytes); 402 | // try std.testing.expectEqualSlices(u8, &orig, &bytes); 403 | // const header2 = Header.parse(&bytes); 404 | // try std.testing.expectEqual(header, header2); 405 | } 406 | 407 | /// Question being asked in the query. 408 | pub const Question = struct { 409 | qname: DomainName, 410 | qtype: QType, 411 | qclass: QClass, 412 | 413 | pub fn to_writer(self: *const Question, writer: anytype) !void { 414 | try self.qname.to_writer(writer); 415 | try writer.writeInt(u16, @intFromEnum(self.qtype), .big); 416 | try writer.writeInt(u16, @intFromEnum(self.qclass), .big); 417 | } 418 | 419 | pub fn from_reader(allocator: mem.Allocator, reader: anytype) !Question { 420 | const qname = try DomainName.from_reader(allocator, reader); 421 | const qtype = try reader.readInt(u16, .big); 422 | const qclass = try reader.readInt(u16, .big); 423 | 424 | return Question{ 425 | .qname = qname, 426 | .qtype = @enumFromInt(qtype), 427 | .qclass = @enumFromInt(qclass), 428 | }; 429 | } 430 | 431 | pub fn deinit(self: *const Question) void { 432 | self.qname.deinit(); 433 | } 434 | 435 | pub fn format(self: *const Question, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 436 | _ = fmt; 437 | _ = options; 438 | 439 | try writer.print(" Question {{\n", .{}); 440 | try writer.print(" Name: {}\n", .{self.qname}); 441 | try writer.print(" QType: {s}\n", .{@tagName(self.qtype)}); 442 | try writer.print(" QClass: {s}\n", .{@tagName(self.qclass)}); 443 | try writer.print(" }}\n", .{}); 444 | } 445 | 446 | pub fn decompress(self: *const Question, allocator: mem.Allocator, packet: []const u8) !Question { 447 | return Question{ 448 | .qname = try self.qname.decompress(allocator, packet), 449 | .qtype = self.qtype, 450 | .qclass = self.qclass, 451 | }; 452 | } 453 | }; 454 | 455 | /// Shared by the answer, authority, and additional sections of the 456 | /// message. 457 | pub const ResourceRecord = struct { 458 | name: DomainName, 459 | type: Type, 460 | class: Class, 461 | ttl: i32, 462 | resource_data_length: u16, 463 | resource_data: ResourceData, 464 | 465 | pub fn to_writer(self: *const ResourceRecord, writer: anytype) !void { 466 | var resource_data = [_]u8{0} ** std.math.maxInt(u16); 467 | var resource_data_stream = std.io.fixedBufferStream(&resource_data); 468 | 469 | try self.resource_data.to_writer(resource_data_stream.writer()); 470 | 471 | try self.name.to_writer(writer); 472 | try writer.writeInt(u16, @intFromEnum(self.type), .big); 473 | try writer.writeInt(u16, @intFromEnum(self.class), .big); 474 | try writer.writeInt(i32, self.ttl, .big); 475 | try writer.writeInt(u16, @as(u16, @intCast(try resource_data_stream.getPos())), .big); 476 | try writer.writeAll(resource_data_stream.getWritten()); 477 | } 478 | 479 | pub fn from_reader(allocator: mem.Allocator, reader: anytype) !ResourceRecord { 480 | const name = try DomainName.from_reader(allocator, reader); 481 | errdefer name.deinit(); 482 | const resource_type: Type = @enumFromInt(try reader.readInt(u16, .big)); 483 | const class: Class = @enumFromInt(try reader.readInt(u16, .big)); 484 | const ttl = try reader.readInt(i32, .big); 485 | const resource_data_length = try reader.readInt(u16, .big); 486 | var counting_reader = io.countingReader(reader); 487 | const resource_data = try ResourceData.from_reader(allocator, counting_reader.reader(), resource_type, resource_data_length); 488 | if (counting_reader.bytes_read != resource_data_length) { 489 | return error.ResourceDataSizeMismatch; 490 | } 491 | 492 | return .{ 493 | .name = name, 494 | .type = resource_type, 495 | .class = class, 496 | .ttl = ttl, 497 | .resource_data_length = resource_data_length, 498 | .resource_data = resource_data, 499 | }; 500 | } 501 | 502 | pub fn deinit(self: *const ResourceRecord) void { 503 | self.name.deinit(); 504 | self.resource_data.deinit(); 505 | } 506 | 507 | pub fn format(self: *const ResourceRecord, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 508 | _ = fmt; 509 | _ = options; 510 | 511 | try writer.print(" Resource Record {{\n", .{}); 512 | try writer.print(" Name: {}\n", .{self.name}); 513 | try writer.print(" Type: {}\n", .{self.type}); 514 | try writer.print(" Class: {}\n", .{self.class}); 515 | try writer.print(" TTL: {d}\n", .{self.ttl}); 516 | try writer.print(" Resource Data Length: {d}\n", .{self.resource_data_length}); 517 | try writer.print(" Resource Data: {}\n", .{self.resource_data}); 518 | try writer.print(" }}\n", .{}); 519 | } 520 | 521 | pub fn decompress(self: *const ResourceRecord, allocator: mem.Allocator, packet: []const u8) !ResourceRecord { 522 | const new_record = try self.resource_data.decompress(allocator, packet); 523 | return ResourceRecord{ 524 | .name = try self.name.decompress(allocator, packet), 525 | .type = self.type, 526 | .class = self.class, 527 | .ttl = self.ttl, 528 | // XXX Should update? 529 | .resource_data_length = self.resource_data_length, 530 | .resource_data = new_record, 531 | }; 532 | } 533 | }; 534 | 535 | /// DNS Resource Record types 536 | pub const Type = enum(u16) { 537 | /// A host address 538 | A = 1, 539 | /// An authoritative name server 540 | NS = 2, 541 | /// A mail destination (Obsolete - use MX) 542 | MD = 3, 543 | /// A mail forwarder (Obsolete - use MX) 544 | MF = 4, 545 | /// The canonical name for an alias 546 | CNAME = 5, 547 | /// Marks the start of a zone of authority 548 | SOA = 6, 549 | /// A mailbox domain name (EXPERIMENTAL) 550 | MB = 7, 551 | /// A mail group member (EXPERIMENTAL) 552 | MG = 8, 553 | /// A mail rename domain name (EXPERIMENTAL) 554 | MR = 9, 555 | /// A null RR (EXPERIMENTAL) 556 | NULL = 10, 557 | /// A well known service description 558 | WKS = 11, 559 | /// A domain name pointer 560 | PTR = 12, 561 | /// Host information 562 | HINFO = 13, 563 | /// Mailbox or mail list information 564 | MINFO = 14, 565 | /// Mail exchange 566 | MX = 15, 567 | /// Text strings 568 | TXT = 16, 569 | /// Responsible Person 570 | RP = 17, 571 | /// An IPv6 host address 572 | AAAA = 28, 573 | /// Location information 574 | LOC = 29, 575 | /// Service locator 576 | SRV = 33, 577 | /// SSH Fingerprint 578 | SSHFP = 44, 579 | /// Uniform Resource Identifier 580 | URI = 256, 581 | 582 | _, 583 | 584 | pub fn format(self: Type, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 585 | _ = fmt; 586 | _ = options; 587 | try formatTagName(self, writer); 588 | } 589 | }; 590 | 591 | /// Types exclusive to QType. 592 | pub const QTypeOnly = enum(u16) { 593 | /// A request for a transfer of an entire zone 594 | AXFR = 252, 595 | /// A request for mailbox-related records (MB, MG or MR) 596 | MAILB = 253, 597 | /// A request for mail agent RRs (Obsolete - see MX) 598 | MAILA = 254, 599 | /// A request for all records 600 | @"*" = 255, 601 | }; 602 | 603 | /// QTYPES are a superset of TYPEs, hence all TYPEs are valid QTYPEs. 604 | pub const QType = blk: { 605 | var info = @typeInfo(Type); 606 | info.Enum.fields = info.Enum.fields ++ @typeInfo(QTypeOnly).Enum.fields; 607 | info.Enum.decls = &.{}; 608 | break :blk @Type(info); 609 | }; 610 | 611 | /// DNS Resource Record Classes 612 | pub const Class = enum(u16) { 613 | /// The Internet 614 | IN = 1, 615 | /// The CSNET class (Obsolete - used only for examples in some obsolete RFCs) 616 | CS = 2, 617 | /// The CHAOS class 618 | CH = 3, 619 | /// Hesiod [Dyer 87] 620 | HS = 4, 621 | 622 | pub fn format(self: Class, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 623 | _ = fmt; 624 | _ = options; 625 | try formatTagName(self, writer); 626 | } 627 | }; 628 | 629 | /// Classes exclusive to QClass. 630 | pub const QClassOnly = enum(u16) { 631 | /// Any Class 632 | @"*" = 255, 633 | }; 634 | 635 | /// QCLASS values are a superset of CLASS values; every CLASS is a valid QCLASS. 636 | pub const QClass = blk: { 637 | var info = @typeInfo(Class); 638 | info.Enum.fields = info.Enum.fields ++ @typeInfo(QClassOnly).Enum.fields; 639 | info.Enum.decls = &.{}; 640 | break :blk @Type(info); 641 | }; 642 | 643 | /// A domain name represented as a sequence of labels, where each 644 | /// label consists of a length octet followed by that number of 645 | /// octets. The domain name terminates with the zero length octet for 646 | /// the null label of the root. Note that this field may be an odd 647 | /// number of octets; no padding is used. 648 | pub const DomainName = struct { 649 | allocator: mem.Allocator, 650 | labels: []Label, 651 | 652 | pub fn from_reader(allocator: mem.Allocator, reader: anytype) !DomainName { 653 | var labels = LabelList.init(allocator); 654 | var header_byte = try reader.readByte(); 655 | errdefer { 656 | for (labels.items) |label| { 657 | switch (label) { 658 | .text => |text| allocator.free(text), 659 | else => {}, 660 | } 661 | } 662 | labels.deinit(); 663 | } 664 | outer: while (true) { 665 | const label_options: Label.Options = @enumFromInt(header_byte >> 6); 666 | switch (label_options) { 667 | .text => { 668 | const header: Label.TextHeader = @bitCast(header_byte); 669 | if (header.length == 0) { 670 | break :outer; 671 | } 672 | const string = try allocator.alloc(u8, header.length); 673 | errdefer allocator.free(string); 674 | const string_length = try reader.readAll(string); 675 | if (string_length < header.length) { 676 | return error.EndOfStream; 677 | } 678 | const label = Label{ 679 | .text = string, 680 | }; 681 | try labels.append(label); 682 | }, 683 | .compressed => { 684 | const header: Label.TextHeader = @bitCast(header_byte); 685 | const pointer_end = try reader.readByte(); 686 | // XXX Different on big-endian systems? 687 | const components = Label.PointerComponents{ .upper = header.length, .lower = pointer_end }; 688 | const pointer: Label.Pointer = @bitCast(components); 689 | const label = Label{ 690 | .compressed = pointer, 691 | }; 692 | try labels.append(label); 693 | break :outer; 694 | }, 695 | else => { 696 | return error.UnsupportedLabel; 697 | }, 698 | } 699 | header_byte = try reader.readByte(); 700 | } 701 | 702 | const empty = Label{ 703 | .text = try allocator.alloc(u8, 0), 704 | }; 705 | errdefer allocator.free(empty.text); 706 | try labels.append(empty); 707 | 708 | return DomainName{ 709 | .allocator = allocator, 710 | .labels = try labels.toOwnedSlice(), 711 | }; 712 | } 713 | 714 | pub fn to_writer(self: *const DomainName, writer: anytype) !void { 715 | for (self.labels) |label| { 716 | switch (label) { 717 | .text => |text| { 718 | if (text.len > std.math.maxInt(Label.Length)) { 719 | return error.LabelTooLong; 720 | } 721 | const header = Label.TextHeader{ .length = @intCast(text.len), .options = .text }; 722 | const header_byte: u8 = @bitCast(header); 723 | try writer.writeByte(header_byte); 724 | try writer.writeAll(text); 725 | }, 726 | .compressed => |pointer| { 727 | const pointer_components: Label.PointerComponents = @bitCast(pointer); 728 | const header = Label.TextHeader{ 729 | .length = pointer_components.upper, 730 | .options = .compressed, 731 | }; 732 | const header_byte: u8 = @bitCast(header); 733 | try writer.writeByte(header_byte); 734 | try writer.writeByte(pointer_components.lower); 735 | }, 736 | // else => return error.UnsupportedLabel, 737 | } 738 | } 739 | } 740 | 741 | pub fn from_string(allocator: mem.Allocator, name: []const u8) !DomainName { 742 | var iter = mem.split(u8, name, "."); 743 | var labels = LabelList.init(allocator); 744 | 745 | errdefer { 746 | for (labels.items) |label| { 747 | switch (label) { 748 | .text => |text| allocator.free(text), 749 | else => {}, 750 | } 751 | } 752 | labels.deinit(); 753 | } 754 | 755 | while (iter.next()) |text| { 756 | if (text.len == 0) 757 | break; 758 | const duped = try allocator.dupe(u8, text); 759 | const label = Label{ 760 | .text = duped, 761 | }; 762 | try labels.append(label); 763 | } 764 | const empty = try allocator.alloc(u8, 0); 765 | errdefer allocator.free(empty); 766 | const label = Label{ 767 | .text = empty, 768 | }; 769 | try labels.append(label); 770 | return DomainName{ 771 | .allocator = allocator, 772 | .labels = try labels.toOwnedSlice(), 773 | }; 774 | } 775 | 776 | pub fn to_string(self: *const DomainName, allocator: mem.Allocator) ![]const u8 { 777 | var string = std.ArrayList(u8).init(allocator); 778 | errdefer string.deinit(); 779 | 780 | for (self.labels) |label| { 781 | switch (label) { 782 | .text => |text| { 783 | if (text.len == 0) { 784 | continue; 785 | } 786 | try string.appendSlice(text); 787 | try string.appendSlice("."); 788 | }, 789 | .compressed => |pointer| { 790 | var writer = string.writer(); 791 | try writer.print("Pointer<{d}>", .{pointer}); 792 | }, 793 | } 794 | } 795 | return try string.toOwnedSlice(); 796 | } 797 | 798 | pub fn format(self: *const DomainName, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 799 | _ = fmt; 800 | _ = options; 801 | 802 | for (self.labels) |label| { 803 | switch (label) { 804 | .text => |text| { 805 | if (text.len == 0) { 806 | continue; 807 | } 808 | try writer.print("{s}.", .{text}); 809 | }, 810 | .compressed => |pointer| { 811 | try writer.print("Pointer<{d}>", .{pointer}); 812 | }, 813 | } 814 | } 815 | } 816 | 817 | pub fn deinit(self: *const DomainName) void { 818 | for (self.labels) |label| { 819 | switch (label) { 820 | .text => self.allocator.free(label.text), 821 | else => {}, 822 | } 823 | } 824 | self.allocator.free(self.labels); 825 | } 826 | 827 | /// Returns a completely newly allocated DomainName 828 | pub fn decompress(self: *const DomainName, allocator: mem.Allocator, packet: []const u8) !DomainName { 829 | var labels = LabelList.init(allocator); 830 | 831 | errdefer { 832 | for (labels.items) |label| { 833 | switch (label) { 834 | .text => |text| allocator.free(text), 835 | .compressed => {}, 836 | } 837 | } 838 | labels.deinit(); 839 | } 840 | 841 | var depth: usize = 0; 842 | var pointer: ?Label.Pointer = null; 843 | for (self.labels) |label| { 844 | switch (label) { 845 | .text => |text| { 846 | const duped = try allocator.dupe(u8, text); 847 | errdefer allocator.free(duped); 848 | const new_label = Label{ .text = duped }; 849 | try labels.append(new_label); 850 | }, 851 | .compressed => |ptr| { 852 | pointer = ptr; 853 | break; 854 | }, 855 | } 856 | } 857 | 858 | while (pointer) |valid_pointer| { 859 | if (depth > max_compression_depth) { 860 | return error.CompressionDepth; 861 | } 862 | var buffer = io.fixedBufferStream(packet); 863 | const reader = buffer.reader(); 864 | buffer.pos = valid_pointer; 865 | const domain = try DomainName.from_reader(allocator, reader); 866 | defer domain.deinit(); 867 | pointer = null; 868 | for (domain.labels) |label| { 869 | switch (label) { 870 | .text => |text| { 871 | const duped = try allocator.dupe(u8, text); 872 | errdefer allocator.free(duped); 873 | const new_label = Label{ .text = duped }; 874 | try labels.append(new_label); 875 | }, 876 | .compressed => |ptr| { 877 | pointer = ptr; 878 | depth += 1; 879 | break; 880 | }, 881 | } 882 | } 883 | } 884 | 885 | return DomainName{ 886 | .allocator = allocator, 887 | .labels = try labels.toOwnedSlice(), 888 | }; 889 | } 890 | 891 | pub const max_compression_depth = 25; 892 | 893 | // TODO: Proper label compression 894 | pub const Label = union(enum) { 895 | text: []const u8, 896 | compressed: Pointer, 897 | // Little bit endian packed struct 898 | pub const TextHeader = packed struct { 899 | length: Length, 900 | options: Options, 901 | }; 902 | 903 | pub const Options = enum(u2) { 904 | text = 0, 905 | compressed = 0b11, 906 | _, 907 | }; 908 | 909 | pub const PointerComponents = packed struct { 910 | lower: u8, 911 | upper: u6, 912 | }; 913 | 914 | pub const Length = u6; 915 | pub const Pointer = u14; 916 | }; 917 | }; 918 | 919 | test "DomainName" { 920 | return error.SkipZigTest; 921 | // const pkt = @embedFile("test/query.bin"); 922 | // const domain = pkt[12..]; 923 | // const parsed = try DomainName.parse(testing.allocator, domain); 924 | // defer parsed.deinit(); 925 | // try testing.expectEqualStrings("lambda", parsed.labels[0]); 926 | // try testing.expectEqualStrings("cx", parsed.labels[1]); 927 | // try testing.expectEqualStrings("", parsed.labels[2]); 928 | // const bytes = try parsed.to_bytes(testing.allocator); 929 | // defer testing.allocator.free(bytes); 930 | // try testing.expectEqualSlices(u8, domain[0..bytes.len], bytes); 931 | // const from_str = try DomainName.from_string(testing.allocator, "lambda.cx"); 932 | // defer from_str.deinit(); 933 | // const from_str_bytes = try from_str.to_bytes(testing.allocator); 934 | // defer testing.allocator.free(from_str_bytes); 935 | // try testing.expectEqualSlices(u8, bytes, from_str_bytes); 936 | } 937 | 938 | /// The data section of a ResourceRecord. Different based on the 939 | /// record type. 940 | pub const ResourceData = union(enum) { 941 | cname: CNAME, 942 | hinfo: HINFO, 943 | mb: MB, 944 | md: MD, 945 | mf: MF, 946 | mg: MG, 947 | minfo: MINFO, 948 | mr: MR, 949 | mx: MX, 950 | null: NULL, 951 | ns: NS, 952 | ptr: PTR, 953 | soa: SOA, 954 | txt: TXT, 955 | a: A, 956 | wks: WKS, 957 | rp: RP, 958 | aaaa: AAAA, 959 | loc: LOC, 960 | srv: SRV, 961 | sshfp: SSHFP, 962 | uri: URI, 963 | unknown: Unknown, 964 | 965 | pub fn to_writer(self: *const ResourceData, writer: anytype) !void { 966 | switch (self.*) { 967 | inline else => |record| try record.to_writer(writer), 968 | } 969 | } 970 | 971 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, resource_type: Type, size: u16) !ResourceData { 972 | return switch (resource_type) { 973 | .CNAME => ResourceData{ .cname = try CNAME.from_reader(allocator, reader, size) }, 974 | .HINFO => ResourceData{ .hinfo = try HINFO.from_reader(allocator, reader, size) }, 975 | .MB => ResourceData{ .mb = try MB.from_reader(allocator, reader, size) }, 976 | .MD => ResourceData{ .md = try MD.from_reader(allocator, reader, size) }, 977 | .MF => ResourceData{ .mf = try MF.from_reader(allocator, reader, size) }, 978 | .MG => ResourceData{ .mg = try MG.from_reader(allocator, reader, size) }, 979 | .MINFO => ResourceData{ .minfo = try MINFO.from_reader(allocator, reader, size) }, 980 | .MR => ResourceData{ .mr = try MR.from_reader(allocator, reader, size) }, 981 | .MX => ResourceData{ .mx = try MX.from_reader(allocator, reader, size) }, 982 | .NULL => ResourceData{ .null = try NULL.from_reader(allocator, reader, size) }, 983 | .NS => ResourceData{ .ns = try NS.from_reader(allocator, reader, size) }, 984 | .PTR => ResourceData{ .ptr = try PTR.from_reader(allocator, reader, size) }, 985 | .SOA => ResourceData{ .soa = try SOA.from_reader(allocator, reader, size) }, 986 | .TXT => ResourceData{ .txt = try TXT.from_reader(allocator, reader, size) }, 987 | .A => ResourceData{ .a = try A.from_reader(allocator, reader, size) }, 988 | .WKS => ResourceData{ .wks = try WKS.from_reader(allocator, reader, size) }, 989 | .RP => ResourceData{ .rp = try RP.from_reader(allocator, reader, size) }, 990 | .AAAA => ResourceData{ .aaaa = try AAAA.from_reader(allocator, reader, size) }, 991 | .LOC => ResourceData{ .loc = try LOC.from_reader(allocator, reader, size) }, 992 | .SRV => ResourceData{ .srv = try SRV.from_reader(allocator, reader, size) }, 993 | .SSHFP => ResourceData{ .sshfp = try SSHFP.from_reader(allocator, reader, size) }, 994 | else => ResourceData{ .unknown = try Unknown.from_reader(allocator, reader, size) }, 995 | }; 996 | } 997 | 998 | pub fn decompress(self: ResourceData, allocator: mem.Allocator, packet: []const u8) !ResourceData { 999 | return switch (self) { 1000 | inline else => |inner, tag| @unionInit(ResourceData, @tagName(tag), try inner.decompress(allocator, packet)), 1001 | }; 1002 | } 1003 | 1004 | pub fn deinit(self: *const ResourceData) void { 1005 | switch (self.*) { 1006 | inline else => |record| record.deinit(), 1007 | } 1008 | } 1009 | 1010 | pub fn format(self: *const ResourceData, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 1011 | _ = fmt; 1012 | _ = options; 1013 | switch (self.*) { 1014 | inline else => |resource| try writer.print("{}", .{resource}), 1015 | } 1016 | } 1017 | 1018 | pub const CNAME = struct { 1019 | /// A domain name which specifies the canonical or primary name 1020 | /// for the owner. The owner name is an alias. 1021 | cname: DomainName, 1022 | 1023 | pub fn to_writer(self: *const CNAME, writer: anytype) !void { 1024 | try self.cname.to_writer(writer); 1025 | } 1026 | 1027 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !CNAME { 1028 | return .{ 1029 | .cname = try DomainName.from_reader(allocator, reader), 1030 | }; 1031 | } 1032 | 1033 | pub fn deinit(self: *const CNAME) void { 1034 | self.cname.deinit(); 1035 | } 1036 | 1037 | pub fn format(self: *const CNAME, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1038 | _ = fmt; 1039 | _ = options; 1040 | 1041 | try writer.print("{}", .{self.cname}); 1042 | } 1043 | 1044 | pub fn decompress(self: CNAME, allocator: mem.Allocator, packet: []const u8) !CNAME { 1045 | return .{ 1046 | .cname = try self.cname.decompress(allocator, packet), 1047 | }; 1048 | } 1049 | }; 1050 | 1051 | pub const HINFO = struct { 1052 | allocator: mem.Allocator, 1053 | /// A string which specifies the CPU type. 1054 | cpu: []const u8, 1055 | /// A string which specifies the operating system type. 1056 | os: []const u8, 1057 | 1058 | pub fn to_writer(self: *const HINFO, writer: anytype) !void { 1059 | if (self.cpu.len > 255) { 1060 | return error.CpuStringTooLong; 1061 | } 1062 | if (self.os.len > 255) { 1063 | return error.OsStringTooLong; 1064 | } 1065 | try writer.writeByte(@intCast(self.cpu.len)); 1066 | try writer.writeAll(self.cpu); 1067 | try writer.writeByte(@intCast(self.os.len)); 1068 | try writer.writeAll(self.os); 1069 | } 1070 | 1071 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !HINFO { 1072 | const cpu = try allocator.alloc(u8, try reader.readByte()); 1073 | errdefer allocator.free(cpu); 1074 | const cpu_len = try reader.readAll(cpu); 1075 | if (cpu_len < cpu.len) { 1076 | return error.EndOfStream; 1077 | } 1078 | const os = try allocator.alloc(u8, try reader.readByte()); 1079 | errdefer allocator.free(os); 1080 | const os_len = try reader.readAll(os); 1081 | if (os_len < os.len) { 1082 | return error.EndOfStream; 1083 | } 1084 | 1085 | return .{ 1086 | .allocator = allocator, 1087 | .cpu = cpu, 1088 | .os = os, 1089 | }; 1090 | } 1091 | 1092 | pub fn deinit(self: *const HINFO) void { 1093 | self.allocator.free(self.cpu); 1094 | self.allocator.free(self.os); 1095 | } 1096 | 1097 | pub fn format(self: *const HINFO, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1098 | _ = fmt; 1099 | _ = options; 1100 | 1101 | try writer.print("CPU: \"{s}\", OS: \"{s}\"", .{ self.cpu, self.os }); 1102 | } 1103 | 1104 | pub fn decompress(self: HINFO, allocator: mem.Allocator, _: []const u8) !HINFO { 1105 | const cpu = try allocator.dupe(u8, self.cpu); 1106 | errdefer allocator.free(cpu); 1107 | const os = try allocator.dupe(u8, self.os); 1108 | return .{ 1109 | .allocator = allocator, 1110 | .cpu = cpu, 1111 | .os = os, 1112 | }; 1113 | } 1114 | }; 1115 | 1116 | pub const MB = struct { 1117 | /// A domain name which specifies a host which has the specified 1118 | /// mailbox. 1119 | madname: DomainName, 1120 | 1121 | pub fn to_writer(self: *const MB, writer: anytype) !void { 1122 | try self.madname.to_writer(writer); 1123 | } 1124 | 1125 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MB { 1126 | return .{ 1127 | .madname = try DomainName.from_reader(allocator, reader), 1128 | }; 1129 | } 1130 | 1131 | pub fn deinit(self: *const MB) void { 1132 | self.madname.deinit(); 1133 | } 1134 | 1135 | pub fn format(self: *const MB, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1136 | _ = fmt; 1137 | _ = options; 1138 | 1139 | try writer.print("{}", .{self.madname}); 1140 | } 1141 | 1142 | pub fn decompress(self: MB, allocator: mem.Allocator, packet: []const u8) !MB { 1143 | return .{ 1144 | .madname = try self.madname.decompress(allocator, packet), 1145 | }; 1146 | } 1147 | }; 1148 | 1149 | pub const MD = struct { 1150 | /// A domain name which specifies a host which has a mail agent 1151 | /// for the domain which should be able to deliver mail for the 1152 | /// domain. 1153 | madname: DomainName, 1154 | 1155 | pub fn to_writer(self: *const MD, writer: anytype) !void { 1156 | try self.madname.to_writer(writer); 1157 | } 1158 | 1159 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MD { 1160 | return .{ 1161 | .madname = try DomainName.from_reader(allocator, reader), 1162 | }; 1163 | } 1164 | 1165 | pub fn deinit(self: *const MD) void { 1166 | self.madname.deinit(); 1167 | } 1168 | 1169 | pub fn format(self: *const MD, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1170 | _ = fmt; 1171 | _ = options; 1172 | 1173 | try writer.print("{}", .{self.madname}); 1174 | } 1175 | 1176 | pub fn decompress(self: MD, allocator: mem.Allocator, packet: []const u8) !MD { 1177 | return .{ 1178 | .madname = try self.madname.decompress(allocator, packet), 1179 | }; 1180 | } 1181 | }; 1182 | 1183 | pub const MF = struct { 1184 | /// A domain name which specifies a host which has a mail agent 1185 | /// for the domain which will accept mail for forwarding to the 1186 | /// domain. 1187 | madname: DomainName, 1188 | 1189 | pub fn to_writer(self: *const MF, writer: anytype) !void { 1190 | try self.madname.to_writer(writer); 1191 | } 1192 | 1193 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MF { 1194 | return .{ 1195 | .madname = try DomainName.from_reader(allocator, reader), 1196 | }; 1197 | } 1198 | 1199 | pub fn deinit(self: *const MF) void { 1200 | self.madname.deinit(); 1201 | } 1202 | 1203 | pub fn format(self: *const MF, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1204 | _ = fmt; 1205 | _ = options; 1206 | 1207 | try writer.print("{}", .{self.madname}); 1208 | } 1209 | 1210 | pub fn decompress(self: MF, allocator: mem.Allocator, packet: []const u8) !MF { 1211 | return .{ 1212 | .madname = try self.madname.decompress(allocator, packet), 1213 | }; 1214 | } 1215 | }; 1216 | 1217 | pub const MG = struct { 1218 | /// A domain name which specifies a mailbox which is a member of 1219 | /// the mail group specified by the domain name. 1220 | madname: DomainName, 1221 | 1222 | pub fn to_writer(self: *const MG, writer: anytype) !void { 1223 | try self.madname.to_writer(writer); 1224 | } 1225 | 1226 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MG { 1227 | return .{ 1228 | .madname = try DomainName.from_reader(allocator, reader), 1229 | }; 1230 | } 1231 | 1232 | pub fn deinit(self: *const MG) void { 1233 | self.madname.deinit(); 1234 | } 1235 | 1236 | pub fn format(self: *const MG, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1237 | _ = fmt; 1238 | _ = options; 1239 | 1240 | try writer.print("{}", .{self.madname}); 1241 | } 1242 | 1243 | pub fn decompress(self: MG, allocator: mem.Allocator, packet: []const u8) !MG { 1244 | return .{ 1245 | .madname = try self.madname.decompress(allocator, packet), 1246 | }; 1247 | } 1248 | }; 1249 | 1250 | pub const MINFO = struct { 1251 | /// A domain name which specifies a mailbox which is responsible 1252 | /// for the mailing list or mailbox. If this domain name names the 1253 | /// root, the owner of the MINFO RR is responsible for itself. 1254 | /// Note that many existing mailing lists use a mailbox X-request 1255 | /// for the RMAILBX field of mailing list X, e.g., Msgroup-request 1256 | /// for Msgroup. This field provides a more general mechanism. 1257 | rmailbx: DomainName, 1258 | /// A domain name which specifies a mailbox which is to receive 1259 | /// error messages related to the mailing list or mailbox 1260 | /// specified by the owner of the MINFO RR (similar to the 1261 | /// ERRORS-TO: field which has been proposed). If this domain name 1262 | /// names the root, errors should be returned to the sender of the 1263 | /// message. 1264 | emailbx: DomainName, 1265 | 1266 | pub fn to_writer(self: *const MINFO, writer: anytype) !void { 1267 | try self.rmailbx.to_writer(writer); 1268 | try self.emailbx.to_writer(writer); 1269 | } 1270 | 1271 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MINFO { 1272 | return .{ 1273 | .rmailbx = try DomainName.from_reader(allocator, reader), 1274 | .emailbx = try DomainName.from_reader(allocator, reader), 1275 | }; 1276 | } 1277 | 1278 | pub fn deinit(self: *const MINFO) void { 1279 | self.rmailbx.deinit(); 1280 | self.emailbx.deinit(); 1281 | } 1282 | 1283 | pub fn format(self: *const MINFO, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1284 | _ = fmt; 1285 | _ = options; 1286 | 1287 | try writer.print("rmailbox: {}, emailbox: {}", .{ self.rmailbx, self.emailbx }); 1288 | } 1289 | 1290 | pub fn decompress(self: MINFO, allocator: mem.Allocator, packet: []const u8) !MINFO { 1291 | const rmailbx = try self.rmailbx.decompress(allocator, packet); 1292 | errdefer rmailbx.deinit(); 1293 | const emailbx = try self.emailbx.decompress(allocator, packet); 1294 | return .{ 1295 | .rmailbx = rmailbx, 1296 | .emailbx = emailbx, 1297 | }; 1298 | } 1299 | }; 1300 | 1301 | pub const MR = struct { 1302 | /// A domain name which specifies a mailbox which is the proper 1303 | /// rename of the specified mailbox. 1304 | madname: DomainName, 1305 | 1306 | pub fn to_writer(self: *const MR, writer: anytype) !void { 1307 | try self.madname.to_writer(writer); 1308 | } 1309 | 1310 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MR { 1311 | return .{ 1312 | .madname = try DomainName.from_reader(allocator, reader), 1313 | }; 1314 | } 1315 | 1316 | pub fn deinit(self: *const MR) void { 1317 | self.madname.deinit(); 1318 | } 1319 | 1320 | pub fn format(self: *const MR, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1321 | _ = fmt; 1322 | _ = options; 1323 | 1324 | try writer.print("{}", .{self.madname}); 1325 | } 1326 | 1327 | pub fn decompress(self: MR, allocator: mem.Allocator, packet: []const u8) !MR { 1328 | return .{ 1329 | .madname = try self.madname.decompress(allocator, packet), 1330 | }; 1331 | } 1332 | }; 1333 | 1334 | pub const MX = struct { 1335 | /// A 16 bit integer which specifies the preference given to this 1336 | /// RR among others at the same owner. Lower values are preferred. 1337 | preference: u16, 1338 | /// A domain name which specifies a host willing to act as a 1339 | /// mail exchange for the owner name. 1340 | exchange: DomainName, 1341 | 1342 | pub fn to_writer(self: *const MX, writer: anytype) !void { 1343 | try writer.writeInt(u16, self.preference, .big); 1344 | try self.exchange.to_writer(writer); 1345 | } 1346 | 1347 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !MX { 1348 | return .{ 1349 | .preference = try reader.readInt(u16, .big), 1350 | .exchange = try DomainName.from_reader(allocator, reader), 1351 | }; 1352 | } 1353 | 1354 | pub fn deinit(self: *const MX) void { 1355 | self.exchange.deinit(); 1356 | } 1357 | 1358 | pub fn format(self: *const MX, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1359 | _ = fmt; 1360 | _ = options; 1361 | 1362 | try writer.print("Preference: {d}, Exchange: {}", .{ self.preference, self.exchange }); 1363 | } 1364 | 1365 | pub fn decompress(self: MX, allocator: mem.Allocator, packet: []const u8) !MX { 1366 | return .{ 1367 | .preference = self.preference, 1368 | .exchange = try self.exchange.decompress(allocator, packet), 1369 | }; 1370 | } 1371 | }; 1372 | 1373 | pub const NULL = struct { 1374 | allocator: mem.Allocator, 1375 | data: []const u8, 1376 | 1377 | pub fn to_writer(self: *const NULL, writer: anytype) !void { 1378 | try writer.writeAll(self.data); 1379 | } 1380 | 1381 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !NULL { 1382 | const data = try allocator.alloc(u8, size); 1383 | errdefer allocator.free(data); 1384 | const len = try reader.readAll(data); 1385 | if (len < size) { 1386 | return error.EndOfStream; 1387 | } 1388 | return .{ 1389 | .allocator = allocator, 1390 | .data = data, 1391 | }; 1392 | } 1393 | 1394 | pub fn deinit(self: *const NULL) void { 1395 | self.allocator.free(self.data); 1396 | } 1397 | 1398 | pub fn format(self: *const NULL, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1399 | _ = fmt; 1400 | _ = options; 1401 | 1402 | try writer.print("{s}", .{std.fmt.fmtSliceHexUpper(self.data)}); 1403 | } 1404 | 1405 | pub fn decompress(self: NULL, allocator: mem.Allocator, _: []const u8) !NULL { 1406 | return .{ 1407 | .allocator = allocator, 1408 | .data = try allocator.dupe(u8, self.data), 1409 | }; 1410 | } 1411 | }; 1412 | 1413 | pub const NS = struct { 1414 | /// A domain name which specifies a host which should be 1415 | /// authoritative for the specified class and domain. 1416 | nsdname: DomainName, 1417 | 1418 | pub fn to_writer(self: *const NS, writer: anytype) !void { 1419 | try self.nsdname.to_writer(writer); 1420 | } 1421 | 1422 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !NS { 1423 | return .{ 1424 | .nsdname = try DomainName.from_reader(allocator, reader), 1425 | }; 1426 | } 1427 | 1428 | pub fn deinit(self: *const NS) void { 1429 | self.nsdname.deinit(); 1430 | } 1431 | 1432 | pub fn format(self: *const NS, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1433 | _ = fmt; 1434 | _ = options; 1435 | 1436 | try writer.print("{}", .{self.nsdname}); 1437 | } 1438 | 1439 | pub fn decompress(self: NS, allocator: mem.Allocator, packet: []const u8) !NS { 1440 | return .{ 1441 | .nsdname = try self.nsdname.decompress(allocator, packet), 1442 | }; 1443 | } 1444 | }; 1445 | 1446 | pub const PTR = struct { 1447 | /// A domain name which points to some location in the domain name 1448 | /// space. 1449 | ptrdname: DomainName, 1450 | 1451 | pub fn to_writer(self: *const PTR, writer: anytype) !void { 1452 | try self.ptrdname.to_writer(writer); 1453 | } 1454 | 1455 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !PTR { 1456 | return .{ 1457 | .ptrdname = try DomainName.from_reader(allocator, reader), 1458 | }; 1459 | } 1460 | 1461 | pub fn deinit(self: *const PTR) void { 1462 | self.ptrdname.deinit(); 1463 | } 1464 | 1465 | pub fn format(self: *const PTR, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1466 | _ = fmt; 1467 | _ = options; 1468 | 1469 | try writer.print("{}", .{self.ptrdname}); 1470 | } 1471 | 1472 | pub fn decompress(self: PTR, allocator: mem.Allocator, packet: []const u8) !PTR { 1473 | return .{ 1474 | .ptrdname = try self.ptrdname.decompress(allocator, packet), 1475 | }; 1476 | } 1477 | }; 1478 | 1479 | pub const SOA = struct { 1480 | /// The domain name of the name server that was the original or 1481 | /// primary source of data for this zone. 1482 | mname: DomainName, 1483 | /// A domain name which specifies the mailbox of the person 1484 | /// responsible for this zone. 1485 | rname: DomainName, 1486 | /// The version number of the original copy of the zone. Zone 1487 | /// transfers preserve this value. This value wraps and should be 1488 | /// compared using sequence space arithmetic. 1489 | serial: u32, 1490 | /// A time interval before the zone should be refreshed. 1491 | refresh: i32, 1492 | /// A time interval that should elapse before a failed refresh 1493 | /// should be retried. 1494 | retry: i32, 1495 | /// A value that specifies the upper limit on the time interval 1496 | /// that can elapse before the zone is no longer authoritative. 1497 | expire: i32, 1498 | /// The minimum TTL field that should be exported with any RR from 1499 | /// this zone. 1500 | minimum: u32, 1501 | 1502 | pub fn to_writer(self: *const SOA, writer: anytype) !void { 1503 | try self.mname.to_writer(writer); 1504 | try self.rname.to_writer(writer); 1505 | try writer.writeInt(u32, self.serial, .big); 1506 | try writer.writeInt(i32, self.refresh, .big); 1507 | try writer.writeInt(i32, self.retry, .big); 1508 | try writer.writeInt(i32, self.expire, .big); 1509 | try writer.writeInt(u32, self.minimum, .big); 1510 | } 1511 | 1512 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !SOA { 1513 | return .{ 1514 | .mname = try DomainName.from_reader(allocator, reader), 1515 | .rname = try DomainName.from_reader(allocator, reader), 1516 | .serial = try reader.readInt(u32, .big), 1517 | .refresh = try reader.readInt(i32, .big), 1518 | .retry = try reader.readInt(i32, .big), 1519 | .expire = try reader.readInt(i32, .big), 1520 | .minimum = try reader.readInt(u32, .big), 1521 | }; 1522 | } 1523 | 1524 | pub fn deinit(self: *const SOA) void { 1525 | self.mname.deinit(); 1526 | self.rname.deinit(); 1527 | } 1528 | 1529 | pub fn format(self: *const SOA, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1530 | _ = fmt; 1531 | _ = options; 1532 | 1533 | try writer.print( 1534 | \\SOA {{ 1535 | \\ Primary Nameserver: {} 1536 | \\ Responsible Person Mailbox: {} 1537 | \\ Version Serial: {d} 1538 | \\ Refresh: {d} 1539 | \\ Retry: {d} 1540 | \\ Expire: {d} 1541 | \\ Minumum: {d} 1542 | \\ }} 1543 | , .{ self.mname, self.rname, self.serial, self.refresh, self.retry, self.expire, self.minimum }); 1544 | } 1545 | 1546 | pub fn decompress(self: SOA, allocator: mem.Allocator, packet: []const u8) !SOA { 1547 | const mname = try self.mname.decompress(allocator, packet); 1548 | const rname = try self.rname.decompress(allocator, packet); 1549 | return .{ 1550 | .mname = mname, 1551 | .rname = rname, 1552 | .serial = self.serial, 1553 | .refresh = self.refresh, 1554 | .retry = self.retry, 1555 | .expire = self.expire, 1556 | .minimum = self.minimum, 1557 | }; 1558 | } 1559 | }; 1560 | 1561 | pub const TXT = struct { 1562 | allocator: mem.Allocator, 1563 | /// One or more strings. 1564 | txt_data: [][]const u8, 1565 | 1566 | pub fn to_writer(self: *const TXT, writer: anytype) !void { 1567 | for (self.txt_data) |txt| { 1568 | if (txt.len > 255) { 1569 | return error.TxtTooLong; 1570 | } 1571 | try writer.writeByte(@intCast(txt.len)); 1572 | try writer.writeAll(txt); 1573 | } 1574 | } 1575 | 1576 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !TXT { 1577 | var txt_data = StrList.init(allocator); 1578 | 1579 | errdefer { 1580 | for (txt_data.items) |txt| { 1581 | allocator.free(txt); 1582 | } 1583 | txt_data.deinit(); 1584 | } 1585 | 1586 | var bytes_read: usize = 0; 1587 | while (bytes_read < size) { 1588 | const txt = try allocator.alloc(u8, try reader.readByte()); 1589 | errdefer allocator.free(txt); 1590 | const txt_len = try reader.readAll(txt); 1591 | if (txt_len < txt.len) { 1592 | return error.EndOfStream; 1593 | } 1594 | try txt_data.append(txt); 1595 | bytes_read += txt_len + 1; 1596 | } 1597 | return .{ 1598 | .allocator = allocator, 1599 | .txt_data = try txt_data.toOwnedSlice(), 1600 | }; 1601 | } 1602 | 1603 | pub fn deinit(self: *const TXT) void { 1604 | for (self.txt_data) |txt| { 1605 | self.allocator.free(txt); 1606 | } 1607 | self.allocator.free(self.txt_data); 1608 | } 1609 | 1610 | pub fn format(self: *const TXT, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 1611 | _ = fmt; 1612 | _ = options; 1613 | for (self.txt_data) |txt| { 1614 | try writer.print("\"{s}\"", .{txt}); 1615 | } 1616 | } 1617 | 1618 | pub fn decompress(self: TXT, allocator: mem.Allocator, _: []const u8) !TXT { 1619 | var str_list = StrList.init(allocator); 1620 | 1621 | errdefer { 1622 | for (str_list.items) |txt| { 1623 | allocator.free(txt); 1624 | } 1625 | str_list.deinit(); 1626 | } 1627 | 1628 | for (self.txt_data) |txt| { 1629 | try str_list.append(try allocator.dupe(u8, txt)); 1630 | } 1631 | return .{ 1632 | .allocator = allocator, 1633 | .txt_data = try str_list.toOwnedSlice(), 1634 | }; 1635 | } 1636 | }; 1637 | 1638 | pub const A = struct { 1639 | /// An internet address 1640 | address: [4]u8, 1641 | 1642 | pub fn to_writer(self: *const A, writer: anytype) !void { 1643 | try writer.writeAll(&self.address); 1644 | } 1645 | 1646 | pub fn from_reader(_: mem.Allocator, reader: anytype, _: u16) !A { 1647 | var address = [4]u8{ 0, 0, 0, 0 }; 1648 | const len = try reader.readAll(&address); 1649 | if (len < 4) { 1650 | return error.EndOfStream; 1651 | } 1652 | return .{ 1653 | .address = address, 1654 | }; 1655 | } 1656 | 1657 | pub fn deinit(_: *const A) void {} 1658 | 1659 | pub fn format(self: *const A, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 1660 | _ = fmt; 1661 | _ = options; 1662 | try writer.print("{d}.{d}.{d}.{d}", .{ self.address[0], self.address[1], self.address[2], self.address[3] }); 1663 | } 1664 | 1665 | pub fn decompress(self: A, _: mem.Allocator, _: []const u8) !A { 1666 | return self; 1667 | } 1668 | }; 1669 | 1670 | pub const WKS = struct { 1671 | allocator: mem.Allocator, 1672 | /// An internet address 1673 | address: [4]u8, 1674 | /// An IP protocol number 1675 | protocol: u8, 1676 | /// A variable length bit map. 1677 | bit_map: []const u8, 1678 | 1679 | pub fn to_writer(self: *const WKS, writer: anytype) !void { 1680 | try writer.writeAll(&self.address); 1681 | try writer.writeByte(self.protocol); 1682 | try writer.writeAll(self.bit_map); 1683 | } 1684 | 1685 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !WKS { 1686 | var address = [4]u8{ 0, 0, 0, 0 }; 1687 | const addr_len = try reader.readAll(&address); 1688 | if (addr_len < size) { 1689 | return error.EndOfStream; 1690 | } 1691 | const protocol = try reader.readByte(); 1692 | const bit_map = try allocator.alloc(u8, size - 5); 1693 | errdefer allocator.free(bit_map); 1694 | const bm_len = try reader.readAll(bit_map); 1695 | if (bm_len + 5 < size) { 1696 | return error.EndOfStream; 1697 | } 1698 | return .{ 1699 | .allocator = allocator, 1700 | .address = address, 1701 | .protocol = protocol, 1702 | .bit_map = bit_map, 1703 | }; 1704 | } 1705 | 1706 | pub fn deinit(self: *const WKS) void { 1707 | self.allocator.free(self.bit_map); 1708 | } 1709 | 1710 | pub fn format(self: *const WKS, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1711 | _ = fmt; 1712 | _ = options; 1713 | 1714 | try writer.print("Address: {d}.{d}.{d}.{d}, Protocol: {d}, Bitmap: {s}", .{ self.address[0], self.address[1], self.address[2], self.address[3], self.protocol, std.fmt.fmtSliceEscapeUpper(self.bit_map) }); 1715 | } 1716 | 1717 | pub fn decompress(self: WKS, allocator: mem.Allocator, _: []const u8) !WKS { 1718 | return .{ 1719 | .allocator = allocator, 1720 | .address = self.address, 1721 | .protocol = self.protocol, 1722 | .bit_map = try allocator.dupe(u8, self.bit_map), 1723 | }; 1724 | } 1725 | }; 1726 | 1727 | pub const Unknown = struct { 1728 | allocator: mem.Allocator, 1729 | data: []const u8, 1730 | 1731 | pub fn to_writer(self: *const Unknown, writer: anytype) !void { 1732 | try writer.writeAll(self.data); 1733 | } 1734 | 1735 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !Unknown { 1736 | const data = try allocator.alloc(u8, size); 1737 | errdefer allocator.free(data); 1738 | const len = try reader.readAll(data); 1739 | if (len < size) { 1740 | return error.EndOfStream; 1741 | } 1742 | return .{ 1743 | .allocator = allocator, 1744 | .data = data, 1745 | }; 1746 | } 1747 | 1748 | pub fn deinit(self: *const Unknown) void { 1749 | self.allocator.free(self.data); 1750 | } 1751 | 1752 | pub fn format(self: *const Unknown, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1753 | _ = fmt; 1754 | _ = options; 1755 | 1756 | try writer.print("{s}", .{std.fmt.fmtSliceEscapeUpper(self.data)}); 1757 | } 1758 | 1759 | pub fn decompress(self: Unknown, allocator: mem.Allocator, _: []const u8) !Unknown { 1760 | return .{ 1761 | .allocator = allocator, 1762 | .data = try allocator.dupe(u8, self.data), 1763 | }; 1764 | } 1765 | }; 1766 | 1767 | pub const RP = struct { 1768 | allocator: mem.Allocator, 1769 | /// A domain name that specifies the mailbox for the 1770 | /// responsible person 1771 | mbox_dname: DomainName, 1772 | /// A domain name for which TXT RR's exist. A subsequent query 1773 | /// can be performed to retrieve the associated TXT resource 1774 | /// records at txt_dname. 1775 | txt_dname: []const u8, 1776 | 1777 | pub fn to_writer(self: *const RP, writer: anytype) !void { 1778 | try self.mbox_dname.to_writer(writer); 1779 | try writer.writeAll(self.txt_dname); 1780 | } 1781 | 1782 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !RP { 1783 | var counting_reader = io.countingReader(reader); 1784 | const mbox = try DomainName.from_reader(allocator, counting_reader.reader()); 1785 | const txt = try allocator.alloc(u8, size - @as(u16, @intCast(counting_reader.bytes_read))); 1786 | errdefer allocator.free(txt); 1787 | const txt_length = try reader.readAll(txt); 1788 | if (txt_length + counting_reader.bytes_read < size) { 1789 | return error.EndOfStream; 1790 | } 1791 | return .{ 1792 | .allocator = allocator, 1793 | .mbox_dname = mbox, 1794 | .txt_dname = txt, 1795 | }; 1796 | } 1797 | 1798 | pub fn deinit(self: *const RP) void { 1799 | self.allocator.free(self.txt_dname); 1800 | self.mbox_dname.deinit(); 1801 | } 1802 | 1803 | pub fn decompress(self: RP, allocator: mem.Allocator, packet: []const u8) !RP { 1804 | const mbox_dname = try self.mbox_dname.decompress(allocator, packet); 1805 | errdefer mbox_dname.deinit(); 1806 | const txt_dname = try allocator.dupe(u8, self.txt_dname); 1807 | return .{ 1808 | .allocator = allocator, 1809 | .mbox_dname = mbox_dname, 1810 | .txt_dname = txt_dname, 1811 | }; 1812 | } 1813 | }; 1814 | 1815 | pub const AAAA = struct { 1816 | address: [16]u8, 1817 | 1818 | pub fn to_writer(self: *const AAAA, writer: anytype) !void { 1819 | try writer.writeAll(&self.address); 1820 | } 1821 | 1822 | pub fn from_reader(_: mem.Allocator, reader: anytype, _: u16) !AAAA { 1823 | var address: [16]u8 = undefined; 1824 | const length = try reader.readAll(&address); 1825 | if (length < 16) { 1826 | return error.EndOfStream; 1827 | } 1828 | return .{ 1829 | .address = address, 1830 | }; 1831 | } 1832 | 1833 | pub fn deinit(_: *const AAAA) void {} 1834 | 1835 | pub fn format(self: *const AAAA, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { 1836 | _ = fmt; 1837 | _ = options; 1838 | try writer.print("{x:0>2}{x:0>2}:{x:0>2}{x:0>2}:{x:0>2}{x:0>2}:{x:0>2}{x:0>2}:{x:0>2}{x:0>2}:{x:0>2}{x:0>2}:{x:0>2}{x:0>2}:{x:0>2}{x:0>2}", .{ 1839 | self.address[0], 1840 | self.address[1], 1841 | self.address[2], 1842 | self.address[3], 1843 | self.address[4], 1844 | self.address[5], 1845 | self.address[6], 1846 | self.address[7], 1847 | self.address[8], 1848 | self.address[9], 1849 | self.address[10], 1850 | self.address[11], 1851 | self.address[12], 1852 | self.address[13], 1853 | self.address[14], 1854 | self.address[15], 1855 | }); 1856 | } 1857 | 1858 | pub fn decompress(self: AAAA, _: mem.Allocator, _: []const u8) !AAAA { 1859 | return self; 1860 | } 1861 | }; 1862 | 1863 | pub const SRV = struct { 1864 | /// The priority of this target host. A client MUST attempt to 1865 | /// contact the target host with the lowest-numbered priority 1866 | /// it can reach 1867 | priority: u16, 1868 | /// A relative weight for entries with the same priority. 1869 | /// Larger weights SHOULD be given a proportionately higher 1870 | /// probability of being selected. 1871 | weight: u16, 1872 | /// The port on this target host of this service. 1873 | port: u16, 1874 | /// The domain name of the target host. 1875 | target: DomainName, 1876 | 1877 | pub fn to_writer(self: *const SRV, writer: anytype) !void { 1878 | try writer.writeInt(u16, self.priority, .big); 1879 | try writer.writeInt(u16, self.weight, .big); 1880 | try writer.writeInt(u16, self.port, .big); 1881 | try self.target.to_writer(writer); 1882 | } 1883 | 1884 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, _: u16) !SRV { 1885 | const priority = try reader.readInt(u16, .big); 1886 | const weight = try reader.readInt(u16, .big); 1887 | const port = try reader.readInt(u16, .big); 1888 | const target = try DomainName.from_reader(allocator, reader); 1889 | 1890 | return .{ 1891 | .priority = priority, 1892 | .weight = weight, 1893 | .port = port, 1894 | .target = target, 1895 | }; 1896 | } 1897 | 1898 | pub fn deinit(self: *const SRV) void { 1899 | self.target.deinit(); 1900 | } 1901 | 1902 | pub fn format(self: *const SRV, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1903 | _ = fmt; 1904 | _ = options; 1905 | 1906 | try writer.print( 1907 | \\SRV {{ 1908 | \\ Priority: {d} 1909 | \\ Weight: {d} 1910 | \\ Port: {d} 1911 | \\ Target: {} 1912 | \\ }} 1913 | , .{ self.priority, self.weight, self.port, self.target }); 1914 | } 1915 | 1916 | pub fn decompress(self: SRV, allocator: mem.Allocator, packet: []const u8) !SRV { 1917 | return .{ 1918 | .priority = self.priority, 1919 | .weight = self.weight, 1920 | .port = self.port, 1921 | .target = try self.target.decompress(allocator, packet), 1922 | }; 1923 | } 1924 | }; 1925 | 1926 | pub const SSHFP = struct { 1927 | allocator: mem.Allocator, 1928 | /// The algorithm of the public key. 1929 | algorithm: Algorithm, 1930 | /// The message-digest algorithm used to calculate the 1931 | /// fingerprint of the public key. 1932 | fingerprint_type: FingerprintType, 1933 | /// Hexadecimal representation of the hash result, as text. 1934 | fingerprint: []const u8, 1935 | 1936 | const Algorithm = enum(u8) { 1937 | RSA = 1, 1938 | DSA = 2, 1939 | ECDSA = 3, 1940 | Ed25519 = 4, 1941 | Ed448 = 5, 1942 | }; 1943 | 1944 | const FingerprintType = enum(u8) { 1945 | SHA1 = 1, 1946 | SHA256 = 2, 1947 | }; 1948 | 1949 | pub fn to_writer(self: *const SSHFP, writer: anytype) !void { 1950 | try writer.writeByte(@intFromEnum(self.algorithm)); 1951 | try writer.writeByte(@intFromEnum(self.fingerprint_type)); 1952 | try writer.writeAll(self.fingerprint); 1953 | } 1954 | 1955 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !SSHFP { 1956 | const algorithm: Algorithm = @enumFromInt(try reader.readByte()); 1957 | const fingerprint_type: FingerprintType = @enumFromInt(try reader.readByte()); 1958 | const fingerprint = try allocator.alloc(u8, size - 2); 1959 | errdefer allocator.free(fingerprint); 1960 | const length = try reader.readAll(fingerprint); 1961 | if (length + 2 < size) { 1962 | return error.EndOfStream; 1963 | } 1964 | 1965 | return .{ 1966 | .allocator = allocator, 1967 | .algorithm = algorithm, 1968 | .fingerprint_type = fingerprint_type, 1969 | .fingerprint = fingerprint, 1970 | }; 1971 | } 1972 | 1973 | pub fn deinit(self: *const SSHFP) void { 1974 | self.allocator.free(self.fingerprint); 1975 | } 1976 | 1977 | pub fn format(self: *const SSHFP, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 1978 | _ = fmt; 1979 | _ = options; 1980 | 1981 | try writer.print("Algorithm: {s}, Fingerprint Type: {s}, Fingerprint: {s}", .{ @tagName(self.algorithm), @tagName(self.fingerprint_type), self.fingerprint }); 1982 | } 1983 | 1984 | pub fn decompress(self: SSHFP, allocator: mem.Allocator, _: []const u8) !SSHFP { 1985 | return .{ 1986 | .allocator = allocator, 1987 | .algorithm = self.algorithm, 1988 | .fingerprint_type = self.fingerprint_type, 1989 | .fingerprint = try allocator.dupe(u8, self.fingerprint), 1990 | }; 1991 | } 1992 | }; 1993 | 1994 | pub const URI = struct { 1995 | allocator: mem.Allocator, 1996 | /// The priority of this target host. A client MUST attempt to 1997 | /// contact the target host with the lowest-numbered priority 1998 | /// it can reach 1999 | priority: u16, 2000 | /// A relative weight for entries with the same priority. 2001 | /// Larger weights SHOULD be given a proportionately higher 2002 | /// probability of being selected. 2003 | weight: u16, 2004 | /// This field holds the URI of the target, enclosed in 2005 | /// double-quote characters ('"'), where the URI is as 2006 | /// specified in RFC 3986. 2007 | target: []const u8, 2008 | 2009 | pub fn to_writer(self: *const URI, writer: anytype) !void { 2010 | try writer.writeInt(u16, self.priority, .big); 2011 | try writer.writeInt(u16, self.weight, .big); 2012 | try writer.writeAll(self.target); 2013 | } 2014 | 2015 | pub fn from_reader(allocator: mem.Allocator, reader: anytype, size: u16) !URI { 2016 | const priority = try reader.readInt(u16, .big); 2017 | const weight = try reader.readInt(u16, .big); 2018 | const target = allocator.alloc(u8, size - 4); 2019 | errdefer allocator.free(target); 2020 | const length = try reader.readAll(target); 2021 | if (length + 4 < size) { 2022 | return error.EndOfStream; 2023 | } 2024 | 2025 | return .{ 2026 | .allocator = allocator, 2027 | .priority = priority, 2028 | .weight = weight, 2029 | .target = target, 2030 | }; 2031 | } 2032 | 2033 | pub fn deinit(self: *const URI) void { 2034 | self.allocator.free(self.target); 2035 | } 2036 | 2037 | pub fn format(self: *const URI, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 2038 | _ = fmt; 2039 | _ = options; 2040 | 2041 | try writer.print("Priority: {d}, Weight: {d}, Target: {s}", .{ self.priority, self.weight, self.target }); 2042 | } 2043 | 2044 | pub fn decompress(self: URI, allocator: mem.Allocator, _: []const u8) !URI { 2045 | return .{ 2046 | .allocator = allocator, 2047 | .priority = self.priority, 2048 | .weight = self.weight, 2049 | .target = try allocator.dupe(u8, self.target), 2050 | }; 2051 | } 2052 | }; 2053 | 2054 | pub const LOC = struct { 2055 | /// Version number of the representation. This must be zero. 2056 | version: u8 = 0, 2057 | /// The diameter of a sphere enclosing the described entity, 2058 | /// in centimeters. 2059 | /// 2060 | /// Default = 1m 2061 | size: PrecisionSize = .{ .base = 1, .power = 2 }, 2062 | /// The horizontal precision of the data, in centimeters. 2063 | /// This is the diameter of the horizontal "circle of error", 2064 | /// rather than a "plus or minus" value. To get a "plus or 2065 | /// minus" value, divide by 2. 2066 | /// 2067 | /// Default = 10km 2068 | horizontal_precision: PrecisionSize = .{ .base = 1, .power = 6 }, 2069 | /// The vertical precision of the data, in centimeters. 2070 | /// This is the total potential vertical error, rather than a 2071 | /// "plus or minus" value. To get a "plus or minus" value, 2072 | /// divide by 2. Note that if altitude above or below sea 2073 | /// level is used as an approximation for altitude relative to 2074 | /// the [WGS 84] ellipsoid, the precision value should be 2075 | /// adjusted. 2076 | /// 2077 | /// Default = 10m 2078 | vertical_precision: PrecisionSize = .{ .base = 1, .power = 3 }, 2079 | /// The latitude of the center of the sphere described by the 2080 | /// size field, in thousandths of a second of arc. 2^31 2081 | /// represents the equator; numbers above that are north 2082 | /// latitude. 2083 | latitude: u32, 2084 | /// The longitude of the center of the sphere described by the 2085 | /// size field, in thousandths of a second of arc, rounded 2086 | /// away from the prime meridian. 2^31 represents the prime 2087 | /// meridian; numbers above that are east longitude. 2088 | longitude: u32, 2089 | /// The altitude of the center of the sphere described by the 2090 | /// size field, in centimeters, from a base of 100,000m below 2091 | /// the [WGS 84] reference spheroid used by GPS (semimajor 2092 | /// axis a=6378137.0, reciprocal flattening rf=298.257223563). 2093 | /// Altitude above (or below) sea level may be used as an 2094 | /// approximation of altitude relative to the the [WGS 84] 2095 | /// spheroid, though due to the Earth's surface not being a 2096 | /// perfect spheroid, there will be differences. (For example, 2097 | /// the geoid (which sea level approximates) for the 2098 | /// continental US ranges from 10 meters to 50 meters below 2099 | /// the [WGS 84] spheroid. Adjustments to altitude and/or 2100 | /// vertical precision will be necessary in most cases. The 2101 | /// Defense Mapping Agency publishes geoid height values 2102 | /// relative to the [WGS 84] ellipsoid. 2103 | altitude: u32, 2104 | 2105 | pub const PrecisionSize = packed struct(u8) { 2106 | /// The power of ten by which to multiply the base. 2107 | power: u4, 2108 | base: u4, 2109 | 2110 | pub fn from_int(n: u32) PrecisionSize { 2111 | const power = std.math.log10(n); 2112 | const base = n / std.math.pow(u32, 10, power); 2113 | 2114 | return .{ 2115 | .power = @intCast(power), 2116 | .base = @intCast(base), 2117 | }; 2118 | } 2119 | 2120 | pub fn to_int(self: *const PrecisionSize) u32 { 2121 | return self.base * (std.math.pow(u32, 10, self.power)); 2122 | } 2123 | }; 2124 | 2125 | pub const LatLong = struct { 2126 | degrees: u8, 2127 | minutes: u8, 2128 | seconds: u8, 2129 | fraction_seconds: u32, 2130 | direction: Direction, 2131 | 2132 | const Direction = enum { 2133 | North, 2134 | East, 2135 | South, 2136 | West, 2137 | }; 2138 | }; 2139 | 2140 | pub const reference_altitude = 100_000 * 100; 2141 | 2142 | pub fn getLatitude(self: *const LOC) LatLong { 2143 | var latitude = self.latitude - (1 << 31); 2144 | var direction: LatLong.Direction = undefined; 2145 | if (latitude < 0) { 2146 | latitude = -latitude; 2147 | direction = .South; 2148 | } else { 2149 | direction = .North; 2150 | } 2151 | const fraction_seconds = latitude % 1000; 2152 | latitude /= 1000; 2153 | const seconds = latitude % 60; 2154 | latitude /= 60; 2155 | const minutes = latitude % 60; 2156 | latitude /= 60; 2157 | const degrees = latitude; 2158 | 2159 | return .{ 2160 | .degrees = @intCast(degrees), 2161 | .minutes = @intCast(minutes), 2162 | .seconds = @intCast(seconds), 2163 | .fraction_seconds = fraction_seconds, 2164 | .direction = direction, 2165 | }; 2166 | } 2167 | 2168 | pub fn getLongitude(self: *const LOC) LatLong { 2169 | var longitude = self.longitude - (1 << 31); 2170 | var direction: LatLong.Direction = undefined; 2171 | if (longitude < 0) { 2172 | longitude = -longitude; 2173 | direction = .East; 2174 | } else { 2175 | direction = .West; 2176 | } 2177 | const fraction_seconds = longitude % 1000; 2178 | longitude /= 1000; 2179 | const seconds = longitude % 60; 2180 | longitude /= 60; 2181 | const minutes = longitude % 60; 2182 | longitude /= 60; 2183 | const degrees = longitude; 2184 | 2185 | return .{ 2186 | .degrees = @intCast(degrees), 2187 | .minutes = @intCast(minutes), 2188 | .seconds = @intCast(seconds), 2189 | .fraction_seconds = fraction_seconds, 2190 | .direction = direction, 2191 | }; 2192 | } 2193 | 2194 | /// Relative to sea level / WGS 84, in centimeters 2195 | pub fn getAltitude(self: *const LOC) i32 { 2196 | return reference_altitude - @as(i32, @intCast(self.altitude)); 2197 | } 2198 | 2199 | pub fn to_writer(self: *const LOC, writer: anytype) !void { 2200 | try writer.writeByte(self.version); 2201 | try writer.writeByte(@bitCast(self.size)); 2202 | try writer.writeByte(@bitCast(self.horizontal_precision)); 2203 | try writer.writeByte(@bitCast(self.vertical_precision)); 2204 | try writer.writeInt(u32, self.latitude, .big); 2205 | try writer.writeInt(u32, self.longitude, .big); 2206 | try writer.writeInt(u32, self.altitude, .big); 2207 | } 2208 | 2209 | pub fn from_reader(_: mem.Allocator, reader: anytype, _: u16) !LOC { 2210 | const version = try reader.readByte(); 2211 | const size: PrecisionSize = @bitCast(try reader.readByte()); 2212 | const horizontal_precision: PrecisionSize = @bitCast(try reader.readByte()); 2213 | const vertical_prevision: PrecisionSize = @bitCast(try reader.readByte()); 2214 | const latitude = try reader.readInt(u32, .big); 2215 | const longitude = try reader.readInt(u32, .big); 2216 | const altitude = try reader.readInt(u32, .big); 2217 | 2218 | return .{ 2219 | .version = version, 2220 | .size = size, 2221 | .horizontal_precision = horizontal_precision, 2222 | .vertical_precision = vertical_prevision, 2223 | .latitude = latitude, 2224 | .longitude = longitude, 2225 | .altitude = altitude, 2226 | }; 2227 | } 2228 | 2229 | pub fn deinit(_: *const LOC) void {} 2230 | 2231 | pub fn decompress(self: LOC, _: mem.Allocator, _: []const u8) !LOC { 2232 | return self; 2233 | } 2234 | }; 2235 | }; 2236 | 2237 | /// Helper for writing errdefer blocks 2238 | fn listDeinit(list: anytype) void { 2239 | for (list.items) |item| { 2240 | item.deinit(); 2241 | } 2242 | list.deinit(); 2243 | } 2244 | 2245 | fn formatTagName(value: anytype, writer: anytype) !void { 2246 | inline for (comptime std.enums.values(@TypeOf(value))) |val| { 2247 | if (value == val) { 2248 | try writer.print("{s}", .{@tagName(value)}); 2249 | return; 2250 | } 2251 | } 2252 | try writer.print("{d}", .{@intFromEnum(value)}); 2253 | } 2254 | 2255 | test "ref all decls" { 2256 | std.testing.refAllDeclsRecursive(@This()); 2257 | } 2258 | --------------------------------------------------------------------------------