Skip to content
wais.go 55.6 KiB
Newer Older
Simon Pastor's avatar
Simon Pastor committed
/*
 * Copyright (c) 2020  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.
 */

package server

import (
Simon Pastor's avatar
Simon Pastor committed
	"bytes"
Simon Pastor's avatar
Simon Pastor committed
	"context"
Simon Pastor's avatar
Simon Pastor committed
	"encoding/json"
	"errors"
	"fmt"
Simon Pastor's avatar
Simon Pastor committed
	"io/ioutil"
Simon Pastor's avatar
Simon Pastor committed
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"
	"sync"
Simon Pastor's avatar
Simon Pastor committed
	"time"

	sbi "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-wais/sbi"
Simon Pastor's avatar
Simon Pastor committed
	appInfoClient "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-info-client"
Simon Pastor's avatar
Simon Pastor committed
	appSupportClient "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-support-client"
Simon Pastor's avatar
Simon Pastor committed
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
	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"
Simon Pastor's avatar
Simon Pastor committed
	redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis"
Simon Pastor's avatar
Simon Pastor committed
	srvMgmtClient "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client"
Simon Pastor's avatar
Simon Pastor committed

	"github.com/gorilla/mux"
)

const waisBasePath = "/wai/v2/"
const waisKey = "wais:"
const logModuleWAIS = "meep-wais"
const serviceName = "WAI Service"

const (
	notifAssocSta    = "AssocStaNotification"
	notifStaDataRate = "StaDataRateNotification"
	notifExpiry      = "ExpiryNotification"
	notifTest        = "TestNotification"
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"
Simon Pastor's avatar
Simon Pastor committed
var currentStoreName = ""
Simon Pastor's avatar
Simon Pastor committed

const assocStaSubscriptionType = "AssocStaSubscription"
const staDataRateSubscriptionType = "StaDataRateSubscription"
Simon Pastor's avatar
Simon Pastor committed
const ASSOC_STA_SUBSCRIPTION = "AssocStaSubscription"
const STA_DATA_RATE_SUBSCRIPTION = "StaDataRateSubscription"
Simon Pastor's avatar
Simon Pastor committed
const ASSOC_STA_NOTIFICATION = "AssocStaNotification"
const STA_DATA_RATE_NOTIFICATION = "StaDataRateNotification"
Simon Pastor's avatar
Simon Pastor committed
const MEASUREMENT_REPORT_SUBSCRIPTION = "MeasurementReportSubscription"
const TEST_NOTIFICATION = "TestNotification"
var assocStaSubscriptionInfoMap = map[int]*AssocStaSubscriptionInfo{}
var staDataRateSubscriptionInfoMap = map[int]*StaDataRateSubscriptionInfo{}
Simon Pastor's avatar
Simon Pastor committed
var subscriptionExpiryMap = map[int][]int{}

var WAIS_DB = 5

var rc *redis.Connector
var hostUrl *url.URL
var sandboxName string
var basePath string
var baseKey string
var mutex sync.Mutex
Simon Pastor's avatar
Simon Pastor committed

var expiryTicker *time.Ticker

var nextSubscriptionIdAvailable int

type ApInfoComplete struct {
	ApId       ApIdentity
	ApLocation ApLocation
	StaMacIds  []string
}

type ApInfoCompleteResp struct {
	ApInfoCompleteList []ApInfoComplete
}

type StaDataRateSubscriptionInfo struct {
Simon Pastor's avatar
Simon Pastor committed
	NextTts                int32 //next time to send, derived from notificationPeriod
	NotificationCheckReady bool
	Subscription           *StaDataRateSubscription
	Triggered              bool
}

type AssocStaSubscriptionInfo struct {
Simon Pastor's avatar
Simon Pastor committed
	NextTts                int32 //next time to send, derived from notificationPeriod
	NotificationCheckReady bool
	Subscription           *AssocStaSubscription
	Triggered              bool
	StaInfo *StaInfo `json:"staInfo"`
Simon Pastor's avatar
Simon Pastor committed
type StaInfoResp struct {
	StaInfoList []StaInfo
}

type ApInfoResp struct {
	ApInfoList []ApInfo
}

Simon Pastor's avatar
Simon Pastor committed
//MEC011 section begin
const serviceAppName = "WAI"
const serviceAppVersion = "2.1.1"

var serviceAppInstanceId string

var appEnablementClientUrl string = "http://meep-app-enablement"
var appEnablementSupport bool = true
var appEnablementSrvMgmtClient *srvMgmtClient.APIClient
Simon Pastor's avatar
Simon Pastor committed
var appEnablementAppSupportClient *appSupportClient.APIClient
var sendAppTerminationWhenDone bool = false
Simon Pastor's avatar
Simon Pastor committed
var retryAppEnablementTicker *time.Ticker

//MEC011 section end

func notImplemented(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusNotImplemented)
}
Simon Pastor's avatar
Simon Pastor committed
func Init() (err error) {

	// Retrieve Sandbox name from environment variable
	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)

	// 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("resource URL: ", hostUrl)

	// Set base path
	basePath = "/" + sandboxName + waisBasePath

	// Get base store key
	baseKey = dkm.GetKeyRoot(sandboxName) + waisKey

	// Connect to Redis DB
	rc, err = redis.NewConnector(redisAddr, WAIS_DB)
	if err != nil {
		log.Error("Failed connection to Redis DB. Error: ", err)
		return err
	}
	_ = rc.DBFlush(baseKey)
Simon Pastor's avatar
Simon Pastor committed
	log.Info("Connected to Redis DB, WAI service table")
Simon Pastor's avatar
Simon Pastor committed

	reInit()

	expiryTicker = time.NewTicker(time.Second)
	go func() {
		for range expiryTicker.C {
			checkForExpiredSubscriptions()
			checkAssocStaPeriodTrigger()
			checkStaDataRatePeriodTrigger()
Simon Pastor's avatar
Simon Pastor committed
		}
	}()

	// Initialize SBI
	sbiCfg := sbi.SbiCfg{
		SandboxName:    sandboxName,
		RedisAddr:      redisAddr,
		InfluxAddr:     influxAddr,
		StaInfoCb:      updateStaInfo,
Simon Pastor's avatar
Simon Pastor committed
		ApInfoCb:       updateApInfo,
		ScenarioNameCb: updateStoreName,
		CleanUpCb:      cleanUp,
	}

	err = sbi.Init(sbiCfg)
	if err != nil {
		log.Error("Failed initialize SBI. Error: ", err)
		return err
	}
	log.Info("SBI Initialized")

Simon Pastor's avatar
Simon Pastor committed
	//register using MEC011
	if appEnablementSupport {
		//delay startup on purpose to give time for appEnablement pod to come up (if all coming up at the same time)
		time.Sleep(2 * time.Second)

		retryAppEnablementTicker = time.NewTicker(time.Second)
		go func() {
Simon Pastor's avatar
Simon Pastor committed
			mecAppReadySent := false
			registrationSent := false
Simon Pastor's avatar
Simon Pastor committed
			for range retryAppEnablementTicker.C {
				if serviceAppInstanceId == "" {
					serviceAppInstanceId = getAppInstanceId(serviceAppName, serviceAppVersion)
				}
				if serviceAppInstanceId != "" {
Simon Pastor's avatar
Simon Pastor committed
					//sending app is ready
					if !mecAppReadySent {
						err := appEnablementMecAppReady(serviceAppInstanceId)
						if err != nil {
							log.Error("Failure when sending the MecAppReady message, keep trying. Error: ", err)
							continue
						} else {
							mecAppReadySent = true
						}
					}
					if !registrationSent {
						err := appEnablementRegistration(serviceAppInstanceId, serviceAppName, serviceAppVersion)
						if err != nil {
							log.Error("Failed to register to appEnablement DB, keep trying. Error: ", err)
							continue
						} else {
							registrationSent = true
						}
						/*
							err = appEnablementAppSupportSubscribe(serviceAppInstanceId)
							if err != nil {
								log.Error("Failed to subscribe to graceful termination. Error: ", err)
							}
							sendAppTerminationWhenDone = true
						*/
					}
					if mecAppReadySent && registrationSent {
Simon Pastor's avatar
Simon Pastor committed
						retryAppEnablementTicker.Stop()
					}
				}
			}
		}()
	}

Simon Pastor's avatar
Simon Pastor committed
	return nil
}

// reInit - finds the value already in the DB to repopulate local stored info
func reInit() {
	//next available subsId will be overrriden if subscriptions already existed
	nextSubscriptionIdAvailable = 1

	keyName := baseKey + "subscription:" + "*"
Simon Pastor's avatar
Simon Pastor committed
	_ = rc.ForEachJSONEntry(keyName, repopulateAssocStaSubscriptionMap, nil)
	_ = rc.ForEachJSONEntry(keyName, repopulateStaDataRateSubscriptionMap, nil)
Simon Pastor's avatar
Simon Pastor committed
}

// Run - Start WAIS
func Run() (err error) {
	return sbi.Run()
}

// Stop - Stop WAIS
func Stop() (err error) {
Simon Pastor's avatar
Simon Pastor committed
	err = sbi.Stop()
	if err != nil {
		return err
	}
Simon Pastor's avatar
Simon Pastor committed
	if appEnablementSupport {
		retryAppEnablementTicker.Stop()
		if sendAppTerminationWhenDone {
			err = appEnablementMecAppTermination(serviceAppInstanceId)
			if err != nil {
				return err
			}
Simon Pastor's avatar
Simon Pastor committed
		}
	}
	return nil
}

func appEnablementMecAppReady(appInstanceId string) error {
	appEnablementAppSupportClientCfg := appSupportClient.NewConfiguration()
	appEnablementAppSupportClientCfg.BasePath = appEnablementClientUrl + "/mec_app_support/v1"

	appEnablementAppSupportClient = appSupportClient.NewAPIClient(appEnablementAppSupportClientCfg)
	if appEnablementAppSupportClient == nil {
		log.Error("Failed to create App Enablement App Support REST API client: ", appEnablementAppSupportClientCfg.BasePath)
		err := errors.New("Failed to create App Enablement AppSupport REST API client")
		return err
	}
	var appReady appSupportClient.AppReadyConfirmation
	//indication
	indication := appSupportClient.READY_ReadyIndicationType
	appReady.Indication = &indication
	_, err := appEnablementAppSupportClient.AppConfirmReadyApi.ApplicationsConfirmReadyPOST(context.TODO(), appReady, appInstanceId)
	if err != nil {
		log.Error("Failed to send a ready confirm acknowlegement: ", err)
		return err
	}
	return nil
}

func appEnablementMecAppTermination(appInstanceId string) error {

	if appEnablementAppSupportClient == nil {
		log.Error("App Enablement App Support REST API client should already exist")
		err := errors.New("App Enablement App Support REST API client should already exist")
		return err
	}
	var appTermination appSupportClient.AppTerminationConfirmation
	//operation action
	operationAction := appSupportClient.TERMINATING_OperationActionType
	appTermination.OperationAction = &operationAction
	_, err := appEnablementAppSupportClient.AppConfirmTerminationApi.ApplicationsConfirmTerminationPOST(context.TODO(), appTermination, appInstanceId)
	if err != nil {
		log.Error("Failed to send a confirm termination acknowlegement: ", err)
		return err
	}
	return nil
Simon Pastor's avatar
Simon Pastor committed
func getAppInstanceId(appName string, appVersion string) string {
	//var client *appInfoClient.APIClient
	appInfoClientCfg := appInfoClient.NewConfiguration()
	appInfoClientCfg.BasePath = appEnablementClientUrl + "/app_info/v1"

	client := appInfoClient.NewAPIClient(appInfoClientCfg)
	if client == nil {
		log.Error("Failed to create App Info REST API client: ", appInfoClientCfg.BasePath)
		return ""
	}
	var appInfo appInfoClient.ApplicationInfo
	appInfo.AppName = appName
	appInfo.Version = appVersion
	state := appInfoClient.INACTIVE_ApplicationState
	appInfo.State = &state
	appInfoResponse, _, err := client.AppsApi.ApplicationsPOST(context.TODO(), appInfo)
	if err != nil {
		log.Error("Failed to communicate with app enablement service: ", err)
		return ""
	}
	return appInfoResponse.AppInstanceId
}

func appEnablementRegistration(appInstanceId string, appName string, appVersion string) error {

	appEnablementSrvMgmtClientCfg := srvMgmtClient.NewConfiguration()
	appEnablementSrvMgmtClientCfg.BasePath = appEnablementClientUrl + "/mec_service_mgmt/v1"

	appEnablementSrvMgmtClient = srvMgmtClient.NewAPIClient(appEnablementSrvMgmtClientCfg)
	if appEnablementSrvMgmtClient == nil {
		log.Error("Failed to create App Enablement Srv Mgmt REST API client: ", appEnablementSrvMgmtClientCfg.BasePath)
		err := errors.New("Failed to create App Enablement Srv Mgmt REST API client")
		return err
	}
	var srvInfo srvMgmtClient.ServiceInfoPost
	//serName
	srvInfo.SerName = appName
	//version
	srvInfo.Version = appVersion
	//state
	state := srvMgmtClient.ACTIVE_ServiceState
	srvInfo.State = &state
	//serializer
	serializer := srvMgmtClient.JSON_SerializerType
	srvInfo.Serializer = &serializer

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

	//serCategory
	var category srvMgmtClient.CategoryRef
	category.Href = "catalogueHref"
	category.Id = "waiId"
	category.Name = "WAI"
	category.Version = "v2"
	srvInfo.SerCategory = &category

	//scopeOfLocality
	scopeOfLocality := srvMgmtClient.MEC_SYSTEM_LocalityType
	srvInfo.ScopeOfLocality = &scopeOfLocality

	//consumedLocalOnly
	srvInfo.ConsumedLocalOnly = false

	appServicesPostResponse, _, err := appEnablementSrvMgmtClient.AppServicesApi.AppServicesPOST(context.TODO(), srvInfo, appInstanceId)
	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
}

Simon Pastor's avatar
Simon Pastor committed
/*
func appEnablementAppSupportSubscribe(appInstanceId string) error {

	if appEnablementAppSupportClient == nil {
		log.Error("App Enablement App Support REST API client should already exist")
		err := errors.New("App Enablement App Support REST API client should already exist")
		return err
	}
	var appTerminationNotificationSubscription appSupportClient.AppTerminationNotificationSubscription
	//operation action
	appTerminationNotificationSubscription.SubscriptionType = "AppTerminationNotificationSubscription"
	appTerminationNotificationSubscription.AppInstanceId = appInstanceId
	appTerminationNotificationSubscription.CallbackReference = hostUrl.String() + basePath

	_, _, err := appEnablementAppSupportClient.AppSubscriptionsApi.ApplicationsSubscriptionsPOST(context.TODO(), appTerminationNotificationSubscription, appInstanceId)
	if err != nil {
		log.Error("Failed to register to App Support subscription: ", err)
		return err
	}
	return nil
}
*/
func updateStaInfo(name string, ownMacId string, apMacId string, rssi *int32, sumUl *int32, sumDl *int32) {
	// Get STA Info from DB, if any
	var staData *StaData
	jsonStaData, _ := rc.JSONGetEntry(baseKey+"UE:"+name, ".")
	if jsonStaData != "" {
		staData = convertJsonToStaData(jsonStaData)
	}

	var dataRate StaDataRate
	if sumDl != nil {
		dataRate.StaLastDataDownlinkRate = *sumDl //kbps
	}
	if sumUl != nil {
		dataRate.StaLastDataUplinkRate = *sumUl //kbps
	}
	// Update DB if STA Info does not exist or has changed
	if staData == nil || isStaInfoUpdateRequired(staData.StaInfo, ownMacId, apMacId, rssi, &dataRate) {
		if staData == nil {
			staData = new(StaData)
			staData.StaInfo = new(StaInfo)
			staData.StaInfo.StaId = new(StaIdentity)

		// Set Associated AP, if any
		if apMacId == "" {
			if staData.StaInfo.ApAssociated == nil {
				staData.StaInfo.ApAssociated = new(ApAssociated)
			staData.StaInfo.ApAssociated.Bssid = apMacId
Simon Pastor's avatar
Simon Pastor committed
		if rssi != nil {
			var rssiObj Rssi
			rssiObj.Rssi = *rssi
Simon Pastor's avatar
Simon Pastor committed
		//no need to populate, repetitive since populated in the StaInfo
		//dataRate.StaId = staData.StaInfo.StaId
		staData.StaInfo.StaDataRate = &dataRate

		_ = rc.JSONSetEntry(baseKey+"UE:"+name, ".", convertStaDataToJson(staData))
Simon Pastor's avatar
Simon Pastor committed
		checkStaDataRateNotificationRegisteredSubscriptions(staData.StaInfo.StaId, dataRate.StaLastDataDownlinkRate, dataRate.StaLastDataUplinkRate, true)
func isStaInfoUpdateRequired(staInfo *StaInfo, ownMacId string, apMacId string, rssi *int32, dataRate *StaDataRate) bool {
	// Check if STA Info exists
	if staInfo == nil {
		return true
	}
	// Compare STA Mac
	if ownMacId != staInfo.StaId.MacId {
		return true
	}
	// Compare AP Mac
	if (apMacId == "" && staInfo.ApAssociated != nil) ||
		(apMacId != "" && (staInfo.ApAssociated == nil || apMacId != staInfo.ApAssociated.Bssid)) {
Simon Pastor's avatar
Simon Pastor committed
	if (rssi == nil && staInfo.Rssi != nil) ||
		(rssi != nil && staInfo.Rssi == nil) ||
		(rssi != nil && staInfo.Rssi != nil && *rssi != staInfo.Rssi.Rssi) {

	if dataRate.StaLastDataDownlinkRate != staInfo.StaDataRate.StaLastDataDownlinkRate || dataRate.StaLastDataUplinkRate != staInfo.StaDataRate.StaLastDataUplinkRate {
		return true
	}
Simon Pastor's avatar
Simon Pastor committed
func convertFloatToGeolocationFormat(value *float32) int32 {

Simon Pastor's avatar
Simon Pastor committed
	if value == nil {
		return 0
	}
Simon Pastor's avatar
Simon Pastor committed
	str := fmt.Sprintf("%f", *value)
	strArray := strings.Split(str, ".")
	integerPart, err := strconv.Atoi(strArray[0])
	if err != nil {
		log.Error("Can't convert float to int")
		return 0
	}
	fractionPart, err := strconv.Atoi(strArray[1])
	if err != nil {
		log.Error("Can't convert float to int")
		return 0
	}

	//9 first bits are the integer part, last 23 bits are fraction part
	valueToReturn := (integerPart << 23) + fractionPart
	return int32(valueToReturn)
}

Simon Pastor's avatar
Simon Pastor committed
func isUpdateApInfoNeeded(jsonApInfoComplete string, newLong int32, newLat int32, staMacIds []string) bool {

	var oldStaMacIds []string
	var oldLat int32 = 0
	var oldLong int32 = 0

	if jsonApInfoComplete == "" {
		return true
	} else {
Simon Pastor's avatar
Simon Pastor committed
		apInfoComplete := convertJsonToApInfoComplete(jsonApInfoComplete)
		oldStaMacIds = apInfoComplete.StaMacIds

Simon Pastor's avatar
Simon Pastor committed
		if apInfoComplete.ApLocation.Geolocation != nil {
			oldLat = int32(apInfoComplete.ApLocation.Geolocation.Lat)
			oldLong = int32(apInfoComplete.ApLocation.Geolocation.Long)
Simon Pastor's avatar
Simon Pastor committed
		}
	}
Simon Pastor's avatar
Simon Pastor committed

	//if AP moved
	if oldLat != newLat || oldLong != newLong {
		return true
	}

	//if number of STAs connected changes
	if len(oldStaMacIds) != len(staMacIds) {
		return true
	}

	//if the list of connected STAs is different
	return !reflect.DeepEqual(oldStaMacIds, staMacIds)
}

Simon Pastor's avatar
Simon Pastor committed
func updateApInfo(name string, apMacId string, longitude *float32, latitude *float32, staMacIds []string) {

	//get from DB
	jsonApInfoComplete, _ := rc.JSONGetEntry(baseKey+"AP:"+name, ".")

Simon Pastor's avatar
Simon Pastor committed
	newLat := convertFloatToGeolocationFormat(latitude)
	newLong := convertFloatToGeolocationFormat(longitude)
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
	if isUpdateApInfoNeeded(jsonApInfoComplete, newLong, newLat, staMacIds) {
Simon Pastor's avatar
Simon Pastor committed
		//updateDB
		var apInfoComplete ApInfoComplete
		var apLocation ApLocation
		var geoLocation GeoLocation
		var apId ApIdentity
		geoLocation.Lat = int32(newLat)
		geoLocation.Long = int32(newLong)
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
		apLocation.Geolocation = &geoLocation
Simon Pastor's avatar
Simon Pastor committed
		apInfoComplete.ApLocation = apLocation
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
		apInfoComplete.StaMacIds = staMacIds
Simon Pastor's avatar
Simon Pastor committed
		apInfoComplete.ApId = apId
		_ = rc.JSONSetEntry(baseKey+"AP:"+name, ".", convertApInfoCompleteToJson(&apInfoComplete))
Simon Pastor's avatar
Simon Pastor committed
		checkAssocStaNotificationRegisteredSubscriptions(staMacIds, apMacId, true)
	}
}

func checkAssocStaPeriodTrigger() {

	//loop through every subscriptions, lower period by one and invoke the notification if a check is warranted
	mutex.Lock()
	defer mutex.Unlock()

	if len(assocStaSubscriptionInfoMap) < 1 {
		return
	}

	//decrease all subscriptions
	//check all that applies
	for _, subInfo := range assocStaSubscriptionInfoMap {
		if subInfo != nil {
Simon Pastor's avatar
Simon Pastor committed
			//if periodic check is needed, value is 0
			if subInfo.NextTts != 0 {
Simon Pastor's avatar
Simon Pastor committed
				subInfo.NextTts--
			}
			if subInfo.NextTts == 0 {
Simon Pastor's avatar
Simon Pastor committed
				subInfo.NotificationCheckReady = true
Simon Pastor's avatar
Simon Pastor committed
			} else {
				//subInfo.NextTts = subInfo.Subscription.NotificationPeriod
				subInfo.NotificationCheckReady = false
			}
		}
	}
	//find every AP info and reuse a function to store them all
	var apInfoCompleteResp ApInfoCompleteResp
	apInfoCompleteResp.ApInfoCompleteList = make([]ApInfoComplete, 0)
	keyName := baseKey + "AP:*"
	err := rc.ForEachJSONEntry(keyName, populateApInfoCompleteList, &apInfoCompleteResp)
	if err != nil {
		log.Error(err.Error())
		return
	}

	//loop through the response for each AP and check subscription with no need for mutex (already used)
	for _, apInfoComplete := range apInfoCompleteResp.ApInfoCompleteList {
Simon Pastor's avatar
Simon Pastor committed
		checkAssocStaNotificationRegisteredSubscriptions(apInfoComplete.StaMacIds, apInfoComplete.ApId.Bssid, false)
	}
}

func checkStaDataRatePeriodTrigger() {

	//loop through every subscriptions, lower period by one and invoke the notification if a check is warranted
	mutex.Lock()
	defer mutex.Unlock()

	if len(staDataRateSubscriptionInfoMap) < 1 {
		return
	}
	//decrease all subscriptions
	//check all that applies
	for _, subInfo := range staDataRateSubscriptionInfoMap {
		if subInfo != nil {
Simon Pastor's avatar
Simon Pastor committed
			//if periodic check is needed, value is 0
			if subInfo.NextTts != 0 {
Simon Pastor's avatar
Simon Pastor committed
				subInfo.NextTts--
			}
			if subInfo.NextTts == 0 {
Simon Pastor's avatar
Simon Pastor committed
				subInfo.NotificationCheckReady = true
Simon Pastor's avatar
Simon Pastor committed
			} else {
				//subInfo.NextTts = subInfo.Subscription.NotificationPeriod
				subInfo.NotificationCheckReady = false
			}
		}
	}
	//find every AP info and reuse a function to store them all
	var staInfoResp StaInfoResp
	staInfoResp.StaInfoList = make([]StaInfo, 0)
	keyName := baseKey + "UE:*"
	err := rc.ForEachJSONEntry(keyName, populateStaData, &staInfoResp)
	if err != nil {
		log.Error(err.Error())
		return
	}

	//loop through the response for each AP and check subscription with no need for mutex (already used)
	for _, staInfo := range staInfoResp.StaInfoList {
		dataRate := staInfo.StaDataRate
Simon Pastor's avatar
Simon Pastor committed
		checkStaDataRateNotificationRegisteredSubscriptions(staInfo.StaId, dataRate.StaLastDataDownlinkRate, dataRate.StaLastDataDownlinkRate, false)
Simon Pastor's avatar
Simon Pastor committed
	}
}

func checkForExpiredSubscriptions() {

	nowTime := int(time.Now().Unix())
	mutex.Lock()
	defer mutex.Unlock()
Simon Pastor's avatar
Simon Pastor committed
	for expiryTime, subsIndexList := range subscriptionExpiryMap {
		if expiryTime <= nowTime {
			subscriptionExpiryMap[expiryTime] = nil
			for _, subsId := range subsIndexList {
				if assocStaSubscriptionInfoMap[subsId] != nil {
Simon Pastor's avatar
Simon Pastor committed

					subsIdStr := strconv.Itoa(subsId)

Simon Pastor's avatar
Simon Pastor committed
					var notif ExpiryNotification
					var expiryTimeStamp TimeStamp
					expiryTimeStamp.Seconds = int32(expiryTime)

					link := new(ExpiryNotificationLinks)
					linkType := new(LinkType)
					linkType.Href = assocStaSubscriptionInfoMap[subsId].Subscription.CallbackReference
					link.Subscription = linkType
					notif.Links = link

					notif.ExpiryDeadline = &expiryTimeStamp

					sendExpiryNotification(link.Subscription.Href, notif)
					_ = delSubscription(baseKey+"subscriptions", subsIdStr, true)
				}
				if staDataRateSubscriptionInfoMap[subsId] != nil {

					subsIdStr := strconv.Itoa(subsId)

					var notif ExpiryNotification
Simon Pastor's avatar
Simon Pastor committed
					var expiryTimeStamp TimeStamp
Simon Pastor's avatar
Simon Pastor committed
					expiryTimeStamp.Seconds = int32(expiryTime)

Simon Pastor's avatar
Simon Pastor committed
					link := new(ExpiryNotificationLinks)
					linkType.Href = staDataRateSubscriptionInfoMap[subsId].Subscription.CallbackReference
Simon Pastor's avatar
Simon Pastor committed
					notif.Links = link

					notif.ExpiryDeadline = &expiryTimeStamp

					sendExpiryNotification(link.Subscription.Href, notif)
Simon Pastor's avatar
Simon Pastor committed
					_ = delSubscription(baseKey+"subscriptions", subsIdStr, true)
Simon Pastor's avatar
Simon Pastor committed
func repopulateAssocStaSubscriptionMap(key string, jsonInfo string, userData interface{}) error {
Simon Pastor's avatar
Simon Pastor committed
	var subscription AssocStaSubscription
Simon Pastor's avatar
Simon Pastor committed

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &subscription)
	if err != nil {
		return err
	}

Simon Pastor's avatar
Simon Pastor committed
	selfUrl := strings.Split(subscription.Links.Self.Href, "/")
Simon Pastor's avatar
Simon Pastor committed
	subsIdStr := selfUrl[len(selfUrl)-1]
	subsId, _ := strconv.Atoi(subsIdStr)

	mutex.Lock()
	defer mutex.Unlock()

	assocStaSubscriptionInfoMap[subsId].Subscription = &subscription
Simon Pastor's avatar
Simon Pastor committed
	assocStaSubscriptionInfoMap[subsId].NextTts = 0
Simon Pastor's avatar
Simon Pastor committed
	assocStaSubscriptionInfoMap[subsId].NotificationCheckReady = false //do not send right away, immediateCheck flag for that
Simon Pastor's avatar
Simon Pastor committed
	if subscription.ExpiryDeadline != nil {
		intList := subscriptionExpiryMap[int(subscription.ExpiryDeadline.Seconds)]
		intList = append(intList, subsId)
		subscriptionExpiryMap[int(subscription.ExpiryDeadline.Seconds)] = intList
	}

	//reinitialisation of next available Id for future subscription request
	if subsId >= nextSubscriptionIdAvailable {
		nextSubscriptionIdAvailable = subsId + 1
	}

	return nil
}

func repopulateStaDataRateSubscriptionMap(key string, jsonInfo string, userData interface{}) error {

	var subscription StaDataRateSubscription

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &subscription)
	if err != nil {
		return err
	}

	selfUrl := strings.Split(subscription.Links.Self.Href, "/")
	subsIdStr := selfUrl[len(selfUrl)-1]
	subsId, _ := strconv.Atoi(subsIdStr)

	mutex.Lock()
	defer mutex.Unlock()

	staDataRateSubscriptionInfoMap[subsId].Subscription = &subscription
Simon Pastor's avatar
Simon Pastor committed
	staDataRateSubscriptionInfoMap[subsId].NextTts = 0
Simon Pastor's avatar
Simon Pastor committed
	staDataRateSubscriptionInfoMap[subsId].NotificationCheckReady = false //do not send right away, immediateCheck flag for that
	if subscription.ExpiryDeadline != nil {
		intList := subscriptionExpiryMap[int(subscription.ExpiryDeadline.Seconds)]
		intList = append(intList, subsId)
		subscriptionExpiryMap[int(subscription.ExpiryDeadline.Seconds)] = intList
	}

	//reinitialisation of next available Id for future subscription request
	if subsId >= nextSubscriptionIdAvailable {
		nextSubscriptionIdAvailable = subsId + 1
	}

	return nil
}

Simon Pastor's avatar
Simon Pastor committed
func checkAssocStaNotificationRegisteredSubscriptions(staMacIds []string, apMacId string, needMutex bool) {
	if needMutex {
		mutex.Lock()
		defer mutex.Unlock()
	}
Simon Pastor's avatar
Simon Pastor committed
	//check all that applies
	for subsId, subInfo := range assocStaSubscriptionInfoMap {
		if subInfo == nil {
			break
		}
		sub := subInfo.Subscription
Simon Pastor's avatar
Simon Pastor committed
		match := false
Simon Pastor's avatar
Simon Pastor committed
		sendingNotificationAllowed := true
Simon Pastor's avatar
Simon Pastor committed

		if sub != nil {
Simon Pastor's avatar
Simon Pastor committed
			if !subInfo.NotificationCheckReady {
Simon Pastor's avatar
Simon Pastor committed
				match = true
			}

			if match {
				if sub.NotificationEvent != nil {
					match = false
					switch sub.NotificationEvent.Trigger {
Simon Pastor's avatar
Simon Pastor committed
					case 1:
						if len(staMacIds) >= int(sub.NotificationEvent.Threshold) {
							match = true
						}
Simon Pastor's avatar
Simon Pastor committed
					case 2:
						if len(staMacIds) <= int(sub.NotificationEvent.Threshold) {
							match = true
						}
					default:
					}
Simon Pastor's avatar
Simon Pastor committed
					//if the notification already triggered, do not send it again unless its a periodic event
					if match {
						if sub.NotificationPeriod == 0 {
							if subInfo.Triggered {
								sendingNotificationAllowed = false
							}
						}
					} else {
						// no match found for threshold, reste trigger
						assocStaSubscriptionInfoMap[subsId].Triggered = false
					}
Simon Pastor's avatar
Simon Pastor committed
			if match && sendingNotificationAllowed {

				assocStaSubscriptionInfoMap[subsId].Triggered = true

Simon Pastor's avatar
Simon Pastor committed
				subsIdStr := strconv.Itoa(subsId)
				log.Info("Sending WAIS notification ", sub.CallbackReference)

Simon Pastor's avatar
Simon Pastor committed
				var notif AssocStaNotification
Simon Pastor's avatar
Simon Pastor committed

				seconds := time.Now().Unix()
Simon Pastor's avatar
Simon Pastor committed
				var timeStamp TimeStamp
Simon Pastor's avatar
Simon Pastor committed
				timeStamp.Seconds = int32(seconds)

Simon Pastor's avatar
Simon Pastor committed
				notif.TimeStamp = &timeStamp
Simon Pastor's avatar
Simon Pastor committed
				notif.NotificationType = ASSOC_STA_NOTIFICATION
Simon Pastor's avatar
Simon Pastor committed
				var apId ApIdentity
Simon Pastor's avatar
Simon Pastor committed
				notif.ApId = &apId

				for _, staMacId := range staMacIds {
Simon Pastor's avatar
Simon Pastor committed
					var staId StaIdentity
Simon Pastor's avatar
Simon Pastor committed
					staId.MacId = staMacId
					notif.StaId = append(notif.StaId, staId)
				}

Simon Pastor's avatar
Simon Pastor committed
				sendAssocStaNotification(sub.CallbackReference, notif)
Simon Pastor's avatar
Simon Pastor committed
				log.Info("Assoc Sta Notification" + "(" + subsIdStr + ")")
Simon Pastor's avatar
Simon Pastor committed
				assocStaSubscriptionInfoMap[subsId].NextTts = subInfo.Subscription.NotificationPeriod
				assocStaSubscriptionInfoMap[subsId].NotificationCheckReady = false
Simon Pastor's avatar
Simon Pastor committed
func checkStaDataRateNotificationRegisteredSubscriptions(staId *StaIdentity, dataRateDl int32, dataRateUl int32, needMutex bool) {
	if needMutex {
		mutex.Lock()
		defer mutex.Unlock()
	}
	for subsId, subInfo := range staDataRateSubscriptionInfoMap {
		if subInfo == nil {
			break
		}
		sub := subInfo.Subscription
Simon Pastor's avatar
Simon Pastor committed
			if !subInfo.NotificationCheckReady {
			notifToSend := false
			var staDataRateList []StaDataRate
			for _, subStaId := range sub.StaId {
				//check to match every values and at least one when its an array
				if staId.MacId != subStaId.MacId {
					continue
				}
				if staId.Aid != subStaId.Aid {
					continue
				}
Simon Pastor's avatar
Simon Pastor committed

Simon Pastor's avatar
Simon Pastor committed
				sendingNotificationAllowed := true
				for _, ssid := range subStaId.Ssid {
					match = false
					//can only have one ssid at a time
					if ssid == staId.Ssid[0] {
						match = true
						break
					}
				}
				if match {
					for _, ipAddress := range subStaId.IpAddress {
						match = false
						//can only have one ip address
						if ipAddress == staId.IpAddress[0] {
							match = true
							break
						}
					}
				}
				if match {
					if sub.NotificationEvent != nil {
						match = false
						switch sub.NotificationEvent.Trigger {
Simon Pastor's avatar
Simon Pastor committed
						case 1:
							if dataRateDl >= sub.NotificationEvent.DownlinkRateThreshold {
								match = true
							}
Simon Pastor's avatar
Simon Pastor committed
						case 2:
							if dataRateDl <= sub.NotificationEvent.DownlinkRateThreshold {
								match = true
							}
Simon Pastor's avatar
Simon Pastor committed
						case 3:
							if dataRateUl >= sub.NotificationEvent.UplinkRateThreshold {
								match = true
							}
Simon Pastor's avatar
Simon Pastor committed
						case 4:
							if dataRateUl <= sub.NotificationEvent.UplinkRateThreshold {
Simon Pastor's avatar
Simon Pastor committed
						case 5:
							if dataRateDl >= sub.NotificationEvent.DownlinkRateThreshold && dataRateUl >= sub.NotificationEvent.UplinkRateThreshold {
								match = true
							}
Simon Pastor's avatar
Simon Pastor committed
						case 6:
							if dataRateDl <= sub.NotificationEvent.DownlinkRateThreshold && dataRateUl <= sub.NotificationEvent.UplinkRateThreshold {
								match = true
							}
Simon Pastor's avatar
Simon Pastor committed
						case 7:
							if dataRateDl >= sub.NotificationEvent.DownlinkRateThreshold || dataRateUl >= sub.NotificationEvent.UplinkRateThreshold {
								match = true
							}
Simon Pastor's avatar
Simon Pastor committed
						case 8:
							if dataRateDl <= sub.NotificationEvent.DownlinkRateThreshold || dataRateUl <= sub.NotificationEvent.UplinkRateThreshold {
								match = true
							}
						default:
						}
Simon Pastor's avatar
Simon Pastor committed
						//if the notification already triggered, do not send it again unless its a periodic event
						if match {
							if sub.NotificationPeriod == 0 {
								if subInfo.Triggered {
									sendingNotificationAllowed = false
								}