diff --git a/agents/dmtagent.go b/agents/dmtagent.go index 3e6f4299f..f8a1584b7 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -68,14 +68,14 @@ func (self *DiameterAgent) handlers() diam.Handler { func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProcessor) (*CCA, error) { passesAllFilters := true for _, fldFilter := range reqProcessor.RequestFilter { - if passes, _ := ccr.passesFieldFilter(fldFilter); !passes { + if passes, _ := passesFieldFilter(ccr.diamMessage, fldFilter); !passes { passesAllFilters = false } } if !passesAllFilters { // Not going with this processor further return nil, nil } - smgEv, err := ccr.AsSMGenericEvent(reqProcessor.ContentFields) + smgEv, err := ccr.AsSMGenericEvent(reqProcessor.CCRFields) if err != nil { return nil, err } @@ -98,8 +98,8 @@ func (self DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestPro cca := NewCCAFromCCR(ccr) cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm - cca.GrantedServiceUnit.CCTime = int(maxUsage) cca.ResultCode = diam.Success + cca.GrantedServiceUnit.CCTime = int(maxUsage) return cca, nil } @@ -124,7 +124,7 @@ func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { utils.Logger.Err(fmt.Sprintf(" No request processor enabled for CCR: %+v, ignoring request", ccr)) return } - if dmtA, err := cca.AsDiameterMessage(); err != nil { + if dmtA, err := cca.AsBareDiameterMessage(); err != nil { utils.Logger.Err(fmt.Sprintf(" Failed to convert cca as diameter message, error: %s", err.Error())) return } else if _, err := dmtA.WriteTo(c); err != nil { diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index cc546a8de..3b013bc72 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -139,11 +139,11 @@ func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { if ccr.diamMessage, err = ccr.AsDiameterMessage(); err != nil { t.Error(err) } - eSMGE := sessionmanager.SMGenericEvent{"EventName": "DIAMETER_CCR", "AccId": "routinga;1442095190;1476802709", + eSMGE := sessionmanager.SMGenericEvent{"EventName": DIAMETER_CCR, "AccId": "routinga;1442095190;1476802709", "Account": "*users", "AnswerTime": "2015-11-23 12:22:24 +0000 UTC", "Category": "call", "Destination": "4986517174964", "Direction": "*out", "ReqType": "*users", "SetupTime": "2015-11-23 12:22:24 +0000 UTC", "Subject": "*users", "SubscriberId": "4986517174963", "TOR": "*voice", "Tenant": "*users", "Usage": "300"} - if smge, err := ccr.AsSMGenericEvent(cfgDefaults.DiameterAgentCfg().RequestProcessors[0].ContentFields); err != nil { + if smge, err := ccr.AsSMGenericEvent(cfgDefaults.DiameterAgentCfg().RequestProcessors[0].CCRFields); err != nil { t.Error(err) } else if !reflect.DeepEqual(eSMGE, smge) { t.Errorf("Expecting: %+v, received: %+v", eSMGE, smge) diff --git a/agents/libdmt.go b/agents/libdmt.go index ed5086d3c..39766b378 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -23,6 +23,7 @@ Build various type of packets here */ import ( + "errors" "fmt" "math" "math/rand" @@ -47,9 +48,8 @@ func init() { } const ( - META_CCR_USAGE = "*ccr_usage" - META_CCR_SMG_EVENT_NAME = "*ccr_smg_event_name" - DIAMETER_CCR = "DIAMETER_CCR" + META_CCR_USAGE = "*ccr_usage" + DIAMETER_CCR = "DIAMETER_CCR" ) func loadDictionaries(dictsDir, componentId string) error { @@ -155,6 +155,148 @@ func storedCdrToCCR(cdr *engine.StoredCdr, originHost, originRealm string, vendo return ccr } +// Not the cleanest but most efficient way to retrieve a string from AVP since there are string methods on all datatypes +// and the output is always in teh form "DataType{real_string}Padding:x" +func avpValAsString(a *diam.AVP) string { + dataVal := a.Data.String() + startIdx := strings.Index(dataVal, "{") + endIdx := strings.Index(dataVal, "}") + if startIdx == 0 || endIdx == 0 { + return "" + } + return dataVal[startIdx+1 : endIdx] +} + +// Handler for meta functions +func metaHandler(m *diam.Message, tag, arg string, debitInterval time.Duration) (string, error) { + switch tag { + case META_CCR_USAGE: + ccReqTypeAvp, err := m.FindAVP("CC-Request-Type", dict.UndefinedVendorID) + if err != nil { + return "", err + } else if ccReqTypeAvp == nil { + return "", errors.New("CC-Request-Type not found") + } + ccReqNrAvp, err := m.FindAVP("CC-Request-Number", dict.UndefinedVendorID) + if err != nil { + return "", err + } else if ccReqNrAvp == nil { + return "", errors.New("CC-Request-Number not found") + } + reqUnitAVPs, err := m.FindAVPsWithPath([]interface{}{"Requested-Service-Unit", "CC-Time"}, dict.UndefinedVendorID) + if err != nil { + return "", err + } else if len(reqUnitAVPs) == 0 { + return "", errors.New("Requested-Service-Unit/CC-Time not found") + } + usedUnitAVPs, err := m.FindAVPsWithPath([]interface{}{"Used-Service-Unit", "CC-Time"}, dict.UndefinedVendorID) + if err != nil { + return "", err + } else if len(usedUnitAVPs) == 0 { + return "", errors.New("Used-Service-Unit/CC-Time not found") + } + usage := usageFromCCR(int(ccReqTypeAvp.Data.(datatype.Enumerated)), + int(ccReqNrAvp.Data.(datatype.Enumerated)), + int(reqUnitAVPs[0].Data.(datatype.Unsigned32)), + int(usedUnitAVPs[0].Data.(datatype.Unsigned32)), debitInterval) + return strconv.FormatFloat(usage.Seconds(), 'f', -1, 64), nil + } + return "", nil +} + +func avpsWithPath(m *diam.Message, rsrFld *utils.RSRField) ([]*diam.AVP, error) { + hierarchyPath := strings.Split(rsrFld.Id, utils.HIERARCHY_SEP) + hpIf := make([]interface{}, len(hierarchyPath)) + for i, val := range hierarchyPath { + hpIf[i] = val + } + return m.FindAVPsWithPath(hpIf, dict.UndefinedVendorID) +} + +// Follows the implementation in the StorCdr +func passesFieldFilter(m *diam.Message, fieldFilter *utils.RSRField) (bool, int) { + if fieldFilter == nil { + return true, 0 + } + avps, err := avpsWithPath(m, fieldFilter) + if err != nil { + return false, 0 + } else if len(avps) == 0 { + return true, 0 + } + for avpIdx, avpVal := range avps { + if fieldFilter.FilterPasses(avpValAsString(avpVal)) { + return true, avpIdx + } + } + return false, 0 +} + +func composedFieldvalue(m *diam.Message, outTpl utils.RSRFields, avpIdx int) string { + var outVal string + for _, rsrTpl := range outTpl { + if rsrTpl.IsStatic() { + outVal += rsrTpl.ParseValue("") + } else { + matchingAvps, err := avpsWithPath(m, rsrTpl) + if err != nil || len(matchingAvps) == 0 { + utils.Logger.Warning(fmt.Sprintf(" Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id)) + continue // Filter not matching + } + if len(matchingAvps) <= avpIdx { + utils.Logger.Warning(fmt.Sprintf(" Cannot retrieve AVP with index %d for field template with id: %s", avpIdx, rsrTpl.Id)) + continue // Not convertible, ignore + } + if matchingAvps[0].Data.Type() == diam.GroupedAVPType { + utils.Logger.Warning(fmt.Sprintf(" Value for field template with id: %s is matching a group AVP, ignoring.", rsrTpl.Id)) + continue // Not convertible, ignore + } + outVal += avpValAsString(matchingAvps[avpIdx]) + } + } + return outVal +} + +func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, debitInterval time.Duration) (fmtValOut string, err error) { + var outVal string + switch cfgFld.Type { + case utils.META_FILLER: + outVal = cfgFld.Value.Id() + cfgFld.Padding = "right" + case utils.META_CONSTANT: + outVal = cfgFld.Value.Id() + case utils.META_HANDLER: + outVal, err = metaHandler(m, cfgFld.HandlerId, cfgFld.Layout, debitInterval) + if err != nil { + utils.Logger.Warning(fmt.Sprintf(" Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) + } + case utils.META_COMPOSED: + outVal = composedFieldvalue(m, cfgFld.Value, 0) + case utils.MetaGrouped: // GroupedAVP + passAtIndex := -1 + matchedAllFilters := true + for _, fldFilter := range cfgFld.FieldFilter { + var pass bool + if pass, passAtIndex = passesFieldFilter(m, fldFilter); !pass { + matchedAllFilters = false + break + } + } + if !matchedAllFilters { + return "", nil // Not matching field filters, will have it empty + } + if passAtIndex == -1 { + passAtIndex = 0 // No filter + } + outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex) + } + if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + utils.Logger.Warning(fmt.Sprintf(" Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) + return "", err + } + return fmtValOut, nil +} + // debitInterval is the configured debitInterval, in sync with the diameter client one func NewCCRFromDiameterMessage(m *diam.Message, debitInterval time.Duration) (*CCR, error) { var ccr CCR @@ -288,127 +430,12 @@ func (self *CCR) AsDiameterMessage() (*diam.Message, error) { return m, nil } -// Not the cleanest but most efficient way to retrieve a string from AVP since there are string methods on all datatypes -// and the output is always in teh form "DataType{real_string}Padding:x" -func avpValAsString(a *diam.AVP) string { - dataVal := a.Data.String() - startIdx := strings.Index(dataVal, "{") - endIdx := strings.Index(dataVal, "}") - if startIdx == 0 || endIdx == 0 { - return "" - } - return dataVal[startIdx+1 : endIdx] -} - -// Handler for meta functions -func (self *CCR) metaHandler(tag, arg string) (string, error) { - switch tag { - case META_CCR_USAGE: - usage := usageFromCCR(self.CCRequestType, self.CCRequestNumber, self.RequestedServiceUnit.CCTime, self.UsedServiceUnit.CCTime, self.debitInterval) - return strconv.FormatFloat(usage.Seconds(), 'f', -1, 64), nil - } - return "", nil -} - -func (self *CCR) avpsWithPath(rsrFld *utils.RSRField) ([]*diam.AVP, error) { - hierarchyPath := strings.Split(rsrFld.Id, utils.HIERARCHY_SEP) - hpIf := make([]interface{}, len(hierarchyPath)) - for i, val := range hierarchyPath { - hpIf[i] = val - } - return self.diamMessage.FindAVPsWithPath(hpIf, dict.UndefinedVendorID) -} - -// Follows the implementation in the StorCdr -func (self *CCR) passesFieldFilter(fieldFilter *utils.RSRField) (bool, int) { - if fieldFilter == nil { - return true, 0 - } - avps, err := self.avpsWithPath(fieldFilter) - if err != nil { - return false, 0 - } else if len(avps) == 0 { - return true, 0 - } - for avpIdx, avpVal := range avps { - if fieldFilter.FilterPasses(avpValAsString(avpVal)) { - return true, avpIdx - } - } - return false, 0 -} - -func (self *CCR) eventFieldValue(fldTpl utils.RSRFields, avpIdx int) string { - var outVal string - for _, rsrTpl := range fldTpl { - if rsrTpl.IsStatic() { - outVal += rsrTpl.ParseValue("") - } else { - matchingAvps, err := self.avpsWithPath(rsrTpl) - if err != nil || len(matchingAvps) == 0 { - utils.Logger.Warning(fmt.Sprintf(" Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id)) - continue // Filter not matching - } - if len(matchingAvps) <= avpIdx { - utils.Logger.Warning(fmt.Sprintf(" Cannot retrieve AVP with index %d for field template with id: %s", avpIdx, rsrTpl.Id)) - continue // Not convertible, ignore - } - if matchingAvps[0].Data.Type() == diam.GroupedAVPType { - utils.Logger.Warning(fmt.Sprintf(" Value for field template with id: %s is matching a group AVP, ignoring.", rsrTpl.Id)) - continue // Not convertible, ignore - } - outVal += avpValAsString(matchingAvps[avpIdx]) - } - } - return outVal -} - -func (self *CCR) fieldOutVal(cfgFld *config.CfgCdrField) (fmtValOut string, err error) { - var outVal string - switch cfgFld.Type { - case utils.META_FILLER: - outVal = cfgFld.Value.Id() - cfgFld.Padding = "right" - case utils.META_CONSTANT: - outVal = cfgFld.Value.Id() - case utils.META_HANDLER: - outVal, err = self.metaHandler(cfgFld.HandlerId, cfgFld.Layout) - if err != nil { - utils.Logger.Warning(fmt.Sprintf(" Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error())) - } - case utils.META_COMPOSED: - outVal = self.eventFieldValue(cfgFld.Value, 0) - case utils.MetaGrouped: // GroupedAVP - passAtIndex := -1 - matchedAllFilters := true - for _, fldFilter := range cfgFld.FieldFilter { - var pass bool - if pass, passAtIndex = self.passesFieldFilter(fldFilter); !pass { - matchedAllFilters = false - break - } - } - if !matchedAllFilters { - return "", nil // Not matching field filters, will have it empty - } - if passAtIndex == -1 { - passAtIndex = 0 // No filter - } - outVal = self.eventFieldValue(cfgFld.Value, passAtIndex) - } - if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { - utils.Logger.Warning(fmt.Sprintf(" Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) - return "", err - } - return fmtValOut, nil -} - // Extracts data out of CCR into a SMGenericEvent based on the configured template func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager.SMGenericEvent, error) { outMap := make(map[string]string) // work with it so we can append values to keys outMap[utils.EVENT_NAME] = DIAMETER_CCR for _, cfgFld := range cfgFlds { - fmtOut, err := self.fieldOutVal(cfgFld) + fmtOut, err := fieldOutVal(self.diamMessage, cfgFld, self.debitInterval) if err != nil { return nil, err } @@ -444,7 +471,7 @@ type CCA struct { } // Converts itself into DiameterMessage -func (self *CCA) AsDiameterMessage() (*diam.Message, error) { +func (self *CCA) AsBareDiameterMessage() (*diam.Message, error) { if _, err := self.diamMessage.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String(self.SessionId)); err != nil { return nil, err } @@ -466,14 +493,16 @@ func (self *CCA) AsDiameterMessage() (*diam.Message, error) { if _, err := self.diamMessage.NewAVP(avp.ResultCode, avp.Mbit, 0, datatype.Unsigned32(self.ResultCode)); err != nil { return nil, err } - ccTimeAvp, err := self.diamMessage.Dictionary().FindAVP(self.diamMessage.Header.ApplicationID, "CC-Time") - if err != nil { - return nil, err - } - if _, err := self.diamMessage.NewAVP("Granted-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(ccTimeAvp.Code, avp.Mbit, 0, datatype.Unsigned32(self.GrantedServiceUnit.CCTime))}}); err != nil { - return nil, err - } + /* + ccTimeAvp, err := self.diamMessage.Dictionary().FindAVP(self.diamMessage.Header.ApplicationID, "CC-Time") + if err != nil { + return nil, err + } + if _, err := self.diamMessage.NewAVP("Granted-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(ccTimeAvp.Code, avp.Mbit, 0, datatype.Unsigned32(self.GrantedServiceUnit.CCTime))}}); err != nil { + return nil, err + } + */ return self.diamMessage, nil } diff --git a/agents/libdmt_test.go b/agents/libdmt_test.go index 99a43a453..49f28b5ed 100644 --- a/agents/libdmt_test.go +++ b/agents/libdmt_test.go @@ -99,11 +99,10 @@ func TestFieldOutVal(t *testing.T) { m.NewAVP("Requested-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ AVP: []*diam.AVP{ diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(360))}}) // CC-Time - ccr := &CCR{diamMessage: m} cfgFld := &config.CfgCdrField{Tag: "StaticTest", Type: utils.META_COMPOSED, FieldId: utils.TOR, Value: utils.ParseRSRFieldsMustCompile("^*voice", utils.INFIELD_SEP), Mandatory: true} eOut := "*voice" - if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil { + if fldOut, err := fieldOutVal(m, cfgFld, time.Duration(0)); err != nil { t.Error(err) } else if fldOut != eOut { t.Errorf("Expecting: %s, received: %s", eOut, fldOut) @@ -111,7 +110,7 @@ func TestFieldOutVal(t *testing.T) { cfgFld = &config.CfgCdrField{Tag: "ComposedTest", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION, Value: utils.ParseRSRFieldsMustCompile("Requested-Service-Unit>CC-Time", utils.INFIELD_SEP), Mandatory: true} eOut = "360" - if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil { + if fldOut, err := fieldOutVal(m, cfgFld, time.Duration(0)); err != nil { t.Error(err) } else if fldOut != eOut { t.Errorf("Expecting: %s, received: %s", eOut, fldOut) @@ -120,7 +119,7 @@ func TestFieldOutVal(t *testing.T) { cfgFld = &config.CfgCdrField{Tag: "Grouped1", Type: utils.MetaGrouped, FieldId: "Account", Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true} eOut = "33708000003" - if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil { + if fldOut, err := fieldOutVal(m, cfgFld, time.Duration(0)); err != nil { t.Error(err) } else if fldOut != eOut { t.Errorf("Expecting: %s, received: %s", eOut, fldOut) @@ -130,7 +129,7 @@ func TestFieldOutVal(t *testing.T) { FieldFilter: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Type(1)", utils.INFIELD_SEP), Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true} eOut = "208708000003" - if fldOut, err := ccr.fieldOutVal(cfgFld); err != nil { + if fldOut, err := fieldOutVal(m, cfgFld, time.Duration(0)); err != nil { t.Error(err) } else if fldOut != eOut { t.Errorf("Expecting: %s, received: %s", eOut, fldOut) diff --git a/config/config_defaults.go b/config/config_defaults.go index 0ffd77b09..ef9a9fc55 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -277,7 +277,7 @@ const CGRATES_CFG_JSON = ` "dry_run": false, // do not send the CDRs to CDRS, just parse them "request_filter": "Subscription-Id>Subscription-Id-Type(0)", // filter requests processed by this processor "continue_on_success": false, // continue to the next template if executed - "content_fields":[ // import content_fields template, tag will match internally CDR field, in case of .csv value will be represented by index of the field value + "ccr_fields":[ // fields taken out of CCR and used in internal requests {"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, {"tag": "accid", "field_id": "AccId", "type": "*composed", "value": "Session-Id", "mandatory": true}, {"tag": "reqtype", "field_id": "ReqType", "type": "*composed", "value": "^*users", "mandatory": true}, @@ -292,6 +292,9 @@ const CGRATES_CFG_JSON = ` {"tag": "usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, {"tag": "subscriber_id", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, ], + "cca_fields":[ // fields returned in CCA + //{"tag": "tor", "field_id": "TOR", "type": "*composed", "value": "^*voice", "mandatory": true}, + ], }, ], }, diff --git a/config/config_json_test.go b/config/config_json_test.go index b618bb1c8..178795081 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -433,7 +433,7 @@ func TestDiameterAgentJsonCfg(t *testing.T) { Dry_run: utils.BoolPointer(false), Request_filter: utils.StringPointer("Subscription-Id>Subscription-Id-Type(0)"), Continue_on_success: utils.BoolPointer(false), - Content_fields: &[]*CdrFieldJsonCfg{ + CCR_fields: &[]*CdrFieldJsonCfg{ &CdrFieldJsonCfg{Tag: utils.StringPointer("tor"), Field_id: utils.StringPointer(utils.TOR), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("^*voice"), Mandatory: utils.BoolPointer(true)}, &CdrFieldJsonCfg{Tag: utils.StringPointer("accid"), Field_id: utils.StringPointer(utils.ACCID), Type: utils.StringPointer(utils.META_COMPOSED), @@ -461,13 +461,15 @@ func TestDiameterAgentJsonCfg(t *testing.T) { &CdrFieldJsonCfg{Tag: utils.StringPointer("subscriber_id"), Field_id: utils.StringPointer("SubscriberId"), Type: utils.StringPointer(utils.META_COMPOSED), Value: utils.StringPointer("Subscription-Id>Subscription-Id-Data"), Mandatory: utils.BoolPointer(true)}, }, + CCA_fields: &[]*CdrFieldJsonCfg{}, }, }, } if cfg, err := dfCgrJsonCfg.DiameterAgentJsonCfg(); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCfg, cfg) { - t.Error("Received: ", cfg) + rcv := *cfg.Request_processors + t.Errorf("Received: %+v", rcv[0].CCA_fields) } } diff --git a/config/daconfig.go b/config/daconfig.go index c358dfa8a..4044457ef 100644 --- a/config/daconfig.go +++ b/config/daconfig.go @@ -100,7 +100,8 @@ type DARequestProcessor struct { DryRun bool RequestFilter utils.RSRFields ContinueOnSuccess bool - ContentFields []*CfgCdrField + CCRFields []*CfgCdrField + CCAFields []*CfgCdrField } func (self *DARequestProcessor) loadFromJsonCfg(jsnCfg *DARequestProcessorJsnCfg) error { @@ -119,8 +120,13 @@ func (self *DARequestProcessor) loadFromJsonCfg(jsnCfg *DARequestProcessorJsnCfg return err } } - if jsnCfg.Content_fields != nil { - if self.ContentFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.Content_fields); err != nil { + if jsnCfg.CCR_fields != nil { + if self.CCRFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.CCR_fields); err != nil { + return err + } + } + if jsnCfg.CCA_fields != nil { + if self.CCAFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.CCA_fields); err != nil { return err } } diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 82240478d..7b230496e 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -257,7 +257,8 @@ type DARequestProcessorJsnCfg struct { Dry_run *bool Request_filter *string Continue_on_success *bool - Content_fields *[]*CdrFieldJsonCfg + CCR_fields *[]*CdrFieldJsonCfg + CCA_fields *[]*CdrFieldJsonCfg } // History server config section