mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
397 lines
14 KiB
Go
397 lines
14 KiB
Go
/*
|
|
Real-time Charging System for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package sessionmanager
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cgrates/cgrates/config"
|
|
"github.com/cgrates/cgrates/engine"
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
const (
|
|
EVENT = "event"
|
|
CGR_AUTH_REQUEST = "CGR_AUTH_REQUEST"
|
|
CGR_LCR_REQUEST = "CGR_LCR_REQUEST"
|
|
CGR_AUTH_REPLY = "CGR_AUTH_REPLY"
|
|
CGR_LCR_REPLY = "CGR_LCR_REPLY"
|
|
CGR_SESSION_DISCONNECT = "CGR_SESSION_DISCONNECT"
|
|
CGR_CALL_START = "CGR_CALL_START"
|
|
CGR_CALL_END = "CGR_CALL_END"
|
|
CGR_SETUPTIME = "cgr_setuptime"
|
|
CGR_ANSWERTIME = "cgr_answertime"
|
|
CGR_STOPTIME = "cgr_stoptime"
|
|
CGR_DURATION = "cgr_duration"
|
|
CGR_PDD = "cgr_pdd"
|
|
|
|
KAM_TR_INDEX = "tr_index"
|
|
KAM_TR_LABEL = "tr_label"
|
|
HASH_ENTRY = "h_entry"
|
|
HASH_ID = "h_id"
|
|
)
|
|
|
|
var primaryFields = []string{EVENT, CALLID, FROM_TAG, HASH_ENTRY, HASH_ID, CGR_ACCOUNT, CGR_SUBJECT, CGR_DESTINATION,
|
|
CGR_CATEGORY, CGR_TENANT, CGR_REQTYPE, CGR_ANSWERTIME, CGR_SETUPTIME, CGR_STOPTIME, CGR_DURATION, CGR_PDD, utils.CGR_SUPPLIER, utils.CGR_DISCONNECT_CAUSE}
|
|
|
|
type KamAuthReply struct {
|
|
Event string // Kamailio will use this to differentiate between requests and replies
|
|
TransactionIndex int // Original transaction index
|
|
TransactionLabel int // Original transaction label
|
|
MaxSessionTime int // Maximum session time in case of success, -1 for unlimited
|
|
Suppliers string // List of suppliers, comma separated
|
|
Error string // Reply in case of error
|
|
}
|
|
|
|
func (self *KamAuthReply) String() string {
|
|
mrsh, _ := json.Marshal(self)
|
|
return string(mrsh)
|
|
}
|
|
|
|
type KamLcrReply struct {
|
|
Event string
|
|
Suppliers string
|
|
Error error
|
|
}
|
|
|
|
func (self *KamLcrReply) String() string {
|
|
self.Event = CGR_LCR_REPLY
|
|
mrsh, _ := json.Marshal(self)
|
|
return string(mrsh)
|
|
}
|
|
|
|
type KamSessionDisconnect struct {
|
|
Event string
|
|
HashEntry string
|
|
HashId string
|
|
Reason string
|
|
}
|
|
|
|
func (self *KamSessionDisconnect) String() string {
|
|
mrsh, _ := json.Marshal(self)
|
|
return string(mrsh)
|
|
}
|
|
|
|
func NewKamEvent(kamEvData []byte) (KamEvent, error) {
|
|
kev := make(map[string]string)
|
|
if err := json.Unmarshal(kamEvData, &kev); err != nil {
|
|
return nil, err
|
|
}
|
|
return kev, nil
|
|
}
|
|
|
|
// Hold events received from Kamailio
|
|
type KamEvent map[string]string
|
|
|
|
// Backwards compatibility, should be AsEvent
|
|
func (kev KamEvent) AsEvent(ignored string) engine.Event {
|
|
return engine.Event(kev)
|
|
}
|
|
|
|
func (kev KamEvent) GetName() string {
|
|
return kev[EVENT]
|
|
}
|
|
func (kev KamEvent) GetCgrId() string {
|
|
setupTime, _ := kev.GetSetupTime(utils.META_DEFAULT)
|
|
return utils.Sha1(kev.GetUUID(), setupTime.UTC().String())
|
|
}
|
|
func (kev KamEvent) GetUUID() string {
|
|
return kev[CALLID] + ";" + kev[FROM_TAG] // ToTag not available in callStart event
|
|
}
|
|
func (kev KamEvent) GetSessionIds() []string {
|
|
return []string{kev[HASH_ENTRY], kev[HASH_ID]}
|
|
}
|
|
func (kev KamEvent) GetDirection(fieldName string) string {
|
|
return utils.OUT
|
|
}
|
|
func (kev KamEvent) GetAccount(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[CGR_ACCOUNT])
|
|
}
|
|
func (kev KamEvent) GetSubject(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[CGR_SUBJECT], kev[CGR_ACCOUNT])
|
|
}
|
|
func (kev KamEvent) GetDestination(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[CGR_DESTINATION])
|
|
}
|
|
func (kev KamEvent) GetCallDestNr(fieldName string) string {
|
|
return kev.GetDestination(utils.META_DEFAULT)
|
|
}
|
|
func (kev KamEvent) GetCategory(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[CGR_CATEGORY], config.CgrConfig().DefaultCategory)
|
|
}
|
|
func (kev KamEvent) GetTenant(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[CGR_TENANT], config.CgrConfig().DefaultTenant)
|
|
}
|
|
func (kev KamEvent) GetReqType(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[CGR_REQTYPE], config.CgrConfig().DefaultReqType)
|
|
}
|
|
func (kev KamEvent) GetAnswerTime(fieldName string) (time.Time, error) {
|
|
aTimeStr := utils.FirstNonEmpty(kev[fieldName], kev[CGR_ANSWERTIME])
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
aTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.ParseTimeDetectLayout(aTimeStr)
|
|
}
|
|
func (kev KamEvent) GetSetupTime(fieldName string) (time.Time, error) {
|
|
sTimeStr := utils.FirstNonEmpty(kev[fieldName], kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME])
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
sTimeStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.ParseTimeDetectLayout(sTimeStr)
|
|
}
|
|
func (kev KamEvent) GetEndTime() (time.Time, error) {
|
|
return utils.ParseTimeDetectLayout(kev[CGR_STOPTIME])
|
|
}
|
|
func (kev KamEvent) GetDuration(fieldName string) (time.Duration, error) {
|
|
durStr := utils.FirstNonEmpty(kev[fieldName], kev[CGR_DURATION])
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
durStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.ParseDurationWithSecs(durStr)
|
|
}
|
|
func (kev KamEvent) GetPdd(fieldName string) (time.Duration, error) {
|
|
var pddStr string
|
|
if utils.IsSliceMember([]string{utils.PDD, utils.META_DEFAULT}, fieldName) {
|
|
pddStr = kev[CGR_PDD]
|
|
} else if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
pddStr = fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
} else {
|
|
pddStr = kev[fieldName]
|
|
}
|
|
return utils.ParseDurationWithSecs(pddStr)
|
|
}
|
|
func (kev KamEvent) GetSupplier(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[utils.CGR_SUPPLIER])
|
|
}
|
|
|
|
func (kev KamEvent) GetDisconnectCause(fieldName string) string {
|
|
if strings.HasPrefix(fieldName, utils.STATIC_VALUE_PREFIX) { // Static value
|
|
return fieldName[len(utils.STATIC_VALUE_PREFIX):]
|
|
}
|
|
return utils.FirstNonEmpty(kev[fieldName], kev[utils.CGR_DISCONNECT_CAUSE])
|
|
}
|
|
|
|
//ToDo: extract the IP of the kamailio server generating the event
|
|
func (kev KamEvent) GetOriginatorIP(string) string {
|
|
return "127.0.0.1"
|
|
}
|
|
func (kev KamEvent) GetExtraFields() map[string]string {
|
|
extraFields := make(map[string]string)
|
|
for field, val := range kev {
|
|
if !utils.IsSliceMember(primaryFields, field) {
|
|
extraFields[field] = val
|
|
}
|
|
}
|
|
return extraFields
|
|
}
|
|
func (kev KamEvent) GetCdrSource() string {
|
|
return "KAMAILIO_" + kev.GetName()
|
|
}
|
|
|
|
func (kev KamEvent) MissingParameter() bool {
|
|
var nullTime time.Time
|
|
switch kev.GetName() {
|
|
case CGR_AUTH_REQUEST:
|
|
if setupTime, err := kev.GetSetupTime(utils.META_DEFAULT); err != nil || setupTime == nullTime {
|
|
return true
|
|
}
|
|
return len(kev.GetAccount(utils.META_DEFAULT)) == 0 ||
|
|
len(kev.GetDestination(utils.META_DEFAULT)) == 0 ||
|
|
len(kev[KAM_TR_INDEX]) == 0 || len(kev[KAM_TR_LABEL]) == 0
|
|
case CGR_LCR_REQUEST:
|
|
return len(kev.GetAccount(utils.META_DEFAULT)) == 0 ||
|
|
len(kev.GetDestination(utils.META_DEFAULT)) == 0 ||
|
|
len(kev[KAM_TR_INDEX]) == 0 || len(kev[KAM_TR_LABEL]) == 0
|
|
case CGR_CALL_START:
|
|
if aTime, err := kev.GetAnswerTime(utils.META_DEFAULT); err != nil || aTime == nullTime {
|
|
return true
|
|
}
|
|
return len(kev.GetUUID()) == 0 ||
|
|
len(kev.GetAccount(utils.META_DEFAULT)) == 0 ||
|
|
len(kev.GetDestination(utils.META_DEFAULT)) == 0 ||
|
|
len(kev[HASH_ENTRY]) == 0 || len(kev[HASH_ID]) == 0
|
|
case CGR_CALL_END:
|
|
return len(kev.GetUUID()) == 0 ||
|
|
len(kev.GetAccount(utils.META_DEFAULT)) == 0 ||
|
|
len(kev.GetDestination(utils.META_DEFAULT)) == 0 ||
|
|
len(kev[CGR_DURATION]) == 0
|
|
default:
|
|
return true
|
|
}
|
|
|
|
}
|
|
|
|
// Useful for CDR generation
|
|
func (kev KamEvent) ParseEventValue(rsrFld *utils.RSRField) string {
|
|
sTime, _ := kev.GetSetupTime(utils.META_DEFAULT)
|
|
aTime, _ := kev.GetAnswerTime(utils.META_DEFAULT)
|
|
duration, _ := kev.GetDuration(utils.META_DEFAULT)
|
|
switch rsrFld.Id {
|
|
case utils.CGRID:
|
|
return rsrFld.ParseValue(kev.GetCgrId())
|
|
case utils.TOR:
|
|
return rsrFld.ParseValue(utils.VOICE)
|
|
case utils.ACCID:
|
|
return rsrFld.ParseValue(kev.GetUUID())
|
|
case utils.CDRHOST:
|
|
return rsrFld.ParseValue(kev.GetOriginatorIP(utils.META_DEFAULT))
|
|
case utils.CDRSOURCE:
|
|
return rsrFld.ParseValue(kev.GetCdrSource())
|
|
case utils.REQTYPE:
|
|
return rsrFld.ParseValue(kev.GetReqType(utils.META_DEFAULT))
|
|
case utils.DIRECTION:
|
|
return rsrFld.ParseValue(kev.GetDirection(utils.META_DEFAULT))
|
|
case utils.TENANT:
|
|
return rsrFld.ParseValue(kev.GetTenant(utils.META_DEFAULT))
|
|
case utils.CATEGORY:
|
|
return rsrFld.ParseValue(kev.GetCategory(utils.META_DEFAULT))
|
|
case utils.ACCOUNT:
|
|
return rsrFld.ParseValue(kev.GetAccount(utils.META_DEFAULT))
|
|
case utils.SUBJECT:
|
|
return rsrFld.ParseValue(kev.GetSubject(utils.META_DEFAULT))
|
|
case utils.DESTINATION:
|
|
return rsrFld.ParseValue(kev.GetDestination(utils.META_DEFAULT))
|
|
case utils.SETUP_TIME:
|
|
return rsrFld.ParseValue(sTime.String())
|
|
case utils.ANSWER_TIME:
|
|
return rsrFld.ParseValue(aTime.String())
|
|
case utils.USAGE:
|
|
return rsrFld.ParseValue(strconv.FormatFloat(utils.Round(duration.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64))
|
|
case utils.PDD:
|
|
return rsrFld.ParseValue(strconv.FormatFloat(utils.Round(duration.Seconds(), 0, utils.ROUNDING_MIDDLE), 'f', -1, 64))
|
|
case utils.SUPPLIER:
|
|
return rsrFld.ParseValue(kev.GetSupplier(utils.META_DEFAULT))
|
|
case utils.DISCONNECT_CAUSE:
|
|
return rsrFld.ParseValue(kev.GetDisconnectCause(utils.META_DEFAULT))
|
|
case utils.MEDI_RUNID:
|
|
return rsrFld.ParseValue(utils.META_DEFAULT)
|
|
case utils.COST:
|
|
return rsrFld.ParseValue("-1.0")
|
|
default:
|
|
return rsrFld.ParseValue(kev.GetExtraFields()[rsrFld.Id])
|
|
}
|
|
}
|
|
func (kev KamEvent) PassesFieldFilter(*utils.RSRField) (bool, string) {
|
|
return false, ""
|
|
}
|
|
|
|
func (kev KamEvent) AsStoredCdr() *engine.StoredCdr {
|
|
storCdr := new(engine.StoredCdr)
|
|
storCdr.CgrId = kev.GetCgrId()
|
|
storCdr.TOR = utils.VOICE
|
|
storCdr.AccId = kev.GetUUID()
|
|
storCdr.CdrHost = kev.GetOriginatorIP(utils.META_DEFAULT)
|
|
storCdr.CdrSource = kev.GetCdrSource()
|
|
storCdr.ReqType = kev.GetReqType(utils.META_DEFAULT)
|
|
storCdr.Direction = kev.GetDirection(utils.META_DEFAULT)
|
|
storCdr.Tenant = kev.GetTenant(utils.META_DEFAULT)
|
|
storCdr.Category = kev.GetCategory(utils.META_DEFAULT)
|
|
storCdr.Account = kev.GetAccount(utils.META_DEFAULT)
|
|
storCdr.Subject = kev.GetSubject(utils.META_DEFAULT)
|
|
storCdr.Destination = kev.GetDestination(utils.META_DEFAULT)
|
|
storCdr.SetupTime, _ = kev.GetSetupTime(utils.META_DEFAULT)
|
|
storCdr.AnswerTime, _ = kev.GetAnswerTime(utils.META_DEFAULT)
|
|
storCdr.Usage, _ = kev.GetDuration(utils.META_DEFAULT)
|
|
storCdr.Pdd, _ = kev.GetPdd(utils.META_DEFAULT)
|
|
storCdr.Supplier = kev.GetSupplier(utils.META_DEFAULT)
|
|
storCdr.DisconnectCause = kev.GetDisconnectCause(utils.META_DEFAULT)
|
|
storCdr.ExtraFields = kev.GetExtraFields()
|
|
storCdr.Cost = -1
|
|
|
|
return storCdr
|
|
}
|
|
|
|
func (kev KamEvent) String() string {
|
|
mrsh, _ := json.Marshal(kev)
|
|
return string(mrsh)
|
|
}
|
|
|
|
func (kev KamEvent) AsKamAuthReply(maxSessionTime float64, suppliers string, resErr error) (*KamAuthReply, error) {
|
|
var err error
|
|
kar := &KamAuthReply{Event: CGR_AUTH_REPLY, Suppliers: suppliers}
|
|
if resErr != nil {
|
|
kar.Error = resErr.Error()
|
|
}
|
|
if _, hasIt := kev[KAM_TR_INDEX]; !hasIt {
|
|
return nil, utils.NewErrMandatoryIeMissing(KAM_TR_INDEX, "")
|
|
}
|
|
if kar.TransactionIndex, err = strconv.Atoi(kev[KAM_TR_INDEX]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, hasIt := kev[KAM_TR_LABEL]; !hasIt {
|
|
return nil, utils.NewErrMandatoryIeMissing(KAM_TR_LABEL, "")
|
|
}
|
|
if kar.TransactionLabel, err = strconv.Atoi(kev[KAM_TR_LABEL]); err != nil {
|
|
return nil, err
|
|
}
|
|
if maxSessionTime != -1 { // Convert maxSessionTime from nanoseconds into seconds
|
|
maxSessionDur := time.Duration(maxSessionTime)
|
|
maxSessionTime = maxSessionDur.Seconds()
|
|
}
|
|
kar.MaxSessionTime = int(utils.Round(maxSessionTime, 0, utils.ROUNDING_MIDDLE))
|
|
return kar, nil
|
|
}
|
|
|
|
// Converts into CallDescriptor due to responder interface needs
|
|
func (kev KamEvent) AsCallDescriptor() (*engine.CallDescriptor, error) {
|
|
lcrReq := &engine.LcrRequest{
|
|
Direction: kev.GetDirection(utils.META_DEFAULT),
|
|
Tenant: kev.GetTenant(utils.META_DEFAULT),
|
|
Category: kev.GetCategory(utils.META_DEFAULT),
|
|
Account: kev.GetAccount(utils.META_DEFAULT),
|
|
Subject: kev.GetSubject(utils.META_DEFAULT),
|
|
Destination: kev.GetDestination(utils.META_DEFAULT),
|
|
StartTime: utils.FirstNonEmpty(kev[CGR_SETUPTIME], kev[CGR_ANSWERTIME]),
|
|
Duration: kev[CGR_DURATION],
|
|
}
|
|
return lcrReq.AsCallDescriptor()
|
|
}
|
|
|
|
func (kev KamEvent) ComputeLcr() bool {
|
|
if computeLcr, err := strconv.ParseBool(kev[utils.CGR_COMPUTELCR]); err != nil {
|
|
return false
|
|
} else {
|
|
return computeLcr
|
|
}
|
|
}
|