audit full project flows and harden backend
This commit is contained in:
@@ -32,6 +32,7 @@ type Service struct {
|
||||
packet atomic.Uint32
|
||||
http *http.Server
|
||||
ready atomic.Bool
|
||||
client *http.Client
|
||||
}
|
||||
type Command struct {
|
||||
Identity string `json:"identity"`
|
||||
@@ -97,7 +98,7 @@ func New(cfg config.Config) (*Service, error) {
|
||||
if err = broker.AddListener(listeners.NewTCP(listenerConfig)); err != nil {
|
||||
return nil, fmt.Errorf("配置 MQTT 监听器: %w", err)
|
||||
}
|
||||
return &Service{cfg: cfg, keys: keys, broker: broker}, nil
|
||||
return &Service{cfg: cfg, keys: keys, broker: broker, client: &http.Client{Timeout: 12 * time.Second}}, nil
|
||||
}
|
||||
|
||||
func makeTLSConfig(cfg config.Config) (*tls.Config, error) {
|
||||
@@ -222,10 +223,14 @@ func (s *Service) command(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Service) onMessage(_ *mqtt.Client, _ packets.Subscription, message packets.Packet) {
|
||||
raw := append([]byte(nil), message.Payload...)
|
||||
envelope := Envelope{Type: "device_message", Topic: message.TopicName, ReceivedAt: time.Now().UTC().Format(time.RFC3339Nano), PayloadHex: hex.EncodeToString(raw)}
|
||||
expectedDeviceID, topicOK := deviceIDFromTopic(s.cfg, message.TopicName)
|
||||
envelope := Envelope{Type: "device_message", Topic: message.TopicName, DeviceID: expectedDeviceID, ReceivedAt: time.Now().UTC().Format(time.RFC3339Nano), PayloadHex: hex.EncodeToString(raw)}
|
||||
if frame, err := protocol.Decode(raw, s.keys); err == nil {
|
||||
deviceID := hex.EncodeToString(frame.DeviceID[:])
|
||||
envelope.DeviceID = deviceID
|
||||
if !topicOK || deviceID != expectedDeviceID {
|
||||
s.forwardEnvelope(envelope)
|
||||
return
|
||||
}
|
||||
main := byte(0)
|
||||
if len(frame.Payload) > 0 {
|
||||
main = frame.Payload[0]
|
||||
@@ -238,6 +243,10 @@ func (s *Service) onMessage(_ *mqtt.Client, _ packets.Subscription, message pack
|
||||
}
|
||||
envelope.Frame = decoded
|
||||
}
|
||||
s.forwardEnvelope(envelope)
|
||||
}
|
||||
|
||||
func (s *Service) forwardEnvelope(envelope Envelope) {
|
||||
data, _ := json.Marshal(envelope)
|
||||
request, err := http.NewRequest(http.MethodPost, s.cfg.HTTP.CallbackURL, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
@@ -245,12 +254,38 @@ func (s *Service) onMessage(_ *mqtt.Client, _ packets.Subscription, message pack
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("X-Heqi-Iot-Token", s.cfg.HTTP.InternalToken)
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
response, err := s.client.Do(request)
|
||||
if err == nil {
|
||||
_ = response.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func deviceIDFromTopic(cfg config.Config, topic string) (string, bool) {
|
||||
for _, pattern := range []string{cfg.MQTT.UpTopic, cfg.MQTT.AckTopic} {
|
||||
parts := strings.Split(pattern, "/")
|
||||
values := strings.Split(topic, "/")
|
||||
if len(parts) != len(values) {
|
||||
continue
|
||||
}
|
||||
deviceID := ""
|
||||
matched := true
|
||||
for index := range parts {
|
||||
if parts[index] == "+" {
|
||||
deviceID = values[index]
|
||||
continue
|
||||
}
|
||||
if parts[index] != values[index] {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched && len(deviceID) == 16 {
|
||||
return strings.ToLower(deviceID), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func decodeDeviceID(value string) ([8]byte, error) {
|
||||
var result [8]byte
|
||||
decoded, err := hex.DecodeString(value)
|
||||
|
||||
22
backend/iot-server/internal/service/service_test.go
Normal file
22
backend/iot-server/internal/service/service_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.apinb.com/heqiapp/platforms/backend/iot-server/internal/config"
|
||||
)
|
||||
|
||||
func TestDeviceIDFromTopic(t *testing.T) {
|
||||
cfg := config.Config{MQTT: config.MQTT{UpTopic: "devices/+/up", AckTopic: "devices/+/ack"}}
|
||||
for _, topic := range []string{"devices/0000000000000001/up", "devices/0000000000000001/ack"} {
|
||||
identity, ok := deviceIDFromTopic(cfg, topic)
|
||||
if !ok || identity != "0000000000000001" {
|
||||
t.Fatalf("topic %s: identity=%q ok=%v", topic, identity, ok)
|
||||
}
|
||||
}
|
||||
for _, topic := range []string{"devices/0001/up", "devices/0000000000000001/down", "other/0000000000000001/up"} {
|
||||
if identity, ok := deviceIDFromTopic(cfg, topic); ok || identity != "" {
|
||||
t.Fatalf("invalid topic %s accepted as %q", topic, identity)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user