adn framework - loltarudesh/adn-warp GitHub Wiki
The ADN project aims at handling flows of packet by building "pipelines". The execution monad, called ADN, is an instance of MonadIO, with a few handy tools. it is simply imported with:
import ADN
Pipelines
A pipeline is a chain of modules dealing with packets, between the network layer up to the application layer. By convention, the start of a pipeline is the network, and the end the application. The different modules in a pipeline communicate together through Channels.
Channel
the type Channel represent a both-way communication channel, with send/recv function similar to a standard network socket.
type Sink p = p -> ADN ()
type Source p = ADN p
data Channel p = Channel {
send :: Sink p
recv :: Source p
}
the type parameter p represent the type of packets exchanged.
WARNING: just like a socket, a Channel cannot be shared! If a thread reads from it, data will not be available to other thread. If you wan't to duplicate packets, you have to find another way!
PipeStart
The beginning of a pipeline does not have a lower layer, but simply an upper one. It is represented by the type PipeStart:
type PipeStart p = ADN (Channel p)
class PipeStartBuilder a p | a -> p where pipeStart :: a -> PipeStart p
where p is the type of packets produced. the parameter a is intended to be the configuration data structure of the module. The class method pipeStart produces, with the configuration, a Channel to be used by the upper layer.
PipeEnd
The end of a pipeline only have a lower layer, but no upper one. It is represented by the type:
type PipeEnd p = Channel p -> ADN ()
class PipeEndBuilder a p | a -> p where pipeEnd :: a -> PipeEnd p
where p is the type of packet processed and a is the configuration. The pipeEnd method receive a Channel from the lower layer, and process it in a monadic action.
Remarque: PipeEnd and PipeStart are very similar class, and can be easily converted one to the other:
pipeStartToEnd :: PipeStartBuider a p => a -> PipeEnd p
pipeEndToStart :: PipeEndBuilder a p => a -> PipeStart p
the existence of both type class is intended for 2 purposes:
- to provide a direction to the pipeline and prevent easy mistakes, like connecting modules backward.
- to make the linking of module simple and intuitive, and limit the threading madness.
Pipe
A pipe is a module located in the middle of a pipeline, and communicate with both a lower and an upper layer. It is represented by the type:
type Pipe p p' = Channel p -> ADN Channel p'
class PipeBuilder a p p' | a -> p, a -> p' where pipe :: a -> Pipe p p'
A Pipe exchange packet of type p with the lower layer, and of type p' with the upper layer. The method pipe receive a Channel from the lower layer, and produces one for the upper layer.
Building a pipeline
Instances of the three class, PipeStart, Pipe and PipeEnd, can be linked together with a simple "bind":
pipeStart RawSocket >>= pipe TCP-IP >>= pipeEnd MyApplication
keep in mind that it is absolutely possible to build multiple pipelines, and have them communicate. It is also possible to create, alter and close pipelines during the runtime, and even inside the send/recv functions of a Channel, thanks to the ADN monad.
ADN Monad
The ADN monad is a MonadIO instance, with a few handy tools to help build and manage pipelines.
Block
One key feature is the encapsulation of a part of execution inside a named "Block":
type BlockName = String
block :: BlockName -> ADN a -> ADN a
The encapsulated code become a sub-block of the caller's block, and can create sub-blocks of itselfs.
Close
It is possible to close the current block, or a sub-block, with the functions:
closeBlock :: ADN ()
closeSubBlock :: BlockName -> ADN ()
This functions close the targeted block and its sub-blocks by calling the functions registered with:
registerClose :: ADN () -> ADN () -- called before closing sub-blocks
registerCloseAfer ADN () -> ADN () -- called after closing sub-blocks
Log
ADN also export a rudimentary logging system:
debug :: String -> ADN ()
info :: String -> ADN ()
warning :: String -> ADN ()
error :: String -> ADN ()
logs are flagged with the origin block, to help debugging.
CLI
Any part of the pipeline can register simple control commands:
type CLI = String -> ADN String
registerCLI :: String -> String -> CLI -> ADN ()
registerCLI_ :: String -> String -> ADN String -> ADN () -- same as registerCLI without arguments
arguments are:
- name of the command
- help message
- the function to execute
reserved commands this commands are implemented by every block:
- help -> print this help, and block-specific commands
- exit -> exit the CLI
- close-> close the block, and it's sub-blocks
- list -> list the availabke sub-blocks (only 1 level)
- tree -> show the whole sub-block tree