graphvent/context.go

335 lines
8.1 KiB
Go

2023-07-09 14:30:30 -06:00
package graphvent
import (
badger "github.com/dgraph-io/badger/v3"
"fmt"
"sync"
"errors"
2023-07-27 15:49:21 -06:00
"runtime"
2023-07-27 23:17:44 -06:00
"crypto/sha512"
"crypto/ecdh"
2023-07-27 23:17:44 -06:00
"encoding/binary"
2023-07-09 14:30:30 -06:00
)
// A Type can be Hashed by Hash
2023-07-29 11:03:41 -06:00
type TypeName interface {
String() string
Prefix() string
}
// Hashed a Type to a uint64
2023-07-29 11:03:41 -06:00
func Hash(t TypeName) uint64 {
hash := sha512.Sum512([]byte(fmt.Sprintf("%s%s", t.Prefix(), t.String())))
return binary.BigEndian.Uint64(hash[(len(hash)-9):(len(hash)-1)])
}
// NodeType identifies the 'class' of a node
type NodeType string
func (node NodeType) Prefix() string { return "NODE: " }
func (node NodeType) String() string { return string(node) }
// ExtType identifies an extension on a node
type ExtType string
func (ext ExtType) Prefix() string { return "EXTENSION: " }
func (ext ExtType) String() string { return string(ext) }
2023-07-26 00:18:11 -06:00
//Function to load an extension from bytes
2023-08-01 20:55:15 -06:00
type ExtensionLoadFunc func(*Context,[]byte) (Extension, error)
func LoadExtension[T any, E interface {
*T
Extension
}](ctx *Context, data []byte) (Extension, error) {
e := E(new(T))
err := e.Deserialize(ctx, data)
if err != nil {
return nil, err
}
return e, nil
}
type PolicyType string
func (policy PolicyType) Prefix() string { return "POLICY: " }
func (policy PolicyType) String() string { return string(policy) }
type PolicyLoadFunc func(*Context,[]byte) (Policy, error)
func LoadPolicy[T any, P interface {
*T
Policy
}](ctx *Context, data []byte) (Policy, error) {
p := P(new(T))
err := p.Deserialize(ctx, data)
if err != nil {
return nil, err
}
return p, nil
}
type PolicyInfo struct {
Load PolicyLoadFunc
Type PolicyType
}
// ExtType and NodeType constants
const (
ListenerExtType = ExtType("LISTENER")
LockableExtType = ExtType("LOCKABLE")
GQLExtType = ExtType("GQL")
GroupExtType = ExtType("GROUP")
ECDHExtType = ExtType("ECDH")
GQLNodeType = NodeType("GQL")
)
var (
NodeNotFoundError = errors.New("Node not found in DB")
ECDH = ecdh.X25519()
)
2023-08-01 20:55:15 -06:00
type SignalLoadFunc func(*Context,[]byte) (Signal, error)
func LoadSignal[T any, S interface{
*T
Signal
}](ctx *Context, data []byte) (Signal, error) {
s := S(new(T))
err := s.Deserialize(ctx, data)
if err != nil {
return nil, err
}
return s, nil
}
2023-07-29 00:28:44 -06:00
type SignalInfo struct {
Load SignalLoadFunc
Type SignalType
}
// Information about a registered extension
2023-07-25 21:43:15 -06:00
type ExtensionInfo struct {
// Function used to load extensions of this type from the database
2023-07-25 21:43:15 -06:00
Load ExtensionLoadFunc
Type ExtType
// Extra context data shared between nodes of this class
2023-07-25 21:43:15 -06:00
Data interface{}
2023-07-09 14:30:30 -06:00
}
// Information about a registered node type
2023-07-26 00:18:11 -06:00
type NodeInfo struct {
Type NodeType
// Required extensions to be a valid node of this class
Extensions []ExtType
2023-07-26 00:18:11 -06:00
}
// A Context stores all the data to run a graphvent process
2023-07-09 14:30:30 -06:00
type Context struct {
2023-07-10 22:31:43 -06:00
// DB is the database connection used to load and write nodes
2023-07-09 14:30:30 -06:00
DB * badger.DB
// Logging interface
2023-07-09 14:30:30 -06:00
Log Logger
// Map between database extension hashes and the registered info
2023-07-25 21:43:15 -06:00
Extensions map[uint64]ExtensionInfo
// Map between databse policy hashes and the registered info
Policies map[uint64]PolicyInfo
2023-07-29 00:28:44 -06:00
// Map between serialized signal hashes and the registered info
Signals map[uint64]SignalInfo
// Map between database type hashes and the registered info
Types map[uint64]*NodeInfo
// Routing map to all the nodes local to this context
NodesLock sync.RWMutex
2023-07-25 21:43:15 -06:00
Nodes map[NodeID]*Node
2023-07-10 21:15:01 -06:00
}
// Register a NodeType to the context, with the list of extensions it requires
func (ctx *Context) RegisterNodeType(node_type NodeType, extensions []ExtType) error {
type_hash := Hash(node_type)
2023-07-26 00:18:11 -06:00
_, exists := ctx.Types[type_hash]
if exists == true {
return fmt.Errorf("Cannot register node type %s, type already exists in context", node_type)
}
ext_found := map[ExtType]bool{}
for _, extension := range(extensions) {
_, in_ctx := ctx.Extensions[Hash(extension)]
if in_ctx == false {
return fmt.Errorf("Cannot register node type %s, required extension %s not in context", node_type, extension)
}
_, duplicate := ext_found[extension]
if duplicate == true {
return fmt.Errorf("Duplicate extension %s found in extension list", extension)
}
ext_found[extension] = true
}
ctx.Types[type_hash] = &NodeInfo{
2023-07-26 00:18:11 -06:00
Type: node_type,
Extensions: extensions,
2023-07-26 00:18:11 -06:00
}
return nil
}
2023-08-01 20:55:15 -06:00
func RegisterSignal[T any, S interface {
*T
Signal
}](ctx *Context, signal_type SignalType) error {
2023-07-29 00:28:44 -06:00
type_hash := Hash(signal_type)
_, exists := ctx.Signals[type_hash]
if exists == true {
return fmt.Errorf("Cannot register signal of type %s, type already exists in context", signal_type)
}
ctx.Signals[type_hash] = SignalInfo{
2023-08-01 20:55:15 -06:00
Load: LoadSignal[T, S],
2023-07-29 00:28:44 -06:00
Type: signal_type,
}
return nil
}
2023-07-10 22:31:43 -06:00
// Add a node to a context, returns an error if the def is invalid or already exists in the context
2023-08-01 20:55:15 -06:00
func RegisterExtension[T any, E interface{
*T
Extension
}](ctx *Context, data interface{}) error {
var zero E
ext_type := zero.Type()
type_hash := Hash(ext_type)
2023-07-25 21:43:15 -06:00
_, exists := ctx.Extensions[type_hash]
2023-07-09 14:30:30 -06:00
if exists == true {
2023-07-25 21:43:15 -06:00
return fmt.Errorf("Cannot register extension of type %s, type already exists in context", ext_type)
2023-07-09 14:30:30 -06:00
}
2023-07-25 21:43:15 -06:00
ctx.Extensions[type_hash] = ExtensionInfo{
2023-08-01 20:55:15 -06:00
Load: LoadExtension[T,E],
2023-07-25 21:43:15 -06:00
Type: ext_type,
2023-07-26 00:18:11 -06:00
Data: data,
2023-07-10 21:15:01 -06:00
}
2023-07-09 14:30:30 -06:00
return nil
}
func (ctx *Context) AddNode(id NodeID, node *Node) {
ctx.NodesLock.Lock()
ctx.Nodes[id] = node
ctx.NodesLock.Unlock()
}
func (ctx *Context) Node(id NodeID) (*Node, bool) {
ctx.NodesLock.RLock()
node, exists := ctx.Nodes[id]
ctx.NodesLock.RUnlock()
return node, exists
}
// Get a node from the context, or load from the database if not loaded
func (ctx *Context) GetNode(id NodeID) (*Node, error) {
target, exists := ctx.Node(id)
2023-07-27 15:49:21 -06:00
if exists == false {
var err error
target, err = LoadNode(ctx, id)
if err != nil {
return nil, err
}
2023-07-27 15:49:21 -06:00
}
return target, nil
}
// Stop every running loop
func (ctx *Context) Stop() {
for _, node := range(ctx.Nodes) {
node.MsgChan <- Message{ZeroID, &StopSignal}
}
}
// Route a Signal to dest. Currently only local context routing is supported
func (ctx *Context) Send(source NodeID, messages []Message) error {
for _, msg := range(messages) {
target, err := ctx.GetNode(msg.NodeID)
if err == nil {
select {
case target.MsgChan <- Message{source, msg.Signal}:
default:
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
stack_str := string(buf[:n])
return fmt.Errorf("SIGNAL_OVERFLOW: %s - %s", msg.NodeID, stack_str)
}
} else if errors.Is(err, NodeNotFoundError) {
// TODO: Handle finding nodes in other contexts
return err
} else {
return err
}
2023-07-27 15:49:21 -06:00
}
return nil
2023-07-27 15:49:21 -06:00
}
// Create a new Context with the base library content added
2023-07-25 21:43:15 -06:00
func NewContext(db * badger.DB, log Logger) (*Context, error) {
2023-07-09 14:30:30 -06:00
ctx := &Context{
DB: db,
Log: log,
2023-07-25 21:43:15 -06:00
Extensions: map[uint64]ExtensionInfo{},
Types: map[uint64]*NodeInfo{},
2023-07-29 00:28:44 -06:00
Signals: map[uint64]SignalInfo{},
2023-07-25 21:43:15 -06:00
Nodes: map[NodeID]*Node{},
2023-07-09 14:30:30 -06:00
}
var err error
2023-08-01 20:55:15 -06:00
err = RegisterExtension[LockableExt,*LockableExt](ctx, nil)
2023-07-26 00:18:11 -06:00
if err != nil {
return nil, err
}
2023-08-01 20:55:15 -06:00
err = RegisterExtension[ListenerExt,*ListenerExt](ctx, nil)
2023-07-26 11:56:10 -06:00
if err != nil {
return nil, err
}
2023-08-01 20:55:15 -06:00
err = RegisterExtension[ECDHExt,*ECDHExt](ctx, nil)
2023-07-26 00:18:11 -06:00
if err != nil {
return nil, err
}
2023-08-01 20:55:15 -06:00
err = RegisterExtension[GroupExt,*GroupExt](ctx, nil)
2023-07-26 00:18:11 -06:00
if err != nil {
return nil, err
}
gql_ctx := NewGQLExtContext()
2023-08-01 20:55:15 -06:00
err = RegisterExtension[GQLExt,*GQLExt](ctx, gql_ctx)
2023-07-25 09:51:55 -06:00
if err != nil {
2023-07-25 21:43:15 -06:00
return nil, err
2023-07-25 09:51:55 -06:00
}
2023-07-10 21:15:01 -06:00
2023-08-01 20:55:15 -06:00
err = RegisterSignal[BaseSignal, *BaseSignal](ctx, StopSignalType)
if err != nil {
return nil, err
}
2023-07-29 00:28:44 -06:00
2023-08-06 12:47:47 -06:00
err = RegisterSignal[BaseSignal, *BaseSignal](ctx, NewSignalType)
if err != nil {
return nil, err
}
err = RegisterSignal[BaseSignal, *BaseSignal](ctx, StartSignalType)
if err != nil {
return nil, err
}
err = ctx.RegisterNodeType(GQLNodeType, []ExtType{GroupExtType, GQLExtType})
if err != nil {
return nil, err
}
schema, err := BuildSchema(gql_ctx)
if err != nil {
return nil, err
}
gql_ctx.Schema = schema
2023-07-25 21:43:15 -06:00
return ctx, nil
2023-07-09 14:30:30 -06:00
}