diff --git a/README.md b/README.md index 6097d2e..68b7d69 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ Astronomical and Spacecraft Toolkit Written in Zig for Zig! ### Spacecraft - [x] CCSDS Packets - - [ ] CCSDS Stream Parser + - [x] CCSDS Stream Parser - [x] VITA49 Packets - - [ ] Vita49 Stream Parser + - [x] Vita49 Stream Parser - [ ] Orbital Maneuvers - [ ] Impulse Maneuvers - [ ] Phase Maneuvers @@ -70,7 +70,56 @@ b.installArtifact(exe); ### Examples -#### Create a CCSDS Parser +### Setup Vita49 Parser + +#### W/ Callback + +```zig +const std = @import("std"); +const astroz = @import("astroz"); +const Vita49 = astroz.vita49.Vita49; +const Parser = astroz.parsers.Parser; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const P = Parser(Vita49); + const ip = "127.0.0.1".*; + const port: u16 = 65432; + var parser = P.new(&ip, port, 1024, allocator); + _ = try parser.start(callback); +} + +fn callback(packet: Vita49) void { + std.debug.print("Packet recieved: {any}", .{packet}); +} +``` + +#### W/O Callback + +```zig +const std = @import("std"); +const astroz = @import("astroz"); +const Vita49 = astroz.vita49.Vita49; +const Parser = astroz.parsers.Parser; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const P = Parser(Vita49); + const ip = "127.0.0.1".*; + const port: u16 = 65432; + var parser = P.new(&ip, port, 1024, allocator); + _ = try parser.start(null); +} + +``` + +#### Create a CCSDS Packet ##### W/O Config @@ -122,7 +171,6 @@ pub fn main() !void { ``` - #### precess a star to July 30, 2005 ```zig diff --git a/build.zig b/build.zig index 02ea037..453d4da 100644 --- a/build.zig +++ b/build.zig @@ -56,6 +56,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + _ = b.addModule("astroz.parsers", .{ + .root_source_file = b.path("src/parser.zig"), + .target = target, + .optimize = optimize, + }); + const coord_unit_tests = b.addTest(.{ .root_source_file = b.path("src/coordinates.zig"), }); @@ -86,10 +92,17 @@ pub fn build(b: *std.Build) void { const run_vita49_unit_tests = b.addRunArtifact(vita49_unit_tests); + const parsers_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/parsers.zig"), + }); + + const run_parsers_unit_tests = b.addRunArtifact(parsers_unit_tests); + const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_coord_unit_tests.step); test_step.dependOn(&run_ccsds_unit_tests.step); test_step.dependOn(&run_time_unit_tests.step); test_step.dependOn(&run_constants_unit_tests.step); test_step.dependOn(&run_vita49_unit_tests.step); + test_step.dependOn(&run_parsers_unit_tests.step); } diff --git a/src/ccsds.zig b/src/ccsds.zig index 637c7d3..f0a2b2c 100644 --- a/src/ccsds.zig +++ b/src/ccsds.zig @@ -16,7 +16,7 @@ pub const CCSDS = struct { const Self = @This(); - pub fn new(raw_packets: []const u8, config: ?Config) Self { + pub fn new(raw_packets: []const u8, config: ?Config) !Self { const primary_header = raw_packets[0..6]; const version = @as(u3, @truncate((primary_header[0] >> 5) & 0x07)); const packet_type = @as(u1, @truncate((primary_header[0] >> 4) & 0x01)); @@ -29,7 +29,7 @@ pub const CCSDS = struct { var start: u8 = 6; const secondary_header: ?[]const u8 = if (secondary_header_flag) blk: { if (raw_packets.len < 10) { - std.debug.print("packet length is too short to have a secondary header", .{}); + std.log.warn("packet length is too short to have a secondary header", .{}); break :blk null; } start = if (config != null) config.?.secondary_header_length else 10; @@ -63,7 +63,7 @@ test "CCSDS Structure Testing w/ config" { const test_allocator = std.testing.allocator; const config = try parse_config(test_config, test_allocator); - const converted_test_packet = CCSDS.new(&raw_test_packet, config); + const converted_test_packet = try CCSDS.new(&raw_test_packet, config); const packets = .{ 7, 8, 9, 10 }; @@ -78,7 +78,7 @@ test "CCSDS Structure Testing w/ config" { test "CCSDS Structure Testing w/o config" { const raw_test_packet: [16]u8 = .{ 0x78, 0x97, 0xC0, 0x00, 0x00, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A }; - const converted_test_packet = CCSDS.new(&raw_test_packet, null); + const converted_test_packet = try CCSDS.new(&raw_test_packet, null); const packets = .{ 5, 6, 7, 8, 9, 10 }; diff --git a/src/lib.zig b/src/lib.zig index 0e86467..8d8e7ca 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3,3 +3,5 @@ pub const constants = @import("constants.zig"); pub const time = @import("time.zig"); pub const coordinates = @import("coordinates.zig"); pub const calculations = @import("calculations.zig"); +pub const vita49 = @import("vita49.zig"); +pub const parsers = @import("parsers.zig"); diff --git a/src/parsers.zig b/src/parsers.zig new file mode 100644 index 0000000..24cb931 --- /dev/null +++ b/src/parsers.zig @@ -0,0 +1,200 @@ +const std = @import("std"); +const net = std.net; +const Vita49 = @import("vita49.zig").Vita49; +const CCSDS = @import("ccsds.zig").CCSDS; + +pub fn Parser(comptime Frame: type) type { + return struct { + ip_address: []const u8, + port: u16, + buffer_size: u64 = 1024, + is_running: bool = false, + should_stop: bool = false, + packets: std.ArrayList(Frame), + allocator: std.mem.Allocator, + + const Self = @This(); + + pub fn new(ip_address: []const u8, port: u16, buffer_size: u64, allocator: std.mem.Allocator) Self { + return .{ + .ip_address = ip_address, + .port = port, + .buffer_size = buffer_size, + .packets = std.ArrayList(Frame).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: *Self) void { + self.packets.deinit(); + } + + pub fn parse_from_file(self: Self) void { + self.is_running; + } + + pub fn start(self: *Self, comptime callback: ?fn (Frame) void) !void { + const addr = try net.Address.parseIp4(self.ip_address, self.port); + + const stream = try net.tcpConnectToAddress(addr); + defer stream.close(); + + std.log.info("connected to socket successful", .{}); + + var incoming_buffer = std.mem.zeroes([1024]u8); + while (!self.should_stop) { + _ = try stream.read(&incoming_buffer); + const new_frame = try Frame.new(&incoming_buffer, null); + std.log.info("message recieved: {any}", .{new_frame}); + _ = try self.packets.append(new_frame); + if (callback != null) { + callback.?(new_frame); + } + } + } + + pub fn stop(self: *Self) void { + self.should_stop = true; + } + }; +} + +/// this is for running the tests ONLY +fn _run_test_server(parse_type: []const u8) !void { + const ip_addr = try net.Ip4Address.parse("127.0.0.1", 65432); + const test_host = net.Address{ .in = ip_addr }; + var server = try test_host.listen(.{ + .reuse_port = true, + }); + defer server.deinit(); + + const addr = server.listen_address; + std.log.info("Listening on {}\n", .{addr.getPort()}); + + var client = try server.accept(); + defer client.stream.close(); + + std.log.info("Connection received! {}\n", .{client.address}); + + var _pkt: []const u8 = undefined; + if (std.mem.eql(u8, parse_type, "vita49")) { + _pkt = &[_]u8{ + 0x3A, 0x02, 0x0A, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x56, 0x34, + 0x12, 0x78, 0x9A, 0xBC, 0xDE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x20, 0x56, 0x49, + 0x54, 0x41, 0x20, 0x34, 0x39, 0x21, + }; + } else { + _pkt = &[_]u8{ + 0x78, 0x97, 0xC0, 0x00, 0x00, 0x0A, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + }; + } + var counter: usize = 0; + while (counter < 5) { + _ = try client.stream.writeAll(_pkt); + std.time.sleep(2 * std.time.ns_per_s); + counter += 1; + } +} + +/// this is for running tests ONLY +fn _test_callback(packet: Vita49) void { + std.log.debug("CALLBACK CALLED: {any}", .{packet}); +} + +test "Vita49 Parser Test" { + const ip = "127.0.0.1".*; + const port: u16 = 65432; + const parser = Parser(Vita49); + var par_test = parser.new(&ip, port, 1024, std.testing.allocator); + defer par_test.deinit(); + + { + const t1 = try std.Thread.spawn(.{}, _run_test_server, .{"vita49"}); + defer t1.join(); + + std.time.sleep(2 * std.time.ns_per_s); + + const t2 = try std.Thread.spawn(.{}, struct { + fn run(pt: *parser) !void { + try pt.start(null); + } + }.run, .{&par_test}); + defer t2.join(); + + std.time.sleep(10 * std.time.ns_per_s); + + const t3 = try std.Thread.spawn(.{}, struct { + fn run(pt: *parser) void { + pt.stop(); + } + }.run, .{&par_test}); + defer t3.join(); + } + try std.testing.expectEqual(8, par_test.packets.capacity); +} + +test "Vita49 Parser Test w/ Callback" { + const ip = "127.0.0.1".*; + const port: u16 = 65432; + const parser = Parser(Vita49); + var par_test = parser.new(&ip, port, 1024, std.testing.allocator); + defer par_test.deinit(); + + { + const t1 = try std.Thread.spawn(.{}, _run_test_server, .{"vita49"}); + defer t1.join(); + + std.time.sleep(2 * std.time.ns_per_s); + + const t2 = try std.Thread.spawn(.{}, struct { + fn run(pt: *parser) !void { + try pt.start(_test_callback); + } + }.run, .{&par_test}); + defer t2.join(); + + std.time.sleep(10 * std.time.ns_per_s); + + const t3 = try std.Thread.spawn(.{}, struct { + fn run(pt: *parser) void { + pt.stop(); + } + }.run, .{&par_test}); + defer t3.join(); + } + try std.testing.expectEqual(8, par_test.packets.capacity); +} + +test "CCSDS Parser Test" { + const ip = "127.0.0.1".*; + const port: u16 = 65432; + const parser = Parser(CCSDS); + var par_test = parser.new(&ip, port, 1024, std.testing.allocator); + defer par_test.deinit(); + + { + const t1 = try std.Thread.spawn(.{}, _run_test_server, .{"ccsds"}); + defer t1.join(); + + std.time.sleep(2 * std.time.ns_per_s); + + const t2 = try std.Thread.spawn(.{}, struct { + fn run(pt: *parser) !void { + try pt.start(null); + } + }.run, .{&par_test}); + defer t2.join(); + + std.time.sleep(10 * std.time.ns_per_s); + + const t3 = try std.Thread.spawn(.{}, struct { + fn run(pt: *parser) void { + pt.stop(); + } + }.run, .{&par_test}); + defer t3.join(); + } + try std.testing.expectEqual(8, par_test.packets.capacity); +} diff --git a/src/time.zig b/src/time.zig index b726c4e..34541e4 100644 --- a/src/time.zig +++ b/src/time.zig @@ -88,7 +88,6 @@ pub const Datetime = struct { const hours = @as(u8, @intCast(@divFloor(remaining_seconds, std.time.s_per_hour))); remaining_seconds -= hours * @as(i64, std.time.s_per_hour); const minutes = @as(u8, @intCast(@divFloor(remaining_seconds, std.time.s_per_min))); - // const seconds = @as(u8, @intCast(remaining_seconds % std.time.s_per_min)); const seconds = @mod(remaining_seconds, std.time.s_per_min); return Datetime.new_datetime(year, month, day, hours, minutes, @as(f16, @floatFromInt(seconds))); diff --git a/src/vita49.zig b/src/vita49.zig index e133ca8..87bdffb 100644 --- a/src/vita49.zig +++ b/src/vita49.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const debug = std.debug; pub const Vita49Error = error{ MalformedPayloadRange, InsufficentData }; @@ -72,14 +71,14 @@ pub const Header = packed struct { } pub fn output(self: Self) void { - debug.print("Vita49 Packet Header:\n", .{}); - debug.print("Packet type: {}\n", .{self.packet_type}); - debug.print("Class ID: {}\n", .{self.class_id}); - debug.print("Trailer Present: {}\n", .{self.trailer}); - debug.print("TSI: {}\n", .{self.tsi}); - debug.print("TSF: {}\n", .{self.tsf}); - debug.print("Packet_Count: {}\n", .{self.packet_count}); - debug.print("Packet_Size: {}\n", .{self.packet_size}); + std.log.info("Vita49 Packet Header:\n", .{}); + std.log.info("Packet type: {}\n", .{self.packet_type}); + std.log.info.print("Class ID: {}\n", .{self.class_id}); + std.log.info("Trailer Present: {}\n", .{self.trailer}); + std.log.info("TSI: {}\n", .{self.tsi}); + std.log.info("TSF: {}\n", .{self.tsf}); + std.log.info("Packet_Count: {}\n", .{self.packet_count}); + std.log.info("Packet_Size: {}\n", .{self.packet_size}); } }; @@ -112,7 +111,10 @@ pub const Vita49 = struct { const Self = @This(); - pub fn new(stream: []const u8) !Self { + pub fn new(stream: []const u8, config: ?[]const u8) !Self { + if (config != null) { + std.log.debug("Config found for Vita49 but this hasnt been implemeneted yet", .{}); + } if (stream.len < 4) { return Vita49Error.InsufficentData; } @@ -156,7 +158,7 @@ pub const Vita49 = struct { trailer = Trailer.new(stream[payload_range.end..]); payload = stream[payload_range.start..payload_range.end]; } else { - payload = stream[payload_range.start..]; + payload = stream[payload_range.start..payload_range.end]; } if (header.tsi != TSI.none) { var tmp_array: [4]u8 = undefined; @@ -209,8 +211,6 @@ pub const Vita49 = struct { } }; -pub const Vita49_Parser = struct { stream: []const u8, packets: []const Vita49 }; - test "Test Vita49 Packet w/o trailer" { const vita49_test_packet = [_]u8{ // Packet header @@ -240,7 +240,7 @@ test "Test Vita49 Packet w/o trailer" { 0x21, }; - const vita49_packet = try Vita49.new(&vita49_test_packet); + const vita49_packet = try Vita49.new(&vita49_test_packet, null); try std.testing.expectEqual(null, vita49_packet.i_timestamp); try std.testing.expectEqual(128, vita49_packet.f_timestamp); @@ -272,7 +272,7 @@ test "Test Vita49 Packet w/ trailer" { 0xBB, 0xCC, 0xDD, }; - const vita49_packet = try Vita49.new(&vita49_test_packet); + const vita49_packet = try Vita49.new(&vita49_test_packet, null); try std.testing.expectEqual(4660, vita49_packet.stream_id); try std.testing.expectEqual(null, vita49_packet.class_id);