{"id":13408831,"url":"https://github.com/Componolit/gneiss","last_synced_at":"2025-03-14T13:32:06.400Z","repository":{"id":87047509,"uuid":"172675097","full_name":"Componolit/gneiss","owner":"Componolit","description":"Framework for platform-independent SPARK components","archived":true,"fork":false,"pushed_at":"2020-08-28T16:42:02.000Z","size":1548,"stargazers_count":22,"open_issues_count":29,"forks_count":2,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-01-10T19:38:35.359Z","etag":null,"topics":["ada","component-based","embedded","formal-methods","formal-verification","spark"],"latest_commit_sha":null,"homepage":"","language":"Ada","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Componolit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-26T09:01:00.000Z","updated_at":"2023-05-22T12:11:45.000Z","dependencies_parsed_at":"2023-05-15T13:30:53.587Z","dependency_job_id":null,"html_url":"https://github.com/Componolit/gneiss","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Componolit%2Fgneiss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Componolit%2Fgneiss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Componolit%2Fgneiss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Componolit%2Fgneiss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Componolit","download_url":"https://codeload.github.com/Componolit/gneiss/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243584475,"owners_count":20314770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ada","component-based","embedded","formal-methods","formal-verification","spark"],"created_at":"2024-07-30T20:00:55.603Z","updated_at":"2025-03-14T13:32:06.385Z","avatar_url":"https://github.com/Componolit.png","language":"Ada","funding_links":[],"categories":["Frameworks"],"sub_categories":["Components"],"readme":"# Gneiss\n\nMany applications still follow a monolithic design pattern today. Often, their\nsize and complexity precludes thorough verification and increases the\nlikelihood of errors. The lack of isolation allows an errors in an uncritical\npart of a software to impact other security critical parts.\n\nA well-known solution to this problem are systems comprised of components which\nonly interact through well-defined communication channels. In such systems\nfunctionality is split into complex untrusted components and simple trusted\ncomponents. While untrusted parts realize sophisticated application logic,\ntrusted components are typically small, implement mandatory policies, and\nenforce security properties. An open question is how to implement such trusted\ncomponents correctly.\n\nGneiss is a SPARK library providing a component-based systems abstraction for\ntrusted components. Its main design goals are portability, performance and\nverifiability. Components built against the library can be compiled for the\n[Genode OS Framework](https://genode.org) and Linux without modification.\nOnly a minimal runtime such as our [ada-runtime](https://github.com/Componolit/ada-runtime)\nis required. To enable high-performance implementations,\nGneiss imposes a fully asynchronous programming style where all work\nis done inside event handlers. All language constructs used in Gneiss can\nbe analyzed by the SPARK proof tools to facilitate formally verified components.\n\n## Architecture\n\nThe library is comprised of two parts: a platform independent interface and a\nplatform implementation. The interface consists mostly of specifications that\ndefine the API and the SPARK contracts. The platform implementation is required\nto implement those specs with platform specific mechanisms. This can either\nbe a native Ada platform or a binding to a different language.\n\nInterfaces are implemented as regular or generic packages. Since components\nbuilt with Gneiss use an asynchronous approach most interfaces trigger\ncallbacks or events. To avoid access procedures that are forbidden in SPARK,\nevent handlers and callbacks are provided as formal generic parameters to\ninterface packages.\n\nInstances of interfaces are called sessions. There are three session types:\nclient, server and dispatcher. The client uses a server session of its own\ntype. The specific implementation of the server is not visible for the client\nbut determined by the platform. The dispatcher is like a special purpose client\nthat does not connect to any server but the platform itself. It is responsible\nfor registering a server implementation on the platform and thereby making it\navailable for clients.\n\n## Building a component\n\nA component is a collection of SPARK packages that contain all its state and\nimplementation. To interact with the system the state needs to hold a system\ncapability and interface instance objects. To announce the component to the\nsystem it needs to instantiate a component package with an initialization\nprocedure. This must only be done once per component. An empty component looks\nas follows:\n\n```Ada\nwith Gneiss;\nwith Gneiss.Component;\n\npackage Component is\n\n   procedure Construct (Cap : Gneiss.Capability);\n   procedure Destruct;\n\n   package Main is new Gneiss.Component (Construct, Destruct);\n\nend Component;\n```\n\nThe procedure `Construct` is the first procedure of the component that is\ncalled (except for elaboration code) and receives a valid system\n[capability](https://en.wikipedia.org/wiki/Capability-based_security). This\ncapability is required to initialize clients or register servers. The procedure\n`Destruct` is called when the platform decides to stop the component and allows\nit to finalize component state. As a convention the components main package is\nalways called `Component` and it must contain an instance of the generic\npackage `Gneiss.Component` that is named `Main`.\n\nThe simplest interfaces are used like regular libraries. An example is the\n`Log` client that provides standard logging facilities. A hello world with the\ncomponent described above looks as follows\n\n```Ada\nwith Gneiss.Log;\nwith Gneiss.Log.Client;\n\npackage body Component is\n\n   package Log is new Gneiss.Log;\n   package Log_Client is new Log.Client;\n\n   Client : Gneiss.Log.Client_Session;\n\n   procedure Construct (Cap : Gneiss.Capability)\n   is\n   begin\n      Log_Client.Initialize (Client, Cap, \"channel1\");\n      if Log.Initialized (Client) then\n         Log_Client.Info (Client, \"Hello World!\");\n         Log_Client.Warning (Client, \"Hello World!\");\n         Log_Client.Error (Client, \"Hello World!\");\n         Main.Vacate (Cap, Main.Success);\n      else\n         Main.Vacate (Cap, Main.Failure);\n      end if;\n   end Construct;\n\n   procedure Destruct\n   is\n   begin\n      Log_Client.Finalize (Client);\n   end Destruct;\n\nend Component;\n```\n\nIn `Construct` the component initializes the log session. Since the\ninitialization can fail it checks again if it succeeded. If this is\nthe case it prints \"Hello World!\" as an info, warning and error\nmessage.\n\nThe component then calls `Vacate` to tell the platform that it has finished its\nwork and can be terminated. `Vacate` does not immediately kill the component\nbut tell the platform that it can safely be stopped now. The current method\nwill still return normally. There is no guarantee that a subprogram that called\n`Vacate` is not called again.\n\nWhen the platform decides to terminate the component at some point it will call\n`Destruct`. This procedure only checks if the `Log` session has been\ninitialized and finalizes it if this is the case. In more complex scenarios the\n`Destruct` procedure can be used to safely shut down hardware devices or write\ndata to disk.\n\nPOSIX:\n```\n[Hello_World] Info: Hello World!\n[Hello_World] Warning: Hello World!\n[Hello_World] Error: Hello World!\n```\nGenode:\n```\n[init -\u003e test-hello_world -\u003e Hello_World] Hello World!\n[init -\u003e test-hello_world -\u003e Hello_World] Warning: Hello World!\n[init -\u003e test-hello_world -\u003e Hello_World] Error: Hello World!\n```\n\nSince Gneiss is an asynchronous framework it often requires callbacks to be\nimplemented. The `Timer` interface is a good example and easy to show. It only\nrequires a single callback procedure that is called after a previously\nspecified time. The package spec is the same as in the previous example.\n\n```Ada\nwith Gneiss.Log;\nwith Gneiss.Log.Client;\nwith Gneiss.Timer;\nwith Gneiss.Timer.Client;\n\npackage body Component is\n\n   Gneiss_Log   : Gneiss.Log.Client_Session;\n   Gneiss_Timer : Gneiss.Timer.Client_Session;\n   Capability   : Gneiss.Capability;\n\n   procedure Event;\n\n   package Log_Client is new Gneiss_Log.Client;\n   package Timer_Client is new Gneiss_Timer.Client (Event);\n\n   procedure Construct (Cap : Gneiss.Capability)\n   is\n   begin\n      Capability := Cap;\n      Log_Client.Initialize (Log, Cap, \"Timer\");\n      Timer_Client.Initialize (Timer, Cap);\n      if\n         Gneiss_Log.Initialized (Log)\n         and then Gneiss.Timer.Initialized (Timer)\n      then\n         Log_Client.Info (Log, \"Start!\");\n         Timer_Client.Set_Timeout (Timer, 60.0);\n      else\n         Main.Vacate (Cap, Main.Failure);\n      end if;\n   end Construct;\n\n   procedure Event\n   is\n   begin\n      if\n         Gneiss_Log.Initialized (Log)\n         and then Gneiss_Timer.Initialized (Timer)\n      then\n         Log_Client.Info (\"60s passed!\");\n         Main.Vacate (Capability, Main.Success);\n      else\n         Main.Vacate (Capability, Main.Failure);\n      end if;\n   end Event;\n\n   procedure Destruct\n   is\n   begin\n      Componolit.Gneiss.Log.Client.Finalize (Log);\n      Timer_Client.Finalize (Timer);\n   end Destruct;\n\nend Component;\n```\n\nThe usage of the log session here is equivalent to the first example. Also the\ninitialization of the timer session is similar. When `Set_Timeout` is called\non the timer a timeout is set on the platform. The platform will then call\nthe `Event` procedure when this timeout triggers. Since the `Event` procedure\nhas no arguments and can be used in multiple contexts no preconditions can be\nset. This requires an initialization check of the timer session.\n\nSome interfaces need more than a simple event callback and require additional\ninterface specific procedures or functions as generic arguments. These more\nspecialized callbacks can provide preconditions that provide certain guarantees\nabout initialized arguments.\n\nFurthermore the capability needs to be copied to be available in the Event for\ncomponent termination. The capability is a special object that can be copied\nbut not created from scratch. Only construct is called with a valid capability\nobject which then must be kept somewhere to be used in other contexts.\n\n## Implementing a new platform\n\nThe generic approach to implement a new platform is to create a new directory\nin `platform` and provide bodies for all specs in the `src` directory. Some of\nthose specs have private parts that include `Gneiss_Internal` packages and rename\ntheir types. Those are platform-specific types and their declaration together\nwith the according `Gneiss_Internal` package spec need to be provided. Platform\nspecific types can be anything, as they're private to all components.\n\nThe log client for example consists of two (in this example simplified) specs:\n\n```Ada\nprivate with Gneiss_Internal.Log;\n\ngeneric\npackage Gneiss.Log is\n\n   type Client_Session is limited private;\n\n   function Initialized (C : Client_Session) return Boolean;\n\nprivate\n\n   type Client_Session is new Gneiss_Internal.Log.Client_Session;\n\nend Gneiss.Log;\n```\n\n```Ada\ngeneric\npackage Gneiss.Log.Client is\n\n   procedure Initialize (C     : in out Client_Session;\n                         Cap   :        Capability;\n                         Label :        String) with\n      Pre =\u003e not Initialized (C);\n\n   procedure Info (C : in out Client_Session;\n                   M :        String) with\n      Pre  =\u003e Initialized (C),\n      Post =\u003e Initialized (C);\n\nend Gneiss.Log.Client;\n```\n\nThe client session is a limited private type that can neither be assigned nor\ncopied. Its state functions, such as the initialization in this case are\nprovided by the `Log` package while all modifying procedures are provided by\n`Log.Client`. In case of a generic package this allows the use of state\nfunctions as function contracts for formal generic parameters.\n\nAn exemplary POSIX implementation consists of three parts: the internal type\npackage, a client body and a C implementation. Since the label should be\nprinted as a prefix in front of each message it needs to be saved in the\n`Client_Session` type. As it can have any length it only is a record containing\na pointer to the actual string object:\n\n```Ada\nwith System;\n\npackage Gneiss_Internal.Log is\n\n   type Client_Session is limited record\n      Label : System.Address := System.Null_Address;\n   end record;\n\nend Gneiss_Internal.Log;\n```\n\nAs the session type is limited and has no initialization operation it requires\na default initializer. The default value should be a state that marks the\nsession as not initialized. Before the package body can be implemented a C\nimplementation must be present. Since the session type is limited Ada will\nalways pass it by reference, so pointers have to be used in the language\nbinding. This roughly represents the subprograms defined in the package spec:\n\n```C\n#include \u003cstring.h\u003e\n#include \u003cstdlib.h\u003e\n#include \u003cstdio.h\u003e\n\ntypedef struct session\n{\n    char *label;\n} session_t;\n\nvoid initialize(session_t *session, char *label)\n{\n    session-\u003elabel = malloc(strlen(label) + 1);\n    if(session-\u003elabel){\n        memcpy(session-\u003elabel, label, strlen(label) + 1);\n    }\n}\n\nvoid info(session_t *session, char *msg)\n{\n    fputs(\"[\", stderr);\n    fputs(session-\u003elabel, stderr);\n    fputs(\"] \", stderr);\n    fputs(msg, stderr);\n    fputs(\"\\n\", stderr);\n}\n```\n\nThe `struct session` is equal to the Ada record. A pointer in C is what a\n`System.Address` is in Ada. Also by using `in out` as passing mode or having a\nlimited type Ada will use pointers so `session_t` will always be passed as a\npointer. Procedures in Ada have no return type so they all are void functions\nin C.\n\n```Ada\nwith System;\n\npackage body Gneiss.Log.Client is\n\n   procedure Initialize (C     : in out Client_Session;\n                         Cap   :        Capability;\n                         Label :        String)\n   is\n      procedure C_Initialize (C_Client : in out Client_Session;\n                              C_Label  :        System.Address) with\n         Import,\n         Convention =\u003e C,\n         External_Name =\u003e \"initialize\";\n      C_String : String := Label \u0026 Character'Val (0);\n   begin\n      C_Initialize (C, C_String'Address);\n   end Initialize;\n\n   procedure Info (C : in out Client_Session;\n                   M :        String)\n   is\n      procedure C_Info (C_Client  : in out Client_Session;\n                        C_Message :        System.Address) with\n         Import,\n         Convention =\u003e C,\n         External_Name =\u003e \"info\";\n      C_Msg : String := M \u0026 Character'Val (0);\n   begin\n      C_Info (C, C_Msg'Address);\n   end Info;\n\nend Gneiss.Log.Client;\n```\n\nIn the package body the C functions are imported. The `Client_Session` can be\n`in out` as it is passed as a pointer and might be modified by C. The message\nstring is more complicated. While C expects a pointer with a null terminated\nstring, Ada uses an array of characters and passes meta data about the length\nof the string. To convert an Ada string to a C string it is put on the stack\nand a NULL character is appended as a terminator. Then the address of the first\nstring element is passed to C.\n\nThe last part is the package body for `Log` which only needs to implement\n`Initialized`. Since this functions properties are likely required in the\nproof context it should not be implemented in C. Also since its contract is\nfixed it needs to be a expression function to get into the proof context:\n\n```Ada\nwith System;\n\npackage body Gneiss.Log is\n\n   use type System.Address;\n\n   function Initialized (C : Client_Session) return Boolean is\n      (C.Label /= System.Null_Address);\n\nend Gneiss.Log;\n```\n\nThe initialization checks if `Label` is a valid address. This ensures that all\nprocedures that have an `Initialized` precondition can safely use the label.\nIt also makes sure that if `malloc` fails in C the session will not be\ninitialized.\n\n##  Buildsystem\n\nGneiss aims to integrate into the existing build systems of the supported platforms.\nOn Genode Gneiss components can be built with the native build system. On Linux we chose\na custom approach that fits our use case.\n\n### Cement\n\nThe Cement build system allows designing and building Gneiss systems that run on Linux either\nin a GNU userspace or as init process. A system consists of a core component that\nis executed with a configuration. The configuration declares the components and their communication\nchannels. A component is compiled into a shared object that is loaded by the core component and\nthen forks into its own process.\n\nThe build configuration is done in XML. The example for a hello world system looks as follows:\n\n```XML\n\u003cconfig\u003e\n    \u003ccomponent name=\"log_server\" file=\"libcomponent_linux_log_server.so\"/\u003e\n    \u003ccomponent name=\"hello_world\" file=\"libcomponent_hello_world.so\"\u003e\n        \u003cservice name=\"Log\" server=\"log_server\"/\u003e\n    \u003c/component\u003e\n\u003c/config\u003e\n```\n\nThe `hello_world` component that is implemented by `libcomponent_hello_world.so` is allowed to\ncommunicate via a `Log` session to the `log_server` which then prints its outputs to the\nterminal. To compile this system `cement` can be called with\n\n```\n$ cd /path/to/gneiss\n$ ./cement build -b build test/hello_world/hello_world.xml . test init lib\n```\n\nThe arguments after the configuration file are the directory where the Gneiss\nrepository is located and the directories that contain the project files for the\ncomponents and libraries.\n\nCore is built in `build/bin` and the components are built in `build/lib`.\nTo run the system add the components that are shared libraries to the\npreload path and run core with the build configuration.\n\n```\n$ export LD_LIBRARY_PATH=build/lib\n$ ./build/bin/core test/hello_world/hello_world.xml\n```\n\nThe resulting output will be:\n\n```\nI: Loading config from test/hello_world/hello_world.xml\nI: Started log_server with PID 19294\nI: Started hello_world with PID 19295\n[hello_world:log_hello_world] Info: Hello World!\n[hello_world:log_hello_world] Warning: Hello World!\n[hello_world:log_hello_world] Error: Hello World!\n[hello_world:log_hello_world] Info: Destructing...\nI: Component hello_world exited with status 0\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FComponolit%2Fgneiss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FComponolit%2Fgneiss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FComponolit%2Fgneiss/lists"}