From 6257896d1a260ca0689bb085711755cf641029e7 Mon Sep 17 00:00:00 2001 From: overflowerror Date: Sun, 14 Nov 2021 12:17:47 +0100 Subject: [PATCH] initial commit --- .idea/.gitignore | 8 +++++ .idea/iot-relay.iml | 9 ++++++ cmd/main.go | 24 +++++++++++++++ config.json | 12 ++++++++ go.mod | 3 ++ internal/client/client.go | 58 +++++++++++++++++++++++++++++++++++ internal/config/config.go | 41 +++++++++++++++++++++++++ internal/server/connection.go | 57 ++++++++++++++++++++++++++++++++++ internal/server/parser.go | 38 +++++++++++++++++++++++ internal/server/server.go | 32 +++++++++++++++++++ internal/types/callback.go | 3 ++ internal/types/request.go | 22 +++++++++++++ 12 files changed, 307 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/iot-relay.iml create mode 100644 cmd/main.go create mode 100644 config.json create mode 100644 go.mod create mode 100644 internal/client/client.go create mode 100644 internal/config/config.go create mode 100644 internal/server/connection.go create mode 100644 internal/server/parser.go create mode 100644 internal/server/server.go create mode 100644 internal/types/callback.go create mode 100644 internal/types/request.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/iot-relay.iml b/.idea/iot-relay.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/iot-relay.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..d9c9798 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,24 @@ +package main + +import ( + . "iot-relay/internal/client" + . "iot-relay/internal/config" + . "iot-relay/internal/server" + "log" +) + +const configFile = "config.json" + +func main() { + log.Println("reading config file") + config, err := ReadConfig(configFile) + if err != nil { + log.Fatalln(err) + } + + log.Println("starting server") + err = Listen(config, GetHandler(config)) + if err != nil { + log.Fatalln(err) + } +} diff --git a/config.json b/config.json new file mode 100644 index 0000000..3833e78 --- /dev/null +++ b/config.json @@ -0,0 +1,12 @@ +{ + "server": { + "bind": "0.0.0.0:20159", + "timeout": 30 + }, + "client": { + "address": "http://localhost:8086", + "db": "grafana", + "measurement": "iot", + "host": "iot-relay" + } +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..78d019c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module iot-relay + +go 1.17 diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..d5f1c09 --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,58 @@ +package client + +import ( + "fmt" + "iot-relay/internal/config" + "iot-relay/internal/types" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +func lineProtocol(request types.Request, config config.Config) string { + var builder strings.Builder + builder.WriteString(config.Client.Measurement) + builder.WriteString(",host=") + builder.WriteString(config.Client.Host) + builder.WriteString(",ip=") + builder.WriteString(request.IP) + builder.WriteString(",id=") + builder.WriteString(request.ID) + builder.WriteString(",loc=") + builder.WriteString(request.Location) + builder.WriteString(" ") + + for key, value := range request.Data { + builder.WriteString(key) + builder.WriteString("=") + builder.WriteString(value) + builder.WriteString(" ") + } + + builder.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10)) + + return builder.String() +} + +func GetHandler(config config.Config) types.Callback { + return func(request types.Request) error { + line := lineProtocol(request, config) + + url := config.Client.Address + "/write?db=" + config.Client.DB + + resp, err := http.Post(url, "application/octet-stream", strings.NewReader(line)) + if err != nil { + log.Println(err) + return err + } + if resp.StatusCode != 204 { + err = fmt.Errorf("influxdb responded with %d", resp.StatusCode) + log.Println(err) + return err + } + + return nil + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..a713409 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,41 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "os" +) + +type Config struct { + Server struct { + Bind string `json:"bind"` + Timeout int `json:"timeout"` // in seconds + } `json:"server"` + Client struct { + Address string `json:"address"` + DB string `json:"db"` + Measurement string `json:"measurement"` + Host string `json:"host"` + } `json:"client"` +} + +func ReadConfig(filename string) (Config, error) { + file, err := os.Open(filename) + if err != nil { + return Config{}, err + } + + content, err := ioutil.ReadAll(file) + if err != nil { + return Config{}, err + } + + var config Config + + err = json.Unmarshal(content, &config) + if err != nil { + return Config{}, err + } + + return config, nil +} diff --git a/internal/server/connection.go b/internal/server/connection.go new file mode 100644 index 0000000..3905d0c --- /dev/null +++ b/internal/server/connection.go @@ -0,0 +1,57 @@ +package server + +import ( + "bufio" + "iot-relay/internal/types" + "log" + "net" + "strings" +) + +func connectionHandler(conn net.Conn) { + reader := bufio.NewReader(conn) + + defer func() { + log.Println("closing connection") + _ = conn.Close() + }() + + var request types.Request + request.Data = make(map[string]string) + request.IP = strings.Split(conn.RemoteAddr().String(), ":")[0] + + for { + line, err := reader.ReadString('\n') + + if err != nil { + log.Printf("connection error: %v", err) + break + } + + if len(line) == 1 { + // end of request + + if !request.IsValid() { + log.Println("protocol error") + _, _ = conn.Write([]byte("bad\n")) + break + } + + err = callback(request) + if err != nil { + // should be logged by callback + _, _ = conn.Write([]byte("fail\n")) + } else { + _, _ = conn.Write([]byte("ok\n")) + } + break + } else { + err = parseLine(line, &request) + if err != nil { + log.Printf("parsing error: %v", err) + _, _ = conn.Write([]byte("bad\n")) + break + } + } + } +} diff --git a/internal/server/parser.go b/internal/server/parser.go new file mode 100644 index 0000000..23624d3 --- /dev/null +++ b/internal/server/parser.go @@ -0,0 +1,38 @@ +package server + +import ( + "errors" + "iot-relay/internal/types" + "strings" +) + +var forbiddenKeys = []string{ + "ip", +} + +func parseLine(line string, request *types.Request) error { + token := strings.Split(line, "=") + if len(token) != 2 { + return errors.New("malformed; missing =") + } + + key := strings.ToLower(token[0]) + value := strings.TrimRight(token[1], "\n") + + for _, forbidden := range forbiddenKeys { + if forbidden == key { + return errors.New("forbidden key") + } + } + + switch key { + case "id": + request.ID = value + case "loc": + request.Location = value + default: + request.Data[key] = value + } + + return nil +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..e34b1f1 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,32 @@ +package server + +import ( + "iot-relay/internal/config" + "iot-relay/internal/types" + "log" + "net" + "time" +) + +var callback types.Callback + +func Listen(config config.Config, _callback types.Callback) error { + callback = _callback + + listener, err := net.Listen("tcp", config.Server.Bind) + if err != nil { + return err + } + + for { + connection, err := listener.Accept() + if err != nil { + log.Println(err) + } else { + log.Printf("connection from %v", connection.RemoteAddr()) + + _ = connection.SetReadDeadline(time.Now().Add(time.Second * time.Duration(config.Server.Timeout))) + go connectionHandler(connection) + } + } +} diff --git a/internal/types/callback.go b/internal/types/callback.go new file mode 100644 index 0000000..49817fb --- /dev/null +++ b/internal/types/callback.go @@ -0,0 +1,3 @@ +package types + +type Callback func(request Request) error diff --git a/internal/types/request.go b/internal/types/request.go new file mode 100644 index 0000000..f1dee1b --- /dev/null +++ b/internal/types/request.go @@ -0,0 +1,22 @@ +package types + +type Request struct { + ID string + Location string + IP string + Data map[string]string +} + +func (r Request) IsValid() bool { + if len(r.IP) == 0 { + return false + } + if len(r.ID) == 0 { + return false + } + if len(r.Location) == 0 { + return false + } + + return true +}