package decoder import ( "bytes" "context" "encoding/hex" "fmt" "log/slog" "strings" "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/common/utils" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" ) // AlertWriter writes decoded beacon events (e.g. to alertbeacons topic). type AlertWriter interface { WriteMessages(ctx context.Context, msgs ...kafka.Message) error } // ProcessIncoming decodes a beacon advertisement and writes the event to the writer if it changed. func ProcessIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer AlertWriter, registry *model.ParserRegistry) { if err := DecodeBeacon(adv, appState, writer, registry); err != nil { slog.Error("decoding beacon", "err", err, "id", adv.ID) } } // DecodeBeacon hex-decodes the payload, runs the parser registry, dedupes by event hash, and writes to writer. func DecodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer AlertWriter, registry *model.ParserRegistry) error { beacon := strings.TrimSpace(adv.Data) id := adv.ID if beacon == "" { return nil } b, err := hex.DecodeString(beacon) if err != nil { return err } b = utils.RemoveFlagBytes(b) indices := utils.ParseADFast(b) event := utils.LoopADStructures(b, indices, id, registry) if event.ID == "" { return nil } prevEvent, ok := appState.GetBeaconEvent(id) appState.UpdateBeaconEvent(id, event) if event.Type == "iBeacon" { event.BtnPressed = true } if ok && bytes.Equal(prevEvent.Hash(), event.Hash()) { return nil } eMsg, err := event.ToJSON() if err != nil { return err } if err := writer.WriteMessages(context.Background(), kafka.Message{Value: eMsg}); err != nil { return fmt.Errorf("write alert: %w", err) } return nil }