/* Real-time Online/Offline Charging System (OCS) 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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package agents import ( "fmt" "strings" "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/sessions" "github.com/cgrates/cgrates/utils" ) func NewSMAsteriskEvent(ariEv map[string]any, asteriskIP, asteriskAlias string) *SMAsteriskEvent { smsmaEv := &SMAsteriskEvent{ ariEv: ariEv, asteriskIP: asteriskIP, asteriskAlias: asteriskAlias, cachedFields: make(map[string]string), opts: make(map[string]any), } smsmaEv.parseStasisArgs() // Populate appArgs return smsmaEv } type SMAsteriskEvent struct { // Standalone struct so we can cache the fields while we parse them ariEv map[string]any // stasis event asteriskIP string asteriskAlias string cachedFields map[string]string // Cache replies here opts map[string]any } // Clone returns a deep copy of SMAsteriskEvent. func (e *SMAsteriskEvent) Clone() *SMAsteriskEvent { ariEvClone := make(map[string]any, len(e.ariEv)) for k, v := range e.ariEv { ariEvClone[k] = v } cachedFieldsClone := make(map[string]string, len(e.cachedFields)) for k, v := range e.cachedFields { cachedFieldsClone[k] = v } optsClone := make(map[string]any, len(e.opts)) for k, v := range e.opts { optsClone[k] = v } return &SMAsteriskEvent{ ariEv: ariEvClone, asteriskIP: e.asteriskIP, asteriskAlias: e.asteriskAlias, cachedFields: cachedFieldsClone, opts: optsClone, } } // parseStasisArgs will convert the args passed to Stasis into CGRateS attribute/value pairs understood by CGRateS and store them in cachedFields // args need to be in the form of []string{"key=value", "key2=value2"} func (smaEv *SMAsteriskEvent) parseStasisArgs() { args, _ := smaEv.ariEv["args"].([]any) for _, arg := range args { if splt := strings.Split(arg.(string), "="); len(splt) > 1 { if !utils.CGROptionsSet.Has(splt[0]) { smaEv.cachedFields[splt[0]] = splt[1] } else { smaEv.opts[splt[0]] = splt[1] } } } } func (smaEv *SMAsteriskEvent) OriginatorIP() string { return smaEv.asteriskIP } func (smaEv *SMAsteriskEvent) EventType() string { cachedKey := eventType cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { cachedVal, _ = smaEv.ariEv["type"].(string) smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } func (smaEv *SMAsteriskEvent) ChannelID() string { cachedKey := channelID cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { channelData, _ := smaEv.ariEv["channel"].(map[string]any) cachedVal, _ = channelData["id"].(string) smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } func (smaEv *SMAsteriskEvent) Timestamp() string { cachedKey := timestamp cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { cachedVal, _ = smaEv.ariEv["timestamp"].(string) smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } func (smaEv *SMAsteriskEvent) ChannelState() string { cachedKey := channelState cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { channelData, _ := smaEv.ariEv["channel"].(map[string]any) cachedVal, _ = channelData["state"].(string) } return cachedVal } func (smaEv *SMAsteriskEvent) SetupTime() string { cachedKey := utils.SetupTime cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { channelData, _ := smaEv.ariEv["channel"].(map[string]any) cachedVal, _ = channelData["creationtime"].(string) smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } func (smaEv *SMAsteriskEvent) Account() string { cachedKey := utils.CGRAccount cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { channelData, _ := smaEv.ariEv["channel"].(map[string]any) callerData, _ := channelData["caller"].(map[string]any) cachedVal, _ = callerData["number"].(string) smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } func (smaEv *SMAsteriskEvent) Destination() string { cachedKey := utils.CGRDestination cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { channelData, _ := smaEv.ariEv["channel"].(map[string]any) dialplanData, _ := channelData["dialplan"].(map[string]any) cachedVal, _ = dialplanData["exten"].(string) smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } func (smaEv *SMAsteriskEvent) RequestType() string { return utils.FirstNonEmpty(smaEv.cachedFields[utils.CGRReqType], config.CgrConfig().GeneralCfg().DefaultReqType) } func (smaEv *SMAsteriskEvent) Tenant() string { return smaEv.cachedFields[utils.CGRTenant] } func (smaEv *SMAsteriskEvent) Category() string { return smaEv.cachedFields[utils.CGRCategory] } func (smaEv *SMAsteriskEvent) Subject() string { return smaEv.cachedFields[utils.CGRSubject] } func (smaEv *SMAsteriskEvent) PDD() string { return smaEv.cachedFields[utils.CGRPdd] } func (smaEv *SMAsteriskEvent) Route() string { return smaEv.cachedFields[utils.CGRRoute] } func (smaEv *SMAsteriskEvent) Subsystems() string { return smaEv.cachedFields[utils.CGRFlags] } func (smaEv *SMAsteriskEvent) OriginHost() string { return smaEv.cachedFields[utils.CGROriginHost] } func (smaEv *SMAsteriskEvent) DisconnectCause() string { cachedKey := utils.CGRDisconnectCause cachedVal, hasIt := smaEv.cachedFields[cachedKey] if !hasIt { cachedVal, _ = smaEv.ariEv["cause_txt"].(string) if len(cachedVal) == 0 { cachedVal = utils.IfaceAsString(smaEv.ariEv["cause"]) } smaEv.cachedFields[cachedKey] = cachedVal } return cachedVal } var primaryFields = utils.NewStringSet([]string{eventType, channelID, timestamp, utils.SetupTime, utils.CGRAccount, utils.CGRDestination, utils.CGRReqType, utils.CGRTenant, utils.CGRCategory, utils.CGRSubject, utils.CGRPdd, utils.CGRRoute, utils.CGRDisconnectCause}) func (smaEv *SMAsteriskEvent) ExtraParameters() (extraParams map[string]string) { extraParams = make(map[string]string) for cachedKey, cachedVal := range smaEv.cachedFields { if !primaryFields.Has(cachedKey) { extraParams[cachedKey] = cachedVal } } return } // Will populate CGREvent with required fields restored from the channel variables func (smaEv *SMAsteriskEvent) RestoreAndUpdateFields(cgrEv *utils.CGREvent) error { resCGREv := *cgrEv // make sure the channel contains the channelvars field to be recovered channvars, has := smaEv.ariEv["channel"].(map[string]any)["channelvars"].(map[string]any) if !has { return fmt.Errorf("channelvars not found in event <%+v>", smaEv.ariEv["channel"].(map[string]any)) } for key, val := range channvars { switch { case key == utils.CGRFlags: // "+" characters are converted to " " white space characters when put in channel variables, cgr_flags dont contain white spaces so we can convert them back to "+" without a problem cgrFlags := strings.ReplaceAll(val.(string), " ", "+") resCGREv.Event[utils.CGRFlags] = cgrFlags case key == "CDR(answer)": resCGREv.Event[utils.AnswerTime] = val.(string) case key == "CDR(billsec)": resCGREv.Event[utils.Usage] = val.(string) + "s" case key == utils.CGRReqType: resCGREv.Event[utils.RequestType] = val.(string) default: resCGREv.Event[key] = val.(string) } } resCGREv.Event[utils.EventName] = SMASessionTerminate resCGREv.Event[utils.DisconnectCause] = smaEv.DisconnectCause() for k, v := range smaEv.opts { resCGREv.APIOpts[k] = v } *cgrEv = resCGREv return nil } func (smaEv *SMAsteriskEvent) UpdateCGREvent(cgrEv *utils.CGREvent) error { resCGREv := *cgrEv switch smaEv.EventType() { case ARIChannelStateChange: resCGREv.Event[utils.EventName] = SMASessionStart resCGREv.Event[utils.AnswerTime] = smaEv.Timestamp() case ARIChannelDestroyed: resCGREv.Event[utils.EventName] = SMASessionTerminate resCGREv.Event[utils.DisconnectCause] = smaEv.DisconnectCause() if _, hasIt := resCGREv.Event[utils.AnswerTime]; !hasIt { resCGREv.Event[utils.Usage] = "0s" } else if aTime, err := utils.IfaceAsTime(resCGREv.Event[utils.AnswerTime], config.CgrConfig().GeneralCfg().DefaultTimezone); err != nil { return err } else if aTime.IsZero() { resCGREv.Event[utils.Usage] = "0s" } else { actualTime, err := utils.ParseTimeDetectLayout(smaEv.Timestamp(), "") if err != nil { return err } resCGREv.Event[utils.Usage] = actualTime.Sub(aTime).String() } } for k, v := range smaEv.opts { resCGREv.APIOpts[k] = v } *cgrEv = resCGREv return nil } func (smaEv *SMAsteriskEvent) AsMapStringInterface() (mp map[string]any) { mp = make(map[string]any) switch smaEv.EventType() { case ARIStasisStart: mp[utils.EventName] = SMAAuthorization case ARIChannelStateChange: mp[utils.EventName] = SMASessionStart case ARIChannelDestroyed: mp[utils.EventName] = SMASessionTerminate } mp[utils.OriginID] = smaEv.ChannelID() if smaEv.RequestType() != "" { mp[utils.RequestType] = smaEv.RequestType() } if smaEv.Tenant() != "" { mp[utils.Tenant] = smaEv.Tenant() } if smaEv.Category() != "" { mp[utils.Category] = smaEv.Category() } if smaEv.Subject() != "" { mp[utils.Subject] = smaEv.Subject() } mp[utils.OriginHost] = utils.FirstNonEmpty(smaEv.OriginHost(), smaEv.asteriskAlias, smaEv.OriginatorIP()) mp[utils.AccountField] = smaEv.Account() mp[utils.Destination] = smaEv.Destination() mp[utils.SetupTime] = smaEv.SetupTime() if smaEv.Route() != "" { mp[utils.Route] = smaEv.Route() } for extraKey, extraVal := range smaEv.ExtraParameters() { // Append extraParameters mp[extraKey] = extraVal } mp[utils.Source] = utils.AsteriskAgent return } // AsCGREvent converts AsteriskEvent into CGREvent func (smaEv *SMAsteriskEvent) AsCGREvent(timezone string) (cgrEv *utils.CGREvent, err error) { var setupTime time.Time if setupTime, err = utils.ParseTimeDetectLayout( smaEv.Timestamp(), timezone); err != nil { return } cgrEv = &utils.CGREvent{ Tenant: utils.FirstNonEmpty(smaEv.Tenant(), config.CgrConfig().GeneralCfg().DefaultTenant), ID: utils.UUIDSha1Prefix(), Time: &setupTime, Event: smaEv.AsMapStringInterface(), APIOpts: smaEv.opts, } return } func (smaEv *SMAsteriskEvent) V1AuthorizeArgs() (args *sessions.V1AuthorizeArgs) { cgrEv, err := smaEv.AsCGREvent(config.CgrConfig().GeneralCfg().DefaultTimezone) if err != nil { return } args = &sessions.V1AuthorizeArgs{ CGREvent: cgrEv, } if smaEv.Subsystems() == utils.EmptyString { utils.Logger.Warning(fmt.Sprintf("<%s> cgr_flags variable is not set, using defaults", utils.AsteriskAgent)) args.GetMaxUsage = true return } args.ParseFlags(smaEv.Subsystems(), utils.PlusChar) return } func (smaEv *SMAsteriskEvent) V1InitSessionArgs(cgrEvDisp utils.CGREvent) (args *sessions.V1InitSessionArgs) { args = &sessions.V1InitSessionArgs{ // defaults CGREvent: &cgrEvDisp, } subsystems, err := cgrEvDisp.FieldAsString(utils.CGRFlags) if err != nil { utils.Logger.Warning(fmt.Sprintf("<%s> event: %s don't have %s variable", utils.AsteriskAgent, utils.ToJSON(cgrEvDisp), utils.CGRFlags)) args.InitSession = true return } args.ParseFlags(subsystems, utils.PlusChar) return } func (smaEv *SMAsteriskEvent) V1TerminateSessionArgs(cgrEvDisp utils.CGREvent) (args *sessions.V1TerminateSessionArgs) { args = &sessions.V1TerminateSessionArgs{ // defaults CGREvent: &cgrEvDisp, } subsystems, err := cgrEvDisp.FieldAsString(utils.CGRFlags) if err != nil { utils.Logger.Warning(fmt.Sprintf("<%s> event: %s don't have %s variable", utils.AsteriskAgent, utils.ToJSON(cgrEvDisp), utils.CGRFlags)) args.TerminateSession = true return } args.ParseFlags(subsystems, utils.PlusChar) return }