From 9ac048861f371b443769687f3b871da1757f51a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Krzy=C5=BCanowski?= Date: Sat, 14 Dec 2024 17:35:23 +0100 Subject: [PATCH] Initial commit --- .idea/.gitignore | 8 ++ .idea/daemonSocketExample.iml | 9 ++ .idea/modules.xml | 8 ++ go.mod | 9 ++ go.sum | 6 + liberum-daemon.log | 5 + main.go | 257 ++++++++++++++++++++++++++++++++++ 7 files changed, 302 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/daemonSocketExample.iml create mode 100644 .idea/modules.xml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 liberum-daemon.log create mode 100644 main.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/daemonSocketExample.iml b/.idea/daemonSocketExample.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/daemonSocketExample.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f34b172 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f56438c --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module daemonSocketExample + +go 1.23.4 + +require ( + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/sevlyar/go-daemon v0.1.6 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cbc8a31 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= +github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/liberum-daemon.log b/liberum-daemon.log new file mode 100644 index 0000000..383ae4f --- /dev/null +++ b/liberum-daemon.log @@ -0,0 +1,5 @@ +2024/12/14 17:34:21 Daemon started +2024/12/14 17:34:22 INFO Got echo request EchoByte=123 +2024/12/14 17:34:53 Daemon started +2024/12/14 17:34:54 INFO Got echo request EchoByte=123 +2024/12/14 17:34:59 INFO Stopping daemon diff --git a/main.go b/main.go new file mode 100644 index 0000000..cb221f0 --- /dev/null +++ b/main.go @@ -0,0 +1,257 @@ +package main + +import ( + "encoding/binary" + "encoding/json" + "errors" + "github.com/sevlyar/go-daemon" + "io" + "log" + "log/slog" + "net" + "os" + "time" +) + +type Message interface { + ID() uint32 +} + +const ( + EchoRequestID = iota +) + +type EchoRequest struct { + EchoByte byte +} + +func (EchoRequest) ID() uint32 { + return EchoRequestID +} + +func unixSocketListen() error { + listener, err := net.Listen("unix", "/tmp/liberum.sock") + if err != nil { + return err + } + + for conn, err := listener.Accept(); err == nil; { + handleConnection(conn) + } + + return nil +} + +func handleConnection(conn net.Conn) { + msgChan := readSocketMessages(conn) + + for msg := range msgChan { + handleMessage(msg) + } +} + +func readSocketMessages(conn net.Conn) chan Message { + msgChan := make(chan Message, 64) + + go func() { + for { + msgBytes, err := readMessage(conn) + if err != nil { + slog.Error("Error reading message", "error", err) + } + + msg, err := decodeMessage(msgBytes) + if err != nil { + slog.Error("Error parsing message", "error", err) + } + + msgChan <- msg + } + }() + + return msgChan +} + +func decodeMessage(msgBytes []byte) (Message, error) { + if len(msgBytes) < 4 { + return nil, errors.New("message too short to have type ID") + } + + msgID := binary.LittleEndian.Uint32(msgBytes[0:4]) + msgRest := msgBytes[4:] + var err error + var msg Message + + switch msgID { + case EchoRequestID: + var echoReq EchoRequest + err = json.Unmarshal(msgRest, &echoReq) + msg = echoReq + default: + err = errors.New("unknown message type ID") + } + + if err != nil { + return nil, err + } + + return msg, nil +} + +func readMessage(conn net.Conn) ([]byte, error) { + msgLenBuf := make([]byte, 4) + n, err := io.ReadFull(conn, msgLenBuf) + if err != nil { + return nil, err + } + + if n != 4 { + return nil, errors.New("could not read message length") + } + + msgLen := binary.LittleEndian.Uint32(msgLenBuf) + msgContent := make([]byte, msgLen) + n, err = io.ReadFull(conn, msgContent) + if err != nil { + return nil, err + } + + if uint32(n) != msgLen { + return nil, errors.New("could not read full message") + } + + return msgContent, nil +} + +func handleMessage(msg Message) { + switch msg.ID() { + case EchoRequestID: + handleEchoRequest(msg.(EchoRequest)) + } +} + +func handleEchoRequest(req EchoRequest) { + slog.Info("Got echo request", "EchoByte", req.EchoByte) +} + +func writeMessage(conn net.Conn, msg Message) error { + msgBytes, err := encodeMessage(msg) + if err != nil { + return err + } + + n, err := conn.Write(msgBytes) + if err != nil { + return err + } + + if n != len(msgBytes) { + return errors.New("could not write full message") + } + + return nil +} + +func encodeMessage(msg Message) ([]byte, error) { + var msgBytes []byte + var msgJsonBytes []byte + var err error + + switch msg.ID() { + case EchoRequestID: + msgJsonBytes, err = json.Marshal(msg.(EchoRequest)) + default: + err = errors.New("unknown message type") + } + + if err != nil { + return nil, err + } + + // +4 for type field length + msgLen := len(msgJsonBytes) + 4 + msgLenBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(msgLenBytes, uint32(msgLen)) + msgBytes = append(msgBytes, msgLenBytes...) + + msgTypeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(msgTypeBytes, msg.ID()) + msgBytes = append(msgBytes, msgTypeBytes...) + + msgBytes = append(msgBytes, msgJsonBytes...) + + return msgBytes, nil +} + +func writeSocketMessages(conn net.Conn, msgChan <-chan Message) error { + for msg := range msgChan { + err := writeMessage(conn, msg) + if err != nil { + return err + } + } + + return nil +} + +func unixSocketConnect() (net.Conn, error) { + conn, err := net.Dial("unix", "/tmp/liberum.sock") + if err != nil { + return nil, err + } + + return conn, nil +} + +func main() { + ctx := &daemon.Context{ + PidFileName: "liberum-daemon.pid", + PidFilePerm: 0644, + LogFileName: "liberum-daemon.log", + LogFilePerm: 0640, + WorkDir: "./", + Umask: 027, + } + + d, err := ctx.Reborn() + if err != nil { + panic(err) + } + + if d != nil { + return + } + + defer func(ctx *daemon.Context) { + slog.Info("Stopping daemon") + _ = ctx.Release() + }(ctx) + log.Println("Daemon started") + + _ = os.Remove("/tmp/liberum.sock") + + go func() { + err = unixSocketListen() + if err != nil { + slog.Error("Error listening unix socket", "error", err) + } + }() + + time.Sleep(1 * time.Second) + conn, err := unixSocketConnect() + if err != nil { + panic(err) + } + + msgWriteChan := make(chan Message, 64) + + go func() { + err = writeSocketMessages(conn, msgWriteChan) + if err != nil { + panic(err) + } + }() + + msgWriteChan <- EchoRequest{123} + time.Sleep(time.Second * 5) +}