Add depot, attendant, jetfire, optimus, ota services with kustomize overlays

This commit is contained in:
Chris Rai
2026-01-31 15:35:07 -05:00
parent a0ec642ca1
commit 9a5cb2f547
404 changed files with 38817 additions and 16 deletions

53
Dockerfile Normal file
View File

@@ -0,0 +1,53 @@
# syntax=docker/dockerfile:1
# Shared Dockerfile for all Go services
# Usage: docker build --build-arg SERVICE=gateway -t gateway .
ARG SERVICE=gateway
# Build stage
FROM golang:1.25-alpine AS builder
ARG SERVICE
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /app
# Copy module files for dependency caching (don't use go.work in container)
COPY pkg/go.mod pkg/go.sum ./pkg/
COPY pkg/can-go/go.mod pkg/can-go/go.sum ./pkg/can-go/
COPY services/${SERVICE}/go.mod services/${SERVICE}/go.sum ./services/${SERVICE}/
# Download dependencies (cached layer)
WORKDIR /app/services/${SERVICE}
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download -x
# Copy source
WORKDIR /app
COPY pkg/ ./pkg/
COPY services/${SERVICE}/ ./services/${SERVICE}/
# Build static binary
WORKDIR /app/services/${SERVICE}
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux \
go build -ldflags="-s -w" -trimpath -o /app-binary .
# Runtime stage - distroless for minimal attack surface
FROM gcr.io/distroless/static-debian12:nonroot
ARG SERVICE
COPY --from=builder /app-binary /app
COPY --from=builder /app/pkg/logger/log_config /log_config
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Copy docs if they exist (optional)
COPY --from=builder /app/services/${SERVICE}/docs* /docs/
ENV LOG_CONFIG=/log_config
ENV TZ=UTC
ENTRYPOINT ["/app"]

View File

@@ -7,6 +7,11 @@ resources:
- ../../base - ../../base
- secrets.yaml - secrets.yaml
- services/gateway/ - services/gateway/
- services/depot/
- services/attendant/
- services/jetfire/
- services/optimus/
- services/ota/
labels: labels:
- pairs: - pairs:

View File

@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: attendant
namespace: cloud-services
labels:
app: attendant
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
selector:
matchLabels:
app: attendant
template:
metadata:
labels:
app: attendant
spec:
containers:
- name: attendant
image: localhost:32000/attendant:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8077
name: http
- containerPort: 11011
name: health
envFrom:
- configMapRef:
name: cloud-common-config
- secretRef:
name: cloud-db-credentials
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
livenessProbe:
httpGet:
path: /liveness
port: 11011
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /readiness
port: 11011
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cloud-services
resources:
- deployment.yaml

View File

@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: depot
namespace: cloud-services
labels:
app: depot
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
selector:
matchLabels:
app: depot
template:
metadata:
labels:
app: depot
spec:
containers:
- name: depot
image: localhost:32000/depot:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8077
name: http
- containerPort: 11011
name: health
envFrom:
- configMapRef:
name: cloud-common-config
- secretRef:
name: cloud-db-credentials
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
livenessProbe:
httpGet:
path: /liveness
port: 11011
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /readiness
port: 11011
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cloud-services
resources:
- deployment.yaml

View File

@@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: jetfire
namespace: cloud-services
labels:
app: jetfire
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
selector:
matchLabels:
app: jetfire
template:
metadata:
labels:
app: jetfire
spec:
containers:
- name: jetfire
image: localhost:32000/jetfire:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8077
name: http
- containerPort: 11011
name: health
env:
- name: CLICKHOUSE_HOST
value: clickhouse.clickhouse.svc.cluster.local
- name: CLICKHOUSE_PORT
value: "9000"
- name: CLICKHOUSE_USER
value: default
- name: CLICKHOUSE_FEATURE_TABLE
value: feature_table
- name: CLICKHOUSE_VEHICLE_SIGNAL_TABLE
value: vehicle_signal
envFrom:
- configMapRef:
name: cloud-common-config
- secretRef:
name: cloud-db-credentials
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 2Gi
livenessProbe:
httpGet:
path: /liveness
port: 11011
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /readiness
port: 11011
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cloud-services
resources:
- deployment.yaml

View File

@@ -0,0 +1,58 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: optimus
namespace: cloud-services
labels:
app: optimus
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
selector:
matchLabels:
app: optimus
template:
metadata:
labels:
app: optimus
spec:
containers:
- name: optimus
image: localhost:32000/optimus:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8077
name: http
- containerPort: 11011
name: health
env:
- name: CLICKHOUSE_HOST
value: clickhouse.clickhouse.svc.cluster.local
- name: CLICKHOUSE_PORT
value: "9000"
- name: CLICKHOUSE_USER
value: default
envFrom:
- configMapRef:
name: cloud-common-config
- secretRef:
name: cloud-db-credentials
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 2Gi
livenessProbe:
httpGet:
path: /liveness
port: 11011
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /readiness
port: 11011
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cloud-services
resources:
- deployment.yaml

View File

@@ -0,0 +1,67 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ota
namespace: cloud-services
labels:
app: ota
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
selector:
matchLabels:
app: ota
template:
metadata:
labels:
app: ota
spec:
containers:
- name: ota
image: localhost:32000/ota_update_go:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8077
name: http
- containerPort: 11011
name: health
env:
- name: SERVICE_BASE_URL
value: /ota_update
envFrom:
- configMapRef:
name: cloud-common-config
- secretRef:
name: cloud-db-credentials
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
livenessProbe:
httpGet:
path: /liveness
port: 11011
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /readiness
port: 11011
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ota
namespace: cloud-services
spec:
selector:
app: ota
ports:
- port: 8077
targetPort: 8077
name: http

View File

@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ota
namespace: cloud-services
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
tls:
- hosts:
- gw.mini.cloud.fiskerinc.com
secretName: cloud-services-tls
rules:
- host: gw.mini.cloud.fiskerinc.com
http:
paths:
- path: /ota_update
pathType: Prefix
backend:
service:
name: ota
port:
number: 8077

View File

@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cloud-services
resources:
- deployment.yaml
- ingress.yaml

View File

@@ -3,5 +3,10 @@ go 1.25
use ( use (
./pkg ./pkg
./pkg/can-go ./pkg/can-go
./services/attendant
./services/depot
./services/gateway ./services/gateway
./services/jetfire
./services/optimus
./services/ota_update_go
) )

View File

@@ -52,6 +52,8 @@ type ConsumerInterface interface {
ConsumeToChannel(topics []string, events chan *Message) error ConsumeToChannel(topics []string, events chan *Message) error
ConsumeToChannelJson(topics []string, events chan common.EventRawJSON) error ConsumeToChannelJson(topics []string, events chan common.EventRawJSON) error
ConsumePartitionsToChannel(partitions []TopicPartition, events chan *Message) error ConsumePartitionsToChannel(partitions []TopicPartition, events chan *Message) error
ConsumeOrRebalancedCatch(topics []string, events chan *Message, rebalance chan struct{}) error
Subscribe(topics []string)
GetMetadata(topic string) (*Metadata, error) GetMetadata(topic string) (*Metadata, error)
Check(ctx context.Context) error Check(ctx context.Context) error
Stop() Stop()
@@ -275,6 +277,48 @@ func (c *Consumer) Stop() {
} }
} }
// Subscribe adds topics to consume (for compatibility with old API)
func (c *Consumer) Subscribe(topics []string) {
c.client.AddConsumeTopics(topics...)
}
// ConsumeOrRebalancedCatch consumes messages and notifies on rebalance events
func (c *Consumer) ConsumeOrRebalancedCatch(topics []string, events chan *Message, rebalance chan struct{}) error {
c.client.AddConsumeTopics(topics...)
c.setConnected(true)
defer c.setConnected(false)
c.running = true
for c.running {
fetches := c.client.PollFetches(c.ctx)
if errs := fetches.Errors(); len(errs) > 0 {
for _, e := range errs {
if e.Err == context.Canceled {
return nil
}
// Check for rebalance-related errors
if e.Err.Error() == "REBALANCE_IN_PROGRESS" || e.Err.Error() == "NOT_COORDINATOR" {
select {
case rebalance <- struct{}{}:
default:
}
return e.Err
}
logger.Error().Err(e.Err).Msgf("fetch error on %s", e.Topic)
c.setConnected(false)
}
continue
}
c.setConnected(true)
fetches.EachRecord(func(r *kgo.Record) {
events <- recordToMessage(r)
})
}
return nil
}
// Check verifies consumer connectivity // Check verifies consumer connectivity
func (c *Consumer) Check(ctx context.Context) error { func (c *Consumer) Check(ctx context.Context) error {
if !c.isConnected() { if !c.isConnected() {

View File

@@ -14,7 +14,6 @@ import (
"github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils" "github.com/fiskerinc/cloud-services/pkg/utils"
cKafka "github.com/confluentinc/confluent-kafka-go/v2/kafka"
"github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -106,10 +105,6 @@ func badDataErrorResp(w http.ResponseWriter, err error, defaultStatus int, errFu
return true return true
} }
if handleKafkaErrors(err, errFunc, w) {
return true
}
logError(err, err.Error(), defaultStatus, errFunc, w) logError(err, err.Error(), defaultStatus, errFunc, w)
return true return true
@@ -169,17 +164,6 @@ func handleSAPErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
return false return false
} }
func handleKafkaErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
if kafkaErr, ok := err.(cKafka.Error); ok {
if kafkaErr.IsFatal() {
logError(kafkaErr, kafkaErr.Error(), http.StatusServiceUnavailable, errFunc, w)
return true
}
}
return false
}
func logError(err error, errMessage string, status int, errFunc respErr, w http.ResponseWriter) { func logError(err error, errMessage string, status int, errFunc respErr, w http.ResponseWriter) {
switch status { switch status {
case http.StatusServiceUnavailable, http.StatusInternalServerError: case http.StatusServiceUnavailable, http.StatusInternalServerError:

36
scripts/build.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Build and optionally deploy a service
# Usage: ./scripts/build.sh <service> [--deploy] [version]
# Example: ./scripts/build.sh depot --deploy v1
set -e
SERVICE=${1:-gateway}
DEPLOY=${2:-}
VERSION=${3:-v1}
REGISTRY="localhost:32000"
TAG="$VERSION"
echo "Building $SERVICE..."
docker build --platform linux/arm64 \
--build-arg SERVICE=$SERVICE \
-t $REGISTRY/$SERVICE:$TAG \
-f Dockerfile .
if [ "$DEPLOY" == "--deploy" ]; then
echo "Saving image..."
docker save $REGISTRY/$SERVICE:$TAG -o /tmp/$SERVICE.tar
echo "Transferring to cluster..."
scp /tmp/$SERVICE.tar admin@control-plane.local:/tmp/
echo "Importing to microk8s..."
ssh admin@control-plane.local "/usr/local/bin/multipass transfer /tmp/$SERVICE.tar microk8s-vm:/tmp/"
ssh admin@control-plane.local "/usr/local/bin/multipass exec microk8s-vm -- microk8s ctr images rm $REGISTRY/$SERVICE:$TAG 2>/dev/null || true"
ssh admin@control-plane.local "/usr/local/bin/multipass exec microk8s-vm -- microk8s ctr images import /tmp/$SERVICE.tar"
echo "Restarting deployment..."
ssh admin@control-plane.local "/usr/local/bin/multipass exec microk8s-vm -- microk8s kubectl rollout restart deployment/$SERVICE -n cloud-services"
echo "Done!"
fi

View File

@@ -0,0 +1,25 @@
ARG BASE_IMAGE=cloud_base_go
FROM ${BASE_IMAGE} as builder-go
WORKDIR /build/attendant
COPY ./attendant/go.mod ./attendant/go.sum ./
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
&& go mod download
COPY ./attendant ./
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
&& go build -tags musl
FROM alpine:3.17
RUN apk add --no-cache librdkafka --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
&& apk add --no-cache ca-certificates
COPY ./modules_go/logger/log_config .
COPY --from=builder-go /build/attendant/attendant .
ENV LOG_CONFIG=log_config
EXPOSE 8077
CMD ./attendant

View File

@@ -0,0 +1,518 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/common"
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/manifestsender"
"github.com/fiskerinc/cloud-services/pkg/redis"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
"github.com/fiskerinc/cloud-services/pkg/hwversion"
"github.com/go-pg/pg/v10"
r "github.com/gomodule/redigo/redis"
"github.com/pkg/errors"
)
const redisObjectExpire = 3600
const (
PackageDownloadStart = "package_download_start"
PackageDownloadComplete = "package_download_complete"
PackageInstallStart = "package_install_start"
PackageInstallComplete = "package_install_complete"
InstallError = "install_error"
)
var RepeatedStatus = errors.New("RepeatedStatus")
// CarUpdateProgress takes in a car update message and saves it to our database
// This includes setting the status of a car update, and telling the car and SAP that the update is done
func NewCarUpdateProgress(clientPool redis.ClientPoolInterface, ka *services.KeepAwake, db *services.DB, device common.Device) CarUpdateProgressInterface {
if device == common.TRex {
return &CarUpdateProgress{
RedisClientPool: clientPool,
DB: db,
ka: ka,
}
}
if device == common.HMI {
return &HMICarUpdateProgress{
conf: services.GetVehicleConfig(),
sms: services.GetSMSClient(),
ka: ka,
CarUpdateProgress: CarUpdateProgress{
RedisClientPool: clientPool,
DB: db,
ka: ka,
},
}
}
return nil
}
type CarUpdateProgressInterface interface {
Process(vin string, data []byte) error
ProcessStatus(vin string, status common.CarUpdateProgress) error
Dispose()
}
type CarUpdateProgress struct {
RedisClientPool redis.ClientPoolInterface
DB *services.DB
ka *services.KeepAwake
}
func (cu *CarUpdateProgress) Process(vin string, data []byte) error {
var status common.CarUpdateProgress
err := json.Unmarshal(data, &status)
if err != nil {
return err
}
return cu.ProcessStatus(vin, status)
}
func (cu *CarUpdateProgress) ProcessStatus(vin string, status common.CarUpdateProgress) (err error) {
if cu.transformDBCarUpdateProgress(&status) {
err = cu.logStatusDB(status)
// If the error is the repeated, we can just exit early
if err != nil {
if errors.Is(err, queries.RepeatedStatus) {
err = nil
}
return
}
}
cu.cancelTheCANAwake(vin, status)
batch := redis.NewRedisBatchCommands()
cu.transformRedisCarUpdateProgress(&status)
cu.BatchCacheRedis(batch, redis.CarUpdateStatusHashKey(status.CarUpdateID), &status)
client := cu.RedisClientPool.GetFromPool()
defer client.Close()
_, err = client.ExecuteBatch(batch)
if err != nil {
return err
}
// do not send car update status for internal cloud statuses
if cu.isInternalStatus(status) {
return nil
}
msg := cu.getMessage(&status)
err = cu.publishStatusHMI(vin, &msg)
if err != nil {
return err
}
msgMobile := cu.getMessageForMobile(&status, vin)
err = cu.publishStatusMobile(vin, &msgMobile)
if err != nil {
return err
}
err = cu.onUpdateManifestComplete(&status, vin)
return err
}
// We will try and cancel sending CAN status stuff
func (cu *CarUpdateProgress) cancelTheCANAwake(vin string, status common.CarUpdateProgress) {
switch status.Status {
case s.DownloadFailed, s.InstallFailed, s.ManifestCancelAccepted, s.ManifestCancelRejected,
s.ManifestError, s.ManifestRejected, s.ManifestValidationFailed, s.RequirementsFailed, s.ManifestCanceled:
logger.Info().Msgf("canceling CAN Awake for %s because %s", vin, status.Status)
cu.ka.RemoveKeepAwakeMessage(vin)
}
}
func (cu *CarUpdateProgress) isInternalStatus(status common.CarUpdateProgress) bool {
return status.Status == s.Sent || status.Status == s.Pending
}
func (cu *CarUpdateProgress) transformDBCarUpdateProgress(status *common.CarUpdateProgress) bool {
switch status.Status {
case s.DownloadStarted:
if status.PackageCurrent == 0 {
status.Status = PackageDownloadStart
}
return true
case s.DownloadCompleted:
if status.PackageCurrent == status.PackageTotal {
status.Status = PackageDownloadComplete
}
return true
case s.InstallStarted:
if status.InstalledFiles == 0 && status.TotalFiles > 0 {
status.Status = PackageInstallStart
}
return true
case s.InstallSucceeded:
if status.InstalledFiles == status.TotalFiles && status.TotalFiles > 0 {
status.Status = PackageInstallComplete
}
return true
case InstallError:
status.Status = s.InstallFailed
return true
case s.Installing:
return false
case s.Downloading:
// these status updates do not need to be saved in the database
return false
}
return true
}
func (cu *CarUpdateProgress) transformRedisCarUpdateProgress(status *common.CarUpdateProgress) {
switch status.Status {
case s.DownloadStarted, s.DownloadCompleted, PackageDownloadStart:
status.Status = s.Downloading
case s.InstallStarted, s.InstallSucceeded, PackageInstallStart:
status.Status = s.Installing
}
}
func (cu *CarUpdateProgress) logStatusDB(status common.CarUpdateProgress) (err error) {
// If we are one of these status's we want to ignore, then we need to do some extra database steps, otherwise insert normally
carUpdate := common.CarUpdate{
ID: status.CarUpdateID,
Status: status.Status,
ErrorCode: status.ErrorCode,
Info: strings.TrimSpace(fmt.Sprintf("%s %s", status.ECU, status.Info)),
}
if _, ok := s.NoRepeatUpdateStatus[status.Status]; ok {
_, err = cu.DB.GetCarUpdates().UpdateStatusIfNotRepeat(&carUpdate)
return
}
_, err = cu.DB.GetCarUpdates().UpdateStatus(&carUpdate)
return err
}
func (cu *CarUpdateProgress) GetCache(key string) (*common.CarUpdateProgress, error) {
client := cu.RedisClientPool.GetFromPool()
defer client.Close()
status := common.CarUpdateProgress{}
err := client.GetObject(key, &status)
return &status, err
}
func (cu *CarUpdateProgress) BatchCacheRedis(batch *redis.RedisBatchCommands, key string, status *common.CarUpdateProgress) {
batch.Add(r.Args{}.Add("HSET").Add(key).AddFlat(status)...)
batch.Add("EXPIRE", key, redisObjectExpire)
}
func (cu *CarUpdateProgress) getMessage(status *common.CarUpdateProgress) common.Message {
return common.Message{
Handler: "car_update_status",
Data: status,
}
}
func (cu *CarUpdateProgress) getMessageForMobile(status *common.CarUpdateProgress, vin string) common.Message {
type mobileData struct {
VIN string `json:"vin"`
*common.CarUpdateProgress
}
return common.Message{
Handler: "car_update_status",
Data: mobileData{vin, status},
}
}
func (cu *CarUpdateProgress) publishStatusHMI(vin string, msg *common.Message) error {
client := cu.RedisClientPool.GetFromPool()
defer client.Close()
// redis publish to HMI
hmiKey := common.HMI.Key(vin)
// Add VIN
err := client.SafePublishMessage(hmiKey, msg)
return err
}
func (cu *CarUpdateProgress) publishStatusMobile(vin string, msg *common.Message) error {
drivers := cache.NewDriversCache(cu.RedisClientPool, cu.DB.GetCars())
// redis publish to mobile devices
driverIDs, err := drivers.RetrieveDriverIDs(vin)
if err != nil {
return err
}
// Change thos for loop to isntead create a batch and execute it all at once
client := cu.RedisClientPool.GetFromPool()
defer client.Close()
for _, d := range driverIDs {
mobileKey := common.Mobile.Key(d)
err = client.SafePublishMessage(mobileKey, msg)
if err != nil {
return err
}
}
return nil
}
func (cu *CarUpdateProgress) Dispose() {
cu.DB = nil
}
type HMICarUpdateProgress struct {
conf vconfig.ConfigServiceInterface
sms sms.SMSServiceClient
ka *services.KeepAwake
CarUpdateProgress
}
func (h *HMICarUpdateProgress) Process(vin string, data []byte) error {
var status common.CarUpdateProgress
err := json.Unmarshal(data, &status)
if err != nil {
return err
}
if h.downloadComplete(&status) {
// stop calling the sendKeepAwakeMessage
h.ka.RemoveKeepAwakeMessage(vin)
_, err = h.sendManifestToTRex(vin, &status)
if err != nil {
return err
}
h.logStatusDB(common.CarUpdateProgress{
CarUpdateID: status.CarUpdateID,
Status: s.Sent,
Info: "TBOX",
})
}
return h.ProcessStatus(vin, status)
}
func (h *HMICarUpdateProgress) downloadComplete(status *common.CarUpdateProgress) bool {
return status.Status == s.DownloadCompleted
}
func (h *HMICarUpdateProgress) getManifest(status *common.CarUpdateProgress) (*common.UpdateManifest, error) {
update := common.CarUpdate{ID: status.CarUpdateID}
err := h.DB.GetCarUpdates().Load(&update)
if err != nil {
return nil, err
}
update.UpdateManifest.CarUpdateID = status.CarUpdateID
return update.UpdateManifest, nil
}
func (h *HMICarUpdateProgress) sendManifestToTRex(vin string, status *common.CarUpdateProgress) (msgID string, err error) {
logger.Info().Msgf("HMICarUpdateProgress sendManifestToTRex car_update_id %d", status.CarUpdateID)
manifest, err := h.getManifest(status)
if err != nil {
return
}
if !manifest.HasSelfDownload() {
logger.Error().Msgf("%s download_completed for non-self-download manifest", vin)
return
}
err = hwversion.SetHWVersion(manifest, vin, services.GetDB().GetCars())
if err != nil {
// An error here is very unexpected. The hw versioning should have been confirmed earlier before ICC was updated
err = errors.WithStack(err)
logger.Err(err).Str("VIN", vin).Int64("UpdateID", status.CarUpdateID).Msg("failed to set hw versions for a manifest after ICC complete update")
err = nil
}
manifest.SortECUs()
manifest.FilterCompatibleECUs(vin)
// This code is going to be removed by mny other PR so not going to mess with it for now
client := h.RedisClientPool.GetFromPool()
defer client.Close()
trex := manifestsender.NewTBOXManifestSender(client, h.conf, h.DB, h.sms, nil)
defer trex.Close()
msgID, err = trex.ProcessSoftwareUpdate(vin, manifest, services.GetDB().GetCarConfigData())
return
}
func (h *HMICarUpdateProgress) GetRedisHashKey(status *common.CarUpdateProgress) string {
return redis.CarUpdateStatusHMIHashKey(status.CarUpdateID)
}
// Car Update Done
func (cu *CarUpdateProgress) onUpdateManifestComplete(status *common.CarUpdateProgress, vin string) (err error) {
success := false
final := false
submitSAP := false
switch status.Status {
case s.ManifestSucceeded:
success = true
submitSAP = true
final = true
case s.ManifestCanceled, s.ManifestError, s.ManifestRejected:
success = false
submitSAP = true
final = true
case s.DownloadFailed, s.ManifestCancelPending, s.RollbackSucceeded, s.RollbackFailed, s.CleanupSucceeded:
final = true
default:
return nil
}
carUpdatesDB := cu.DB.GetCarUpdates()
carUpdate, err := carUpdatesDB.SelectByID(status.CarUpdateID)
if err != nil {
err = errors.WithStack(err)
return
}
if carUpdate != nil {
// Notify car user of in progress update through FOA API
fs := services.GetFoaService()
foaResp, err := fs.OtaUpdateStatus(vin, carUpdate, status)
if err != nil || (foaResp != nil && foaResp.StatusCode != http.StatusOK) {
bodyBytes, _ := io.ReadAll(foaResp.Body)
bodyString := string(bodyBytes)
logger.Err(err).Msgf("notify FOA for update manifest %d final state %s for %s failed with http status %d and message %s", carUpdate.UpdateManifestID, status.Status, vin, foaResp.StatusCode, bodyString)
err = nil
}
}
logger.Info().Msgf("Manifest update completed for %s with status of %s", vin, status.Status)
if submitSAP {
logger.Info().Msg("SAP: No Longer Submit Updates")
// sap := services.GetSapService()
// err = sap.SubmitResult(vin, success)
// if err != nil {
// requestBody := struct {
// VIN string
// Success bool
// CarUpdateProgress common.CarUpdateProgress
// }{VIN: vin, Success: success, CarUpdateProgress: *status}
// logger.Err(err).Interface("body", requestBody).Msgf("failed to call sap submit result")
// err = nil
// }
}
if success {
// If we are successful, we want to possibly update the cars sums version
// Need to pull the manifest to check it has a sums version, and then update the car
err = cu.updateCarsSUMSVersion(status)
if err != nil {
logger.Err(err).Msgf("failed to update car sums version for manifest with CarUpdateID %d", status.CarUpdateID)
err = nil
}
// Send the read_ecu_versions remote command so that the ECU data is updated in postgres ASAP
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err = client.SafePublishMessage(
common.TRex.Key(vin),
common.Message{
Handler: "read_ecu_versions",
Data: common.RemoteReadVersionsCommandArgs{
ECUName: "*",
},
},
)
if err != nil {
logger.Err(err).Msgf("failed to send read_ecu_versions command to vin %s", vin)
err = nil
}
}
if final {
// if the manifest is in a final state
// then delete the redundant requirements_await rows from car_update_statuses, to avoid overcrowding the table
err = cu.truncateRequirementsAwaitForUpdate(status)
if err != nil {
logger.Err(err).Msgf("failed to delete redundant requirements_await rows from car_update_statuses for manifest with CarUpdateID %d", status.CarUpdateID)
err = nil
}
}
return err
}
func (cu *CarUpdateProgress) truncateRequirementsAwaitForUpdate(status *common.CarUpdateProgress) error {
logger.Info().Msgf("Manifest with CarUpdateID %d successful with status %s. Deleting redundant requirements_await rows from car_update_statuses", status.CarUpdateID, status.Status)
_, err := cu.DB.GetCarUpdates().TruncateRequirementsAwaitForUpdate(status.CarUpdateID)
if err != nil && !errors.Is(err, pg.ErrNoRows) {
return err
}
return nil
}
// Find the car update, and it gives the update manifest
// If the manifest has a sums version, apply it to the car
func (cu *CarUpdateProgress) updateCarsSUMSVersion(status *common.CarUpdateProgress) (err error) {
carUpdatesDB := cu.DB.GetCarUpdates()
carUpdate, err := carUpdatesDB.SelectByID(status.CarUpdateID)
if err != nil {
err = errors.WithStack(err)
return
}
if carUpdate.UpdateManifest == nil {
err = errors.New("failed to pull car updates update manifest")
return
}
um := carUpdate.UpdateManifest
// So if we have have a sums version we want to update
if um.SUMS == "" {
return
}
carsDB := cu.DB.GetCars()
filter := common.Car{
VIN: carUpdate.VIN,
}
cars, err := carsDB.Select(&filter, nil)
if err != nil {
err = errors.WithStack(err)
return
}
if len(cars) != 1 {
err = fmt.Errorf("did not receive only one car, received: %d", len(cars))
err = errors.WithStack(err)
return
}
car := cars[0]
car.SUMSVersion = um.SUMS
_, err = carsDB.Update(&car)
if err != nil {
err = errors.WithStack(err)
}
return
}

View File

@@ -0,0 +1,184 @@
package controllers
import (
"github.com/fiskerinc/cloud-services/services/attendant/services"
"encoding/base64"
"encoding/json"
"errors"
"sync"
"time"
"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
)
type DTCEntry struct {
CreatedAt time.Time `bson:"created_at"`
VIN string `bson:"vin"`
ECU string `json:"ecu" bson:"ecu"`
DTC uint64 `json:"dtc" bson:"dtc"`
Status uint8 `json:"status" bson:"status"`
Timestamp time.Time `json:"timestamp" bson:"timestamp"`
Speed uint16 `json:"speed" bson:"speed"`
Mileage uint32 `json:"mileage" bson:"mileage"`
Voltage uint16 `json:"voltage" bson:"voltage"`
SnapshotBase64 string `json:"snapshot,omitempty" bson:"snapshot,omitempty"`
}
func GRPCToDTCEntry(payload *kafka_grpc.GRPC_AttendantPayload) []byte {
if payload.Data == nil {
return nil
}
data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_DtcEntry)
if data == nil {
return nil
}
dtc := &DTCEntry{
VIN: data.DtcEntry.Vin,
ECU: data.DtcEntry.Ecu,
CreatedAt: milliToDate(data.DtcEntry.CreatedAt),
DTC: data.DtcEntry.Dtc,
Status: uint8(data.DtcEntry.Status),
Timestamp: milliToDate(data.DtcEntry.Timestamp),
Speed: uint16(data.DtcEntry.Speed),
Mileage: data.DtcEntry.Mileage,
Voltage: uint16(data.DtcEntry.Volt),
SnapshotBase64: data.DtcEntry.SnapshotBase64,
}
bytes, _ := json.Marshal(dtc)
return bytes
}
func milliToDate(timestamp int64) time.Time {
seconds := timestamp / 1000
nanoseconds := (timestamp % 1000) * int64(time.Millisecond)
return time.Unix(seconds, nanoseconds)
}
const SNAPSHOT_LEN = 21
func (entry *DTCEntry) ParseSnapshot() error {
data, errc := base64.StdEncoding.DecodeString(entry.SnapshotBase64)
if errc != nil {
return errc
}
payloadLen := len(data) - 2
if payloadLen < SNAPSHOT_LEN {
logger.Debug().Msgf("DTC snapshot payload is too small. Required length=%d, actual=%d, ECU=%s\n", SNAPSHOT_LEN, len(data), entry.ECU)
return errors.New("Snapshot too small")
}
// Our "Basic Diagnostic" spec defines 4 mandatory DIDs that must be
// stored inside DTCStapshotRecord:
// 1. EF F6 - Timestamp (6 bytes).
// 2. EF F7 - Vehicle Speed (2 bytes).
// 3. EF F8 - Milage (idk, our spec doesn't specify exact number.
// Empirically it's 4 bytes (same as "ICC_0x531::TotMilg_ODO").
// 4. EF F9 - Battery Voltage (2 bytes).
///entry.Speed = int16(speed.ToPhysical(float64(uint16(decodedBytes[0])<<8 | uint16(decodedBytes[1]))))
//entry.Voltage = int16(voltage.ToPhysical(float64(uint16(decodedBytes[0])<<8 | uint16(decodedBytes[1]))))
for idx := 2; idx < len(data)-1; {
if data[idx] != 0xEF {
idx++
continue
}
if data[idx+1] == 0xF6 && idx+7 < len(data) {
idx += 2
entry.Timestamp, idx = consumeTimestamp(data, idx)
} else if data[idx+1] == 0xF7 && idx+3 < len(data) {
idx += 2
entry.Speed, idx = consumeSpeed(data, idx)
} else if data[idx+1] == 0xF8 && idx+5 < len(data) {
idx += 2
entry.Mileage, idx = consumeMileage(data, idx)
} else if data[idx+1] == 0xF9 && idx+3 < len(data) {
idx += 2
entry.Voltage, idx = consumeVoltage(data, idx)
} else {
idx++
}
}
return nil
}
var onceTimestamp sync.Once
var (
day *descriptor.Signal
hr *descriptor.Signal
mins *descriptor.Signal
yr *descriptor.Signal
month *descriptor.Signal
sec *descriptor.Signal
)
func consumeTimestamp(data []byte, idx int) (res time.Time, idx_ret int) {
onceTimestamp.Do(func() {
day, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Day")
hr, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Hr")
mins, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Mins")
yr, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Yr")
month, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Mth")
sec, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Sec")
})
res = time.Date(int(yr.ToPhysical(float64(data[idx]))),
time.Month(month.ToPhysical(float64(data[idx+1]))),
int(day.ToPhysical(float64(data[idx+2]))),
int(hr.ToPhysical(float64(data[idx+3]))),
int(mins.ToPhysical(float64(data[idx+4]))),
int(sec.ToPhysical(float64(data[idx+5]))), 0, time.UTC)
idx_ret = idx + 6
return res, idx_ret
}
var onceSpeed sync.Once
var speed *descriptor.Signal
func consumeSpeed(data []byte, idx int) (res uint16, idx_ret int) {
onceSpeed.Do(func() {
speed, _ = services.GetDBC().Signal(0x318, "ESP_VehSpd")
})
res = uint16(speed.ToPhysical(float64(uint16(data[idx])<<8 | uint16(data[idx+1]))))
idx_ret = idx + 2
return res, idx_ret
}
var onceMileage sync.Once
var mileage *descriptor.Signal
func consumeMileage(data []byte, idx int) (res uint32, idx_ret int) {
onceMileage.Do(func() {
mileage, _ = services.GetDBC().Signal(0x531, "ICC_TotMilg_ODO")
})
res = uint32(mileage.ToPhysical(
float64(
uint64(data[idx])<<24 |
uint64(data[idx+1])<<16 |
uint64(data[idx+2])<<8 | uint64(data[idx+3]))))
idx_ret = idx + 4
return res, idx_ret
}
var onceVoltage sync.Once
var voltage *descriptor.Signal
func consumeVoltage(data []byte, idx int) (res uint16, idx_ret int) {
onceVoltage.Do(func() {
voltage, _ = services.GetDBC().Signal(0x507, "VCU_BattVolt")
})
res = uint16(voltage.ToPhysical(float64(uint16(data[idx])<<8 | uint16(data[idx+1]))))
idx_ret = idx + 2
return res, idx_ret
}

View File

@@ -0,0 +1,60 @@
package controllers_test
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"encoding/json"
"fmt"
"testing"
)
func TestParsing(t *testing.T) {
ecc := []byte(`{"ecu":"ECC","dtc":1719200,"status":9,"snapshot":"AQTv9ucIARIKNO/3AADv+AAAAADv+TLI"}`)
icc := []byte(`{"ecu":"ICC","dtc":14302599,"status":9,"snapshot":"AQUFAAHv+AAAAADv+S2W7/cAAO/2B+cIChAB"}`)
gw := []byte(`{"ecu":"GW","dtc":10718486,"status":8,"snapshot":"AQTv+QLA7/cAAO/2CAcHFAMs7/gAAAAA"}`)
mcu := []byte(`{"ecu":"MCU","dtc":14123795,"status":47,"snapshot":"AQTv9ggHCRcBD+/3AADv+AAAAADv+TLI"}`)
check := func(ecu []byte) {
var entry controllers.DTCEntry
err := json.Unmarshal(ecu, &entry)
if err != nil {
t.Error(err)
}
entry.ParseSnapshot()
if entry.Mileage != 0 {
t.Errorf("Incorrect Mileage %d", entry.Mileage)
}
fmt.Println(entry.Timestamp.Unix())
switch entry.ECU {
case "ECC":
if entry.Timestamp.Unix() != 8730871852 {
t.Errorf("Incorrect Timestamp %d", entry.Timestamp.Unix())
}
if entry.Voltage != 13 {
t.Errorf("Incorrect Voltage %d", entry.Voltage)
}
case "ICC":
if entry.Timestamp.Unix() != 1670580961 {
t.Errorf("Incorrect Timestamp %d", entry.Timestamp.Unix())
}
if entry.Voltage != 11 {
t.Errorf("Incorrect Voltage %d", entry.Voltage)
}
case "GW":
if entry.Timestamp.Unix() != 1691525024 {
t.Errorf("Incorrect Timestamp %d", entry.Timestamp.Unix())
}
case "MCU":
if entry.Timestamp.Unix() != 1691708475 {
t.Errorf("Incorrect Timestamp %d", entry.Timestamp.Unix())
}
}
}
check(ecc)
check(icc)
check(gw)
check(mcu)
}

View File

@@ -0,0 +1,81 @@
package controllers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
// Mega ducky repeat of /handlers/get_file_keys. Need a code re-org
func GetFileKeys(db *services.DB, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("GetFileKeys %v %s", device, id)
var err error
var req *common.FileKeysRequest
client := services.RedisClientPool().GetFromPool()
defer client.Close()
req, err = parseGetFileKeysRequest(data)
if err != nil {
notifyFileKeysGeneralError(client, device, id, err)
return err
}
keys, err := cache.RetrieveFileEncryptionParams(client, db.GetFileKeys(), req.FileIDs)
if err != nil {
notifyFileKeysGeneralError(client, device, id, err)
return err
}
err = client.SafePublishMessage(device.Key(id), common.Message{
Handler: "filekeys",
Data: keys,
})
if err != nil {
return err
}
logger.Debug().Msgf("GetFileKeys sent %v %s", device, id)
return nil
}
func parseGetFileKeysRequest(data []byte) (*common.FileKeysRequest, error) {
var status common.FileKeysRequest
err := json.Unmarshal(data, &status)
if err != nil {
return nil, errors.WithStack(err)
}
err = validator.ValidateStruct(status)
if err != nil {
return &status, errors.WithStack(err)
}
return &status, nil
}
func notifyFileKeysGeneralError(client redis.Client, device common.Device, id string, err error) {
e := client.SafePublishMessage(device.Key(id), common.Message{
Handler: "filekeys",
Data: []common.FileKeyResponse{
{
FileID: "0",
Error: err.Error(),
},
},
})
if e != nil {
logger.Error().Err(errors.WithStack(e)).Send()
}
}

View File

@@ -0,0 +1,59 @@
package controllers
import (
"time"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/health"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
var mismatchTypeError = errors.New("mismatch type error")
func HealthCheck() {
redis := health.NewRedisHealth(services.RedisClientPool())
server := health.HealthCheckServer{}
err := server.Serve([]health.Config{
{
Name: "db",
Check: health.NewPostgresCheck(services.GetDB().GetDBClient().GetConn()),
Timeout: time.Second * 1,
},
{
Name: "redis",
Check: redis.Check,
Timeout: time.Second * 1,
},
{
Name: "kafka",
Check: health.NewKafkaMultiCheck(getKafkaConsumer),
Timeout: time.Second * 1,
Vital: true,
},
})
if err != nil {
logger.Error().Err(err).Send()
}
}
func getKafkaConsumer() (connections []health.KafkaConnCheckInterface, err error) {
client, oldClient, err := services.GetKafkaConsumer()
if err != nil {
return connections, err
}
conn, ok := client.(health.KafkaConnCheckInterface)
if !ok {
return nil, errors.WithStack(mismatchTypeError)
}
connections = append(connections, conn)
oldConn, ok := oldClient.(health.KafkaConnCheckInterface)
if !ok {
return connections, errors.WithStack(mismatchTypeError)
}
connections = append(connections, oldConn)
return connections, nil
}

View File

@@ -0,0 +1,403 @@
package controllers
import (
"encoding/json"
"fmt"
"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/carcommand"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/hwversion"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/manifestsender"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/tmobile"
uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
"github.com/fiskerinc/cloud-services/pkg/utils/randomvalues"
"github.com/fiskerinc/cloud-services/pkg/utils/whereami"
"github.com/fiskerinc/cloud-services/pkg/validator"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
"google.golang.org/protobuf/proto"
"sync"
"time"
"github.com/pkg/errors"
)
// 0100084000000101012200010101010001010101000000000000000000ff7eff7f000101010101000101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101000100010001010101010101010101010000000000000100010101020101010101010000000000000000ffffff00010102010102020001010000000000000000000000000000000000000000000000000000000000000000000100202310010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6
var defaultVOD string = envtool.GetEnv("DEFAULT_VOD", "00FF111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111114A")
var fileKeyBypass map[int64]string
var FileKeyBypassManifest_US map[int64]string = map[int64]string{
1189: "fe4f19fbc940ed58",
1380: "6a44fd71c940716d",
}
var FileKeyBypassManifest_EU map[int64]string = map[int64]string{
893: "fe4f19fbc940ed58",
1002: "6a44fd71c940716d",
1012: "d70e1d32c940a221",
}
func init() {
if whereami.Environment == whereami.PRODUCTION_EU {
fileKeyBypass = FileKeyBypassManifest_EU
} else {
fileKeyBypass = FileKeyBypassManifest_US
}
}
func NewManifestSender(
r redis.ClientPoolInterface,
db *services.DB,
conf vconfig.ConfigServiceInterface,
device common.Device,
ka *services.KeepAwake,
seed int64, //This seed is used only for testing
) *ManifestSender {
randomGenerator := randomvalues.NewNonCryptoGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ", seed)
return &ManifestSender{
Redis: r,
db: db,
conf: conf,
Device: device,
sms: services.GetSMSClient(),
ka: ka,
gen: randomGenerator,
}
}
type ManifestSender struct {
Redis redis.ClientPoolInterface
db *services.DB
conf vconfig.ConfigServiceInterface
sms sms.SMSServiceClient
Device common.Device
ka *services.KeepAwake
gen randomvalues.NonCryptoGenerator
}
func (m *ManifestSender) Process(id string, data []byte) error {
// id string parameter is unused except in the following log
// the data might not be used either, just for its single carUpdateID
u, err := m.parseRequest(data)
if err != nil {
logger.Err(err).Msgf("Manifest sender, unable to process update %s", id)
return err
}
carUpdate := common.CarUpdate{ID: u.CarUpdateID}
err = m.loadCarUpdate(&carUpdate)
if err != nil {
return err
}
logger.Info().Msgf("ManifestSender car_update_id %d", carUpdate.ID)
carUpdate.UpdateManifest.CarUpdateID = carUpdate.ID
helper := uhelpers.NewECUKeys(m.db.GetECCKeys())
err = helper.AddECUECCKeys(carUpdate.UpdateManifest)
if err != nil {
logger.Err(err).Msgf("Unable to decrypt keys for %d", carUpdate.ID)
return err //comment out for local debugging
}
// If we fail to deliver the sms, then we don't believe the car has awoken
// then set the manifest as failed
err = m.send(carUpdate.VIN, carUpdate.UpdateManifest)
if err != nil {
update := NewCarUpdateProgress(m.Redis, m.ka, m.db, common.TRex)
err = update.ProcessStatus(carUpdate.VIN, common.CarUpdateProgress{
CarUpdateID: u.CarUpdateID,
Status: carupdatestatus.ManifestCanceled,
Info: " Underlying error: " + err.Error(),
})
}
return err
}
func (m *ManifestSender) destination(manifest *common.UpdateManifest) string {
if manifest.HasSelfDownload() {
return "ICC"
} else {
return "ICC/TBOX"
}
}
func (m *ManifestSender) parseRequest(data []byte) (*common.UpdateManifest, error) {
var req common.UpdateManifest
err := json.Unmarshal(data, &req)
if err != nil {
return nil, errors.WithStack(err)
}
err = validator.ValidateIDField(req.CarUpdateID)
if err != nil {
return &req, errors.WithStack(err)
}
return &req, nil
}
func (m *ManifestSender) loadCarUpdate(cu *common.CarUpdate) error {
err := m.db.GetCarUpdates().Load(cu)
if err != nil {
return errors.WithStack(err)
}
if cu.UpdateManifest == nil {
return nil
}
return nil
}
func (m *ManifestSender) queueManifest(vin string, manifest *common.UpdateManifest) error {
loggedManifest := manifest.Copy()
loggedManifest.RemoveECCKeysFromECUs()
logger.At(logger.Info(), common.HMI.Key(vin), "update").Interface("manifest.json", loggedManifest).Msgf("HMI ManifestSender sent %v %s %d", common.HMI, vin, manifest.CarUpdateID)
client := m.Redis.GetFromPool()
defer client.Close()
err := client.SafeQueueMessage(common.HMI.Key(vin), common.Message{
Handler: "update_manifest",
Data: manifest,
})
if err != nil {
logger.Err(err).Msgf("failed to queue manifest in redis vin %s ", vin)
}
return err
}
func (m *ManifestSender) send(vin string, manifest *common.UpdateManifest) error {
updateManifestID := manifest.ID
manifest.TransformECUNames()
err := hwversion.SetHWVersion(manifest, vin, m.db.GetCars())
if err != nil {
logger.Err(err).Msgf("manifest sender failed at SetHwVersion for vin %s", vin)
return err //comment out for local debugging
}
manifest.SortECUs()
manifest.RemoveOriginalS19HexFiles()
manifest.RemoveOriginalS19HexFilesRollbacks()
manifest.FilterCompatibleECUs(vin)
err = uhelpers.PopulateECUsCurrentVersion(m.db.GetCars(), vin, manifest.ECUs)
if err != nil {
logger.Err(err).Msgf("manifest sender failed at PupulateECUsCurrentVersion for vin %s", vin)
return err
}
fpparams := manifestfingerprintparams.GetFPParams()
manifest.GenerateFingerprint(fpparams.CurTime(), fpparams.ManifestSerial())
hmiManifest := manifest.Copy()
cds, err := m.addVOD(hmiManifest, vin)
if err != nil {
logger.Err(err).Msgf("manifest sender failed at adding the vod for vin %s", vin)
return err // comment out for local debugging
}
err = validator.ValidateField(hmiManifest.SUMS, "sums_version")
if err == nil {
err = hmiManifest.AddSUMSToVOD()
if err != nil {
logger.Err(err).Msgf("manifest sender failed at AddSUMSToVOD for vin %s", vin)
return err
}
} else {
logger.Err(err).Msgf("manifest sender failed at Validation for vin %s", vin)
return err
}
hmiManifest.Scrub(common.HMI)
client := m.Redis.GetFromPool()
defer client.Close()
// If HasSelfDownload, we are not yet ready to send to t box, so we wake the car if the update is forced
if manifest.HasSelfDownload() && m.sms != nil {
// The function that calls this one handles canceling the manifest
var msgID string
res, err := carcommand.QueueSMSWakeUp(vin, true, client, m.db.GetCars(), m.sms)
if err != nil || res == nil || !res.SentSuccessful {
logger.Err(err).Msgf("Attendant:manifest sender failed at sendSMSWakeUp for vin %s", vin)
msgID = m.gen.GetString(10)
defer m.selfKafkaCallback(msgID)
} else {
msgID = res.SmsMsgID
}
err = m.ka.SendFirstKeepAwakeMessage(vin)
if err != nil {
logger.Err(err).Msgf("failed to SendFirstKeepAwakeMessage on msgID %s", msgID)
}
m.queueManifest(vin, hmiManifest)
fileID, ok := fileKeyBypass[updateManifestID]
if ok {
logger.Info().Str("VIN", vin).Msg("Queueing SendFileKeys")
time.AfterFunc(time.Second*3, func() { sendFileKeys(vin, fileID) })
}
// Do not send manifest to TBOX, wait until ICC has finished downloading
return err
}
trex := manifestsender.NewTBOXManifestSender(client, m.conf, m.db, services.GetSMSClient(), cds)
defer trex.Close()
//smsID, err := trex.ProcessSoftwareUpdate(vin, manifest)
_, err = trex.ProcessSoftwareUpdate(vin, manifest, services.GetDB().GetCarConfigData())
logger.Debug().Msgf("Send HMI manifest to %s ", vin)
m.queueManifest(vin, hmiManifest)
// We managed to send an sms, so we want to continue with an sms delivered check
// if smsID != "" {
// err = m.setManifestCache(smsID, ManifestCachedPoint{
// VIN: vin,
// ManifestID: manifest.ID,
// Destination: m.destination(manifest),
// })
// if err != nil {
// return err
// }
// }
return err
}
// So this flow of continue will be activated at two different times, possible multiple times.
// 1) When a new car_update comes through and the .hasdownload is true, and then again when the manifest is being sent to the tbox
func (m *ManifestSender) ContinueTBOXSend(id string, data []byte) (err error) {
// After a text has been succesfully delivered, we can now continue the update
// Check the status and make sure that is is success, otherwise fail and set the update status as failed
var msgStatus tmobile.MessageStatus
err = json.Unmarshal(data, &msgStatus)
if err != nil {
return
}
return nil
}
func (m *ManifestSender) setManifestCache(msgID string, cache ManifestCachedPoint) (err error) {
client := m.Redis.GetFromPool()
defer client.Close()
return client.Set(msgIDToRedisKey(msgID), cache)
}
type ManifestCachedPoint struct {
VIN string
ManifestID int64
Destination string
}
func msgIDToRedisKey(msgID string) (redisKey string) {
return fmt.Sprintf("manifest_tbox_send_cache:%s", msgID)
}
func (m *ManifestSender) addVOD(manifest *common.UpdateManifest, vin string) (map[string]string, error) {
cds, err := uhelpers.GetCDS(m.conf, services.GetDB().GetCarConfigData(), vin)
if err != nil {
return nil, err
}
if vod, ok := cds["VOD"]; ok && vod != "" {
manifest.VOD = vod
} else {
manifest.VOD = defaultVOD
}
return cds, nil
}
// When car is a virtual trex, we are going to produce to the kafka message queue as if the sms service did it
func (m *ManifestSender) selfKafkaCallback(messageID string) {
producer, err := services.GetKafkaProducer()
if err != nil {
logger.Err(err).Send()
return
}
messageStatus := &kafka_grpc.GRPC_AttendantPayload_MessageStatus{
MessageStatus: &kafka_grpc.MessageStatus{
MessageId: messageID,
Status: kafka_grpc.EmumStatus_DELIVERED,
},
}
kafkaMSG := kafka_grpc.GRPC_AttendantPayload{
Handler: "sms_delivery_status_manifest",
Data: messageStatus,
}
binaryPayload, _ := proto.Marshal(&kafkaMSG)
err = producer.ProduceBinary(kafka.AttendantServiceGRPCKafka, "4:Service", binaryPayload, nil)
if err != nil {
err = errors.WithStack(err)
logger.Err(err).Msgf("failed to produce kafka message for sms id %s", messageID)
}
}
func (m *ManifestSender) Release() {
m.Redis = nil
m.db = nil
m.conf = nil
}
var fpParams FingerprintParamer
var fpParamsOnce sync.Once
func SetFPParams(fpp FingerprintParamer) {
fpParams = fpp
}
func GetFPParams() FingerprintParamer {
fpParamsOnce.Do(func() {
if fpParams == nil {
fpParams = &fingerprintParams{
serialNum: envtool.GetEnv("OTA_MANIFEST_SERIAL", "00000000000000000"),
}
}
})
return fpParams
}
type fingerprintParams struct {
serialNum string
}
func (p *fingerprintParams) ManifestSerial() string {
return p.serialNum
}
func (p *fingerprintParams) CurTime() time.Time {
return time.Now().UTC()
}
type FingerprintParamer interface {
ManifestSerial() string
CurTime() time.Time
}
// Mega hack mode
func sendFileKeys(vin string, fileID string) {
logger.Info().Str("VIN", vin).Msg("Sending SendFileKeys")
db := services.GetDB()
err := GetFileKeys(db, common.HMI, vin, []byte(`{"file_ids": ["`+fileID+`"]}`))
if err != nil {
logger.Err(err).Str("VIN", vin).Str("file ID", fileID).Msg("Failed to SendFileKeys")
}
}

115
services/attendant/go.mod Normal file
View File

@@ -0,0 +1,115 @@
module github.com/fiskerinc/cloud-services/services/attendant
go 1.25
toolchain go1.25.0
require (
github.com/fiskerinc/cloud-services/pkg v0.0.0-00010101000000-000000000000
github.com/fiskerinc/cloud-services/pkg/can-go v0.0.0-00010101000000-000000000000
github.com/pkg/errors v0.9.1
)
require (
github.com/go-pg/pg/v10 v10.11.1
github.com/gomodule/redigo v1.8.9
github.com/jinzhu/copier v0.3.5
github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.67.3
google.golang.org/protobuf v1.36.1
)
require (
github.com/DataDog/appsec-internal-go v1.4.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
github.com/DataDog/go-libddwaf/v2 v2.2.3 // indirect
github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect
github.com/DataDog/sketches-go v1.4.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.5.2 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/jeremywohl/flatten v1.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.25.0 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/rs/zerolog v1.29.1 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/twmb/franz-go v1.20.6 // indirect
github.com/twmb/franz-go/pkg/kadm v1.17.2 // indirect
github.com/twmb/franz-go/pkg/kmsg v1.12.0 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect
mellium.im/sasl v0.3.1 // indirect
)
replace (
github.com/fiskerinc/cloud-services/pkg => ../../pkg
github.com/fiskerinc/cloud-services/pkg/can-go => ../../pkg/can-go
)

483
services/attendant/go.sum Normal file
View File

@@ -0,0 +1,483 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/appsec-internal-go v1.4.0 h1:KFI8ElxkJOgpw+cUm9TXK/jh5EZvRaWM07sXlxGg9Ck=
github.com/DataDog/appsec-internal-go v1.4.0/go.mod h1:ONW8aV6R7Thgb4g0bB9ZQCm+oRgyz5eWiW7XoQ19wIc=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ=
github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8=
github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q=
github.com/DataDog/go-libddwaf/v2 v2.2.3 h1:LpKE8AYhVrEhlmlw6FGD41udtDf7zW/aMdLNbCXpegQ=
github.com/DataDog/go-libddwaf/v2 v2.2.3/go.mod h1:8nX0SYJMB62+fbwYmx5J7zuCGEjiC/RxAo3+AuYJuFE=
github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I=
github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0=
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/DataDog/sketches-go v1.4.2 h1:gppNudE9d19cQ98RYABOetxIhpTCl4m7CnbRZjvVA/o=
github.com/DataDog/sketches-go v1.4.2/go.mod h1:xJIXldczJyyjnbDop7ZZcLxJdV3+7Kra7H1KMgpgkLk=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM=
github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 h1:m3Ayfs5OcAlIMEdLIQKubBsVLGee4YMUr14+d1256WE=
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7/go.mod h1:QIAMbrwsnQZ2ES3G26RubSrDB5SPyzsp9Hts5NJdTrI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts=
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8=
github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg=
github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0AA2Ibj+nNo4ek=
github.com/docker/docker v23.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/ebitengine/purego v0.5.2 h1:r2MQEtkGzZ4LRtFZVAg5bjYKnUbxxloaeuGxH0t7qfs=
github.com/ebitengine/purego v0.5.2/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
github.com/go-pg/pg/v10 v10.11.1 h1:vYwbFpqoMpTDphnzIPshPPepdy3VpzD8qo29OFKp4vo=
github.com/go-pg/pg/v10 v10.11.1/go.mod h1:ExJWndhDNNftBdw1Ow83xqpSf4WMSJK8urmXD5VXS1I=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redismock/v9 v9.2.0 h1:ZrMYQeKPECZPjOj5u9eyOjg8Nnb0BS9lkVIZ6IpsKLw=
github.com/go-redis/redismock/v9 v9.2.0/go.mod h1:18KHfGDK4Y6c2R0H38EUGWAdc7ZQS9gfYxc94k7rWT0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA=
github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg=
github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8=
github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/twmb/franz-go v1.20.6 h1:TpQTt4QcixJ1cHEmQGPOERvTzo99s8jAutmS7rbSD6w=
github.com/twmb/franz-go v1.20.6/go.mod h1:u+FzH2sInp7b9HNVv2cZN8AxdXy6y/AQ1Bkptu4c0FM=
github.com/twmb/franz-go/pkg/kadm v1.17.2 h1:g5f1sAxnTkYC6G96pV5u715HWhxd66hWaDZUAQ8xHY8=
github.com/twmb/franz-go/pkg/kadm v1.17.2/go.mod h1:ST55zUB+sUS+0y+GcKY/Tf1XxgVilaFpB9I19UubLmU=
github.com/twmb/franz-go/pkg/kmsg v1.12.0 h1:CbatD7ers1KzDNgJqPbKOq0Bz/WLBdsTH75wgzeVaPc=
github.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/intern v0.0.0-20230525184215-6c62f75575cb h1:ae7kzL5Cfdmcecbh22ll7lYP3iuUdnfnhiPcSaDgH/8=
go4.org/intern v0.0.0-20230525184215-6c62f75575cb/go.mod h1:Ycrt6raEcnF5FTsLiLKkhBTO6DPX3RCUCUVnks3gFJU=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 h1:Sqkq62MxQW/RD+sgZsQuUdHWHyXI4JS5x0lxlxrv2Hk=
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1/go.mod h1:6aArYrAHjnuaofJ3lKuSRQbhrBx1LcSpiEYCIScJE5Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ=
honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM=
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=

View File

@@ -0,0 +1,26 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
func CarUpdateProgressStatus(db *services.DB, ka *services.KeepAwake, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("CarUpdateProgressStatus %v %s", device, id)
clientPool := services.RedisClientPool()
handler := controllers.NewCarUpdateProgress(clientPool, ka, db, device)
if handler == nil {
return errors.Errorf("NewCarUpdateProgress cannot handle device %v", device)
}
defer handler.Dispose()
err := handler.Process(id, data)
return err
}

View File

@@ -0,0 +1,389 @@
package handlers_test
import (
"fmt"
"testing"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestCarUpdateProgressFunctional(t *testing.T) {
t.Skip()
testVIN := "WBSEH93466B798124"
conn := tester.NewRedisMock()
db := services.GetDB()
manifest, carUpdateID, err := setupCarUpdateProgressFunc(db, testVIN)
if err != nil {
panic(err)
}
defer func() {
if carUpdateID > 0 {
db.GetCarUpdates().Delete(&common.CarUpdate{ID: carUpdateID})
conn.Delete(redis.CarUpdateStatusTBOXHashKey(carUpdateID), redis.CarUpdateStatusHMIHashKey(carUpdateID))
}
if manifest != nil && manifest.ID > 0 {
db.GetUpdateManifests().Delete(manifest)
}
}()
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
type testCase struct {
Name string
Device common.Device
Payload string
ExpectedMsg string
ExpectInstalled int
ExpectInstallTotal int
ExpectDBStatus string
ExpectDownloadCurrent uint64
ExpectDownloadTotal uint64
ExpectErrorCode int
}
tests := []testCase{
{
Name: "manifest_received",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"msg": "manifest_received"
}`, carUpdateID),
ExpectedMsg: "manifest_received",
ExpectDBStatus: "manifest_received",
},
{
Name: "install_approval_await",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"msg": "install_approval_await"
}`, carUpdateID),
ExpectedMsg: "install_approval_await",
ExpectDBStatus: "install_approval_await",
},
{
Name: "other error",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 3,
"total_files": 10,
"msg": "other error",
"err": -100
}`, carUpdateID),
ExpectedMsg: "other error",
ExpectDBStatus: "other error",
ExpectInstalled: 3,
ExpectInstallTotal: 10,
ExpectErrorCode: -100,
},
{
Name: "download_start",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 0,
"file_total": 100,
"package_current": 0,
"package_total": 100,
"msg": "download_start",
"err": 0
}`, carUpdateID),
ExpectedMsg: "downloading",
ExpectDBStatus: "package_download_start",
ExpectDownloadCurrent: 0,
ExpectDownloadTotal: 100,
},
{
Name: "downloading",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 0,
"file_total": 100,
"package_current": 30,
"package_total": 100,
"msg": "downloading",
"err": 0
}`, carUpdateID),
ExpectedMsg: "downloading",
ExpectDBStatus: "package_download_start",
ExpectDownloadCurrent: 30,
ExpectDownloadTotal: 100,
},
{
Name: "download_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 100,
"file_total": 100,
"package_current": 900,
"package_total": 1000,
"msg": "download_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "downloading",
ExpectDBStatus: "package_download_start",
ExpectDownloadCurrent: 900,
ExpectDownloadTotal: 1000,
},
{
Name: "package_download_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 100,
"file_total": 100,
"package_current": 1000,
"package_total": 1000,
"msg": "download_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "package_download_complete",
ExpectDBStatus: "package_download_complete",
ExpectDownloadCurrent: 1000,
ExpectDownloadTotal: 1000,
},
{
Name: "download_error",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"file_current": 0,
"file_total": 100,
"package_current": 0,
"package_total": 1000,
"msg": "download_error",
"err": 0
}`, carUpdateID),
ExpectedMsg: "download_error",
ExpectDBStatus: "download_error",
ExpectDownloadCurrent: 0,
ExpectDownloadTotal: 1000,
},
{
Name: "install_start",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 0,
"total_files": 10,
"msg": "install_start",
"err": 0
}`, carUpdateID),
ExpectedMsg: "installing",
ExpectDBStatus: "package_install_start",
ExpectInstalled: 0,
ExpectInstallTotal: 10,
},
{
Name: "installing",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 2,
"total_files": 10,
"msg": "installing",
"err": 0
}`, carUpdateID),
ExpectedMsg: "installing",
ExpectDBStatus: "package_install_start",
ExpectInstalled: 2,
ExpectInstallTotal: 10,
},
{
Name: "install_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 9,
"total_files": 10,
"msg": "install_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "installing",
ExpectDBStatus: "package_install_start",
ExpectInstalled: 9,
ExpectInstallTotal: 10,
},
{
Name: "package_install_complete",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 10,
"total_files": 10,
"msg": "install_complete",
"err": 0
}`, carUpdateID),
ExpectedMsg: "package_install_complete",
ExpectDBStatus: "package_install_complete",
ExpectInstalled: 10,
ExpectInstallTotal: 10,
},
{
Name: "install_error",
Device: common.TRex,
Payload: fmt.Sprintf(`{
"car_update_id": %d,
"ecu": "TEST",
"installed": 3,
"total_files": 10,
"msg": "install_error",
"err": 0
}`, carUpdateID),
ExpectedMsg: "install_error",
ExpectDBStatus: "install_error",
ExpectInstalled: 3,
ExpectInstallTotal: 10,
},
{
Name: "package_download_complete",
Device: common.HMI,
Payload: fmt.Sprintf(`{
"car_update_id":%d,
"ecu":"ICC",
"file_current":null,
"file_total":null,
"package_current":920639485,
"package_total":920639485,
"installed":null,
"total_files":null,
"msg":"package_download_complete",
"err":null
}`, carUpdateID),
ExpectedMsg: "package_download_complete",
ExpectDBStatus: "package_download_complete",
ExpectInstalled: 3,
ExpectInstallTotal: 10,
ExpectDownloadCurrent: 920639485,
ExpectDownloadTotal: 920639485,
},
}
keys := make([]string, 1)
statuses := make([]interface{}, 1)
for _, test := range tests {
err := handlers.CarUpdateProgressStatus(db, ka, test.Device, testVIN, []byte(test.Payload))
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s output error", test.Device, test.Name), nil, err)
}
status := common.CarUpdateProgress{}
keys[0] = redis.CarUpdateStatusHashKey(carUpdateID)
statuses[0] = &status
err = conn.GetObjectsMulti(keys, statuses)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "GetObjectsMulti", nil, err)
return
}
if status.Status != test.ExpectedMsg {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s Status", test.Device, test.Name), test.ExpectedMsg, status.Status)
}
if status.InstalledFiles != test.ExpectInstalled {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s InstalledFiles", test.Device, test.Name), test.ExpectInstalled, status.InstalledFiles)
}
if status.TotalFiles != test.ExpectInstallTotal {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s TotalFiles", test.Device, test.Name), test.ExpectInstallTotal, status.TotalFiles)
}
if status.PackageCurrent != test.ExpectDownloadCurrent {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s PackageCurrent", test.Device, test.Name), test.ExpectDownloadCurrent, status.PackageCurrent)
}
if status.PackageTotal != test.ExpectDownloadTotal {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s PackageTotal", test.Device, test.Name), test.ExpectDownloadTotal, status.PackageTotal)
}
if status.ErrorCode != test.ExpectErrorCode {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s ErrorCode", test.Device, test.Name), test.ExpectErrorCode, status.ErrorCode)
}
cu, err := db.GetCarUpdates().SelectByID(carUpdateID)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "Get from DB", nil, err)
return
}
if cu.Status != test.ExpectDBStatus {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s DB Status", test.Device, test.Name), test.ExpectDBStatus, cu.Status)
}
if cu.ErrorCode != test.ExpectErrorCode {
t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("[%v] %s DB ErrorCode", test.Device, test.Name), test.ExpectErrorCode, cu.ErrorCode)
}
}
}
func setupCarUpdateProgressFunc(db *services.DB, vin string) (*common.UpdateManifest, int64, error) {
_, err := db.GetCars().SelectOrInsert(&common.Car{
VIN: vin,
Model: "Ocean",
Year: 2022,
Trim: "Sport",
})
if err != nil {
return nil, 0, err
}
manifest := common.UpdateManifest{
Name: "TestCarUpdateProgressFunctional",
Version: "1000",
Description: "For TestCarUpdateProgressFunctional",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
}
_, err = db.GetUpdateManifests().Insert(&manifest)
if err != nil {
return nil, 0, err
}
_, err = db.GetUpdateManifests().ECUInsert(&common.UpdateManifestECU{
UpdateManifestID: manifest.ID,
ECU: "ICC",
Version: "ICCVERSION",
})
if err != nil {
return nil, 0, err
}
_, err = db.GetUpdateManifests().ECUInsert(&common.UpdateManifestECU{
UpdateManifestID: manifest.ID,
ECU: "ADAS",
Version: "ADASVERSION",
})
if err != nil {
return nil, 0, err
}
carupdate := common.CarUpdate{
VIN: vin,
UpdateManifestID: manifest.ID,
}
_, err = db.GetCarUpdates().Insert(&carupdate)
if err != nil {
return nil, 0, err
}
return &manifest, carupdate.ID, nil
}

View File

@@ -0,0 +1,776 @@
package handlers_test
import (
"errors"
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
)
var (
schemaToTRex = "file://" + th.GetSchemaDirPath() + "/trex/RXMessage.json"
schemaToHMI = "file://" + th.GetSchemaDirPath() + "/hmi/RXMessage.json"
)
func TestCarUpdateProgress(t *testing.T) {
testGetSetResult := `["valid-cognito-id-1","valid-cognito-id-2"]`
testVIN := "JH4KA7680RC01"
mobile1Key := "3:valid-cognito-id-1"
mobile2Key := "3:valid-cognito-id-2"
hmiKey := "2:JH4KA7680RC01"
trexKey := common.TRex.Key(testVIN)
carupdateKey := "carupdate:297"
var bhex common.BinaryHex
expectedExpire := 3600
bhex = []byte("test")
fingerprintTime, _ := time.Parse("02/01/06", "19/01/24")
fpp := manifestfingerprintparams.MockFingerprintParamer{
ManifestSerialValue: "00000000000000000",
Time: fingerprintTime,
}
manifestfingerprintparams.SetFPParams(&fpp)
manifest := common.UpdateManifest{
ID: 1,
Name: "test",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00.E",
Description: "description",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
RollbackEnabled: true,
Type: "standard",
ECUs: []*common.UpdateManifestECU{
{
ECU: "ICC",
Version: "version",
HWVersions: []string{"hardware_version"},
Mode: "D",
SelfDownload: true,
Files: []*common.UpdateManifestFile{
{
FileID: "fileid",
URL: "http://download.com",
Filename: "filename.bin",
FileSize: 10000,
FileType: common.Software,
WriteRegionID: 2222,
WriteRegion: common.MemoryRegion{
ID: 2000,
Offset: 10000,
Length: 20,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ECU: "ADAS",
Version: "version",
HWVersions: []string{"hardware_version"},
Mode: "A",
InstallPriority: 10,
Files: []*common.UpdateManifestFile{
{
FileID: "fileid",
URL: "http://download.com",
Filename: "adas.bin",
FileSize: 9999,
FileType: common.Software,
WriteRegionID: 9999,
WriteRegion: common.MemoryRegion{
ID: 8888,
Offset: 8888,
Length: 8888,
},
DBModelBase: th.Timestamp,
},
},
ECCKeys: &common.ECCKeys{
ECU: "ADAS",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
DBModelBase: th.Timestamp,
},
{
ECU: "ECUA",
Version: "version",
HWVersions: []string{"hardware_version"},
Mode: "A",
InstallPriority: 5,
Files: []*common.UpdateManifestFile{
{
FileID: "fileid",
URL: "http://download.com",
Filename: "adas.bin",
FileSize: 9999,
FileType: common.Software,
WriteRegionID: 9999,
WriteRegion: common.MemoryRegion{
ID: 8888,
Offset: 8888,
Length: 8888,
},
DBModelBase: th.Timestamp,
},
{
FileID: "SHOULD_NOT_BE_IN_UPDATE",
URL: "http://download.com/SHOULD_NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: elptr.ElPtr(false),
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: elptr.ElPtr(true),
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
DBModelBase: th.Timestamp,
},
},
ECCKeys: &common.ECCKeys{
ECU: "ECUA",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
}
ecuaRollback := []*common.UpdateManifestECU{
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "VERSIONOLD",
HWVersions: []string{"hardware_version"},
Mode: "A",
DBModelBase: th.Timestamp,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEIDOLD",
UpdateManifestECUID: 1001,
Filename: "FILENAMEOLD",
URL: "URLOLD",
FileType: common.Software,
WriteRegionID: 700,
WriteRegion: common.MemoryRegion{
Offset: 701,
Length: 702,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
ECCKeys: &common.ECCKeys{
ECU: "ECUA",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
},
}
mockDB := &services.DB{}
mockCars := &mocks.MockCars{}
mockCarUpdates := &mocks.MockCarUpdates{
SelectCarUpdateResponse: &common.CarUpdate{
UpdateManifestID: 816,
UpdateManifest: &common.UpdateManifest{
ID: 816,
},
},
}
mockManifests := &mocks.MockUpdateManifests{
ECUUpdatesMock: func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return ecuaRollback, nil
}
return nil, nil
},
}
mockKeys := &mocks.MockEccKeys{
MockListResponse: []common.ECCKeys{
{
ECU: "PDU",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
{
ECU: "TBOX",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
},
},
}
mockDB.SetCars(mockCars)
mockDB.SetCarUpdates(mockCarUpdates)
mockDB.SetECCKeys(mockKeys)
mockDB.SetManifests(mockManifests)
mockFoa := FoaServiceMock{}
services.SetFoaService(&mockFoa)
mockRedis := tester.NewRedisMock()
mockKeepAwake := services.NewKeepAwakeService()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
},
}, nil
}}
mockConf := vconfig.ConfigMock{GetVODCDSCodingDataMock: func(request common.VODCDSRequest) (map[string]string, error) {
return map[string]string{
"ECUA": "config",
"VOD": "00a62299027600000101012200010100010001010101000000000000000000fffeffff000101010101010101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101010100010001010101010101010201010000000000000100000101ff00000001010200000000000003ffffffff0000000201010200000100000000000000000000000000000000000000000000000000000000000000000001202310010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, nil
}}
services.SetSapService(mockSap)
services.SetVehicleConfig(mockConf)
schemaTesterHMI := th.NewSchemaTestHelper(t, schemaToHMI)
schemaTesterTRex := th.NewSchemaTestHelper(t, schemaToTRex)
tests := []AttendentRouteTestCase{
{
Name: "[HMI] install_error",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"install_error","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"install_failed","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[HMI] download_completed",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ICC","file_current":null,"file_total":null,"package_current":920639485,"package_total":920639485,"installed":null,"total_files":null,"msg":"download_completed","err":null}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":920639485,"ecu":"ICC","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"package_download_complete","total_files":0,"total_size":920639485}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
trexKey: `{"handler":"update_manifest","data":{"ecu_updates":[{"name":"ICC","version":"version","hw_version":"hardware_version","self_download":true},{"name":"ECUA","version":"version","hw_version":"hardware_version","configuration":"config","files":[{"file_id":"fileid","url":"http://download.com","file_size":9999,"type":"software","write_region":{"offset":8888,"length":8888}},{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}}],"rollback":[{"version":"VERSIONOLD","files":[{"file_id":"FILEIDOLD","url":"URLOLD","file_size":240,"type":"software","write_region":{"offset":701,"length":702}}]}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}},{"name":"ADAS","version":"version","hw_version":"hardware_version","files":[{"file_id":"fileid","url":"http://download.com","file_size":9999,"type":"software","write_region":{"offset":8888,"length":8888}}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}}],"fingerprint":"240119FISKER00000000000000000","car_update_id":297,"rollback":true,"type":"standard","vod":"01012299027600000101012200010100010001010101000000000000000000fffeffff000101010101010101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101010100010001010101010101010201010000000000000100000101ff00000001010200000000000003ffffffff00000002010102000001000000000000000000000000000000000000000000000000000000000000000000012023100100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021","update_duration":30}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"package_download_complete","err":0}}`,
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"package_download_complete","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"package_download_complete","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
MockLoadManifest: &manifest,
},
{
Name: "[HMI] manifest_succeeded",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ICC","file_current":null,"file_total":null,"package_current":920639485,"package_total":920639485,"installed":null,"total_files":null,"msg":"manifest_succeeded","err":null}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":920639485,"ecu":"ICC","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"manifest_succeeded","total_files":0,"total_size":920639485}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"manifest_succeeded","err":0}}`,
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"manifest_succeeded","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":920639485,"package_total":920639485,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ICC","msg":"manifest_succeeded","err":0}}`,
"1:JH4KA7680RC01": `{"handler":"read_ecu_versions","data":{"ecu_name":"*"}}`,
},
MockRedisGetSet: testGetSetResult,
},
SelectCarUpdate: &common.CarUpdate{
UpdateManifest: &validUpdateManifest,
},
},
{
Name: "[TREX] manifest_received",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"manifest_received","err":-6,"extra_info":""}`,
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_received","err":-6}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_received","err":-6}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_received","err":-6}}`,
},
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-6,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"manifest_received","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] manifest_accepted",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"manifest_accepted","err":-7,"extra_info":""}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-7,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"manifest_accepted","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_accepted","err":-7}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_accepted","err":-7}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"manifest_accepted","err":-7}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_started",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"download_started","err":-14,"extra_info":""}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-14,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"downloading","err":-14}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"downloading","err":-14}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"downloading","err":-14}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] downloading",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ADAS","file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"msg":"downloading","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":1048576,"ecu":"ADAS","errorcode":0,"file_size":1048576,"file_total":1264672,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1048576,"file_total":1264672,"package_current":1048576,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_completed ECU 1",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"ADAS","file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"msg":"download_completed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":1264672,"ecu":"ADAS","errorcode":0,"file_size":1264672,"file_total":1264672,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1264672,"file_total":1264672,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"ADAS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_started ECU 2",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"EKS","file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"msg":"download_started","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":1264672,"ecu":"EKS","errorcode":0,"file_size":0,"file_total":1265184,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":1265184,"package_current":1264672,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] downloading ECU 2",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"EKS","file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"msg":"downloading","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":2313248,"ecu":"EKS","errorcode":0,"file_size":1048576,"file_total":1265184,"id":297,"installed":0,"status":"downloading","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1048576,"file_total":1265184,"package_current":2313248,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"downloading","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_completed ECU 2",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"EKS","file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"msg":"download_completed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":2529856,"ecu":"EKS","errorcode":0,"file_size":1265184,"file_total":1265184,"id":297,"installed":0,"status":"package_download_complete","total_files":0,"total_size":2529856}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"package_download_complete","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"package_download_complete","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":1265184,"file_total":1265184,"package_current":2529856,"package_total":2529856,"installed":0,"total_files":0,"car_update_id":297,"ecu":"EKS","msg":"package_download_complete","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] package_download_complete",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"download_completed","err":-15,"extra_info":""}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":-15,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"package_download_complete","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"package_download_complete","err":-15}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"package_download_complete","err":-15}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"package_download_complete","err":-15}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] download_failed",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","file_current":0,"file_total":100,"package_current":0,"package_total":1000,"msg":"download_failed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":100,"id":297,"installed":0,"status":"download_failed","total_files":0,"total_size":1000}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":100,"package_current":0,"package_total":1000,"installed":0,"total_files":0,"car_update_id":297,"ecu":"TEST","msg":"download_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":100,"package_current":0,"package_total":1000,"installed":0,"total_files":0,"car_update_id":297,"ecu":"TEST","msg":"download_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":100,"package_current":0,"package_total":1000,"installed":0,"total_files":0,"car_update_id":297,"ecu":"TEST","msg":"download_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_started",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":0,"total_files":10,"msg":"install_started","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"installing","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] installing",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"installing","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"installing","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"installing","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_succeeded ECU",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":10,"total_files":10,"msg":"install_succeeded"}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":10,"status":"package_install_complete","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"package_install_complete","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"package_install_complete","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"package_install_complete","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] package_install_complete",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"msg":"install_succeeded","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":0,"status":"installing","total_files":0,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"installing","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"installing","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":297,"ecu":"","msg":"installing","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_failed",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"install_failed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"install_failed","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] requirements_failed",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":5,"total_files":10,"msg":"requirements_failed","err":0}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":5,"status":"requirements_failed","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"requirements_failed","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"requirements_failed","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":5,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"requirements_failed","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] install_scheduled ECU",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":10,"total_files":10,"msg":"install_scheduled"}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":10,"status":"install_scheduled","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_scheduled","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_scheduled","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"install_scheduled","err":0}}`,
},
MockRedisGetSet: testGetSetResult,
},
},
{
Name: "[TREX] manifest_succeeded",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":297,"ecu":"TEST","installed":10,"total_files":10,"msg":"manifest_succeeded"}`,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
carupdateKey: {
Value: `{"current_size":0,"ecu":"TEST","errorcode":0,"file_size":0,"file_total":0,"id":297,"installed":10,"status":"manifest_succeeded","total_files":10,"total_size":0}`,
Expires: expectedExpire,
},
},
ExpectedMessages: map[string]string{
mobile1Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"manifest_succeeded","err":0}}`,
mobile2Key: `{"handler":"car_update_status","data":{"vin":"JH4KA7680RC01","file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"manifest_succeeded","err":0}}`,
hmiKey: `{"handler":"car_update_status","data":{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":10,"total_files":10,"car_update_id":297,"ecu":"TEST","msg":"manifest_succeeded","err":0}}`,
"1:JH4KA7680RC01": `{"handler":"read_ecu_versions","data":{"ecu_name":"*"}}`,
},
MockRedisGetSet: testGetSetResult,
},
SelectCarUpdate: &common.CarUpdate{
UpdateManifest: &validUpdateManifest,
},
},
}
for i := range tests {
mockRedis.Reset()
test := &tests[i]
test.SetupRedis(mockRedis)
test.SetupDB(mockCars, mockCarUpdates, test)
redisPool := tester.NewMockClientPool(mockRedis)
handler := controllers.NewCarUpdateProgress(redisPool, mockKeepAwake, mockDB, test.Device)
if handler == nil {
t.Error(errors.New("NewCarUpdateProgress cannot handle device %v"))
continue
}
err := handler.Process(test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
for key, m := range test.RedisTestCase.ExpectedMessages {
name := fmt.Sprintf("%s %s", test.Name, key)
if strings.Contains(key, "1:") {
schemaTesterTRex.ValidateSchemaObject(name, []byte(m))
} else if strings.Contains(key, "2:") {
schemaTesterHMI.ValidateSchemaObject(name, []byte(m))
}
}
}
}
type FoaServiceMock struct{}
func (f *FoaServiceMock) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) {
return &http.Response{StatusCode: 200}, nil
}

View File

@@ -0,0 +1,74 @@
package handlers
import (
"encoding/json"
"errors"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
fv "github.com/fiskerinc/cloud-services/pkg/flashpackversion"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/validator"
)
func UpdateCarState(db *services.DB, vin string, data []byte) error {
logger.Debug().Msgf("UpdateCarState %s", vin)
var update common.CarStateUpdate
err := json.Unmarshal(data, &update)
if err != nil {
return err
}
err = validator.ValidateStruct(&update)
if err != nil {
logger.Err(err).Interface("CarStateUpdate", update).Str("CarStateUpdateByteValue as string", string(data)).Send()
return err
}
return processUpdateCarStateECUs(db, vin, update.ECUs)
}
func processUpdateCarStateECUs(db *services.DB, vin string, ecus map[string]common.CarECU) error {
cache := services.GetCarEcuCache()
insert := []common.CarECU{}
errs := []error{}
for name, ecu := range ecus {
ecu.VIN = vin
ecu.ECU = name
err := validator.ValidateStruct(&ecu)
if err != nil {
logger.Error().Msgf("invalid CarECU %v", err)
errs = append(errs, err)
continue
}
if !cache.Exists(ecu.CacheKey(), ecu.HashValues()) {
insert = append(insert, ecu)
}
}
if len(insert) == 0 {
return combineErrors(errs)
}
err := fv.InsertCarECUsAndUpdateFlashpackVersion(db.GetCars(), db.GetCarVersionsLog(), vin, insert)
if err != nil {
errs = append(errs, err)
}
return combineErrors(errs)
}
func combineErrors(errs []error) error {
if len(errs) == 0 {
return nil
}
errString := errs[0].Error()
for i := 1; i < len(errs); i++ {
errString += " " + errs[i].Error()
}
return errors.New(errString)
}

View File

@@ -0,0 +1,113 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
)
func TestUpdateCarState(t *testing.T) {
setupDBMock()
setupRedisMock()
mockSap := vconfig.SAPServiceMock{}
services.SetSapService(mockSap)
type testCase struct {
Name string
VIN string
Payload string
ExpectedErr string
}
tests := []testCase{
{
Name: "Empty ECU",
VIN: "JH4KA7680RC011845",
Payload: `{"ecus": {}}`,
ExpectedErr: "Key: 'CarStateUpdate.ECUs' Error:Field validation for 'ECUs' failed on the 'min' tag",
},
{
Name: "Bad VIN and ECU",
VIN: "JH4KA7680RC01",
Payload: `{
"ecus": {
"ECU1": {
"serial_number": "AAAAA",
"hw_version": "2000",
"boot_loader_version": "3000",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
}
}
}`,
ExpectedErr: "Key: 'CarECU.VIN' Error:Field validation for 'VIN' failed on the 'vin' tag",
},
{
Name: "Good ECUs with/without software version",
VIN: "JH4KA7680RC011845",
Payload: `{
"ecus": {
"ECU1": {
"sw_version": "1000",
"serial_number": "AAAAA",
"hw_version": "2000",
"boot_loader_version": "3000",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
},
"ECU2": {
"serial_number": "BBBBBBB",
"hw_version": "2001",
"boot_loader_version": "3001",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
}
}
}`,
ExpectedErr: "",
},
{
Name: "bad ECU followed by a Good ECU",
VIN: "JH4KA7680RC011845",
Payload: `{
"ecus": {
"ECU1": {
"serial_number": "AAAAA",
"hw_version": "2000",
"boot_loader_version": "3000",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029",
"epoch_usec": "ABC"
},
"ECU2": {
"serial_number": "BBBBBBB",
"hw_version": "2001",
"boot_loader_version": "3001",
"fingerprint": "0xffffffffffffffffff",
"config": "0x024000941f9fffbfffe2dd9bff5860000007dfff091fff7f",
"vendor": "A021E00029"
}
}
}`,
ExpectedErr: "json: cannot unmarshal string into Go struct field CarECU.ecus.epoch_usec of type int64",
},
}
for _, test := range tests {
err := handlers.UpdateCarState(mockDB, test.VIN, []byte(test.Payload))
if err != nil && test.ExpectedErr != err.Error() {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedErr, err.Error())
} else if err == nil && test.ExpectedErr != "" {
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedErr, err)
}
}
}

View File

@@ -0,0 +1,94 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/services"
"encoding/json"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
func GetAllEccKeys(db *services.DB, id string, data []byte) error {
logger.Debug().Msgf("GetAllEccKeys %v %s", common.TRex, id)
var err error
var eccKeys []common.ECCKeys
req, err := parseGetAllEccKeysRequest(data)
if err != nil {
return err
}
if req.CarUpdateID == 0 {
eccKeys, err = db.GetECCKeys().SelectAllPrivateKeysByVIN(id)
} else {
eccKeys, err = db.GetECCKeys().SelectAllPrivateKeysByCarUpdateID(req.CarUpdateID)
}
if err != nil {
return err
}
if eccKeys == nil {
eccKeys = make([]common.ECCKeys, 0)
}
eccKeys = replaceECU(eccKeys)
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err = client.SafePublishMessage(common.TRex.Key(id), common.Message{
Handler: "ecc_keys",
Data: eccKeys,
})
if err != nil {
return err
}
logger.Debug().Msgf("GetAllEccKeys sent %v %s", common.TRex, id)
return nil
}
func parseGetAllEccKeysRequest(data []byte) (common.CarUpdateRequest, error) {
var req common.CarUpdateRequest
if len(data) == 0 {
return req, nil
}
err := json.Unmarshal(data, &req)
if err != nil {
return req, errors.WithStack(err)
}
return req, nil
}
func replaceECU(eccKeys []common.ECCKeys) []common.ECCKeys {
ecuReplacements := common.ECUReplacement()
for i := 0; i < len(eccKeys); i++ {
ecu := eccKeys[i].ECU
if replacement, exist := ecuReplacements[ecu]; exist {
eccKeys[i].ECU = replacement
}
}
return eccKeys
}
/*
func notifyEccKeysGeneralError(client redis.Client, device common.Device, id string, err error) {
e := client.PublishMessage(device.Key(id), common.Message{
Handler: "ecc_keys",
Data: []struct{ Error string }{
{
Error: err.Error(),
},
},
})
if e != nil {
logger.Error().Err(errors.WithStack(e)).Send()
}
}
*/

View File

@@ -0,0 +1,111 @@
package handlers_test
import (
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"testing"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/pkg/errors"
)
func TestGetAllEccKeys(t *testing.T) {
testVIN := "JH4KA7680RC01"
trexKey := "1:JH4KA7680RC01"
pk11 := common.NewBinaryHex([]byte("testprivkey11"))
pk12 := common.NewBinaryHex([]byte("testprivkey12"))
pk13 := common.NewBinaryHex([]byte("testprivkey13"))
pk21 := common.NewBinaryHex([]byte("testprivkey21"))
pk22 := common.NewBinaryHex([]byte("testprivkey22"))
pk23 := common.NewBinaryHex([]byte("testprivkey23"))
testDBQuery := []common.ECCKeys{
{
ECU: "testecu1",
PrivKey1: &pk11,
PrivKey2: &pk12,
PrivKey3: &pk13,
},
{
ECU: "testecu2",
PrivKey1: &pk21,
PrivKey2: &pk22,
PrivKey3: &pk23,
},
{
ECU: "PDU",
PrivKey1: &pk21,
PrivKey2: &pk22,
PrivKey3: &pk23,
},
}
mockRedis := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockEccKeys := &mocks.MockEccKeys{}
mockDB := &services.DB{}
mockDB.SetECCKeys(mockEccKeys)
tests := []AttendentRouteTestCase{
{
Name: "[TREX] From DB, no car update id",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
ExpectedMessages: map[string]string{
trexKey: `{"handler":"ecc_keys","data":[{"ecu":"testecu1","level_1":"74657374707269766b65793131","level_2":"74657374707269766b65793132","level_3":"74657374707269766b65793133"},{"ecu":"testecu2","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"},{"ecu":"OBC","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"}]}`,
},
},
MockEccKeysSelect: testDBQuery,
},
{
Name: "[TREX] From DB, with car update id",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
ExpectedMessages: map[string]string{
trexKey: `{"handler":"ecc_keys","data":[{"ecu":"testecu1","level_1":"74657374707269766b65793131","level_2":"74657374707269766b65793132","level_3":"74657374707269766b65793133"},{"ecu":"testecu2","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"},{"ecu":"OBC","level_1":"74657374707269766b65793231","level_2":"74657374707269766b65793232","level_3":"74657374707269766b65793233"}]}`,
},
PayloadData: `{"car_update_id":10000}`,
},
MockEccKeysSelect: testDBQuery,
},
{
Name: "Error",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
ExpectedError: "something went wrong",
},
MockEccKeysSelect: nil,
},
}
schemaTester := testhelper.NewSchemaTestHelper(t, schemaToTRex)
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
mockRedis.Reset()
test.SetupRedis(mockRedis)
mockEccKeys.MockListResponse = test.MockEccKeysSelect
if test.Name == "Error" {
mockEccKeys.Error = errors.New("something went wrong")
} else {
mockEccKeys.Error = nil
}
err := handlers.GetAllEccKeys(mockDB, test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
for _, m := range test.ExpectedMessages {
schemaTester.ValidateSchemaObject(test.Name, []byte(m))
}
})
}
}

View File

@@ -0,0 +1,80 @@
package handlers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
func GetFileKeys(db *services.DB, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("GetFileKeys %v %s", device, id)
var err error
var req *common.FileKeysRequest
client := services.RedisClientPool().GetFromPool()
defer client.Close()
req, err = parseGetFileKeysRequest(data)
if err != nil {
notifyFileKeysGeneralError(client, device, id, err)
return err
}
keys, err := cache.RetrieveFileEncryptionParams(client, db.GetFileKeys(), req.FileIDs)
if err != nil {
notifyFileKeysGeneralError(client, device, id, err)
return err
}
err = client.SafeQueueMessage(device.Key(id), common.Message{
Handler: "filekeys",
Data: keys,
})
if err != nil {
return err
}
logger.Debug().Msgf("GetFileKeys sent %v %s", device, id)
return nil
}
func parseGetFileKeysRequest(data []byte) (*common.FileKeysRequest, error) {
var status common.FileKeysRequest
err := json.Unmarshal(data, &status)
if err != nil {
return nil, errors.WithStack(err)
}
err = validator.ValidateStruct(status)
if err != nil {
return &status, errors.WithStack(err)
}
return &status, nil
}
func notifyFileKeysGeneralError(client redis.Client, device common.Device, id string, err error) {
e := client.SafePublishMessage(device.Key(id), common.Message{
Handler: "filekeys",
Data: []common.FileKeyResponse{
{
FileID: "0",
Error: err.Error(),
},
},
})
if e != nil {
logger.Error().Err(errors.WithStack(e)).Send()
}
}

View File

@@ -0,0 +1,124 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
)
func TestGetFileKey(t *testing.T) {
testVIN := "JH4KA7680RC01"
trexKey := "1:JH4KA7680RC01"
hmiKey := "2:JH4KA7680RC01"
fileCache1 := "fileid:b7d94be8c94062cf"
fileCache2 := "fileid:83165a80c940e8b3"
testPayload := `{"file_ids": ["b7d94be8c94062cf","83165a80c940e8b3"]}`
testDBQuery := []common.FileKey{
{
FileID: "b7d94be8c94062cf",
Auth: []byte("AuthValue"),
Key: []byte("KeyValue"),
Nonce: []byte("NonceValue"),
},
{
FileID: "83165a80c940e8b3",
Auth: []byte("AuthValue2"),
Key: []byte("KeyValue2"),
Nonce: []byte("NonceValue2"),
},
}
mockRedis := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockFileKeys := &mocks.MockFileKeys{}
mockDB := &services.DB{}
mockDB.SetFileKeys(mockFileKeys)
tests := []AttendentRouteTestCase{
{
Name: "[TREX] From DB",
RedisTestCase: tester.RedisTestCase{
Device: common.TRex,
DeviceKey: testVIN,
PayloadData: testPayload,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
fileCache1: {
Value: `{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="}`,
Expires: 86400,
},
fileCache2: {
Value: `{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}`,
Expires: 86400,
},
},
ExpectedMessages: map[string]string{
trexKey: `{"handler":"filekeys","data":[{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="},{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}]}`,
},
},
MockFileKeysSelect: testDBQuery,
MockFileKeysError: nil,
},
{
Name: "[HMI] From DB",
RedisTestCase: tester.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: testPayload,
ExpectedCaches: map[string]tester.ExpiringCacheResult{
fileCache1: {
Value: `{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="}`,
Expires: 86400,
},
fileCache2: {
Value: `{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}`,
Expires: 86400,
},
},
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"filekeys","data":[{"file_id":"b7d94be8c94062cf","key":"S2V5VmFsdWU=","auth":"QXV0aFZhbHVl","nonce":"Tm9uY2VWYWx1ZQ=="},{"file_id":"83165a80c940e8b3","key":"S2V5VmFsdWUy","auth":"QXV0aFZhbHVlMg==","nonce":"Tm9uY2VWYWx1ZTI="}]}`,
},
},
MockFileKeysSelect: testDBQuery,
MockFileKeysError: nil,
},
}
schemaTester := testhelper.NewSchemaTestHelper(t, schemaToTRex)
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
mockRedis.Reset()
test.SetupRedis(mockRedis)
mockFileKeys.GetMultiResponse = test.MockFileKeysSelect
mockFileKeys.Error = test.MockFileKeysError
err := handlers.GetFileKeys(mockDB, test.Device, test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
for _, m := range test.ExpectedMessages {
schemaTester.ValidateSchemaObject(test.Name, []byte(m))
}
})
}
}
func BenchmarkGetFileKey(b *testing.B) {
db := services.GetDB()
vin := "1F15K3R45N1234567"
id := []byte(`{"file_ids": ["b7d94be8c94062cf","83165a80c940e8b3"]}`)
for n := 0; n < b.N; n++ {
err := handlers.GetFileKeys(db, common.TRex, vin, id)
if err != nil {
b.Error(err)
}
}
}

View File

@@ -0,0 +1,113 @@
package handlers_test
import (
"encoding/json"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/go-pg/pg/v10/orm"
)
var mockRedis *redis.Connection
var mockDB *services.DB
var testDateTime time.Time = time.Date(2022, 1, 2, 3, 4, 5, 6, time.UTC)
func setupRedisMock() {
redis.MockRedisConnection()
mockRedis = &redis.Connection{}
}
func setupDBMock() {
db := services.DB{}
db.SetCarUpdates(&mocks.MockCarUpdates{
SelectCarUpdateResponse: &common.CarUpdate{
ID: 1,
VIN: "FISKER123",
UpdateManifestID: 2,
UpdateManifest: &common.UpdateManifest{
ID: 2,
Name: "TEST_PACKAGE",
Version: "1.0.0",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
},
},
})
db.SetECU(&mocks.MockEcuDtc{})
db.SetCars(&mocks.MockCars{
SelectResponse: &common.Car{},
})
carVersionLogMock := mocks.MockCarVersionsLog{MockLogVersionChange: func(log *common.CarVersionLogs) (orm.Result, error) {
return nil, nil
}}
db.SetCarVersionsLog(&carVersionLogMock)
mockDB = &db
}
type mockRedisCache struct {
redis.Connection
}
func (c *mockRedisCache) GetSet(id string, data interface{}) error {
driverIDs := []string{"valid-cognito-id-1", "valid-cognito-id-2"}
dataBytes, err := json.Marshal(driverIDs)
if err != nil {
return err
}
err = json.Unmarshal(dataBytes, data)
if err != nil {
return err
}
return nil
}
func NewRedisMock() *tester.MockRedis {
redis.MockRedisConnection()
return &tester.MockRedis{}
}
type AttendentRouteTestCase struct {
Name string
SelectCarUpdate *common.CarUpdate
SelectCarUpdates []common.CarUpdate
CarUpdateError error
SelectCarToDrivers []common.CarToDriver
CarToDriversError error
MockLoadManifest *common.UpdateManifest
MockFileKeysSelect []common.FileKey
MockFileKeysError error
MockEccKeysSelect []common.ECCKeys
tester.RedisTestCase
}
func (at *AttendentRouteTestCase) SetupDB(mockCars *mocks.MockCars, mockCarUpdates *mocks.MockCarUpdates, test *AttendentRouteTestCase) {
if test == nil {
return
}
if mockCars != nil {
mockCars.SelectCarsForDrivers = test.SelectCarToDrivers
mockCars.Error = test.CarToDriversError
}
if mockCarUpdates != nil {
mockCarUpdates.SelectCarUpdateResponse = test.SelectCarUpdate
mockCarUpdates.SelectCarUpdatesResponse = test.SelectCarUpdates
mockCarUpdates.LoadManifest = test.MockLoadManifest
mockCarUpdates.Error = test.CarUpdateError
}
}

View File

@@ -0,0 +1,160 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/manifestsender"
"github.com/fiskerinc/cloud-services/pkg/tmobile"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
var ENABLE_ORDERUPDATE_SENDS = envtool.GetEnvBool("ENABLE_ORDERUPDATE_SENDS", false)
// Take the feature codes from VehicleOrder message to generate the VOD and CDSs
// Create a new common.UpdateManifest with the VOD and CDS and save it as a common.ConfigUpdateType
// Create a new common.CarUpdate for the VIN and the manifest in step 2 and save it to the database
// Copy the car update id into the UpdateManifest
// Send the UpdateManifest to both trex and hmi
func OrderUpdated(db *services.DB, smsClient sms.SMSServiceClient, vin string, data []byte) error {
logger.Debug().Msgf("Order updated %s", vin)
var order common.VehicleOrder
err := json.Unmarshal(data, &order)
if err != nil {
return err
}
err = setCarSettings(db, vin, order)
if err != nil {
return err
}
err = changeRatePlan(db, smsClient, vin, order.VehicleSpecification.DestinationCountry)
if err != nil {
logger.Error().Msgf("Failed to change rate plan for %s, %s", vin, err)
}
if !ENABLE_ORDERUPDATE_SENDS {
return nil
}
cs := services.GetVehicleConfig()
r := services.RedisClientPool().GetFromPool()
defer r.Close()
trex := manifestsender.NewTBOXManifestSender(r, cs, services.GetDB(), nil, nil)
defer trex.Close()
// I don't think this has ever been called on production, or dev !
input := manifestsender.ProcessConfigUpdateStruct{
VIN: vin,
Name: "SAP order change update",
Username: "unidentified attendant sap user",
SendToCar: true,
DontCreateDatabaseEntry: false,
Forced: false,
}
_, err = trex.ProcessConfigUpdate(input, services.GetDB().GetCarConfigData())
return err
}
func setCarSettings(db *services.DB, vin string, order common.VehicleOrder) (err error) {
// If the sequence number is missing, go will fill it in as 0, which is the case where the car has no sequence number
if order.VehicleSpecification.SequenceNumber != "" {
_, err = db.GetCars().SetSetting(&common.CarSetting{
VIN: vin,
Name: common.SEQUENCE_NUMBER,
Value: fmt.Sprint(order.VehicleSpecification.SequenceNumber),
Type: "string",
})
if err != nil {
return err
}
}
_, err = db.GetCars().SetSetting(&common.CarSetting{
VIN: vin,
Name: common.BODY_COLOR,
Value: common.FeatureCodeToBodyColor(order.VehicleSpecification.VehicleFeatures),
Type: "string",
})
if err != nil {
return err
}
// Incase SAP hasn't sent this value yet
if order.VehicleSpecification.DestinationCountry != "" {
_, err = db.GetCars().SetSetting(&common.CarSetting{
VIN: vin,
Name: common.DELIVERY_DESTINATION,
Value: order.VehicleSpecification.DestinationCountry,
Type: "string",
})
if err != nil {
return err
}
}
return
}
func changeRatePlan(db *services.DB, smsClient sms.SMSServiceClient, vin, destinationCountry string) error {
car, err := services.GetDB().GetCars().SelectByVIN(vin)
if err != nil {
return err
}
if len(car.ICCID) == 0 {
return fmt.Errorf("no iccid found for vehicle %s", vin)
}
iccid := strings.TrimSuffix(strings.ToLower(car.ICCID), "f")
customAtributeReq := sms.CustomAtributesRequest{
ICCID: iccid,
AccountCustom1: destinationCountry,
}
_, err = smsClient.HandleCustomAttributes(context.Background(), &customAtributeReq)
if err != nil {
return err
}
ratePlan, err := db.GetRatePlan().Select(destinationCountry)
if err != nil {
return err
}
changeRatePlanRequest := sms.ChangeRatePlanRequest{
ICCID: iccid,
ProductId: ratePlan.ProductID,
AccountId: tmobile.FISKER_TMOBILE_ACCOUNT_ID,
}
_, err = smsClient.HandleChangeRatePlan(context.Background(), &changeRatePlanRequest)
if err != nil {
return err
}
go verifyRatePlan(smsClient, iccid, destinationCountry)
return nil
}
func verifyRatePlan(smsClient sms.SMSServiceClient, iccid, destinationCountry string) error {
deviceDetailsRequest := sms.DeviceDetailsRequest{
ICCID: iccid,
}
details, err := smsClient.HandleDeviceDetails(context.Background(), &deviceDetailsRequest)
if err != nil {
logger.Error().Msgf("failed to check device details for iccid %s", iccid)
}
logger.Info().Msgf("device details for iccid %s: %+v", iccid, details)
return nil
}

View File

@@ -0,0 +1,224 @@
package handlers_test
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/pkg/errors"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
q "github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
"github.com/stretchr/testify/assert"
)
var (
someErr = errors.New("some error")
vinMock = "FISKER123"
vodMock = "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
confMock = "000000000000000000000000000000000000000"
validOrder = common.VehicleOrder{
SpecID: 800010200,
OrderNumber: 8000102,
MessageIdentifier: "VEHICLEORDERSUBMISSION",
VehicleSpecification: common.VehicleSpecification{
OrderIndicator: "S",
FleetOrderIndicator: "N",
ProductionPhaseIndicator: "01",
VehicleIndicator: "000",
ManufacturingPlant: "G",
ExpectedReferenceDate: common.ExpectedReferenceDate{
Time: time.Date(2022, 5, 26, 0, 0, 0, 0, time.UTC),
},
ModelType: "FM29",
ModelYearIndicator: 2023,
VehicleModel: "F29",
VinPrefix: "VCF1ZBU2_PG",
VehicleFeatures: []common.FeatureCodes{
{
FamilyCode: "2801",
FeatureCode: "280102",
},
{
FamilyCode: "2804",
FeatureCode: "280401",
},
{
FamilyCode: "2805",
FeatureCode: "280501",
},
},
},
}
validUpdateManifest = common.UpdateManifest{
ID: 1,
CarUpdateID: 1,
Version: fmt.Sprint(validOrder.OrderNumber),
Description: fmt.Sprintf("configuration %s %s", vinMock, validOrder.MessageIdentifier),
ManifestType: common.ConfigUpdateType,
VOD: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
}
validRespECUs = []*common.UpdateManifestECU{
{
UpdateManifestID: 1,
ECU: "ICC",
Configuration: confMock,
},
}
smsMock = sms.NewSMSMockSuccess()
mockCars = mocks.MockCars{}
)
func TestOrderUpdated(t *testing.T) {
handlers.ENABLE_ORDERUPDATE_SENDS = true
validUpdateManifestResp := validUpdateManifest
validUpdateManifestResp.ID = 1
validUpdateManifestResp.ECUs = validRespECUs
scrubbedValidUpdateManifest := validUpdateManifestResp.ToUpdateConfigManifest()
scrubbedValidUpdateManifest.Type = "standard"
successPayload, _ := json.Marshal(validOrder)
redisMock := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(redisMock))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
{
FamilyCode: "VOD",
FeatureCode: vodMock,
},
}}, nil
}}
services.SetSapService(mockSap)
mockCars.SetLoadResp(&common.Car{
VIN: vinMock,
ICCID: "1234567890",
SoldStatus: common.CarSoldStatusRetailed,
})
tests := map[string]struct {
updateManifest q.UpdateManifestsInterface
cars q.CarsInterface
carsUpdate q.CarUpdatesInterface
ratePlan q.RatePlanInterface
vConfig vconfig.ConfigServiceInterface
payload []byte
expRedisMsgs map[string]interface{}
expErr error
}{
"success": {
updateManifest: &mocks.MockUpdateManifests{SelectResponse: []common.UpdateManifest{{ID: 1234}}},
cars: &mockCars,
carsUpdate: &mocks.MockCarUpdates{},
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: SuccessGetCDSMock},
payload: successPayload,
expRedisMsgs: map[string]interface{}{
"2:" + vinMock: common.Message{
Handler: "car_update",
Data: common.CarUpdate{
ID: 1,
VIN: vinMock,
UpdateManifestID: 1,
UpdateManifest: &validUpdateManifestResp,
},
},
"1:" + vinMock: common.Message{
Handler: "config_update",
Data: scrubbedValidUpdateManifest,
},
},
expErr: nil,
},
"json_parse_err": {
payload: []byte(`12`),
expErr: errors.New("json: cannot unmarshal number into Go value of type common.VehicleOrder"),
},
"failed_cars_db": {
updateManifest: &mocks.MockUpdateManifests{SelectResponse: []common.UpdateManifest{{ID: 1234}}},
cars: &mocks.MockCars{
DBMockHelper: mocks.DBMockHelper{
Error: someErr,
},
},
carsUpdate: &mocks.MockCarUpdates{},
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: SuccessGetCDSMock},
payload: successPayload,
expErr: someErr,
},
"failed_cds": {
updateManifest: &mocks.MockUpdateManifests{SelectResponse: []common.UpdateManifest{{ID: 1234}}},
carsUpdate: &mocks.MockCarUpdates{},
cars: &mockCars,
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: FailedGetCDSMock},
payload: successPayload,
expErr: someErr,
},
"failed_db": {
updateManifest: &mocks.MockUpdateManifests{
DBMockHelper: mocks.DBMockHelper{
Error: someErr,
},
},
cars: &mockCars,
carsUpdate: &mocks.MockCarUpdates{},
ratePlan: &mocks.MockRatePlan{},
vConfig: vconfig.ConfigMock{GetVODCDSCodingDataMock: SuccessGetCDSMock},
payload: successPayload,
expErr: someErr,
},
}
for tname, tt := range tests {
t.Run(tname, func(t *testing.T) {
redisMock.Reset()
db := services.GetDB()
db.SetManifests(tt.updateManifest)
db.SetCars(tt.cars)
db.SetCarUpdates(tt.carsUpdate)
db.SetRatePlan(tt.ratePlan)
services.SetVehicleConfig(tt.vConfig)
err := handlers.OrderUpdated(db, &smsMock, vinMock, tt.payload)
if err != nil && tt.expErr != nil {
assert.Equal(t, tt.expErr.Error(), err.Error())
return
}
assert.Equal(t, tt.expErr, err)
// assert.Equal(t, tt.expRedisMsgs, redisMock.PublishedMessages)
})
}
}
func SuccessGetCDSMock(request common.VODCDSRequest) (map[string]string, error) {
ecus := map[string]string{
"VOD": vodMock,
"ICC": confMock,
}
return ecus, nil
}
func FailedGetCDSMock(request common.VODCDSRequest) (map[string]string, error) {
return nil, someErr
}

View File

@@ -0,0 +1,24 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
var PrivateSeed int64 // Used only for testing. Do not change otherwise
func SendManifest(d *services.DB, ka *services.KeepAwake, device common.Device, id string, data []byte) error {
logger.Debug().Msgf("SendManifest %v %s", device, id)
clientPool := services.RedisClientPool()
configService := services.GetVehicleConfig()
generator := controllers.NewManifestSender(clientPool, d, configService, device, ka, PrivateSeed)
defer generator.Release()
return generator.Process(id, data)
}

View File

@@ -0,0 +1,25 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
// If the update is not finished installing on the car, we send out an sms to wake the car up.
// If this message is failed to deliver, we need to cancel the car update and set its status as failed
func CarSendManifestSMSWakeUpCallback(d *services.DB, ka *services.KeepAwake, device common.Device, id string, data []byte) (err error) {
logger.Info().Msgf("SMS delivered but not sending TBox manifest %v %s", device, id)
return nil
/*
clientPool := services.RedisClientPool()
sap := services.GetSapService()
configService := services.GetVehicleConfig()
generator := controllers.NewManifestSender(clientPool, d, sap, configService, device, ka, PrivateSeed)
defer generator.Release()
return generator.ContinueTBOXSend(id, data) */
}

View File

@@ -0,0 +1,962 @@
package handlers_test
import (
"fmt"
"strings"
"testing"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"os"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel"
"github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams"
dbm "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/kafka"
kafkaMock "github.com/fiskerinc/cloud-services/pkg/kafka/mock"
"github.com/fiskerinc/cloud-services/pkg/redis"
rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/pkg/testrunner"
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
"github.com/jinzhu/copier"
)
func TestSendManifest(t *testing.T) {
os.Setenv("APP_SERVICE_NAME", "ATTENDANT")
handlers.PrivateSeed = 123456789
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
now := time.Now()
testVIN := "JH4KA7680RC01"
tboxKey := common.TRex.Key(testVIN)
hmiKey := common.HMI.Key(testVIN)
smsKey := "manifest_tbox_send_cache:100"
var bhex common.BinaryHex = []byte("test")
redis.MockRedisConnection()
falsePtrValue := elptr.ElPtr(false)
truePtrValue := elptr.ElPtr(true)
ecuaRollback := []*common.UpdateManifestECU{
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "VERSIONOLD",
Mode: "A",
DBModelBase: th.Timestamp,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEIDOLD",
UpdateManifestECUID: 1001,
Filename: "FILENAMEOLD",
URL: "URLOLD",
FileType: common.Software,
WriteRegionID: 700,
WriteRegion: common.MemoryRegion{
Offset: 701,
Length: 702,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
},
}
ecus := []*common.UpdateManifestECU{
{
ECU: "ICC",
Version: "SWVERSION",
HWVersions: []string{"HWVERSION"},
Mode: "D",
SelfDownload: true,
InstallPriority: 13,
Files: []*common.UpdateManifestFile{
{
FileID: "AAAAAAA",
URL: "http://download.com/file1.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
DBModelBase: th.Timestamp,
},
{
FileID: "SHOULD_NOT_BE_IN_UPDATE",
URL: "http://download.com/SHOULD_NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: falsePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
},
},
},
{
ECU: "ECUD",
Version: "SWVERSION",
HWVersions: []string{"HWVERSION"},
Mode: "D",
InstallPriority: 4,
Files: []*common.UpdateManifestFile{
{
FileID: "SHOULD_NOT_BE_IN_UPDATE",
URL: "http://download.com/SHOULD_NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: falsePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "BBBBBBB",
URL: "http://download.com/file2.bin",
FileSize: 2000,
Checksum: "BBBBBBB",
FileType: common.Calibration,
WriteRegionID: 300,
WriteRegion: common.MemoryRegion{
Offset: 301,
Length: 302,
},
EraseRegionID: 400,
EraseRegion: &common.MemoryRegion{
Offset: 401,
Length: 402,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "PDU",
Version: "PDU-VERS",
HWVersions: []string{"PDU-VERS"},
Mode: "PDU",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 7,
Files: []*common.UpdateManifestFile{
{
FileID: "NOT_BE_IN_UPDATE",
URL: "http://download.com/NOT_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: falsePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "MUST_BE_IN_UPDATE",
URL: "http://download.com/MUST_BE_IN_UPDATE.bin",
FileSize: 1000,
Checksum: "AAAAAAA",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Calibration,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
},
{
FileID: "AAAAAAAA",
URL: "http://download.com/filea.bin",
FileSize: 2000,
Checksum: "AAAAAAAA",
WriteRegionID: 500,
FileType: common.Calibration,
WriteRegion: common.MemoryRegion{
Offset: 501,
Length: 502,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "A-VERS",
HWVersions: []string{"A-VERS"},
Mode: "A",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 1,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEID",
UpdateManifestECUID: 100,
Filename: "FILENAME",
URL: "URL",
FileType: common.Calibration,
WriteRegionID: 600,
WriteRegion: common.MemoryRegion{
Offset: 601,
Length: 602,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
}
fingerprint := getTodaysFingerprint()
standardManifest := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: common.ManifestTypeForced,
Active: truePtrValue,
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
RollbackEnabled: true,
ECUs: ecus,
UpdateDuration: 30,
}
rollbackDisabledManifest := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00.E",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: "standard",
Active: truePtrValue,
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
ECUs: ecus,
UpdateDuration: 30,
}
standardManifestNoICC := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00.E",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: "standard",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
RollbackEnabled: true,
BodyType: "truck",
ECUs: []*common.UpdateManifestECU{
{
ECU: "ECUD",
HWVersions: []string{"HWVERSION"},
Version: "SWVERSION",
Mode: "D",
InstallPriority: 7,
Files: []*common.UpdateManifestFile{
{
FileID: "BBBBBBB",
URL: "http://download.com/file2.bin",
FileSize: 2000,
Checksum: "BBBBBBB",
FileType: common.Calibration,
WriteRegionID: 700,
WriteRegion: common.MemoryRegion{
Offset: 701,
Length: 702,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "PDU",
Version: "PDU-VERS",
HWVersions: []string{"PDU-VERS"},
Mode: "PDU",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 10,
Files: []*common.UpdateManifestFile{
{
FileID: "AAAAAAAA",
URL: "http://download.com/filea.bin",
FileSize: 2000,
FileType: common.Calibration,
Checksum: "AAAAAAAA",
WriteRegionID: 800,
WriteRegion: common.MemoryRegion{
Offset: 801,
Length: 802,
},
EraseRegionID: 900,
EraseRegion: &common.MemoryRegion{
Offset: 901,
Length: 902,
},
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
{
ID: 100,
UpdateManifestID: 200,
ECU: "ECUA",
Version: "A-VERS",
HWVersions: []string{"A-VERS"},
Mode: "A",
ConfigurationMask: "AAAAAAAA",
InstallPriority: 4,
Files: []*common.UpdateManifestFile{
{
FileID: "FILEID",
UpdateManifestECUID: 100,
Filename: "FILENAME",
URL: "URL",
FileType: common.Calibration,
WriteRegionID: 600,
WriteRegion: common.MemoryRegion{
Offset: 601,
Length: 602,
},
FileSize: 240,
DBModelBase: th.Timestamp,
},
},
DBModelBase: th.Timestamp,
},
},
UpdateDuration: 30,
}
mockCarUpdate := &common.CarUpdate{
ID: 297,
VIN: testVIN,
UpdateManifestID: 100,
}
mockCarECUs := []common.CarECU{
{ECU: "ECUD", HWVersion: "HWVERSION"},
{ECU: "ECUA", HWVersion: "A-VERS", Version: "TEST122"},
{ECU: "OBC", HWVersion: "PDU-VERS", Version: "TEST121"},
{ECU: "BCM", HWVersion: ";#A;#"},
{ECU: "ICC", HWVersion: "HWVERSION", Version: "TEST123"},
}
mockRedis := rm.MockRedis{}
mockCUDB := dbm.MockCarUpdates{}
mockManDB := dbm.MockUpdateManifests{}
mockKeys := dbm.MockEccKeys{
MockListResponse: []common.ECCKeys{
{
ECU: "PDU",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
{
ECU: "ECUA",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
{
ECU: "ECUD",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
},
}
services.GetDB().SetCarUpdates(&mockCUDB)
services.GetDB().SetCars(&dbm.MockCars{SelectCarECUs: mockCarECUs, SelectResponse: &common.Car{ICCID: "8000000000000000000"}})
services.GetDB().SetManifests(&mockManDB)
services.GetDB().SetECCKeys(&mockKeys)
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
},
}, nil
}}
mockConf := vconfig.ConfigMock{GetVODCDSCodingDataMock: func(request common.VODCDSRequest) (map[string]string, error) {
return map[string]string{
"EPS": "000147303031313632011D",
"ESP": "000F47303031313632000001012301027600000101000002B0",
"ECUA": "config",
"VOD": "",
"ADAS": "000147303031313632011D",
}, nil
}}
mockSMS := &sms.SMSMock{}
mockSMS.SetHandleSMSQueueResponse(&sms.SMSQueueResponse{
SmsMsgID: "100",
SentSuccessful: true,
}, nil)
services.SetSapService(mockSap)
services.SetVehicleConfig(mockConf)
services.SetSmsClient(mockSMS)
mockKafkaProducer := kafkaMock.GetKafkaMock(nil)
services.SetKafkaProducer(mockKafkaProducer)
clonedStandardManifest := common.UpdateManifest{}
clonedStandardManifestNoICC := common.UpdateManifest{}
err := copier.CopyWithOption(&clonedStandardManifest, &standardManifest, copier.Option{DeepCopy: true})
if err != nil {
t.Fatal(err)
}
err = copier.CopyWithOption(&clonedStandardManifestNoICC, &standardManifestNoICC, copier.Option{DeepCopy: true})
if err != nil {
t.Fatal(err)
}
tests := []testrunner.TestCase{
{
Name: "Send manifest w Self Download",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &clonedStandardManifest
mockManDB.ECUUpdatesMock = func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return ecuaRollback, nil
}
return nil, nil
}
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}]},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":301,"length":302},"erase_region":{"offset":401,"length":402}}]},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":501,"length":502}}]},{"name":"ICC","version":"SWVERSION","current_version":"TEST123","hw_version":"HWVERSION","self_download":true,"files":[{"file_id":"AAAAAAA","url":"http://download.com/file1.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":true,"type":"forced","vod":"01011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110100202310010011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100001e","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{
// carupdateKey: {
// Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":297,"info":"ICC","installed":0,"status":"sent","total_files":0,"total_size":0}`,
// Expires: expectedExpire,
// },
smsKey: {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":100,"Destination":"ICC"}`,
},
},
},
},
{
Name: "Send manifest w Self Download rollback disabled",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &rollbackDisabledManifest
mockManDB.ECUUpdatesMock = func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return ecuaRollback, nil
}
return nil, nil
}
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}]},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":301,"length":302},"erase_region":{"offset":401,"length":402}}]},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":501,"length":502}}]},{"name":"ICC","version":"SWVERSION","current_version":"TEST123","hw_version":"HWVERSION","self_download":true,"files":[{"file_id":"AAAAAAA","url":"http://download.com/file1.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"MUST_BE_IN_UPDATE","url":"http://download.com/MUST_BE_IN_UPDATE.bin","file_size":1000,"checksum":"AAAAAAA","type":"calibration","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":false,"type":"standard","vod":"0101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010020231001000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000ba","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{
smsKey: {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":100,"Destination":"ICC"}`,
},
},
},
},
{
Name: "Bad message",
RedisTestCase: &rm.RedisTestCase{
Device: common.HMI,
DeviceKey: testVIN,
PayloadData: `{"car_update_id":`,
ExpectedError: "unexpected end of JSON input",
},
},
{
Name: "Send manifest w No Self Download",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &clonedStandardManifestNoICC
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}]},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":701,"length":702}}]},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":801,"length":802},"erase_region":{"offset":901,"length":902}}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":true,"type":"standard","vod":"0101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010020231001000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000ba","update_duration":30}}`,
tboxKey: `{"handler":"update_manifest","data":{"ecu_updates":[{"name":"ECUA","version":"A-VERS","current_version":"TEST122","hw_version":"A-VERS","configuration_mask":"AAAAAAAA","configuration":"config","files":[{"file_id":"FILEID","url":"URL","file_size":240,"type":"calibration","write_region":{"offset":601,"length":602}}],"rollback":[{"version":"VERSIONOLD","files":[{"file_id":"FILEIDOLD","url":"URLOLD","file_size":240,"type":"software","write_region":{"offset":701,"length":702}}]}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}},{"name":"ECUD","version":"SWVERSION","hw_version":"HWVERSION","files":[{"file_id":"BBBBBBB","url":"http://download.com/file2.bin","file_size":2000,"checksum":"BBBBBBB","type":"calibration","write_region":{"offset":701,"length":702}}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}},{"name":"OBC","version":"PDU-VERS","current_version":"TEST121","hw_version":"PDU-VERS","configuration_mask":"AAAAAAAA","files":[{"file_id":"AAAAAAAA","url":"http://download.com/filea.bin","file_size":2000,"checksum":"AAAAAAAA","type":"calibration","write_region":{"offset":801,"length":802},"erase_region":{"offset":901,"length":902}}],"ecc_keys":{"level_1":"74657374","level_2":"74657374","level_3":"74657374"}}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":true,"type":"standard","vod":"0101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111010020231001000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000ba","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{
"manifest_tbox_send_cache:UQIDEPFQUH": {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":0,"Destination":"ICC/TBOX"}`,
},
},
},
},
}
schemaTesterHMI := th.NewSchemaTestHelper(t, schemaToHMI)
schemaTesterTRex := th.NewSchemaTestHelper(t, schemaToTRex)
for _, test := range tests {
mockRedis.Reset()
for _, ecu := range ecus {
ecu.Rollback = nil
}
if test.DBTestCase != nil {
test.DBTestCase.SetupDB(&mockCUDB)
}
if test.RedisTestCase != nil {
test.RedisTestCase.SetupRedis(&mockRedis)
err := handlers.SendManifest(services.GetDB(), ka, test.RedisTestCase.Device, test.RedisTestCase.DeviceKey, []byte(test.RedisTestCase.PayloadData))
test.RedisTestCase.CheckHandlerError(t, test.Name, err)
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
}
if test.DBTestCase != nil {
test.DBTestCase.Validate(t, test.Name, &mockCUDB)
}
for key, m := range test.RedisTestCase.ExpectedMessages {
name := fmt.Sprintf("%s %s", test.Name, key)
if strings.Contains(key, "2:") {
schemaTesterHMI.ValidateSchemaObject(name, []byte(m))
continue
}
schemaTesterTRex.ValidateSchemaObject(name, []byte(m))
}
}
}
func TestSendManifestMultiFile(t *testing.T) {
os.Setenv("APP_SERVICE_NAME", "ATTENDANT")
testVIN := "JH4KA7680RC01"
now := time.Now()
hmiKey := common.HMI.Key(testVIN)
falsePtrValue := elptr.ElPtr(false)
truePtrValue := elptr.ElPtr(true)
var bhex common.BinaryHex = []byte("test")
redis.MockRedisConnection()
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
ecus := []*common.UpdateManifestECU{
{
ECU: "BCM",
Version: "DB22121A",
HWVersions: []string{";#A;#"},
Mode: "D",
SelfDownload: true,
Files: []*common.UpdateManifestFile{
{
FileID: "ShouldNotSee",
Filename: "notparsed.s19",
URL: "fakeurl.com",
Checksum: "shouldnotsee",
Parsed: falsePtrValue,
},
{
FileID: "3ee5fd6bc9402d67",
Filename: "MAGNA_BCM_FBL_driver.s19_0.bin",
URL: "https://upload-dev.fiskerdps.com/4466d78a-50e1-4f1d-a4b8-5f78076758ed/MAGNA_BCM_FBL_driver.s19_0.bin",
FileSize: 1000,
Checksum: "shouldsee",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Bootloader,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
DBModelBase: th.Timestamp,
Parsed: truePtrValue,
},
{
FileID: "7878ede9c9407779",
Filename: "FM29_Application_FR40_220111.s19_0.bin",
URL: "ttps://upload-dev.fiskerdps.com/b5c3c8c8-86cd-422e-aa11-a248697edfa3/FM29_Application_FR40_220111.s19_0.bin",
FileSize: 1000,
Checksum: "SHOULD NOT SEE",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Software,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
Signature: "ShouldNOtSee",
DBModelBase: th.Timestamp,
},
{
FileID: "7bb5083dc9407ae9",
Filename: "FM29_Application_FR40_220111.s19_1.bin",
URL: "https://upload-dev.fiskerdps.com/2cdeb566-cb00-42cd-93a8-69769b19269a/FM29_Application_FR40_220111.s19_1.bin",
FileSize: 1000,
Checksum: "shantsee",
WriteRegionID: 100,
WriteRegion: common.MemoryRegion{
Offset: 101,
Length: 102,
},
FileType: common.Software,
EraseRegionID: 200,
EraseRegion: &common.MemoryRegion{
Offset: 201,
Length: 202,
},
Parsed: truePtrValue,
DBModelBase: th.Timestamp,
Signature: "SHOULD NOT SEE",
},
{
FileID: "7ebb4fadc9409b91",
Filename: "FM29_Application_FR40_220111.s19_2.bin",
URL: "https://upload-dev.fiskerdps.com/ec9f8a84-61fb-4a9b-9735-ac6f91b07a83/FM29_Application_FR40_220111.s19_2.bin",
FileType: common.Software,
Parsed: truePtrValue,
Signature: "SomeSignatureYouShouldSee",
},
},
},
}
fingerprint := getTodaysFingerprint()
standardManifest := common.UpdateManifest{
ID: 100,
CarUpdateID: 297,
Name: "TEST",
Version: "MANIFEST_VERSION",
SUMS: "2023.10.01.00",
Description: "TESTDESC",
ReleaseNotes: "http://fiskerinc.com/release_notes",
Type: "standard",
Active: truePtrValue,
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
RollbackEnabled: false,
ECUs: ecus,
UpdateDuration: 30,
}
mockCarUpdate := &common.CarUpdate{
ID: 297,
VIN: testVIN,
UpdateManifestID: 100,
}
mockCarECUs := []common.CarECU{
{ECU: "ECUD", HWVersion: "HWVERSION"},
{ECU: "ECUA", HWVersion: "A-VERS"},
{ECU: "OBC", HWVersion: "PDU-VERS"},
{ECU: "BCM", HWVersion: ";#A;#"},
{ECU: "ICC", HWVersion: "HWVERSION"},
}
mockRedis := rm.MockRedis{}
mockCUDB := dbm.MockCarUpdates{}
mockManDB := dbm.MockUpdateManifests{}
mockKeys := dbm.MockEccKeys{
MockListResponse: []common.ECCKeys{
{
ECU: "BCM",
PrivKey1: &bhex,
PrivKey2: &bhex,
PrivKey3: &bhex,
PubKey1: &bhex,
PubKey2: &bhex,
PubKey3: &bhex,
Env: "current",
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
},
}
services.GetDB().SetCarUpdates(&mockCUDB)
services.GetDB().SetCars(&dbm.MockCars{SelectCarECUs: mockCarECUs, SelectResponse: &common.Car{ICCID: ""}})
services.GetDB().SetManifests(&mockManDB)
services.GetDB().SetECCKeys(&mockKeys)
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
mockSap := vconfig.SAPServiceMock{GetConfigurationMock: func(vin string) (common.SAPResponse, error) {
return common.SAPResponse{
ModelYear: 2023,
ModelType: "Ocean",
VersionDuringModelYear: "1",
Features: []common.SAPFeature{
{
FamilyCode: "FamilyCode1",
FeatureCode: "FeatureCode1",
},
{
FamilyCode: "FamilyCode2",
FeatureCode: "FeatureCode2",
},
},
}, nil
}}
mockConf := vconfig.ConfigMock{GetVODCDSCodingDataMock: func(request common.VODCDSRequest) (map[string]string, error) {
return map[string]string{
"ECUA": "config",
"VOD": "",
}, nil
}}
mockSMS := &sms.SMSMock{}
mockSMS.SetHandleSMSQueueResponse(&sms.SMSQueueResponse{
SmsMsgID: "100",
SentSuccessful: true,
}, nil)
services.SetSmsClient(mockSMS)
services.SetSapService(mockSap)
services.SetVehicleConfig(mockConf)
smsKey := "manifest_tbox_send_cache:100"
tests := []testrunner.TestCase{
{
Name: "MultiFile right order",
DBTestCase: &dbm.DBTestCase{
MockLoadResponse: mockCarUpdate,
SetupMockResponse: func() {
mockCUDB.LoadManifest = &standardManifest
mockManDB.ECUUpdatesMock = func(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error) {
if man.ECU == "ECUA" {
return nil, nil
}
return nil, nil
}
},
},
RedisTestCase: &rm.RedisTestCase{
Device: common.Service,
DeviceKey: kafka.OTAUpdateService,
PayloadData: `{"car_update_id":297}`,
ExpectedMessages: map[string]string{
hmiKey: `{"handler":"update_manifest","data":{"name":"TEST","version":"MANIFEST_VERSION","description":"TESTDESC","release_notes":"http://fiskerinc.com/release_notes","ecu_updates":[{"name":"BCM","version":"DB22121A","hw_version":";#A;#","self_download":true,"files":[{"file_id":"3ee5fd6bc9402d67","url":"https://upload-dev.fiskerdps.com/4466d78a-50e1-4f1d-a4b8-5f78076758ed/MAGNA_BCM_FBL_driver.s19_0.bin","file_size":1000,"checksum":"shouldsee","type":"bootloader","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"7878ede9c9407779","url":"ttps://upload-dev.fiskerdps.com/b5c3c8c8-86cd-422e-aa11-a248697edfa3/FM29_Application_FR40_220111.s19_0.bin","file_size":1000,"type":"software","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"7bb5083dc9407ae9","url":"https://upload-dev.fiskerdps.com/2cdeb566-cb00-42cd-93a8-69769b19269a/FM29_Application_FR40_220111.s19_1.bin","file_size":1000,"type":"software","write_region":{"offset":101,"length":102},"erase_region":{"offset":201,"length":202}},{"file_id":"7ebb4fadc9409b91","url":"https://upload-dev.fiskerdps.com/ec9f8a84-61fb-4a9b-9735-ac6f91b07a83/FM29_Application_FR40_220111.s19_2.bin","type":"software","write_region":{"offset":0,"length":0},"signature":"SomeSignatureYouShouldSee"}]}],"fingerprint":"` + fingerprint + `","car_update_id":297,"rollback":false,"type":"standard","vod":"01011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110100202310010011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100001e","update_duration":30}}`,
},
ExpectedCaches: map[string]rm.ExpiringCacheResult{smsKey: {
Value: `{"VIN":"JH4KA7680RC01","ManifestID":100,"Destination":"ICC"}`,
}},
},
},
}
for _, test := range tests {
mockRedis.Reset()
for _, ecu := range ecus {
ecu.Rollback = nil
}
if test.DBTestCase != nil {
test.DBTestCase.SetupDB(&mockCUDB)
}
if test.RedisTestCase != nil {
test.RedisTestCase.SetupRedis(&mockRedis)
err := handlers.SendManifest(services.GetDB(), ka, test.RedisTestCase.Device, test.RedisTestCase.DeviceKey, []byte(test.RedisTestCase.PayloadData))
test.RedisTestCase.CheckHandlerError(t, test.Name, err)
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
}
if test.DBTestCase != nil {
test.DBTestCase.Validate(t, test.Name, &mockCUDB)
}
}
}
func TestSendManifestIntegration(t *testing.T) {
os.Setenv("APP_SERVICE_NAME", "ATTENDANT")
ka := services.NewKeepAwakeService()
ka.SetService(&services.MockKeepAwakeImplementation{})
err := handlers.SendManifest(services.GetDB(), ka, common.Service, "", []byte(`{"car_update_id":297}`))
if err != nil {
t.Error(err)
}
}
func getTodaysFingerprint() string {
// generate today's fingerprint
fpparams := manifestfingerprintparams.GetFPParams()
var fm = common.UpdateManifest{}
fm.GenerateFingerprint(fpparams.CurTime(), fpparams.ManifestSerial())
return fm.Fingerprint
}

View File

@@ -0,0 +1,55 @@
package handlers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
// UpdateData extracts update ID from byte slice
type UpdateData struct {
ID int `json:"id"`
}
// ApproveUpdate updates DB update field and
//
// sends redis message to vehicle to initialize download
func ApproveUpdate(db *services.DB, id string, data []byte) error {
logger.Debug().Msgf("ApproveUpdate %s", id)
client := services.RedisClientPool().GetFromPool()
defer client.Close()
// TODO: NEEDS VALIDATION THAT INCOMING ID CAN APPROVE
// CAN COME FROM MOBILE OR HMI - NEEDS TO HANDLE BOTH CASES
var u UpdateData
err := json.Unmarshal(data, &u)
if err != nil {
return errors.WithStack(err)
}
update, err := db.ModifyUpdateStatus(u.ID, "approved")
if err != nil {
return err
}
logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", update.VIN, "update_download", u)
err = client.SafeQueueMessage(
common.TRex.Key(update.VIN),
common.Message{
Handler: "update_download",
Data: u,
},
)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,28 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestApproveUpdate(t *testing.T) {
mockRedis := &tester.MockRedis{}
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
data := []byte(`{"id": 1}`)
err := handlers.ApproveUpdate(mockDB, "FISKER123", data)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "ApproveUpdate", "no error", err)
}
err = handlers.ApproveUpdate(mockDB, "FISKER123", nil)
if err == nil {
t.Errorf(testhelper.TestErrorTemplate, "ApproveUpdate", "error", err)
}
}

View File

@@ -0,0 +1,70 @@
package handlers
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/common"
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
// VehicleData extracts VIN from byte slice
type VehicleData struct {
VIN string `json:"vin" validate:"required,vin"`
}
// GetUpdates queries DB for updates based off VIN and
//
// sends redis message back to requester
func GetUpdates(db *services.DB, id string, data []byte) error {
clientPool := services.RedisClientPool()
var v VehicleData
err := json.Unmarshal(data, &v)
if err != nil {
return errors.WithStack(err)
}
err = validator.ValidateStruct(&v)
if err != nil {
return errors.WithStack(err)
}
ok, err := cache.VerifyCarToDriver(clientPool, db.GetCars(), v.VIN, id)
if err != nil {
return err
} else if !ok {
return cache.ErrInvalidCarToDriverAssociation(v.VIN, id)
}
cu := common.CarUpdate{
VIN: v.VIN,
Status: s.InstallApprovalAwait,
}
updates, err := db.GetCarUpdates().Select(&cu, nil)
if err != nil {
return err
}
result := make([]common.ApprovalUpdate, len(updates))
for i := range updates {
result[i] = common.NewApprovalUpdates(&updates[i])
}
client := clientPool.GetFromPool()
defer client.Close()
logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", id, "updates", result)
return client.SafeQueueMessage(
common.Mobile.Key(id),
common.Message{
Handler: "updates",
Data: result,
},
)
}

View File

@@ -0,0 +1,148 @@
package handlers_test
import (
"fmt"
"testing"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/pkg/errors"
)
func TestUpdatesGet(t *testing.T) {
mockRedis := &tester.MockRedis{}
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
mockCars := &mocks.MockCars{}
mockCarUpdates := &mocks.MockCarUpdates{}
mockDB = &services.DB{}
mockDB.SetCars(mockCars)
mockDB.SetCarUpdates(mockCarUpdates)
cognitoID := "valid-cognito-id-1"
mobileKey := common.Mobile.Key(cognitoID)
vin := "JM1BG2241R0797923"
data := fmt.Sprintf(`{"vin":"%s"}`, vin)
now := time.Now()
selectList := []common.CarUpdate{
{
ID: 1234,
VIN: vin,
UpdateManifestID: 4321,
Status: s.InstallApprovalAwait,
UpdateManifest: &common.UpdateManifest{
Name: "TEST",
Description: "TEST",
ReleaseNotes: "http://releasenotes.com",
Country: "US",
PowerTrain: "MD23",
Restraint: "None",
Model: "Ocean",
Trim: "Sport",
Year: 2022,
BodyType: "truck",
ECUs: []*common.UpdateManifestECU{
{
ECU: "ADAS",
Version: "VERSION",
Mode: "A",
},
},
DBModelBase: dbbasemodel.DBModelBase{
CreatedAt: &now,
UpdatedAt: &now,
},
},
},
}
carToDrivers := []common.CarToDriver{
{
ID: 2000,
VIN: vin,
DriverID: cognitoID,
},
}
tests := []AttendentRouteTestCase{
{
Name: "No data",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: "",
ExpectedError: "unexpected end of JSON input",
},
},
{
Name: "Bad request",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: "{}",
ExpectedError: "Key: 'VehicleData.VIN' Error:Field validation for 'VIN' failed on the 'required' tag",
},
},
{
Name: "Bad association",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedError: "no relationship found between vin JM1BG2241R0797923 and driver valid-cognito-id-1",
},
},
{
Name: "Cache error",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedError: "cache error",
},
CarToDriversError: errors.New("cache error"),
},
{
Name: "Good request",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedMessages: map[string]string{
mobileKey: `{"handler":"updates","data":[{"id":1234,"vin":"JM1BG2241R0797923","name":"TEST","description":"TEST","release_notes":"http://releasenotes.com"}]}`,
},
},
SelectCarToDrivers: carToDrivers,
SelectCarUpdates: selectList,
},
{
Name: "DB error",
RedisTestCase: tester.RedisTestCase{
Device: common.Mobile,
DeviceKey: cognitoID,
PayloadData: data,
ExpectedError: "database error",
},
SelectCarToDrivers: carToDrivers,
SelectCarUpdates: nil,
CarUpdateError: errors.New("database error"),
},
}
for i := range tests {
mockRedis.Reset()
test := &tests[i]
test.SetupRedis(mockRedis)
test.SetupDB(mockCars, mockCarUpdates, test)
err := handlers.GetUpdates(mockDB, test.DeviceKey, []byte(test.PayloadData))
test.CheckHandlerError(t, test.Name, err)
test.Validate(t, test.Name, mockRedis)
}
}

View File

@@ -0,0 +1,45 @@
package handlers
import (
"encoding/json"
"time"
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/mongo"
"github.com/fiskerinc/cloud-services/pkg/validator"
"github.com/pkg/errors"
)
func UploadDtc(id string, data []byte) error {
// ID is a vin in this case
logger.Debug().Msgf("Upload DTC for %s", id)
var DTCEntry controllers.DTCEntry
DTCEntry.VIN = id
err := json.Unmarshal(data, &DTCEntry)
if err != nil {
return err
}
if err = validator.GetValidator().Struct(&DTCEntry); err != nil {
return errors.WithMessage(err, "failed structure validation")
}
err = DTCEntry.ParseSnapshot()
if err != nil {
logger.Warn().Msg(err.Error())
return err
}
client, err := services.GetMongoClient()
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
return err
}
dtcs := mongo.NewCollection(client.Collection("dtcs"))
DTCEntry.CreatedAt = time.Now()
_, err = dtcs.InsertOne(DTCEntry)
return err
}

View File

@@ -0,0 +1,27 @@
package main
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/server"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/app"
)
func init() {
app.Setup("attendant", cleanup)
}
func main() {
defer cleanup()
go controllers.HealthCheck()
go server.StartConsumer(kafka.AttendantServiceGRPCKafka, kafka.AttendantService)
select {}
}
func cleanup() {
logger.Close()
}

View File

@@ -0,0 +1,7 @@
package server
import (
"github.com/pkg/errors"
)
var ErrInvalidDevice = errors.New("invalid device associated to message")

View File

@@ -0,0 +1,221 @@
package server
import (
"github.com/fiskerinc/cloud-services/services/attendant/controllers"
"github.com/fiskerinc/cloud-services/services/attendant/handlers"
"github.com/fiskerinc/cloud-services/services/attendant/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
"github.com/fiskerinc/cloud-services/pkg/tmobile"
"google.golang.org/protobuf/proto"
)
// StartConsumer runs consumer and puts events into a channel for router
func StartConsumer(topic, oldTopic string) {
defer func() {
if err := recover(); err != nil {
logger.Error().Msgf("PanicConsumer %v", err)
}
}()
events := make(chan *kafka.Message)
eventsJSON := make(chan common.EventRawJSON)
go routeEvents(events)
go routeOldEvents(eventsJSON)
logger.Info().Msgf("consumer intialized for topic: %v", topic)
consumer, oldConsumer, err := services.GetKafkaConsumer()
if err != nil {
panic(err)
}
go func() {
err = oldConsumer.ConsumeToChannelJson([]string{oldTopic}, eventsJSON)
loggerdataresp.BadDataError(err, loggerdataresp.EofErrorCheck)
}()
err = consumer.ConsumeToChannel([]string{topic}, events)
loggerdataresp.BadDataError(err, loggerdataresp.EofErrorCheck)
}
func routeEvents(events chan *kafka.Message) {
db := services.GetDB()
defer db.Close()
sms := services.GetSMSClient()
ka := services.NewKeepAwakeService()
for {
event := <-events
var err error
payload := &kafka_grpc.GRPC_AttendantPayload{}
err = proto.Unmarshal(event.Value, payload)
if err != nil {
logger.Warn().Err(err).Send()
}
device, key := common.ParseDeviceKey(string(event.Key))
logger.Debug().Str("id", key).Msgf("source: %s, type: %s, handler: %s", key, device, payload.GetHandler())
switch device {
case common.TRex:
if payload.GetHandler() == "dtcs" {
d := &common.ConsumerPayload{
Handler: payload.GetHandler(),
Data: controllers.GRPCToDTCEntry(payload),
}
routeService(db, ka, sms, key, d)
break
}
d, _ := common.AttendantRouteTRexPayload(payload)
routeTRex(db, ka, key, d)
case common.HMI:
d, _ := common.AttendantRouteHMIPayload(payload)
routeHMI(db, ka, key, d)
case common.Mobile:
d, _ := common.AttendantRouteMobilePayload(payload)
routeMobile(db, key, d)
case common.Service:
if payload.GetHandler() == "sms_delivery_status_manifest" {
d := &common.ConsumerPayload{
Handler: payload.GetHandler(),
Data: tmobile.GRPCToTMessage(payload),
}
routeService(db, ka, sms, key, d)
break
}
d, _ := common.AttendantRouteServicePayload(payload)
routeService(db, ka, sms, key, d)
default:
{
logger.Error().Str("id", key).Msgf("Unknown device: %s, type: %s, handler: %s", device, key, payload.GetHandler())
loggerdataresp.BadDataError(ErrInvalidDevice)
}
}
loggerdataresp.BadDataError(err)
}
}
func routeOldEvents(events chan common.EventRawJSON) {
db := services.GetDB()
defer db.Close()
sms := services.GetSMSClient()
ka := services.NewKeepAwakeService()
for {
event := <-events
var p common.Payload
err := p.Unmarshal(event.Payload)
device, key := common.ParseDeviceKey(event.Key)
payload := &common.ConsumerPayload{
Handler: p.Handler,
Data: p.Data,
}
logger.Debug().Str("id", key).Msgf("source: %s, type: %s, handler: %s", key, device, payload.GetHandler())
switch device {
case common.TRex:
routeTRex(db, ka, key, payload)
case common.HMI:
routeHMI(db, ka, key, payload)
case common.Mobile:
routeMobile(db, key, payload)
case common.Service:
routeService(db, ka, sms, key, payload)
default:
{
logger.Error().Str("id", key).Msgf("Unknown device: %s, type: %s, handler: %s", device, key, p.Handler)
loggerdataresp.BadDataError(ErrInvalidDevice)
}
}
loggerdataresp.BadDataError(err)
}
}
func routeTRex(db *services.DB, ka *services.KeepAwake, id string, p common.ConsumerPayloadInterface) {
// route TRex messages. Also update cloud/modules_go/kafka/topics.go
var err error
switch p.GetHandler() {
case "car_state":
err = handlers.UpdateCarState(db, id, p.GetData())
loggerdataresp.BadDataError(err)
case "car_update_status", "car_update_download", "car_update_install":
err = handlers.CarUpdateProgressStatus(db, ka, common.TRex, id, p.GetData())
loggerdataresp.BadDataError(err)
case "get_filekeys":
err = handlers.GetFileKeys(db, common.TRex, id, p.GetData())
case "dtcs":
err = handlers.UploadDtc(id, p.GetData())
case "ecc_keys":
err = handlers.GetAllEccKeys(db, id, p.GetData())
default:
err = kafka.ErrUnhandledMessage(common.TRex, id, p.GetHandler(), string(p.GetData()))
}
loggerdataresp.BadDataError(err)
}
func routeHMI(db *services.DB, ka *services.KeepAwake, id string, p common.ConsumerPayloadInterface) {
// route HMI messages. Also update cloud/modules_go/kafka/topics.go
var err error
switch p.GetHandler() {
case "update_approve":
err = handlers.ApproveUpdate(db, id, p.GetData())
case "car_update_status", "car_update_download", "car_update_install":
go func() {
err = handlers.CarUpdateProgressStatus(db, ka, common.HMI, id, p.GetData())
loggerdataresp.BadDataError(err)
}()
err = handlers.CarUpdateProgressStatus(db, ka, common.HMI, id, p.GetData())
loggerdataresp.BadDataError(err)
case "get_filekeys":
err = handlers.GetFileKeys(db, common.HMI, id, p.GetData())
default:
err = kafka.ErrUnhandledMessage(common.HMI, id, p.GetHandler(), string(p.GetData()))
}
loggerdataresp.BadDataError(err)
}
func routeMobile(db *services.DB, id string, p common.ConsumerPayloadInterface) {
// route mobile messages. Also update cloud/modules_go/kafka/topics.go
var err error
switch p.GetHandler() {
case "update_approve":
err = handlers.ApproveUpdate(db, id, p.GetData())
case "updates_get":
err = handlers.GetUpdates(db, id, p.GetData())
default:
err = kafka.ErrUnhandledMessage(common.Mobile, id, p.GetHandler(), string(p.GetData()))
}
loggerdataresp.BadDataError(err)
}
func routeService(db *services.DB, ka *services.KeepAwake, sms sms.SMSServiceClient, id string, p common.ConsumerPayloadInterface) {
// route cloud service messages. Also update cloud/modules_go/kafka/topics.go
var err error
// We should switch out these strings for constants in the kafka library
switch p.GetHandler() {
case "send_manifest":
err = handlers.SendManifest(db, ka, common.Service, id, p.GetData())
case "order_updated":
err = handlers.OrderUpdated(db, sms, id, p.GetData())
case "sms_delivery_status_manifest":
err = handlers.CarSendManifestSMSWakeUpCallback(db, ka, common.Service, id, p.GetData())
default:
err = kafka.ErrUnhandledMessage(common.Service, id, p.GetHandler(), string(p.GetData()))
}
loggerdataresp.BadDataError(err)
}

View File

@@ -0,0 +1,29 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/logger"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
)
var (
configOnce sync.Once
configInstance vconfig.ConfigServiceInterface
)
func GetVehicleConfig() vconfig.ConfigServiceInterface {
configOnce.Do(func() {
if configInstance != nil {
return
}
logger.Info().Msg("init vehicle config instance")
configInstance = vconfig.NewConfigService()
})
return configInstance
}
func SetVehicleConfig(c vconfig.ConfigServiceInterface) {
configInstance = c
}

View File

@@ -0,0 +1,278 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db"
q "github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
var (
dbOnce sync.Once
dbInstance *DB
)
type DB struct {
client *db.DBClient
cars q.CarsInterface
carVersionsLog q.CarVersionsLogInterface
carupdates q.CarUpdatesInterface
filekeys q.FileKeysInterface
manifests q.UpdateManifestsInterface
ecu q.ECUInterface
eccKeys q.EccKeysInterface
ratePlan q.RatePlanInterface
updateManifestSUMSVersions q.SUMSVersionsInterface
carConfigData q.CarConfigDataInterface
onceEcu sync.Once
onceClient sync.Once
onceCars sync.Once
onceCarVersionsLog sync.Once
onceCarUpdates sync.Once
onceFileKeys sync.Once
onceManifests sync.Once
onceEccKeys sync.Once
onceRatePlan sync.Once
onceUpdateManifestSUMSVersions sync.Once
onceCarConfigData sync.Once
}
func GetDB() *DB {
dbOnce.Do(func() {
if dbInstance != nil {
return
}
logger.Info().Msg("init DB instance")
dbInstance = &DB{}
})
return dbInstance
}
func SetDB(db *DB) {
if dbInstance != nil {
dbInstance.Close()
}
dbInstance = db
}
func (d *DB) GetDBClient() *db.DBClient {
d.onceClient.Do(func() {
if d.client != nil {
return
}
logger.Info().Msg("init DBClient instance")
client := &db.DBClient{}
client.RegisterManyToManyRel([]interface{}{
(*common.CarToDriver)(nil),
})
err := client.InitSchema([]interface{}{
(*common.UpdateManifest)(nil),
(*common.Car)(nil),
(*common.CarToDriver)(nil),
(*common.CarUpdateStatus)(nil),
(*common.CarUpdate)(nil),
(*common.FileKey)(nil),
(*common.RatePlanTMobile)(nil),
})
if err != nil {
logger.Error().Err(err).Send()
}
// Uncomment below to show generated SQL queries
// client.GetConn().AddQueryHook(db.SQLLogger{})
d.client = client
})
return d.client
}
func (d *DB) SetDBClient(client *db.DBClient) {
if d.client != nil {
d.client.Close()
}
d.client = client
}
func (d *DB) Close() {
if d.client == nil {
return
}
d.client.Close()
}
func (d *DB) GetCarUpdates() q.CarUpdatesInterface {
d.onceCarUpdates.Do(func() {
if d.carupdates != nil {
return
}
instance := &q.CarUpdates{}
instance.SetClient(d.GetDBClient())
d.carupdates = instance
})
return d.carupdates
}
func (d *DB) SetCarUpdates(carupdates q.CarUpdatesInterface) {
d.carupdates = carupdates
}
func (d *DB) GetCars() q.CarsInterface {
d.onceCars.Do(func() {
if d.cars != nil {
return
}
instance := &q.Cars{}
instance.SetClient(d.GetDBClient())
d.cars = instance
})
return d.cars
}
func (d *DB) SetCars(cars q.CarsInterface) {
d.cars = cars
}
func (d *DB) GetCarVersionsLog() q.CarVersionsLogInterface {
d.onceCarVersionsLog.Do(func() {
if d.carVersionsLog != nil {
return
}
instance := &q.CarVersionsLog{}
instance.SetClient(d.GetDBClient())
d.carVersionsLog = instance
})
return d.carVersionsLog
}
func (d *DB) SetCarVersionsLog(log q.CarVersionsLogInterface) {
d.carVersionsLog = log
}
func (d *DB) GetFileKeys() q.FileKeysInterface {
d.onceFileKeys.Do(func() {
if d.filekeys != nil {
return
}
instance := &q.FileKeys{}
instance.SetClient(d.GetDBClient())
d.filekeys = instance
})
return d.filekeys
}
func (d *DB) SetFileKeys(filekeys q.FileKeysInterface) {
d.filekeys = filekeys
}
func (d *DB) GetUpdateManifests() q.UpdateManifestsInterface {
d.onceManifests.Do(func() {
if d.manifests != nil {
return
}
instance := q.NewUpdateManifest(nil)
instance.SetClient(d.GetDBClient())
d.manifests = instance
})
return d.manifests
}
func (d *DB) GetECU() q.ECUInterface {
d.onceEcu.Do(func() {
if d.ecu != nil {
return
}
instance := &q.ECU{}
instance.SetClient(d.GetDBClient())
d.ecu = instance
})
return d.ecu
}
func (d *DB) SetECU(instance q.ECUInterface) {
d.ecu = instance
}
func (d *DB) SetManifests(manifests q.UpdateManifestsInterface) {
d.manifests = manifests
}
func (d *DB) ModifyUpdateStatus(id int, status string) (*common.CarUpdate, error) {
cu := d.GetCarUpdates()
updates, err := cu.SelectByID(int64(id))
if err != nil {
return updates, err
}
updates.Status = status
_, err = cu.UpdateStatus(updates)
return updates, err
}
func (d *DB) GetECCKeys() q.EccKeysInterface {
d.onceEccKeys.Do(func() {
if d.eccKeys != nil {
return
}
eccKeys := &q.EccKeys{}
eccKeys.SetClient(d.GetDBClient())
d.eccKeys = eccKeys
})
return d.eccKeys
}
func (d *DB) SetECCKeys(eccKeys q.EccKeysInterface) {
d.eccKeys = eccKeys
}
func (d *DB) GetRatePlan() q.RatePlanInterface {
d.onceRatePlan.Do(func() {
if d.ratePlan != nil {
return
}
instance := &q.RatePlanTmobile{}
instance.SetClient(d.GetDBClient())
d.ratePlan = instance
})
return d.ratePlan
}
func (d *DB) SetRatePlan(ratePlan q.RatePlanInterface) {
d.ratePlan = ratePlan
}
func (d *DB) GetUpdateManifestSUMSVersions() q.SUMSVersionsInterface {
d.onceUpdateManifestSUMSVersions.Do(func() {
if d.updateManifestSUMSVersions != nil {
return
}
instance := &q.SUMSVersions{}
instance.SetClient(d.GetDBClient())
d.updateManifestSUMSVersions = instance
})
return d.updateManifestSUMSVersions
}
func (d *DB) SetUpdateManifestVersions(umv q.SUMSVersionsInterface) {
d.updateManifestSUMSVersions = umv
}
func (d *DB) GetCarConfigData() q.CarConfigDataInterface {
d.onceCarConfigData.Do(func() {
if d.carConfigData != nil {
return
}
logger.Debug().Msg("Init CarConfigData instance")
carConfigData := &q.CarConfigData{}
carConfigData.SetClient(d.GetDBClient())
d.carConfigData = carConfigData
})
return d.carConfigData
}
func (d *DB) SetCarConfigData(carConfigData q.CarConfigDataInterface) {
d.carConfigData = carConfigData
}

View File

@@ -0,0 +1,20 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/dbc"
"github.com/fiskerinc/cloud-services/pkg/dbc/models"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
)
var model models.DBCVersionInterface
var collectionOnce sync.Once
// GetDBCCollection returns singleton instance of collection of DBCs
func GetDBC() *descriptor.Database {
collectionOnce.Do(func() {
model = dbc.NewFM29_FRSD390_DBC()
})
return model.GetDatabase()
}

View File

@@ -0,0 +1,24 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
var (
MAX_KEY_CACHE int = envtool.GetEnvInt("MAX_KEY_CACHE", 10000)
carDtcsCache cache.CarDTCsCacheInterface
onceDTCCache sync.Once
)
func GetCarDtcCache() cache.CarDTCsCacheInterface {
onceDTCCache.Do(func() {
if carDtcsCache == nil {
carDtcsCache = cache.NewCarDTCsCache(MAX_KEY_CACHE)
}
})
return carDtcsCache
}

View File

@@ -0,0 +1,28 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
var (
MAX_ECU_KEY_CACHE int = envtool.GetEnvInt("MAX_ECU_KEY_CACHE", 10000)
ring cache.RingMapInterface
onceECUCache sync.Once
)
func GetCarEcuCache() cache.RingMapInterface {
onceECUCache.Do(func() {
if ring == nil {
ring = cache.NewRingMap(MAX_ECU_KEY_CACHE)
}
})
return ring
}
func SetCarEcuCache(value cache.RingMapInterface) {
ring = value
}

View File

@@ -0,0 +1,88 @@
package services
import (
"net/http"
"net/url"
"slices"
"sync"
"github.com/fiskerinc/cloud-services/pkg/common"
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
"github.com/fiskerinc/cloud-services/pkg/foa"
"github.com/fiskerinc/cloud-services/pkg/httpclient"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
var UPDATE_MANIFEST_IDS_TO_NOTIFY_FOA = []int64{816, 817, 818, 819, 820}
var (
foaService FoaServiceInterface
foaOnce sync.Once
)
func GetFoaService() FoaServiceInterface {
foaOnce.Do(func() {
if foaService != nil {
return
}
foaService = NewFoaService()
})
return foaService
}
func SetFoaService(foa FoaServiceInterface) {
foaService = foa
}
func NewFoaService() FoaServiceInterface {
return &FoaService{
foaURL: envtool.GetEnv("FOA_URL", "REPLACE_ME"),
foaAPIToken: envtool.GetEnv("FOA_API_KEY", "REPLACE_ME"),
}
}
type FoaServiceInterface interface {
OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error)
}
type FoaService struct {
foaURL string
foaAPIToken string
}
func (f *FoaService) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) {
if !slices.Contains(UPDATE_MANIFEST_IDS_TO_NOTIFY_FOA, carUpdate.UpdateManifestID) {
// Nothing to send if the manifest is not one of the specified IDs
return nil, nil
}
var body interface{} = nil
switch status.Status {
case s.ManifestSucceeded:
body = foa.BuildOtaUpdateStatusSuccessRequest(vin, carUpdate.UpdateManifestID)
case s.ManifestError:
body = foa.BuildOtaUpdateStatusFailedRequest(vin, carUpdate.UpdateManifestID, status.Info)
case s.ManifestCanceled:
body = foa.BuildOtaUpdateStatusCanceledRequest(vin, carUpdate.UpdateManifestID, status.Info)
}
if body == nil {
return nil, nil
}
logger.Info().Msgf("Notifying FOA for %s of update %d status %s", vin, carUpdate.UpdateManifestID, status.Status)
urlString, err := url.JoinPath(f.foaURL, "ota/update_status")
if err != nil {
return nil, err
}
postHeader := http.Header{}
postHeader.Add("Authorization", "Bearer "+f.foaAPIToken)
postHeader.Add("Content-Type", "application/json")
return httpclient.Post(urlString, body, postHeader)
}

View File

@@ -0,0 +1,62 @@
package services
import (
"context"
"sync"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
const serviceName = "attendant"
const oldServiceName = "old-attendant"
var consumer, oldConsumer kafka.ConsumerInterface
var consumerOnce sync.Once
// GetKafkaConsumer returns singleton instance of kafka consumer
func GetKafkaConsumer() (kafka.ConsumerInterface, kafka.ConsumerInterface, error) {
var err error
consumerOnce.Do(func() {
consumer, err = kafka.NewConsumer(serviceName)
if err != nil {
logger.Error().Err(err).Send()
}
oldConsumer, err = kafka.NewConsumer(oldServiceName)
if err != nil {
logger.Error().Err(err).Send()
}
})
if err != nil {
return nil, nil, err
}
return consumer, oldConsumer, nil
}
var producer kafka.ProducerInterface
var producerOnce sync.Once
func GetKafkaProducer() (kafka.ProducerInterface, error) {
var err error
producerOnce.Do(func() {
if producer == nil {
var producerTemp kafka.ProducerInterface
producerTemp, err = kafka.NewProducer(context.Background())
if err != nil {
logger.Err(err).Send()
}
producer = producerTemp
}
})
if err != nil {
return nil, err
}
return producer, err
}
func SetKafkaProducer(k kafka.ProducerInterface) {
producer = k
}

View File

@@ -0,0 +1,215 @@
package services
import (
"fmt"
"reflect"
"sync"
"time"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/pkg/errors"
)
// This can probably be moved into a better spot, but we are on a time limit
var ADD_TIME = time.Minute
type KeepAwake struct {
tick *time.Ticker
KAI KeepAwakeInterface
}
type KeepAwakeInterface interface {
CheckKeepAwakeMessages()
// addToKeepAwakeMessages(vin string) (err error)
RemoveKeepAwakeMessage(vin string) (err error)
// getKeepAwakeMessages() (vins []string, err error)
SendFirstKeepAwakeMessage(vin string) (err error)
// sendKeepAwakeMessage(vin string) (err error)
}
type KeepAwakeImplementation struct {
Cars map[string]time.Time // Map of car vins to the time it was last ran
oneTime sync.Once
mapLock sync.Mutex
}
func NewKeepAwakeService() (ka *KeepAwake) {
ka = &KeepAwake{}
ka.tick = time.NewTicker(time.Minute)
ka.KAI = &KeepAwakeImplementation{}
// Start our timer
go func() {
for {
select {
case <-ka.tick.C:
ka.KAI.CheckKeepAwakeMessages()
}
}
}()
return
}
func (ka *KeepAwake) SetService(inf KeepAwakeInterface) {
ka.KAI = inf
}
func (ka *KeepAwake) RemoveKeepAwakeMessage(vin string) (err error) {
return ka.KAI.RemoveKeepAwakeMessage(vin)
}
func (ka *KeepAwake) SendFirstKeepAwakeMessage(vin string) (err error) {
return ka.KAI.SendFirstKeepAwakeMessage(vin)
}
// Need to move this out of here, as this manifest sender is created every time a manifest is to be sent
// On a timeout, check if the keep awake messages need to be sent
func (k *KeepAwakeImplementation) CheckKeepAwakeMessages() {
k.oneTime.Do(func() {
k.Cars = make(map[string]time.Time)
})
k.mapLock.Lock()
vins, _ := k.getKeepAwakeMessages()
for _, vin := range vins {
k.sendKeepAwakeMessage(vin)
k.addToKeepAwakeMessages(vin)
}
k.mapLock.Unlock()
// Call this function again in some amount of time
}
func (k *KeepAwakeImplementation) addToKeepAwakeMessages(vin string) (err error) {
k.Cars[vin] = time.Now().Add(ADD_TIME)
return
}
func (k *KeepAwakeImplementation) RemoveKeepAwakeMessage(vin string) (err error) {
k.oneTime.Do(func() {
k.Cars = make(map[string]time.Time)
})
// Remove from Redis
err = k.removeRedisCarEntry(vin)
if err != nil {
logger.Err(err).Msg("Unable to remove keeep awake message from redis")
}
logger.Info().Msgf("Ended keep awake messaging for %s", vin)
k.mapLock.Lock()
defer k.mapLock.Unlock()
_, ok := k.Cars[vin]
if !ok {
logger.Debug().Msgf("Attempted to end keep awake for %s, but not in list", vin)
}
delete(k.Cars, vin)
return err
}
// This is a list of vins that need sendKeepAwakeMessage
func (k *KeepAwakeImplementation) getKeepAwakeMessages() (vins []string, err error) {
for vin, t := range k.Cars {
// Parse out the time and check if its before the time it is now
if t.Before(time.Now()) {
// This needs processing
vins = append(vins, vin)
}
}
return
}
func (k *KeepAwakeImplementation) SendFirstKeepAwakeMessage(vin string) (err error) {
k.oneTime.Do(func() {
k.Cars = make(map[string]time.Time)
})
// Additionally add to the list of redis
err = k.addRedisCarEntry(vin)
logger.Err(err).Msg("")
logger.Info().Msgf("Started keep awake messaging for %s", vin)
k.mapLock.Lock()
defer k.mapLock.Unlock()
err = k.sendKeepAwakeMessage(vin)
k.addToKeepAwakeMessages(vin)
return err
}
// Send a special message to keep the tbox awake to download the update
// We want to continue to call this until the download is complete or if we fail
func (k *KeepAwakeImplementation) sendKeepAwakeMessage(vin string) (err error) {
client := RedisClientPool().GetFromPool()
defer client.Close()
// check if in redis
found, err := k.checkRedisForCarEntry(vin, client)
if err != nil {
return
}
// If we did not find the redis entry, then another service has removed it from the keep alive set
// Don't call k.Delete as it creates a lock on the map, and then we will be deadlocked
if !found {
delete(k.Cars, vin)
return
}
logger.Info().Msgf("sending keep awake message for %s", vin)
type Action struct {
Action string `json:"action"`
Timeout int32 `json:"timeout"`
}
logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", vin, "can_network", Action{Action: "on"})
err = client.SafeQueueMessage(common.TRex.Key(vin), common.Message{
Handler: "can_network",
Data: Action{Action: "on", Timeout: 120},
})
return errors.WithStack(err)
}
func (k *KeepAwakeImplementation) checkRedisForCarEntry(vin string, client redis.Client) (found bool, err error) {
line, err := client.Execute("SISMEMBER", "can_keep_awake", vin)
if err != nil {
return false, errors.WithStack(err)
}
check, ok := line.(int64)
if !ok {
err = fmt.Errorf("received wrong type from redis for the can_keep_awake for %s, %v, %s", vin, line, reflect.TypeOf(line))
}
return check > 0, err
}
func (k *KeepAwakeImplementation) removeRedisCarEntry(vin string) (err error) {
client := RedisClientPool().GetFromPool()
defer client.Close()
_, err = client.Execute("SREM", "can_keep_awake", vin)
return
}
func (k *KeepAwakeImplementation) addRedisCarEntry(vin string) (err error) {
client := RedisClientPool().GetFromPool()
defer client.Close()
_, err = client.Execute("SADD", "can_keep_awake", vin)
return
}
type MockKeepAwakeImplementation struct{}
func (k *MockKeepAwakeImplementation) CheckKeepAwakeMessages() {}
func (k *MockKeepAwakeImplementation) RemoveKeepAwakeMessage(vin string) (err error) {
return err
}
func (k *MockKeepAwakeImplementation) SendFirstKeepAwakeMessage(vin string) (err error) {
return
}

View File

@@ -0,0 +1,68 @@
package services
import (
"testing"
"time"
)
func TestKeepAwake(t *testing.T){
t.Skip()
ADD_TIME = 0
ka := KeepAwake{}
impl := &KeepAwakeImplementation{}
ka.SetService(impl)
testVin := "JH4KA7680RC01"
// Check that trying to delete a non-existing set is okay
err := impl.RemoveKeepAwakeMessage(testVin)
if err != nil{
t.Error(err)
}
client := RedisClientPool().GetFromPool()
found, err := impl.checkRedisForCarEntry(testVin, client)
if err != nil {
t.Error(err)
}
if found{
t.Error("did find expected vin when not supposed to")
}
client.Close()
// Check we can successfully add
err = impl.SendFirstKeepAwakeMessage(testVin)
if err != nil{
t.Error(err)
}
client = RedisClientPool().GetFromPool()
found, err = impl.checkRedisForCarEntry(testVin, client)
if err != nil {
t.Error(err)
}
if !found{
t.Error("did not find expected vin")
}
client.Close()
time.Sleep(time.Second * 2)
vins, err := impl.getKeepAwakeMessages()
if err != nil{
t.Error(err)
}
if len(vins) != 1{
t.Error("len of vins wrong ", len(vins))
}
err = impl.RemoveKeepAwakeMessage(testVin)
if err != nil{
t.Error(err)
}
vins, err = impl.getKeepAwakeMessages()
if err != nil{
t.Error(err)
}
if len(vins) != 0{
t.Error("len of vins wrong ", len(vins))
}
}

View File

@@ -0,0 +1,32 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/mongo"
)
var (
clientOnce sync.Once
client mongo.Client
)
// GetMongoClient returns singleton instance of mongo client
func GetMongoClient() (mongo.Client, error) {
var err error
clientOnce.Do(func() {
client, err = initMongoClient()
})
return client, err
}
func initMongoClient() (mongo.Client, error) {
var err error
if client == nil {
client, err = mongo.NewClient(mongo.StandardDB)
}
return client, err
}

View File

@@ -0,0 +1,28 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/redis"
)
var (
clientPoolOnce sync.Once
clientPool redis.ClientPoolInterface
)
func RedisClientPool() redis.ClientPoolInterface {
clientPoolOnce.Do(func() {
if clientPool != nil {
return
}
clientPool = redis.NewClientPool()
})
return clientPool
}
func SetRedisClientPool(cp redis.ClientPoolInterface) {
clientPool = cp
}

View File

@@ -0,0 +1,28 @@
package services
import (
"sync"
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
)
var (
sapService vconfig.SAPServiceInterface
sapOnce sync.Once
)
func GetSapService() vconfig.SAPServiceInterface {
sapOnce.Do(func() {
if sapService != nil {
return
}
sapService = vconfig.NewSAPService()
})
return sapService
}
// SetSapService is supposed t be used for testing.
func SetSapService(sap vconfig.SAPServiceInterface) {
sapService = sap
}

View File

@@ -0,0 +1,43 @@
package services
import (
"fmt"
"sync"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var smsClient sms.SMSServiceClient
var smsClientOnce sync.Once
func newSmsClient() {
logger.Info().Msg("Init SMS client")
target := fmt.Sprintf("%s:%s",
envtool.GetEnv("SMS_HOST", "sms"),
envtool.GetEnv("SMS_PORT", "8077"))
c, err := grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
logger.Error().Err(err).Send()
}
smsClient = sms.NewSMSServiceClient(c)
}
func GetSMSClient() sms.SMSServiceClient {
smsClientOnce.Do(func() {
if smsClient != nil {
return
}
newSmsClient()
})
return smsClient
}
func SetSmsClient(c sms.SMSServiceClient) {
smsClient = c
}

25
services/depot/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
ARG BASE_IMAGE=cloud_base_go
FROM ${BASE_IMAGE} as builder-go
WORKDIR /build/depot
COPY ./depot/go.mod ./depot/go.sum ./
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
&& go mod download
COPY ./depot ./
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
&& go build -tags musl
FROM alpine:3.17
RUN apk add --no-cache librdkafka --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
&& apk add --no-cache ca-certificates
COPY ./modules_go/logger/log_config .
COPY --from=builder-go /build/depot/depot .
ENV LOG_CONFIG=log_config
EXPOSE 8077
CMD ./depot

View File

@@ -0,0 +1,81 @@
package controllers
import (
"time"
"github.com/pkg/errors"
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/health"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
var mismatchTypeError = errors.New("mismatch type error")
func HealthCheck() {
redis := health.NewRedisHealth(services.RedisClientPool())
server := health.HealthCheckServer{}
err := server.Serve([]health.Config{
{
Name: "db",
Check: health.NewPostgresCheck(services.GetDB().GetDBClient().GetConn()),
Timeout: time.Second * 1,
},
{
Name: "redis",
Check: redis.Check,
Timeout: time.Second * 1,
Info: redis.RedisStatus,
},
{
Name: "mongodb",
Check: health.NewMongoDBCheck(getMongoClient),
Timeout: time.Second * 1,
},
{
Name: "kafka",
Check: health.NewKafkaMultiCheck(getKafkaConsumer),
Timeout: time.Second * 1,
Vital: true,
},
})
if err != nil {
logger.Error().Err(err).Send()
}
}
func getMongoClient() (health.MongoConnCheckInterface, error) {
client, err := services.GetMongoClient()
if err != nil {
return nil, err
}
conn, ok := client.(health.MongoConnCheckInterface)
if !ok {
return nil, errors.WithStack(mismatchTypeError)
}
return conn, nil
}
func getKafkaConsumer() ([]health.KafkaConnCheckInterface, error) {
var connections []health.KafkaConnCheckInterface
client, oldClient, err := services.GetKafkaConsumer()
if err != nil {
return connections, err
}
conn, ok := client.(health.KafkaConnCheckInterface)
if !ok {
return nil, errors.WithStack(mismatchTypeError)
}
connections = append(connections, conn)
oldConn, ok := oldClient.(health.KafkaConnCheckInterface)
if !ok {
return connections, errors.WithStack(mismatchTypeError)
}
connections = append(connections, oldConn)
return connections, nil
}

109
services/depot/go.mod Normal file
View File

@@ -0,0 +1,109 @@
module github.com/fiskerinc/cloud-services/services/depot
go 1.25
toolchain go1.25.0
require (
github.com/fiskerinc/cloud-services/pkg v0.0.0-00010101000000-000000000000
github.com/fiskerinc/cloud-services/pkg/can-go v0.0.0-00010101000000-000000000000
github.com/go-pg/pg/v10 v10.11.1
github.com/gomodule/redigo v1.8.9
github.com/pkg/errors v0.9.1
github.com/sony/gobreaker v0.5.0
go.mongodb.org/mongo-driver v1.14.0
google.golang.org/grpc v1.67.3
google.golang.org/protobuf v1.36.1
)
require (
github.com/DataDog/appsec-internal-go v1.4.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
github.com/DataDog/go-libddwaf/v2 v2.2.3 // indirect
github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect
github.com/DataDog/sketches-go v1.4.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.5.2 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/fiskerinc/cloud-services/pkg/can-go v0.0.0-00010101000000-000000000000 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/jinzhu/copier v0.3.5 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.25.0 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/rs/zerolog v1.29.1 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/twmb/franz-go v1.20.6 // indirect
github.com/twmb/franz-go/pkg/kadm v1.17.2 // indirect
github.com/twmb/franz-go/pkg/kmsg v1.12.0 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.uber.org/atomic v1.11.0 // indirect
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 // indirect
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect
mellium.im/sasl v0.3.1 // indirect
)
replace (
github.com/fiskerinc/cloud-services/pkg => ../../pkg
github.com/fiskerinc/cloud-services/pkg/can-go => ../../pkg/can-go
)

481
services/depot/go.sum Normal file
View File

@@ -0,0 +1,481 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/appsec-internal-go v1.4.0 h1:KFI8ElxkJOgpw+cUm9TXK/jh5EZvRaWM07sXlxGg9Ck=
github.com/DataDog/appsec-internal-go v1.4.0/go.mod h1:ONW8aV6R7Thgb4g0bB9ZQCm+oRgyz5eWiW7XoQ19wIc=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ=
github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8=
github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q=
github.com/DataDog/go-libddwaf/v2 v2.2.3 h1:LpKE8AYhVrEhlmlw6FGD41udtDf7zW/aMdLNbCXpegQ=
github.com/DataDog/go-libddwaf/v2 v2.2.3/go.mod h1:8nX0SYJMB62+fbwYmx5J7zuCGEjiC/RxAo3+AuYJuFE=
github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I=
github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0=
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/DataDog/sketches-go v1.4.2 h1:gppNudE9d19cQ98RYABOetxIhpTCl4m7CnbRZjvVA/o=
github.com/DataDog/sketches-go v1.4.2/go.mod h1:xJIXldczJyyjnbDop7ZZcLxJdV3+7Kra7H1KMgpgkLk=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM=
github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 h1:m3Ayfs5OcAlIMEdLIQKubBsVLGee4YMUr14+d1256WE=
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7/go.mod h1:QIAMbrwsnQZ2ES3G26RubSrDB5SPyzsp9Hts5NJdTrI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts=
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8=
github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg=
github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0AA2Ibj+nNo4ek=
github.com/docker/docker v23.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/ebitengine/purego v0.5.2 h1:r2MQEtkGzZ4LRtFZVAg5bjYKnUbxxloaeuGxH0t7qfs=
github.com/ebitengine/purego v0.5.2/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-pg/pg/v10 v10.11.1 h1:vYwbFpqoMpTDphnzIPshPPepdy3VpzD8qo29OFKp4vo=
github.com/go-pg/pg/v10 v10.11.1/go.mod h1:ExJWndhDNNftBdw1Ow83xqpSf4WMSJK8urmXD5VXS1I=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redismock/v9 v9.2.0 h1:ZrMYQeKPECZPjOj5u9eyOjg8Nnb0BS9lkVIZ6IpsKLw=
github.com/go-redis/redismock/v9 v9.2.0/go.mod h1:18KHfGDK4Y6c2R0H38EUGWAdc7ZQS9gfYxc94k7rWT0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA=
github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg=
github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8=
github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/twmb/franz-go v1.20.6 h1:TpQTt4QcixJ1cHEmQGPOERvTzo99s8jAutmS7rbSD6w=
github.com/twmb/franz-go v1.20.6/go.mod h1:u+FzH2sInp7b9HNVv2cZN8AxdXy6y/AQ1Bkptu4c0FM=
github.com/twmb/franz-go/pkg/kadm v1.17.2 h1:g5f1sAxnTkYC6G96pV5u715HWhxd66hWaDZUAQ8xHY8=
github.com/twmb/franz-go/pkg/kadm v1.17.2/go.mod h1:ST55zUB+sUS+0y+GcKY/Tf1XxgVilaFpB9I19UubLmU=
github.com/twmb/franz-go/pkg/kmsg v1.12.0 h1:CbatD7ers1KzDNgJqPbKOq0Bz/WLBdsTH75wgzeVaPc=
github.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/intern v0.0.0-20230525184215-6c62f75575cb h1:ae7kzL5Cfdmcecbh22ll7lYP3iuUdnfnhiPcSaDgH/8=
go4.org/intern v0.0.0-20230525184215-6c62f75575cb/go.mod h1:Ycrt6raEcnF5FTsLiLKkhBTO6DPX3RCUCUVnks3gFJU=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 h1:Sqkq62MxQW/RD+sgZsQuUdHWHyXI4JS5x0lxlxrv2Hk=
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1/go.mod h1:6aArYrAHjnuaofJ3lKuSRQbhrBx1LcSpiEYCIScJE5Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ=
honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM=
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=

View File

@@ -0,0 +1,118 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/common"
fv "github.com/fiskerinc/cloud-services/pkg/flashpackversion"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
// get vehicle settings
func getSettings(db *services.DB, vin string) ([]common.CarSetting, error) {
settings, err := db.GetCars().GetVehicleSpecificSettings(&common.CarToDriver{
VIN: vin,
})
if err != nil {
return nil, errors.WithStack(err)
}
osVersionSetting, err := getOSVersion(vin)
if err == nil && osVersionSetting != nil {
settings = append(settings, *osVersionSetting)
} else {
logger.Error().Msgf("failed to get os version %v", err)
}
flashpackNumber, err := getFlashpackNumber(vin)
if err != nil {
return nil, errors.WithStack(err)
}
if flashpackNumber != nil {
settings = append(settings, *flashpackNumber)
} else {
logger.Warn().Msgf("no flashpack number yet for vin %s because not enough ECUs have been updated", vin)
}
certNeedsRenewal, err := services.GetCertService().CheckCertificateNeedsRenewal(vin, common.CertICC)
if err != nil {
logger.Error().Msgf("failed to check for certificate renewal %v", err)
} else if certNeedsRenewal {
logger.Debug().Msg("ICC cert for vin " + vin + " is out of date")
settings = append(settings, common.CarSetting{
Name: "certificates",
Value: "expired",
Type: "string",
})
}
clearSettings(settings)
return settings, nil
}
// Get the OS version given a vin
func getOSVersion(vin string) (*common.CarSetting, error) {
sumsVersion, err := getSUMS(vin)
if err != nil {
return nil, err
}
sumsDB := services.GetDB().GetUpdateManifestSUMSVersions()
sums, err := sumsDB.Select(sumsVersion)
if err != nil {
logger.Error().Msgf("can not load SUMS, %v", err)
return nil, err
}
cs := common.CarSetting{
Name: "os_version",
Value: sums.OSVersion,
Type: "string",
}
cs.CreatedAt = sums.CreatedAt
cs.UpdatedAt = sums.UpdatedAt
return &cs, nil
}
// Get the SUMS version from update manifest for a given vin
func getSUMS(vin string) (sumsVersion string, err error) {
carsDB := services.GetDB().GetCars()
car, err := carsDB.SelectByVIN(vin)
if err != nil {
logger.Err(err).Msgf("failed in getSUMS(vin string) for car %s to fetch car", vin)
return
}
return car.SUMSVersion, err
}
// Get the flash pack number
func getFlashpackNumber(vin string) (*common.CarSetting, error) {
cars := services.GetDB().GetCars()
car, err := cars.SelectByVIN(vin)
if err != nil {
return nil, err
}
flashpackNumber, err := fv.FindCurrentFlashpackVersionForCar(cars, *car)
if err != nil {
return nil, err
}
if flashpackNumber != "" {
cs := &common.CarSetting{
Name: "flashpack_number",
Value: flashpackNumber,
Type: "string",
}
return cs, nil
}
return nil, nil
}

View File

@@ -0,0 +1,36 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
)
func HMIDel(db *services.DB, id string) error {
client := services.RedisClientPool().GetFromPool()
defer client.Close()
logger.Info().Msgf("Remove HMI session in Redis for %s", id)
err := removeHMISession(client, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
err = removeHMISessionID(client, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
return nil
}
func removeHMISession(client redis.Client, id string) error {
_, err := client.Execute("SREM", redis.HMISessionsKey(), id)
return err
}
func removeHMISessionID(client redis.Client, id string) error {
return client.Delete(redis.HMISessionKey(id))
}

View File

@@ -0,0 +1,20 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestHMIDel(t *testing.T) {
setupRedisMock()
setupDBMock()
id := "FISKER123"
err := handlers.HMIDel(mockDB, id)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestHMIDel", nil, err)
}
}

View File

@@ -0,0 +1,99 @@
package handlers
import (
"encoding/json"
"fmt"
"time"
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/pkg/errors"
)
func HMIInit(db *services.DB, id string, data []byte) error {
fmt.Printf("HMIInit()")
client := services.RedisClientPool().GetFromPool()
defer client.Close()
var sessionPayload common.HMISessionData
err := json.Unmarshal(data, &sessionPayload)
if err != nil {
return errors.WithStack(err)
}
logger.Info().Msgf("Initialize HMI session in Redis for %s with ID %s", id, sessionPayload.SessionID)
err = addHMISession(client, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
err = addHMISessionID(client, id, sessionPayload.SessionID)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
if sessionPayload.Salt != "" {
err = addHMISaltID(client, id, sessionPayload.Salt)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
}
err = sendSettingsHMI(client, db, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
return err
}
return nil
}
func addHMISession(client redis.Client, id string) error {
_, err := client.Execute("SADD", redis.HMISessionsKey(), id)
return err
}
func addHMISessionID(client redis.Client, id string, sessionID string) error {
_, err := client.Execute("SET", redis.HMISessionKey(id), sessionID)
return err
}
func addHMISaltID(client redis.Client, id string, salt string) error {
_, err := client.Execute("SET", redis.HMISaltKey(id), salt)
return err
}
func sendSettingsHMI(client redis.Client, db *services.DB, vin string) error {
settings, err := getSettings(db, vin)
if err != nil {
return err
}
if len(settings) == 0 {
logger.Info().Msgf("no settings to send for car %s", vin)
return nil
}
return client.SafePublishMessage(common.HMI.Key(vin),
common.Message{
Handler: "car_settings",
Data: settings,
},
)
}
func clearSettings(settings []common.CarSetting) {
cTime := time.Now()
for i, s := range settings {
s.CreatedAt = nil
if s.UpdatedAt == nil {
s.UpdatedAt = &cTime
}
settings[i] = s
}
}

View File

@@ -0,0 +1,30 @@
package handlers_test
import (
"encoding/json"
"testing"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestHMIInit(t *testing.T) {
setupRedisMock()
setupDBMock()
id := "FISKER123"
sessionData := common.HMISessionData{
SessionID: "XXXXX",
}
data, err := json.Marshal(sessionData)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestHMIInit", nil, err)
}
err = handlers.HMIInit(mockDB, id, data)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestHMIInit", nil, err)
}
}

View File

@@ -0,0 +1,25 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
)
func MobileDel(db *services.DB, id string) error {
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err := removeMobileSession(client, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
return nil
}
func removeMobileSession(client redis.Client, id string) error {
_, err := client.Execute("SREM", redis.MobileSessionsKey(), id)
return err
}

View File

@@ -0,0 +1,20 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestMobileDel(t *testing.T) {
setupRedisMock()
setupDBMock()
id := "VALID-COGNITO-ID-1"
err := handlers.MobileDel(mockDB, id)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestMobileDel", nil, err)
}
}

View File

@@ -0,0 +1,60 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/redis"
)
func MobileInit(db *services.DB, id string) error {
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err := addMobileSession(client, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
carDrivers, err := db.GetCars().GetCarsForDriver(id)
if err != nil {
logger.Error().Msgf("can not load cars for driver %v", err)
return err
}
for _, carDriver := range carDrivers {
sendSettingsMobile(client, db, carDriver.VIN, id)
// If we fail to send the digital twin, it is not a major problem
sendFullDigitalTwinToMobile(carDriver.VIN, id)
}
return nil
}
func addMobileSession(client redis.Client, id string) error {
_, err := client.Execute("SADD", redis.MobileSessionsKey(), id)
return err
}
func sendSettingsMobile(client redis.Client, db *services.DB, vin, driverID string) error {
settings, err := getSettings(db, vin)
if err != nil {
return err
}
return client.SafePublishMessage(common.Mobile.Key(driverID),
common.Message{
Handler: "car_settings",
Data: settings,
},
)
}
func sendFullDigitalTwinToMobile(vin string, driverID string) (err error) {
err = services.GetSendDigitalTwin().SendToDriver(vin, driverID)
if err != nil {
logger.Err(err).Str("VIN", vin).Str("Driver ID", driverID).Msg("Failed to Send Full Digital Twin to Mobile Connection on INIT")
}
return
}

View File

@@ -0,0 +1,20 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestMobileInit(t *testing.T) {
setupRedisMock()
setupDBMock()
id := "VALID-COGNITO-ID-1"
err := handlers.MobileInit(mockDB, id)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestMobileInit", nil, err)
}
}

View File

@@ -0,0 +1,51 @@
package handlers_test
import (
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/go-pg/pg/v10/orm"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
)
var mockRedis *redis.Connection
var mockDB *services.DB
func setupRedisMock() {
mockRedis := tester.NewRedisMock()
services.SetRedisClientPool(tester.NewMockClientPool(mockRedis))
}
func setupDBMock() {
db := services.DB{}
db.SetCars(&mocks.MockCars{
SelectResponse: &common.Car{VIN: "FISKER123", ICCID: "1111111111111111111F"},
SelectCarSettings: []common.CarSetting{},
SelectCarECUs: []common.CarECU{
{
VIN: "FISKER123",
ECU: "ADAS",
SupplierSWVersion: "ADASVersion",
},
{
VIN: "FISKER123",
ECU: "ACUN",
SupplierSWVersion: "ACUNVersion",
},
{
VIN: "FISKER123",
ECU: "BCM",
SupplierSWVersion: "BCMVersion",
},
},
})
db.SetCarVersionsLog(&mocks.MockCarVersionsLog{MockLogVersionChange: func(log *common.CarVersionLogs) (orm.Result, error) {
return nil, nil
}})
mockDB = &db
services.SetDB(&db)
}

View File

@@ -0,0 +1,24 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/redis"
)
func TRexDel(id string) error {
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err := removeTRexSession(client, id)
if err != nil {
return err
}
return nil
}
func removeTRexSession(client redis.Client, id string) error {
_, err := client.Execute("SREM", redis.CarSessionsKey(), id)
return err
}

View File

@@ -0,0 +1,20 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
)
func TestTrexDel(t *testing.T) {
setupRedisMock()
setupDBMock()
id := "FISKER123"
err := handlers.TRexDel(id)
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestTrexDel", nil, err)
}
}

View File

@@ -0,0 +1,336 @@
package handlers
import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/cache"
"github.com/fiskerinc/cloud-services/pkg/carcommand"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/dbc/state"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/mongo"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/userconsent"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
"github.com/fiskerinc/cloud-services/pkg/utils"
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
"github.com/go-pg/pg/v10"
redigo "github.com/gomodule/redigo/redis"
"github.com/pkg/errors"
mon "go.mongodb.org/mongo-driver/mongo"
)
var errInvalidICCID = errors.New("invalid iccid submitted")
var errBlocked = errors.New("unable to connect: car is blocked")
var logLevel = envtool.GetEnv("TREX_LOG_LEVEL", common.CriticalLabel)
var wakeUpVINS = envtool.GetEnv("WAKE_UP_VINS", "VCF1EBU2XPG001140")
var defaultFleet = envtool.GetEnv("DEFAULT_FLEET", "Default-Ocean")
// id is the vehicles VIN
func TRexInit(id string, vMap map[string]string) error {
carsDB := services.GetDB().GetCars()
blocked, err := insertIfNotExist(id, carsDB)
if err != nil {
return err
}
if blocked {
return errBlocked
}
if strings.Contains(wakeUpVINS, id) {
go wakeup(id)
}
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err = addTRexSession(client, id)
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
}
m, err := services.GetMongoClient()
if err != nil {
logger.Warn().Str("id", id).Err(err).Send()
} else {
msg, err := retrieveTRexSettings(client, m, id)
if err != nil {
return err
}
// Log the config that will be sent to TREX
config, err := json.Marshal(msg)
if err != nil {
return err
}
logger.Info().Msgf("TREX Config sent for vin %s: \n%s", id, config)
err = sendTRexSettings(client, id, msg)
if err != nil {
return err
}
}
err = setTRexVersion(client, id, vMap["version"])
if err != nil {
return err
}
err = setDBCVersion(client, id, vMap["dbc_version"])
if err != nil {
return err
}
err = setTRexIP(client, id, vMap["ip"])
if err != nil {
return err
}
err = addICCID(carsDB, id, vMap["iccid"])
if err != nil {
logger.Warn().Err(err).Str("VIN", id).Str("ICCID", vMap["iccid"]).Msg("failed to add iccid to car")
}
uMsg, err := getUserConsentData(id)
if err != nil {
return err
}
err = sendTRexUserConsentData(client, id, uMsg)
if err != nil {
return err
}
err = checkAndRenewCertificate(id)
if err != nil {
return err
}
return nil
}
func checkAndRenewCertificate(id string) error {
logger.Debug().Msg("on trex init: checking TBOX cert for vin " + id)
cs := services.GetCertService()
certNeedsRenewal, err := cs.CheckCertificateNeedsRenewal(id, common.CertTBOX)
if err != nil {
return err
} else if certNeedsRenewal {
logger.Debug().Msg("TBOX cert for vin " + id + " is out of date")
cert, err := cs.RenewCertificate(id, common.CertTBOX)
if err != nil {
return err
}
client := services.RedisClientPool().GetFromPool()
defer client.Close()
err = client.SafeQueueMessage(
common.TRex.Key(id),
common.Message{
Handler: "update_cert",
Data: common.UpdateCert{
SSLCertBase64: cert.PublicKey,
},
},
)
if err != nil {
return err
}
}
logger.Debug().Msg("TBOX cert for vin " + id + " is not out of date")
return nil
}
func getUserConsentData(id string) ([]common.UserConsentDataTrexMsg, error) {
var ucdToSend = []common.UserConsentDataTrexMsg{}
resp, err := userconsent.GetUserConsentService().UserConsentByVehicleNumber(id)
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("call to user-consent/byVehicleNumber endpoint returned " + resp.Status)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.WithStack(err)
}
ucdResult := []common.UserConsentDataResponse{}
err = json.Unmarshal(respBody, &ucdResult)
if err != nil {
return nil, errors.WithStack(err)
}
if len(ucdResult) < 1 {
return ucdToSend, nil
}
// only send to TREX the consents that have the userID of the first consent returned
var userId = ucdResult[0].UserID
for _, ucd := range ucdResult {
if ucd.UserID == userId {
ucdToSend = append(ucdToSend, common.BuildUserConsentTrexMessage(ucd))
}
}
return ucdToSend, nil
}
func sendTRexUserConsentData(r redis.Client, id string, msg []common.UserConsentDataTrexMsg) error {
if len(msg) > 0 {
return r.SafePublishMessage(
common.TRex.Key(id),
common.Message{
Handler: "consent",
Data: msg,
},
)
}
return nil
}
func insertIfNotExist(id string, carsDB queries.CarsInterface) (bool, error) {
car, err := carsDB.SelectByVIN(id)
if err == nil && car != nil {
return car.Blocked, nil
}
if errors.Is(err, pg.ErrNoRows) {
err = nil
c, err := utils.ParseVIN(id)
if err != nil {
return false, errors.WithStack(err)
}
_, err = carsDB.Insert(c)
if err != nil {
return false, errors.WithStack(err)
}
client, err := services.GetMongoClient()
if err != nil {
return false, errors.WithStack(err)
}
v := &mongo.Vehicle{
VIN: id,
CANBus: common.CANBus{Enabled: true, DataLogger: true, DTCEnabled: elptr.ElPtr(false)},
LogLevel: common.UnmarshalLogLevelString(logLevel),
}
err = client.GetVehicles().AddVehicle(v)
if mon.IsDuplicateKeyError(err) {
err = client.GetVehicles().UpdateVehicle(v)
}
if err != nil {
return false, errors.WithStack(err)
}
err = client.GetFleets().AddVehiclesToFleet(defaultFleet, []string{id})
if err != nil {
return false, errors.WithStack(err)
}
return false, nil
}
return false, errors.WithStack(err)
}
func addICCID(db queries.CarsInterface, id, iccid string) error {
// Going to only update where an iccid is valid
if len(iccid) != 20 {
return errInvalidICCID
}
car := &common.Car{ICCID: iccid, VIN: id}
if ct, err := db.UpdateICCID(car); err != nil {
return errors.Wrapf(err, "failed to update car's iccid %s", id)
} else if ct.RowsAffected() == 1 {
logger.Info().Msgf("car %s has been updated with iccid %s", id, iccid)
}
return nil
}
func addTRexSession(client redis.Client, id string) error {
_, err := client.Execute("SADD", redis.CarSessionsKey(), id)
return err
}
func retrieveTRexSettings(r redis.Client, m mongo.Client, id string) (*common.TRexConfigResponse, error) {
return cache.RetrieveVehicleConfig(r, m, id)
}
func sendTRexSettings(r redis.Client, id string, msg *common.TRexConfigResponse) error {
err := r.SafePublishMessage(
common.TRex.Key(id),
common.Message{
Handler: "config",
Data: msg,
},
)
return err
}
func setTRexIP(r redis.Client, vin, ip string) error {
elms := strings.Split(ip, ":")
return r.SetObjectField(redis.CarStateHashKey(vin), state.TREX_IP, elms[0])
}
func setDBCVersion(r redis.Client, vin, version string) error {
return setVersion(r, vin, version, state.DBC_VERSION, common.DBCVersionSource)
}
func setTRexVersion(r redis.Client, vin, version string) error {
return setVersion(r, vin, version, state.TREX_VERSION, common.TREXVersionSource)
}
func setVersion(r redis.Client, vin, version, redisField string, versionSource common.VersionSource) error {
v, err := r.GetObjectField(redis.CarStateHashKey(vin), redisField)
if err != nil && !errors.Is(err, redigo.ErrNil) {
return errors.WithStack(err)
}
if v == version {
return nil
}
db := services.GetDB().GetCarVersionsLog()
err = r.SetObjectField(redis.CarStateHashKey(vin), redisField, version)
if err != nil {
return errors.WithStack(err)
}
_, err = db.LogVersionChange(&common.CarVersionLogs{
VIN: vin,
VersionSource: versionSource,
Version: version,
})
if err != nil {
return errors.WithStack(err)
}
return nil
}
func wakeup(vin string) {
wake := carcommand.NewCarWakeUp(services.GetDB().GetCars(), services.GetSMSClient())
err := wake.WakeUp(vin, false)
loggerdataresp.BadDataError(err)
}

View File

@@ -0,0 +1,78 @@
package handlers_test
import (
"testing"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/mongo"
"github.com/fiskerinc/cloud-services/pkg/redis"
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
"github.com/fiskerinc/cloud-services/pkg/testhelper"
"github.com/fiskerinc/cloud-services/pkg/userconsent"
)
func TestTrexInit(t *testing.T) {
rMock := tester.MockRedis{}
rMock.Reset()
mockUserConsent := userconsent.UserConsentServiceMock{}
userconsent.SetUserConsentService(&mockUserConsent)
mockCert := CertServiceMock{}
services.SetCertService(&mockCert)
services.SetRedisClientPool(tester.NewMockClientPool(&rMock))
id := "FISKER123"
setupDBMock()
services.SetMongoClient(mongo.NewMockClient())
mockCertificates := mocks.MockCertificates{}
mockCertificates.MockCertificate = &common.Certificate{
PublicKey: "test",
SerialNumber: "",
Type: common.CertTBOX,
}
services.GetDB().SetCertificates(&mockCertificates)
mocksms := sms.NewSMSMockSuccess()
services.SetSmsClient(&mocksms)
err := handlers.TRexInit(
id,
map[string]string{"version": "1.2.3", "iccid": "123456789123456789123456789", "ip": "172.20.0.17:49850"})
if err != nil {
t.Errorf(testhelper.TestErrorTemplate, "TestTRexInit", nil, err)
}
rez := rMock.SetValues[redis.CarStateHashKey(id)]
if rez.Value.(string) != `{"ip":"172.20.0.17","trex_version":"1.2.3"}` {
t.Errorf(testhelper.TestErrorTemplate, "TestTRexInit_wrongVersion", `{"ip":"172.20.0.17","trex_version":"1.2.3"}`, rez.Value)
}
rez = rMock.SetValues[redis.CarConfigKey(id)]
if rez.Value.(string) != `{"canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"log_level":"trace","log":{"matches":[{"channel":"cmd","level":"trace"}]}}` {
t.Errorf(testhelper.TestErrorTemplate, "TestTRexInit_wrongVersion", `{"canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"log_level":"trace"}`, rez.Value)
}
}
type CertServiceMock struct{}
func (csm *CertServiceMock) CheckCertificateNeedsRenewal(vin string, certType string) (bool, error) {
return certType == common.CertTBOX, nil
}
func (csm *CertServiceMock) RenewCertificate(vin string, certType string) (*common.Certificate, error) {
cert := common.Certificate{
PublicKey: "test",
SerialNumber: "",
Type: common.CertTBOX,
}
return &cert, nil
}

31
services/depot/main.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"github.com/fiskerinc/cloud-services/services/depot/controllers"
"github.com/fiskerinc/cloud-services/services/depot/server"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/tracer"
"github.com/fiskerinc/cloud-services/pkg/utils/app"
)
func init() {
app.Setup("depot", cleanup)
}
func main() {
defer cleanup()
tracer.Start()
defer tracer.Stop()
go controllers.HealthCheck()
go server.StartConsumer(kafka.DepotServiceGRPCKafka, kafka.DepotService)
select {}
}
func cleanup() {
logger.Close()
}

View File

@@ -0,0 +1,7 @@
package server
import (
"github.com/pkg/errors"
)
var ErrInvalidDevice = errors.New("invalid device associated to message")

View File

@@ -0,0 +1,158 @@
package server
import (
"encoding/json"
"github.com/fiskerinc/cloud-services/services/depot/handlers"
"github.com/fiskerinc/cloud-services/services/depot/services"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
"google.golang.org/protobuf/proto"
)
// StartConsumer runs consumer and puts events into a channel for router
func StartConsumer(topic, oldTopic string) {
defer func() {
if err := recover(); err != nil {
logger.Error().Msgf("PanicConsumer %v", err)
}
}()
eventsJSON := make(chan common.EventRawJSON)
events := make(chan *kafka.Message)
go routeEvents(events)
go routeOldEvents(eventsJSON)
logger.Info().Msgf("consumer initialized for topic: %v", topic)
consumer, oldConsumer, err := services.GetKafkaConsumer()
if err != nil {
panic(err)
}
go func() {
err = oldConsumer.ConsumeToChannelJson([]string{oldTopic}, eventsJSON)
loggerdataresp.BadDataError(err, loggerdataresp.EofErrorCheck)
}()
err = consumer.ConsumeToChannel([]string{topic}, events)
loggerdataresp.BadDataError(err, loggerdataresp.EofErrorCheck)
}
func routeOldEvents(events chan common.EventRawJSON) {
p := services.GetDB()
defer p.Close()
for {
event := <-events
var err error
var dt common.Payload
err = dt.Unmarshal(event.Payload)
device, k := common.ParseDeviceKey(event.Key)
payload := &common.ConsumerPayload{
Handler: dt.Handler,
Data: dt.Data,
}
logger.Debug().Str("id", k).Msgf("source: %s, type: %s, handler: %s", k, device, payload.GetHandler())
switch device {
case common.TRex:
err = routeTRex(k, payload)
case common.HMI:
err = routeHMI(p, k, payload)
case common.Mobile:
err = routeMobile(p, k, payload)
default:
err = ErrInvalidDevice
}
loggerdataresp.BadDataError(err, loggerdataresp.EofErrorCheck)
}
}
func routeEvents(events chan *kafka.Message) {
p := services.GetDB()
defer p.Close()
for {
event := <-events
var err error
payload := &kafka_grpc.GRPC_DepotPayload{}
err = proto.Unmarshal(event.Value, payload)
device, k := common.ParseDeviceKey(string(event.Key))
logger.Debug().Str("id", k).Msgf("source: %s, type: %s, handler: %s", k, device, payload.GetHandler())
switch device {
case common.TRex:
d, _ := common.DepotRouteTRexPayload(payload)
err = routeTRex(k, d)
case common.HMI:
d, _ := common.DepotRouteHMIPayload(payload)
err = routeHMI(p, k, d)
case common.Mobile:
d, _ := common.DepotRouteMobilePayload(payload)
err = routeMobile(p, k, d)
default:
err = ErrInvalidDevice
}
loggerdataresp.BadDataError(err, loggerdataresp.EofErrorCheck)
}
}
func routeTRex(id string, d common.ConsumerPayloadInterface) error {
// route TRex messages
var err error
switch d.GetHandler() {
case "init":
var vMap map[string]string
err = json.Unmarshal(d.GetData(), &vMap)
if err != nil {
return err
}
err = handlers.TRexInit(id, vMap)
case "del":
err = handlers.TRexDel(id)
default:
err = kafka.ErrUnhandledMessage(common.TRex, id, d.GetHandler(), string(d.GetData()))
}
return err
}
func routeHMI(p *services.DB, id string, d common.ConsumerPayloadInterface) error {
// route HMI messages
var err error
switch d.GetHandler() {
case "init":
err = handlers.HMIInit(p, id, d.GetData())
case "del":
err = handlers.HMIDel(p, id)
default:
err = kafka.ErrUnhandledMessage(common.HMI, id, d.GetHandler(), string(d.GetData()))
}
return err
}
func routeMobile(p *services.DB, id string, d common.ConsumerPayloadInterface) error {
// route mobile messages
var err error
switch d.GetHandler() {
case "init":
err = handlers.MobileInit(p, id)
case "del":
err = handlers.MobileDel(p, id)
default:
err = kafka.ErrUnhandledMessage(common.Mobile, id, d.GetHandler(), string(d.GetData()))
}
return err
}

View File

@@ -0,0 +1,113 @@
package services
import (
"bytes"
"encoding/json"
"net/http"
"sync"
"time"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
"github.com/go-pg/pg/v10"
"github.com/pkg/errors"
)
var (
certService CertServiceInterface
certOnce sync.Once
)
func GetCertService() CertServiceInterface {
certOnce.Do(func() {
if certService != nil {
return
}
certService = NewCertService()
})
return certService
}
func SetCertService(cs CertServiceInterface) {
certService = cs
}
func NewCertService() CertServiceInterface {
return &CertService{
certURL: envtool.GetEnv("CERT_URL", "REPLACE_ME"),
certAPIToken: envtool.GetEnv("CERTIFICATE_API_KEY", "REPLACE_ME"),
}
}
type CertServiceInterface interface {
CheckCertificateNeedsRenewal(vin string, certType string) (bool, error)
RenewCertificate(vin string, certType string) (*common.Certificate, error)
}
type CertService struct {
certURL string
certAPIToken string
}
func (cs *CertService) CheckCertificateNeedsRenewal(vin string, certType string) (bool, error) {
logger.Debug().Msg("checking " + certType + " cert for vin " + vin)
cert, err := GetDB().GetCertificates().SelectMostRecent(vin, certType)
if err != nil && !errors.Is(err, pg.ErrNoRows) {
return false, err
}
if cert == nil {
logger.Debug().Msg("no existing " + certType + " cert to renew for vin " + vin)
return false, nil
}
var daysBeforeExp = 183 // six months
switch certType {
case common.CertTBOX:
daysBeforeExp = envtool.GetEnvInt("TBOX_CERT_RENEW_DAYS_BEFORE_EXP", daysBeforeExp)
case common.CertICC:
daysBeforeExp = envtool.GetEnvInt("ICC_CERT_RENEW_DAYS_BEFORE_EXP", daysBeforeExp)
}
logger.Debug().Msg("checking validity of " + certType + " cert with serial number " + cert.SerialNumber + " for vin " + vin)
return cert.IsExpiredOrInvalidAtTime(time.Now(), daysBeforeExp)
}
func (cs *CertService) RenewCertificate(vin string, certType string) (*common.Certificate, error) {
logger.Debug().Msg("renewing " + certType + " cert for vin " + vin)
jsonBytes, err := json.Marshal(common.CertificateRenewRequest{
Type: certType,
CommonName: vin,
})
if err != nil {
return nil, err
}
request, err := http.NewRequest(http.MethodPost, cs.certURL+"renew", bytes.NewReader(jsonBytes))
if err != nil {
return nil, err
}
request.Header.Add("Api-Key", cs.certAPIToken)
resp, err := http.DefaultClient.Do(request)
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("renew " + certType + " certificate returned " + resp.Status + " for vin " + vin)
}
defer resp.Body.Close()
var cert common.Certificate
err = json.NewDecoder(resp.Body).Decode(&cert)
if err != nil {
return nil, err
}
return &cert, err
}

View File

@@ -0,0 +1,170 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db"
q "github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
var (
dbOnce sync.Once
dbInstance *DB
)
type DB struct {
client *db.DBClient
cars q.CarsInterface
carVersionsLog q.CarVersionsLogInterface
certificates q.CertificatesInterface
updateManifest q.UpdateManifestsInterface
updateManifestSUMSVersions q.SUMSVersionsInterface
onceClient sync.Once
onceCars sync.Once
onceCarVersionsLog sync.Once
onceCertificates sync.Once
onceUpdateManifest sync.Once
onceUpdateManifestSUMSVersions sync.Once
}
func GetDB() *DB {
dbOnce.Do(func() {
if dbInstance != nil {
return
}
logger.Info().Msg("init DB instance")
dbInstance = &DB{}
})
return dbInstance
}
func SetDB(db *DB) {
if dbInstance != nil {
dbInstance.Close()
}
dbInstance = db
}
func (d *DB) GetDBClient() *db.DBClient {
d.onceClient.Do(func() {
if d.client != nil {
return
}
logger.Info().Msg("init DBClient instance")
client := &db.DBClient{}
err := client.InitSchema([]interface{}{
(*common.UpdateManifest)(nil),
(*common.CarUpdate)(nil),
(*common.CarToDriver)(nil),
(*common.CarSetting)(nil),
(*common.SUMSVersion)(nil),
})
if err != nil {
logger.Error().Err(err).Send()
}
d.client = client
})
return d.client
}
func (d *DB) SetDBClient(client *db.DBClient) {
if d.client != nil {
d.client.Close()
}
d.client = client
}
func (d *DB) Close() {
if d.client == nil {
return
}
d.client.Close()
}
///----------
func (d *DB) GetCars() q.CarsInterface {
d.onceCars.Do(func() {
if d.cars != nil {
return
}
instance := &q.Cars{}
instance.SetClient(d.GetDBClient())
d.cars = instance
})
return d.cars
}
func (d *DB) SetCars(cars q.CarsInterface) {
d.cars = cars
}
func (d *DB) GetCarVersionsLog() q.CarVersionsLogInterface {
d.onceCarVersionsLog.Do(func() {
if d.carVersionsLog != nil {
return
}
instance := &q.CarVersionsLog{}
instance.SetClient(d.GetDBClient())
d.carVersionsLog = instance
})
return d.carVersionsLog
}
func (d *DB) SetCarVersionsLog(log q.CarVersionsLogInterface) {
d.carVersionsLog = log
}
func (d *DB) GetCertificates() q.CertificatesInterface {
d.onceCertificates.Do(func() {
if d.certificates != nil {
return
}
logger.Debug().Msg("Init Certificates instance")
certificates := &q.Certificates{}
certificates.SetClient(d.GetDBClient())
d.certificates = certificates
})
return d.certificates
}
func (d *DB) SetCertificates(certificates q.CertificatesInterface) {
d.certificates = certificates
}
func (d *DB) GetUpdateManifests() q.UpdateManifestsInterface {
d.onceUpdateManifest.Do(func() {
if d.updateManifest != nil {
return
}
logger.Debug().Msg("Init UpdateManifest instance")
updateManifest := q.NewUpdateManifest(nil)
updateManifest.SetClient(d.GetDBClient())
d.updateManifest = updateManifest
})
return d.updateManifest
}
func (d *DB) SetUpdateManifests(updateManifest q.UpdateManifestsInterface) {
d.updateManifest = updateManifest
}
func (d *DB) GetUpdateManifestSUMSVersions() q.SUMSVersionsInterface {
d.onceUpdateManifestSUMSVersions.Do(func() {
if d.updateManifestSUMSVersions != nil {
return
}
instance := &q.SUMSVersions{}
instance.SetClient(d.GetDBClient())
d.updateManifestSUMSVersions = instance
})
return d.updateManifestSUMSVersions
}
func (d *DB) SetUpdateManifestVersions(umv q.SUMSVersionsInterface) {
d.updateManifestSUMSVersions = umv
}

View File

@@ -0,0 +1,23 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/digitaltwin"
)
// There is no need to recreate the digital twin sender every time we send a digital twin
// so keeping a reference here
var sendDigitalTwin *digitaltwin.SendDigitalTwin
var sendDigitalTwinOnce sync.Once
func GetSendDigitalTwin()(*digitaltwin.SendDigitalTwin){
sendDigitalTwinOnce.Do(func(){
if sendDigitalTwin == nil {
notSureWhyIcantTakeAddress := digitaltwin.NewSendDigitalTwin(RedisClientPool(), GetDB().GetCars())
sendDigitalTwin = &notSureWhyIcantTakeAddress
}
})
return sendDigitalTwin
}

View File

@@ -0,0 +1,36 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
const serviceName = "depot"
const oldServiceName = "old-depot"
var consumer kafka.ConsumerInterface
var oldConsumer kafka.ConsumerInterface
var consumerOnce sync.Once
// GetKafkaConsumer returns singleton instance of kafka consumer
func GetKafkaConsumer() (kafka.ConsumerInterface, kafka.ConsumerInterface, error) {
var err error
consumerOnce.Do(func() {
consumer, err = kafka.NewConsumer(serviceName)
if err != nil {
logger.Error().Err(err).Send()
}
oldConsumer, err = kafka.NewConsumer(oldServiceName)
if err != nil {
logger.Error().Err(err).Send()
}
})
if err != nil {
return consumer, oldConsumer, err
}
return consumer, oldConsumer, nil
}

View File

@@ -0,0 +1,72 @@
package services
import (
"context"
"sync"
"time"
"github.com/fiskerinc/cloud-services/pkg/mongo"
"github.com/pkg/errors"
"github.com/sony/gobreaker"
)
var (
clientlock sync.Mutex
client mongo.Client
cb *gobreaker.CircuitBreaker
)
func init() {
cb = gobreaker.NewCircuitBreaker(
gobreaker.Settings{
Timeout: 10 * time.Second,
Interval: 15 * time.Minute,
},
)
}
// GetMongoClient returns singleton instance of mongo client
func GetMongoClient() (mongo.Client, error) {
err := ping()
if err != nil {
return initMongoClient()
}
return client, nil
}
func initMongoClient() (mongo.Client, error) {
clientlock.Lock()
defer clientlock.Unlock()
var err error
client, err = mongo.NewClient(mongo.StandardDB)
if err != nil {
return nil, err
}
_, _ = cb.Execute(func() (interface{}, error) {
err = ping()
return nil, err
})
return client, errors.WithStack(err)
}
func ping() error {
if client == nil {
return errors.New("client is nil")
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
return client.Ping(ctx)
}
// SetMongoClient is supposed to be used for tests.
func SetMongoClient(cl mongo.Client) {
client = cl
}
// db.dtc_lookup.aggregate({"$match": {"ecuName": "TBOX", "DtcUniqueId": "V2.6.0"}}, {"$unwind": "$dtcData.DtcList"}, {"$match": {"dtcData.DtcList.TroubleCodeHex":"D77F16"}}, {"$project":{"_id": 0, "Obj": "$dtcData.DtcList"}})

View File

@@ -0,0 +1,28 @@
package services
import (
"sync"
"github.com/fiskerinc/cloud-services/pkg/redis"
)
var (
clientPoolOnce sync.Once
clientPool redis.ClientPoolInterface
)
func RedisClientPool() redis.ClientPoolInterface {
clientPoolOnce.Do(func() {
if clientPool != nil {
return
}
clientPool = redis.NewClientPool()
})
return clientPool
}
func SetRedisClientPool(cp redis.ClientPoolInterface) {
clientPool = cp
}

View File

@@ -0,0 +1,42 @@
package services
import (
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
)
var smsClient sms.SMSServiceClient
var smsClientOnce sync.Once
func newSmsClient() {
logger.Info().Msg("Init SMS client")
target := fmt.Sprintf("%s:%s",
envtool.GetEnv("SMS_HOST", "sms"),
envtool.GetEnv("SMS_PORT", "8077"))
c, err := grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
logger.Error().Err(err).Send()
}
smsClient = sms.NewSMSServiceClient(c)
}
func GetSMSClient() sms.SMSServiceClient {
smsClientOnce.Do(func() {
if smsClient != nil {
return
}
newSmsClient()
})
return smsClient
}
func SetSmsClient(c sms.SMSServiceClient) {
smsClient = c
}

View File

@@ -0,0 +1,25 @@
CLICKHOUSE_USER="default"
CLICKHOUSE_PASS=""
CLICKHOUSE_FEATURE_TABLE="feature_table"
CLICKHOUSE_VEHICLE_SIGNAL_TABLE="vehicle_signal"
CLICKHOUSE_HOST="localhost"
KAFKA_HOSTS="localhost:9093"
CLICKHOUSE_MAX_CONNS=5
JETFIRE_VEHICLE_SIGNAL_BATCH_PERIOD_MS=10000
JETFIRE_FEATURE_BATCH_PERIOD_MS=10000
JETFIRE_FEATURE_DOWNSAMPLE_US=1000000
JETFIRE_TRIP_TIMEOUT_MS=300000
JETFIRE_STATE_TIMEOUT_MS=3600000
JETFIRE_FUTURE_THRESHOLD_MS=604800000
JETFIRE_SCHEMA_RESET_PERIOD_MS=3600000
JETFIRE_MAX_BUFFER_ROWS=1000
LOG_LEVEL="debug"

View File

@@ -0,0 +1,32 @@
## Build binaries for event detection using cloud_base_go image
ARG BASE_IMAGE=cloud_base_go
FROM ${BASE_IMAGE} as builder-go
WORKDIR /build/jetfire
COPY ./jetfire/go.mod ./jetfire/go.sum ./
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
&& go mod download
COPY ./jetfire ./
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
&& go build -tags musl
## Build image for event detection, pulling binaries from builder image
FROM alpine:3.17
RUN apk add --no-cache librdkafka --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
&& apk add --no-cache ca-certificates
COPY ./modules_go/logger/log_config .
COPY ./jetfire/default-feature-vars.json .
COPY --from=builder-go /build/jetfire/jetfire .
ENV LOG_LEVEL=log_config
EXPOSE 8077
CMD ./jetfire

View File

@@ -0,0 +1,26 @@
# Jetfire Data Routing Service
Jetfire Service listens to CAN Signals on the vehicle_signals topic on Kafka,
and performs a pivot operation for populating the feature_table on Clickhouse,
and populating the real_time table on Clickhouse.
CAN Signals are batched and inserted into vehicle_signal table without any transform performed on the data.
Feature Table inserts are typically batched at `JETFIRE_FEATURE_BATCH_PERIOD_MS=10000`.
Vehicle Signal Table inserts are typically batched at `JETFIRE_VEHICLE_SIGNAL_BATCH_PERIOD_MS=10000`
The schemas for sink tables are fetched periodically, set to `JETFIRE_SCHEMA_RESET_PERIOD_MS=3600000`
Additionally, a GET request to the `/reset` endpoint on port `8077` will also trigger an immediate schema reset.
For more information about Jetfire service, see https://fiskerinc.atlassian.net/wiki/spaces/COM/pages/1401487522/Jetfire+Service
## Usage
Copy `./.env.tenmplate` to `./.env` file
Secrets in the .env file will need to be filled in manually.
Running jetfire locally
```
source set_envs.sh
go run main.go
```

View File

@@ -0,0 +1,54 @@
package controllers
import (
"github.com/fiskerinc/cloud-services/services/jetfire/services"
"time"
"github.com/fiskerinc/cloud-services/pkg/health"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/pkg/errors"
)
var mismatchTypeError = errors.New("mismatch type error")
func HealthCheck() {
server := health.HealthCheckServer{}
err := server.Serve([]health.Config{
{
Name: "clickhouse",
Check: health.NewClickhouseCheck(getClickhouseConsumer),
Timeout: time.Second * 1,
},
})
if err != nil {
logger.Error().Err(err).Send()
}
}
func getKafkaConsumer() (health.KafkaConnCheckInterface, error) {
client, err := services.GetKafkaConsumer()
if err != nil {
return nil, err
}
conn, ok := client.(health.KafkaConnCheckInterface)
if !ok {
return nil, errors.WithStack(mismatchTypeError)
}
return conn, nil
}
func getClickhouseConsumer() (health.ClickhouseConnCheckInterface, error) {
client, err := services.GetClickhouseConnection()
if err != nil {
return nil, err
}
conn, ok := client.(health.ClickhouseConnCheckInterface)
if !ok {
return nil, errors.WithStack(mismatchTypeError)
}
return conn, nil
}

View File

@@ -0,0 +1,65 @@
[
"BCM_DrFrntDoorSts",
"BCM_FrntDrDoorLockSts",
"BCM_TotMilg_ODO",
"BCM_PwrMod",
"BMS_AccueChrgTotAh",
"BMS_AccueDchaTotAh",
"BMS_Bat_Actual_Pack_Capacity",
"BMS_Bat_HVmeasure_Current",
"BMS_Bat_SoC_usable",
"BMS_BattAvrgT",
"BMS_Bat_measure_Energy",
"BMS_PwrBattChrgDchaCrt1",
"BMS_PwrBattChrgDchaCrt2",
"BMS_PwrBattRmngCpSOC",
"BMS_PwrBattSOH",
"BMS_VehChrgDchgMod",
"BMS_Cell_Volt_max",
"BMS_Cell_Volt_min",
"BMS_Bat_Coolant_in",
"BMS_Bat_Coolant_out",
"ECC_OutdT",
"ESP_TotBrkTqReq",
"ESP_VehSpd",
"IBS_StateOfCharge",
"IBS_StateOfHealth",
"IBS_BatteryVoltage",
"IBS_BatteryCurrent",
"IBS_BatteryTemperature",
"IBS_AvgRi",
"IBS_AvailableCapacity",
"ICC_DispVehSpd",
"ICC_FrntWiprCtrl",
"MCU_F_AlrmLamp_FS",
"MCU_F_CrtTq",
"MCU_F_HVActvDchaSts",
"MCU_R_AlrmLamp_FS",
"MCU_R_CrtTq",
"MCU_R_HVActvDchaSts",
"OBC_DCPosRlyCtrlSts",
"VCU_ACChrgShttrSts",
"VCU_APSPerc",
"VCU_BrkPedlSts_GB",
"VCU_BrkSig",
"VCU_ChrgSts",
"VCU_ChrgSts_GB",
"VCU_ChrgSysOperCmd",
"VCU_DCChrgShttrSts",
"VCU_GearSig_GB",
"VCU_VehChrgDchgMod",
"VCU_VehOperMod",
"TBOX_GPSHei",
"TBOX_GPSLati",
"TBOX_GPSLongi"
]

143
services/jetfire/go.mod Normal file
View File

@@ -0,0 +1,143 @@
module github.com/fiskerinc/cloud-services/services/jetfire
go 1.25
toolchain go1.25.0
require (
github.com/ClickHouse/ch-go v0.58.2
github.com/ClickHouse/clickhouse-go/v2 v2.6.0
github.com/fiskerinc/cloud-services/pkg v0.0.0-00010101000000-000000000000
github.com/intel-go/fastjson v0.0.0-20170329170629-f846ae58a1ab
github.com/julienschmidt/httprouter v1.3.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.29.1
github.com/sony/gobreaker v0.5.0
github.com/stretchr/testify v1.10.0
google.golang.org/protobuf v1.36.1
gopkg.in/retry.v1 v1.0.3
)
require (
github.com/DataDog/appsec-internal-go v1.4.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
github.com/DataDog/go-libddwaf/v2 v2.2.3 // indirect
github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect
github.com/DataDog/sketches-go v1.4.2 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dmarkham/enumer v1.5.8 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.5.2 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/fiskerinc/cloud-services/pkg/can-go v0.0.0-00010101000000-000000000000 // indirect
github.com/frankban/quicktest v1.14.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.6.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-pg/pg/v10 v10.11.1 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/jinzhu/copier v0.3.5 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx v1.2.25 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.25.0 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/pascaldekloe/name v1.0.1 // indirect
github.com/paulmach/orb v0.8.0 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f // indirect
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
github.com/swaggo/http-swagger v1.3.3 // indirect
github.com/swaggo/swag v1.8.8 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/twmb/franz-go v1.20.6 // indirect
github.com/twmb/franz-go/pkg/kadm v1.17.2 // indirect
github.com/twmb/franz-go/pkg/kmsg v1.12.0 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect
mellium.im/sasl v0.3.1 // indirect
)
replace (
github.com/fiskerinc/cloud-services/pkg => ../../pkg
github.com/fiskerinc/cloud-services/pkg/can-go => ../../pkg/can-go
)

548
services/jetfire/go.sum Normal file
View File

@@ -0,0 +1,548 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0=
github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw=
github.com/ClickHouse/clickhouse-go/v2 v2.6.0 h1:NmnPY2Cg4hCqS2ZGBep9EWHfQPAco2Vkpwb02VXtWew=
github.com/ClickHouse/clickhouse-go/v2 v2.6.0/go.mod h1:SvXuWqDsiHJE3VAn2+3+nz9W9exOSigyskcs4DAcxJQ=
github.com/DataDog/appsec-internal-go v1.4.0 h1:KFI8ElxkJOgpw+cUm9TXK/jh5EZvRaWM07sXlxGg9Ck=
github.com/DataDog/appsec-internal-go v1.4.0/go.mod h1:ONW8aV6R7Thgb4g0bB9ZQCm+oRgyz5eWiW7XoQ19wIc=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c=
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ=
github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8=
github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q=
github.com/DataDog/go-libddwaf/v2 v2.2.3 h1:LpKE8AYhVrEhlmlw6FGD41udtDf7zW/aMdLNbCXpegQ=
github.com/DataDog/go-libddwaf/v2 v2.2.3/go.mod h1:8nX0SYJMB62+fbwYmx5J7zuCGEjiC/RxAo3+AuYJuFE=
github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I=
github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0=
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/DataDog/sketches-go v1.4.2 h1:gppNudE9d19cQ98RYABOetxIhpTCl4m7CnbRZjvVA/o=
github.com/DataDog/sketches-go v1.4.2/go.mod h1:xJIXldczJyyjnbDop7ZZcLxJdV3+7Kra7H1KMgpgkLk=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM=
github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 h1:m3Ayfs5OcAlIMEdLIQKubBsVLGee4YMUr14+d1256WE=
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7/go.mod h1:QIAMbrwsnQZ2ES3G26RubSrDB5SPyzsp9Hts5NJdTrI=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/confluentinc/confluent-kafka-go v1.9.2 h1:gV/GxhMBUb03tFWkN+7kdhg+zf+QUM+wVkI9zwh770Q=
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts=
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dmarkham/enumer v1.5.8 h1:fIF11F9l5jyD++YYvxcSH5WgHfeaSGPaN/T4kOQ4qEM=
github.com/dmarkham/enumer v1.5.8/go.mod h1:d10o8R3t/gROm2p3BXqTkMt2+HMuxEmWCXzorAruYak=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/ebitengine/purego v0.5.2 h1:r2MQEtkGzZ4LRtFZVAg5bjYKnUbxxloaeuGxH0t7qfs=
github.com/ebitengine/purego v0.5.2/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-pg/pg/v10 v10.11.1 h1:vYwbFpqoMpTDphnzIPshPPepdy3VpzD8qo29OFKp4vo=
github.com/go-pg/pg/v10 v10.11.1/go.mod h1:ExJWndhDNNftBdw1Ow83xqpSf4WMSJK8urmXD5VXS1I=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/intel-go/fastjson v0.0.0-20170329170629-f846ae58a1ab h1:K7WJJ5AnrQV/6tEh0Qqs19KLzvsq5V15f9CifKii6aU=
github.com/intel-go/fastjson v0.0.0-20170329170629-f846ae58a1ab/go.mod h1:xr9Svf97gkxlW+ZDxs47vReKp7m9EUzNhEGOLyBHR+8=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA=
github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=
github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcMb0=
github.com/pascaldekloe/name v1.0.1/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
github.com/paulmach/orb v0.8.0 h1:W5XAt5yNPNnhaMNEf0xNSkBMJ1LzOzdk2MRlB6EN0Vs=
github.com/paulmach/orb v0.8.0/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk=
github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM=
github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg=
github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA=
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc=
github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo=
github.com/swaggo/swag v1.8.8 h1:/GgJmrJ8/c0z4R4hoEPZ5UeEhVGdvsII4JbVDLbR7Xc=
github.com/swaggo/swag v1.8.8/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/twmb/franz-go v1.20.6 h1:TpQTt4QcixJ1cHEmQGPOERvTzo99s8jAutmS7rbSD6w=
github.com/twmb/franz-go v1.20.6/go.mod h1:u+FzH2sInp7b9HNVv2cZN8AxdXy6y/AQ1Bkptu4c0FM=
github.com/twmb/franz-go/pkg/kadm v1.17.2 h1:g5f1sAxnTkYC6G96pV5u715HWhxd66hWaDZUAQ8xHY8=
github.com/twmb/franz-go/pkg/kadm v1.17.2/go.mod h1:ST55zUB+sUS+0y+GcKY/Tf1XxgVilaFpB9I19UubLmU=
github.com/twmb/franz-go/pkg/kmsg v1.12.0 h1:CbatD7ers1KzDNgJqPbKOq0Bz/WLBdsTH75wgzeVaPc=
github.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/intern v0.0.0-20230525184215-6c62f75575cb h1:ae7kzL5Cfdmcecbh22ll7lYP3iuUdnfnhiPcSaDgH/8=
go4.org/intern v0.0.0-20230525184215-6c62f75575cb/go.mod h1:Ycrt6raEcnF5FTsLiLKkhBTO6DPX3RCUCUVnks3gFJU=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 h1:Sqkq62MxQW/RD+sgZsQuUdHWHyXI4JS5x0lxlxrv2Hk=
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1/go.mod h1:6aArYrAHjnuaofJ3lKuSRQbhrBx1LcSpiEYCIScJE5Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs=
gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ=
honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM=
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=

View File

@@ -0,0 +1,97 @@
package handlers
import (
"github.com/fiskerinc/cloud-services/services/jetfire/models"
"github.com/fiskerinc/cloud-services/services/jetfire/services"
"github.com/fiskerinc/cloud-services/services/jetfire/utils"
"time"
"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
)
var (
//downsampling timers and delays
clearCacheTimer = time.Now().Truncate(time.Second)
featureDelay = envtool.GetEnvDuration("JETFIRE_FEATURE_DOWNSAMPLE_US", 1000000) * time.Microsecond
clearCacheDelay = envtool.GetEnvDuration("JETFIRE_STATE_TIMEOUT_MS", 3600000) * time.Millisecond
futureTimeThreshold = envtool.GetEnvDuration("JETFIRE_FUTURE_THRESHOLD_MS", 2*24*60*60*1000) * time.Millisecond
)
// Handles the batch of Signals. returns batchFlag, error
// where batchFlag corresponds to FeatureUpdateFlag for which buffers were updated with this batch
func HandleSignalBatch(batchData []*kafka_grpc.GRPC_CANSignal, vehicleCache *models.VehicleCache, producerChannel chan models.InsertCommand) (uint, error) {
start := time.Now()
batchFlag := uint(0)
//Iterate through received can signals and add to cache
futureWarning := false //only one future warning per batch of data
//sets of pointers to insert into insertion buffers
featureUpdates := make(map[*models.VehicleState]bool)
logger.Debug().Msgf("Processing batch %d signals...", len(batchData))
skipLog := false
for _, signal := range batchData {
signal.Timestamp = utils.FixFloatTimestampScale(signal.Timestamp)
if start.Add(futureTimeThreshold).Before(utils.FloatToTime(signal.Timestamp)) && !futureWarning {
// Throw out any signals that are from the future.
futureWarning = true
logger.Warn().Msgf("ignoring signal(s) from %s from future: %f (currently %f)", signal.Vin, signal.Timestamp, utils.TimeToFloat(start))
continue
}
err := vehicleCache.UpdateSignal(signal, utils.FeatureUpdateFlag)
if err != nil {
logger.Error().Err(err).Send()
//do not continue yet; attempt to add to vehicle signal buffer
}
//Check vehicle state for update
// if signal is not in tracked vars, then skip.
// if VIN is not in vehicle cache, then log and skip
state, ok := vehicleCache.Cache[signal.Vin]
if !ok || state == nil {
if len(*vehicleCache.SignalsSet) > 0 {
_, ok = (*vehicleCache.SignalsSet)[signal.Name]
if ok && !skipLog {
// no vin state but signal update should have created a vin state... log warning
logger.Error().Msgf("unexpected missing VIN state from cache for VIN {%s} and signal {%s}", signal.Vin, signal.Name)
skipLog = true
}
}
continue
}
if state.TimeSincePolled(utils.FeatureUpdateFlag) > featureDelay {
featureUpdates[state] = true
}
}
services.SchemaLock.Lock()
defer services.SchemaLock.Unlock()
//TODO: investigate optimizing selecting VINs that need to be appended to buffer
// iterating through maps is slow!
//batch rows for insertion for feature
for vinState := range featureUpdates {
err := services.GetFeatureBatch().AppendRow(services.GetFeatureVars(), vinState, producerChannel)
if err != nil {
logger.Error().Err(err).Send()
} else {
vinState.SetPollTime(utils.FeatureUpdateFlag)
batchFlag |= utils.FeatureUpdateFlag
}
err = services.GetFeatureLastBatch().AppendRow(services.GetFeatureVars(), vinState, producerChannel)
if err != nil {
logger.Error().Err(err).Send()
}
}
return batchFlag, nil
}

View File

@@ -0,0 +1,21 @@
package handlers
import (
"net/http"
"time"
"github.com/fiskerinc/cloud-services/services/jetfire/services"
"github.com/fiskerinc/cloud-services/pkg/logger"
)
func ResetSchemaDefinitions(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
logger.Info().Msg("resetting tracked variables and schemas")
services.ResetCacheVars()
endTime := time.Now()
logger.Debug().Msgf("reset tracked variables and schemas, time taken: %fms; %d variables",
float64(endTime.UnixMilli()-startTime.UnixMilli()),
len(services.GetFeatureVars()),
)
}

45
services/jetfire/main.go Normal file
View File

@@ -0,0 +1,45 @@
package main
import (
"context"
"github.com/fiskerinc/cloud-services/services/jetfire/controllers"
"github.com/fiskerinc/cloud-services/services/jetfire/server"
"github.com/fiskerinc/cloud-services/services/jetfire/services"
"github.com/fiskerinc/cloud-services/pkg/kafka"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/tracer"
"github.com/fiskerinc/cloud-services/pkg/utils/app"
)
var (
SERVICE_NAME = "jetfire"
)
func init() {
app.Setup(SERVICE_NAME, cleanup)
}
func main() {
defer cleanup()
tracer.Start()
defer tracer.Stop()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go controllers.HealthCheck()
services.ResetCacheVars()
go server.StartHTTPServer() //listen for reset requests
go server.StartConsumer(ctx, kafka.VehicleSignal)
select {}
}
func cleanup() {
logger.Close()
}

Some files were not shown because too many files have changed in this diff Show More