-- FYI - For Your Information implements a very simple UDP protocol, the server half of Let Me Know. -- Copyright (C) 2025 Prince Trippy . -- This program is free software: you can redistribute it and/or modify it under the terms of the -- GNU Affero General Public License version 3 as published by the Free Software Foundation. -- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -- even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -- See the GNU Affero General Public License for more details. -- You should have received a copy of the GNU Affero General Public License along with this program. -- If not, see . pragma Assertion_Policy(Check); with Interfaces; with Ada.Command_Line, Ada.Sequential_IO; with Usable_Datagram_Package, Less_Trivial_Trie; -- FYI stores numbered tags in a trie and answers questions about whether or not they currently are. -- FYI, For Your Information, is a protocol, and this server implementation thereof, under port 411. -- This is a throwaway protocol, a protocol created for experimentation, but which may be discarded. -- This was designed to be as trivial and generic as possible, regarding the Let Me Know suggestion. -- The users knows the format of tags, like ``ARGUMENTUM AD MUNDUM 3'', and uses this in his client. -- The client sends the tag in a UDP packet and then awaits an answer of an echo or an empty packet. -- If the server echoes the tag, then it exists; an empty packet serves as negative acknowledgement. procedure FYI is package UDP renames Usable_Datagram_Package; Tag_Limit : constant := 64; subtype Tag_Length is Natural range 0 .. Tag_Limit; subtype Request is UDP.Octet_Array (1 .. Tag_Limit); package T is new Less_Trivial_Trie (Index_Type => UDP.Data_Length, Domain_Type => Interfaces.Unsigned_8, Input_Type => UDP.Octet_Array, Stored_Type => Boolean); package S is new Ada.Sequential_IO (Interfaces.Unsigned_8); Trie : T.Trie; File : S.File_Type; -- This task handles adding any and all new entries to that trie; Less_Trivial_Trie is protected. -- If any tag too long to be handled by the system is provided, the server will shut itself down. -- This task will end the entire program in the case of any error, really; this seems to be best. -- The blank tag is invalid here, but this one case could be ignored without consequence, really. task Tag_Eater is entry Start; entry Stop; end Tag_Eater; -- This task must be stoppable in order for Tag_Eater to shut down the program in case it errors. -- It ends Tag_Eater in the rare case it encounters an unrecoverable error during initialization. task Responder is entry Start; entry Stop; end Responder; task body Tag_Eater is C, Terminator : Interfaces.Unsigned_8; R : Request; I : Tag_Length := 0; use type Interfaces.Unsigned_8; begin select -- I believe this addresses a likely issue where Responder would call Stop immediately. accept Start; or accept Stop; goto Early_Exit; end select; S.Open(File, Mode => S.In_File, Name => Ada.Command_Line.Argument(1)); -- I'm particularly pleased with the expression of this command line parameter, as documented. -- It can be provided in several ways, each a convenience or avoiding some asinine limitation. declare S : String := Ada.Command_Line.Argument(3); begin if S'Length = 1 then Terminator := Character'Pos(S(S'First)); else Terminator := Character'Pos(Character'Value(S)); end if; exception when others => Responder.Stop; goto Early_Exit; end; -- This extra call to Start handles the case where the command line parameter is unacceptable. Responder.Start; loop select accept Stop; exit; else -- It's possible for the system to deadlock in Read here, so Responder starts Tag_Eater. S.Read(File, C); if C = Terminator and I = 0 then exit; elsif C = Terminator then T.Give(Path => R(1 .. I), Tree => Trie, What => True); I := 0; else exit when I = Tag_Limit; I := I + 1; R(I) := C; end if; end select; end loop; Responder.Stop; <> exception when S.End_Error => S.Close(File); when others => S.Close(File); Responder.Stop; end Tag_Eater; task body Responder is begin accept Start; declare World : UDP.System_Resource(Where => UDP.Port'Value(Ada.Command_Line.Argument(2))); A : UDP.Address; P : UDP.Port; R : Request; I : Integer; begin Tag_Eater.Start; -- This second accept handles that case of Tag_Eater and an invalid command line parameter. select accept Start; or accept Stop; goto Early_Exit; end select; loop select accept Stop; exit; else declare Here, There : Boolean; begin UDP.Get(World, Data => R, From => A, From_Port => P, Length => I); if I > Tag_Limit then -- A question larger than the limit is necessarily not here. UDP.Hit(World, Data => "", To => A, At_Port => P); else T.Find(Tree => Trie, Seen => Here, Unto => There, Path => R(1 .. I)); if not Here then I := 0; end if; UDP.Hit(World, Data => R(1 .. I), To => A, At_Port => P); end if; exception when others => null; -- Any and all UDP errors will be ignored in this prime loop. end; end select; end loop; <> end; exception when others => Tag_Eater.Stop; end Responder; begin Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Failure); case Ada.Command_Line.Argument_Count is -- This program returns silently in the case of an error. when 3 => null; when others => return; end case; Responder.Start; end FYI; .