Skip to content
loc-serv.go 119 KiB
Newer Older
 * Copyright (c) 2019  InterDigital Communications, Inc
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
Simon Pastor's avatar
Simon Pastor committed
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sync"
	"time"

	sbi "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-loc-serv/sbi"
	asc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-support-client"
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
Simon Pastor's avatar
Simon Pastor committed
	gisClient "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-gis-engine-client"
	httpLog "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-http-logger"
	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
	met "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-metrics"
	redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis"
	scc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sandbox-ctrl-client"
	smc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client"
	"github.com/gorilla/mux"
)

Kevin Di Lallo's avatar
Kevin Di Lallo committed
const moduleName = "meep-loc-serv"
Kevin Di Lallo's avatar
Kevin Di Lallo committed
const LocServBasePath = "location/v2/"
const locServKey = "loc-serv"
Kevin Di Lallo's avatar
Kevin Di Lallo committed
const defaultMepName = "global"
const defaultScopeOfLocality = "MEC_SYSTEM"
const defaultConsumedLocalOnly = true

const typeZone = "zone"
const typeAccessPoint = "accessPoint"
const typeUser = "user"
const typeZonalSubscription = "zonalsubs"
const typeUserSubscription = "usersubs"
const typeZoneStatusSubscription = "zonestatus"
Simon Pastor's avatar
Simon Pastor committed
const typeDistanceSubscription = "distance"
const typeAreaCircleSubscription = "areacircle"
Simon Pastor's avatar
Simon Pastor committed
const typePeriodicSubscription = "periodic"
const (
	notifZonalPresence = "ZonalPresenceNotification"
	notifZoneStatus    = "ZoneStatusNotification"
Simon Pastor's avatar
Simon Pastor committed
	notifSubscription  = "SubscriptionNotification"
Simon Pastor's avatar
Simon Pastor committed
	queryZoneId  []string
	queryApId    []string
	queryAddress []string
Simon Pastor's avatar
Simon Pastor committed
	userList     *UserList
}

type ApUserData struct {
	queryInterestRealm string
	apList             *AccessPointList
}

Simon Pastor's avatar
Simon Pastor committed
type Pair struct {
	addr1 string
	addr2 string
}

var nextZonalSubscriptionIdAvailable int
var nextUserSubscriptionIdAvailable int
var nextZoneStatusSubscriptionIdAvailable int
Simon Pastor's avatar
Simon Pastor committed
var nextDistanceSubscriptionIdAvailable int
var nextAreaCircleSubscriptionIdAvailable int
Simon Pastor's avatar
Simon Pastor committed
var nextPeriodicSubscriptionIdAvailable int
var zonalSubscriptionEnteringMap = map[int]string{}
var zonalSubscriptionLeavingMap = map[int]string{}
var zonalSubscriptionTransferringMap = map[int]string{}
var zonalSubscriptionMap = map[int]string{}
var userSubscriptionEnteringMap = map[int]string{}
var userSubscriptionLeavingMap = map[int]string{}
var userSubscriptionTransferringMap = map[int]string{}
var userSubscriptionMap = map[int]string{}
var zoneStatusSubscriptionMap = map[int]*ZoneStatusCheck{}
Simon Pastor's avatar
Simon Pastor committed
var distanceSubscriptionMap = map[int]*DistanceCheck{}
Simon Pastor's avatar
Simon Pastor committed
var periodicTicker *time.Ticker
Simon Pastor's avatar
Simon Pastor committed
var areaCircleSubscriptionMap = map[int]*AreaCircleCheck{}
Simon Pastor's avatar
Simon Pastor committed
var periodicSubscriptionMap = map[int]*PeriodicCheck{}

Simon Pastor's avatar
Simon Pastor committed
var addressConnectedMap = map[string]bool{}

type ZoneStatusCheck struct {
	ZoneId                 string
	Serviceable            bool
	Unserviceable          bool
	Unknown                bool
Simon Pastor's avatar
Simon Pastor committed
	NbUsersInZoneThreshold int32
	NbUsersInAPThreshold   int32
Simon Pastor's avatar
Simon Pastor committed
type DistanceCheck struct {
Simon Pastor's avatar
Simon Pastor committed
	NextTts                int32 //next time to send, derived from frequency
	NbNotificationsSent    int32
	NotificationCheckReady bool
	Subscription           *DistanceNotificationSubscription
Simon Pastor's avatar
Simon Pastor committed
}

type AreaCircleCheck struct {
Simon Pastor's avatar
Simon Pastor committed
	NextTts                int32 //next time to send, derived from frequency
	AddrInArea             map[string]bool
	NbNotificationsSent    int32
	NotificationCheckReady bool
	Subscription           *CircleNotificationSubscription
Simon Pastor's avatar
Simon Pastor committed
}

Simon Pastor's avatar
Simon Pastor committed
type PeriodicCheck struct {
	NextTts      int32 //next time to send, derived from frequency
	Subscription *PeriodicNotificationSubscription
}

Simon Pastor's avatar
Simon Pastor committed
var currentStoreName = ""
Simon Pastor's avatar
Simon Pastor committed
var redisAddr string = "meep-redis-master.default.svc.cluster.local:6379"
var influxAddr string = "http://meep-influxdb.default.svc.cluster.local:8086"
var sbxCtrlUrl string = "http://meep-sandbox-ctrl"
Simon Pastor's avatar
Simon Pastor committed
var rc *redis.Connector
Kevin Di Lallo's avatar
Kevin Di Lallo committed
var mepName string = defaultMepName
var scopeOfLocality string = defaultScopeOfLocality
var consumedLocalOnly bool = defaultConsumedLocalOnly
Kevin Di Lallo's avatar
Kevin Di Lallo committed
var locality []string
var mutex sync.Mutex
Simon Pastor's avatar
Simon Pastor committed
var gisAppClient *gisClient.APIClient
Simon Pastor's avatar
Simon Pastor committed
var gisAppClientUrl string = "http://meep-gis-engine"
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
const serviceAppVersion = "2.1.1"

var serviceAppInstanceId string

var appEnablementUrl string
var appEnablementEnabled bool
Simon Pastor's avatar
Simon Pastor committed
var sendAppTerminationWhenDone bool = false
var appSupportClient *asc.APIClient
var svcMgmtClient *smc.APIClient
var sbxCtrlClient *scc.APIClient
Simon Pastor's avatar
Simon Pastor committed

var registrationTicker *time.Ticker
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
// Init - Location Service initialization
func Init() (err error) {
	// Retrieve Instance ID from environment variable if present
	instanceIdEnv := strings.TrimSpace(os.Getenv("MEEP_INSTANCE_ID"))
	if instanceIdEnv != "" {
		instanceId = instanceIdEnv
	}
	log.Info("MEEP_INSTANCE_ID: ", instanceId)

	// Retrieve Instance Name from environment variable
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	instanceName = moduleName
	instanceNameEnv := strings.TrimSpace(os.Getenv("MEEP_POD_NAME"))
	if instanceNameEnv != "" {
		instanceName = instanceNameEnv
	}
	log.Info("MEEP_POD_NAME: ", instanceName)

Kevin Di Lallo's avatar
Kevin Di Lallo committed
	// Get Sandbox name
Simon Pastor's avatar
Simon Pastor committed
	sandboxNameEnv := strings.TrimSpace(os.Getenv("MEEP_SANDBOX_NAME"))
	if sandboxNameEnv != "" {
		sandboxName = sandboxNameEnv
	}
	if sandboxName == "" {
		err = errors.New("MEEP_SANDBOX_NAME env variable not set")
		log.Error(err.Error())
		return err
	}
	log.Info("MEEP_SANDBOX_NAME: ", sandboxName)
Simon Pastor's avatar
Simon Pastor committed
	// hostUrl is the url of the node serving the resourceURL
	// Retrieve public url address where service is reachable, if not present, use Host URL environment variable
	hostUrl, err = url.Parse(strings.TrimSpace(os.Getenv("MEEP_PUBLIC_URL")))
	if err != nil || hostUrl == nil || hostUrl.String() == "" {
		hostUrl, err = url.Parse(strings.TrimSpace(os.Getenv("MEEP_HOST_URL")))
		if err != nil {
			hostUrl = new(url.URL)
		}
	log.Info("MEEP_HOST_URL: ", hostUrl)
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	// Get MEP name
	mepNameEnv := strings.TrimSpace(os.Getenv("MEEP_MEP_NAME"))
	if mepNameEnv != "" {
		mepName = mepNameEnv
	}
	log.Info("MEEP_MEP_NAME: ", mepName)

	// Get App Enablement URL
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	appEnablementEnabled = false
	appEnablementEnv := strings.TrimSpace(os.Getenv("MEEP_APP_ENABLEMENT"))
	if appEnablementEnv != "" {
		appEnablementUrl = "http://" + appEnablementEnv
		appEnablementEnabled = true
	}
	log.Info("MEEP_APP_ENABLEMENT: ", appEnablementUrl)

Kevin Di Lallo's avatar
Kevin Di Lallo committed
	// Get scope of locality
	scopeOfLocalityEnv := strings.TrimSpace(os.Getenv("MEEP_SCOPE_OF_LOCALITY"))
	if scopeOfLocalityEnv != "" {
		scopeOfLocality = scopeOfLocalityEnv
	}
	log.Info("MEEP_SCOPE_OF_LOCALITY: ", scopeOfLocality)

	// Get local consumption
	consumedLocalOnlyEnv := strings.TrimSpace(os.Getenv("MEEP_CONSUMED_LOCAL_ONLY"))
	if consumedLocalOnlyEnv != "" {
Kevin Di Lallo's avatar
Kevin Di Lallo committed
		value, err := strconv.ParseBool(consumedLocalOnlyEnv)
Kevin Di Lallo's avatar
Kevin Di Lallo committed
		if err == nil {
			consumedLocalOnly = value
		}
	}
	log.Info("MEEP_CONSUMED_LOCAL_ONLY: ", consumedLocalOnly)
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	// Get locality
	localityEnv := strings.TrimSpace(os.Getenv("MEEP_LOCALITY"))
	if localityEnv != "" {
		locality = strings.Split(localityEnv, ":")
	}
	log.Info("MEEP_LOCALITY: ", locality)

	// Set base path
	if mepName == defaultMepName {
Kevin Di Lallo's avatar
Kevin Di Lallo committed
		basePath = "/" + sandboxName + "/" + LocServBasePath
	} else {
		basePath = "/" + sandboxName + "/" + mepName + "/" + LocServBasePath
	// Set base storage key
	baseKey = dkm.GetKeyRoot(sandboxName) + locServKey + ":mep:" + mepName + ":"

Simon Pastor's avatar
Simon Pastor committed
	rc, err = redis.NewConnector(redisAddr, LOC_SERV_DB)
Simon Pastor's avatar
Simon Pastor committed
		log.Error("Failed connection to Redis DB. Error: ", err)
	_ = rc.DBFlush(baseKey)
Simon Pastor's avatar
Simon Pastor committed
	log.Info("Connected to Redis DB, location service table")
Simon Pastor's avatar
Simon Pastor committed
	gisAppClientCfg := gisClient.NewConfiguration()
Simon Pastor's avatar
Simon Pastor committed
	gisAppClientCfg.BasePath = gisAppClientUrl + "/gis/v1"
Simon Pastor's avatar
Simon Pastor committed

	gisAppClient = gisClient.NewAPIClient(gisAppClientCfg)
	if gisAppClient == nil {
		log.Error("Failed to create GIS App REST API client: ", gisAppClientCfg.BasePath)
		err := errors.New("Failed to create GIS App REST API client")
		return err
	}

	userTrackingReInit()
	zonalTrafficReInit()
Simon Pastor's avatar
Simon Pastor committed
	distanceReInit()
	areaCircleReInit()
Simon Pastor's avatar
Simon Pastor committed
	periodicReInit()
	// Initialize SBI
	sbiCfg := sbi.SbiCfg{
		SandboxName:    sandboxName,
		RedisAddr:      redisAddr,
Kevin Di Lallo's avatar
Kevin Di Lallo committed
		Locality:       locality,
		UserInfoCb:     updateUserInfo,
		ZoneInfoCb:     updateZoneInfo,
		ApInfoCb:       updateAccessPointInfo,
		ScenarioNameCb: updateStoreName,
		CleanUpCb:      cleanUp,
	}
	err = sbi.Init(sbiCfg)
	if err != nil {
		log.Error("Failed initialize SBI. Error: ", err)
		return err
	}
	log.Info("SBI Initialized")

	// Create App Enablement REST clients
	if appEnablementEnabled {
		// Create App Info client
		sbxCtrlClientCfg := scc.NewConfiguration()
		sbxCtrlClientCfg.BasePath = sbxCtrlUrl + "/sandbox-ctrl/v1"
		sbxCtrlClient = scc.NewAPIClient(sbxCtrlClientCfg)
		if sbxCtrlClient == nil {
			return errors.New("Failed to create App Info REST API client")
		}

		// Create App Support client
		appSupportClientCfg := asc.NewConfiguration()
		appSupportClientCfg.BasePath = appEnablementUrl + "/mec_app_support/v1"
		appSupportClient = asc.NewAPIClient(appSupportClientCfg)
		if appSupportClient == nil {
			return errors.New("Failed to create App Enablement App Support REST API client")
		}

		// Create Service Management client
		srvMgmtClientCfg := smc.NewConfiguration()
		srvMgmtClientCfg.BasePath = appEnablementUrl + "/mec_service_mgmt/v1"
		svcMgmtClient = smc.NewAPIClient(srvMgmtClientCfg)
		if svcMgmtClient == nil {
			return errors.New("Failed to create App Enablement Service Management REST API client")
		}
Simon Pastor's avatar
Simon Pastor committed
	}

	log.Info("Location Service successfully initialized")
// Run - Start Location Service

	// Start MEC Service registration ticker
	if appEnablementEnabled {
		startRegistrationTicker()
	}

Simon Pastor's avatar
Simon Pastor committed
	periodicTicker = time.NewTicker(time.Second)
Simon Pastor's avatar
Simon Pastor committed
	go func() {
Simon Pastor's avatar
Simon Pastor committed
		for range periodicTicker.C {
Simon Pastor's avatar
Simon Pastor committed
			checkNotificationDistancePeriodicTrigger()
Simon Pastor's avatar
Simon Pastor committed
			updateNotificationAreaCirclePeriodicTrigger()
Simon Pastor's avatar
Simon Pastor committed
			checkNotificationPeriodicTrigger()
Simon Pastor's avatar
Simon Pastor committed
		}
	}()
Simon Pastor's avatar
Simon Pastor committed
// Stop - Stop Location
Simon Pastor's avatar
Simon Pastor committed
func Stop() (err error) {
	// Stop MEC Service registration ticker
	if appEnablementEnabled {
		stopRegistrationTicker()
Simon Pastor's avatar
Simon Pastor committed
		if sendAppTerminationWhenDone {
			err = sendTerminationConfirmation(serviceAppInstanceId)
Simon Pastor's avatar
Simon Pastor committed
			if err != nil {
				return err
			}
Simon Pastor's avatar
Simon Pastor committed
		}
	}

	periodicTicker.Stop()
	err = sbi.Stop()
Simon Pastor's avatar
Simon Pastor committed
	if err != nil {
		return err
	}
	return nil
}

func startRegistrationTicker() {
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	// Make sure ticker is not running
	if registrationTicker != nil {
		log.Warn("Registration ticker already running")
		return
	}

	// Wait a few seconds to allow App Enablement Service to start.
	// This is done to avoid the default 20 second TCP socket connect timeout
	// if the App Enablement Service is not yet running.
	log.Info("Waiting for App Enablement Service to start")
	time.Sleep(5 * time.Second)
Simon Pastor's avatar
Simon Pastor committed

Kevin Di Lallo's avatar
Kevin Di Lallo committed
	// Start registration ticker
	registrationTicker = time.NewTicker(5 * time.Second)
	go func() {
		mecAppReadySent := false
		// registrationSent := false
		for range registrationTicker.C {
			// Get Application instance ID if not already available
			if serviceAppInstanceId == "" {
				var err error
				serviceAppInstanceId, err = getAppInstanceId()
				if err != nil || serviceAppInstanceId == "" {
					continue
				}
			}

			// Send App Ready message
			if !mecAppReadySent {
				err := sendReadyConfirmation(serviceAppInstanceId)
				if err != nil {
					log.Error("Failure when sending the MecAppReady message. Error: ", err)
					continue
				}
				mecAppReadySent = true
			}
Simon Pastor's avatar
Simon Pastor committed

			// Register service instance
			// if !registrationSent {
			err := registerService(serviceAppInstanceId)
			if err != nil {
				log.Error("Failed to register to appEnablement DB, keep trying. Error: ", err)
				continue
			}
			// 	registrationSent = true
			// }

			// // Register for graceful termination
			// if !subscriptionSent {
			// 	err = subscribeAppTermination(serviceAppInstanceId)
			// 	if err != nil {
			// 		log.Error("Failed to subscribe to graceful termination. Error: ", err)
			// 		continue
			// 	}
			// 	sendAppTerminationWhenDone = true
			// 	subscriptionSent = true
			// }

			// Registration complete
			log.Info("Successfully registered with App Enablement Service")
			stopRegistrationTicker()
			return
		}
	}()
}
Simon Pastor's avatar
Simon Pastor committed

func stopRegistrationTicker() {
	if registrationTicker != nil {
		log.Info("Stopping App Enablement registration ticker")
		registrationTicker.Stop()
		registrationTicker = nil
Simon Pastor's avatar
Simon Pastor committed
	}
func getAppInstanceId() (id string, err error) {
	var appInfo scc.ApplicationInfo
	appInfo.Id = instanceId
	appInfo.Name = instanceName
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	appInfo.MepName = mepName
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	appType := scc.SYSTEM_ApplicationType
	appInfo.Type_ = &appType
	state := scc.INITIALIZED_ApplicationState
Simon Pastor's avatar
Simon Pastor committed
	appInfo.State = &state
Kevin Di Lallo's avatar
Kevin Di Lallo committed
	response, _, err := sbxCtrlClient.ApplicationsApi.ApplicationsPOST(context.TODO(), appInfo)
Simon Pastor's avatar
Simon Pastor committed
	if err != nil {
		log.Error("Failed to get App Instance ID with error: ", err)
		return "", err
Simon Pastor's avatar
Simon Pastor committed
	}
Simon Pastor's avatar
Simon Pastor committed
}

func registerService(appInstanceId string) error {
	var srvInfo smc.ServiceInfoPost
Simon Pastor's avatar
Simon Pastor committed
	//serName
Simon Pastor's avatar
Simon Pastor committed
	//version
Simon Pastor's avatar
Simon Pastor committed
	//state
Simon Pastor's avatar
Simon Pastor committed
	srvInfo.State = &state
	//serializer
Simon Pastor's avatar
Simon Pastor committed
	srvInfo.Serializer = &serializer

	//transportInfo
Simon Pastor's avatar
Simon Pastor committed
	transportInfo.Id = "transport"
	transportInfo.Name = "REST"
	transportType := smc.REST_HTTP_TransportType
Simon Pastor's avatar
Simon Pastor committed
	transportInfo.Type_ = &transportType
	transportInfo.Protocol = "HTTP"
	transportInfo.Version = "2.0"
	var endpoint smc.OneOfTransportInfoEndpoint
Simon Pastor's avatar
Simon Pastor committed
	endpointPath := hostUrl.String() + basePath
	endpoint.Uris = append(endpoint.Uris, endpointPath)
	transportInfo.Endpoint = &endpoint
	srvInfo.TransportInfo = &transportInfo

	//serCategory
Simon Pastor's avatar
Simon Pastor committed
	category.Href = "catalogueHref"
	category.Id = "locationId"
	category.Name = "Location"
	category.Version = "v2"
	srvInfo.SerCategory = &category

	//scopeOfLocality
	localityType := smc.LocalityType(scopeOfLocality)
	srvInfo.ScopeOfLocality = &localityType
Simon Pastor's avatar
Simon Pastor committed

	//consumedLocalOnly
	srvInfo.ConsumedLocalOnly = consumedLocalOnly
Simon Pastor's avatar
Simon Pastor committed

	appServicesPostResponse, _, err := svcMgmtClient.AppServicesApi.AppServicesPOST(context.TODO(), srvInfo, appInstanceId)
Simon Pastor's avatar
Simon Pastor committed
	if err != nil {
		log.Error("Failed to register the service to app enablement registry: ", err)
		return err
	}
	log.Info("Application Enablement Service instance Id: ", appServicesPostResponse.SerInstanceId)
	return nil
}

func sendReadyConfirmation(appInstanceId string) error {
	var appReady asc.AppReadyConfirmation
	indication := asc.READY_ReadyIndicationType
	appReady.Indication = &indication
	_, err := appSupportClient.AppConfirmReadyApi.ApplicationsConfirmReadyPOST(context.TODO(), appReady, appInstanceId)
	if err != nil {
		log.Error("Failed to send a ready confirm acknowlegement: ", err)
Simon Pastor's avatar
Simon Pastor committed
		return err
	}
Simon Pastor's avatar
Simon Pastor committed

func sendTerminationConfirmation(appInstanceId string) error {
	var appTermination asc.AppTerminationConfirmation
	operationAction := asc.TERMINATING_OperationActionType
	appTermination.OperationAction = &operationAction
	_, err := appSupportClient.AppConfirmTerminationApi.ApplicationsConfirmTerminationPOST(context.TODO(), appTermination, appInstanceId)
Simon Pastor's avatar
Simon Pastor committed
	if err != nil {
		log.Error("Failed to send a confirm termination acknowlegement: ", err)
Simon Pastor's avatar
Simon Pastor committed
		return err
	}
	return nil
}

// func subscribeAppTermination(appInstanceId string) error {
// 	var subscription asc.AppTerminationNotificationSubscription
// 	subscription.SubscriptionType = "AppTerminationNotificationSubscription"
// 	subscription.AppInstanceId = appInstanceId
// 	subscription.CallbackReference = hostUrl.String() + basePath
// 	_, _, err := appSupportClient.AppSubscriptionsApi.ApplicationsSubscriptionsPOST(context.TODO(), subscription, appInstanceId)
// 	if err != nil {
// 		log.Error("Failed to register to App Support subscription: ", err)
// 		return err
// 	}
// 	return nil
// }
Simon Pastor's avatar
Simon Pastor committed

func deregisterZoneStatus(subsIdStr string) {
Simon Pastor's avatar
Simon Pastor committed
	subsId, err := strconv.Atoi(subsIdStr)
	if err != nil {
		log.Error(err)
	}
Simon Pastor's avatar
Simon Pastor committed

	mutex.Lock()
	defer mutex.Unlock()
Simon Pastor's avatar
Simon Pastor committed
	zoneStatusSubscriptionMap[subsId] = nil
func registerZoneStatus(zoneId string, nbOfUsersZoneThreshold int32, nbOfUsersAPThreshold int32, opStatus []OperationStatus, subsIdStr string) {
Simon Pastor's avatar
Simon Pastor committed
	subsId, err := strconv.Atoi(subsIdStr)
	if err != nil {
		log.Error(err)
	}

	var zoneStatus ZoneStatusCheck
	if opStatus != nil {
		for i := 0; i < len(opStatus); i++ {
			switch opStatus[i] {
			case SERVICEABLE:
				zoneStatus.Serviceable = true
			case UNSERVICEABLE:
				zoneStatus.Unserviceable = true
			case OPSTATUS_UNKNOWN:
				zoneStatus.Unknown = true
			default:
			}
		}
	}
Simon Pastor's avatar
Simon Pastor committed
	zoneStatus.NbUsersInZoneThreshold = nbOfUsersZoneThreshold
	zoneStatus.NbUsersInAPThreshold = nbOfUsersAPThreshold
	mutex.Lock()
	defer mutex.Unlock()
	zoneStatusSubscriptionMap[subsId] = &zoneStatus
}

func deregisterZonal(subsIdStr string) {
Simon Pastor's avatar
Simon Pastor committed
	subsId, err := strconv.Atoi(subsIdStr)
	if err != nil {
		log.Error(err)
	}

	mutex.Lock()
	defer mutex.Unlock()
	zonalSubscriptionMap[subsId] = ""
	zonalSubscriptionEnteringMap[subsId] = ""
	zonalSubscriptionLeavingMap[subsId] = ""
	zonalSubscriptionTransferringMap[subsId] = ""
func registerZonal(zoneId string, event []UserEventType, subsIdStr string) {

Simon Pastor's avatar
Simon Pastor committed
	subsId, err := strconv.Atoi(subsIdStr)
	if err != nil {
		log.Error(err)
	}
	mutex.Lock()
	defer mutex.Unlock()
	if event != nil {
		for i := 0; i < len(event); i++ {
			switch event[i] {
Simon Pastor's avatar
Simon Pastor committed
			case ENTERING_EVENT:
				zonalSubscriptionEnteringMap[subsId] = zoneId
Simon Pastor's avatar
Simon Pastor committed
			case LEAVING_EVENT:
				zonalSubscriptionLeavingMap[subsId] = zoneId
Simon Pastor's avatar
Simon Pastor committed
			case TRANSFERRING_EVENT:
				zonalSubscriptionTransferringMap[subsId] = zoneId
		zonalSubscriptionEnteringMap[subsId] = zoneId
		zonalSubscriptionLeavingMap[subsId] = zoneId
		zonalSubscriptionTransferringMap[subsId] = zoneId
	}
	zonalSubscriptionMap[subsId] = zoneId
}

func deregisterUser(subsIdStr string) {
Simon Pastor's avatar
Simon Pastor committed
	subsId, err := strconv.Atoi(subsIdStr)
	if err != nil {
		log.Error(err)
	}
Simon Pastor's avatar
Simon Pastor committed

	mutex.Lock()
	defer mutex.Unlock()
	userSubscriptionMap[subsId] = ""
	userSubscriptionEnteringMap[subsId] = ""
	userSubscriptionLeavingMap[subsId] = ""
	userSubscriptionTransferringMap[subsId] = ""
func registerUser(userAddress string, event []UserEventType, subsIdStr string) {

Simon Pastor's avatar
Simon Pastor committed
	subsId, err := strconv.Atoi(subsIdStr)
	if err != nil {
		log.Error(err)
	}
Simon Pastor's avatar
Simon Pastor committed

	mutex.Lock()
	defer mutex.Unlock()
	if event != nil {
		for i := 0; i < len(event); i++ {
			switch event[i] {
Simon Pastor's avatar
Simon Pastor committed
			case ENTERING_EVENT:
				userSubscriptionEnteringMap[subsId] = userAddress
Simon Pastor's avatar
Simon Pastor committed
			case LEAVING_EVENT:
				userSubscriptionLeavingMap[subsId] = userAddress
Simon Pastor's avatar
Simon Pastor committed
			case TRANSFERRING_EVENT:
				userSubscriptionTransferringMap[subsId] = userAddress
		userSubscriptionEnteringMap[subsId] = userAddress
		userSubscriptionLeavingMap[subsId] = userAddress
		userSubscriptionTransferringMap[subsId] = userAddress
	}
	userSubscriptionMap[subsId] = userAddress
}

Simon Pastor's avatar
Simon Pastor committed
func updateNotificationAreaCirclePeriodicTrigger() {
	//only check if there is at least one subscription
	mutex.Lock()
	defer mutex.Unlock()

	for _, areaCircleCheck := range areaCircleSubscriptionMap {
		if areaCircleCheck != nil {
			if areaCircleCheck.NextTts != 0 {
				areaCircleCheck.NextTts--
			}
			if areaCircleCheck.NextTts == 0 {
				areaCircleCheck.NotificationCheckReady = true
			} else {
				areaCircleCheck.NotificationCheckReady = false
			}
		}
	}
}

Simon Pastor's avatar
Simon Pastor committed
func checkNotificationDistancePeriodicTrigger() {

	//only check if there is at least one subscription
	mutex.Lock()
	defer mutex.Unlock()
	//check all that applies
	for subsId, distanceCheck := range distanceSubscriptionMap {
		if distanceCheck != nil && distanceCheck.Subscription != nil {
Simon Pastor's avatar
Simon Pastor committed
			if distanceCheck.Subscription.Count == 0 || (distanceCheck.Subscription.Count != 0 && distanceCheck.NbNotificationsSent < distanceCheck.Subscription.Count) {
Simon Pastor's avatar
Simon Pastor committed
				if distanceCheck.NextTts != 0 {
					distanceCheck.NextTts--
				}
				if distanceCheck.NextTts == 0 {
					distanceCheck.NotificationCheckReady = true
				} else {
					distanceCheck.NotificationCheckReady = false
				}

				if !distanceCheck.NotificationCheckReady {
Simon Pastor's avatar
Simon Pastor committed
					continue
Simon Pastor's avatar
Simon Pastor committed
				}
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
				//loop through every reference address
				returnAddr := make(map[string]*gisClient.Distance)
				skipThisSubscription := false

				//if reference address is specified, reference addresses are checked agains each monitored address
				//if reference address is nil, each pair of the monitored address should be checked
				//creating address pairs to check
				//e.g. refAddr = A, B ; monitoredAddr = C, D, E ; resultingPairs {A,C - A,D - A,E - B,C - B,D - B-E}
				//e.g. monitoredAddr = A, B, C ; resultingPairs {A,B - B,A - A,C - C,A - B,C - C,B}

				var addressPairs []Pair
				if distanceCheck.Subscription.ReferenceAddress != nil {
					for _, refAddr := range distanceCheck.Subscription.ReferenceAddress {
						//loop through every monitored address
						for _, monitoredAddr := range distanceCheck.Subscription.MonitoredAddress {
							pair := Pair{addr1: refAddr, addr2: monitoredAddr}
							addressPairs = append(addressPairs, pair)
						}
					}
				} else {
					nbIndex := len(distanceCheck.Subscription.MonitoredAddress)
					for i := 0; i < nbIndex-1; i++ {
						for j := i + 1; j < nbIndex; j++ {
							pair := Pair{addr1: distanceCheck.Subscription.MonitoredAddress[i], addr2: distanceCheck.Subscription.MonitoredAddress[j]}
							addressPairs = append(addressPairs, pair)
							//need pair to be symmetrical so that each is used as reference point and monitored address
							pair = Pair{addr1: distanceCheck.Subscription.MonitoredAddress[j], addr2: distanceCheck.Subscription.MonitoredAddress[i]}
							addressPairs = append(addressPairs, pair)
						}
Simon Pastor's avatar
Simon Pastor committed
					}
				}

Simon Pastor's avatar
Simon Pastor committed
				for _, pair := range addressPairs {
					refAddr := pair.addr1
					monitoredAddr := pair.addr2

Simon Pastor's avatar
Simon Pastor committed
					//check if one of the address if both addresses are connected, if not, disregard this pair
					if !addressConnectedMap[refAddr] || !addressConnectedMap[monitoredAddr] {
						//ignore that pair and continue processing
						continue
					}

Simon Pastor's avatar
Simon Pastor committed
					var distParam gisClient.TargetPoint
					distParam.AssetName = monitoredAddr

Simon Pastor's avatar
Simon Pastor committed
					distResp, httpResp, err := gisAppClient.GeospatialDataApi.GetDistanceGeoDataByName(context.TODO(), refAddr, distParam)
Simon Pastor's avatar
Simon Pastor committed
					if err != nil {
						//getting distance of an element that is not in the DB (not in scenario, not connected) returns error code 400 (bad parameters) in the API. Using that error code to track that request made it to GIS but no good result, so ignore that address (monitored or ref)
Simon Pastor's avatar
Simon Pastor committed
						if httpResp.StatusCode == http.StatusBadRequest {
Simon Pastor's avatar
Simon Pastor committed
							//ignore that pair and continue processing
							continue
						} else {
							log.Error("Failed to communicate with gis engine: ", err)
							return
						}
					}
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
					distance := int32(distResp.Distance)
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
					switch *distanceCheck.Subscription.Criteria {
					case ALL_WITHIN_DISTANCE:
						if float32(distance) < distanceCheck.Subscription.Distance {
							returnAddr[monitoredAddr] = &distResp
						} else {
							skipThisSubscription = true
						}
					case ALL_BEYOND_DISTANCE:
						if float32(distance) > distanceCheck.Subscription.Distance {
							returnAddr[monitoredAddr] = &distResp
						} else {
							skipThisSubscription = true
						}
					case ANY_WITHIN_DISTANCE:
						if float32(distance) < distanceCheck.Subscription.Distance {
							returnAddr[monitoredAddr] = &distResp
						}
					case ANY_BEYOND_DISTANCE:
						if float32(distance) > distanceCheck.Subscription.Distance {
							returnAddr[monitoredAddr] = &distResp
						}
					default:
Simon Pastor's avatar
Simon Pastor committed
					}
Simon Pastor's avatar
Simon Pastor committed
					if skipThisSubscription {
						break
Simon Pastor's avatar
Simon Pastor committed
					}
				}
Simon Pastor's avatar
Simon Pastor committed
				if skipThisSubscription {
Simon Pastor's avatar
Simon Pastor committed
					continue
Simon Pastor's avatar
Simon Pastor committed
				}
Simon Pastor's avatar
Simon Pastor committed
				if len(returnAddr) > 0 {
					//update nb of notification sent anch check if valid
					subsIdStr := strconv.Itoa(subsId)
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
					var distanceNotif SubscriptionNotification
					distanceNotif.DistanceCriteria = distanceCheck.Subscription.Criteria
					distanceNotif.IsFinalNotification = false
					distanceNotif.Link = distanceCheck.Subscription.Link
					var terminalLocationList []TerminalLocation
					for terminalAddr, distanceInfo := range returnAddr {
						var terminalLocation TerminalLocation
						terminalLocation.Address = terminalAddr
						var locationInfo LocationInfo
						locationInfo.Latitude = nil
Simon Pastor's avatar
Simon Pastor committed
						locationInfo.Latitude = append(locationInfo.Latitude, distanceInfo.DstLatitude)
Simon Pastor's avatar
Simon Pastor committed
						locationInfo.Longitude = nil
Simon Pastor's avatar
Simon Pastor committed
						locationInfo.Longitude = append(locationInfo.Longitude, distanceInfo.DstLongitude)
Simon Pastor's avatar
Simon Pastor committed
						locationInfo.Shape = 2
						seconds := time.Now().Unix()
						var timestamp TimeStamp
						timestamp.Seconds = int32(seconds)
						locationInfo.Timestamp = &timestamp
						terminalLocation.CurrentLocation = &locationInfo
						retrievalStatus := RETRIEVED
						terminalLocation.LocationRetrievalStatus = &retrievalStatus
						terminalLocationList = append(terminalLocationList, terminalLocation)
					}
					distanceNotif.TerminalLocation = terminalLocationList
					distanceNotif.CallbackData = distanceCheck.Subscription.CallbackReference.CallbackData
					var inlineDistanceSubscriptionNotification InlineSubscriptionNotification
					inlineDistanceSubscriptionNotification.SubscriptionNotification = &distanceNotif
					distanceCheck.NbNotificationsSent++
					sendSubscriptionNotification(distanceCheck.Subscription.CallbackReference.NotifyURL, inlineDistanceSubscriptionNotification)
					log.Info("Distance Notification"+"("+subsIdStr+") For ", returnAddr)
Simon Pastor's avatar
Simon Pastor committed
					distanceSubscriptionMap[subsId].NextTts = distanceCheck.Subscription.Frequency
					distanceSubscriptionMap[subsId].NotificationCheckReady = false
Simon Pastor's avatar
Simon Pastor committed
				}
			}
		}
	}
}

func checkNotificationAreaCircle(addressToCheck string) {
	//only check if there is at least one subscription
	mutex.Lock()
	defer mutex.Unlock()
	//check all that applies
	for subsId, areaCircleCheck := range areaCircleSubscriptionMap {
		if areaCircleCheck != nil && areaCircleCheck.Subscription != nil {
Simon Pastor's avatar
Simon Pastor committed
			if areaCircleCheck.Subscription.Count == 0 || (areaCircleCheck.Subscription.Count != 0 && areaCircleCheck.NbNotificationsSent < areaCircleCheck.Subscription.Count) {
Simon Pastor's avatar
Simon Pastor committed
				if !areaCircleCheck.NotificationCheckReady {
Simon Pastor's avatar
Simon Pastor committed
					continue
Simon Pastor's avatar
Simon Pastor committed
				}
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
				//loop through every reference address
				for _, addr := range areaCircleCheck.Subscription.Address {
					if addr != addressToCheck {
Simon Pastor's avatar
Simon Pastor committed
						continue
Simon Pastor's avatar
Simon Pastor committed
					}
Simon Pastor's avatar
Simon Pastor committed
					if !addressConnectedMap[addr] {
						continue
					}
Simon Pastor's avatar
Simon Pastor committed
					//check if address is already inside the area or not based on the subscription
					var withinRangeParam gisClient.TargetRange
					withinRangeParam.Latitude = areaCircleCheck.Subscription.Latitude
					withinRangeParam.Longitude = areaCircleCheck.Subscription.Longitude
					withinRangeParam.Radius = areaCircleCheck.Subscription.Radius

Simon Pastor's avatar
Simon Pastor committed
					withinRangeResp, httpResp, err := gisAppClient.GeospatialDataApi.GetWithinRangeByName(context.TODO(), addr, withinRangeParam)
Simon Pastor's avatar
Simon Pastor committed
					if err != nil {
						//getting element that is not in the DB (not in scenario, not connected) returns error code 400 (bad parameters) in the API. Using that error code to track that request made it to GIS but no good result, so ignore that address (monitored or ref)
Simon Pastor's avatar
Simon Pastor committed
						if httpResp.StatusCode == http.StatusBadRequest {
Simon Pastor's avatar
Simon Pastor committed
							//if the UE was within the zone, continue processing to send a LEAVING notification, otherwise, go to next subscription
							if !areaCircleCheck.AddrInArea[addr] {
								continue
							}
						} else {
							log.Error("Failed to communicate with gis engine: ", err)
							return
						}
					}
					//check if there is a change
					var event EnteringLeavingCriteria
					if withinRangeResp.Within {
						if areaCircleCheck.AddrInArea[addr] {
							//no change
							continue
						} else {
							areaCircleCheck.AddrInArea[addr] = true
							event = ENTERING_CRITERIA
						}
Simon Pastor's avatar
Simon Pastor committed
					} else {
Simon Pastor's avatar
Simon Pastor committed
						if !areaCircleCheck.AddrInArea[addr] {
							//no change
							continue
						} else {
							areaCircleCheck.AddrInArea[addr] = false
							event = LEAVING_CRITERIA
						}
Simon Pastor's avatar
Simon Pastor committed
					}
Simon Pastor's avatar
Simon Pastor committed
					//no tracking this event, stop looking for this UE
					if *areaCircleCheck.Subscription.EnteringLeavingCriteria != event {
Simon Pastor's avatar
Simon Pastor committed
						continue
					}
Simon Pastor's avatar
Simon Pastor committed
					subsIdStr := strconv.Itoa(subsId)
					var areaCircleNotif SubscriptionNotification
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
					areaCircleNotif.EnteringLeavingCriteria = areaCircleCheck.Subscription.EnteringLeavingCriteria
					areaCircleNotif.IsFinalNotification = false
					areaCircleNotif.Link = areaCircleCheck.Subscription.Link
					var terminalLocationList []TerminalLocation
					var terminalLocation TerminalLocation
					terminalLocation.Address = addr
					var locationInfo LocationInfo
					locationInfo.Latitude = nil
Simon Pastor's avatar
Simon Pastor committed
					locationInfo.Latitude = append(locationInfo.Latitude, withinRangeResp.SrcLatitude)
Simon Pastor's avatar
Simon Pastor committed
					locationInfo.Longitude = nil
Simon Pastor's avatar
Simon Pastor committed
					locationInfo.Longitude = append(locationInfo.Longitude, withinRangeResp.SrcLongitude)
Simon Pastor's avatar
Simon Pastor committed
					locationInfo.Shape = 2
					seconds := time.Now().Unix()
					var timestamp TimeStamp
					timestamp.Seconds = int32(seconds)
					locationInfo.Timestamp = &timestamp
					terminalLocation.CurrentLocation = &locationInfo
					retrievalStatus := RETRIEVED
					terminalLocation.LocationRetrievalStatus = &retrievalStatus
					terminalLocationList = append(terminalLocationList, terminalLocation)
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
					areaCircleNotif.TerminalLocation = terminalLocationList
					areaCircleNotif.CallbackData = areaCircleCheck.Subscription.CallbackReference.CallbackData
					var inlineCircleSubscriptionNotification InlineSubscriptionNotification
					inlineCircleSubscriptionNotification.SubscriptionNotification = &areaCircleNotif
					areaCircleCheck.NbNotificationsSent++
					sendSubscriptionNotification(areaCircleCheck.Subscription.CallbackReference.NotifyURL, inlineCircleSubscriptionNotification)
					log.Info("Area Circle Notification" + "(" + subsIdStr + ") For " + addr + " when " + string(*areaCircleCheck.Subscription.EnteringLeavingCriteria) + " area")
Simon Pastor's avatar
Simon Pastor committed
					areaCircleSubscriptionMap[subsId].NextTts = areaCircleCheck.Subscription.Frequency
					areaCircleSubscriptionMap[subsId].NotificationCheckReady = false
Simon Pastor's avatar
Simon Pastor committed
				}
Simon Pastor's avatar
Simon Pastor committed
func checkNotificationPeriodicTrigger() {

	//only check if there is at least one subscription
	mutex.Lock()
	defer mutex.Unlock()

	//check all that applies
	for subsId, periodicCheck := range periodicSubscriptionMap {
		if periodicCheck != nil && periodicCheck.Subscription != nil {
			//decrement the next time to send a message
			periodicCheck.NextTts--
			if periodicCheck.NextTts > 0 {
				continue
			} else { //restart the nextTts and continue processing to send notification or not
				periodicCheck.NextTts = periodicCheck.Subscription.Frequency
			}

			//loop through every reference address
			var terminalLocationList []TerminalLocation
			var periodicNotif SubscriptionNotification

			for _, addr := range periodicCheck.Subscription.Address {

Simon Pastor's avatar
Simon Pastor committed
				if !addressConnectedMap[addr] {
					continue
				}

Simon Pastor's avatar
Simon Pastor committed
				geoDataInfo, _, err := gisAppClient.GeospatialDataApi.GetGeoDataByName(context.TODO(), addr, nil)
				if err != nil {
					log.Error("Failed to communicate with gis engine: ", err)
					return
				}

				var terminalLocation TerminalLocation
				terminalLocation.Address = addr
				var locationInfo LocationInfo