diff --git a/backend/services/controller/internal/api/api.go b/backend/services/controller/internal/api/api.go index d0b6898..29e5227 100644 --- a/backend/services/controller/internal/api/api.go +++ b/backend/services/controller/internal/api/api.go @@ -53,6 +53,11 @@ func (a *Api) StartApi() { iot := r.PathPrefix("/api/device").Subrouter() iot.HandleFunc("/alias", a.setDeviceAlias).Methods("PUT") iot.HandleFunc("/auth", a.deviceAuth).Methods("GET", "POST", "DELETE") + iot.HandleFunc("/message/{type}", a.addTemplate).Methods("POST") + iot.HandleFunc("/message", a.updateTemplate).Methods("PUT") + iot.HandleFunc("/message", a.getTemplate).Methods("GET") + iot.HandleFunc("/message", a.deleteTemplate).Methods("DELETE") + iot.HandleFunc("/cwmp/{sn}/generic", a.cwmpGenericMsg).Methods("PUT") iot.HandleFunc("/cwmp/{sn}/getParameterNames", a.cwmpGetParameterNamesMsg).Methods("PUT") iot.HandleFunc("/cwmp/{sn}/getParameterValues", a.cwmpGetParameterValuesMsg).Methods("PUT") iot.HandleFunc("/cwmp/{sn}/getParameterAttributes", a.cwmpGetParameterAttributesMsg).Methods("PUT") @@ -61,6 +66,7 @@ func (a *Api) StartApi() { iot.HandleFunc("/cwmp/{sn}/deleteObject", a.cwmpDeleteObjectMsg).Methods("PUT") iot.HandleFunc("", a.retrieveDevices).Methods("GET") iot.HandleFunc("/filterOptions", a.filterOptions).Methods("GET") + iot.HandleFunc("/{sn}/{mtp}/generic", a.deviceGenericMessage).Methods("PUT") iot.HandleFunc("/{sn}/{mtp}/get", a.deviceGetMsg).Methods("PUT") iot.HandleFunc("/{sn}/{mtp}/add", a.deviceCreateMsg).Methods("PUT") iot.HandleFunc("/{sn}/{mtp}/del", a.deviceDeleteMsg).Methods("PUT") diff --git a/backend/services/controller/internal/api/cwmp.go b/backend/services/controller/internal/api/cwmp.go index bb9d34c..3eb6e6f 100644 --- a/backend/services/controller/internal/api/cwmp.go +++ b/backend/services/controller/internal/api/cwmp.go @@ -17,6 +17,31 @@ import ( var errDeviceModelNotFound = errors.New("device model not found") +func (a *Api) cwmpGenericMsg(w http.ResponseWriter, r *http.Request) { + + sn := getSerialNumberFromRequest(r) + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + if len(payload) == 0 { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall("Empty payload")) + return + } + + data, _, err := cwmpInteraction[cwmp.SoapEnvelope](sn, payload, w, a.nc) + if err != nil { + return + } + + w.Write(data) +} + func (a *Api) cwmpGetParameterNamesMsg(w http.ResponseWriter, r *http.Request) { sn := getSerialNumberFromRequest(r) @@ -125,7 +150,7 @@ func (a *Api) cwmpDeleteObjectMsg(w http.ResponseWriter, r *http.Request) { w.Write(data) } -func cwmpInteraction[T cwmp.SetParameterValuesResponse | cwmp.DeleteObjectResponse | cwmp.GetParameterAttributesResponse | cwmp.GetParameterNamesResponse | cwmp.GetParameterValuesResponse | cwmp.AddObjectResponse]( +func cwmpInteraction[T cwmp.SetParameterValuesResponse | cwmp.SoapEnvelope | cwmp.DeleteObjectResponse | cwmp.GetParameterAttributesResponse | cwmp.GetParameterNamesResponse | cwmp.GetParameterValuesResponse | cwmp.AddObjectResponse]( sn string, payload []byte, w http.ResponseWriter, nc *nats.Conn, ) ([]byte, T, error) { diff --git a/backend/services/controller/internal/api/device.go b/backend/services/controller/internal/api/device.go index a24485f..4270186 100644 --- a/backend/services/controller/internal/api/device.go +++ b/backend/services/controller/internal/api/device.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/gorilla/mux" "github.com/leandrofars/oktopus/internal/bridge" "github.com/leandrofars/oktopus/internal/db" "github.com/leandrofars/oktopus/internal/entity" @@ -367,3 +368,141 @@ func (a *Api) filterOptions(w http.ResponseWriter, r *http.Request) { w.WriteHeader(resp.Code) w.Write(utils.Marshall(resp.Msg)) } + +func (a *Api) updateTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No name provided", w) + return + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("Error to decode payload: "+err.Error(), w) + return + } + + payloadLen := len(payload) + if payloadLen == 0 { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No payload provided", w) + return + } + + err = a.db.UpdateTemplate(name, string(payload)) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(err.Error()) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (a *Api) addTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No name provided", w) + return + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("Error to decode payload: "+err.Error(), w) + return + } + + payloadLen := len(payload) + if payloadLen == 0 { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No payload provided", w) + return + } + + vars := mux.Vars(r) + switch vars["type"] { + case "cwmp": + err = a.db.AddTemplate(name, "cwmp", string(payload)) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + return + case "usp": + err = a.db.AddTemplate(name, "usp", string(payload)) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + return + default: + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("Invalid template type", w) + return + } +} + +func (a *Api) getTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + msgType := r.URL.Query().Get("type") + + if name == "" { + + var filter bson.D + if msgType == "" { + filter = bson.D{} + } else { + filter = bson.D{{"type", msgType}} + } + + result, err := a.db.AllTemplates(filter) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode("Error to get all templates: " + err.Error()) + return + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(result) + return + } else { + t, err := a.db.FindTemplate(bson.D{{"name", name}}) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode("error to find message: " + err.Error()) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(t.Value)) + return + } + +} + +func (a *Api) deleteTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode("needs template name!") + return + } else { + err := a.db.DeleteTemplate(name) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode("error to delete template: " + err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/backend/services/controller/internal/api/usp.go b/backend/services/controller/internal/api/usp.go index 7a663ff..d6c961a 100644 --- a/backend/services/controller/internal/api/usp.go +++ b/backend/services/controller/internal/api/usp.go @@ -1,6 +1,7 @@ package api import ( + "io" "log" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/leandrofars/oktopus/internal/usp/usp_utils" "github.com/leandrofars/oktopus/internal/utils" "github.com/nats-io/nats.go" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -86,6 +88,44 @@ func sendUspMsg(msg usp_msg.Msg, sn string, w http.ResponseWriter, nc *nats.Conn return nil } +func (a *Api) deviceGenericMessage(w http.ResponseWriter, r *http.Request) { + + sn := getSerialNumberFromRequest(r) + mtp, err := getMtpFromRequest(r, w) + if err != nil { + return + } + + if mtp == "" { + var ok bool + mtp, ok = deviceStateOK(w, a.nc, sn) + if !ok { + return + } + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + var msg usp_msg.Msg + + err = protojson.Unmarshal(payload, &msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + err = sendUspMsg(msg, sn, w, a.nc, mtp) + if err != nil { + return + } +} + func (a *Api) deviceGetMsg(w http.ResponseWriter, r *http.Request) { sn := getSerialNumberFromRequest(r) diff --git a/backend/services/controller/internal/db/db.go b/backend/services/controller/internal/db/db.go index c3365b2..c5289ab 100644 --- a/backend/services/controller/internal/db/db.go +++ b/backend/services/controller/internal/db/db.go @@ -10,9 +10,10 @@ import ( ) type Database struct { - client *mongo.Client - users *mongo.Collection - ctx context.Context + client *mongo.Client + users *mongo.Collection + template *mongo.Collection + ctx context.Context } func NewDatabase(ctx context.Context, mongoUri string) Database { @@ -43,6 +44,16 @@ func NewDatabase(ctx context.Context, mongoUri string) Database { log.Fatalln(err) } + db.template = client.Database("general").Collection("templates") + indexField = bson.M{"name": 1} + _, err = db.template.Indexes().CreateOne(ctx, mongo.IndexModel{ + Keys: indexField, + Options: options.Index().SetUnique(true), + }) + if err != nil { + log.Fatalln(err) + } + db.ctx = ctx return db diff --git a/backend/services/controller/internal/db/template.go b/backend/services/controller/internal/db/template.go new file mode 100644 index 0000000..efdcfd4 --- /dev/null +++ b/backend/services/controller/internal/db/template.go @@ -0,0 +1,72 @@ +package db + +import ( + "errors" + "log" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type Template struct { + Name string `json:"name" bson:"name"` + Type string `json:"type" bson:"type"` + Value string `json:"value" bson:"value"` +} + +var ErrorTemplateExists = errors.New("message already exists") +var ErrorTemplateNotExists = errors.New("message don't exist") + +func (d *Database) FindTemplate(filter interface{}) (Template, error) { + var result Template + err := d.template.FindOne(d.ctx, filter).Decode(&result) + return result, err +} + +func (d *Database) AllTemplates(filter interface{}) ([]Template, error) { + var results []Template + + cursor, err := d.template.Find(d.ctx, filter) + if err != nil { + return results, err + } + if err = cursor.All(d.ctx, &results); err != nil { + log.Println(err) + } + return results, err +} + +func (d *Database) AddTemplate(name, tr string, t string) error { + opts := options.FindOneAndReplace().SetUpsert(true) + err := d.template.FindOneAndReplace(d.ctx, bson.D{{"name", name}}, Template{Name: name, Type: tr, Value: t}, opts).Err() + if err != nil { + if err == mongo.ErrNoDocuments { + log.Printf("New message %s added to database", name) + return nil + } + return err + } + log.Printf("Message %s already existed, and got replaced for new payload", name) + return err +} + +func (d *Database) UpdateTemplate(name, t string) error { + result, err := d.template.UpdateOne(d.ctx, bson.D{{"name", name}}, bson.D{{"$set", bson.D{{"value", t}}}}) + if err == nil { + if result.MatchedCount == 0 { + return ErrorTemplateNotExists + } + } + return err +} + +func (d *Database) DeleteTemplate(name string) error { + result, err := d.template.DeleteOne(d.ctx, bson.D{{"name", name}}) + if err == nil { + if result.DeletedCount == 0 { + return ErrorTemplateNotExists + } + } + return err +}