/*
 * Copyright (c) 2022  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.
 *
 * AdvantEDGE Multi-access Traffic Steering API
 *
 * Multi-access Traffic Steering Sercice is AdvantEDGE's implementation of [ETSI MEC ISG MEC015 Traffic Management APIs](https://www.etsi.org/deliver/etsi_gs/MEC/001_099/015/02.02.01_60/gs_MEC015v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-tm](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-tm/server/mts) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about MTS Info and Session(s) in the network <p>**Note**<br>AdvantEDGE supports all Multi-access Traffic Steering API endpoints.
 *
 * API version: 2.2.1
 * Contact: AdvantEDGE@InterDigital.com
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package server

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	asc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-support-client"
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
	dataModel "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-model"
	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
	mod "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-model"
	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"
	sam "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-swagger-api-mgr"

	"github.com/gorilla/mux"
)

const moduleName = "meep-tm"
const mtsBasePath = "mts/v1/"

// const tmKey = "traffic-mgmt"
const globalMepName = "global"

const defaultMepName = "global"
const defaultScopeOfLocality = "MEC_SYSTEM"
const defaultConsumedLocalOnly = true
const appTerminationPath = "notifications/mec011/appTermination"

const serviceCategory = "MTS"

var sbxCtrlUrl string = "http://meep-sandbox-ctrl"
var Traffic_Mgmt_DB = 0

var instanceId string
var instanceName string

var appEnablementUrl string
var appEnablementEnabled bool

var activeModel *mod.Model
var apiMgr *sam.SwaggerApiMgr
var appSupportClient *asc.APIClient
var svcMgmtClient *smc.APIClient
var sbxCtrlClient *scc.APIClient
var appEnablementServiceId string
var appTermSubId string
var sendAppTerminationWhenDone bool = false
var serviceAppInstanceId string

var globalMtsMode []uint32

var nextSessionIdAvailable int = 0

var localityEnabled bool
var mapLocality map[string]bool

// App Ins ID fields
const fieldState = "state"
const APP_STATE_READY = "READY"
const appEnablementKey = "app-enablement"

var registrationTicker *time.Ticker
var rc *redis.Connector
var hostUrl *url.URL
var sandboxName string
var mepName string = defaultMepName
var scopeOfLocality string = defaultScopeOfLocality
var consumedLocalOnly bool = defaultConsumedLocalOnly
var locality []string
var basePath string
var baseKey string

// var appStore *apps.ApplicationStore
const serviceAppVersion = "2.2.1"

type InitCfg struct {
	SandboxName  string
	MepName      string
	InstanceId   string
	InstanceName string
	BaseKey      string
	HostUrl      *url.URL
	RedisConn    *redis.Connector
	Model        *mod.Model
}
type MtsSessInfoList struct {
	SessionList   []MtsSessionInfo
	AppInstanceId []string
	AppName       []string
	SessionId     []string
}

type flowFilterListCheck struct {
	flowFilterBool bool
	FilterList     []MtsSessionInfoFlowFilter
}

func mec011AppTerminationPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
}

func getAppInstanceId() (id string, err error) {
	var appInfo scc.ApplicationInfo
	appInfo.Id = instanceId
	appInfo.Name = serviceCategory
	appInfo.Type_ = "SYSTEM"
	appInfo.NodeName = mepName
	if mepName == defaultMepName {
		appInfo.Persist = true
	} else {
		appInfo.Persist = false
	}
	response, _, err := sbxCtrlClient.ApplicationsApi.ApplicationsPOST(context.TODO(), appInfo)
	if err != nil {
		log.Error("Failed to get App Instance ID with error: ", err)
		return "", err
	}
	return response.Id, nil
}

func registerService(appInstanceId string) error {
	// Build Service Info
	state := smc.ACTIVE_ServiceState
	serializer := smc.JSON_SerializerType
	transportType := smc.REST_HTTP_TransportType
	localityType := smc.LocalityType(scopeOfLocality)
	srvInfo := smc.ServiceInfoPost{
		SerName:           instanceName,
		Version:           serviceAppVersion,
		State:             &state,
		Serializer:        &serializer,
		ScopeOfLocality:   &localityType,
		ConsumedLocalOnly: consumedLocalOnly,
		TransportInfo: &smc.TransportInfo{
			Id:       "sandboxTransport",
			Name:     "REST",
			Type_:    &transportType,
			Protocol: "HTTP",
			Version:  "2.0",
			Endpoint: &smc.OneOfTransportInfoEndpoint{},
		},
		SerCategory: &smc.CategoryRef{
			Href:    "catalogueHref",
			Id:      "mtsId",
			Name:    serviceCategory,
			Version: "v1",
		},
	}
	srvInfo.TransportInfo.Endpoint.Uris = append(srvInfo.TransportInfo.Endpoint.Uris, hostUrl.String()+basePath)

	appServicesPostResponse, _, err := svcMgmtClient.MecServiceMgmtApi.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)
	appEnablementServiceId = appServicesPostResponse.SerInstanceId
	return nil
}

func sendReadyConfirmation(appInstanceId string) error {
	var appReady asc.AppReadyConfirmation
	appReady.Indication = "READY"
	_, err := appSupportClient.MecAppSupportApi.ApplicationsConfirmReadyPOST(context.TODO(), appReady, appInstanceId)
	if err != nil {
		log.Error("Failed to send a ready confirm acknowlegement: ", err)
		return err
	}
	return nil
}

func subscribeAppTermination(appInstanceId string) error {
	var sub asc.AppTerminationNotificationSubscription
	sub.SubscriptionType = "AppTerminationNotificationSubscription"
	sub.AppInstanceId = appInstanceId
	if mepName == defaultMepName {
		sub.CallbackReference = "http://" + moduleName + "/" + mtsBasePath + appTerminationPath
	} else {
		sub.CallbackReference = "http://" + mepName + "-" + moduleName + "/" + mtsBasePath + appTerminationPath
	}
	subscription, _, err := appSupportClient.MecAppSupportApi.ApplicationsSubscriptionsPOST(context.TODO(), sub, appInstanceId)
	if err != nil {
		log.Error("Failed to register to App Support subscription: ", err)
		return err
	}
	appTermSubLink := subscription.Links.Self.Href
	appTermSubId = appTermSubLink[strings.LastIndex(appTermSubLink, "/")+1:]
	return nil
}

func Init(mtsCfg InitCfg) (err error) {
	sandboxName = mtsCfg.SandboxName
	hostUrl = mtsCfg.HostUrl
	mepName = mtsCfg.MepName
	instanceId = mtsCfg.InstanceId
	instanceName = mtsCfg.InstanceName
	baseKey = mtsCfg.BaseKey
	rc = mtsCfg.RedisConn
	activeModel = mtsCfg.Model

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

	// 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 != "" {
		value, err := strconv.ParseBool(consumedLocalOnlyEnv)
		if err == nil {
			consumedLocalOnly = value
		}
	}
	log.Info("MEEP_CONSUMED_LOCAL_ONLY: ", consumedLocalOnly)

	// Get locality
	localityEnv := strings.TrimSpace(os.Getenv("MEEP_LOCALITY"))
	if localityEnv != "" {
		locality = strings.Split(localityEnv, ":")
	}
	log.Info("MEEP_LOCALITY: ", locality)

	// Fill locality map
	if len(locality) > 0 {
		mapLocality = make(map[string]bool)
		for _, locLocality := range locality {
			mapLocality[locLocality] = true
		}
		localityEnabled = true
	} else {
		localityEnabled = false
	}

	// Set base path
	if mepName == globalMepName {
		basePath = "/" + sandboxName + "/" + mtsBasePath
	} else {
		basePath = "/" + sandboxName + "/" + mepName + "/" + mtsBasePath
	}

	// Create App Enablement REST clients
	if appEnablementEnabled {
		// Create Sandbox Controller client
		sbxCtrlClientCfg := scc.NewConfiguration()
		sbxCtrlClientCfg.BasePath = sbxCtrlUrl + "/sandbox-ctrl/v1"
		sbxCtrlClient = scc.NewAPIClient(sbxCtrlClientCfg)
		if sbxCtrlClient == nil {
			return errors.New("Failed to create Sandbox Controller REST API client")
		}
		log.Info("Create Sandbox Controller 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")
		}
		log.Info("Create App Enablement App Support REST API client")

		// Create App Info 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")
		}
		log.Info("Create App Enablement Service Management REST API client")
	}

	// Store mtsCapabilityInfo key-value in Redis when MTS service is initialized
	err = storeMtsCapabilityInfoKey()
	if err != nil {
		log.Error("Failed to store mtsCapabilityInfo in Redis: ", err.Error())
		return err
	}

	log.Info("MTS successfully initialized")

	return nil
}

// Run - Start MTS
func Run() (err error) {
	// Start MEC Service registration ticker
	if appEnablementEnabled {
		startRegistrationTicker()
	}

	return nil
}

// Stop - Stop MTS
func Stop() (err error) {
	// Stop MEC Service registration ticker
	if appEnablementEnabled {
		stopRegistrationTicker()
	}

	if apiMgr != nil {
		// Remove APIs
		err = apiMgr.RemoveApis()
		if err != nil {
			log.Error("Failed to remove APIs with err: ", err.Error())
			return err
		}
	}

	return nil
}

func startRegistrationTicker() {
	// 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)

	// Start registration ticker
	registrationTicker = time.NewTicker(5 * time.Second)
	go func() {
		mecAppReadySent := false
		registrationSent := false
		subscriptionSent := false
		for range registrationTicker.C {
			// Get Application instance ID
			if serviceAppInstanceId == "" {
				// If a sandbox service, request an app instance ID from Sandbox Controller
				// Otherwise use the scenario-provisioned instance ID
				if mepName == defaultMepName {
					var err error
					serviceAppInstanceId, err = getAppInstanceId()
					if err != nil || serviceAppInstanceId == "" {
						continue
					}
				} else {
					serviceAppInstanceId = instanceId
				}
			}

			// 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
			}

			// 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
			}

			if mecAppReadySent && registrationSent && subscriptionSent {

				// Registration complete
				log.Info("Successfully registered with App Enablement Service")
				stopRegistrationTicker()
				return
			}
		}
	}()
}

func stopRegistrationTicker() {
	if registrationTicker != nil {
		log.Info("Stopping App Enablement registration ticker")
		registrationTicker.Stop()
		registrationTicker = nil
	}
}

/*
* storeMtsCapabilityInfoKey will create mtsCapabilityInfo body and save it in the redis DB in JSON format
* @return {error} err It returns error if error occurs in storing mtsCapabilityInfo in redis DB
 */
func storeMtsCapabilityInfoKey() (err error) {
	var mtsCapabilityInfo MtsCapabilityInfo
	//AccessType 1 refers to Any IEEE802.11-based WLAN technology
	accessInfo1 := MtsCapabilityInfoMtsAccessInfo{
		AccessId:   2001,
		AccessType: 1,
		Metered:    2,
	}
	//AccessType 2 refers to Any 2 = Any 3GPP-based Cellular technology
	accessInfo2 := MtsCapabilityInfoMtsAccessInfo{
		AccessId:   2002,
		AccessType: 2,
		Metered:    2,
	}

	mtsCapabilityInfo.MtsAccessInfo = append(mtsCapabilityInfo.MtsAccessInfo, accessInfo1)
	mtsCapabilityInfo.MtsAccessInfo = append(mtsCapabilityInfo.MtsAccessInfo, accessInfo2)

	mtsCapabilityInfo.MtsMode = []uint32{0, 1, 2, 3, 4}
	globalMtsMode = mtsCapabilityInfo.MtsMode

	seconds := time.Now().Unix()
	nanoseconds := time.Now().UnixNano()

	mtsCapabilityInfo.TimeStamp = &MtsCapabilityInfoTimeStamp{
		NanoSeconds: uint32(nanoseconds),
		Seconds:     uint32(seconds),
	}

	// Make key for MTS Capability Info
	mtsCapInfoKey := baseKey + "mtsCapabilityInfo"

	// Store mtsCapabilityInfo model in the redis DB
	err = rc.JSONSetEntry(mtsCapInfoKey, ".", convertMtsCapabilityInfoToJson(&mtsCapabilityInfo))
	if err != nil {
		return err
	}

	return nil
}

// mtsCapabilityInfoGet is to retrieve mtsCapabilityInfo at /mts_capability_info endpoint
func mtsCapabilityInfoGet(w http.ResponseWriter, r *http.Request) {
	log.Info("mtsCapabilityInfoGet")
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	keyName := baseKey + "mtsCapabilityInfo"

	// retrieve mtsCapabilityInfo from redis DB
	mtsCapabilityInfoJson, _ := rc.JSONGetEntry(keyName, ".")
	if mtsCapabilityInfoJson == "" {
		return
	}

	// Prepare & send mtsCapabilityInfo as a response
	var mtsResp MtsCapabilityInfo
	err := json.Unmarshal([]byte(mtsCapabilityInfoJson), &mtsResp)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError)
		return
	}
	jsonResponse := convertMtsCapabilityInfoToJson(&mtsResp)

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, jsonResponse)
}

// mtsSessionDelete is to delete a specific mtsSessionInfo at /mts_sessions/{sessionId} endpoint
func mtsSessionDelete(w http.ResponseWriter, r *http.Request) {
	log.Info("Delete mtsSessionInfo by sessionId")

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	vars := mux.Vars(r)
	sessionId := vars["sessionId"]

	keyName := baseKey + "mtsSessionInfo:" + sessionId

	// Find mtsSessionInfo entry in redis DB
	_, err := rc.JSONGetEntry(keyName, ".")
	if err != nil {
		err = errors.New("mtsSessionInfo not found against the provided the sessionId")
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusNotFound)
		return
	}

	// Delete mtsSessionInfo entry from redis DB
	err = rc.JSONDelEntry(keyName, ".")
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusNotFound)
		return
	}

	// Send response on successful deletion of MTS session resource
	w.WriteHeader(http.StatusNoContent)
}

// mtsSessionGet is to retrieve a specific mtsSessionInfo at /mts_sessions/{sessionId} endpoint
func mtsSessionGet(w http.ResponseWriter, r *http.Request) {
	log.Info("mtsSessionGet")

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	vars := mux.Vars(r)
	sessionId := vars["sessionId"]

	keyName := baseKey + "mtsSessionInfo:" + sessionId

	// Find mtsSessionInfo entry in redis DB
	mtsSessionJson, err := rc.JSONGetEntry(keyName, ".")
	if err != nil {
		err = errors.New("mtsSessionInfo not found against the provided the sessionId")
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusNotFound)
		return
	}

	// Prepare & send mtsSessionInfo as a response
	var mtsSessionResp MtsSessionInfo
	err = json.Unmarshal([]byte(mtsSessionJson), &mtsSessionResp)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError)
		return
	}
	jsonResponse := convertMtsSessionInfoToJson(&mtsSessionResp)

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, jsonResponse)
}

// mtsSessionPost is to create mtsSessionInfo at /mts_sessions endpoint
func mtsSessionPost(w http.ResponseWriter, r *http.Request) {
	log.Info("mtsSessionPost")

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	var requestBody MtsSessionInfo

	// Read JSON input stream provided in the Request, and stores it in the buffer of a Decoder object
	decoder := json.NewDecoder(r.Body)
	// Decode function return strings containing the text provided in the request body
	err := decoder.Decode(&requestBody)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Validating mandatory parameters provided in the request body
	if requestBody.AppInsId == "" {
		log.Error("Mandatory appInsId parameter should be present")
		errHandlerProblemDetails(w, "Mandatory attribute appInsId is missing in the request body.", http.StatusBadRequest)
		return
	}

	if requestBody.RequestType == nil {
		log.Error("Mandatory requestType parameter should be present")
		errHandlerProblemDetails(w, "Mandatory requestType parameter is missing in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.RequestType > 1 {
		log.Error("Valid requestType value should be present")
		errHandlerProblemDetails(w, "Valid requestType value should be present in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.RequestType == 0 && requestBody.FlowFilter != nil {
		log.Error("flowFilter shall not be present if requestType is 0")
		errHandlerProblemDetails(w, "flowFilter shall not be present if requestType is 0 in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.RequestType == 1 && requestBody.FlowFilter == nil {
		log.Error("flowFilter shall be present if requestType is 1")
		errHandlerProblemDetails(w, "flowFilter shall be present if requestType is 1 in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.RequestType == 1 && len(requestBody.FlowFilter) == 0 {
		log.Error("flowFilter shall be present if requestType is 1")
		errHandlerProblemDetails(w, "flowFilter shall be present if requestType is 1 in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.RequestType == 1 && requestBody.FlowFilter != nil {
		for _, flowFilterVal := range requestBody.FlowFilter {
			if flowFilterVal.SourceIp == "" && *flowFilterVal.SourcePort == 0 && flowFilterVal.DstIp == "" && *flowFilterVal.DstPort == 0 && flowFilterVal.Protocol == nil && flowFilterVal.Dscp == nil && flowFilterVal.Flowlabel == nil {
				log.Error("At least one of flowFilter subfields shall be included")
				errHandlerProblemDetails(w, "At least one of flowFilter subfields shall be included in the request body.", http.StatusBadRequest)
				return
			}
		}
	}

	if *requestBody.RequestType == 1 && requestBody.FlowFilter != nil {
		sessionSlice := make([]MtsSessionInfoFlowFilter, 0)
		for index, singleFlowFilter := range requestBody.FlowFilter {
			if index == 0 {
				sessionSlice = append(sessionSlice, singleFlowFilter)
			} else {
				sessionSlice, err = sessionContains(sessionSlice, singleFlowFilter)
				if err != nil {
					errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
					return
				}
			}
		}
	}

	var mtsCheck bool = false
	for _, x := range globalMtsMode {
		if x == *requestBody.MtsMode {
			mtsCheck = true
			break
		}
	}

	if !mtsCheck {
		log.Error("Valid mtsMode value should be present")
		errHandlerProblemDetails(w, "Valid mtsMode value should be present in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.MtsMode == 4 && requestBody.QosD == nil {
		log.Error("qosD shall be present if mtsMode is equals to 4")
		errHandlerProblemDetails(w, "qosD shall be present if mtsMode is equals to 4 in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBody.MtsMode != 4 && requestBody.QosD != nil {
		log.Error("qosD shall not be present if mtsMode is not 4")
		errHandlerProblemDetails(w, "qosD shall not be present if mtsMode is not 4 in the request body.", http.StatusBadRequest)
		return
	}

	if requestBody.TrafficDirection == "" {
		log.Error("Mandatory trafficDirection parameter is missing")
		errHandlerProblemDetails(w, "Mandatory attribute trafficDirection is missing in the request body.", http.StatusBadRequest)
		return
	}

	if requestBody.TrafficDirection != "00" && requestBody.TrafficDirection != "01" && requestBody.TrafficDirection != "10" {
		log.Error("Valid trafficDirection value should be provided")
		errHandlerProblemDetails(w, "Valid trafficDirection value should be provided in the request body.", http.StatusBadRequest)
		return
	}

	flowFilterList := &flowFilterListCheck{
		flowFilterBool: false,
		FilterList:     requestBody.FlowFilter,
	}

	key := baseKey + "mtsSessionInfo:*"

	if *requestBody.RequestType == 1 {
		// Retrieve MTS sessions from redis DB one by one and store in the mtsSessionInfoList array
		err = rc.ForEachJSONEntry(key, compareFlowFilters, flowFilterList)
		if err != nil {
			errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError)
			return
		}
		//check bool here
		if flowFilterList.flowFilterBool {
			errHandlerProblemDetails(w, "Provide flowFilter matches an already existing session", http.StatusBadRequest)
			return
		}
	}

	// timestamp to send in response
	seconds := time.Now().Unix()
	nanoseconds := time.Now().UnixNano()

	requestBody.TimeStamp = &MtsSessionInfoTimeStamp{
		NanoSeconds: uint32(nanoseconds),
		Seconds:     uint32(seconds),
	}

	if *requestBody.RequestType == 1 {
		switch requestBody.TrafficDirection {
		case "00":
			// if the provided destination IP range matches with the existing UE IP(s)
			err = checkDstIP(&requestBody)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}

		case "01":
			// if the provided source IP range matches with the existing UE IP(s)
			err = checkSrcIP(&requestBody)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}

		case "10":
			// if the provided source IP range matches with the existing UE IP(s)
			err = checkSrcIP(&requestBody)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}
			// if the provided destination IP range matches with the existing UE IP(s)
			err = checkDstIP(&requestBody)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}
		}
	}

	// Validate appInsId from redis DB
	appInfo, err := getAppInfo(requestBody.AppInsId)
	if err != nil {
		errHandlerProblemDetails(w, err.Error(), http.StatusNotFound)
		return
	}

	// Validate state of the provided app ID
	code, problemDetails, err := validateAppInfo(appInfo)
	if err != nil {
		log.Error(err.Error())
		if problemDetails != "" {
			w.WriteHeader(code)
			fmt.Fprint(w, problemDetails)
		} else {
			errHandlerProblemDetails(w, err.Error(), code)
		}
		return
	}

	// session id will be generated sequentially
	nextSessionIdAvailable++
	newSessionId := nextSessionIdAvailable
	sessionIdStr := strconv.Itoa(newSessionId)

	requestBody.SessionId = sessionIdStr

	// Store mtsSessionInfo key in redis
	err = rc.JSONSetEntry(baseKey+"mtsSessionInfo:"+sessionIdStr, ".", convertMtsSessionInfoToJson(&requestBody))
	if err != nil {
		log.Error("Failed to store MtsSessionInfo in the redis DB: ", err)
	}

	// Prepare & send response
	jsonResponse := convertMtsSessionInfoToJson(&requestBody)

	w.WriteHeader(http.StatusCreated)
	fmt.Fprint(w, jsonResponse)
}

/*
* compareFlowFilters compares the requested allocation flowFilters with the existing unique flowFilters stored in redis of allocated resources
* @param {string} key	mts Session Informations stored with this key
* @param {string} jsonInfo mts Session Information
* @return {String} error error message
 */
func compareFlowFilters(key string, jsonInfo string, flowFilterList interface{}) error {

	// Get query params & mtsSessionInfo
	data := flowFilterList.(*flowFilterListCheck)
	if data == nil {
		return errors.New("mtsSessionInfo list not found")
	}

	// Retrieve mtsSessionInfo from DB
	var filterInfo MtsSessionInfo
	err := json.Unmarshal([]byte(jsonInfo), &filterInfo)
	if err != nil {
		return err
	}

	for _, flowFilterData := range data.FilterList {
		for _, redisFlowFilterData := range filterInfo.FlowFilter {
			if reflect.DeepEqual(flowFilterData, redisFlowFilterData) {
				data.flowFilterBool = true
				return nil
			}
		}
	}

	return nil
}

/*
* checkSrcIP validate source ip range passed in CIDR notation with existing UE ip
* @param {*MtsSessionInfo} MTS session request body of POST method
* @return {error} err It returns err if the provided IP range is not valid against the existing UE IP (s)
 */
func checkSrcIP(requestBody *MtsSessionInfo) (err error) {
	ueNameList := activeModel.GetNodeNames("UE")
	// if the provided source IP range matches with the existing UE IP(s)
	if len(ueNameList) > 0 {
		for _, ip := range requestBody.FlowFilter {
			ipFound := true
			if ip.SourceIp != "" {
				ipName := net.ParseIP(ip.SourceIp)
				if ipName != nil {
					for _, name := range ueNameList {
						// Ignore disconnected UEs
						if !isUeConnected(name) || !isInLocality(name) {
							continue
						}
						if ipName.String() == name {
							log.Info("Provided source IP range is valid against the existing UE IPs")
							ipFound = true
							break
						} else {
							ipFound = false
						}
					}
				}
				// Return error if IP not in ueNameList
				if !ipFound {
					log.Error("Provided source IP range is not valid against the existing UE IPs")
					err = errors.New("Provided source IP range is not valid against the existing UE IPs in the request body.")
					return err
				}
			}
		}
	}
	return nil
}

/*
* checkDstIP validate destination ip range passed in CIDR notation with existing UE IPs
* @param {*MtsSessionInfo} MTS session request body of POST method
* @return {error} err It returns err if the provided IP range is not valid against the existing UE IP (s)
 */
func checkDstIP(requestBody *MtsSessionInfo) (err error) {
	ueNameList := activeModel.GetNodeNames("UE")
	// if the provided destination IP range matches with the existing UE IP(s)
	if len(ueNameList) > 0 {
		for _, ip := range requestBody.FlowFilter {
			ipFound := true
			if ip.DstIp != "" {
				ipName := net.ParseIP(ip.DstIp)
				if ipName != nil {
					for _, name := range ueNameList {
						// Ignore disconnected UEs
						if !isUeConnected(name) || !isInLocality(name) {
							continue
						}
						if ipName.String() == name {
							log.Info("Provided destination IP range is valid against the existing UE IPs")
							ipFound = true
							break
						} else {
							ipFound = false
						}
					}
				}
				// Return error if IP not in ueNameList
				if !ipFound {
					log.Error("Provided destination IP range is not valid against the existing UE IPs")
					err = errors.New("Provided destination IP range is not valid against the existing UE IPs in the request body.")
					return err
				}
			}
		}
	}
	return nil
}

/*
* isInLocality validate UE ip only when they are in locality of current active model
* @param {string} name name(IP) of a UE
* @return {bool} bool It returns the locality status of UE present in the active model zone
 */
func isInLocality(name string) bool {
	if localityEnabled {
		ctx := activeModel.GetNodeContext(name)
		if ctx == nil {
			log.Error("Error getting context for: " + name)
			return false
		}
		if _, found := mapLocality[ctx.Parents[mod.Zone]]; !found {
			return false
		}
	}
	return true
}

/*
* isUeConnected validate UE ip with connected UE(s) in the current active model
* @param {string} name name (IP) of a UE
* @return {bool} bool It returns the status of network connectivity of UE
 */
func isUeConnected(name string) bool {
	node := activeModel.GetNode(name)
	if node != nil {
		pl := node.(*dataModel.PhysicalLocation)
		return pl.Connected
	}
	return false
}

/*
* getAppInfo gets application information using its application Instance Id stored in redis
* @param {string} appId application Instance Id used to retrive its information from redis DB
*	@return {map[string]string} appInfo application information
* @return {error} err It returns error if there is no information associated with this appId
 */
func getAppInfo(appId string) (map[string]string, error) {
	var appInfo map[string]string

	// Get app instance from local MEP only
	baseKeyAppEn := dkm.GetKeyRoot(sandboxName) + appEnablementKey + ":mep:" + mepName + ":"
	key := baseKeyAppEn + "app:" + appId + ":info"
	appInfo, err := rc.GetEntry(key)
	if err != nil || len(appInfo) == 0 {
		return nil, errors.New("App Instance not found")
	}
	return appInfo, nil
}

/*
* validateAppInfo validates the status information of application to be in ready state
* @param {map[string]string} appInfo Information associated with application
* @return {int} int It returns the http status for validation
* @return {string} string It returns the details of the problem if any
* @return {error} error It returns the error message for error if any otherwise nil
 */
func validateAppInfo(appInfo map[string]string) (int, string, error) {
	// Make sure App is in ready state
	if appInfo[fieldState] != APP_STATE_READY {
		var problemDetails ProblemDetails
		problemDetails.Status = http.StatusForbidden
		problemDetails.Detail = "App Instance not ready. Waiting for AppReadyConfirmation."
		return http.StatusForbidden, convertProblemDetailsToJson(&problemDetails), errors.New("App Instance not ready")
	}
	return http.StatusOK, "", nil
}

// mtsSessionPut updates the information about a specific mtsSessionInfo at /mts_sessions/{sessionId} endpoint
func mtsSessionPut(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	log.Info("mtsSessionPut Intializing")
	vars := mux.Vars(r)
	sessionIdStr := vars["sessionId"]

	var requestBodyPut MtsSessionInfo

	// Read JSON input stream provided in the Request, and stores it in the buffer of a Decoder object
	decoder := json.NewDecoder(r.Body)
	// Decode function return strings containing the text provided in the request body
	err := decoder.Decode(&requestBodyPut)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
		return
	}

	jsonMtsSessionInfo, _ := rc.JSONGetEntry(baseKey+"mtsSessionInfo:"+sessionIdStr, ".")

	if jsonMtsSessionInfo == "" {
		log.Error("mtsSession not found against the provided sessionId")
		errHandlerProblemDetails(w, "mtsSession not found against the provided sessionId", http.StatusNotFound)
		return
	}

	if requestBodyPut.SessionId != "" {
		if sessionIdStr != requestBodyPut.SessionId {
			log.Error("sessionId provided in endpoint and in request body not matching")
			errHandlerProblemDetails(w, "sessionId provided in endpoint and in request body not matching", http.StatusNotFound)
			return
		}
	}

	var storedMtsSession MtsSessionInfo
	err = json.Unmarshal([]byte(jsonMtsSessionInfo), &storedMtsSession)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Validating mandatory parameters in request
	if requestBodyPut.AppInsId != "" {
		if requestBodyPut.AppInsId != storedMtsSession.AppInsId {
			// Validate appInsId from redis DB
			appInfo, err := getAppInfo(requestBodyPut.AppInsId)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusNotFound)
				return
			}

			// Validate state of the provided app ID
			code, problemDetails, err := validateAppInfo(appInfo)
			if err != nil {
				log.Error(err.Error())
				if problemDetails != "" {
					w.WriteHeader(code)
					fmt.Fprint(w, problemDetails)
				} else {
					errHandlerProblemDetails(w, err.Error(), code)
				}
				return
			}
		}
	} else {
		log.Error("Mandatory appInsId parameter should be present")
		errHandlerProblemDetails(w, "Mandatory attribute appInsId is missing in the request body.", http.StatusBadRequest)
		return
	}

	if requestBodyPut.RequestType != nil {
		if *requestBodyPut.RequestType > 1 {
			log.Error("Valid requestType value should be present")
			errHandlerProblemDetails(w, "Valid requestType value should be present in the request body.", http.StatusBadRequest)
			return
		}

		if *requestBodyPut.RequestType == 0 && requestBodyPut.FlowFilter != nil {
			log.Error("flowFilter shall not be present if requestType is 0")
			errHandlerProblemDetails(w, "flowFilter shall not be present if requestType is 0 in the request body.", http.StatusBadRequest)
			return
		}

		if *requestBodyPut.RequestType == 1 && requestBodyPut.FlowFilter == nil {
			log.Error("flowFilter shall be present if requestType is 1")
			errHandlerProblemDetails(w, "flowFilter shall be present if requestType is 1 in the request body.", http.StatusBadRequest)
			return
		}

		if *requestBodyPut.RequestType == 1 && len(requestBodyPut.FlowFilter) == 0 {
			log.Error("flowFilter shall be present if requestType is 1")
			errHandlerProblemDetails(w, "flowFilter shall be present if requestType is 1 in the request body.", http.StatusBadRequest)
			return
		}

	} else {
		log.Error("Mandatory requestType parameter should be present")
		errHandlerProblemDetails(w, "Mandatory attribute requestType is missing in the request body.", http.StatusBadRequest)
		return
	}

	if *requestBodyPut.RequestType == 1 && requestBodyPut.FlowFilter != nil {
		for _, flowFilterVal := range requestBodyPut.FlowFilter {
			if flowFilterVal.SourceIp == "" && *flowFilterVal.SourcePort == 0 && flowFilterVal.DstIp == "" && *flowFilterVal.DstPort == 0 && flowFilterVal.Protocol == nil && flowFilterVal.Dscp == nil && flowFilterVal.Flowlabel == nil {
				log.Error("At least one of flowFilter subfields shall be included")
				errHandlerProblemDetails(w, "At least one of flowFilter subfields shall be included in the request body.", http.StatusBadRequest)
				return
			}
		}
	}

	if *requestBodyPut.RequestType == 1 && requestBodyPut.FlowFilter != nil {
		sessionSlice := make([]MtsSessionInfoFlowFilter, 0)
		for index, singleSessionFilter := range requestBodyPut.FlowFilter {
			if index == 0 {
				sessionSlice = append(sessionSlice, singleSessionFilter)
			} else {
				sessionSlice, err = sessionContains(sessionSlice, singleSessionFilter)
				if err != nil {
					errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
					return
				}
			}
		}
	}

	if requestBodyPut.MtsMode != nil {
		var mtsCheck bool = false
		for _, x := range globalMtsMode {
			if x == *requestBodyPut.MtsMode {
				mtsCheck = true
				break
			}
		}

		if !mtsCheck {
			log.Error("Valid mtsMode value should be present")
			errHandlerProblemDetails(w, "Valid mtsMode value should be present in the request body.", http.StatusBadRequest)
			return
		}

		if *requestBodyPut.MtsMode == 4 && requestBodyPut.QosD == nil {
			log.Error("qosD shall be present if mtsMode is equals to 4")
			errHandlerProblemDetails(w, "qosD shall be present if mtsMode is equals to 4 in the request body.", http.StatusBadRequest)
			return
		}

		if *requestBodyPut.MtsMode != 4 && requestBodyPut.QosD != nil {
			log.Error("qosD shall not be present if mtsMode is not 4")
			errHandlerProblemDetails(w, "qosD shall not be present if mtsMode is not 4 in the request body.", http.StatusBadRequest)
			return
		}
	} else {
		log.Error("Mandatory mtsMode parameter should be present")
		errHandlerProblemDetails(w, "Mandatory attribute mtsMode is missing in the request body.", http.StatusBadRequest)
		return
	}

	if requestBodyPut.TrafficDirection != "" {
		if requestBodyPut.TrafficDirection != "00" && requestBodyPut.TrafficDirection != "01" && requestBodyPut.TrafficDirection != "10" {
			log.Error("Valid trafficDirection value should be provided")
			errHandlerProblemDetails(w, "Valid trafficDirection value should be provided in the request body.", http.StatusBadRequest)
			return
		}
	} else {
		log.Error("Mandatory trafficDirection parameter should be present")
		errHandlerProblemDetails(w, "Mandatory attribute trafficDirection is missing in the request body.", http.StatusBadRequest)
		return
	}

	// timestamp to send in response
	seconds := time.Now().Unix()
	nanoseconds := time.Now().UnixNano()

	requestBodyPut.TimeStamp = &MtsSessionInfoTimeStamp{
		NanoSeconds: uint32(nanoseconds),
		Seconds:     uint32(seconds),
	}

	if *requestBodyPut.RequestType == 0 && *requestBodyPut.RequestType != *storedMtsSession.RequestType {
		requestBodyPut.FlowFilter = nil
	} else if *requestBodyPut.RequestType == 1 && *requestBodyPut.RequestType != *storedMtsSession.RequestType {
		switch requestBodyPut.TrafficDirection {
		case "00":
			// if the provided destination IP range matches with the existing UE IP(s)
			err = checkDstIP(&requestBodyPut)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}

		case "01":
			// if the provided source IP range matches with the existing UE IP(s)
			err = checkSrcIP(&requestBodyPut)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}

		case "10":
			// if the provided source IP range matches with the existing UE IP(s)
			err = checkSrcIP(&requestBodyPut)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}
			// if the provided destination IP range matches with the existing UE IP(s)
			err = checkDstIP(&requestBodyPut)
			if err != nil {
				errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
				return
			}
		}
	}

	requestBodyPut.SessionId = sessionIdStr

	// Store mtsSessionInfo key in redis
	err = rc.JSONSetEntry(baseKey+"mtsSessionInfo:"+sessionIdStr, ".", convertMtsSessionInfoToJson(&requestBodyPut))
	if err != nil {
		log.Error("Failed to store MtsSessionInfo in the redis DB: ", err)
	}

	// Prepare & send response
	jsonResponse := convertMtsSessionInfoToJson(&requestBodyPut)

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, jsonResponse)
}

// mtsSessionsListGet is to retrieve the information about all existing mtsSessionInfo at /mts_sessions endpoint
func mtsSessionsListGet(w http.ResponseWriter, r *http.Request) {
	log.Info("mtsSessionsListGet")
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	// Validate query parameters
	u, _ := url.Parse(r.URL.String())
	q := u.Query()

	appInstanceId := q["app_instance_id"]
	appName := q["app_name"]
	sessionId := q["session_id"]

	validQueryParams := []string{"app_instance_id", "app_name", "session_id"}
	err := validateQueryParams(q, validQueryParams)
	if err != nil {
		errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
		return
	}

	// MtsSessInfoList struct is passed to mtsSessionInfoList as a pointer
	// MtsSessInfoList is a struct which has four fields
	// SessionList here is the array of the MtsSessionInfo struct.
	mtsSessionInfoList := &MtsSessInfoList{
		AppInstanceId: appInstanceId,
		AppName:       appName,
		SessionId:     sessionId,
		SessionList:   make([]MtsSessionInfo, 0),
	}

	// Make sure only 1 or none of the following are present: appInstanceId, appName, sessionId
	err = validateMtsSesInfoQueryParams(appInstanceId, appName, sessionId)
	if err != nil {
		errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
		return
	}

	key := baseKey + "mtsSessionInfo:*"

	// Retrieve MTS sessions from redis DB one by one and store in the mtsSessionInfoList array
	err = rc.ForEachJSONEntry(key, populateMtsSessInfoList, mtsSessionInfoList)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Prepare & send response
	jsonResponse, err := json.Marshal(mtsSessionInfoList.SessionList)
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, string(jsonResponse))
}

/**
* Function that list down all the MTS session(s) based on query parameters
* @param {string} key	MTS session stored with this key
* @param {string} jsonInfo MTS session information
* @param {*MtsSessInfoList} mtsSessionInfoList MTS session information
* @return {error} error An error will be return if occurs
 */
func populateMtsSessInfoList(key string, jsonInfo string, mtsSessionInfoList interface{}) error {
	// Get query params & mtsSessionInfo
	data := mtsSessionInfoList.(*MtsSessInfoList)
	if data == nil {
		return errors.New("mtsSessionInfo list not found")
	}

	// Retrieve mtsSessionInfo from DB
	var sessionInfo MtsSessionInfo
	err := json.Unmarshal([]byte(jsonInfo), &sessionInfo)
	if err != nil {
		return err
	}

	foundAMatch := false
	// Filter using query params
	if len(data.AppInstanceId) > 0 {
		foundAMatch = false
		for _, QueryAppInstanceId := range data.AppInstanceId {
			if sessionInfo.AppInsId == QueryAppInstanceId {
				foundAMatch = true
			}
		}
		if !foundAMatch {
			return nil
		}
	}

	if len(data.AppName) > 0 {
		foundAMatch = false
		for _, QueryAppName := range data.AppName {
			if sessionInfo.AppName == QueryAppName {
				foundAMatch = true
			}
		}
		if !foundAMatch {
			return nil
		}
	}

	if len(data.SessionId) > 0 {
		foundAMatch = false
		for _, QuerySessionId := range data.SessionId {
			if sessionInfo.SessionId == QuerySessionId {
				foundAMatch = true
			}
		}
		if !foundAMatch {
			return nil
		}
	}

	data.SessionList = append(data.SessionList, sessionInfo)
	return nil
}

/*
	* validateQueryParams ensures that valid query parameters should be used to retrieve one of the
		app_instance_id or app_name or session_id attributes from the user
	* @return {error} error An error will be return if occurs
*/
func validateQueryParams(params url.Values, validParams []string) error {
	for param := range params {
		found := false
		for _, validParam := range validParams {
			if param == validParam {
				found = true
				break
			}
		}
		if !found {
			err := errors.New("Invalid query param: " + param)
			log.Error(err.Error())
			return err
		}
	}
	return nil
}

/*
	* validateMtsSesInfoQueryParams check that either app_instance_id or app_name or session_id or
		none should be provided in the request
	* @return {error} error An error will be return if occurs
*/
func validateMtsSesInfoQueryParams(appInstanceId []string, appName []string, sessionId []string) error {
	count := 0
	if len(appInstanceId) != 0 {
		count++
	}
	if len(appName) != 0 {
		count++
	}
	if len(sessionId) != 0 {
		count++
	}
	if count > 1 {
		err := errors.New("Either app_instance_id or app_name or session_id or none of them shall be present")
		log.Error(err.Error())
		return err
	}
	return nil
}

func errHandlerProblemDetails(w http.ResponseWriter, error string, code int) {
	var pd ProblemDetails
	pd.Detail = error
	pd.Status = uint32(code)

	jsonResponse := convertProblemDetailsToJson(&pd)

	w.WriteHeader(code)
	fmt.Fprint(w, jsonResponse)
}

/*
* sessionContains compares the slice of unique sessions with the passed single session of the requested allocation
* @param {[]MtsSessionInfoFlowFilter} sessionSlice	slice contains the unique FlowFilter
* @param {MtsSessionInfoFlowFilter} singleFlowFilter	single FlowFilter to be compared
* @return {[]MtsSessionInfoFlowFilter} returns the updated slice of FlowFilter
* @return {error} An error will be return if occurs
 */
func sessionContains(sessionSlice []MtsSessionInfoFlowFilter, singleFlowFilter MtsSessionInfoFlowFilter) ([]MtsSessionInfoFlowFilter, error) {
	for _, sessionSlice1 := range sessionSlice {
		if reflect.DeepEqual(singleFlowFilter, sessionSlice1) {
			err := errors.New("flowFilter contains duplicate sessions")
			return nil, err
		} else {
			sessionSlice = append(sessionSlice, singleFlowFilter)
		}
	}
	return sessionSlice, nil
}
