From dcea99b9c968422325c6468d112888219fbf3e76 Mon Sep 17 00:00:00 2001 From: akiroz Date: Mon, 6 Dec 2021 09:44:33 +0800 Subject: [PATCH] Implement server --- src/client.zig | 8 +- src/config.zig | 1 - src/mqtt.zig | 17 +- src/rheia/LICENSE | 201 ---------- src/rheia/intrusive.zig | 179 --------- src/rheia/lru.zig | 866 ---------------------------------------- src/server.zig | 121 ++++-- zika_config.sample.json | 2 - 8 files changed, 107 insertions(+), 1288 deletions(-) delete mode 100644 src/rheia/LICENSE delete mode 100644 src/rheia/intrusive.zig delete mode 100644 src/rheia/lru.zig diff --git a/src/client.zig b/src/client.zig index 78d17a3..b48c5dc 100644 --- a/src/client.zig +++ b/src/client.zig @@ -99,6 +99,8 @@ pub const Client = struct { var arena = ArenaAllocator.init(parent_alloc); const self = try arena.allocator.create(Self); self.arena = arena; + self.ifce = null; + self.mqtt = null; errdefer self.deinit(); const alloc = &self.arena.allocator; @@ -125,8 +127,8 @@ pub const Client = struct { } pub fn deinit(self: *Self) void { - self.mqtt.?.deinit(); - self.ifce.?.deinit(); + if(self.mqtt) |m| m.deinit(); + if(self.ifce) |i| i.deinit(); self.arena.deinit(); } @@ -184,6 +186,6 @@ pub fn main() !void { const alloc = &gpa.allocator; const conf_path = try std.fs.cwd().realpathAlloc(alloc, "zika_config.json"); const conf = try config.get(alloc, conf_path); - const client = try Client(void).init(alloc, &conf); + const client = try Client.init(alloc, &conf); try client.run(); } diff --git a/src/config.zig b/src/config.zig index 707435f..f75431c 100644 --- a/src/config.zig +++ b/src/config.zig @@ -43,7 +43,6 @@ pub const DriverConfig = struct { pub const ServerConfig = struct { id_length: u8 = 4, topic: []const u8, - max_tunnels: u16 = 16, pool_start: []const u8, pool_end: []const u8, }; diff --git a/src/mqtt.zig b/src/mqtt.zig index 71f2344..f7add30 100644 --- a/src/mqtt.zig +++ b/src/mqtt.zig @@ -38,6 +38,7 @@ pub fn Client(comptime T: type) type { alloc: *Allocator, mosq: *Mosq.mosquitto, + mosq_thread: ?*std.Thread, conf: Conf, host_cstr: []const u8, @@ -116,20 +117,28 @@ pub fn Client(comptime T: type) type { } pub fn deinit(self: *Self) void { + _ = Mosq.mosquitto_disconnect(self.mosq); + if(self.mosq_thread) |t| std.Thread.wait(t); Mosq.mosquitto_destroy(self.mosq); } pub fn connect(self: *Self) !void { - var rc = Mosq.mosquitto_loop_start(self.mosq); + _ = Mosq.mosquitto_threaded_set(self.mosq, true); + self.mosq_thread = try std.Thread.spawn(thread_main, self); + + const keepalive = self.conf.opts.keepalive_interval; + const rc = Mosq.mosquitto_connect_async(self.mosq, @ptrCast([*c]const u8, self.host_cstr), self.conf.port, keepalive); if (rc != Mosq.MOSQ_ERR_SUCCESS) { - std.log.err("mosquitto_loop_start: {s}", .{Mosq.mosquitto_strerror(rc)}); + std.log.err("mosquitto_connect_async: {s}", .{Mosq.mosquitto_strerror(rc)}); return Error.ConnectFailed; } + } + fn thread_main(self: *Self) !void { const keepalive = self.conf.opts.keepalive_interval; - rc = Mosq.mosquitto_connect_async(self.mosq, @ptrCast([*c]const u8, self.host_cstr), self.conf.port, keepalive); + const rc = Mosq.mosquitto_loop_forever(self.mosq, keepalive*1000, 1); if (rc != Mosq.MOSQ_ERR_SUCCESS) { - std.log.err("mosquitto_connect_async: {s}", .{Mosq.mosquitto_strerror(rc)}); + std.log.err("mosquitto_loop_forever: {s}", .{Mosq.mosquitto_strerror(rc)}); return Error.ConnectFailed; } } diff --git a/src/rheia/LICENSE b/src/rheia/LICENSE deleted file mode 100644 index e563385..0000000 --- a/src/rheia/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 Kenta Iwasaki - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/src/rheia/intrusive.zig b/src/rheia/intrusive.zig deleted file mode 100644 index aa8e430..0000000 --- a/src/rheia/intrusive.zig +++ /dev/null @@ -1,179 +0,0 @@ -const std = @import("std"); - -const meta = std.meta; - -/// A singly-linked list. Keeps track of one head pointer. -pub fn SinglyLinkedList(comptime T: type, comptime next_field: meta.FieldEnum(T)) type { - const next = meta.fieldInfo(T, next_field).name; - - return struct { - const Self = @This(); - - head: ?*T = null, - - pub fn isEmpty(self: *const Self) bool { - return self.head == null; - } - - pub fn prepend(self: *Self, value: *T) void { - if (!self.isEmpty() and self.head == value) return; - @field(value, next) = self.head; - self.head = value; - } - - pub fn popFirst(self: *Self) ?*T { - const head = self.head orelse return null; - self.head = @field(head, next); - @field(head, next) = null; - return head; - } - }; -} - -/// A double-ended doubly-linked list (doubly-linked deque). Keeps track of two pointers: one head pointer, and one tail pointer. -pub fn DoublyLinkedDeque(comptime T: type, comptime next_field: anytype, comptime prev_field: anytype) type { - const next = meta.fieldInfo(T, next_field).name; - const prev = meta.fieldInfo(T, prev_field).name; - - return struct { - const Self = @This(); - - head: ?*T = null, - tail: ?*T = null, - - pub fn isEmpty(self: *const Self) bool { - return self.head == null; - } - - pub fn prepend(self: *Self, value: *T) void { - if (self.head) |head| { - if (head == value) return; - @field(head, prev) = value; - } else { - self.tail = value; - } - @field(value, prev) = null; - @field(value, next) = self.head; - self.head = value; - } - - pub fn append(self: *Self, value: *T) void { - if (self.tail) |tail| { - if (tail == value) return; - @field(tail, next) = value; - } else { - self.head = value; - } - @field(value, prev) = self.tail; - @field(value, next) = null; - self.tail = value; - } - - pub fn concat(self: *Self, other: Self) void { - if (self.tail) |tail| { - @field(tail, next) = other.head; - if (other.head) |other_head| { - @field(other_head, prev) = self.tail; - } - } else { - self.head = other.head; - } - self.tail = other.tail; - } - - pub fn popFirst(self: *Self) ?*T { - const head = self.head orelse return null; - if (@field(head, next)) |next_value| { - @field(next_value, prev) = null; - } else { - self.tail = null; - } - self.head = @field(head, next); - @field(head, next) = null; - @field(head, prev) = null; - return head; - } - - pub fn pop(self: *Self) ?*T { - const tail = self.tail orelse return null; - if (@field(tail, prev)) |prev_value| { - @field(prev_value, next) = null; - } else { - self.head = null; - } - self.tail = @field(tail, prev); - @field(tail, next) = null; - @field(tail, prev) = null; - return tail; - } - - pub fn remove(self: *Self, value: *T) bool { - if (self.head == null) { - return false; - } - - if (self.head != value and @field(value, next) == null and @field(value, prev) == null) { - return false; - } - - if (@field(value, next)) |next_value| { - @field(next_value, prev) = @field(value, prev); - } else { - self.tail = @field(value, prev); - } - if (@field(value, prev)) |prev_value| { - @field(prev_value, next) = @field(value, next); - } else { - self.head = @field(value, next); - } - - @field(value, next) = null; - @field(value, prev) = null; - - return true; - } - }; -} - -/// A double-ended singly-linked list (singly-linked deque). Keeps track of two pointers: one head pointer, and one tail pointer. -pub fn SinglyLinkedDeque(comptime T: type, comptime next_field: meta.FieldEnum(T)) type { - const next = meta.fieldInfo(T, next_field).name; - - return struct { - const Self = @This(); - - head: ?*T = null, - tail: ?*T = null, - - pub fn isEmpty(self: *const Self) bool { - return self.head == null; - } - - pub fn prepend(self: *Self, value: *T) void { - if (self.head) |head| { - if (head == value) return; - } else { - self.tail = value; - } - @field(value, next_field) = self.head; - self.head = value; - } - - pub fn append(self: *Self, value: *T) void { - if (self.tail) |tail| { - if (tail == value) return; - @field(tail, next) = value; - } else { - self.head = value; - } - self.tail = value; - } - - pub fn popFirst(self: *Self) ?*T { - const head = self.head orelse return null; - self.head = @field(head, next); - @field(head, next) = null; - return head; - } - }; -} diff --git a/src/rheia/lru.zig b/src/rheia/lru.zig deleted file mode 100644 index b96dd21..0000000 --- a/src/rheia/lru.zig +++ /dev/null @@ -1,866 +0,0 @@ -const std = @import("std"); - -const mem = std.mem; -const math = std.math; -const testing = std.testing; - -const SinglyLinkedList = @import("intrusive.zig").SinglyLinkedList; -const DoublyLinkedDeque = @import("intrusive.zig").DoublyLinkedDeque; - -const assert = std.debug.assert; - -pub fn AutoHashMap( - comptime K: type, - comptime V: type, - comptime max_load_percentage: comptime_int, -) type { - return HashMap(K, V, max_load_percentage, std.hash_map.AutoContext(K)); -} - -pub fn HashMap( - comptime K: type, - comptime V: type, - comptime max_load_percentage: comptime_int, - comptime Context: type, -) type { - return struct { - pub const Entry = struct { - key: K = undefined, - value: V = undefined, - prev: ?*Entry = null, - next: ?*Entry = null, - }; - - const Self = @This(); - - entries: [*]?*Entry, - nodes: [*]Entry, - - len: usize = 0, - shift: u6, - - free: SinglyLinkedList(Entry, .next), - live: DoublyLinkedDeque(Entry, .next, .prev) = .{}, - - put_probe_count: usize = 0, - get_probe_count: usize = 0, - del_probe_count: usize = 0, - - pub fn initCapacity(gpa: *mem.Allocator, capacity: u64) !Self { - assert(math.isPowerOfTwo(capacity)); - - const shift = 63 - math.log2_int(u64, capacity) + 1; - const overflow = capacity / 10 + (63 - @as(u64, shift) + 1) << 1; - - const entries = try gpa.alloc(?*Entry, @intCast(usize, capacity + overflow)); - errdefer gpa.free(entries); - - const nodes = try gpa.alloc(Entry, @intCast(usize, capacity * max_load_percentage / 100)); - errdefer gpa.free(nodes); - - mem.set(?*Entry, entries, null); - mem.set(Entry, nodes, .{}); - - var free: SinglyLinkedList(Entry, .next) = .{}; - for (nodes) |*node| free.prepend(node); - - return Self{ - .entries = entries.ptr, - .nodes = nodes.ptr, - .shift = shift, - .free = free, - }; - } - - pub fn deinit(self: *Self, gpa: *mem.Allocator) void { - const capacity = @as(u64, 1) << (63 - self.shift + 1); - const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; - gpa.free(self.entries[0..@intCast(usize, capacity + overflow)]); - gpa.free(self.nodes[0..@intCast(usize, capacity * max_load_percentage / 100)]); - } - - pub fn clear(self: *Self) void { - const capacity = @as(u64, 1) << (63 - self.shift + 1); - const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; - mem.set(?*Entry, self.entries[0..@intCast(usize, capacity + overflow)], null); - mem.set(Entry, self.nodes[0..@intCast(usize, capacity * max_load_percentage / 100)], .{}); - self.len = 0; - } - - pub fn slice(self: *Self) []?*Entry { - const capacity = @as(u64, 1) << (63 - self.shift + 1); - const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; - return self.entries[0..@intCast(usize, capacity + overflow)]; - } - - pub const KV = struct { - key: K, - value: V, - }; - - pub const GetOrPutResult = struct { - evicted: ?KV, - node: *Entry, - found_existing: bool, - }; - - pub fn getOrPut(self: *Self, key: K) GetOrPutResult { - if (@sizeOf(Context) != 0) { - @compileError("getOrPutContext must be used."); - } - return self.getOrPutContext(key, undefined); - } - - pub fn getOrPutContext(self: *Self, key: K, ctx: Context) GetOrPutResult { - var it: ?*Entry = null; - var i = ctx.hash(key) >> self.shift; - - var inserted_at: ?usize = null; - while (true) : (i += 1) { - const entry = self.entries[i] orelse { - self.entries[i] = it; - - if (self.free.popFirst()) |node| { - node.key = key; - self.entries[inserted_at orelse i] = node; - self.len += 1; - - return .{ - .evicted = null, - .node = node, - .found_existing = false, - }; - } else { - const tail = self.live.tail.?; - - const evicted: KV = .{ - .key = tail.key, - .value = tail.value, - }; - - self.entries[inserted_at orelse i] = &Entry{ .key = key }; - const tail_index = self.getIndex(tail.key).?; - self.entries[inserted_at orelse i] = tail; - - self.shiftBackwardsContext(tail_index, ctx); - - tail.key = key; - tail.value = undefined; - - return .{ - .evicted = evicted, - .node = tail, - .found_existing = false, - }; - } - }; - if (ctx.hash(entry.key) > ctx.hash(if (it) |node| node.key else key)) { - self.entries[i] = it; - if (inserted_at == null) { - inserted_at = i; - } - it = entry; - } else if (ctx.eql(entry.key, key)) { - return .{ - .evicted = null, - .node = entry, - .found_existing = true, - }; - } - self.put_probe_count += 1; - } - } - - pub fn getIndex(self: *Self, key: K) ?usize { - if (@sizeOf(Context) != 0) { - @compileError("getContext must be used."); - } - return self.getIndexContext(key, undefined); - } - - pub fn getIndexContext(self: *Self, key: K, ctx: Context) ?usize { - const hash = ctx.hash(key); - - var i = hash >> self.shift; - while (true) : (i += 1) { - const entry = self.entries[i] orelse return null; - if (ctx.hash(entry.key) > hash) return null; - if (ctx.eql(entry.key, key)) return i; - self.get_probe_count += 1; - } - } - - pub fn get(self: *Self, key: K) ?*Entry { - if (@sizeOf(Context) != 0) { - @compileError("getContext must be used."); - } - return self.getContext(key, undefined); - } - - pub fn getContext(self: *Self, key: K, ctx: Context) ?*Entry { - const hash = ctx.hash(key); - - var i = hash >> self.shift; - while (true) : (i += 1) { - const entry = self.entries[i] orelse return null; - if (ctx.hash(entry.key) > hash) return null; - if (ctx.eql(entry.key, key)) return entry; - self.get_probe_count += 1; - } - } - - pub fn moveToFront(self: *Self, node: *Entry) void { - _ = self.live.remove(node); - self.live.prepend(node); - } - - pub fn moveToBack(self: *Self, node: *Entry) void { - _ = self.live.remove(node); - self.live.append(node); - } - - pub fn delete(self: *Self, key: K) ?KV { - if (@sizeOf(Context) != 0) { - @compileError("deleteContext must be used."); - } - return self.deleteContext(key, undefined); - } - - pub fn deleteContext(self: *Self, key: K, ctx: Context) ?KV { - const hash = ctx.hash(key); - - var i = hash >> self.shift; - while (true) : (i += 1) { - const entry = self.entries[i] orelse return null; - if (ctx.hash(entry.key) > hash) return null; - if (ctx.eql(entry.key, key)) break; - self.del_probe_count += 1; - } - - const entry = self.entries[i].?; - - const kv: KV = .{ - .key = entry.key, - .value = entry.value, - }; - - assert(self.live.remove(entry)); - self.free.prepend(entry); - - self.shiftBackwardsContext(i, ctx); - self.len -= 1; - - return kv; - } - - fn shiftBackwards(self: *Self, i_const: usize) void { - if (@sizeOf(Context) != 0) { - @compileError("shiftBackwardsContext must be used."); - } - self.shiftBackwardsContext(i_const, undefined); - } - - fn shiftBackwardsContext(self: *Self, i_const: usize, ctx: Context) void { - var i = i_const; - while (true) : (i += 1) { - const next_entry = self.entries[i + 1] orelse break; - const j = ctx.hash(next_entry.key) >> self.shift; - if (i < j) break; - - self.entries[i] = self.entries[i + 1]; - self.del_probe_count += 1; - } - - self.entries[i] = null; - } - }; -} - -pub fn AutoIntrusiveHashMap( - comptime K: type, - comptime V: type, - comptime max_load_percentage: comptime_int, -) type { - return IntrusiveHashMap(K, V, max_load_percentage, std.hash_map.AutoContext(K)); -} - -pub fn IntrusiveHashMap( - comptime K: type, - comptime V: type, - comptime max_load_percentage: comptime_int, - comptime Context: type, -) type { - return struct { - pub const Entry = struct { - key: K = undefined, - value: V = undefined, - prev: ?*Entry = null, - next: ?*Entry = null, - - pub fn isEmpty(self: *const Entry, map: *const Self) bool { - return map.len == 0 or (map.head != self and self.prev == null and self.next == null); - } - - pub fn format(self: *const Entry, comptime layout: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = layout; - _ = options; - try std.fmt.format(writer, "{*} (key: {}, value: {}, prev: {*}, next: {*})", .{ - self, - self.key, - self.value, - self.prev, - self.next, - }); - } - }; - - pub const KV = struct { - key: K, - value: V, - }; - - const Self = @This(); - - entries: [*]Entry, - len: usize = 0, - shift: u6, - - head: ?*Entry = null, - tail: ?*Entry = null, - - put_probe_count: usize = 0, - get_probe_count: usize = 0, - del_probe_count: usize = 0, - - pub fn initCapacity(gpa: *mem.Allocator, capacity: u64) !Self { - assert(math.isPowerOfTwo(capacity)); - - const shift = 63 - math.log2_int(u64, capacity) + 1; - const overflow = capacity / 10 + (63 - @as(u64, shift) + 1) << 1; - - const entries = try gpa.alloc(Entry, @intCast(usize, capacity + overflow)); - mem.set(Entry, entries, .{}); - - return Self{ - .entries = entries.ptr, - .shift = shift, - }; - } - - pub fn deinit(self: *Self, gpa: *mem.Allocator) void { - gpa.free(self.slice()); - } - - pub fn clear(self: *Self) void { - mem.set(Entry, self.slice(), .{}); - self.len = 0; - self.head = null; - self.tail = null; - } - - pub fn slice(self: *Self) []Entry { - const capacity = @as(u64, 1) << (63 - self.shift + 1); - const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; - return self.entries[0..@intCast(usize, capacity + overflow)]; - } - - pub const UpdateResult = union(enum) { - // The evicted key-value pair. - evicted: KV, - /// The last value that was paired with provided key. - updated: V, - inserted, - }; - - pub fn update(self: *Self, key: K, value: V) UpdateResult { - if (@sizeOf(Context) != 0) { - @compileError("updateContext must be used."); - } - return self.updateContext(key, value, undefined); - } - - /// After calling this method, all pointers to entries except the one returned - /// should be assumed to have been invalidated. - pub fn updateContext(self: *Self, key: K, value: V, ctx: Context) UpdateResult { - const result = self.getOrPutContext(key, ctx); - const result_value = result.entry.value; - - result.entry.value = value; - self.moveToFront(result.entry); - - if (result.found_existing) { - return .{ .updated = result_value }; - } - - const capacity = @as(u64, 1) << (63 - self.shift + 1); - if (self.len > capacity * max_load_percentage / 100) { - return .{ .evicted = self.popContext(ctx).? }; - } - - return .inserted; - } - - pub const GetOrPutResult = struct { - entry: *Entry, - found_existing: bool, - }; - - /// Get or put a value at a provided key. If the key exists, the key is moved - /// to the head of the LRU cache. If the key does not exist, a new entry is - /// created for the provided value to be placed within. After calling this - /// method, all pointers to entries except the one returned should be assumed - /// to have been invalidated. - pub fn getOrPut(self: *Self, key: K) GetOrPutResult { - if (@sizeOf(Context) != 0) { - @compileError("getOrPutContext must be used."); - } - return self.getOrPutContext(key, undefined); - } - - /// Get or put a value at a provided key. If the key exists, the key is moved - /// to the head of the LRU cache. If the key does not exist, a new entry is - /// created for the provided value to be placed within. After calling this - /// method, all pointers to entries except the one returned should be assumed - /// to have been invalidated. - pub fn getOrPutContext(self: *Self, key: K, ctx: Context) GetOrPutResult { - var it: Entry = .{ .key = key, .value = undefined }; - var i = ctx.hash(key) >> self.shift; - - var inserted_at: ?usize = null; - while (true) : (i += 1) { - if (self.entries[i].isEmpty(self)) { - if (inserted_at != null) { - self.readjustNodePointers(&it, &self.entries[i]); - } - self.entries[i] = it; - self.len += 1; - return .{ - .entry = &self.entries[inserted_at orelse i], - .found_existing = false, - }; - } else if (ctx.hash(self.entries[i].key) > ctx.hash(it.key)) { - if (inserted_at == null) { - inserted_at = i; - } else { - self.readjustNodePointers(&it, &self.entries[i]); - } - mem.swap(Entry, &it, &self.entries[i]); - } else if (ctx.eql(self.entries[i].key, key)) { - assert(inserted_at == null); - return .{ - .entry = &self.entries[i], - .found_existing = true, - }; - } - self.put_probe_count += 1; - } - } - - pub fn get(self: *Self, key: K) ?*Entry { - if (@sizeOf(Context) != 0) { - @compileError("getContext must be used."); - } - return self.getContext(key, undefined); - } - - pub fn getContext(self: *Self, key: K, ctx: Context) ?*Entry { - const hash = ctx.hash(key); - - var i = hash >> self.shift; - while (true) : (i += 1) { - const entry = &self.entries[i]; - if (entry.isEmpty(self) or ctx.hash(entry.key) > hash) { - return null; - } else if (ctx.eql(entry.key, key)) { - return entry; - } - self.get_probe_count += 1; - } - } - - pub fn delete(self: *Self, key: K) ?V { - if (@sizeOf(Context) != 0) { - @compileError("deleteContext must be used."); - } - return self.deleteContext(key, undefined); - } - - pub fn deleteContext(self: *Self, key: K, ctx: Context) ?V { - const hash = ctx.hash(key); - - var i = hash >> self.shift; - while (true) : (i += 1) { - const entry = &self.entries[i]; - if (entry.isEmpty(self) or ctx.hash(entry.key) > hash) { - return null; - } else if (ctx.eql(entry.key, key)) { - break; - } - self.del_probe_count += 1; - } - - return self.deleteEntryAtIndex(i, ctx); - } - - /// Moves the entry to the front of the LRU cache. This method should NOT - /// be called if getOrPut() or update() or any other methods have been - /// called that may invalidate pointers to entries in this cache. - pub fn moveToFront(self: *Self, entry: *Entry) void { - self.removeNode(entry); - self.prependNode(entry); - } - - /// Moves the entry to the end of the LRU cache. This method should NOT - /// be called if getOrPut() or update() or any other methods have been - /// called that may invalidate pointers to entries in this cache. - pub fn moveToBack(self: *Self, entry: *Entry) void { - self.removeNode(entry); - self.appendNode(entry); - } - - pub fn popFirst(self: *Self) ?KV { - if (@sizeOf(Context) != 0) { - @compileError("popFirstContext must be used."); - } - return self.popFirstContext(undefined); - } - - pub fn popFirstContext(self: *Self, ctx: Context) ?KV { - const head = self.head orelse return null; - const head_index = (@ptrToInt(head) - @ptrToInt(self.entries)) / @sizeOf(Entry); - return KV{ .key = head.key, .value = self.deleteEntryAtIndex(head_index, ctx) }; - } - - pub fn pop(self: *Self) ?KV { - if (@sizeOf(Context) != 0) { - @compileError("popContext must be used."); - } - return self.popContext(undefined); - } - - pub fn popContext(self: *Self, ctx: Context) ?KV { - const tail = self.tail orelse return null; - const tail_index = (@ptrToInt(tail) - @ptrToInt(self.entries)) / @sizeOf(Entry); - return KV{ .key = tail.key, .value = self.deleteEntryAtIndex(tail_index, ctx) }; - } - - fn deleteEntryAtIndex(self: *Self, i_const: usize, ctx: Context) V { - const value = self.entries[i_const].value; - self.removeNode(&self.entries[i_const]); - - var i = i_const; - while (true) : (i += 1) { - const j = ctx.hash(self.entries[i + 1].key) >> self.shift; - if (i < j or self.entries[i + 1].isEmpty(self)) { - break; - } - self.entries[i] = self.entries[i + 1]; - self.readjustNodePointers(&self.entries[i], &self.entries[i]); - self.del_probe_count += 1; - } - - self.entries[i] = .{}; - self.len -= 1; - - return value; - } - - /// Prepend entry to head of linked list. - fn prependNode(self: *Self, entry: *Entry) void { - assert(entry.prev == null); - assert(entry.next == null); - if (self.head) |head| { - head.prev = entry; - } else { - self.tail = entry; - } - entry.next = self.head; - self.head = entry; - } - - /// Append entry to tail of linked list. - fn appendNode(self: *Self, entry: *Entry) void { - assert(entry.prev == null); - assert(entry.next == null); - if (self.tail) |tail| { - tail.next = entry; - } else { - self.head = entry; - } - entry.prev = self.tail; - self.tail = entry; - } - - /// Remove entry from the linked list. - fn removeNode(self: *Self, entry: *Entry) void { - if (self.head == null) { - return; - } - if (self.head != entry and entry.next == null and entry.prev == null) { - return; - } - if (entry.prev) |prev| { - prev.next = entry.next; - } else { - self.head = entry.next; - } - if (entry.next) |next| { - next.prev = entry.prev; - } else { - self.tail = entry.prev; - } - entry.next = null; - entry.prev = null; - } - - /// Re-adjust entry's linked list pointers. - fn readjustNodePointers(self: *Self, it: *Entry, entry: *Entry) void { - if (it.prev) |prev| { - prev.next = entry; - } else { - self.head = entry; - } - if (it.next) |next| { - next.prev = entry; - } else { - self.tail = entry; - } - } - }; -} - -test "lru.HashMap: eviction on insert" { - const Cache = AutoHashMap(usize, usize, 100); - - var map = try Cache.initCapacity(testing.allocator, 4); - defer map.deinit(testing.allocator); - - var i: usize = 0; - while (i < 4) : (i += 1) { - const result = map.getOrPut(i); - try testing.expect(!result.found_existing); - result.node.value = i; - map.moveToFront(result.node); - } - - while (i < 8) : (i += 1) { - const result = map.getOrPut(i); - - try testing.expect(!result.found_existing); - result.node.value = i; - map.moveToFront(result.node); - - const evicted = result.evicted orelse return error.EvictionExpected; - try testing.expectEqual(i - 4, evicted.key); - try testing.expectEqual(i - 4, evicted.value); - } - - try testing.expectEqual(@as(usize, 4), map.len); - - try testing.expectEqual(@as(usize, 7), map.live.head.?.key); - try testing.expectEqual(@as(usize, 7), map.live.head.?.value); - - try testing.expectEqual(@as(usize, 4), map.live.tail.?.key); - try testing.expectEqual(@as(usize, 4), map.live.tail.?.value); - - var it = map.live.head; - while (it) |node| : (it = node.next) { - try testing.expectEqual(i - 1, node.key); - try testing.expectEqual(i - 1, node.value); - i -= 1; - } - - while (i < 8) : (i += 1) { - const kv = map.delete(i) orelse return error.ExpectedSuccessfulDeletion; - try testing.expectEqual(i, kv.key); - try testing.expectEqual(i, kv.value); - } - try testing.expectEqual(@as(usize, 0), map.len); - try testing.expectEqual(@as(?*Cache.Entry, null), map.live.head); - try testing.expectEqual(@as(?*Cache.Entry, null), map.live.tail); -} - -test "lru.HashMap: update, get, delete without eviction" { - const Cache = AutoHashMap(usize, usize, 100); - - var seed: usize = 0; - while (seed < 10_000) : (seed += 1) { - var rng = std.rand.DefaultPrng.init(seed); - - const keys = try testing.allocator.alloc(usize, 128); - defer testing.allocator.free(keys); - - for (keys) |*key| key.* = rng.random.int(usize); - - var map = try Cache.initCapacity(testing.allocator, 128); - defer map.deinit(testing.allocator); - - // add all entries - - for (keys) |key, i| { - const result = map.getOrPut(key); - try testing.expect(!result.found_existing); - try testing.expect(result.evicted == null); - result.node.value = i; - map.moveToFront(result.node); - } - - for (keys) |key, i| try testing.expectEqual(i, map.get(key).?.value); - try testing.expectEqual(keys.len, map.len); - - try testing.expectEqual(keys[keys.len - 1], map.live.head.?.key); - try testing.expectEqual(keys.len - 1, map.live.head.?.value); - - try testing.expectEqual(keys[0], map.live.tail.?.key); - try testing.expectEqual(@as(usize, 0), map.live.tail.?.value); - - // randomly promote half of all entries to head except tail - - var key_index: usize = 0; - while (key_index < keys.len / 2) : (key_index += 1) { - const index = rng.random.intRangeAtMost(usize, 1, keys.len - 1); - - const result = map.getOrPut(keys[index]); - try testing.expect(result.found_existing); - try testing.expect(result.evicted == null); - result.node.value = index; - map.moveToFront(result.node); - - try testing.expectEqual(keys[index], map.live.head.?.key); - try testing.expectEqual(index, map.live.head.?.value); - - try testing.expectEqual(keys[0], map.live.tail.?.key); - try testing.expectEqual(@as(usize, 0), map.live.tail.?.value); - } - - // promote tail to head - - const expected = map.live.tail.?.prev.?; - - const result = map.getOrPut(keys[0]); - try testing.expect(result.found_existing); - try testing.expect(result.evicted == null); - result.node.value = 0; - map.moveToFront(result.node); - - for (keys) |key, i| try testing.expectEqual(i, map.get(key).?.value); - try testing.expectEqual(keys.len, map.len); - - try testing.expectEqual(keys[0], map.live.head.?.key); - try testing.expectEqual(@as(usize, 0), map.live.head.?.value); - - try testing.expectEqual(expected.key, map.live.tail.?.key); - try testing.expectEqual(expected.value, map.live.tail.?.value); - - // delete all entries - - for (keys) |key, i| try testing.expectEqual(i, map.delete(key).?.value); - try testing.expectEqual(@as(usize, 0), map.len); - try testing.expectEqual(@as(?*Cache.Entry, null), map.live.head); - try testing.expectEqual(@as(?*Cache.Entry, null), map.live.tail); - } -} - -test "lru.IntrusiveHashMap: eviction on insert" { - const Cache = AutoIntrusiveHashMap(usize, usize, 100); - - var map = try Cache.initCapacity(testing.allocator, 4); - defer map.deinit(testing.allocator); - - var i: usize = 0; - while (i < 4) : (i += 1) { - try testing.expectEqual(Cache.UpdateResult.inserted, map.update(i, i)); - } - - while (i < 8) : (i += 1) { - const evicted = map.update(i, i).evicted; - try testing.expectEqual(i - 4, evicted.key); - try testing.expectEqual(i - 4, evicted.value); - } - - try testing.expectEqual(@as(usize, 4), map.len); - - try testing.expectEqual(@as(usize, 7), map.head.?.key); - try testing.expectEqual(@as(usize, 7), map.head.?.value); - - try testing.expectEqual(@as(usize, 4), map.tail.?.key); - try testing.expectEqual(@as(usize, 4), map.tail.?.value); - - var it = map.head; - while (it) |node| : (it = node.next) { - try testing.expectEqual(i - 1, node.key); - try testing.expectEqual(i - 1, node.value); - i -= 1; - } - - while (i < 8) : (i += 1) { - try testing.expectEqual(i, map.delete(i).?); - } - try testing.expectEqual(@as(usize, 0), map.len); - try testing.expectEqual(@as(?*Cache.Entry, null), map.head); - try testing.expectEqual(@as(?*Cache.Entry, null), map.tail); -} - -test "lru.IntrusiveHashMap: update, get, delete without eviction" { - const Cache = AutoIntrusiveHashMap(usize, usize, 100); - - var seed: usize = 0; - while (seed < 10_000) : (seed += 1) { - var rng = std.rand.DefaultPrng.init(seed); - - const keys = try testing.allocator.alloc(usize, 128); - defer testing.allocator.free(keys); - - for (keys) |*key| key.* = rng.random.int(usize); - - var map = try Cache.initCapacity(testing.allocator, 128); - defer map.deinit(testing.allocator); - - // add all entries - - for (keys) |key, i| try testing.expectEqual(Cache.UpdateResult.inserted, map.update(key, i)); - for (keys) |key, i| try testing.expectEqual(i, map.get(key).?.value); - try testing.expectEqual(keys.len, map.len); - - try testing.expectEqual(keys[keys.len - 1], map.head.?.key); - try testing.expectEqual(keys.len - 1, map.head.?.value); - - try testing.expectEqual(keys[0], map.tail.?.key); - try testing.expectEqual(@as(usize, 0), map.tail.?.value); - - // randomly promote half of all entries to head except tail - - var key_index: usize = 0; - while (key_index < keys.len / 2) : (key_index += 1) { - const index = rng.random.intRangeAtMost(usize, 1, keys.len - 1); - try testing.expectEqual(Cache.UpdateResult.updated, map.update(keys[index], index)); - - try testing.expectEqual(keys[index], map.head.?.key); - try testing.expectEqual(index, map.head.?.value); - - try testing.expectEqual(keys[0], map.tail.?.key); - try testing.expectEqual(@as(usize, 0), map.tail.?.value); - } - - // promote tail to head - - const expected = map.tail.?.prev.?; - - try testing.expectEqual(Cache.UpdateResult.updated, map.update(keys[0], 0)); - for (keys) |key, i| try testing.expectEqual(i, map.get(key).?.value); - try testing.expectEqual(keys.len, map.len); - - try testing.expectEqual(keys[0], map.head.?.key); - try testing.expectEqual(@as(usize, 0), map.head.?.value); - - try testing.expectEqual(expected.key, map.tail.?.key); - try testing.expectEqual(expected.value, map.tail.?.value); - - // delete all entries - - for (keys) |key, i| try testing.expectEqual(i, map.delete(key).?); - try testing.expectEqual(@as(usize, 0), map.len); - try testing.expectEqual(@as(?*Cache.Entry, null), map.head); - try testing.expectEqual(@as(?*Cache.Entry, null), map.tail); - } -} diff --git a/src/server.zig b/src/server.zig index c7366a0..d507706 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1,86 +1,143 @@ const builtin = @import("builtin"); const std = @import("std"); -const rheia = @import("rheia/lru.zig"); const mqtt = @import("mqtt.zig"); const driver = @import("driver.zig"); const config = @import("config.zig"); const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; const Ip4Address = std.net.Ip4Address; const Base64UrlEncoder = std.base64.url_safe_no_pad.Encoder; const Config = config.Config; const NetInterface = driver.NetInterface; const IpHeader = driver.IpHeader; +const Mqtt = mqtt.Mqtt; pub const Server = struct { const Self = @This(); - const IdCache = rheia.AutoHashMap(u32, []u8, 100); - const IpCache = rheia.AutoHashMap(u128, u32, 100); + const IpCache = std.AutoHashMap(u128, u32); + const IdCache = std.AutoHashMap(u32, u128); + const TopicCache = std.AutoHashMap(u32, [:0]u8); const Error = error { ConfigMissing }; - max_tunnels: u16, + arena: ArenaAllocator, + ifce: ?*NetInterface(*Self), + mqtt: ?*Mqtt(*Self), + pool_start: Ip4Address, pool_end: Ip4Address, - id_cache: IdCache, + pool_next_alloc: u32, + + id_len: u8, ip_cache: IpCache, - ifce: NetInterface(*Self), + id_cache: IdCache, + + b64_len: usize, + topic: []const u8, + topic_cache: TopicCache, - pub fn init(alloc: *Allocator, conf: * const Config) !*Self { + pub fn init(parent_alloc: *Allocator, conf: *const Config) !*Self { const server_conf = conf.server orelse { std.log.err("missing server config", .{}); return Error.ConfigMissing; }; - const self = try alloc.create(Self); - errdefer alloc.destroy(self); + var arena = ArenaAllocator.init(parent_alloc); + const self = try arena.allocator.create(Self); + self.arena = arena; + self.ifce = null; + self.mqtt = null; + errdefer self.deinit(); + const alloc = &self.arena.allocator; - self.max_tunnels = roundPowerOf2(server_conf.max_tunnels); self.pool_start = try Ip4Address.parse(server_conf.pool_start, 0); self.pool_end = try Ip4Address.parse(server_conf.pool_end, 0); - self.id_cache = try IdCache.initCapacity(alloc, self.max_tunnels); - self.ip_cache = try IpCache.initCapacity(alloc, self.max_tunnels); + self.pool_next_alloc = self.pool_start.sa.addr; + self.id_len = server_conf.id_length; + self.ip_cache = IpCache.init(alloc); + self.id_cache = IdCache.init(alloc); + self.b64_len = Base64UrlEncoder.calcSize(self.id_len); + self.topic = server_conf.topic; + self.topic_cache = TopicCache.init(alloc); std.log.info("== Server Config =================================", .{}); std.log.info("ID Length: {d}", .{server_conf.id_length}); std.log.info("Topic: {s}", .{server_conf.topic}); - std.log.info("Max Tunnels: {d} (rounded to 2^n)", .{self.max_tunnels}); std.log.info("IP Pool: {s} - {s}", .{server_conf.pool_start, server_conf.pool_end}); - self.ifce = try NetInterface(*Self).init(alloc, conf, self, @ptrCast(driver.PacketHandler(*Self), &recv)); + self.ifce = try NetInterface(*Self).init(alloc, conf, self, @ptrCast(driver.PacketHandler(*Self), &up)); + self.mqtt = try Mqtt(*Self).init(alloc, conf, self, @ptrCast(mqtt.PacketHandler(*Self), &down), 1); std.log.info("==================================================", .{}); return self; } - fn recv(self: *Self, pkt: []u8) void { - std.log.info("got pkt {d}", .{pkt.len}); + pub fn deinit(self: *Self) void { + if(self.mqtt) |m| m.deinit(); + if(self.ifce) |i| i.deinit(); + self.arena.deinit(); + } + + pub fn run(self: *Self) !void { + try self.mqtt.?.connect(); + try self.ifce.?.run(); + } + + fn allocIp(self: *Self, id: u128) !u32 { + const alloc = &self.arena.allocator; + const next_addr = self.pool_next_alloc; + self.pool_next_alloc += 1; + if(self.pool_next_alloc > self.pool_end.sa.addr) { + self.pool_next_alloc = self.pool_start.sa.addr; + } + + if(self.id_cache.fetchRemove(next_addr)) |entry| { + alloc.free(self.b64_cache.fetchRemove(next_addr).?.value); + _ = self.ip_cache.remove(entry.value); + } + + try self.ip_cache.put(id, next_addr); + try self.id_cache.put(next_addr, id); + + var b64_id = try alloc.alloc(u8, self.b64_len); + defer alloc.free(b64_id); + var id_bytes = std.mem.toBytes(id); + id_bytes.len = self.id_len; + Base64UrlEncoder.encode(b64_id, id_bytes); + const topic = try std.fmt.allocPrint(alloc, "{s}/{s}", .{self.topic, b64_id}); + try self.topic_cache.put(next_addr, topic); + + return next_addr; } - fn allocIp(self: *Self, id: u128) u32 { - const id_str = std.mem.zeroes([B64_ID_SIZE]u8); - Base64UrlEncoder.encode(id_str, *@ptrCast([]u8, &id)); - // TODO + fn up(self: *Self, pkt: []u8) void { + const hdr = @ptrCast(*IpHeader, pkt); + if(self.topic_cache.get(hdr.dst)) |topic| { + self.mqtt.?.send(topic, pkt) catch |err| { + std.log.warn("up: {s}", .{err}); + }; + } } - fn roundPowerOf2(val: u16) u16 { - var v = val; - v -= 1; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v += 1; - return v; + fn down(self: *Self, topic: []const u8, msg: []u8) void { + var id: u128 = 0; + std.mem.copy(u8, std.mem.toBytes(id)[0..], msg[0..self.id_len]); + if(self.ip_cache.get(id)) |addr| { + self.ifce.?.inject(addr, msg[self.id_len..]) catch |err| { + std.log.warn("down: {s}", .{err}); + }; + } } }; pub fn main() !void { - const alloc = std.heap.c_allocator; + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = &gpa.allocator; const conf_path = try std.fs.cwd().realpathAlloc(alloc, "zika_config.json"); - const conf = try config.get(conf_path); + const conf = try config.get(alloc, conf_path); const server = try Server.init(alloc, &conf); - while (true) std.time.sleep(10_000_000_000); + try server.run(); } diff --git a/zika_config.sample.json b/zika_config.sample.json index 450bbca..0b189af 100644 --- a/zika_config.sample.json +++ b/zika_config.sample.json @@ -37,8 +37,6 @@ "// In bytes (Optional, default shown, must match client)": "", "id_length": 4, "topic": "zika/OjFcZWEAGy2E3Vkh", - "// Optional, default shown": "", - "max_tunnels": 16, "pool_start": "172.20.0.100", "pool_end": "172.20.0.200" },