feat(controller): custom cwmp/usp messages templates

This commit is contained in:
leandrofars 2024-10-30 14:14:08 -03:00
parent a895e3ce31
commit c4ac20c1cc
6 changed files with 297 additions and 4 deletions

View File

@ -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")

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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)

View File

@ -12,6 +12,7 @@ import (
type Database struct {
client *mongo.Client
users *mongo.Collection
template *mongo.Collection
ctx context.Context
}
@ -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

View File

@ -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
}