diff --git a/agents/agentreq.go b/agents/agentreq.go index a83d1030c..a52008cfa 100644 --- a/agents/agentreq.go +++ b/agents/agentreq.go @@ -20,21 +20,30 @@ package agents import ( "fmt" + "strconv" "strings" + "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) -func newAgentRequest(req config.DataProvider, tntTpl config.RSRParsers, - dfltTenant, timezone string, filterS *engine.FilterS) (ar *AgentRequest) { +func newAgentRequest(req config.DataProvider, + vars map[string]interface{}, + rply *config.NavigableMap, + tntTpl config.RSRParsers, + dfltTenant, timezone string, + filterS *engine.FilterS) (ar *AgentRequest) { + if rply == nil { + rply = config.NewNavigableMap(nil) + } ar = &AgentRequest{ Request: req, - Vars: config.NewNavigableMap(nil), + Vars: config.NewNavigableMap(vars), CGRRequest: config.NewNavigableMap(nil), CGRReply: config.NewNavigableMap(nil), - Reply: config.NewNavigableMap(nil), + Reply: rply, timezone: timezone, filterS: filterS, } @@ -88,20 +97,11 @@ func (ar *AgentRequest) FieldAsInterface(fldPath []string) (val interface{}, err // FieldAsString implements engine.DataProvider func (ar *AgentRequest) FieldAsString(fldPath []string) (val string, err error) { - switch fldPath[0] { - default: - return "", fmt.Errorf("unsupported field prefix: <%s>", fldPath[0]) - case utils.MetaReq: - return ar.Request.FieldAsString(fldPath[1:]) - case utils.MetaVars: - return ar.Vars.FieldAsString(fldPath[1:]) - case utils.MetaCgreq: - return ar.CGRRequest.FieldAsString(fldPath[1:]) - case utils.MetaCgrep: - return ar.CGRReply.FieldAsString(fldPath[1:]) - case utils.MetaRep: - return ar.Reply.FieldAsString(fldPath[1:]) + var iface interface{} + if iface, err = ar.FieldAsInterface(fldPath); err != nil { + return } + return utils.IfaceAsString(iface) } // AsNavigableMap implements engine.DataProvider @@ -176,6 +176,44 @@ func (aReq *AgentRequest) ParseField( } out = tEnd.Sub(tStart).String() isString = true + case utils.MetaCCUsage: + if len(cfgFld.Value) != 3 { + return nil, fmt.Errorf("invalid arguments <%s> to %s", + utils.ToJSON(cfgFld.Value), utils.MetaCCUsage) + } + strVal1, err := cfgFld.Value[0].ParseDataProvider(aReq, utils.NestingSep) // ReqNr + if err != nil { + return "", err + } + reqNr, err := strconv.ParseInt(strVal1, 10, 64) + if err != nil { + return "", fmt.Errorf("invalid requestNumber <%s> to %s", + strVal1, utils.MetaCCUsage) + } + strVal2, err := cfgFld.Value[1].ParseDataProvider(aReq, utils.NestingSep) // TotalUsage + if err != nil { + return "", err + } + usedCCTime, err := utils.ParseDurationWithNanosecs(strVal2) + if err != nil { + return "", fmt.Errorf("invalid usedCCTime <%s> to %s", + strVal2, utils.MetaCCUsage) + } + strVal3, err := cfgFld.Value[2].ParseDataProvider(aReq, utils.NestingSep) // DebitInterval + if err != nil { + return "", err + } + debitItvl, err := utils.ParseDurationWithNanosecs(strVal3) + if err != nil { + return "", fmt.Errorf("invalid debitInterval <%s> to %s", + strVal3, utils.MetaCCUsage) + } + mltpl := reqNr - 2 // init and terminate will be ignored + if mltpl < 0 { + mltpl = 0 + } + return usedCCTime + time.Duration(debitItvl.Nanoseconds()*mltpl), nil + } if err != nil { return diff --git a/agents/agentreq_test.go b/agents/agentreq_test.go index 2081dcf18..43f0e6eff 100644 --- a/agents/agentreq_test.go +++ b/agents/agentreq_test.go @@ -33,8 +33,7 @@ func TestAgReqAsNavigableMap(t *testing.T) { dm := engine.NewDataManager(data) cfg, _ := config.NewDefaultCGRConfig() filterS := engine.NewFilterS(cfg, nil, dm) - agReq := newAgentRequest(nil, nil, - "cgrates.org", "", filterS) + agReq := newAgentRequest(nil, nil, nil, nil, "cgrates.org", "", filterS) // populate request, emulating the way will be done in HTTPAgent agReq.CGRRequest.Set([]string{utils.CGRID}, utils.Sha1("dsafdsaf", time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC).String()), false) diff --git a/agents/diam_it_test.go b/agents/diam_it_test.go new file mode 100644 index 000000000..41ed380dc --- /dev/null +++ b/agents/diam_it_test.go @@ -0,0 +1,535 @@ +// +build integration + +/* +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 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 +*/ + +package agents + +import ( + "flag" + "net/rpc" + "net/rpc/jsonrpc" + "path" + "testing" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/fiorix/go-diameter/diam" + "github.com/fiorix/go-diameter/diam/avp" + "github.com/fiorix/go-diameter/diam/datatype" + "github.com/fiorix/go-diameter/diam/dict" +) + +var waitRater = flag.Int("wait_rater", 100, "Number of miliseconds to wait for rater to start and cache") +var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here") +var interations = flag.Int("iterations", 1, "Number of iterations to do for dry run simulation") +var replyTimeout = flag.String("reply_timeout", "1s", "Maximum duration to wait for a reply") + +var daCfgPath string +var daCfg *config.CGRConfig +var apierRpc *rpc.Client +var diamClnt *DiameterClient + +var rplyTimeout time.Duration + +func TestDiamItInitCfg(t *testing.T) { + daCfgPath = path.Join(*dataDir, "conf", "samples", "diamagent") + // Init config first + var err error + daCfg, err = config.NewCGRConfigFromFolder(daCfgPath) + if err != nil { + t.Error(err) + } + daCfg.DataFolderPath = *dataDir // Share DataFolderPath through config towards StoreDb for Flush() + config.SetCgrConfig(daCfg) + rplyTimeout, _ = utils.ParseDurationWithSecs(*replyTimeout) +} + +// Remove data in both rating and accounting db +func TestDiamItResetDataDb(t *testing.T) { + if err := engine.InitDataDb(daCfg); err != nil { + t.Fatal(err) + } +} + +// Wipe out the cdr database +func TestDiamItResetStorDb(t *testing.T) { + if err := engine.InitStorDb(daCfg); err != nil { + t.Fatal(err) + } +} + +// Start CGR Engine +func TestDiamItStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(daCfgPath, 4000); err != nil { + t.Fatal(err) + } +} + +func TestDiamItConnectDiameterClient(t *testing.T) { + diamClnt, err = NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "INTEGRATION_TESTS", + daCfg.DiameterAgentCfg().OriginRealm, + daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, + utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesPath) + if err != nil { + t.Fatal(err) + } +} + +// Connect rpc client to rater +func TestDiamItApierRpcConn(t *testing.T) { + var err error + apierRpc, err = jsonrpc.Dial("tcp", daCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal(err) + } +} + +// Load the tariff plan, creating accounts and their balances +func TestDiamItTPFromFolder(t *testing.T) { + attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")} + var loadInst utils.LoadInstance + if err := apierRpc.Call("ApierV2.LoadTariffPlanFromFolder", attrs, &loadInst); err != nil { + t.Error(err) + } + time.Sleep(time.Duration(1000) * time.Millisecond) // Give time for scheduler to execute topups +} + +func TestDiamItDryRun(t *testing.T) { + ccr := diam.NewRequest(diam.CreditControl, 4, nil) + ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("cgrates;1451911932;00082")) + ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + ccr.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + ccr.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + ccr.NewAVP(avp.UserName, avp.Mbit, 0, datatype.UTF8String("CGR-DA")) + ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("TestDiamItDryRun")) // Match specific DryRun profile + ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(1)) + ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) + ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) + ccr.NewAVP(avp.TerminationCause, avp.Mbit, 0, datatype.Enumerated(1)) + ccr.NewAVP(443, avp.Mbit, 0, &diam.GroupedAVP{ // Subscription-Id + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data + }}) + ccr.NewAVP(443, avp.Mbit, 0, &diam.GroupedAVP{ // Subscription-Id + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(1)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208123456789")), // Subscription-Id-Data + }}) + ccr.NewAVP(439, avp.Mbit, 0, datatype.Unsigned32(0)) // Service-Identifier + ccr.NewAVP(437, avp.Mbit, 0, &diam.GroupedAVP{ // Requested-Service-Unit + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300)), // CC-Time + }}) + ccr.NewAVP(873, avp.Mbit|avp.Vbit, 10415, &diam.GroupedAVP{ // Service-information + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(20336, avp.Mbit, 2011, datatype.UTF8String("1001")), // CallingPartyAdress + diam.NewAVP(20337, avp.Mbit, 2011, datatype.UTF8String("1002")), // CalledPartyAdress + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // ChargeFlowType + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("33609004940")), // CallingVlrNumber + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("208104941749984")), // CallingCellID + diam.NewAVP(20313, avp.Mbit, 2011, datatype.OctetString("0x8090a3")), // BearerCapability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.OctetString("0x401c4132ed665")), // CallreferenceNumber + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("33609004940")), // MSCAddress + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("20160501010101")), // SSPTime + diam.NewAVP(20938, avp.Mbit, 2011, datatype.OctetString("0x00000001")), // HighLayerCharacteristics + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Integer32(8)), // Time-Zone + }, + }), + }}) + if _, err := ccr.NewAVP("Framed-IP-Address", avp.Mbit, 0, datatype.UTF8String("10.228.16.4")); err != nil { + t.Error(err) + } + for i := 0; i < *interations; i++ { + if err := diamClnt.SendMessage(ccr); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2002" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + eVal = "cgrates;1451911932;00082" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Session-Id"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + eVal = "CGR-DA" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Origin-Host"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + eVal = "cgrates.org" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Origin-Realm"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + eVal = "4" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Auth-Application-Id"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + eVal = "1" + if avps, err := msg.FindAVPsWithPath([]interface{}{"CC-Request-Type"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + eVal = "1" + if avps, err := msg.FindAVPsWithPath([]interface{}{"CC-Request-Number"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + } +} + +func TestDiamItCCRInit(t *testing.T) { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("192.168.1.1")) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(1)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) + m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("voice@DiamItCCRInit")) + m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 4, 14, 42, 20, 0, time.UTC))) + m.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1006")), // Subscription-Id-Data + }}) + m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) + m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(0))}}) + m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String("1006")), // Calling-Party-Address + diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String("1002")), // Called-Party-Address + diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String("1002")), // Real-Called-Number + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // Charge-Flow-Type + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-Vlr-Number + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-CellID-Or-SAI + diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String("")), // Bearer-Capability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")), // Call-Reference-Number + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("")), // MSC-Address + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(0)), // Time-Zone + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("")), // Called-Party-NP + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("")), // SSP-Time + }, + }), + }}) + if err := diamClnt.SendMessage(m); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2001" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + // Result-Code + eVal = "300" // 5 mins of session + if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, + dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } +} + +func TestDiamItCCRUpdate(t *testing.T) { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("192.168.1.1")) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(2)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(2)) + m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("voice@DiamItCCRInit")) + m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 4, 14, 57, 20, 0, time.UTC))) + m.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1006")), // Subscription-Id-Data + }}) + m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) + m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) + m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String("1006")), // Calling-Party-Address + diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String("1002")), // Called-Party-Address + diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String("1002")), // Real-Called-Number + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // Charge-Flow-Type + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-Vlr-Number + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-CellID-Or-SAI + diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String("")), // Bearer-Capability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")), // Call-Reference-Number + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("")), // MSC-Address + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(0)), // Time-Zone + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("")), // Called-Party-NP + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("")), // SSP-Time + }, + }), + }}) + if err := diamClnt.SendMessage(m); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2001" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + // Result-Code + eVal = "300" // 5 mins of session + if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, + dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } +} + +func TestDiamItCCRTerminate(t *testing.T) { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("192.168.1.1")) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(3)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(3)) + m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("voice@DiamItCCRInit")) + m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 4, 15, 12, 20, 0, time.UTC))) + m.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1006")), // Subscription-Id-Data + }}) + m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(0))}}) + m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(250))}}) + m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String("1006")), // Calling-Party-Address + diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String("1002")), // Called-Party-Address + diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String("1002")), // Real-Called-Number + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // Charge-Flow-Type + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-Vlr-Number + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-CellID-Or-SAI + diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String("")), // Bearer-Capability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")), // Call-Reference-Number + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("")), // MSC-Address + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(0)), // Time-Zone + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("")), // Called-Party-NP + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("")), // SSP-Time + }, + }), + }}) + if err := diamClnt.SendMessage(m); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2001" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + time.Sleep(time.Duration(*waitRater) * time.Millisecond) + var cdrs []*engine.CDR + args := utils.RPCCDRsFilter{RunIDs: []string{utils.MetaRaw}} + if err := apierRpc.Call(utils.CdrsV1GetCDRs, args, &cdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if len(cdrs) != 1 { + t.Error("Unexpected number of CDRs returned: ", len(cdrs)) + } else { + if cdrs[0].Usage != 550*time.Second { + t.Errorf("Unexpected Usage CDR: %+v", cdrs[0]) + } + } +} + +func TestDiamItCCRSMS(t *testing.T) { + ccr := diam.NewRequest(diam.CreditControl, 4, nil) + ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("TestDmtAgentSendCCRSMS")) + ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("message@DiamItCCRSMS")) + ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(4)) + ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(0)) + ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 5, 11, 43, 10, 0, time.UTC))) + ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), + diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data + }}) + ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), + diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("104502200011")), // Subscription-Id-Data + }}) + ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(0)) + ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(1))}}) + ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP + }, + }), + diam.NewAVP(2000, avp.Mbit, 10415, &diam.GroupedAVP{ // SMS-Information + AVP: []*diam.AVP{ + diam.NewAVP(886, avp.Mbit, 10415, &diam.GroupedAVP{ // Originator-Address + AVP: []*diam.AVP{ + diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type + diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1001")), // Address-Data + }}), + diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // Recipient-Address + AVP: []*diam.AVP{ + diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type + diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1003")), // Address-Data + }}), + }, + }), + }}) + if err := diamClnt.SendMessage(ccr); err != nil { + t.Error(err) + } + + time.Sleep(time.Duration(100) * time.Millisecond) + diamClnt.ReceivedMessage(rplyTimeout) + + var cdrs []*engine.CDR + args := utils.RPCCDRsFilter{RunIDs: []string{utils.MetaRaw}, ToRs: []string{utils.SMS}} + if err := apierRpc.Call(utils.CdrsV1GetCDRs, args, &cdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if len(cdrs) != 1 { + t.Error("Unexpected number of CDRs returned: ", len(cdrs)) + } else { + if cdrs[0].Usage != 1 { + t.Errorf("Unexpected Usage CDR: %+v", cdrs[0]) + } + } +} diff --git a/agents/diamagent.go b/agents/diamagent.go new file mode 100644 index 000000000..ef4b16e89 --- /dev/null +++ b/agents/diamagent.go @@ -0,0 +1,319 @@ +/* +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 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 +*/ + +package agents + +import ( + "fmt" + "reflect" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/sessions" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/rpcclient" + "github.com/fiorix/go-diameter/diam" + "github.com/fiorix/go-diameter/diam/datatype" + "github.com/fiorix/go-diameter/diam/sm" +) + +func NewDiameterAgent(cgrCfg *config.CGRConfig, filterS *engine.FilterS, + sessionS rpcclient.RpcClientConnection) (*DiameterAgent, error) { + if sessionS != nil && reflect.ValueOf(sessionS).IsNil() { + sessionS = nil + } + da := &DiameterAgent{cgrCfg: cgrCfg, filterS: filterS, sessionS: sessionS} + dictsPath := cgrCfg.DiameterAgentCfg().DictionariesPath + if len(dictsPath) != 0 { + if err := loadDictionaries(dictsPath, utils.DiameterAgent); err != nil { + return nil, err + } + } + msgTemplates := da.cgrCfg.DiameterAgentCfg().Templates + // Inflate *template field types + for _, procsr := range da.cgrCfg.DiameterAgentCfg().RequestProcessors { + if tpls, err := config.InflateTemplates(procsr.RequestFields, msgTemplates); err != nil { + return nil, err + } else if tpls != nil { + procsr.RequestFields = tpls + } + if tpls, err := config.InflateTemplates(procsr.ReplyFields, msgTemplates); err != nil { + return nil, err + } else if tpls != nil { + procsr.ReplyFields = tpls + } + } + return da, nil +} + +type DiameterAgent struct { + cgrCfg *config.CGRConfig + filterS *engine.FilterS + sessionS rpcclient.RpcClientConnection // Connection towards CGR-SessionS component +} + +// ListenAndServe is called when DiameterAgent is started, usually from within cmd/cgr-engine +func (da *DiameterAgent) ListenAndServe() error { + return diam.ListenAndServe(da.cgrCfg.DiameterAgentCfg().Listen, da.handlers(), nil) +} + +// Creates the message handlers +func (da *DiameterAgent) handlers() diam.Handler { + settings := &sm.Settings{ + OriginHost: datatype.DiameterIdentity(da.cgrCfg.DiameterAgentCfg().OriginHost), + OriginRealm: datatype.DiameterIdentity(da.cgrCfg.DiameterAgentCfg().OriginRealm), + VendorID: datatype.Unsigned32(da.cgrCfg.DiameterAgentCfg().VendorId), + ProductName: datatype.UTF8String(da.cgrCfg.DiameterAgentCfg().ProductName), + FirmwareRevision: datatype.Unsigned32(utils.DIAMETER_FIRMWARE_REVISION), + } + dSM := sm.New(settings) + dSM.HandleFunc("ALL", da.handleMessage) // route all commands to one dispatcher + go func() { + for err := range dSM.ErrorReports() { + utils.Logger.Err(fmt.Sprintf("<%s> sm error: %v", utils.DiameterAgent, err)) + } + }() + return dSM +} + +// handleALL is the handler of all messages coming in via Diameter +func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) { + dApp, err := m.Dictionary().App(m.Header.ApplicationID) + if err != nil { + utils.Logger.Err(fmt.Sprintf("<%s> decoding app: %d, err: %s", + utils.DiameterAgent, m.Header.ApplicationID, err.Error())) + writeOnConn(c, m.Answer(diam.NoCommonApplication)) + return + } + dCmd, err := m.Dictionary().FindCommand( + m.Header.ApplicationID, + m.Header.CommandCode) + if err != nil { + utils.Logger.Warning(fmt.Sprintf("<%s> decoding app: %d, command %d, err: %s", + utils.DiameterAgent, m.Header.ApplicationID, m.Header.CommandCode, err.Error())) + writeOnConn(c, m.Answer(diam.CommandUnsupported)) + return + } + reqVars := map[string]interface{}{ + utils.OriginHost: da.cgrCfg.DiameterAgentCfg().OriginHost, // used in templates + utils.OriginRealm: da.cgrCfg.DiameterAgentCfg().OriginRealm, + utils.ProductName: da.cgrCfg.DiameterAgentCfg().ProductName, + utils.MetaApp: dApp.Name, + utils.MetaAppID: dApp.ID, + utils.MetaCmd: dCmd.Short + "R", + } + rply := config.NewNavigableMap(nil) // share it among different processors + var processed bool + for _, reqProcessor := range da.cgrCfg.DiameterAgentCfg().RequestProcessors { + var lclProcessed bool + lclProcessed, err = da.processRequest(reqProcessor, + newAgentRequest( + newDADataProvider(m), reqVars, rply, + reqProcessor.Tenant, da.cgrCfg.DefaultTenant, + utils.FirstNonEmpty(reqProcessor.Timezone, config.CgrConfig().DefaultTimezone), + da.filterS)) + if lclProcessed { + processed = lclProcessed + } + if err != nil || + (lclProcessed && !reqProcessor.ContinueOnSuccess) { + break + } + } + if err != nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %s processing message: %s", + utils.DiameterAgent, err.Error(), m)) + writeOnConn(c, m.Answer(diam.UnableToComply)) + return + } else if !processed { + utils.Logger.Warning( + fmt.Sprintf("<%s> no request processor enabled, ignoring message %s from %s", + utils.DiameterAgent, m, c.RemoteAddr())) + writeOnConn(c, m.Answer(diam.UnableToComply)) + return + } + a := m.Answer(diam.Success) + // write reply into message + for _, val := range rply.Values() { + nmItms, isNMItems := val.([]*config.NMItem) + if !isNMItems { + utils.Logger.Warning( + fmt.Sprintf("<%s> cannot encode reply field: %s, ignoring message %s from %s", + utils.DiameterAgent, utils.ToJSON(val), m, c.RemoteAddr())) + writeOnConn(c, m.Answer(diam.UnableToComply)) + return + } + // find out the first itm which is not an attribute + var itm *config.NMItem + for _, cfgItm := range nmItms { + if cfgItm.Config == nil || cfgItm.Config.AttributeID == "" { + itm = cfgItm + break + } + } + + if itm == nil { + continue // all attributes, not writable to diameter packet + } + itmStr, err := utils.IfaceAsString(itm.Data) + if err != nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %s processing reply item: %s for message: %s", + utils.DiameterAgent, err.Error(), utils.ToJSON(itm), m)) + writeOnConn(c, m.Answer(diam.UnableToComply)) + return + } + var newBranch bool + if itm.Config != nil && itm.Config.NewBranch { + newBranch = true + } + if err := messageSetAVPsWithPath(a, itm.Path, + itmStr, newBranch, da.cgrCfg.DefaultTimezone); err != nil { + utils.Logger.Warning( + fmt.Sprintf("<%s> error: %s setting reply item: %s for message: %s", + utils.DiameterAgent, err.Error(), utils.ToJSON(itm), m)) + writeOnConn(c, m.Answer(diam.UnableToComply)) + return + } + } + writeOnConn(c, a) +} + +func (da *DiameterAgent) processRequest(reqProcessor *config.DARequestProcessor, + agReq *AgentRequest) (processed bool, err error) { + if pass, err := da.filterS.Pass(agReq.tenant, + reqProcessor.Filters, agReq); err != nil || !pass { + return pass, err + } + if agReq.CGRRequest, err = agReq.AsNavigableMap(reqProcessor.RequestFields); err != nil { + return + } + cgrEv := agReq.CGRRequest.AsCGREvent(agReq.tenant, utils.NestingSep) + var reqType string + for _, typ := range []string{ + utils.MetaDryRun, utils.MetaAuth, + utils.MetaInitiate, utils.MetaUpdate, + utils.MetaTerminate, utils.MetaEvent, + utils.MetaCDRs} { + if reqProcessor.Flags.HasKey(typ) { // request type is identified through flags + reqType = typ + break + } + } + switch reqType { + default: + return false, fmt.Errorf("unknown request type: <%s>", reqType) + case utils.MetaDryRun: + utils.Logger.Info( + fmt.Sprintf("<%s> DRY_RUN, processorID: %s, CGREvent: %s", + utils.DiameterAgent, reqProcessor.ID, utils.ToJSON(cgrEv))) + case utils.MetaAuth: + authArgs := sessions.NewV1AuthorizeArgs( + reqProcessor.Flags.HasKey(utils.MetaAttributes), + reqProcessor.Flags.HasKey(utils.MetaResources), + reqProcessor.Flags.HasKey(utils.MetaAccounts), + reqProcessor.Flags.HasKey(utils.MetaThresholds), + reqProcessor.Flags.HasKey(utils.MetaStats), + reqProcessor.Flags.HasKey(utils.MetaSuppliers), + reqProcessor.Flags.HasKey(utils.MetaSuppliersIgnoreErrors), + reqProcessor.Flags.HasKey(utils.MetaSuppliersEventCost), + *cgrEv) + var authReply sessions.V1AuthorizeReply + err = da.sessionS.Call(utils.SessionSv1AuthorizeEvent, + authArgs, &authReply) + if agReq.CGRReply, err = NewCGRReply(&authReply, err); err != nil { + return + } + case utils.MetaInitiate: + initArgs := sessions.NewV1InitSessionArgs( + reqProcessor.Flags.HasKey(utils.MetaAttributes), + reqProcessor.Flags.HasKey(utils.MetaResources), + reqProcessor.Flags.HasKey(utils.MetaAccounts), + reqProcessor.Flags.HasKey(utils.MetaThresholds), + reqProcessor.Flags.HasKey(utils.MetaStats), *cgrEv) + var initReply sessions.V1InitSessionReply + err = da.sessionS.Call(utils.SessionSv1InitiateSession, + initArgs, &initReply) + if agReq.CGRReply, err = NewCGRReply(&initReply, err); err != nil { + return + } + case utils.MetaUpdate: + updateArgs := sessions.NewV1UpdateSessionArgs( + reqProcessor.Flags.HasKey(utils.MetaAttributes), + reqProcessor.Flags.HasKey(utils.MetaAccounts), *cgrEv) + var updateReply sessions.V1UpdateSessionReply + err = da.sessionS.Call(utils.SessionSv1UpdateSession, + updateArgs, &updateReply) + if agReq.CGRReply, err = NewCGRReply(&updateReply, err); err != nil { + return + } + case utils.MetaTerminate: + terminateArgs := sessions.NewV1TerminateSessionArgs( + reqProcessor.Flags.HasKey(utils.MetaAccounts), + reqProcessor.Flags.HasKey(utils.MetaResources), + reqProcessor.Flags.HasKey(utils.MetaThresholds), + reqProcessor.Flags.HasKey(utils.MetaStats), *cgrEv) + var tRply string + err = da.sessionS.Call(utils.SessionSv1TerminateSession, + terminateArgs, &tRply) + if agReq.CGRReply, err = NewCGRReply(nil, err); err != nil { + return + } + case utils.MetaEvent: + evArgs := sessions.NewV1ProcessEventArgs( + reqProcessor.Flags.HasKey(utils.MetaResources), + reqProcessor.Flags.HasKey(utils.MetaAccounts), + reqProcessor.Flags.HasKey(utils.MetaAttributes), + reqProcessor.Flags.HasKey(utils.MetaThresholds), + reqProcessor.Flags.HasKey(utils.MetaStats), + *cgrEv) + var eventRply sessions.V1ProcessEventReply + err = da.sessionS.Call(utils.SessionSv1ProcessEvent, + evArgs, &eventRply) + if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { + cgrEv.Event[utils.Usage] = 0 // avoid further debits + } else if eventRply.MaxUsage != nil { + cgrEv.Event[utils.Usage] = *eventRply.MaxUsage // make sure the CDR reflects the debit + } + if agReq.CGRReply, err = NewCGRReply(&eventRply, err); err != nil { + return + } + case utils.MetaCDRs: // allow CDR processing + } + // separate request so we can capture the Terminate/Event also here + if reqProcessor.Flags.HasKey(utils.MetaCDRs) && + !reqProcessor.Flags.HasKey(utils.MetaDryRun) { + var rplyCDRs string + if err = da.sessionS.Call(utils.SessionSv1ProcessCDR, + cgrEv, &rplyCDRs); err != nil { + agReq.CGRReply.Set([]string{utils.Error}, err.Error(), false) + } + } + if nM, err := agReq.AsNavigableMap(reqProcessor.ReplyFields); err != nil { + return false, err + } else { + agReq.Reply.Merge(nM) + } + if reqType == utils.MetaDryRun { + utils.Logger.Info( + fmt.Sprintf("<%s> DRY_RUN, Diameter reply: %s", + utils.DiameterAgent, agReq.Reply)) + } + return true, nil +} diff --git a/agents/dmtclient.go b/agents/diamclient.go similarity index 100% rename from agents/dmtclient.go rename to agents/diamclient.go diff --git a/agents/dmtagent.go b/agents/dmtagent.go deleted file mode 100644 index b6f4bec8b..000000000 --- a/agents/dmtagent.go +++ /dev/null @@ -1,269 +0,0 @@ -/* -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 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 -*/ - -package agents - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "sync" - "time" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/sessions" - "github.com/cgrates/cgrates/utils" - "github.com/cgrates/rpcclient" - "github.com/fiorix/go-diameter/diam" - "github.com/fiorix/go-diameter/diam/datatype" - "github.com/fiorix/go-diameter/diam/sm" -) - -func NewDiameterAgent(cgrCfg *config.CGRConfig, sessionS rpcclient.RpcClientConnection, - pubsubs rpcclient.RpcClientConnection) (*DiameterAgent, error) { - da := &DiameterAgent{cgrCfg: cgrCfg, sessionS: sessionS, - pubsubs: pubsubs, connMux: new(sync.Mutex)} - if reflect.ValueOf(da.pubsubs).IsNil() { - da.pubsubs = nil // Empty it so we can check it later - } - dictsDir := cgrCfg.DiameterAgentCfg().DictionariesDir - if len(dictsDir) != 0 { - if err := loadDictionaries(dictsDir, "DiameterAgent"); err != nil { - return nil, err - } - } - return da, nil -} - -type DiameterAgent struct { - cgrCfg *config.CGRConfig - sessionS rpcclient.RpcClientConnection // Connection towards CGR-SMG component - pubsubs rpcclient.RpcClientConnection // Connection towards CGR-PubSub component - connMux *sync.Mutex // Protect connection for read/write -} - -// Creates the message handlers -func (self *DiameterAgent) handlers() diam.Handler { - settings := &sm.Settings{ - OriginHost: datatype.DiameterIdentity(self.cgrCfg.DiameterAgentCfg().OriginHost), - OriginRealm: datatype.DiameterIdentity(self.cgrCfg.DiameterAgentCfg().OriginRealm), - VendorID: datatype.Unsigned32(self.cgrCfg.DiameterAgentCfg().VendorId), - ProductName: datatype.UTF8String(self.cgrCfg.DiameterAgentCfg().ProductName), - FirmwareRevision: datatype.Unsigned32(utils.DIAMETER_FIRMWARE_REVISION), - } - dSM := sm.New(settings) - dSM.HandleFunc("CCR", self.handleCCR) - dSM.HandleFunc("ALL", self.handleALL) - go func() { - for err := range dSM.ErrorReports() { - utils.Logger.Err(fmt.Sprintf(" StateMachine error: %+v", err)) - } - }() - return dSM -} - -func (da DiameterAgent) processCCR(ccr *CCR, reqProcessor *config.DARequestProcessor, - procVars processorVars, cca *CCA) (processed bool, err error) { - passesAllFilters := true - for _, fldFilter := range reqProcessor.RequestFilter { - if passes, _ := passesFieldFilter(ccr.diamMessage, fldFilter, nil); !passes { - passesAllFilters = false - } - } - if !passesAllFilters { // Not going with this processor further - return false, nil - } - if reqProcessor.DryRun { // DryRun should log the matching processor as well as the received CCR - utils.Logger.Info(fmt.Sprintf(" RequestProcessor: %s", reqProcessor.Id)) - utils.Logger.Info(fmt.Sprintf(" CCR message: %s", ccr.diamMessage)) - } - if !reqProcessor.AppendCCA { - *cca = *NewBareCCAFromCCR(ccr, da.cgrCfg.DiameterAgentCfg().OriginHost, da.cgrCfg.DiameterAgentCfg().OriginRealm) - procVars = make(processorVars) - } - smgEv, err := ccr.AsMapIface(reqProcessor.CCRFields) - if err != nil { - utils.Logger.Err(fmt.Sprintf(" Processing message: %+v AsSMGenericEvent, error: %s", ccr.diamMessage, err)) - *cca = *NewBareCCAFromCCR(ccr, da.cgrCfg.DiameterAgentCfg().OriginHost, da.cgrCfg.DiameterAgentCfg().OriginRealm) - if err := messageSetAVPsWithPath(cca.diamMessage, []interface{}{"Result-Code"}, strconv.Itoa(DiameterRatingFailed), - false, da.cgrCfg.DiameterAgentCfg().Timezone); err != nil { - utils.Logger.Err(fmt.Sprintf(" Processing message: %+v messageSetAVPsWithPath, error: %s", cca.diamMessage, err.Error())) - return false, err - } - return false, ErrDiameterRatingFailed - } - if len(reqProcessor.Flags) != 0 { - smgEv[utils.CGRFlags] = reqProcessor.Flags.String() // Populate CGRFlags automatically - for flag, val := range reqProcessor.Flags { - procVars[flag] = val - } - } - if reqProcessor.PublishEvent && da.pubsubs != nil { - evt, err := engine.NewMapEvent(smgEv).AsMapString(nil) - if err != nil { - *cca = *NewBareCCAFromCCR(ccr, da.cgrCfg.DiameterAgentCfg().OriginHost, da.cgrCfg.DiameterAgentCfg().OriginRealm) - if err := messageSetAVPsWithPath(cca.diamMessage, []interface{}{"Result-Code"}, strconv.Itoa(DiameterRatingFailed), - false, da.cgrCfg.DiameterAgentCfg().Timezone); err != nil { - return false, err - } - utils.Logger.Err(fmt.Sprintf(" Processing message: %+v failed converting SMGEvent to pubsub one, error: %s", ccr.diamMessage, err)) - return false, ErrDiameterRatingFailed - } - var reply string - if err := da.pubsubs.Call("PubSubV1.Publish", engine.CgrEvent(evt), &reply); err != nil { - *cca = *NewBareCCAFromCCR(ccr, da.cgrCfg.DiameterAgentCfg().OriginHost, da.cgrCfg.DiameterAgentCfg().OriginRealm) - if err := messageSetAVPsWithPath(cca.diamMessage, []interface{}{"Result-Code"}, strconv.Itoa(DiameterRatingFailed), - false, da.cgrCfg.DiameterAgentCfg().Timezone); err != nil { - return false, err - } - utils.Logger.Err(fmt.Sprintf(" Processing message: %+v failed publishing event, error: %s", ccr.diamMessage, err)) - return false, ErrDiameterRatingFailed - } - } - if reqProcessor.DryRun { // DryRun does not send over network - utils.Logger.Info(fmt.Sprintf(" SMGenericEvent: %+v", smgEv)) - procVars[CGRResultCode] = strconv.Itoa(diam.LimitedSuccess) - } else { // Query SessionS over APIs - var tnt string - if tntIf, has := smgEv[utils.Tenant]; has { - if tntStr, err := utils.IfaceAsString(tntIf); err == nil { - tnt = tntStr - } - } - cgrEv := &utils.CGREvent{ - Tenant: utils.FirstNonEmpty(tnt, - config.CgrConfig().DefaultTenant), - ID: "dmt:" + utils.UUIDSha1Prefix(), - Time: utils.TimePointer(time.Now()), - Event: smgEv, - } - switch ccr.CCRequestType { - case 1: - var initReply sessions.V1InitSessionReply - err = da.sessionS.Call(utils.SessionSv1InitiateSession, - procVars.asV1InitSessionArgs(cgrEv), &initReply) - if procVars[utils.MetaCGRReply], err = NewCGRReply(&initReply, err); err != nil { - return - } - case 2: - var updateReply sessions.V1UpdateSessionReply - err = da.sessionS.Call(utils.SessionSv1UpdateSession, - procVars.asV1UpdateSessionArgs(cgrEv), &updateReply) - if procVars[utils.MetaCGRReply], err = NewCGRReply(&updateReply, err); err != nil { - return - } - case 3, 4: // Handle them together since we generate CDR for them - var rpl string - if ccr.CCRequestType == 3 { - if err = da.sessionS.Call(utils.SessionSv1TerminateSession, - procVars.asV1TerminateSessionArgs(cgrEv), &rpl); err != nil { - procVars[utils.MetaCGRReply], _ = NewCGRReply(nil, err) - } - } else if ccr.CCRequestType == 4 { - var evntRply sessions.V1ProcessEventReply - err = da.sessionS.Call(utils.SessionSv1ProcessEvent, - procVars.asV1ProcessEventArgs(cgrEv), &evntRply) - if utils.ErrHasPrefix(err, utils.RalsErrorPrfx) { - cgrEv.Event[utils.Usage] = 0 // avoid further debits - } else if evntRply.MaxUsage != nil { - cgrEv.Event[utils.Usage] = *evntRply.MaxUsage // make sure the CDR reflects the debit - } - if procVars[utils.MetaCGRReply], err = NewCGRReply(&evntRply, err); err != nil { - return - } - } - if da.cgrCfg.DiameterAgentCfg().CreateCDR && - (!da.cgrCfg.DiameterAgentCfg().CDRRequiresSession || err == nil || - !strings.HasSuffix(err.Error(), utils.ErrNoActiveSession.Error())) { // Check if CDR requires session - if errCdr := da.sessionS.Call(utils.SessionSv1ProcessCDR, cgrEv, &rpl); errCdr != nil { - err = errCdr - procVars[utils.MetaCGRReply], _ = NewCGRReply(nil, err) - } - } - } - } - diamCode := strconv.Itoa(diam.Success) - if procVars.hasVar(CGRResultCode) { - diamCode, _ = procVars.valAsString(CGRResultCode) - } - if err := messageSetAVPsWithPath(cca.diamMessage, []interface{}{"Result-Code"}, diamCode, - false, da.cgrCfg.DiameterAgentCfg().Timezone); err != nil { - return false, err - } - if err := cca.SetProcessorAVPs(reqProcessor, procVars); err != nil { - if err := messageSetAVPsWithPath(cca.diamMessage, []interface{}{"Result-Code"}, strconv.Itoa(DiameterRatingFailed), - false, da.cgrCfg.DiameterAgentCfg().Timezone); err != nil { - return false, err - } - utils.Logger.Err(fmt.Sprintf(" CCA SetProcessorAVPs for message: %+v, error: %s", ccr.diamMessage, err)) - return false, ErrDiameterRatingFailed - } - if reqProcessor.DryRun { - utils.Logger.Info(fmt.Sprintf(" CCA message: %s", cca.diamMessage)) - } - return true, nil -} - -func (self *DiameterAgent) handlerCCR(c diam.Conn, m *diam.Message) { - ccr, err := NewCCRFromDiameterMessage(m, self.cgrCfg.DiameterAgentCfg().DebitInterval) - if err != nil { - utils.Logger.Err(fmt.Sprintf(" Unmarshaling message: %s, error: %s", m, err)) - return - } - cca := NewBareCCAFromCCR(ccr, self.cgrCfg.DiameterAgentCfg().OriginHost, self.cgrCfg.DiameterAgentCfg().OriginRealm) - var processed, lclProcessed bool - procVars := make(processorVars) // Shared between processors - for _, reqProcessor := range self.cgrCfg.DiameterAgentCfg().RequestProcessors { - lclProcessed, err = self.processCCR(ccr, reqProcessor, procVars, cca) - if lclProcessed { // Process local so we don't overwrite globally - processed = lclProcessed - } - if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) { - break - } - } - if err != nil && err != ErrDiameterRatingFailed { - utils.Logger.Err(fmt.Sprintf(" CCA SetProcessorAVPs for message: %+v, error: %s", ccr.diamMessage, err)) - return - } else if !processed { - utils.Logger.Err(fmt.Sprintf(" No request processor enabled for CCR: %s, ignoring request", ccr.diamMessage)) - return - } - self.connMux.Lock() - defer self.connMux.Unlock() - if _, err := cca.AsDiameterMessage().WriteTo(c); err != nil { - utils.Logger.Err(fmt.Sprintf(" Failed to write message to %s: %s\n%s\n", c.RemoteAddr(), err, cca.AsDiameterMessage())) - return - } -} - -// Simply dispatch the handling in goroutines -// Could be futher improved with rate control -func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) { - go self.handlerCCR(c, m) -} - -func (self *DiameterAgent) handleALL(c diam.Conn, m *diam.Message) { - utils.Logger.Warning(fmt.Sprintf(" Received unexpected message from %s:\n%s", c.RemoteAddr(), m)) -} - -func (self *DiameterAgent) ListenAndServe() error { - return diam.ListenAndServe(self.cgrCfg.DiameterAgentCfg().Listen, self.handlers(), nil) -} diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go deleted file mode 100644 index df69ca8a0..000000000 --- a/agents/dmtagent_it_test.go +++ /dev/null @@ -1,1075 +0,0 @@ -// +build integration - -/* -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 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 -*/ - -package agents - -import ( - "flag" - "net/rpc" - "net/rpc/jsonrpc" - "path" - "reflect" - "testing" - "time" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/utils" - "github.com/fiorix/go-diameter/diam" - "github.com/fiorix/go-diameter/diam/avp" - "github.com/fiorix/go-diameter/diam/datatype" - "github.com/fiorix/go-diameter/diam/dict" -) - -var waitRater = flag.Int("wait_rater", 100, "Number of miliseconds to wait for rater to start and cache") -var dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here") -var interations = flag.Int("iterations", 1, "Number of iterations to do for dry run simulation") -var replyTimeout = flag.String("reply_timeout", "1s", "Maximum duration to wait for a reply") - -var daCfgPath string -var daCfg *config.CGRConfig -var apierRpc *rpc.Client -var dmtClient *DiameterClient - -var rplyTimeout time.Duration - -func TestDmtAgentInitCfg(t *testing.T) { - daCfgPath = path.Join(*dataDir, "conf", "samples", "dmtagent") - // Init config first - var err error - daCfg, err = config.NewCGRConfigFromFolder(daCfgPath) - if err != nil { - t.Error(err) - } - daCfg.DataFolderPath = *dataDir // Share DataFolderPath through config towards StoreDb for Flush() - config.SetCgrConfig(daCfg) - rplyTimeout, _ = utils.ParseDurationWithSecs(*replyTimeout) -} - -func TestDmtAgentCCRAsSMGenericEvent(t *testing.T) { - cfgDefaults, _ := config.NewDefaultCGRConfig() - loadDictionaries(cfgDefaults.DiameterAgentCfg().DictionariesDir, "UNIT_TEST") - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - ccr := &CCR{ - SessionId: "routinga;1442095190;1476802709", - OriginHost: cfgDefaults.DiameterAgentCfg().OriginHost, - OriginRealm: cfgDefaults.DiameterAgentCfg().OriginRealm, - DestinationHost: cfgDefaults.DiameterAgentCfg().OriginHost, - DestinationRealm: cfgDefaults.DiameterAgentCfg().OriginRealm, - AuthApplicationId: 4, - ServiceContextId: "voice@huawei.com", - CCRequestType: 1, - CCRequestNumber: 0, - EventTimestamp: time.Date(2015, 11, 23, 12, 22, 24, 0, time.UTC), - ServiceIdentifier: 0, - SubscriptionId: []struct { - SubscriptionIdType int `avp:"Subscription-Id-Type"` - SubscriptionIdData string `avp:"Subscription-Id-Data"` - }{ - struct { - SubscriptionIdType int `avp:"Subscription-Id-Type"` - SubscriptionIdData string `avp:"Subscription-Id-Data"` - }{SubscriptionIdType: 0, SubscriptionIdData: "4986517174963"}, - struct { - SubscriptionIdType int `avp:"Subscription-Id-Type"` - SubscriptionIdData string `avp:"Subscription-Id-Data"` - }{SubscriptionIdType: 0, SubscriptionIdData: "4986517174963"}}, - debitInterval: time.Duration(300) * time.Second, - } - ccr.RequestedServiceUnit.CCTime = 300 - ccr.UsedServiceUnit.CCTime = 0 - ccr.ServiceInformation.INInformation.CallingPartyAddress = "4986517174963" - ccr.ServiceInformation.INInformation.CalledPartyAddress = "4986517174964" - ccr.ServiceInformation.INInformation.RealCalledNumber = "4986517174964" - ccr.ServiceInformation.INInformation.ChargeFlowType = 0 - ccr.ServiceInformation.INInformation.CallingVlrNumber = "49123956767" - ccr.ServiceInformation.INInformation.CallingCellIDOrSAI = "12340185301425" - ccr.ServiceInformation.INInformation.BearerCapability = "capable" - ccr.ServiceInformation.INInformation.CallReferenceNumber = "askjadkfjsdf" - ccr.ServiceInformation.INInformation.MSCAddress = "123324234" - ccr.ServiceInformation.INInformation.TimeZone = 0 - ccr.ServiceInformation.INInformation.CalledPartyNP = "4986517174964" - ccr.ServiceInformation.INInformation.SSPTime = "20091020120101" - var err error - if ccr.diamMessage, err = ccr.AsDiameterMessage(); err != nil { - t.Error(err) - } - eSMGE := map[string]interface{}{"EventName": DIAMETER_CCR, - "OriginID": "routinga;1442095190;1476802709", - "Account": "*users", "Category": "call", - "AnswerTime": "2015-11-23 12:22:24 +0000 UTC", - "Destination": "4986517174964", "Direction": "*out", - "RequestType": "*users", - "SetupTime": "2015-11-23 12:22:24 +0000 UTC", - "Subject": "*users", "SubscriberId": "4986517174963", - "ToR": "*voice", "Tenant": "*users", "Usage": "5m0s"} - ccrFields := []*config.CfgCdrField{ - &config.CfgCdrField{Tag: "TOR", FieldId: "ToR", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^*voice", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "OriginID", FieldId: "OriginID", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("Session-Id", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "RequestType", FieldId: "RequestType", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^*users", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Direction", FieldId: "Direction", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^*out", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Tenant", FieldId: "Tenant", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^*users", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Category", FieldId: "Category", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^call", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Account", FieldId: "Account", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^*users", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Subject", FieldId: "Subject", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("^*users", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Destination", FieldId: "Destination", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("Service-Information>IN-Information>Real-Called-Number", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "SetupTime", FieldId: "SetupTime", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("Event-Timestamp", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "AnswerTime", FieldId: "AnswerTime", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("Event-Timestamp", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Usage", FieldId: "Usage", Type: "*handler", HandlerId: "*ccr_usage", Mandatory: true}, - &config.CfgCdrField{Tag: "SubscriberID", FieldId: "SubscriberId", Type: "*composed", - Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true}} - if smge, err := ccr.AsMapIface(ccrFields); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eSMGE, smge) { - t.Errorf("Expecting: %+v, received: %+v", eSMGE, smge) - } -} - -func TestDmtAgentPopulateCCTotalOctets(t *testing.T) { - daRP := &config.DARequestProcessor{CCAFields: []*config.CfgCdrField{ - &config.CfgCdrField{Tag: "GrantedUnit", - FieldFilter: utils.ParseRSRFieldsMustCompile("CGRError(^$)", utils.INFIELD_SEP), - FieldId: "Multiple-Services-Credit-Control>Granted-Service-Unit>CC-Time", - Type: utils.META_COMPOSED, - Value: utils.ParseRSRFieldsMustCompile("CGRMaxUsage", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "GrantedOctet", - FieldFilter: utils.ParseRSRFieldsMustCompile("CGRError(^$)", utils.INFIELD_SEP), - FieldId: "Multiple-Services-Credit-Control>Granted-Service-Unit>CC-Total-Octets", - Type: utils.META_COMPOSED, Value: utils.ParseRSRFieldsMustCompile("CGRMaxUsage", utils.INFIELD_SEP), - Mandatory: true}, - }} - ccr := new(CCR) - ccr.diamMessage = ccr.AsBareDiameterMessage() - cca := NewBareCCAFromCCR(ccr, "cgr-da", "cgrates.org") - if err := cca.SetProcessorAVPs(daRP, - processorVars{CGRError: "", CGRMaxUsage: "153600"}); err != nil { - t.Error(err) - } - if avps, err := cca.diamMessage.FindAVPsWithPath([]interface{}{ - "Multiple-Services-Credit-Control", "Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Not found") - } else if strResult := avpValAsString(avps[0]); strResult != "153600" { // Result-Code set in the template - t.Errorf("Expecting 153600, received: %s", strResult) - } - if avps, err := cca.diamMessage.FindAVPsWithPath([]interface{}{ - "Multiple-Services-Credit-Control", "Granted-Service-Unit", "CC-Total-Octets"}, - dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Not found") - } else if strResult := avpValAsString(avps[0]); strResult != "153600" { // Result-Code set in the template - t.Errorf("Expecting 153600, received: %s", strResult) - } -} - -// Remove data in both rating and accounting db -func TestDmtAgentResetDataDb(t *testing.T) { - if err := engine.InitDataDb(daCfg); err != nil { - t.Fatal(err) - } -} - -// Wipe out the cdr database -func TestDmtAgentResetStorDb(t *testing.T) { - if err := engine.InitStorDb(daCfg); err != nil { - t.Fatal(err) - } -} - -// Start CGR Engine -func TestDmtAgentStartEngine(t *testing.T) { - if _, err := engine.StopStartEngine(daCfgPath, 4000); err != nil { - t.Fatal(err) - } -} - -// Connect rpc client to rater -func TestDmtAgentApierRpcConn(t *testing.T) { - var err error - apierRpc, err = jsonrpc.Dial("tcp", daCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed - if err != nil { - t.Fatal(err) - } -} - -// Load the tariff plan, creating accounts and their balances -func TestDmtAgentTPFromFolder(t *testing.T) { - attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "oldtutorial")} - var loadInst utils.LoadInstance - if err := apierRpc.Call("ApierV2.LoadTariffPlanFromFolder", attrs, &loadInst); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(1000) * time.Millisecond) // Give time for scheduler to execute topups -} - -func TestDmtAgentConnectDiameterClient(t *testing.T) { - dmtClient, err = NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, - daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DictionariesDir) - if err != nil { - t.Fatal(err) - } -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:47:26Z"' -func TestDmtAgentSendCCRInit(t *testing.T) { - cdr := &engine.CDR{ - CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), - OrderID: 123, ToR: utils.VOICE, OriginID: "testccr1", OriginHost: "192.168.1.1", - Source: utils.UNIT_TEST, RequestType: utils.META_RATED, - Tenant: "cgrates.org", Category: "call", Account: "1001", - Subject: "1001", Destination: "1004", - SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), - AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), - Usage: time.Duration(0), RunID: utils.DEFAULT_RUNID, - ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, - } - ccr := storedCdrToCCR(cdr, "UNIT_TEST", - daCfg.DiameterAgentCfg().OriginRealm, - daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, - utils.DIAMETER_FIRMWARE_REVISION, - daCfg.DiameterAgentCfg().DebitInterval, false) - m, err := ccr.AsDiameterMessage() - if err != nil { - t.Error(err) - } - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, - dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Granted-Service-Unit not found") - } else if strCCTime := avpValAsString(avps[0]); strCCTime != "300" { - t.Errorf("Expecting 300, received: %s", strCCTime) - } - if result, err := msg.FindAVP("Result-Code", dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if resultStr := avpValAsString(result); resultStr != "2001" { - t.Errorf("Expecting 2001, received: %s", resultStr) - } - var acnt *engine.Account - attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.5008 - if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { - t.Error(err) - } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { - t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) - } -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:52:26Z"' -func TestDmtAgentSendCCRUpdate(t *testing.T) { - cdr := &engine.CDR{CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderID: 123, ToR: utils.VOICE, - OriginID: "testccr1", OriginHost: "192.168.1.1", Source: utils.UNIT_TEST, RequestType: utils.META_RATED, - Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", - SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), RunID: utils.DEFAULT_RUNID, - Usage: time.Duration(300) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, - } - ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) - m, err := ccr.AsDiameterMessage() - if err != nil { - t.Error(err) - } - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Granted-Service-Unit not found") - } else if strCCTime := avpValAsString(avps[0]); strCCTime != "300" { - t.Errorf("Expecting 300, received: %s, (%+v)", strCCTime, avps) - } - var acnt *engine.Account - attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.251800 - if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { - t.Error(err) - } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { - t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) - } -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:57:26Z"' -func TestDmtAgentSendCCRUpdate2(t *testing.T) { - cdr := &engine.CDR{CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderID: 123, ToR: utils.VOICE, - OriginID: "testccr1", OriginHost: "192.168.1.1", Source: utils.UNIT_TEST, RequestType: utils.META_RATED, - Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1004", - SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), RunID: utils.DEFAULT_RUNID, - Usage: time.Duration(600) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, - } - ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) - m, err := ccr.AsDiameterMessage() - if err != nil { - t.Error(err) - } - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Granted-Service-Unit not found") - } else if strCCTime := avpValAsString(avps[0]); strCCTime != "300" { - t.Errorf("Expecting 300, received: %s", strCCTime) - } - var acnt *engine.Account - attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.002800 - if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { - t.Error(err) - } else if utils.Round(acnt.BalanceMap[utils.MONETARY].GetTotalValue(), 5, utils.ROUNDING_MIDDLE) != eAcntVal { - t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) - } -} - -func TestDmtAgentSendCCRTerminate(t *testing.T) { - cdr := &engine.CDR{ - CGRID: utils.Sha1("testccr1", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), - OrderID: 123, ToR: utils.VOICE, - OriginID: "testccr1", OriginHost: "192.168.1.1", - Source: utils.UNIT_TEST, RequestType: utils.META_RATED, - Tenant: "cgrates.org", Category: "call", Account: "1001", - Subject: "1001", Destination: "1004", - SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), - AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), - RunID: utils.DEFAULT_RUNID, - Usage: time.Duration(610) * time.Second, - ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, - } - ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, - daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, - daCfg.DiameterAgentCfg().DebitInterval, true) - m, err := ccr.AsDiameterMessage() - if err != nil { - t.Error(err) - } - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No answer to CCR terminate received") - } - var acnt *engine.Account - attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.243500 - if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { - t.Error(err) - } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 - t.Errorf("Expected: %v, received: %v", - eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) - } -} - -func TestDmtAgentSendCCRSMS(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("testccr2")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("message@huawei.com")) - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(4)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("104502200011")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(0)) - ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(1))}}) - ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP - }, - }), - diam.NewAVP(2000, avp.Mbit, 10415, &diam.GroupedAVP{ // SMS-Information - AVP: []*diam.AVP{ - diam.NewAVP(886, avp.Mbit, 10415, &diam.GroupedAVP{ // Originator-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1003")), // Address-Data - }}), - diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // Recipient-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1002")), // Address-Data - }}), - }, - }), - }}) - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - - time.Sleep(time.Duration(100) * time.Millisecond) - dmtClient.ReceivedMessage(rplyTimeout) // Discard the received message so we can test next one - /* - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Granted-Service-Unit not found") - } else if strCCTime := avpValAsString(avps[0]); strCCTime != "0" { - t.Errorf("Expecting 0, received: %s", strCCTime) - } - var acnt *engine.Account - attrs := &utils.AttrGetAccount{Tenant: "cgrates.org", Account: "1001"} - eAcntVal := 9.205 - if err := apierRpc.Call("ApierV2.GetAccount", attrs, &acnt); err != nil { - t.Error(err) - } else if acnt.BalanceMap[utils.MONETARY].GetTotalValue() != eAcntVal { // Should also consider derived charges which double the cost of 6m10s - 2x0.7584 - t.Errorf("Expected: %f, received: %f", eAcntVal, acnt.BalanceMap[utils.MONETARY].GetTotalValue()) - } - */ - var cdrs []*engine.ExternalCDR - req := utils.RPCCDRsFilter{RunIDs: []string{utils.META_DEFAULT}, ToRs: []string{utils.SMS}} - if err := apierRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil { - t.Error("Unexpected error: ", err.Error()) - } else if len(cdrs) != 1 { - t.Error("Unexpected number of CDRs returned: ", len(cdrs)) - } else { - if cdrs[0].Usage != "1" { // should be 1 but maxUsage returns rounded version - t.Errorf("Unexpected CDR Usage received, cdr: %+v ", cdrs[0]) - } - if cdrs[0].Cost != 0.6 { - t.Errorf("Unexpected CDR Cost received, cdr: %+v ", cdrs[0].Cost) - } - } -} - -func TestDmtAgentSendCCRSMSWrongAccount(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("testccr3")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("message@huawei.com")) - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(4)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("non_existent")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("104502200011")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(0)) - ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(1))}}) - ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP - }, - }), - diam.NewAVP(2000, avp.Mbit, 10415, &diam.GroupedAVP{ // SMS-Information - AVP: []*diam.AVP{ - diam.NewAVP(886, avp.Mbit, 10415, &diam.GroupedAVP{ // Originator-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("49602200011")), // Address-Data - }}), - diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // Recipient-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("49780029555")), // Address-Data - }}), - }, - }), - }}) - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(100) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) // Discard the received message so we can test next one - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code") - } else if strResult := avpValAsString(avps[0]); strResult != "5030" { // Result-Code set in the template - t.Errorf("Expecting 5030, received: %s", strResult) - } -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:47:26Z"' -func TestDmtAgentSendCCRInitWrongAccount(t *testing.T) { - cdr := &engine.CDR{CGRID: utils.Sha1("testccr4", time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC).String()), OrderID: 123, ToR: utils.VOICE, - OriginID: "testccr4", OriginHost: "192.168.1.1", Source: utils.UNIT_TEST, RequestType: utils.META_RATED, - Tenant: "cgrates.org", Category: "call", Account: "non_existent", Subject: "non_existent", Destination: "1004", - SetupTime: time.Date(2015, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2015, 11, 7, 8, 42, 26, 0, time.UTC), RunID: utils.DEFAULT_RUNID, - Usage: time.Duration(0) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, - } - ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, - daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, daCfg.DiameterAgentCfg().DebitInterval, false) - m, err := ccr.AsDiameterMessage() - if err != nil { - t.Error(err) - } - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(100) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) // Discard the received message so we can test next one - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code") - } else if strResult := avpValAsString(avps[0]); strResult != "5030" { // Result-Code set in the template - t.Errorf("Expecting 5030, received: %s", strResult) - } -} - -func TestDmtAgentSendCCRSimpaEvent(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("testccr5")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("routing1.huawei.com")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("simpa@huawei.com")) - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(4)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 13, 16, 47, 58, 0, time.UTC))) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(1)) - ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCMoney, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.UnitValue, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(10000)), - diam.NewAVP(avp.Exponent, avp.Mbit, 0, datatype.Integer32(-5)), - }, - }), - diam.NewAVP(avp.CurrencyCode, avp.Mbit, 0, datatype.Unsigned32(33)), - }, - }), - }, - }) - ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // Service-Information - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP - }, - }), - diam.NewAVP(29000, avp.Mbit, 2011, &diam.GroupedAVP{ // MC-Information - AVP: []*diam.AVP{ - diam.NewAVP(20938, avp.Mbit, 2011, datatype.OctetString("0x38924012914528")), // HighLayerCharacteristics - diam.NewAVP(29002, avp.Mbit, 2011, datatype.UTF8String("12928471313847173")), // MC-Service-Id - diam.NewAVP(29003, avp.Mbit, 2011, datatype.UTF8String("SPV123456012123")), // TransparentData - diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // MC-Information - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(0)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("33780029555")), // Address-Data - }, - }), - }, - }), - }}) - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) // Discard the received message so we can test next one - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code") - } else if strResult := avpValAsString(avps[0]); strResult != "2001" { // Result-Code set in the template - t.Errorf("Expecting 2001, received: %s", strResult) - } -} - -func TestDmtAgentCdrs(t *testing.T) { - var cdrs []*engine.ExternalCDR - req := utils.RPCCDRsFilter{RunIDs: []string{utils.META_DEFAULT}, ToRs: []string{utils.VOICE}} - if err := apierRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil { - t.Error("Unexpected error: ", err.Error()) - } else if len(cdrs) != 1 { - t.Error("Unexpected number of CDRs returned: ", len(cdrs)) - } else { - if cdrs[0].Usage != "10m10s" { - t.Errorf("Unexpected CDR Usage received, cdr: %+v ", cdrs[0]) - } - if cdrs[0].Cost != 0.7565 { - t.Errorf("Unexpected CDR Cost received, cdr: %+v ", cdrs[0]) - } - } -} - -func TestDmtAgentSendDataGrpInit(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("testdatagrp")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("gprs@huawei.com")) - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(1)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("104502200011")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(0)) - ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(1))}}) - ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP - }, - }), - diam.NewAVP(2000, avp.Mbit, 10415, &diam.GroupedAVP{ // SMS-Information - AVP: []*diam.AVP{ - diam.NewAVP(886, avp.Mbit, 10415, &diam.GroupedAVP{ // Originator-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1003")), // Address-Data - }}), - diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // Recipient-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1002")), // Address-Data - }}), - }, - }), - }}) - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code not found") - } else if resCode := avpValAsString(avps[0]); resCode != "2001" { - t.Errorf("Expecting 2001, received: %s", resCode) - } -} - -func TestDmtAgentSendDataGrpUpdate(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("testdatagrp")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("gprs@huawei.com")) - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(2)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("104502200011")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(0)) - ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(1))}}) - ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP - }, - }), - diam.NewAVP(2000, avp.Mbit, 10415, &diam.GroupedAVP{ // SMS-Information - AVP: []*diam.AVP{ - diam.NewAVP(886, avp.Mbit, 10415, &diam.GroupedAVP{ // Originator-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1003")), // Address-Data - }}), - diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // Recipient-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1002")), // Address-Data - }}), - }, - }), - }}) - ccr.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(446, avp.Mbit, 0, &diam.GroupedAVP{ // Used-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(452, avp.Mbit, 0, datatype.Enumerated(0)), // Tariff-Change-Usage - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(20)), // CC-Time - diam.NewAVP(412, avp.Mbit, 0, datatype.Unsigned64(1000)), // CC-Input-Octets - diam.NewAVP(414, avp.Mbit, 0, datatype.Unsigned64(24)), // CC-Output-Octets - }, - }), - diam.NewAVP(432, avp.Mbit, 0, datatype.Unsigned32(1)), // Data session for group 1 - }, - }) - ccr.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(446, avp.Mbit, 0, &diam.GroupedAVP{ // Used-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(452, avp.Mbit, 0, datatype.Enumerated(0)), // Tariff-Change-Usage - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(20)), // CC-Time - diam.NewAVP(412, avp.Mbit, 0, datatype.Unsigned64(1024)), // CC-Input-Octets - diam.NewAVP(414, avp.Mbit, 0, datatype.Unsigned64(512)), // CC-Output-Octets - }, - }), - diam.NewAVP(432, avp.Mbit, 0, datatype.Unsigned32(2)), // Data session for group 2 - }, - }) - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(*waitRater) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code not found") - } else if resCode := avpValAsString(avps[0]); resCode != "2001" { - t.Errorf("Expecting 2001, received: %s", resCode) - } -} - -func TestDmtAgentSendDataGrpTerminate(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("testdatagrp")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("gprs@huawei.com")) - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(3)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), - diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("104502200011")), // Subscription-Id-Data - }}) - ccr.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.RequestedAction, avp.Mbit, 0, datatype.Enumerated(0)) - ccr.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(1))}}) - ccr.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ // - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("22509")), // Calling-Vlr-Number - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("4002")), // Called-Party-NP - }, - }), - diam.NewAVP(2000, avp.Mbit, 10415, &diam.GroupedAVP{ // SMS-Information - AVP: []*diam.AVP{ - diam.NewAVP(886, avp.Mbit, 10415, &diam.GroupedAVP{ // Originator-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1003")), // Address-Data - }}), - diam.NewAVP(1201, avp.Mbit, 10415, &diam.GroupedAVP{ // Recipient-Address - AVP: []*diam.AVP{ - diam.NewAVP(899, avp.Mbit, 10415, datatype.Enumerated(1)), // Address-Type - diam.NewAVP(897, avp.Mbit, 10415, datatype.UTF8String("1002")), // Address-Data - }}), - }, - }), - }}) - ccr.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(446, avp.Mbit, 0, &diam.GroupedAVP{ // Used-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(452, avp.Mbit, 0, datatype.Enumerated(0)), // Tariff-Change-Usage - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(20)), // CC-Time - diam.NewAVP(412, avp.Mbit, 0, datatype.Unsigned64(512)), // CC-Input-Octets - diam.NewAVP(414, avp.Mbit, 0, datatype.Unsigned64(0)), // CC-Output-Octets - }, - }), - }, - }) - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(3000) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code not found") - } else if resCode := avpValAsString(avps[0]); resCode != "2001" { - t.Errorf("Expecting 2001, received: %s", resCode) - } -} - -func TestDmtAgentSendDataGrpCDRs(t *testing.T) { - var cdrs []*engine.ExternalCDR - req := utils.RPCCDRsFilter{CGRIDs: []string{utils.Sha1("testdatagrp")}} - if err := apierRpc.Call("ApierV2.GetCdrs", req, &cdrs); err != nil { - t.Error("Unexpected error: ", err.Error()) - } else if len(cdrs) != 3 { - t.Error("Unexpected number of CDRs returned: ", len(cdrs)) - } -} - -/* -func TestDmtAgentDryRun1(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("cgrates;1451911932;00082")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("pubsub1")) // Match specific DryRun profile - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(4)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(0)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - if _, err := ccr.NewAVP("Framed-IP-Address", avp.Mbit, 0, datatype.UTF8String("10.228.16.4")); err != nil { - t.Error(err) - } - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(100) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No message returned") - } - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code") - } else if strResult := avpValAsString(avps[0]); strResult != "300" { // Result-Code set in the template - t.Errorf("Expecting 300, received: %s", strResult) - } -} -*/ - -func TestDmtAgentDryRun1(t *testing.T) { - ccr := diam.NewRequest(diam.CreditControl, 4, nil) - ccr.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("cgrates;1451911932;00082")) - ccr.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - ccr.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - ccr.NewAVP(avp.UserName, avp.Mbit, 0, datatype.UTF8String("CGR-DA")) - ccr.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - ccr.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("pubsub1")) // Match specific DryRun profile - ccr.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(2)) - ccr.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1)) - ccr.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2016, 1, 5, 11, 30, 10, 0, time.UTC))) - ccr.NewAVP(avp.TerminationCause, avp.Mbit, 0, datatype.Enumerated(1)) - ccr.NewAVP(443, avp.Mbit, 0, &diam.GroupedAVP{ // Subscription-Id - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1001")), // Subscription-Id-Data - }}) - ccr.NewAVP(443, avp.Mbit, 0, &diam.GroupedAVP{ // Subscription-Id - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(1)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208123456789")), // Subscription-Id-Data - }}) - ccr.NewAVP(439, avp.Mbit, 0, datatype.Unsigned32(0)) // Service-Identifier - ccr.NewAVP(437, avp.Mbit, 0, &diam.GroupedAVP{ // Requested-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300)), // CC-Time - }}) - ccr.NewAVP(873, avp.Mbit|avp.Vbit, 10415, &diam.GroupedAVP{ // Service-information - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(20336, avp.Mbit, 2011, datatype.UTF8String("1001")), // CallingPartyAdress - diam.NewAVP(20337, avp.Mbit, 2011, datatype.UTF8String("1002")), // CalledPartyAdress - diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // ChargeFlowType - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("33609004940")), // CallingVlrNumber - diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("208104941749984")), // CallingCellID - diam.NewAVP(20313, avp.Mbit, 2011, datatype.OctetString("0x8090a3")), // BearerCapability - diam.NewAVP(20321, avp.Mbit, 2011, datatype.OctetString("0x401c4132ed665")), // CallreferenceNumber - diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("33609004940")), // MSCAddress - diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("20160501010101")), // SSPTime - diam.NewAVP(20938, avp.Mbit, 2011, datatype.OctetString("0x00000001")), // HighLayerCharacteristics - diam.NewAVP(20324, avp.Mbit, 2011, datatype.Integer32(8)), // Time-Zone - }, - }), - }}) - if _, err := ccr.NewAVP("Framed-IP-Address", avp.Mbit, 0, datatype.UTF8String("10.228.16.4")); err != nil { - t.Error(err) - } - for i := 0; i < *interations; i++ { - if err := dmtClient.SendMessage(ccr); err != nil { - t.Error(err) - } - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No message returned") - } - /* - if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { - t.Error(err) - } else if len(avps) == 0 { - t.Error("Result-Code") - } else if strResult := avpValAsString(avps[0]); strResult != "300" { // Result-Code set in the template - t.Errorf("Expecting 300, received: %s", strResult) - } - */ - } -} - -/* -func TestDmtAgentLoadCER(t *testing.T) { - m := diam.NewRequest(diam.CapabilitiesExchange, 4, dict.Default) - m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) - m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) - m.NewAVP(avp.HostIPAddress, avp.Mbit, 0, datatype.Address(net.ParseIP("127.0.0.1"))) - m.NewAVP(avp.VendorID, avp.Mbit, 0, datatype.Unsigned32(999)) - m.NewAVP(avp.ProductName, 0, 0, datatype.UTF8String("CGR-DA")) - m.NewAVP(avp.OriginStateID, avp.Mbit, 0, datatype.Unsigned32(1)) - m.NewAVP(avp.AcctApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) - m.NewAVP(avp.FirmwareRevision, avp.Mbit, 0, datatype.Unsigned32(1)) - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } - time.Sleep(time.Duration(100) * time.Millisecond) - msg := dmtClient.ReceivedMessage(rplyTimeout) - if msg == nil { - t.Fatal("No message returned") - } -} - -func TestDmtAgentConnectDiameterClientCER(t *testing.T) { - dmtClient, err = NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, - daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, "") - if err != nil { - t.Fatal(err) - } - m := diam.NewRequest(diam.CapabilitiesExchange, 0, dict.Default) - m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("UNIT_TEST2")) - m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity(daCfg.DiameterAgentCfg().OriginRealm)) - m.NewAVP(avp.VendorID, avp.Mbit, 0, datatype.Unsigned32(daCfg.DiameterAgentCfg().VendorId)) - m.NewAVP(avp.ProductName, 0, 0, datatype.UTF8String(daCfg.DiameterAgentCfg().ProductName)) - m.NewAVP(avp.VendorSpecificApplicationID, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.VendorID, avp.Mbit, 0, datatype.Unsigned32(10415)), - diam.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)), - }, - }) - if err := dmtClient.SendMessage(m); err != nil { - t.Error(err) - } -} -*/ - -func TestDmtAgentStopEngine(t *testing.T) { - if err := engine.KillEngine(*waitRater); err != nil { - t.Error(err) - } -} diff --git a/agents/hapool_it_test.go b/agents/hapool_it_test.go deleted file mode 100644 index 69d7f0061..000000000 --- a/agents/hapool_it_test.go +++ /dev/null @@ -1,120 +0,0 @@ -// +build integration - -/* -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 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 -*/ - -package agents - -/* -import ( - "os/exec" - "path" - "testing" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/engine" -) - -var cgrRater1Cmd, cgrSmg1Cmd *exec.Cmd - -func TestHaPoolInitCfg(t *testing.T) { - daCfgPath = path.Join(*dataDir, "conf", "samples", "hapool", "cgrrater1") - // Init config first - var err error - daCfg, err = config.NewCGRConfigFromFolder(daCfgPath) - if err != nil { - t.Error(err) - } - daCfg.DataFolderPath = *dataDir // Share DataFolderPath through config towards StoreDb for Flush() - config.SetCgrConfig(daCfg) -} - -// Remove data in both rating and accounting db -func TestHaPoolResetDataDb(t *testing.T) { - TestDmtAgentResetDataDb(t) -} - -// Wipe out the cdr database -func TestHaPoolResetStorDb(t *testing.T) { - TestDmtAgentResetStorDb(t) -} - -// Start CGR Engine -func TestHaPoolStartEngine(t *testing.T) { - engine.KillEngine(*waitRater) // just to make sure - var err error - cgrRater1 := path.Join(*dataDir, "conf", "samples", "hapool", "cgrrater1") - if cgrRater1Cmd, err = engine.StartEngine(cgrRater1, *waitRater); err != nil { - t.Fatal("cgrRater1: ", err) - } - cgrRater2 := path.Join(*dataDir, "conf", "samples", "hapool", "cgrrater2") - if _, err = engine.StartEngine(cgrRater2, *waitRater); err != nil { - t.Fatal("cgrRater2: ", err) - } - cgrSmg1 := path.Join(*dataDir, "conf", "samples", "hapool", "cgrsmg1") - if cgrSmg1Cmd, err = engine.StartEngine(cgrSmg1, *waitRater); err != nil { - t.Fatal("cgrSmg1: ", err) - } - cgrSmg2 := path.Join(*dataDir, "conf", "samples", "hapool", "cgrsmg2") - if _, err = engine.StartEngine(cgrSmg2, *waitRater); err != nil { - t.Fatal("cgrSmg2: ", err) - } - cgrDa := path.Join(*dataDir, "conf", "samples", "hapool", "dagent") - if _, err = engine.StartEngine(cgrDa, *waitRater); err != nil { - t.Fatal("cgrDa: ", err) - } - -} - -// Connect rpc client to rater -func TestHaPoolApierRpcConn(t *testing.T) { - TestDmtAgentApierRpcConn(t) -} - -// Load the tariff plan, creating accounts and their balances -func TestHaPoolTPFromFolder(t *testing.T) { - TestDmtAgentTPFromFolder(t) -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:47:26Z"' -func TestHaPoolSendCCRInit(t *testing.T) { - TestDmtAgentSendCCRInit(t) -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:52:26Z"' -func TestHaPoolSendCCRUpdate(t *testing.T) { - TestDmtAgentSendCCRUpdate(t) -} - -// cgr-console 'cost Category="call" Tenant="cgrates.org" Subject="1001" Destination="1004" TimeStart="2015-11-07T08:42:26Z" TimeEnd="2015-11-07T08:57:26Z"' -func TestHaPoolSendCCRUpdate2(t *testing.T) { - TestDmtAgentSendCCRUpdate2(t) -} - -func TestHaPoolSendCCRTerminate(t *testing.T) { - TestDmtAgentSendCCRTerminate(t) -} - -func TestHaPoolCdrs(t *testing.T) { - TestDmtAgentCdrs(t) -} - -func TestHaPoolStopEngine(t *testing.T) { - TestDmtAgentStopEngine(t) -} -*/ diff --git a/agents/httpagent.go b/agents/httpagent.go index 8144329eb..ef273e5c5 100644 --- a/agents/httpagent.go +++ b/agents/httpagent.go @@ -58,9 +58,9 @@ func (ha *HTTPAgent) ServeHTTP(w http.ResponseWriter, req *http.Request) { utils.HTTPAgent, err.Error())) return } - for _, reqProcessor := range ha.reqProcessors { - agReq := newAgentRequest(dcdr, reqProcessor.Tenant, ha.dfltTenant, + agReq := newAgentRequest(dcdr, nil, nil, + reqProcessor.Tenant, ha.dfltTenant, utils.FirstNonEmpty(reqProcessor.Timezone, config.CgrConfig().DefaultTimezone), ha.filterS) lclProcessed, err := ha.processRequest(reqProcessor, agReq) @@ -177,7 +177,10 @@ func (ha *HTTPAgent) processRequest(reqProcessor *config.HttpAgntProcCfg, evArgs := sessions.NewV1ProcessEventArgs( reqProcessor.Flags.HasKey(utils.MetaResources), reqProcessor.Flags.HasKey(utils.MetaAccounts), - reqProcessor.Flags.HasKey(utils.MetaAttributes), *cgrEv) + reqProcessor.Flags.HasKey(utils.MetaAttributes), + reqProcessor.Flags.HasKey(utils.MetaThresholds), + reqProcessor.Flags.HasKey(utils.MetaStats), + *cgrEv) var eventRply sessions.V1ProcessEventReply err = ha.sessionS.Call(utils.SessionSv1ProcessEvent, evArgs, &eventRply) diff --git a/agents/httpagent_it_test.go b/agents/httpagent_it_test.go index bdf7a6e97..b2bc47702 100644 --- a/agents/httpagent_it_test.go +++ b/agents/httpagent_it_test.go @@ -42,6 +42,7 @@ var ( haCfg *config.CGRConfig haRPC *rpc.Client httpC *http.Client // so we can cache the connection + err error ) func TestHAitInitCfg(t *testing.T) { diff --git a/agents/libdiam.go b/agents/libdiam.go new file mode 100644 index 000000000..d2e9e5879 --- /dev/null +++ b/agents/libdiam.go @@ -0,0 +1,384 @@ +/* +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 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 +*/ + +package agents + +import ( + "errors" + "fmt" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/utils" + "github.com/fiorix/go-diameter/diam" + "github.com/fiorix/go-diameter/diam/avp" + "github.com/fiorix/go-diameter/diam/datatype" + "github.com/fiorix/go-diameter/diam/dict" +) + +func loadDictionaries(dictsDir, componentId string) error { + fi, err := os.Stat(dictsDir) + if err != nil { + if strings.HasSuffix(err.Error(), "no such file or directory") { + return fmt.Errorf("<%s> Invalid dictionaries folder: <%s>", componentId, dictsDir) + } + return err + } else if !fi.IsDir() { // If config dir defined, needs to exist + return fmt.Errorf(" Path: <%s> is not a directory", dictsDir) + } + return filepath.Walk(dictsDir, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + return nil + } + cfgFiles, err := filepath.Glob(filepath.Join(path, "*.xml")) // Only consider .xml files + if err != nil { + return err + } + if cfgFiles == nil { // No need of processing further since there are no dictionary files in the folder + return nil + } + for _, filePath := range cfgFiles { + utils.Logger.Info(fmt.Sprintf("<%s> Loading dictionary out of file %s", componentId, filePath)) + if err := dict.Default.LoadFile(filePath); err != nil { + return err + } + } + return nil + }) +} + +// diamAVPValue will extract the go primary value out of diameter type value +func diamAVPAsIface(dAVP *diam.AVP) (val interface{}, err error) { + if dAVP == nil { + return nil, errors.New("nil AVP") + } + switch dAVP.Data.Type() { + default: + return nil, fmt.Errorf("unsupported AVP data type: %d", dAVP.Data.Type()) + case datatype.AddressType: + return net.IP([]byte(dAVP.Data.(datatype.Address))), nil + case datatype.DiameterIdentityType: + return string(dAVP.Data.(datatype.DiameterIdentity)), nil + case datatype.DiameterURIType: + return string(dAVP.Data.(datatype.DiameterURI)), nil + case datatype.EnumeratedType: + return int32(dAVP.Data.(datatype.Enumerated)), nil + case datatype.Float32Type: + return float32(dAVP.Data.(datatype.Float32)), nil + case datatype.Float64Type: + return float64(dAVP.Data.(datatype.Float64)), nil + case datatype.IPFilterRuleType: + return string(dAVP.Data.(datatype.IPFilterRule)), nil + case datatype.IPv4Type: + return net.IP([]byte(dAVP.Data.(datatype.IPv4))), nil + case datatype.Integer32Type: + return int32(dAVP.Data.(datatype.Integer32)), nil + case datatype.Integer64Type: + return int64(dAVP.Data.(datatype.Integer64)), nil + case datatype.OctetStringType: + return string(dAVP.Data.(datatype.OctetString)), nil + case datatype.QoSFilterRuleType: + return string(dAVP.Data.(datatype.QoSFilterRule)), nil + case datatype.TimeType: + return time.Time(dAVP.Data.(datatype.Time)), nil + case datatype.UTF8StringType: + return string(dAVP.Data.(datatype.UTF8String)), nil + case datatype.Unsigned32Type: + return uint32(dAVP.Data.(datatype.Unsigned32)), nil + case datatype.Unsigned64Type: + return uint64(dAVP.Data.(datatype.Unsigned64)), nil + } +} + +func diamAVPAsString(dAVP *diam.AVP) (s string, err error) { + var iface interface{} + if iface, err = diamAVPAsIface(dAVP); err != nil { + return + } + return utils.IfaceAsString(iface) +} + +// newDiamDataType constructs dataType from valStr +func newDiamDataType(typ datatype.TypeID, valStr, + tmz string) (dt datatype.Type, err error) { + switch typ { + default: + return nil, fmt.Errorf("unsupported AVP data type: %d", typ) + case datatype.AddressType: + return datatype.Address(net.ParseIP(valStr)), nil + case datatype.DiameterIdentityType: + return datatype.DiameterIdentity(valStr), nil + case datatype.DiameterURIType: + return datatype.DiameterURI(valStr), nil + case datatype.EnumeratedType: + i, err := strconv.ParseInt(valStr, 10, 32) + if err != nil { + return nil, err + } + return datatype.Enumerated(int32(i)), nil + case datatype.Float32Type: + f, err := strconv.ParseFloat(valStr, 32) + if err == nil { + return nil, err + } + return datatype.Float32(float32(f)), nil + case datatype.Float64Type: + f, err := strconv.ParseFloat(valStr, 64) + if err == nil { + return nil, err + } + return datatype.Float64(f), nil + case datatype.IPFilterRuleType: + return datatype.IPFilterRule(valStr), nil + case datatype.IPv4Type: + return datatype.IPv4(net.ParseIP(valStr)), nil + case datatype.Integer32Type: + i, err := strconv.ParseInt(valStr, 10, 32) + if err != nil { + return nil, err + } + return datatype.Integer32(int32(i)), nil + case datatype.Integer64Type: + i, err := strconv.ParseInt(valStr, 10, 64) + if err != nil { + return nil, err + } + return datatype.Integer64(i), nil + case datatype.OctetStringType: + return datatype.OctetString(valStr), nil + case datatype.QoSFilterRuleType: + return datatype.QoSFilterRule(valStr), nil + case datatype.TimeType: + t, err := utils.ParseTimeDetectLayout(valStr, tmz) + if err != nil { + return nil, err + } + return datatype.Time(t), nil + case datatype.UTF8StringType: + return datatype.UTF8String(valStr), nil + case datatype.Unsigned32Type: + i, err := strconv.ParseUint(valStr, 10, 32) + if err != nil { + return nil, err + } + return datatype.Unsigned32(uint32(i)), nil + case datatype.Unsigned64Type: + i, err := strconv.ParseUint(valStr, 10, 64) + if err != nil { + return nil, err + } + return datatype.Unsigned64(i), nil + } +} + +// messageAddAVPsWithPath will dynamically add AVPs into the message +// append: append to the message, on false overwrite if AVP is single or add to group if AVP is Grouped +func messageSetAVPsWithPath(m *diam.Message, pathStr []string, + avpValStr string, newBranch bool, tmz string) (err error) { + if len(pathStr) == 0 { + return errors.New("empty path as AVP filter") + } + path := utils.SliceStringToIface(pathStr) + dictAVPs := make([]*dict.AVP, len(path)) // for each subpath, one dictionary AVP + for i, subpath := range path { + if dictAVP, err := m.Dictionary().FindAVP(m.Header.ApplicationID, subpath); err != nil { + return err + } else if dictAVP == nil { + return fmt.Errorf("cannot find AVP with id: %s", path[len(path)-1]) + } else { + dictAVPs[i] = dictAVP + } + } + lastAVPIdx := len(path) - 1 + if dictAVPs[lastAVPIdx].Data.Type == diam.GroupedAVPType { + return errors.New("last AVP in path cannot be GroupedAVP") + } + var msgAVP *diam.AVP // Keep a reference here towards last AVP + for i := lastAVPIdx; i >= 0; i-- { + var typeVal datatype.Type + if i == lastAVPIdx { + if typeVal, err = newDiamDataType(dictAVPs[i].Data.Type, avpValStr, tmz); err != nil { + return err + } + } else { + typeVal = &diam.GroupedAVP{ + AVP: []*diam.AVP{msgAVP}} + } + newMsgAVP := diam.NewAVP(dictAVPs[i].Code, avp.Mbit, dictAVPs[i].VendorID, typeVal) // FixMe: maybe Mbit with dictionary one + if i == lastAVPIdx-1 && !newBranch { + avps, err := m.FindAVPsWithPath(path[:lastAVPIdx], dict.UndefinedVendorID) + if err != nil { + return err + } + if len(avps) != 0 { // Group AVP already in the message + prevGrpData := avps[len(avps)-1].Data.(*diam.GroupedAVP) // Take the last avp found to append there + prevGrpData.AVP = append(prevGrpData.AVP, msgAVP) + m.Header.MessageLength += uint32(msgAVP.Len()) + return nil + } + } + msgAVP = newMsgAVP + } + if !newBranch { // Not group AVP, replace the previous set one with this one + avps, err := m.FindAVPsWithPath(path, dict.UndefinedVendorID) + if err != nil { + return err + } + if len(avps) != 0 { // Group AVP already in the message + m.Header.MessageLength -= uint32(avps[len(avps)-1].Len()) // decrease message length since we overwrite + *avps[len(avps)-1] = *msgAVP + m.Header.MessageLength += uint32(msgAVP.Len()) + return nil + } + } + m.AVP = append(m.AVP, msgAVP) + m.Header.MessageLength += uint32(msgAVP.Len()) + return nil +} + +// writeOnConn writes the message on connection, logs failures +func writeOnConn(c diam.Conn, m *diam.Message) { + if _, err := m.WriteTo(c); err != nil { + utils.Logger.Warning(fmt.Sprintf("<%s> failed writing message to %s, err: %s, msg: %s", + utils.DiameterAgent, c.RemoteAddr(), err.Error(), m)) + } +} + +// newDADataProvider constructs a DataProvider for a diameter message +func newDADataProvider(m *diam.Message) config.DataProvider { + return &diameterDP{m: m, cache: config.NewNavigableMap(nil)} + +} + +// diameterDP implements engine.DataProvider, serving as diam.Message data decoder +// decoded data is only searched once and cached +type diameterDP struct { + m *diam.Message + cache *config.NavigableMap +} + +// String is part of engine.DataProvider interface +// when called, it will display the already parsed values out of cache +func (dP *diameterDP) String() string { + return utils.ToJSON(dP.cache) +} + +// AsNavigableMap is part of engine.DataProvider interface +func (dP *diameterDP) AsNavigableMap([]*config.FCTemplate) ( + nm *config.NavigableMap, err error) { + return nil, utils.ErrNotImplemented +} + +// FieldAsString is part of engine.DataProvider interface +func (dP *diameterDP) FieldAsString(fldPath []string) (data string, err error) { + var valIface interface{} + valIface, err = dP.FieldAsInterface(fldPath) + if err != nil { + return + } + return utils.IfaceAsString(valIface) +} + +// FieldAsInterface is part of engine.DataProvider interface +func (dP *diameterDP) FieldAsInterface(fldPath []string) (data interface{}, err error) { + if data, err = dP.cache.FieldAsInterface(fldPath); err != nil { + if err != utils.ErrNotFound { // item found in cache + return nil, err + } + err = nil // cancel previous err + } else { + return // data was found in cache + } + // lastPath can contain selector inside + lastPath := fldPath[len(fldPath)-1] + var slctrStr string + if splt := strings.Split(lastPath, "["); len(splt) != 1 { + lastPath = splt[0] + if splt[1][len(splt[1])-1:] != "]" { + return nil, fmt.Errorf("filter rule <%s> needs to end in ]", splt[1]) + } + slctrStr = splt[1][:len(splt[1])-1] // also strip the last ] + } + pathIface := utils.SliceStringToIface(fldPath) + if slctrStr != "" { // last path was having selector inside before + pathIface[len(pathIface)-1] = lastPath // last path was changed + } + var avps []*diam.AVP + if avps, err = dP.m.FindAVPsWithPath( + pathIface, dict.UndefinedVendorID); err != nil { + return nil, err + } else if len(avps) == 0 { + return nil, utils.ErrNotFound + } + slectedIdx := 0 // by default we select AVP[0] + if slctrStr != "" { + if slectedIdx, err = strconv.Atoi(slctrStr); err != nil { // not int, compile it as RSRParser + selIndxs := make(map[int]int) // use it to find intersection of all matched filters + slctrStrs := strings.Split(slctrStr, utils.PipeSep) + for _, slctrStr := range slctrStrs { + slctr, err := config.NewRSRParser(slctrStr, true) + if err != nil { + return nil, err + } + pathIface[len(pathIface)-1] = slctr.AttrName() // search for AVPs which are having common path but different end element + fltrAVPs, err := dP.m.FindAVPsWithPath(pathIface, dict.UndefinedVendorID) + if err != nil { + return nil, err + } else if len(fltrAVPs) == 0 || len(fltrAVPs) != len(avps) { + return nil, utils.ErrFilterNotPassingNoCaps + } + for k, fAVP := range fltrAVPs { + if dataAVP, err := diamAVPAsIface(fAVP); err != nil { + return nil, err + } else if _, err := slctr.ParseValue(dataAVP); err != nil { + if err != utils.ErrFilterNotPassingNoCaps { + return nil, err + } + continue // filter not passing, not really error + } else { + selIndxs[k+1] += 1 // filter passing, index it with one higher to cover 0 + } + } + } + var oneMatches bool + for k, matches := range selIndxs { + if matches == len(slctrStrs) { // all filters in selection matching + oneMatches = true + slectedIdx = k - 1 // decrease it to reflect real index + break + } + } + if !oneMatches { + return nil, utils.ErrFilterNotPassingNoCaps + } + } + } + if slectedIdx >= len(avps) { + return nil, errors.New("avp index higher than number of AVPs") + } + if data, err = diamAVPAsIface(avps[slectedIdx]); err != nil { + return nil, err + } + dP.cache.Set(fldPath, data, false) + return +} diff --git a/agents/libdiam_test.go b/agents/libdiam_test.go new file mode 100644 index 000000000..151bea642 --- /dev/null +++ b/agents/libdiam_test.go @@ -0,0 +1,179 @@ +/* +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 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 +*/ + +package agents + +import ( + "reflect" + "testing" + + "github.com/fiorix/go-diameter/diam" + "github.com/fiorix/go-diameter/diam/avp" + "github.com/fiorix/go-diameter/diam/datatype" +) + +func TestDPFieldAsInterface(t *testing.T) { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.CCMoney, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.UnitValue, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(10000)), + diam.NewAVP(avp.Exponent, avp.Mbit, 0, datatype.Integer32(-5)), + }, + }), + diam.NewAVP(avp.CurrencyCode, avp.Mbit, 0, datatype.Unsigned32(33)), + }, + }), + }, + }) + m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data + diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(10000)), + }}) + m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(1)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208708000003")), // Subscription-Id-Data + diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(20000)), + }}) + m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(2)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208708000004")), // Subscription-Id-Data + diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(20000)), + }}) + + dP := newDADataProvider(m) + eOut := interface{}("simuhuawei;1449573472;00002") + if out, err := dP.FieldAsInterface([]string{"Session-Id"}); err != nil { + t.Error(err) + } else if eOut != out { + t.Errorf("Expecting: %v, received: %v", eOut, out) + } + eOut = interface{}(int64(10000)) + if out, err := dP.FieldAsInterface([]string{"Requested-Service-Unit", "CC-Money", "Unit-Value", "Value-Digits"}); err != nil { + t.Error(err) + } else if eOut != out { + t.Errorf("Expecting: %v, received: %v", eOut, out) + } + eOut = interface{}("208708000003") // with filter on second group item + if out, err := dP.FieldAsInterface([]string{"Subscription-Id", + "Subscription-Id-Data[1]"}); err != nil { // on index + t.Error(err) + } else if eOut != out { + t.Errorf("Expecting: %v, received: %v", eOut, out) + } + if out, err := dP.FieldAsInterface([]string{"Subscription-Id", + "Subscription-Id-Data[~Subscription-Id-Type(1)]"}); err != nil { // on filter + t.Error(err) + } else if out != eOut { // can be any result since both entries are matching single filter + t.Errorf("expecting: %v, received: %v", eOut, out) + } + eOut = interface{}("208708000004") + if out, err := dP.FieldAsInterface([]string{"Subscription-Id", + "Subscription-Id-Data[~Subscription-Id-Type(2)|~Value-Digits(20000)]"}); err != nil { // on multiple filter + t.Error(err) + } else if eOut != out { + t.Errorf("Expecting: %v, received: %v", eOut, out) + } + eOut = interface{}("33708000003") + if out, err := dP.FieldAsInterface([]string{"Subscription-Id", "Subscription-Id-Data"}); err != nil { + t.Error(err) + } else if eOut != out { + t.Errorf("Expecting: %v, received: %v", eOut, out) + } +} + +func TestMessageSetAVPsWithPath(t *testing.T) { + eMessage := diam.NewRequest(diam.CreditControl, 4, nil) + eMessage.NewAVP("Session-Id", avp.Mbit, 0, + datatype.UTF8String("simuhuawei;1449573472;00001")) + m := diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, + eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) + if err := messageSetAVPsWithPath(m, + []string{"Session-Id"}, "simuhuawei;1449573472;00001", + false, "UTC"); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage, m) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + // create same attribute twice + eMessage.NewAVP("Session-Id", avp.Mbit, 0, + datatype.UTF8String("simuhuawei;1449573472;00002")) + if err := messageSetAVPsWithPath(m, + []string{"Session-Id"}, "simuhuawei;1449573472;00002", + true, "UTC"); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage.AVP, m.AVP) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + // overwrite of previous attribute + eMessage = diam.NewRequest(diam.CreditControl, 4, nil) + eMessage.NewAVP("Session-Id", avp.Mbit, 0, + datatype.UTF8String("simuhuawei;1449573472;00001")) + eMessage.NewAVP("Session-Id", avp.Mbit, 0, + datatype.UTF8String("simuhuawei;1449573472;00003")) + if err := messageSetAVPsWithPath(m, + []string{"Session-Id"}, "simuhuawei;1449573472;00003", + false, "UTC"); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(eMessage.AVP, m.AVP) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + // adding a groupped AVP + eMessage.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(0)), + diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1001")), + }}) + if err := messageSetAVPsWithPath(m, + []string{"Subscription-Id", "Subscription-Id-Type"}, "0", + false, "UTC"); err != nil { + t.Error(err) + } + if err := messageSetAVPsWithPath(m, + []string{"Subscription-Id", "Subscription-Id-Data"}, "1001", + false, "UTC"); err != nil { + t.Error(err) + } else if len(eMessage.AVP) != len(m.AVP) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } + eMessage.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(avp.SubscriptionIDType, avp.Mbit, 0, datatype.Enumerated(1)), + diam.NewAVP(avp.SubscriptionIDData, avp.Mbit, 0, datatype.UTF8String("1002")), + }}) + if err := messageSetAVPsWithPath(m, + []string{"Subscription-Id", "Subscription-Id-Type"}, "1", + true, "UTC"); err != nil { + t.Error(err) + } + if err := messageSetAVPsWithPath(m, + []string{"Subscription-Id", "Subscription-Id-Data"}, "1002", + false, "UTC"); err != nil { + t.Error(err) + } else if len(eMessage.AVP) != len(m.AVP) { + t.Errorf("Expecting: %+v, received: %+v", eMessage, m) + } +} diff --git a/agents/libdmt.go b/agents/libdmt.go deleted file mode 100644 index 01e3447ea..000000000 --- a/agents/libdmt.go +++ /dev/null @@ -1,890 +0,0 @@ -/* -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 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 -*/ - -package agents - -/* -Build various type of packets here -*/ - -import ( - "errors" - "fmt" - "math" - "math/rand" - "net" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/engine" - "github.com/cgrates/cgrates/sessions" - "github.com/cgrates/cgrates/utils" - "github.com/fiorix/go-diameter/diam" - "github.com/fiorix/go-diameter/diam/avp" - "github.com/fiorix/go-diameter/diam/datatype" - "github.com/fiorix/go-diameter/diam/dict" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -const ( - META_CCR_USAGE = "*ccr_usage" - META_VALUE_EXPONENT = "*value_exponent" - META_SUM = "*sum" - DIAMETER_CCR = "DIAMETER_CCR" - DiameterRatingFailed = 5031 - CGRError = "CGRError" - CGRMaxUsage = "CGRMaxUsage" - CGRResultCode = "CGRResultCode" -) - -var ( - ErrFilterNotPassing = errors.New("Filter not passing") - ErrDiameterRatingFailed = errors.New("Diameter rating failed") -) - -// processorVars will hold various variables using during request processing -// here so we can define methods on it -type processorVars map[string]interface{} - -// hasSubsystems will return true on single subsystem being present in processorVars -func (pv processorVars) hasSubsystems() (has bool) { - for _, k := range []string{utils.MetaAccounts, utils.MetaResources, - utils.MetaSuppliers, utils.MetaAttributes} { - if _, has = pv[k]; has { - return - } - } - return -} - -func (pv processorVars) hasVar(k string) (has bool) { - _, has = pv[k] - return -} - -// valAsInterface returns the string value for fldName -func (pv processorVars) valAsInterface(fldPath string) (val interface{}, err error) { - fldName := fldPath - if strings.HasPrefix(fldPath, utils.MetaCGRReply) { - fldName = utils.MetaCGRReply - } - if !pv.hasVar(fldName) { - err = errors.New("not found") - return - } - return config.NewNavigableMap(pv).FieldAsInterface(strings.Split(fldPath, utils.HIERARCHY_SEP)) -} - -// valAsString returns the string value for fldName -// returns empty if fldName not found -func (pv processorVars) valAsString(fldPath string) (val string, err error) { - fldName := fldPath - if strings.HasPrefix(fldPath, utils.MetaCGRReply) { - fldName = utils.MetaCGRReply - } - if !pv.hasVar(fldName) { - return "", utils.ErrNotFoundNoCaps - } - return config.NewNavigableMap(pv).FieldAsString(strings.Split(fldPath, utils.HIERARCHY_SEP)) -} - -// asV1AuthorizeArgs returns the arguments needed by SessionSv1.AuthorizeEvent -func (pv processorVars) asV1AuthorizeArgs(cgrEv *utils.CGREvent) (args *sessions.V1AuthorizeArgs) { - args = &sessions.V1AuthorizeArgs{ // defaults - GetMaxUsage: true, - CGREvent: *cgrEv, - } - if !pv.hasSubsystems() { - return - } - if !pv.hasVar(utils.MetaAccounts) { - args.GetMaxUsage = false - } - if pv.hasVar(utils.MetaResources) { - args.AuthorizeResources = true - } - if pv.hasVar(utils.MetaSuppliers) { - args.GetSuppliers = true - } - if pv.hasVar(utils.MetaAttributes) { - args.GetAttributes = true - } - return -} - -// asV1InitSessionArgs returns the arguments used in SessionSv1.InitSession -func (pv processorVars) asV1InitSessionArgs(cgrEv *utils.CGREvent) (args *sessions.V1InitSessionArgs) { - args = &sessions.V1InitSessionArgs{ // defaults - InitSession: true, - CGREvent: *cgrEv, - } - if !pv.hasSubsystems() { - return - } - if !pv.hasVar(utils.MetaAccounts) { - args.InitSession = false - } - if pv.hasVar(utils.MetaResources) { - args.AllocateResources = true - } - if pv.hasVar(utils.MetaAttributes) { - args.GetAttributes = true - } - return -} - -// asV1UpdateSessionArgs returns the arguments used in SessionSv1.InitSession -func (pv processorVars) asV1UpdateSessionArgs(cgrEv *utils.CGREvent) (args *sessions.V1UpdateSessionArgs) { - args = &sessions.V1UpdateSessionArgs{ // defaults - UpdateSession: true, - CGREvent: *cgrEv, - } - if !pv.hasSubsystems() { - return - } - if !pv.hasVar(utils.MetaAccounts) { - args.UpdateSession = false - } - if pv.hasVar(utils.MetaAttributes) { - args.GetAttributes = true - } - return -} - -// asV1TerminateSessionArgs returns the arguments used in SMGv1.TerminateSession -func (pv processorVars) asV1TerminateSessionArgs(cgrEv *utils.CGREvent) (args *sessions.V1TerminateSessionArgs) { - args = &sessions.V1TerminateSessionArgs{ // defaults - TerminateSession: true, - CGREvent: *cgrEv, - } - if !pv.hasSubsystems() { - return - } - if !pv.hasVar(utils.MetaAccounts) { - args.TerminateSession = false - } - if pv.hasVar(utils.MetaResources) { - args.ReleaseResources = true - } - return -} - -func (pv processorVars) asV1ProcessEventArgs(cgrEv *utils.CGREvent) (args *sessions.V1ProcessEventArgs) { - args = &sessions.V1ProcessEventArgs{ // defaults - Debit: true, - CGREvent: *cgrEv, - } - if !pv.hasSubsystems() { - return - } - if !pv.hasVar(utils.MetaAccounts) { - args.Debit = false - } - if pv.hasVar(utils.MetaResources) { - args.AllocateResources = true - } - if pv.hasVar(utils.MetaAttributes) { - args.GetAttributes = true - } - return -} - -func loadDictionaries(dictsDir, componentId string) error { - fi, err := os.Stat(dictsDir) - if err != nil { - if strings.HasSuffix(err.Error(), "no such file or directory") { - return fmt.Errorf(" Invalid dictionaries folder: <%s>", dictsDir) - } - return err - } else if !fi.IsDir() { // If config dir defined, needs to exist - return fmt.Errorf(" Path: <%s> is not a directory", dictsDir) - } - return filepath.Walk(dictsDir, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() { - return nil - } - cfgFiles, err := filepath.Glob(filepath.Join(path, "*.xml")) // Only consider .xml files - if err != nil { - return err - } - if cfgFiles == nil { // No need of processing further since there are no dictionary files in the folder - return nil - } - for _, filePath := range cfgFiles { - utils.Logger.Info(fmt.Sprintf("<%s> Loading dictionary out of file %s", componentId, filePath)) - if err := dict.Default.LoadFile(filePath); err != nil { - return err - } - } - return nil - }) - -} - -// Returns reqType, requestNr and ccTime in seconds -func disectUsageForCCR(usage time.Duration, debitInterval time.Duration, callEnded bool) (reqType, reqNr, reqCCTime, usedCCTime int) { - usageSecs := usage.Seconds() - debitIntervalSecs := debitInterval.Seconds() - reqType = 1 - if usage > 0 { - reqType = 2 - } - if callEnded { - reqType = 3 - } - reqNr = int(usageSecs / debitIntervalSecs) - if callEnded { - reqNr += 1 - } - ccTimeFloat := debitInterval.Seconds() - if callEnded { - ccTimeFloat = math.Mod(usageSecs, debitIntervalSecs) - } - if reqType == 1 { // Initial does not have usedCCTime - reqCCTime = int(ccTimeFloat) - } else if reqType == 2 { - reqCCTime = int(ccTimeFloat) - usedCCTime = int(math.Mod(usageSecs, debitIntervalSecs)) - } else if reqType == 3 { - usedCCTime = int(ccTimeFloat) // Termination does not have requestCCTime - } - return -} - -func usageFromCCR(reqType int, reqNr, usedCCTime int64, debitIterval time.Duration) (usage time.Duration) { - //dISecs := debitIterval.Nano() - //var ccTime int - usage = debitIterval - if reqType == 3 { - reqNr -= 1 // decrease request number to reach the real number - usage = (time.Duration(usedCCTime) * time.Second) + time.Duration(debitIterval.Nanoseconds()*reqNr) - } - return -} - -// Utility function to convert from StoredCdr to CCR struct -func storedCdrToCCR(cdr *engine.CDR, originHost, originRealm string, vendorId int, productName string, - firmwareRev int, debitInterval time.Duration, callEnded bool) *CCR { - //sid := "session;" + strconv.Itoa(int(rand.Uint32())) - reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(cdr.Usage, debitInterval, callEnded) - ccr := &CCR{SessionId: cdr.CGRID, OriginHost: originHost, OriginRealm: originRealm, DestinationHost: originHost, DestinationRealm: originRealm, - AuthApplicationId: 4, ServiceContextId: cdr.ExtraFields["Service-Context-Id"], CCRequestType: reqType, CCRequestNumber: reqNr, EventTimestamp: cdr.AnswerTime, - ServiceIdentifier: 0} - ccr.SubscriptionId = make([]struct { - SubscriptionIdType int `avp:"Subscription-Id-Type"` - SubscriptionIdData string `avp:"Subscription-Id-Data"` - }, 1) - ccr.SubscriptionId[0].SubscriptionIdType = 0 - ccr.SubscriptionId[0].SubscriptionIdData = cdr.Account - ccr.RequestedServiceUnit.CCTime = reqCCTime - ccr.UsedServiceUnit.CCTime = usedCCTime - ccr.ServiceInformation.INInformation.CallingPartyAddress = cdr.Account - ccr.ServiceInformation.INInformation.CalledPartyAddress = cdr.Destination - ccr.ServiceInformation.INInformation.RealCalledNumber = cdr.Destination - ccr.ServiceInformation.INInformation.ChargeFlowType = 0 - ccr.ServiceInformation.INInformation.CallingVlrNumber = cdr.ExtraFields["Calling-Vlr-Number"] - ccr.ServiceInformation.INInformation.CallingCellIDOrSAI = cdr.ExtraFields["Calling-CellID-Or-SAI"] - ccr.ServiceInformation.INInformation.BearerCapability = cdr.ExtraFields["Bearer-Capability"] - ccr.ServiceInformation.INInformation.CallReferenceNumber = cdr.CGRID - ccr.ServiceInformation.INInformation.TimeZone = 0 - ccr.ServiceInformation.INInformation.SSPTime = cdr.ExtraFields["SSP-Time"] - 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, procVars processorVars, - tag, arg string, dur time.Duration) (string, error) { - switch tag { - case META_CCR_USAGE: - var ok bool - var reqType datatype.Enumerated - var reqNr, usedUnit datatype.Unsigned32 - if ccReqTypeAvp, err := m.FindAVP("CC-Request-Type", 0); err != nil { - return "", err - } else if ccReqTypeAvp == nil { - return "", errors.New("CC-Request-Type not found") - } else if reqType, ok = ccReqTypeAvp.Data.(datatype.Enumerated); !ok { - return "", fmt.Errorf("CC-Request-Type must be Enumerated and not %v", ccReqTypeAvp.Data.Type()) - } - if ccReqNrAvp, err := m.FindAVP("CC-Request-Number", 0); err != nil { - return "", err - } else if ccReqNrAvp == nil { - return "", errors.New("CC-Request-Number not found") - } else if reqNr, ok = ccReqNrAvp.Data.(datatype.Unsigned32); !ok { - return "", fmt.Errorf("CC-Request-Number must be Unsigned32 and not %v", ccReqNrAvp.Data.Type()) - } - switch reqType { - case datatype.Enumerated(1), datatype.Enumerated(2): - if reqUnitAVPs, err := m.FindAVPsWithPath([]interface{}{"Requested-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - return "", err - } else if len(reqUnitAVPs) == 0 { - return "", errors.New("Requested-Service-Unit>CC-Time not found") - } else if usedUnit, ok = reqUnitAVPs[0].Data.(datatype.Unsigned32); !ok { - return "", fmt.Errorf("Requested-Service-Unit>CC-Time must be Unsigned32 and not %v", reqUnitAVPs[0].Data.Type()) - } - case datatype.Enumerated(3), datatype.Enumerated(4): - if usedUnitAVPs, err := m.FindAVPsWithPath([]interface{}{"Used-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { - return "", err - } else if len(usedUnitAVPs) != 0 { - if usedUnit, ok = usedUnitAVPs[0].Data.(datatype.Unsigned32); !ok { - return "", fmt.Errorf("Used-Service-Unit>CC-Time must be Unsigned32 and not %v", usedUnitAVPs[0].Data.Type()) - } - } - } - return usageFromCCR(int(reqType), int64(reqNr), int64(usedUnit), dur).String(), nil - } - return "", nil -} - -// metaValueExponent will multiply the float value with the exponent provided. -// Expects 2 arguments in template separated by | -func metaValueExponent(m *diam.Message, procVars processorVars, - argsTpl utils.RSRFields, roundingDecimals int) (string, error) { - valStr := composedFieldvalue(m, argsTpl, 0, procVars) - handlerArgs := strings.Split(valStr, utils.HandlerArgSep) - if len(handlerArgs) != 2 { - return "", errors.New("Unexpected number of arguments") - } - val, err := strconv.ParseFloat(handlerArgs[0], 64) - if err != nil { - return "", err - } - exp, err := strconv.Atoi(handlerArgs[1]) - if err != nil { - return "", err - } - res := val * math.Pow10(exp) - return strconv.FormatFloat(utils.Round(res, roundingDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil -} - -func metaSum(m *diam.Message, procVars processorVars, - argsTpl utils.RSRFields, passAtIndex, roundingDecimals int) (string, error) { - valStr := composedFieldvalue(m, argsTpl, passAtIndex, procVars) - handlerArgs := strings.Split(valStr, utils.HandlerArgSep) - var summed float64 - for _, arg := range handlerArgs { - val, err := strconv.ParseFloat(arg, 64) - if err != nil { - return "", err - } - summed += val - } - return strconv.FormatFloat(utils.Round(summed, roundingDecimals, utils.ROUNDING_MIDDLE), 'f', -1, 64), nil -} - -// splitIntoInterface is used to split a string into []interface{} instead of []string -func splitIntoInterface(content, sep string) []interface{} { - spltStr := strings.Split(content, sep) - spltIf := make([]interface{}, len(spltStr)) - for i, val := range spltStr { - spltIf[i] = val - } - return spltIf -} - -// avpsWithPath is used to find AVPs by specifying RSRField as filter -func avpsWithPath(m *diam.Message, rsrFld *utils.RSRField) ([]*diam.AVP, error) { - return m.FindAVPsWithPath( - splitIntoInterface(rsrFld.Id, utils.HIERARCHY_SEP), dict.UndefinedVendorID) -} - -func passesFieldFilter(m *diam.Message, fieldFilter *utils.RSRField, procVars processorVars) (bool, int) { - if fieldFilter == nil { - return true, 0 - } - // check procVars before AVPs - if val, err := procVars.valAsString(fieldFilter.Id); err != utils.ErrNotFoundNoCaps { - if err != nil { - utils.Logger.Warning( - fmt.Sprintf(" parsing value: <%s> as string, error: <%s>", - fieldFilter.Id, err.Error())) - return false, 0 - } - if _, err := fieldFilter.Parse(val); err != nil { - return false, 0 - } - return true, 0 - } - avps, err := avpsWithPath(m, fieldFilter) - if err != nil { - return false, 0 - } - if len(avps) == 0 { // No AVP found in request, treat it same as empty - if _, err := fieldFilter.Parse(""); err != nil { - return false, 0 - } - return true, -1 - } - for avpIdx, avpVal := range avps { // First match wins due to index - if _, err = fieldFilter.Parse(avpValAsString(avpVal)); err == nil { - return true, avpIdx - } - } - return false, 0 -} - -func composedFieldvalue(m *diam.Message, outTpl utils.RSRFields, avpIdx int, procVars processorVars) (outVal string) { - var err error - for _, rsrTpl := range outTpl { - var valToParse string - if !rsrTpl.IsStatic() { // for Static we will parse empty valToParse bellow - // check procVars before AVPs - if valToParse, err = procVars.valAsString(rsrTpl.Id); err != nil { - if err != utils.ErrNotFoundNoCaps { - utils.Logger.Warning( - fmt.Sprintf("<%s> %s", utils.DiameterAgent, err.Error())) - continue - } - // not found in processorVars, look in AVPs - // AVPs from here - matchingAvps, err := avpsWithPath(m, rsrTpl) - if err != nil || len(matchingAvps) == 0 { - if err != nil { - utils.Logger.Err(fmt.Sprintf("<%s> Error matching AVPS: %s", - utils.DiameterAgent, err.Error())) - } - continue - } - if len(matchingAvps) <= avpIdx { - utils.Logger.Warning( - fmt.Sprintf("<%s> Cannot retrieve AVP with index %d for field template with id: %s", - utils.DiameterAgent, avpIdx, rsrTpl.Id)) - continue // Not convertible, ignore - } - if matchingAvps[0].Data.Type() == diam.GroupedAVPType { - utils.Logger.Warning( - fmt.Sprintf("<%s> Value for field template with id: %s is matching a group AVP, ignoring.", - utils.DiameterAgent, rsrTpl.Id)) - continue // Not convertible, ignore - } - valToParse = avpValAsString(matchingAvps[avpIdx]) - } - } - if parsed, err := rsrTpl.Parse(valToParse); err != nil { - utils.Logger.Warning( - fmt.Sprintf("<%s> %s", - utils.DiameterAgent, err.Error())) - } else { - outVal += parsed - } - } - return -} - -// Used to return the encoded value based on what AVP understands for it's type -func serializeAVPValueFromString(dictAVP *dict.AVP, valStr, timezone string) ([]byte, error) { - switch dictAVP.Data.Type { - case datatype.OctetStringType, datatype.DiameterIdentityType, datatype.DiameterURIType, datatype.IPFilterRuleType, datatype.QoSFilterRuleType, datatype.UTF8StringType: - return []byte(valStr), nil - case datatype.AddressType: - return []byte(net.ParseIP(valStr)), nil - case datatype.EnumeratedType, datatype.Integer32Type, datatype.Unsigned32Type: - i, err := strconv.Atoi(valStr) - if err != nil { - return nil, err - } - return datatype.Enumerated(i).Serialize(), nil - case datatype.Unsigned64Type, datatype.Integer64Type: - i, err := strconv.ParseInt(valStr, 10, 64) - if err != nil { - return nil, err - } - return datatype.Unsigned64(i).Serialize(), nil - case datatype.Float32Type: - f, err := strconv.ParseFloat(valStr, 32) - if err != nil { - return nil, err - } - return datatype.Float32(f).Serialize(), nil - case datatype.Float64Type: - f, err := strconv.ParseFloat(valStr, 64) - if err != nil { - return nil, err - } - return datatype.Float64(f).Serialize(), nil - case datatype.GroupedType: - return nil, errors.New("GroupedType not supported for serialization") - case datatype.IPv4Type: - return datatype.IPv4(net.ParseIP(valStr)).Serialize(), nil - case datatype.TimeType: - t, err := utils.ParseTimeDetectLayout(valStr, timezone) - if err != nil { - return nil, err - } - return datatype.Time(t).Serialize(), nil - default: - return nil, fmt.Errorf("Unsupported type for serialization: %v", dictAVP.Data.Type) - } -} - -func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, - extraParam interface{}, procVars processorVars) (fmtValOut string, err error) { - var outVal string - passAtIndex := -1 - passedAllFilters := true - for _, fldFilter := range cfgFld.FieldFilter { - var pass bool - if pass, passAtIndex = passesFieldFilter(m, fldFilter, procVars); !pass { - passedAllFilters = false - break - } - } - if !passedAllFilters { - return "", ErrFilterNotPassing // Not matching field filters, will have it empty - } - if passAtIndex == -1 { - passAtIndex = 0 // No filter - } - 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: - switch cfgFld.HandlerId { - case META_VALUE_EXPONENT: - outVal, err = metaValueExponent(m, procVars, cfgFld.Value, 10) // FixMe: add here configured number of decimals - case META_SUM: - outVal, err = metaSum(m, procVars, cfgFld.Value, passAtIndex, 10) - default: - outVal, err = metaHandler(m, procVars, cfgFld.HandlerId, cfgFld.Layout, extraParam.(time.Duration)) - 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, procVars) - case utils.MetaGrouped: // GroupedAVP - outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex, procVars) - } - if fmtValOut, err = utils.FmtFieldWidth(cfgFld.Tag, 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 -} - -// messageAddAVPsWithPath will dynamically add AVPs into the message -// append: append to the message, on false overwrite if AVP is single or add to group if AVP is Grouped -func messageSetAVPsWithPath(m *diam.Message, path []interface{}, - avpValStr string, appnd bool, timezone string) error { - if len(path) == 0 { - return errors.New("Empty path as AVP filter") - } - dictAVPs := make([]*dict.AVP, len(path)) // for each subpath, one dictionary AVP - for i, subpath := range path { - if dictAVP, err := m.Dictionary().FindAVP(m.Header.ApplicationID, subpath); err != nil { - return err - } else if dictAVP == nil { - return fmt.Errorf("Cannot find AVP with id: %s", path[len(path)-1]) - } else { - dictAVPs[i] = dictAVP - } - } - if dictAVPs[len(path)-1].Data.Type == diam.GroupedAVPType { - return errors.New("Last AVP in path cannot be GroupedAVP") - } - var msgAVP *diam.AVP // Keep a reference here towards last AVP - lastAVPIdx := len(path) - 1 - for i := lastAVPIdx; i >= 0; i-- { - var typeVal datatype.Type - if i == lastAVPIdx { - avpValByte, err := serializeAVPValueFromString(dictAVPs[i], avpValStr, timezone) - if err != nil { - return err - } - typeVal, err = datatype.Decode(dictAVPs[i].Data.Type, avpValByte) // Check here - if err != nil { - return err - } - } else { - typeVal = &diam.GroupedAVP{ - AVP: []*diam.AVP{msgAVP}} - } - newMsgAVP := diam.NewAVP(dictAVPs[i].Code, avp.Mbit, dictAVPs[i].VendorID, typeVal) // FixMe: maybe Mbit with dictionary one - if i == lastAVPIdx-1 && !appnd { // last AVP needs to be appended in group - avps, _ := m.FindAVPsWithPath(path[:lastAVPIdx], dict.UndefinedVendorID) - if len(avps) != 0 { // Group AVP already in the message - prevGrpData := avps[len(avps)-1].Data.(*diam.GroupedAVP) // Take the last avp found to append there - prevGrpData.AVP = append(prevGrpData.AVP, msgAVP) - m.Header.MessageLength += uint32(msgAVP.Len()) - return nil - } - } - msgAVP = newMsgAVP - } - if !appnd { // Not group AVP, replace the previous set one with this one - avps, _ := m.FindAVPsWithPath(path, dict.UndefinedVendorID) - if len(avps) != 0 { // Group AVP already in the message - m.Header.MessageLength -= uint32(avps[len(avps)-1].Len()) // decrease message length since we overwrite - *avps[len(avps)-1] = *msgAVP - m.Header.MessageLength += uint32(msgAVP.Len()) - return nil - } - } - m.AVP = append(m.AVP, msgAVP) - m.Header.MessageLength += uint32(msgAVP.Len()) - return 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 - if err := m.Unmarshal(&ccr); err != nil { - return nil, err - } - ccr.diamMessage = m - ccr.debitInterval = debitInterval - return &ccr, nil -} - -// CallControl Request -// FixMe: strip it down to mandatory bare structure format by RFC 4006 -type CCR struct { - SessionId string `avp:"Session-Id"` - OriginHost string `avp:"Origin-Host"` - OriginRealm string `avp:"Origin-Realm"` - DestinationHost string `avp:"Destination-Host"` - DestinationRealm string `avp:"Destination-Realm"` - AuthApplicationId int `avp:"Auth-Application-Id"` - ServiceContextId string `avp:"Service-Context-Id"` - CCRequestType int `avp:"CC-Request-Type"` - CCRequestNumber int `avp:"CC-Request-Number"` - EventTimestamp time.Time `avp:"Event-Timestamp"` - SubscriptionId []struct { - SubscriptionIdType int `avp:"Subscription-Id-Type"` - SubscriptionIdData string `avp:"Subscription-Id-Data"` - } `avp:"Subscription-Id"` - ServiceIdentifier int `avp:"Service-Identifier"` - RequestedServiceUnit struct { - CCTime int `avp:"CC-Time"` - } `avp:"Requested-Service-Unit"` - UsedServiceUnit struct { - CCTime int `avp:"CC-Time"` - } `avp:"Used-Service-Unit"` - ServiceInformation struct { - INInformation struct { - CallingPartyAddress string `avp:"Calling-Party-Address"` - CalledPartyAddress string `avp:"Called-Party-Address"` - RealCalledNumber string `avp:"Real-Called-Number"` - ChargeFlowType int `avp:"Charge-Flow-Type"` - CallingVlrNumber string `avp:"Calling-Vlr-Number"` - CallingCellIDOrSAI string `avp:"Calling-CellID-Or-SAI"` - BearerCapability string `avp:"Bearer-Capability"` - CallReferenceNumber string `avp:"Call-Reference-Number"` - MSCAddress string `avp:"MSC-Address"` - TimeZone int `avp:"Time-Zone"` - CalledPartyNP string `avp:"Called-Party-NP"` - SSPTime string `avp:"SSP-Time"` - } `avp:"IN-Information"` - } `avp:"Service-Information"` - diamMessage *diam.Message // Used to parse fields with CGR templates - debitInterval time.Duration // Configured debit interval -} - -// AsBareDiameterMessage converts CCR into a bare DiameterMessage -// Compatible with the required fields of CCA -func (self *CCR) AsBareDiameterMessage() *diam.Message { - m := diam.NewRequest(diam.CreditControl, 4, nil) - m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String(self.SessionId)) - m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)) - m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)) - m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)) - m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)) - m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(self.CCRequestNumber)) - return m -} - -// Used when sending from client to agent -func (self *CCR) AsDiameterMessage() (*diam.Message, error) { - m := self.AsBareDiameterMessage() - if _, err := m.NewAVP("Destination-Host", avp.Mbit, 0, datatype.DiameterIdentity(self.DestinationHost)); err != nil { - return nil, err - } - if _, err := m.NewAVP("Destination-Realm", avp.Mbit, 0, datatype.DiameterIdentity(self.DestinationRealm)); err != nil { - return nil, err - } - if _, err := m.NewAVP("Service-Context-Id", avp.Mbit, 0, datatype.UTF8String(self.ServiceContextId)); err != nil { - return nil, err - } - if _, err := m.NewAVP("Event-Timestamp", avp.Mbit, 0, datatype.Time(self.EventTimestamp)); err != nil { - return nil, err - } - for _, subscriptionId := range self.SubscriptionId { - if _, err := m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(subscriptionId.SubscriptionIdType)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String(subscriptionId.SubscriptionIdData)), // Subscription-Id-Data - }}); err != nil { - return nil, err - } - } - if _, err := m.NewAVP("Service-Identifier", avp.Mbit, 0, datatype.Unsigned32(self.ServiceIdentifier)); err != nil { - return nil, err - } - if _, err := m.NewAVP("Requested-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(self.RequestedServiceUnit.CCTime))}}); err != nil { // CC-Time - return nil, err - } - if _, err := m.NewAVP("Used-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(self.UsedServiceUnit.CCTime))}}); err != nil { // CC-Time - return nil, err - } - if _, err := m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information - AVP: []*diam.AVP{ - diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String(self.ServiceInformation.INInformation.CallingPartyAddress)), // Calling-Party-Address - diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String(self.ServiceInformation.INInformation.CalledPartyAddress)), // Called-Party-Address - diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.RealCalledNumber)), // Real-Called-Number - diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(self.ServiceInformation.INInformation.ChargeFlowType)), // Charge-Flow-Type - diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.CallingVlrNumber)), // Calling-Vlr-Number - diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.CallingCellIDOrSAI)), // Calling-CellID-Or-SAI - diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.BearerCapability)), // Bearer-Capability - diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.CallReferenceNumber)), // Call-Reference-Number - diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.MSCAddress)), // MSC-Address - diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(self.ServiceInformation.INInformation.TimeZone)), // Time-Zone - diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.CalledPartyNP)), // Called-Party-NP - diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String(self.ServiceInformation.INInformation.SSPTime)), // SSP-Time - }, - }), - }}); err != nil { - return nil, err - } - return m, nil -} - -// Extracts data out of CCR into a SMGenericEvent based on the configured template -func (self *CCR) AsMapIface(cfgFlds []*config.CfgCdrField) (map[string]interface{}, 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 := fieldOutVal(self.diamMessage, cfgFld, self.debitInterval, nil) - if err != nil { - if err == ErrFilterNotPassing { - continue // Do nothing in case of Filter not passing - } - return nil, err - } - if _, hasKey := outMap[cfgFld.FieldId]; hasKey && cfgFld.Append { - outMap[cfgFld.FieldId] += fmtOut - } else { - outMap[cfgFld.FieldId] = fmtOut - - } - if cfgFld.BreakOnSuccess { - break - } - } - return utils.ConvertMapValStrIf(outMap), nil -} - -func NewBareCCAFromCCR(ccr *CCR, originHost, originRealm string) *CCA { - cca := &CCA{SessionId: ccr.SessionId, AuthApplicationId: ccr.AuthApplicationId, CCRequestType: ccr.CCRequestType, CCRequestNumber: ccr.CCRequestNumber, - OriginHost: originHost, OriginRealm: originRealm, - diamMessage: diam.NewMessage(ccr.diamMessage.Header.CommandCode, ccr.diamMessage.Header.CommandFlags&^diam.RequestFlag, ccr.diamMessage.Header.ApplicationID, - ccr.diamMessage.Header.HopByHopID, ccr.diamMessage.Header.EndToEndID, ccr.diamMessage.Dictionary()), ccrMessage: ccr.diamMessage, debitInterval: ccr.debitInterval, - } - cca.diamMessage = cca.AsBareDiameterMessage() // Add the required fields to the diameterMessage - return cca -} - -// Call Control Answer, bare structure so we can dynamically manage adding it's fields -type CCA struct { - SessionId string `avp:"Session-Id"` - OriginHost string `avp:"Origin-Host"` - OriginRealm string `avp:"Origin-Realm"` - AuthApplicationId int `avp:"Auth-Application-Id"` - CCRequestType int `avp:"CC-Request-Type"` - CCRequestNumber int `avp:"CC-Request-Number"` - ResultCode int `avp:"Result-Code"` - GrantedServiceUnit struct { - CCTime int `avp:"CC-Time"` - } `avp:"Granted-Service-Unit"` - ccrMessage *diam.Message - diamMessage *diam.Message - debitInterval time.Duration - timezone string -} - -// AsBareDiameterMessage converts CCA into a bare DiameterMessage -func (self *CCA) AsBareDiameterMessage() *diam.Message { - var m diam.Message - utils.Clone(self.diamMessage, &m) - m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String(self.SessionId)) - m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)) - m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)) - m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)) - m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)) - m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)) - m.NewAVP(avp.ResultCode, avp.Mbit, 0, datatype.Unsigned32(self.ResultCode)) - return &m -} - -// AsDiameterMessage returns the diameter.Message which can be later written on network -func (self *CCA) AsDiameterMessage() *diam.Message { - return self.diamMessage -} - -// SetProcessorAVPs will add AVPs to self.diameterMessage based on template defined in processor.CCAFields -func (self *CCA) SetProcessorAVPs(reqProcessor *config.DARequestProcessor, processorVars processorVars) error { - for _, cfgFld := range reqProcessor.CCAFields { - fmtOut, err := fieldOutVal(self.ccrMessage, cfgFld, nil, processorVars) - if err == ErrFilterNotPassing { // Field not in or filter not passing, try match in answer - fmtOut, err = fieldOutVal(self.diamMessage, cfgFld, nil, processorVars) - } - if err != nil { - if err == ErrFilterNotPassing { - continue - } - return err - } - if err := messageSetAVPsWithPath(self.diamMessage, - splitIntoInterface(cfgFld.FieldId, utils.HIERARCHY_SEP), - fmtOut, cfgFld.Append, self.timezone); err != nil { - return err - } - if cfgFld.BreakOnSuccess { // don't look for another field - break - } - } - return nil -} diff --git a/agents/libdmt_test.go b/agents/libdmt_test.go deleted file mode 100644 index a9770eea3..000000000 --- a/agents/libdmt_test.go +++ /dev/null @@ -1,564 +0,0 @@ -/* -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 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 -*/ - -package agents - -import ( - "bytes" - "encoding/binary" - "fmt" - "reflect" - "testing" - "time" - - "github.com/cgrates/cgrates/config" - "github.com/cgrates/cgrates/utils" - "github.com/fiorix/go-diameter/diam" - "github.com/fiorix/go-diameter/diam/avp" - "github.com/fiorix/go-diameter/diam/datatype" - "github.com/fiorix/go-diameter/diam/dict" -) - -var err error - -func TestDisectUsageForCCR(t *testing.T) { - if reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(time.Duration(0)*time.Second, - time.Duration(300)*time.Second, false); reqType != 1 || reqNr != 0 || reqCCTime != 300 || usedCCTime != 0 { - t.Error(reqType, reqNr, reqCCTime, usedCCTime) - } - if reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(time.Duration(35)*time.Second, time.Duration(300)*time.Second, false); reqType != 2 || reqNr != 0 || reqCCTime != 300 || usedCCTime != 35 { - t.Error(reqType, reqNr, reqCCTime, usedCCTime) - } - if reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(time.Duration(935)*time.Second, time.Duration(300)*time.Second, false); reqType != 2 || reqNr != 3 || reqCCTime != 300 || usedCCTime != 35 { - t.Error(reqType, reqNr, reqCCTime, usedCCTime) - } - if reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(time.Duration(35)*time.Second, time.Duration(300)*time.Second, true); reqType != 3 || reqNr != 1 || reqCCTime != 0 || usedCCTime != 35 { - t.Error(reqType, reqNr, reqCCTime, usedCCTime) - } - if reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(time.Duration(610)*time.Second, time.Duration(300)*time.Second, true); reqType != 3 || reqNr != 3 || reqCCTime != 0 || usedCCTime != 10 { - t.Error(reqType, reqNr, reqCCTime, usedCCTime) - } - if reqType, reqNr, reqCCTime, usedCCTime := disectUsageForCCR(time.Duration(935)*time.Second, time.Duration(300)*time.Second, true); reqType != 3 || reqNr != 4 || reqCCTime != 0 || usedCCTime != 35 { - t.Error(reqType, reqNr, reqCCTime, usedCCTime) - } -} - -func TestUsageFromCCR(t *testing.T) { - if usage := usageFromCCR(1, 0, 0, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second { - t.Error(usage) - } - if usage := usageFromCCR(2, 0, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second { - t.Error(usage) - } - if usage := usageFromCCR(2, 3, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second { - t.Error(usage.Seconds()) - } - if usage := usageFromCCR(3, 3, 10, time.Duration(300)*time.Second); usage != time.Duration(610)*time.Second { - t.Error(usage) - } - if usage := usageFromCCR(3, 4, 35, time.Duration(300)*time.Second); usage != time.Duration(935)*time.Second { - t.Error(usage) - } - if usage := usageFromCCR(3, 1, 35, time.Duration(300)*time.Second); usage != time.Duration(35)*time.Second { - t.Error(usage) - } - if usage := usageFromCCR(1, 0, 0, time.Duration(360)*time.Second); usage != time.Duration(360)*time.Second { - t.Error(usage) - } -} - -func TestAvpValAsString(t *testing.T) { - originHostStr := "unit_test" - a := diam.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity(originHostStr)) - if avpValStr := avpValAsString(a); avpValStr != originHostStr { - t.Errorf("Expected: %s, received: %s", originHostStr, avpValStr) - } -} - -func TestMetaValueExponent(t *testing.T) { - m := diam.NewRequest(diam.CreditControl, 4, nil) - m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) - m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCMoney, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.UnitValue, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(10000)), - diam.NewAVP(avp.Exponent, avp.Mbit, 0, datatype.Integer32(-5)), - }, - }), - diam.NewAVP(avp.CurrencyCode, avp.Mbit, 0, datatype.Unsigned32(33)), - }, - }), - }, - }) - if val, err := metaValueExponent(m, nil, utils.ParseRSRFieldsMustCompile( - "Requested-Service-Unit>CC-Money>Unit-Value>Value-Digits;^|;Requested-Service-Unit>CC-Money>Unit-Value>Exponent", - utils.INFIELD_SEP), 10); err != nil { - t.Error(err) - } else if val != "0.1" { - t.Error("Received: ", val) - } - if _, err = metaValueExponent(m, nil, - utils.ParseRSRFieldsMustCompile( - "Requested-Service-Unit>CC-Money>Unit-Value>Value-Digits;Requested-Service-Unit>CC-Money>Unit-Value>Exponent", - utils.INFIELD_SEP), 10); err == nil { - t.Error("Should have received error") // Insufficient number arguments - } -} - -func TestMetaSum(t *testing.T) { - m := diam.NewRequest(diam.CreditControl, 4, nil) - m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) - m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.CCMoney, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.UnitValue, avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(avp.ValueDigits, avp.Mbit, 0, datatype.Integer64(10000)), - diam.NewAVP(avp.Exponent, avp.Mbit, 0, datatype.Integer32(-5)), - }, - }), - diam.NewAVP(avp.CurrencyCode, avp.Mbit, 0, datatype.Unsigned32(33)), - }, - }), - }, - }) - if val, err := metaSum(m, nil, - utils.ParseRSRFieldsMustCompile( - "Requested-Service-Unit>CC-Money>Unit-Value>Value-Digits;^|;Requested-Service-Unit>CC-Money>Unit-Value>Exponent", - utils.INFIELD_SEP), 0, 10); err != nil { - t.Error(err) - } else if val != "9995" { - t.Error("Received: ", val) - } - if _, err = metaSum(m, nil, utils.ParseRSRFieldsMustCompile( - "Requested-Service-Unit>CC-Money>Unit-Value>Value-Digits;Requested-Service-Unit>CC-Money>Unit-Value>Exponent", - utils.INFIELD_SEP), 0, 10); err == nil { - t.Error("Should have received error") // Insufficient number arguments - } -} - -func TestFieldOutVal(t *testing.T) { - m := diam.NewRequest(diam.CreditControl, 4, nil) - m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String("simuhuawei;1449573472;00002")) - m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data - }}) - m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(1)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("208708000003")), // Subscription-Id-Data - }}) - m.NewAVP("Service-Identifier", avp.Mbit, 0, datatype.Unsigned32(0)) - m.NewAVP("Requested-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(360))}}) // CC-Time - 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 := fieldOutVal(m, cfgFld, time.Duration(0), nil); err != nil { - t.Error(err) - } else if fldOut != eOut { - t.Errorf("Expecting:\n%s\nReceived:\n%s", eOut, fldOut) - } - 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 := fieldOutVal(m, cfgFld, time.Duration(0), nil); err != nil { - t.Error(err) - } else if fldOut != eOut { - t.Errorf("Expecting:\n%s\nReceived:\n%s", eOut, fldOut) - } - // With filter on ProcessorVars - cfgFld = &config.CfgCdrField{Tag: "ComposedTestWithProcessorVarsFilter", Type: utils.META_COMPOSED, FieldId: utils.Destination, - FieldFilter: utils.ParseRSRFieldsMustCompile("CGRError(INSUFFICIENT_CREDIT)", utils.INFIELD_SEP), - Value: utils.ParseRSRFieldsMustCompile("Requested-Service-Unit>CC-Time", utils.INFIELD_SEP), Mandatory: true} - if _, err := fieldOutVal(m, cfgFld, time.Duration(0), nil); err == nil { - t.Error("Should have error") - } - eOut = "360" - if fldOut, err := fieldOutVal(m, cfgFld, time.Duration(0), - processorVars{"CGRError": "INSUFFICIENT_CREDIT"}); err != nil { - t.Error(err) - } else if fldOut != eOut { - t.Errorf("Expecting:\n%s\nReceived:\n%s", eOut, fldOut) - } - // Without filter, we shoud get always the first subscriptionId - 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 := fieldOutVal(m, cfgFld, time.Duration(0), nil); err != nil { - t.Error(err) - } else if fldOut != eOut { - t.Errorf("Expecting:\n%s\nReceived:\n%s", eOut, fldOut) - } - // Without groupedAVP, we shoud get the first subscriptionId - cfgFld = &config.CfgCdrField{ - Tag: "Grouped2", - Type: utils.MetaGrouped, FieldId: "Account", - 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 := fieldOutVal(m, cfgFld, time.Duration(0), nil); err != nil { - t.Error(err) - } else if fldOut != eOut { - t.Errorf("Expecting:\n%s\nReceived:\n%s", eOut, fldOut) - } - cfgFld = &config.CfgCdrField{ - Tag: "TestMultipleFiltersEmptyReply", - Type: utils.META_COMPOSED, FieldId: "Account", - FieldFilter: utils.ParseRSRFieldsMustCompile( - "*cgrReply>Error(^$);*cgrReply>MaxUsage(!300);*cgrReply>MaxUsage(!0)", - utils.INFIELD_SEP), - Value: utils.ParseRSRFieldsMustCompile( - "Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), - Mandatory: true} - procVars := processorVars{ - utils.MetaCGRReply: map[string]interface{}{ - utils.Error: "RALS_ERROR:NOT_FOUND", - }, - } - if _, err := fieldOutVal(m, cfgFld, time.Duration(0), - procVars); err != ErrFilterNotPassing { - t.Error(err) - } -} - -func TestSerializeAVPValueFromString(t *testing.T) { - dictAVP, _ := dict.Default.FindAVP(4, "Session-Id") - eValByte := []byte("simuhuawei;1449573472;00002") - if valByte, err := serializeAVPValueFromString(dictAVP, "simuhuawei;1449573472;00002", "UTC"); err != nil { - t.Error(err) - } else if !bytes.Equal(eValByte, valByte) { - t.Errorf("Expecting: %+v, received: %+v", eValByte, valByte) - } - dictAVP, _ = dict.Default.FindAVP(4, "Result-Code") - eValByte = make([]byte, 4) - binary.BigEndian.PutUint32(eValByte, uint32(5031)) - if valByte, err := serializeAVPValueFromString(dictAVP, "5031", "UTC"); err != nil { - t.Error(err) - } else if !bytes.Equal(eValByte, valByte) { - t.Errorf("Expecting: %+v, received: %+v", eValByte, valByte) - } -} - -func TestMessageSetAVPsWithPath(t *testing.T) { - eMessage := diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Session-Id", avp.Mbit, 0, - datatype.UTF8String("simuhuawei;1449573472;00002")) - m := diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, - eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageSetAVPsWithPath(m, - []interface{}{"Session-Id", "Unknown"}, "simuhuawei;1449573472;00002", - false, "UTC"); err == nil || - err.Error() != "Could not find AVP Unknown" { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, - []interface{}{"Session-Id"}, "simuhuawei;1449573472;00002", - false, "UTC"); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eMessage, m) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - // test append - eMessage.NewAVP("Session-Id", avp.Mbit, 0, - datatype.UTF8String("simuhuawei;1449573472;00003")) - if err := messageSetAVPsWithPath(m, []interface{}{"Session-Id"}, - "simuhuawei;1449573472;00003", true, "UTC"); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eMessage, m) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - // test overwrite - eMessage = diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Session-Id", avp.Mbit, 0, - datatype.UTF8String("simuhuawei;1449573472;00002")) - m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, - eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageSetAVPsWithPath(m, - []interface{}{"Session-Id"}, "simuhuawei;1449573472;00001", - false, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, - []interface{}{"Session-Id"}, "simuhuawei;1449573472;00002", - false, "UTC"); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eMessage, m) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - eMessage = diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data - }}) - m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, - eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageSetAVPsWithPath(m, - []interface{}{"Subscription-Id", "Subscription-Id-Data"}, - "33708000003", false, "UTC"); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eMessage, m) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - // test append - eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Data - }}) - if err := messageSetAVPsWithPath(m, - []interface{}{"Subscription-Id", "Subscription-Id-Type"}, - "0", true, "UTC"); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eMessage, m) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - // test group append - eMessage = diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Data - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data - }}) - eMsgSrl, _ := eMessage.Serialize() - m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageSetAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Type"}, "0", false, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, []interface{}{"Subscription-Id", "Subscription-Id-Data"}, "33708000003", false, "UTC"); err != nil { - t.Error(err) - } else { - mSrl, _ := m.Serialize() - if !bytes.Equal(eMsgSrl, mSrl) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - } - eMessage = diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Granted-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300)), // Subscription-Id-Data - }}) - m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageSetAVPsWithPath(m, []interface{}{"Granted-Service-Unit", "CC-Time"}, "300", false, "UTC"); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eMessage, m) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } - // Multiple append - eMessage = diam.NewRequest(diam.CreditControl, 4, nil) - eMessage.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(431, avp.Mbit, 0, &diam.GroupedAVP{ // Granted-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(3600)), - diam.NewAVP(421, avp.Mbit, 0, datatype.Unsigned64(153600)), // "CC-Total-Octets" - }, - }), - diam.NewAVP(432, avp.Mbit, 0, datatype.Unsigned32(10)), - }, - }) - eMessage.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(431, avp.Mbit, 0, &diam.GroupedAVP{ // Granted-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(2600)), - diam.NewAVP(421, avp.Mbit, 0, datatype.Unsigned64(143600)), // "CC-Total-Octets" - }, - }), - diam.NewAVP(432, avp.Mbit, 0, datatype.Unsigned32(11)), // Rating-Group - }, - }) - m = diam.NewMessage(diam.CreditControl, diam.RequestFlag, 4, eMessage.Header.HopByHopID, eMessage.Header.EndToEndID, nil) - if err := messageSetAVPsWithPath(m, []interface{}{"Multiple-Services-Credit-Control", "Granted-Service-Unit", "CC-Time"}, "3600", false, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, []interface{}{"Multiple-Services-Credit-Control", "Granted-Service-Unit", "CC-Total-Octets"}, "153600", false, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, []interface{}{"Multiple-Services-Credit-Control", "Rating-Group"}, "10", false, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, []interface{}{"Multiple-Services-Credit-Control", "Granted-Service-Unit", "CC-Time"}, "2600", true, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, []interface{}{"Multiple-Services-Credit-Control", "Granted-Service-Unit", "CC-Total-Octets"}, "143600", false, "UTC"); err != nil { - t.Error(err) - } - if err := messageSetAVPsWithPath(m, []interface{}{"Multiple-Services-Credit-Control", "Rating-Group"}, "11", false, "UTC"); err != nil { - t.Error(err) - } - if fmt.Sprintf("%q", eMessage) != fmt.Sprintf("%q", m) { // test with fmt since reflect.DeepEqual does not perform properly here - t.Errorf("Expecting: %+v, received: %+v", eMessage, m) - } -} - -func TestCCASetProcessorAVPs(t *testing.T) { - ccr := &CCR{ // Bare information, just the one needed for answer - SessionId: "routinga;1442095190;1476802709", - AuthApplicationId: 4, - CCRequestType: 1, - CCRequestNumber: 0, - } - ccr.diamMessage = ccr.AsBareDiameterMessage() - ccr.diamMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data - }}) - ccr.debitInterval = time.Duration(300) * time.Second - cca := NewBareCCAFromCCR(ccr, "CGR-DA", "cgrates.org") - reqProcessor := &config.DARequestProcessor{Id: "UNIT_TEST", // Set template for tests - CCAFields: []*config.CfgCdrField{ - &config.CfgCdrField{Tag: "Subscription-Id/Subscription-Id-Type", Type: utils.META_COMPOSED, - FieldId: "Subscription-Id>Subscription-Id-Type", - Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Type", utils.INFIELD_SEP), Mandatory: true}, - &config.CfgCdrField{Tag: "Subscription-Id/Subscription-Id-Data", Type: utils.META_COMPOSED, - FieldId: "Subscription-Id>Subscription-Id-Data", - Value: utils.ParseRSRFieldsMustCompile("Subscription-Id>Subscription-Id-Data", utils.INFIELD_SEP), Mandatory: true}, - }, - } - eMessage := cca.AsDiameterMessage() - eMessage.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type - diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("33708000003")), // Subscription-Id-Data - }}) - if err := cca.SetProcessorAVPs(reqProcessor, processorVars{}); err != nil { - t.Error(err) - } else if ccaMsg := cca.AsDiameterMessage(); !reflect.DeepEqual(eMessage, ccaMsg) { - t.Errorf("Expecting: %+v, received: %+v", eMessage, ccaMsg) - } -} - -func TestCCRAsSMGenericEvent(t *testing.T) { - ccr := &CCR{ // Bare information, just the one needed for answer - SessionId: "ccrasgen1", - AuthApplicationId: 4, - CCRequestType: 3, - } - ccr.diamMessage = ccr.AsBareDiameterMessage() - ccr.diamMessage.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(446, avp.Mbit, 0, &diam.GroupedAVP{ // Used-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(17)), // CC-Time - diam.NewAVP(412, avp.Mbit, 0, datatype.Unsigned64(1341)), // CC-Input-Octets - diam.NewAVP(414, avp.Mbit, 0, datatype.Unsigned64(3079)), // CC-Output-Octets - }, - }), - diam.NewAVP(432, avp.Mbit, 0, datatype.Unsigned32(99)), - }, - }) - ccr.diamMessage.NewAVP("Multiple-Services-Credit-Control", avp.Mbit, 0, &diam.GroupedAVP{ - AVP: []*diam.AVP{ - diam.NewAVP(446, avp.Mbit, 0, &diam.GroupedAVP{ // Used-Service-Unit - AVP: []*diam.AVP{ - diam.NewAVP(452, avp.Mbit, 0, datatype.Enumerated(0)), // Tariff-Change-Usage - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(20)), // CC-Time - diam.NewAVP(412, avp.Mbit, 0, datatype.Unsigned64(8046)), // CC-Input-Octets - diam.NewAVP(414, avp.Mbit, 0, datatype.Unsigned64(46193)), // CC-Output-Octets - }, - }), - diam.NewAVP(432, avp.Mbit, 0, datatype.Unsigned32(1)), - }, - }) - ccr.diamMessage.NewAVP("FramedIPAddress", avp.Mbit, 0, datatype.OctetString("0AE40041")) - cfgFlds := make([]*config.CfgCdrField, 0) - eSMGEv := map[string]interface{}{"EventName": "DIAMETER_CCR"} - if rSMGEv, err := ccr.AsMapIface(cfgFlds); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eSMGEv, rSMGEv) { - t.Errorf("Expecting: %+v, received: %+v", eSMGEv, rSMGEv) - } - cfgFlds = []*config.CfgCdrField{ - &config.CfgCdrField{ - Tag: "LastUsed", - FieldFilter: utils.ParseRSRFieldsMustCompile("~Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets:s/^(.*)$/test/(test);Multiple-Services-Credit-Control>Rating-Group(1)", utils.INFIELD_SEP), - FieldId: "LastUsed", - Type: "*handler", - HandlerId: "*sum", - Value: utils.ParseRSRFieldsMustCompile("Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets", utils.INFIELD_SEP), - Mandatory: true, - }, - } - eSMGEv = map[string]interface{}{"EventName": "DIAMETER_CCR", "LastUsed": "54239"} - if rSMGEv, err := ccr.AsMapIface(cfgFlds); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eSMGEv, rSMGEv) { - t.Errorf("Expecting: %+v, received: %+v", eSMGEv, rSMGEv) - } - cfgFlds = []*config.CfgCdrField{ - &config.CfgCdrField{ - Tag: "LastUsed", - FieldFilter: utils.ParseRSRFieldsMustCompile("~Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets:s/^(.*)$/test/(test);Multiple-Services-Credit-Control>Rating-Group(99)", utils.INFIELD_SEP), - FieldId: "LastUsed", - Type: "*handler", - HandlerId: "*sum", - Value: utils.ParseRSRFieldsMustCompile("Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets", utils.INFIELD_SEP), - Mandatory: true, - }, - } - eSMGEv = map[string]interface{}{"EventName": "DIAMETER_CCR", "LastUsed": "4420"} - if rSMGEv, err := ccr.AsMapIface(cfgFlds); err != nil { - t.Error(err) - } else if !reflect.DeepEqual(eSMGEv, rSMGEv) { - t.Errorf("Expecting: %+v, received: %+v", eSMGEv, rSMGEv) - } -} - -func TestPassesFieldFilter(t *testing.T) { - m := diam.NewRequest(diam.CreditControl, 4, nil) // Multiple-Services-Credit-Control>Rating-Group - if pass, _ := passesFieldFilter(m, - utils.ParseRSRFieldsMustCompile("Multiple-Services-Credit-Control>Rating-Group(^$)", - utils.INFIELD_SEP)[0], nil); !pass { - t.Error("Does not pass") - } - procVars := processorVars{ - utils.MetaCGRReply: map[string]interface{}{ - utils.CapAttributes: map[string]interface{}{ - "RadReply": "AccessAccept", - utils.Account: "1001", - }, - utils.CapMaxUsage: time.Duration(0), - utils.Error: "", - }, - } - if pass, _ := passesFieldFilter(nil, - utils.ParseRSRFieldsMustCompile("*cgrReply>MaxUsage(^0s$)", utils.INFIELD_SEP)[0], - procVars); !pass { - t.Error("not passing valid filter") - } - if pass, _ := passesFieldFilter(nil, - utils.ParseRSRFieldsMustCompile("*cgrReply>MaxUsage{*duration_seconds}(^0$)", utils.INFIELD_SEP)[0], - procVars); !pass { - t.Error("not passing valid filter") - } - if pass, _ := passesFieldFilter(nil, - utils.ParseRSRFieldsMustCompile("*cgrReply>Error(^$)", utils.INFIELD_SEP)[0], - procVars); !pass { - t.Error("not passing valid filter") - } -} diff --git a/agents/libhttpagent.go b/agents/libhttpagent.go index 602882dfd..d2ed91599 100644 --- a/agents/libhttpagent.go +++ b/agents/libhttpagent.go @@ -69,11 +69,14 @@ func (hU *httpUrlDP) FieldAsInterface(fldPath []string) (data interface{}, err e if len(fldPath) != 1 { return nil, utils.ErrNotFound } - if data, err = hU.cache.FieldAsInterface(fldPath); err == nil || - err != utils.ErrNotFound { // item found in cache - return + if data, err = hU.cache.FieldAsInterface(fldPath); err != nil { + if err != utils.ErrNotFound { // item found in cache + return + } + err = nil // cancel previous err + } else { + return // data found in cache } - err = nil // cancel previous err data = hU.req.FormValue(fldPath[0]) hU.cache.Set(fldPath, data, false) return diff --git a/agents/librad.go b/agents/librad.go index 3ec9a50e6..afa9f0d59 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -150,7 +150,7 @@ type radiusDP struct { // String is part of engine.DataProvider interface // when called, it will display the already parsed values out of cache func (pk *radiusDP) String() string { - return utils.ToJSON(pk) + return utils.ToJSON(pk.cache) } // FieldAsInterface is part of engine.DataProvider interface @@ -158,11 +158,14 @@ func (pk *radiusDP) FieldAsInterface(fldPath []string) (data interface{}, err er if len(fldPath) != 1 { return nil, utils.ErrNotFound } - if data, err = pk.cache.FieldAsInterface(fldPath); err == nil || - err != utils.ErrNotFound { // item found in cache - return + if data, err = pk.cache.FieldAsInterface(fldPath); err != nil { + if err != utils.ErrNotFound { // item found in cache + return + } + err = nil // cancel previous err + } else { + return // data found in cache } - err = nil // cancel previous err if len(pk.req.AttributesWithName(fldPath[0], "")) != 0 { data = pk.req.AttributesWithName(fldPath[0], "")[0].GetStringValue() } diff --git a/agents/librad_test.go b/agents/librad_test.go index 3fa557357..425c5e3be 100644 --- a/agents/librad_test.go +++ b/agents/librad_test.go @@ -97,7 +97,7 @@ func TestRadComposedFieldValue(t *testing.T) { if err := pkt.AddAVPWithName("Cisco-NAS-Port", "CGR1", "Cisco"); err != nil { t.Error(err) } - agReq := newAgentRequest(nil, nil, "cgrates.org", "", nil) + agReq := newAgentRequest(nil, nil, nil, nil, "cgrates.org", "", nil) agReq.Vars.Set([]string{MetaRadReqType}, MetaRadAcctStart, false) agReq.Vars.Set([]string{"Cisco"}, "CGR1", false) agReq.Vars.Set([]string{"User-Name"}, "flopsy", false) @@ -117,7 +117,7 @@ func TestRadFieldOutVal(t *testing.T) { t.Error(err) } eOut := fmt.Sprintf("%s|flopsy|CGR1", MetaRadAcctStart) - agReq := newAgentRequest(nil, nil, "cgrates.org", "", nil) + agReq := newAgentRequest(nil, nil, nil, nil, "cgrates.org", "", nil) agReq.Vars.Set([]string{MetaRadReqType}, MetaRadAcctStart, false) agReq.Vars.Set([]string{"Cisco"}, "CGR1", false) agReq.Vars.Set([]string{"User-Name"}, "flopsy", false) @@ -139,7 +139,7 @@ func TestRadReplyAppendAttributes(t *testing.T) { &config.FCTemplate{Tag: "Acct-Session-Time", FieldId: "Acct-Session-Time", Type: utils.META_COMPOSED, Value: config.NewRSRParsersMustCompile("~*cgrep.MaxUsage{*duration_seconds}", true)}, } - agReq := newAgentRequest(nil, nil, "cgrates.org", "", nil) + agReq := newAgentRequest(nil, nil, nil, nil, "cgrates.org", "", nil) agReq.CGRReply.Set([]string{utils.CapMaxUsage}, time.Duration(time.Hour), false) agReq.CGRReply.Set([]string{utils.CapAttributes, "RadReply"}, "AccessAccept", false) agReq.CGRReply.Set([]string{utils.CapAttributes, utils.Account}, "1001", false) diff --git a/agents/radagent.go b/agents/radagent.go index ffd40eb41..9d79b8a2e 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -83,7 +83,8 @@ func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err e rpl.Code = radigo.AccessAccept var processed bool for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors { - agReq := newAgentRequest(dcdr, reqProcessor.Tenant, ra.cgrCfg.DefaultTenant, + agReq := newAgentRequest(dcdr, nil, nil, + reqProcessor.Tenant, ra.cgrCfg.DefaultTenant, utils.FirstNonEmpty(reqProcessor.Timezone, config.CgrConfig().DefaultTimezone), ra.filterS) agReq.Vars.Set([]string{MetaRadReqType}, utils.StringToInterface(MetaRadAuth), true) @@ -123,7 +124,8 @@ func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err e rpl.Code = radigo.AccountingResponse var processed bool for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors { - agReq := newAgentRequest(dcdr, reqProcessor.Tenant, ra.cgrCfg.DefaultTenant, + agReq := newAgentRequest(dcdr, nil, nil, + reqProcessor.Tenant, ra.cgrCfg.DefaultTenant, utils.FirstNonEmpty(reqProcessor.Timezone, config.CgrConfig().DefaultTimezone), ra.filterS) var lclProcessed bool @@ -231,7 +233,10 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor, evArgs := sessions.NewV1ProcessEventArgs( reqProcessor.Flags.HasKey(utils.MetaResources), reqProcessor.Flags.HasKey(utils.MetaAccounts), - reqProcessor.Flags.HasKey(utils.MetaAttributes), *cgrEv) + reqProcessor.Flags.HasKey(utils.MetaAttributes), + reqProcessor.Flags.HasKey(utils.MetaThresholds), + reqProcessor.Flags.HasKey(utils.MetaStats), + *cgrEv) var eventRply sessions.V1ProcessEventReply err = ra.sessionS.Call(utils.SessionSv1ProcessEvent, evArgs, &eventRply) diff --git a/cmd/cgr-engine/cgr-engine.go b/cmd/cgr-engine/cgr-engine.go index d1e46b583..91d5b651f 100644 --- a/cmd/cgr-engine/cgr-engine.go +++ b/cmd/cgr-engine/cgr-engine.go @@ -278,31 +278,25 @@ func startAsteriskAgent(internalSMGChan chan rpcclient.RpcClientConnection, exit exitChan <- true } -func startDiameterAgent(internalSMGChan, internalPubSubSChan chan rpcclient.RpcClientConnection, exitChan chan bool) { +func startDiameterAgent(internalSMGChan chan rpcclient.RpcClientConnection, + exitChan chan bool, filterSChan chan *engine.FilterS) { var err error utils.Logger.Info("Starting CGRateS DiameterAgent service") - var smgConn, pubsubConn *rpcclient.RpcClientPool + filterS := <-filterSChan + filterSChan <- filterS + var smgConn *rpcclient.RpcClientPool if len(cfg.DiameterAgentCfg().SessionSConns) != 0 { smgConn, err = engine.NewRPCPool(rpcclient.POOL_FIRST, cfg.TLSClientKey, cfg.TLSClientCerificate, cfg.ConnectAttempts, cfg.Reconnects, cfg.ConnectTimeout, cfg.ReplyTimeout, cfg.DiameterAgentCfg().SessionSConns, internalSMGChan, cfg.InternalTtl) if err != nil { - utils.Logger.Crit(fmt.Sprintf(" Could not connect to SMG: %s", err.Error())) + utils.Logger.Crit(fmt.Sprintf("<%s> Could not connect to %s: %s", + utils.DiameterAgent, utils.SessionS, err.Error())) exitChan <- true return } } - if len(cfg.DiameterAgentCfg().PubSubConns) != 0 { - pubsubConn, err = engine.NewRPCPool(rpcclient.POOL_FIRST, cfg.TLSClientKey, cfg.TLSClientCerificate, - cfg.ConnectAttempts, cfg.Reconnects, cfg.ConnectTimeout, cfg.ReplyTimeout, - cfg.DiameterAgentCfg().PubSubConns, internalPubSubSChan, cfg.InternalTtl) - if err != nil { - utils.Logger.Crit(fmt.Sprintf(" Could not connect to PubSubS: %s", err.Error())) - exitChan <- true - return - } - } - da, err := agents.NewDiameterAgent(cfg, smgConn, pubsubConn) + da, err := agents.NewDiameterAgent(cfg, filterS, smgConn) if err != nil { utils.Logger.Err(fmt.Sprintf(" error: %s!", err)) exitChan <- true @@ -1303,7 +1297,7 @@ func main() { } if cfg.DiameterAgentCfg().Enabled { - go startDiameterAgent(internalSMGChan, internalPubSubSChan, exitChan) + go startDiameterAgent(internalSMGChan, exitChan, filterSChan) } if cfg.RadiusAgentCfg().Enabled { diff --git a/config/cdrcconfig.go b/config/cdrcconfig.go index 369831061..6a2ddcced 100644 --- a/config/cdrcconfig.go +++ b/config/cdrcconfig.go @@ -129,22 +129,22 @@ func (self *CdrcConfig) loadFromJsonCfg(jsnCfg *CdrcJsonCfg) error { self.PartialCacheExpiryAction = *jsnCfg.Partial_cache_expiry_action } if jsnCfg.Header_fields != nil { - if self.HeaderFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Header_fields); err != nil { + if self.HeaderFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Header_fields); err != nil { return err } } if jsnCfg.Content_fields != nil { - if self.ContentFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Content_fields); err != nil { + if self.ContentFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Content_fields); err != nil { return err } } if jsnCfg.Trailer_fields != nil { - if self.TrailerFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Trailer_fields); err != nil { + if self.TrailerFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Trailer_fields); err != nil { return err } } if jsnCfg.Cache_dump_fields != nil { - if self.CacheDumpFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Cache_dump_fields); err != nil { + if self.CacheDumpFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Cache_dump_fields); err != nil { return err } } diff --git a/config/cdreconfig.go b/config/cdreconfig.go index f4332d343..c4ce20d93 100644 --- a/config/cdreconfig.go +++ b/config/cdreconfig.go @@ -80,17 +80,17 @@ func (self *CdreConfig) loadFromJsonCfg(jsnCfg *CdreJsonCfg) (err error) { self.CostMultiplyFactor = *jsnCfg.Cost_multiply_factor } if jsnCfg.Header_fields != nil { - if self.HeaderFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Header_fields); err != nil { + if self.HeaderFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Header_fields); err != nil { return err } } if jsnCfg.Content_fields != nil { - if self.ContentFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Content_fields); err != nil { + if self.ContentFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Content_fields); err != nil { return err } } if jsnCfg.Trailer_fields != nil { - if self.TrailerFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Trailer_fields); err != nil { + if self.TrailerFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Trailer_fields); err != nil { return err } } diff --git a/config/config.go b/config/config.go index 7bb89776f..413622c48 100755 --- a/config/config.go +++ b/config/config.go @@ -612,12 +612,8 @@ func (self *CGRConfig) checkConfigSanity() error { if self.diameterAgentCfg.Enabled { for _, daSMGConn := range self.diameterAgentCfg.SessionSConns { if daSMGConn.Address == utils.MetaInternal && !self.sessionSCfg.Enabled { - return errors.New("SMGeneric not enabled but referenced by DiameterAgent component") - } - } - for _, daPubSubSConn := range self.diameterAgentCfg.PubSubConns { - if daPubSubSConn.Address == utils.MetaInternal && !self.PubSubServerEnabled { - return errors.New("PubSubS not enabled but requested by DiameterAgent component.") + return fmt.Errorf("%s not enabled but referenced by %s component", + utils.SessionS, utils.DiameterAgent) } } } diff --git a/config/config_defaults.go b/config/config_defaults.go index 0f6f7c8d9..1f687d59a 100755 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -365,19 +365,30 @@ const CGRATES_CFG_JSON = ` "diameter_agent": { "enabled": false, // enables the diameter agent: "listen": "127.0.0.1:3868", // address where to listen for diameter requests - "dictionaries_dir": "/usr/share/cgrates/diameter/dict/", // path towards directory holding additional dictionaries to load + "dictionaries_path": "/usr/share/cgrates/diameter/dict/", // path towards directory holding additional dictionaries to load "sessions_conns": [ {"address": "*internal"} // connection towards SessionService ], - "pubsubs_conns": [], // address where to reach the pubusb service, empty to disable pubsub functionality: <""|*internal|x.y.z.y:1234> - "create_cdr": true, // create CDR out of CCR terminate and send it to SessionS - "cdr_requires_session": true, // only create CDR if there is an active session at terminate - "debit_interval": "5m", // interval for CCR updates - "timezone": "", // timezone for timestamps where not specified, empty for general defaults <""|UTC|Local|$IANA_TZ_DB> "origin_host": "CGR-DA", // diameter Origin-Host AVP used in replies "origin_realm": "cgrates.org", // diameter Origin-Realm AVP used in replies "vendor_id": 0, // diameter Vendor-Id AVP used in replies "product_name": "CGRateS", // diameter Product-Name AVP used in replies + "templates":{ + "*cca": [ + {"tag": "SessionId", "field_id": "Session-Id", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginHost", "field_id": "Origin-Host", "type": "*composed", + "value": "~*vars.OriginHost", "mandatory": true}, + {"tag": "OriginRealm", "field_id": "Origin-Realm", "type": "*composed", + "value": "~*vars.OriginRealm", "mandatory": true}, + {"tag": "AuthApplicationId", "field_id": "Auth-Application-Id", "type": "*composed", + "value": "~*vars.*appid", "mandatory": true}, + {"tag": "CCRequestType", "field_id": "CC-Request-Type", "type": "*composed", + "value": "~*req.CC-Request-Type", "mandatory": true}, + {"tag": "CCRequestNumber", "field_id": "CC-Request-Number", "type": "*composed", + "value": "~*req.CC-Request-Number", "mandatory": true}, + ] + }, "request_processors": [], }, diff --git a/config/config_json_test.go b/config/config_json_test.go index b436e696a..bf09494af 100755 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -595,29 +595,57 @@ func TestAsteriskAgentJsonCfg(t *testing.T) { func TestDiameterAgentJsonCfg(t *testing.T) { eCfg := &DiameterAgentJsonCfg{ - Enabled: utils.BoolPointer(false), - Listen: utils.StringPointer("127.0.0.1:3868"), - Dictionaries_dir: utils.StringPointer("/usr/share/cgrates/diameter/dict/"), + Enabled: utils.BoolPointer(false), + Listen: utils.StringPointer("127.0.0.1:3868"), + Dictionaries_path: utils.StringPointer("/usr/share/cgrates/diameter/dict/"), Sessions_conns: &[]*HaPoolJsonCfg{ &HaPoolJsonCfg{ Address: utils.StringPointer(utils.MetaInternal), }}, - Pubsubs_conns: &[]*HaPoolJsonCfg{}, - Create_cdr: utils.BoolPointer(true), - Cdr_requires_session: utils.BoolPointer(true), - Debit_interval: utils.StringPointer("5m"), - Timezone: utils.StringPointer(""), - Origin_host: utils.StringPointer("CGR-DA"), - Origin_realm: utils.StringPointer("cgrates.org"), - Vendor_id: utils.IntPointer(0), - Product_name: utils.StringPointer("CGRateS"), - Request_processors: &[]*DARequestProcessorJsnCfg{}, + Origin_host: utils.StringPointer("CGR-DA"), + Origin_realm: utils.StringPointer("cgrates.org"), + Vendor_id: utils.IntPointer(0), + Product_name: utils.StringPointer("CGRateS"), + Templates: map[string][]*FcTemplateJsonCfg{ + utils.MetaCCA: { + {Tag: utils.StringPointer("SessionId"), + Field_id: utils.StringPointer("Session-Id"), + Type: utils.StringPointer(utils.META_COMPOSED), + Value: utils.StringPointer("~*req.Session-Id"), + Mandatory: utils.BoolPointer(true)}, + {Tag: utils.StringPointer("OriginHost"), + Field_id: utils.StringPointer("Origin-Host"), + Type: utils.StringPointer(utils.META_COMPOSED), + Value: utils.StringPointer("~*vars.OriginHost"), + Mandatory: utils.BoolPointer(true)}, + {Tag: utils.StringPointer("OriginRealm"), + Field_id: utils.StringPointer("Origin-Realm"), + Type: utils.StringPointer(utils.META_COMPOSED), + Value: utils.StringPointer("~*vars.OriginRealm"), + Mandatory: utils.BoolPointer(true)}, + {Tag: utils.StringPointer("AuthApplicationId"), + Field_id: utils.StringPointer("Auth-Application-Id"), + Type: utils.StringPointer(utils.META_COMPOSED), + Value: utils.StringPointer("~*vars.*appid"), + Mandatory: utils.BoolPointer(true)}, + {Tag: utils.StringPointer("CCRequestType"), + Field_id: utils.StringPointer("CC-Request-Type"), + Type: utils.StringPointer(utils.META_COMPOSED), + Value: utils.StringPointer("~*req.CC-Request-Type"), + Mandatory: utils.BoolPointer(true)}, + {Tag: utils.StringPointer("CCRequestNumber"), + Field_id: utils.StringPointer("CC-Request-Number"), + Type: utils.StringPointer(utils.META_COMPOSED), + Value: utils.StringPointer("~*req.CC-Request-Number"), + Mandatory: utils.BoolPointer(true)}, + }, + }, + Request_processors: &[]*DARequestProcessorJsnCfg{}, } if cfg, err := dfCgrJsonCfg.DiameterAgentJsonCfg(); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCfg, cfg) { - rcv := *cfg.Request_processors - t.Errorf("Received: %+v", rcv[0].CCA_fields) + t.Errorf("Received: %+v", cfg) } } @@ -637,8 +665,7 @@ func TestRadiusAgentJsonCfg(t *testing.T) { &HaPoolJsonCfg{ Address: utils.StringPointer(utils.MetaInternal), }}, - Cdr_requires_session: utils.BoolPointer(false), - Request_processors: &[]*RAReqProcessorJsnCfg{}, + Request_processors: &[]*RAReqProcessorJsnCfg{}, } if cfg, err := dfCgrJsonCfg.RadiusAgentJsonCfg(); err != nil { t.Error(err) diff --git a/config/config_test.go b/config/config_test.go index 2622c1171..52861b489 100755 --- a/config/config_test.go +++ b/config/config_test.go @@ -929,15 +929,11 @@ func TestCgrCfgJSONDefaultSupplierSCfg(t *testing.T) { func TestCgrCfgJSONDefaultsDiameterAgentCfg(t *testing.T) { testDA := &DiameterAgentCfg{ - Enabled: false, - Listen: "127.0.0.1:3868", - DictionariesDir: "/usr/share/cgrates/diameter/dict/", + Enabled: false, + Listen: "127.0.0.1:3868", + DictionariesPath: "/usr/share/cgrates/diameter/dict/", SessionSConns: []*HaPoolConfig{ - {Address: "*internal"}}, - PubSubConns: []*HaPoolConfig{}, - CreateCDR: true, - DebitInterval: 5 * time.Minute, - Timezone: "", + &HaPoolConfig{Address: "*internal"}}, OriginHost: "CGR-DA", OriginRealm: "cgrates.org", VendorId: 0, @@ -951,24 +947,12 @@ func TestCgrCfgJSONDefaultsDiameterAgentCfg(t *testing.T) { if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.Listen, testDA.Listen) { t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.Listen, testDA.Listen) } - if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.DictionariesDir, testDA.DictionariesDir) { - t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.DictionariesDir, testDA.DictionariesDir) + if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.DictionariesPath, testDA.DictionariesPath) { + t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.DictionariesPath, testDA.DictionariesPath) } if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.SessionSConns, testDA.SessionSConns) { t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.SessionSConns, testDA.SessionSConns) } - if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.PubSubConns, testDA.PubSubConns) { - t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.PubSubConns, testDA.PubSubConns) - } - if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.CreateCDR, testDA.CreateCDR) { - t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.CreateCDR, testDA.CreateCDR) - } - if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.DebitInterval, testDA.DebitInterval) { - t.Errorf("expecting: %+v, received: %+v", cgrCfg.diameterAgentCfg.DebitInterval, testDA.DebitInterval) - } - if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.Timezone, testDA.Timezone) { - t.Errorf("received: %+v, expecting: %+v", cgrCfg.diameterAgentCfg.Timezone, testDA.Timezone) - } if !reflect.DeepEqual(cgrCfg.diameterAgentCfg.OriginHost, testDA.OriginHost) { t.Errorf("received: %+v, expecting: %+v", cgrCfg.diameterAgentCfg.OriginHost, testDA.OriginHost) } @@ -1068,8 +1052,7 @@ func TestRadiusAgentCfg(t *testing.T) { ListenAcct: "127.0.0.1:1813", ClientSecrets: map[string]string{utils.META_DEFAULT: "CGRateS.org"}, ClientDictionaries: map[string]string{utils.META_DEFAULT: "/usr/share/cgrates/radius/dict/"}, - SessionSConns: []*HaPoolConfig{{Address: utils.MetaInternal}}, - CDRRequiresSession: false, + SessionSConns: []*HaPoolConfig{&HaPoolConfig{Address: utils.MetaInternal}}, RequestProcessors: nil, } if !reflect.DeepEqual(cgrCfg.radiusAgentCfg, testRA) { diff --git a/config/daconfig.go b/config/daconfig.go index e7daf35a8..011ff7639 100644 --- a/config/daconfig.go +++ b/config/daconfig.go @@ -19,88 +19,70 @@ along with this program. If not, see package config import ( - "time" - "github.com/cgrates/cgrates/utils" ) type DiameterAgentCfg struct { - Enabled bool // enables the diameter agent: - Listen string // address where to listen for diameter requests - DictionariesDir string - SessionSConns []*HaPoolConfig // connections towards SMG component - PubSubConns []*HaPoolConfig // connection towards pubsubs - CreateCDR bool - CDRRequiresSession bool - DebitInterval time.Duration - Timezone string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> - OriginHost string - OriginRealm string - VendorId int - ProductName string - RequestProcessors []*DARequestProcessor + Enabled bool // enables the diameter agent: + Listen string // address where to listen for diameter requests + DictionariesPath string + SessionSConns []*HaPoolConfig // connections towards SMG component + OriginHost string + OriginRealm string + VendorId int + ProductName string + Templates map[string][]*FCTemplate + RequestProcessors []*DARequestProcessor } -func (self *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) error { +func (da *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) (err error) { if jsnCfg == nil { return nil } if jsnCfg.Enabled != nil { - self.Enabled = *jsnCfg.Enabled + da.Enabled = *jsnCfg.Enabled } if jsnCfg.Listen != nil { - self.Listen = *jsnCfg.Listen + da.Listen = *jsnCfg.Listen } - if jsnCfg.Dictionaries_dir != nil { - self.DictionariesDir = *jsnCfg.Dictionaries_dir + if jsnCfg.Dictionaries_path != nil { + da.DictionariesPath = *jsnCfg.Dictionaries_path } if jsnCfg.Sessions_conns != nil { - self.SessionSConns = make([]*HaPoolConfig, len(*jsnCfg.Sessions_conns)) + da.SessionSConns = make([]*HaPoolConfig, len(*jsnCfg.Sessions_conns)) for idx, jsnHaCfg := range *jsnCfg.Sessions_conns { - self.SessionSConns[idx] = NewDfltHaPoolConfig() - self.SessionSConns[idx].loadFromJsonCfg(jsnHaCfg) + da.SessionSConns[idx] = NewDfltHaPoolConfig() + da.SessionSConns[idx].loadFromJsonCfg(jsnHaCfg) } } - if jsnCfg.Pubsubs_conns != nil { - self.PubSubConns = make([]*HaPoolConfig, len(*jsnCfg.Pubsubs_conns)) - for idx, jsnHaCfg := range *jsnCfg.Pubsubs_conns { - self.PubSubConns[idx] = NewDfltHaPoolConfig() - self.PubSubConns[idx].loadFromJsonCfg(jsnHaCfg) - } - } - if jsnCfg.Create_cdr != nil { - self.CreateCDR = *jsnCfg.Create_cdr - } - if jsnCfg.Cdr_requires_session != nil { - self.CDRRequiresSession = *jsnCfg.Cdr_requires_session - } - if jsnCfg.Debit_interval != nil { - var err error - if self.DebitInterval, err = utils.ParseDurationWithNanosecs(*jsnCfg.Debit_interval); err != nil { - return err - } - } - if jsnCfg.Timezone != nil { - self.Timezone = *jsnCfg.Timezone - } if jsnCfg.Origin_host != nil { - self.OriginHost = *jsnCfg.Origin_host + da.OriginHost = *jsnCfg.Origin_host } if jsnCfg.Origin_realm != nil { - self.OriginRealm = *jsnCfg.Origin_realm + da.OriginRealm = *jsnCfg.Origin_realm } if jsnCfg.Vendor_id != nil { - self.VendorId = *jsnCfg.Vendor_id + da.VendorId = *jsnCfg.Vendor_id } if jsnCfg.Product_name != nil { - self.ProductName = *jsnCfg.Product_name + da.ProductName = *jsnCfg.Product_name + } + if jsnCfg.Templates != nil { + if da.Templates == nil { + da.Templates = make(map[string][]*FCTemplate) + } + for k, jsnTpls := range jsnCfg.Templates { + if da.Templates[k], err = FCTemplatesFromFCTemplatesJsonCfg(jsnTpls); err != nil { + return + } + } } if jsnCfg.Request_processors != nil { for _, reqProcJsn := range *jsnCfg.Request_processors { rp := new(DARequestProcessor) var haveID bool - for _, rpSet := range self.RequestProcessors { - if reqProcJsn.Id != nil && rpSet.Id == *reqProcJsn.Id { + for _, rpSet := range da.RequestProcessors { + if reqProcJsn.Id != nil && rpSet.ID == *reqProcJsn.Id { rp = rpSet // Will load data into the one set haveID = true break @@ -110,7 +92,7 @@ func (self *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) erro return nil } if !haveID { - self.RequestProcessors = append(self.RequestProcessors, rp) + da.RequestProcessors = append(da.RequestProcessors, rp) } } } @@ -119,53 +101,49 @@ func (self *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) erro // One Diameter request processor configuration type DARequestProcessor struct { - Id string - DryRun bool - PublishEvent bool - RequestFilter utils.RSRFields - Flags utils.StringMap // Various flags to influence behavior + ID string + Tenant RSRParsers + Filters []string + Flags utils.StringMap + Timezone string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> ContinueOnSuccess bool - AppendCCA bool - CCRFields []*CfgCdrField - CCAFields []*CfgCdrField + RequestFields []*FCTemplate + ReplyFields []*FCTemplate } -func (self *DARequestProcessor) loadFromJsonCfg(jsnCfg *DARequestProcessorJsnCfg) error { +func (dap *DARequestProcessor) loadFromJsonCfg(jsnCfg *DARequestProcessorJsnCfg) (err error) { if jsnCfg == nil { return nil } if jsnCfg.Id != nil { - self.Id = *jsnCfg.Id + dap.ID = *jsnCfg.Id } - if jsnCfg.Dry_run != nil { - self.DryRun = *jsnCfg.Dry_run + if jsnCfg.Tenant != nil { + dap.Tenant = NewRSRParsersMustCompile(*jsnCfg.Tenant, true) } - if jsnCfg.Publish_event != nil { - self.PublishEvent = *jsnCfg.Publish_event - } - var err error - if jsnCfg.Request_filter != nil { - if self.RequestFilter, err = utils.ParseRSRFields(*jsnCfg.Request_filter, utils.INFIELD_SEP); err != nil { - return err + if jsnCfg.Filters != nil { + dap.Filters = make([]string, len(*jsnCfg.Filters)) + for i, fltr := range *jsnCfg.Filters { + dap.Filters[i] = fltr } } if jsnCfg.Flags != nil { - self.Flags = utils.StringMapFromSlice(*jsnCfg.Flags) + dap.Flags = utils.StringMapFromSlice(*jsnCfg.Flags) + } + if jsnCfg.Timezone != nil { + dap.Timezone = *jsnCfg.Timezone } if jsnCfg.Continue_on_success != nil { - self.ContinueOnSuccess = *jsnCfg.Continue_on_success + dap.ContinueOnSuccess = *jsnCfg.Continue_on_success } - if jsnCfg.Append_cca != nil { - self.AppendCCA = *jsnCfg.Append_cca - } - if jsnCfg.CCR_fields != nil { - if self.CCRFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.CCR_fields); err != nil { - return err + if jsnCfg.Request_fields != nil { + if dap.RequestFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Request_fields); err != nil { + return } } - if jsnCfg.CCA_fields != nil { - if self.CCAFields, err = CfgCdrFieldsFromCdrFieldsJsonCfg(*jsnCfg.CCA_fields); err != nil { - return err + if jsnCfg.Reply_fields != nil { + if dap.ReplyFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Reply_fields); err != nil { + return } } return nil diff --git a/config/fctemplate.go b/config/fctemplate.go index d004f056f..38d9da4fa 100755 --- a/config/fctemplate.go +++ b/config/fctemplate.go @@ -18,6 +18,12 @@ along with this program. If not, see package config +import ( + "fmt" + + "github.com/cgrates/cgrates/utils" +) + func NewFCTemplateFromFCTemplateJsonCfg(jsnCfg *FcTemplateJsonCfg) (*FCTemplate, error) { fcTmp := new(FCTemplate) var err error @@ -112,7 +118,7 @@ type FCTemplate struct { MaskLen int } -func FCTemplatesFromFCTemapltesJsonCfg(jsnCfgFlds []*FcTemplateJsonCfg) ([]*FCTemplate, error) { +func FCTemplatesFromFCTemplatesJsonCfg(jsnCfgFlds []*FcTemplateJsonCfg) ([]*FCTemplate, error) { retFields := make([]*FCTemplate, len(jsnCfgFlds)) var err error for i, jsnFld := range jsnCfgFlds { @@ -122,3 +128,35 @@ func FCTemplatesFromFCTemapltesJsonCfg(jsnCfgFlds []*FcTemplateJsonCfg) ([]*FCTe } return retFields, nil } + +// InflateTemplates will replace the *template fields with template content out msgTpls +func InflateTemplates(fcts []*FCTemplate, msgTpls map[string][]*FCTemplate) ([]*FCTemplate, error) { + var hasTpl bool + for i := 0; i < len(fcts); { + if fcts[i].Type == utils.MetaTemplate { + hasTpl = true + tplID, err := fcts[i].Value.ParseValue(nil) + if err != nil { + return nil, err + } + refTpl, has := msgTpls[tplID] + if !has { + return nil, fmt.Errorf("no template with id: <%s>", tplID) + } else if len(refTpl) == 0 { + continue + } + wrkSlice := make([]*FCTemplate, len(refTpl)+len(fcts[i:])-1) // so we can cover tpls[i+1:] + copy(wrkSlice[:len(refTpl)], refTpl) // copy fields out of referenced template + if len(fcts[i:]) > 1 { // copy the rest of the fields after MetaTemplate + copy(wrkSlice[len(refTpl):], fcts[i+1:]) + } + fcts = append(fcts[:i], wrkSlice...) // append the work + continue // don't increase index so we can recheck + } + i++ + } + if !hasTpl { + return nil, nil + } + return fcts, nil +} diff --git a/config/fctemplate_test.go b/config/fctemplate_test.go index dc58b6396..442b74d62 100755 --- a/config/fctemplate_test.go +++ b/config/fctemplate_test.go @@ -46,7 +46,7 @@ func TestNewFCTemplateFromFCTemplateJsonCfg(t *testing.T) { } } -func TestFCTemplatesFromFCTemapltesJsonCfg(t *testing.T) { +func TestFCTemplatesFromFCTemplatesJsonCfg(t *testing.T) { jsnCfgs := []*FcTemplateJsonCfg{ &FcTemplateJsonCfg{ Tag: utils.StringPointer("Tenant"), @@ -79,7 +79,7 @@ func TestFCTemplatesFromFCTemapltesJsonCfg(t *testing.T) { Value: NewRSRParsersMustCompile("SampleValue", true), }, } - if rcv, err := FCTemplatesFromFCTemapltesJsonCfg(jsnCfgs); err != nil { + if rcv, err := FCTemplatesFromFCTemplatesJsonCfg(jsnCfgs); err != nil { t.Error(err) } else if !reflect.DeepEqual(expected, rcv) { t.Errorf("expected: %s ,received: %s", utils.ToJSON(expected), utils.ToJSON(rcv)) diff --git a/config/httpagntcfg.go b/config/httpagntcfg.go index 29f00f71a..78bc80546 100644 --- a/config/httpagntcfg.go +++ b/config/httpagntcfg.go @@ -115,12 +115,12 @@ func (ha *HttpAgntProcCfg) loadFromJsonCfg(jsnCfg *HttpAgentProcessorJsnCfg) (er ha.ContinueOnSuccess = *jsnCfg.Continue_on_success } if jsnCfg.Request_fields != nil { - if ha.RequestFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Request_fields); err != nil { + if ha.RequestFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Request_fields); err != nil { return } } if jsnCfg.Reply_fields != nil { - if ha.ReplyFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Reply_fields); err != nil { + if ha.ReplyFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Reply_fields); err != nil { return } } diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 249b82b93..f2887a4eb 100755 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -327,48 +327,42 @@ type OsipsConnJsonCfg struct { // DiameterAgent configuration type DiameterAgentJsonCfg struct { - Enabled *bool // enables the diameter agent: - Listen *string // address where to listen for diameter requests - Dictionaries_dir *string // path towards additional dictionaries - Sessions_conns *[]*HaPoolJsonCfg // Connections towards generic SM - Pubsubs_conns *[]*HaPoolJsonCfg // connection towards pubsubs - Create_cdr *bool - Cdr_requires_session *bool - Debit_interval *string - Timezone *string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> - Origin_host *string - Origin_realm *string - Vendor_id *int - Product_name *string - Request_processors *[]*DARequestProcessorJsnCfg + Enabled *bool // enables the diameter agent: + Listen *string // address where to listen for diameter requests + Dictionaries_path *string // path towards additional dictionaries + Sessions_conns *[]*HaPoolJsonCfg // Connections towards SessionS + Origin_host *string + Origin_realm *string + Vendor_id *int + Product_name *string + Templates map[string][]*FcTemplateJsonCfg + Request_processors *[]*DARequestProcessorJsnCfg } // One Diameter request processor configuration type DARequestProcessorJsnCfg struct { Id *string - Dry_run *bool - Publish_event *bool - Request_filter *string + Tenant *string + Filters *[]string Flags *[]string + Timezone *string // timezone for timestamps where not specified <""|UTC|Local|$IANA_TZ_DB> Continue_on_success *bool - Append_cca *bool - CCR_fields *[]*CdrFieldJsonCfg - CCA_fields *[]*CdrFieldJsonCfg + Request_fields *[]*FcTemplateJsonCfg + Reply_fields *[]*FcTemplateJsonCfg } // Radius Agent configuration section type RadiusAgentJsonCfg struct { - Enabled *bool - Listen_net *string - Listen_auth *string - Listen_acct *string - Client_secrets *map[string]string - Client_dictionaries *map[string]string - Sessions_conns *[]*HaPoolJsonCfg - Tenant *string - Cdr_requires_session *bool - Timezone *string - Request_processors *[]*RAReqProcessorJsnCfg + Enabled *bool + Listen_net *string + Listen_auth *string + Listen_acct *string + Client_secrets *map[string]string + Client_dictionaries *map[string]string + Sessions_conns *[]*HaPoolJsonCfg + Tenant *string + Timezone *string + Request_processors *[]*RAReqProcessorJsnCfg } type RAReqProcessorJsnCfg struct { diff --git a/config/loadersconfig.go b/config/loadersconfig.go index 33e0208a0..fe94d0c9b 100644 --- a/config/loadersconfig.go +++ b/config/loadersconfig.go @@ -69,7 +69,7 @@ func (self *LoaderDataType) loadFromJsonCfg(jsnCfg *LoaderJsonDataType) (err err self.Filename = *jsnCfg.File_name } if jsnCfg.Fields != nil { - if self.Fields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Fields); err != nil { + if self.Fields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Fields); err != nil { return } } diff --git a/config/raconfig.go b/config/raconfig.go index b3b196b7e..11e91967c 100644 --- a/config/raconfig.go +++ b/config/raconfig.go @@ -30,7 +30,6 @@ type RadiusAgentCfg struct { ClientSecrets map[string]string ClientDictionaries map[string]string SessionSConns []*HaPoolConfig - CDRRequiresSession bool RequestProcessors []*RARequestProcessor } @@ -73,9 +72,6 @@ func (self *RadiusAgentCfg) loadFromJsonCfg(jsnCfg *RadiusAgentJsonCfg) (err err self.SessionSConns[idx].loadFromJsonCfg(jsnHaCfg) } } - if jsnCfg.Cdr_requires_session != nil { - self.CDRRequiresSession = *jsnCfg.Cdr_requires_session - } if jsnCfg.Request_processors != nil { for _, reqProcJsn := range *jsnCfg.Request_processors { rp := new(RARequestProcessor) @@ -138,12 +134,12 @@ func (self *RARequestProcessor) loadFromJsonCfg(jsnCfg *RAReqProcessorJsnCfg) (e self.Timezone = *jsnCfg.Timezone } if jsnCfg.Request_fields != nil { - if self.RequestFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Request_fields); err != nil { + if self.RequestFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Request_fields); err != nil { return } } if jsnCfg.Reply_fields != nil { - if self.ReplyFields, err = FCTemplatesFromFCTemapltesJsonCfg(*jsnCfg.Reply_fields); err != nil { + if self.ReplyFields, err = FCTemplatesFromFCTemplatesJsonCfg(*jsnCfg.Reply_fields); err != nil { return } } diff --git a/config/rsrparser.go b/config/rsrparser.go index 4892224cf..6022a561e 100644 --- a/config/rsrparser.go +++ b/config/rsrparser.go @@ -207,6 +207,11 @@ type RSRParser struct { filters utils.RSRFilters // The value to compare when used as filter } +// AttrName exports the attribute name of the RSRParser +func (prsr *RSRParser) AttrName() string { + return prsr.attrName +} + // Compile parses Rules string and repopulates other fields func (prsr *RSRParser) Compile() (err error) { var newPrsr *RSRParser @@ -276,12 +281,12 @@ func (prsr *RSRParser) ParseDataProvider(dP DataProvider, separator string) (out } func (prsr *RSRParser) ParseDataProviderWithInterfaces(dP DataProvider, separator string) (out string, err error) { - var outStr interface{} + var outIface interface{} if prsr.attrValue == "" { - if outStr, err = dP.FieldAsInterface( + if outIface, err = dP.FieldAsInterface( strings.Split(prsr.attrName, separator)); err != nil { return } } - return prsr.ParseValue(outStr) + return prsr.ParseValue(outIface) } diff --git a/data/conf/samples/dmtagent/cgrates.json b/data/conf/samples/diamagent/cgrates.json similarity index 64% rename from data/conf/samples/dmtagent/cgrates.json rename to data/conf/samples/diamagent/cgrates.json index 3e4c4ae09..e3ed92240 100644 --- a/data/conf/samples/dmtagent/cgrates.json +++ b/data/conf/samples/diamagent/cgrates.json @@ -30,18 +30,6 @@ "rals": { "enabled": true, - "cdrstats_conns": [ - {"address": "*internal"} - ], - "pubsubs_conns": [ - {"address": "*internal"} - ], - "users_conns": [ - {"address": "*internal"} - ], - "aliases_conns": [ - {"address": "*internal"} - ], }, "scheduler": { @@ -50,49 +38,35 @@ "cdrs": { "enabled": true, - "cdrstats_conns": [ - {"address": "*internal"} - ], -}, - -"cdrstats": { - "enabled": true, -}, - -"pubsubs": { - "enabled": true, // starts PubSub service: . -}, - -"aliases": { - "enabled": true, // starts Aliases service: . -}, - -"users": { - "enabled": true, - "indexes": ["SubscriberId"], -}, - -"resources": { - "enabled": true, }, "attributes": { "enabled": true, }, -"suppliers": { +"chargers": { "enabled": true, }, + "sessions": { "enabled": true, + "attributes_conns": [ + {"address": "127.0.0.1:2012","transport":"*json"} + ], + "chargers_conns": [ + {"address": "127.0.0.1:2012","transport":"*json"} + ], + "rals_conns": [ + {"address": "127.0.0.1:2012","transport":"*json"} + ], + "cdrs_conns": [ + {"address": "127.0.0.1:2012","transport":"*json"} + ], }, "diameter_agent": { "enabled": true, - "pubsubs_conns": [ - {"address": "*internal"} - ], "sessions_conns": [ {"address": "127.0.0.1:2012","transport":"*json"} ], diff --git a/data/conf/samples/diamagent/data.json b/data/conf/samples/diamagent/data.json new file mode 100644 index 000000000..98a8ae7eb --- /dev/null +++ b/data/conf/samples/diamagent/data.json @@ -0,0 +1,125 @@ + +{ + +"diameter_agent": { + "request_processors": [ + + { + "id": "data_init", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:1", "*prefix:*req.Service-Context-Id:gprs"], + "flags": ["*initiate", "*accounts"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*data"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "generic"}, + {"tag": "Account", "field_id": "Account", "type": "*composed", + "value": "~*req.Subscription-Id.Subscription-Id-Data[~Subscription-Id-Type(1)]", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, + ], + "reply_fields": [ + {"tag": "CCATemplate", "type": "*template", "value": "*cca"}, + {"tag": "ResultCode", "filters": ["*rsr::~*cgrep.Error(!^$)"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + ], + }, + + { + "id": "data_update_grp1", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:2", + "*string:*req.Multiple-Services-Credit-Control.Rating-Group:1", "*prefix:*req.Service-Context-Id:gprs"], + "flags": ["*update", "*accounts"], + "continue_on_success": true, + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*data"}, + {"tag": "InitialOriginID", "field_id": "InitialOriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "_grp1"}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "Category", "field_id": "Category", "type": "*contant", "value": "generic"}, + {"tag": "Account", "field_id": "Account", "type": "*composed", + "value": "~*req.Subscription-Id.Subscription-Id-Data[~Subscription-Id-Type(0)]"}, + {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*sum", + "value": "~*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Input-Octets[~Rating-Group(1)];~*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Output-Octets[~Rating-Group(1)]"}, + ], + "reply_fields": [ + {"tag": "CCATemplate", "type": "*template", "value": "*cca"}, + {"tag": "ResultCode", "filters": ["*rsr::~*cgrep.Error(!^$)"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + ], + }, + + { + "id": "data_update_grp2", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:2", + "*string:*req.Multiple-Services-Credit-Control.Rating-Group[1]:2", "*prefix:*req.Service-Context-Id:gprs"], + "flags": ["*update", "*accounts"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*data"}, + {"tag": "InitialOriginID", "field_id": "InitialOriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*constant", "value": "_grp2"}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "generic"}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "mandatory": true, + "value": "~*req.Subscription-Id.Subscription-Id-Data[~Subscription-Id-Type(0)]"}, + {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*sum", + "value": "~*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Input-Octets[~Rating-Group(2)];~*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Output-Octets[~Rating-Group(2)]"}, + ], + "reply_fields": [ + {"tag": "CCATemplate", "type": "*template", "value": "*cca"}, + {"tag": "ResultCode", "filters": ["*rsr::~*cgrep.Error(!^$)"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + ], + }, + + { + "id": "data_terminate", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:3", + "*prefix:*req.Service-Context-Id:gprs"], + "flags": ["*terminate", "*accounts"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*data"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginIDPrefix", "field_id": "OriginIDPrefix", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "generic"}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "mandatory": true, + "value": "~*req.Subscription-Id.Subscription-Id-Data[~Subscription-Id-Type(0)]"}, + {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*handler", "handler_id": "*sum", + "value": "~*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Input-Octets;~*req.Multiple-Services-Credit-Control.Used-Service-Unit.CC-Output-Octets"}, + ], + }, + ] +} + +} \ No newline at end of file diff --git a/data/conf/samples/diamagent/dryrun.json b/data/conf/samples/diamagent/dryrun.json new file mode 100644 index 000000000..452149651 --- /dev/null +++ b/data/conf/samples/diamagent/dryrun.json @@ -0,0 +1,25 @@ +{ + +"diameter_agent": { + "request_processors": [ + + { + "id": "dryrun1", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.Service-Context-Id:TestDiamItDryRun"], + "flags": ["*dryrun"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*sms"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "~*req.Event-Timestamp", "mandatory": true}, + ], + "reply_fields":[ + {"tag": "CCATemplate", "type": "*template", "value": "*cca"}, + {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "2002"}, + ], + }, + ], +}, + +} \ No newline at end of file diff --git a/data/conf/samples/diamagent/message.json b/data/conf/samples/diamagent/message.json new file mode 100644 index 000000000..5c8eb0f5a --- /dev/null +++ b/data/conf/samples/diamagent/message.json @@ -0,0 +1,37 @@ +{ + +"diameter_agent": { + "request_processors": [ + + { + "id": "message", + "filters": ["*string:*vars.*cmd:CCR", "*prefix:*req.Service-Context-Id:message", + "*string:*req.CC-Request-Type:4"], + "flags": ["*event", "*accounts", "*cdrs"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*sms"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "sms"}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "mandatory": true, + "value": "~*req.Subscription-Id.Subscription-Id-Data[~Subscription-Id-Type(0)]"}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", "mandatory": true, + "value": "~*req.Service-Information.SMS-Information.Recipient-Address.Address-Data"}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", + "value": "~*req.Requested-Service-Unit.CC-Time", "mandatory": true}, + ], + "reply_fields":[ + {"tag": "ResultCode", "filters": ["*rsr::~*cgrep.Error(!^$)"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + ], + }, + + ], +}, + +} \ No newline at end of file diff --git a/data/conf/samples/diamagent/simpa.json b/data/conf/samples/diamagent/simpa.json new file mode 100644 index 000000000..d58867b16 --- /dev/null +++ b/data/conf/samples/diamagent/simpa.json @@ -0,0 +1,28 @@ + +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "simpa_event", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:4", + "*prefix:*req.Service-Context-Id:simpa"], + "flags": ["*event", "*accounts"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*generic"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "generic"}, + {"tag": "Account", "field_id": "Account", "type": "*composed", "mandatory": true, + "value": "~*req.Subscription-Id.Subscription-Id-Data[~Subscription-Id-Type(0)]"}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*value_exponent", "mandatory": true, + "value": "~*req.Requested-Service-Unit.CC-Money.Unit-Value.Value-Digits;~*req.Requested-Service-Unit.CC-Money.Unit-Value.Exponent"}, + ], + }, + ], +}, + +} \ No newline at end of file diff --git a/data/conf/samples/diamagent/voice.json b/data/conf/samples/diamagent/voice.json new file mode 100644 index 000000000..bf085e906 --- /dev/null +++ b/data/conf/samples/diamagent/voice.json @@ -0,0 +1,90 @@ + +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "VoiceInit", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:1", + "*prefix:*req.Service-Context-Id:voice"], + "flags": ["*initiate", "*accounts", "*attributes"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*voice"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*attributes"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "call"}, + {"tag": "Account", "field_id": "Account", "type": "*constant", "value": "*attributes"}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", + "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", + "value": "~*req.Requested-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, + {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", + "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, + ], + "reply_fields":[ + {"tag": "ResultCode", "filters": ["*rsr::~*cgrep.Error(!^$)"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + {"tag": "GrantedUnits", "field_id": "Granted-Service-Unit.CC-Time", "type": "*composed", + "value": "~*cgrep.MaxUsage{*duration_seconds}", "mandatory": true}, + ], + }, + { + "id": "VoiceUpdate", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:2", + "*prefix:*req.Service-Context-Id:voice"], + "flags": ["*update", "*accounts", "*attributes"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*voice"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*attributes"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "call"}, + {"tag": "Account", "field_id": "Account", "type": "*constant", "value": "*attributes"}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", + "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", + "value": "~*req.Requested-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*composed", + "value": "~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, + {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", + "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, + ], + "reply_fields":[ + {"tag": "ResultCode", "filters": ["*rsr::~*cgrep.Error(!^$)"], + "field_id": "Result-Code", "type": "*constant", "value": "5030", "blocker": true}, + {"tag": "GrantedUnits", "field_id": "Granted-Service-Unit.CC-Time", "type": "*composed", + "value": "~*cgrep.MaxUsage{*duration_seconds}", "mandatory": true}, + ], + }, + { + "id": "VoiceTerminate", + "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:3", + "*prefix:*req.Service-Context-Id:voice"], + "flags": ["*terminate", "*accounts", "*attributes", "*cdrs"], + "request_fields":[ + {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*voice"}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*attributes"}, + {"tag": "Account", "field_id": "Account", "type": "*constant", "value": "*attributes"}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", + "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "~*req.Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*cc_usage", "mandatory": true, + "value": "~*req.CC-Request-Number;~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/;5m"}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*composed", + "value": "~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, + {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", + "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, + ], + }, + ], +}, + +} \ No newline at end of file diff --git a/data/conf/samples/dmtagent/data.json b/data/conf/samples/dmtagent/data.json deleted file mode 100644 index b3dba2295..000000000 --- a/data/conf/samples/dmtagent/data.json +++ /dev/null @@ -1,107 +0,0 @@ - -{ - -"diameter_agent": { - "request_processors": [ - { - "id": "data_init", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^gprs);CC-Request-Type(1)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*data", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^generic", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*grouped", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, - ], - "cca_fields": [ - {"tag": "ResultCode", "field_filter": "*cgrReply>MaxUsage(^0$)", - "field_id": "Result-Code", "type": "*constant", "value": "4010"}, - ], - }, - { - "id": "data_update_grp1", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^gprs);CC-Request-Type(2);Multiple-Services-Credit-Control>Rating-Group(1)", // filter requests processed by this processor - "continue_on_success": true, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*data", "mandatory": true}, - {"tag": "InitialOriginID", "field_id": "InitialOriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "^_grp1", "append": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^generic", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*grouped", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, - {"tag": "LastUsed", "field_id": "LastUsed", "field_filter":"Multiple-Services-Credit-Control>Rating-Group(1)", "type": "*handler", "handler_id": "*sum", - "value": "Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets"}, - ], - "cca_fields": [ - {"tag": "ResultCode", "field_filter": "*cgrReply>MaxUsage(^0$)", - "field_id": "Result-Code", "type": "*constant", "value": "4010"}, - ], - }, - { - "id": "data_update_grp2", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^gprs);CC-Request-Type(2);Multiple-Services-Credit-Control>Rating-Group(2)", // filter requests processed by this processor - "continue_on_success": true, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*data", "mandatory": true}, - {"tag": "InitialOriginID", "field_id": "InitialOriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "^_grp2", "append": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^generic", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*grouped", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "2048"}, - {"tag": "LastUsed", "field_id": "LastUsed", "field_filter":"Multiple-Services-Credit-Control>Rating-Group(2)", "type": "*handler", "handler_id": "*sum", - "value": "Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets"}, - ], - "cca_fields": [ - {"tag": "ResultCode", "field_filter": "*cgrReply>MaxUsage(^0$)", - "field_id": "Result-Code", "type": "*constant", "value": "4010"}, - ], - }, - { - "id": "data_terminate", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^gprs);CC-Request-Type(3)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*data", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "OriginIDPrefix", "field_id": "OriginIDPrefix", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^generic", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*grouped", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*constant", "value": "data"}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "LastUsed", "field_id": "LastUsed", "type": "*handler", "handler_id": "*sum", - "value": "Multiple-Services-Credit-Control>Used-Service-Unit>CC-Input-Octets;^|;Multiple-Services-Credit-Control>Used-Service-Unit>CC-Output-Octets"}, - ], - }, - ] -} - -} \ No newline at end of file diff --git a/data/conf/samples/dmtagent/diameter_processors.json b/data/conf/samples/dmtagent/diameter_processors.json deleted file mode 100644 index 875b811dd..000000000 --- a/data/conf/samples/dmtagent/diameter_processors.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - -"diameter_agent": { - "request_processors": [ - { - "id": "*default", // formal identifier of this processor - "request_filter": "Service-Context-Id(nonexistent)", // cancel matching of this processor - }, - { - "id": "dryrun1", // formal identifier of this processor - "dry_run": true, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(dryrun1)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*sms", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - ], - "cca_fields":[ // fields returned in CCA - {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "300"}, - ], - }, - { - "id": "pubsub1", // formal identifier of this processor - "dry_run": true, // do not send the events to SMG, just log them - "publish_event": true, - "request_filter": "Service-Context-Id(pubsub1)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*sms", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - ], - "cca_fields":[ // fields returned in CCA - {"tag": "ResultCode", "field_id": "Result-Code", "type": "*constant", "value": "300"}, - ], - }, - { - "id": "message", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^message)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*sms", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>SMS-Information>Recipient-Address>Address-Data", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*composed", "value": "Requested-Service-Unit>CC-Time", "mandatory": true}, - ], - "cca_fields":[ - {"tag": "ResultCode", "field_filter":"*cgrReply>Error(ACCOUNT_NOT_FOUND)", - "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - {"tag": "ResultCode", "field_filter":"*cgrReply>Error(USER_NOT_FOUND)", - "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - ], - }, - - ], -}, - -} - diff --git a/data/conf/samples/dmtagent/simpa.json b/data/conf/samples/dmtagent/simpa.json deleted file mode 100644 index 6a1fb9503..000000000 --- a/data/conf/samples/dmtagent/simpa.json +++ /dev/null @@ -1,29 +0,0 @@ - -{ - -"diameter_agent": { - "request_processors": [ - { - "id": "simpa_event", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^simpa);CC-Request-Type(4)", // filter requests processed by this processor - "continue_on_success": false, // continue to the next template if executed - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*generic", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*prepaid", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^cgrates.org", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^generic", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "field_filter":"Subscription-Id>Subscription-Id-Type(0)", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*value_exponent", - "value": "Requested-Service-Unit>CC-Money>Unit-Value>Value-Digits;^|;Requested-Service-Unit>CC-Money>Unit-Value>Exponent", "mandatory": true}, - ], - }, - ], -}, - -} \ No newline at end of file diff --git a/data/conf/samples/dmtagent/voice.json b/data/conf/samples/dmtagent/voice.json deleted file mode 100644 index 0c53c09f9..000000000 --- a/data/conf/samples/dmtagent/voice.json +++ /dev/null @@ -1,87 +0,0 @@ - -{ - -"diameter_agent": { - "request_processors": [ - { - "id": "VoiceInit", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^voice);CC-Request-Type(1)", // filter requests processed by this processor - "flags": ["*accounts"], - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", - "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, - {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, - ], - "cca_fields":[ - {"tag": "ResultCode", "field_filter":"*cgrReply>Error(ACCOUNT_NOT_FOUND)", - "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - {"tag": "ResultCode", "field_filter":"*cgrReply>Error(USER_NOT_FOUND)", - "field_id": "Result-Code", "type": "*constant", "value": "5030"}, - {"tag": "GrantedUnits", "field_filter":"*cgrReply>Error(^$)", - "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", - "value": "*cgrReply>MaxUsage{*duration_seconds}", "mandatory": true}, - ], - }, - { - "id": "VoiceUpdate", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^voice);CC-Request-Type(2)", // filter requests processed by this processor - "flags": ["*accounts"], - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, - {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, - ], - "cca_fields":[ // fields returned in CCA - {"tag": "GrantedUnits", "field_filter":"*cgrReply>Error(^$)", - "field_id": "Granted-Service-Unit>CC-Time", "type": "*composed", - "value": "*cgrReply>MaxUsage{*duration_seconds}", "mandatory": true}, - ], - }, - { - "id": "VoiceTerminate", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "Service-Context-Id(^voice);CC-Request-Type(3)", // filter requests processed by this processor - "flags": ["*accounts"], - "ccr_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 - {"tag": "TOR", "field_id": "ToR", "type": "*composed", "value": "^*voice", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Session-Id", "mandatory": true}, - {"tag": "RequestType", "field_id": "RequestType", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Direction", "field_id": "Direction", "type": "*composed", "value": "^*out", "mandatory": true}, - {"tag": "Tenant", "field_id": "Tenant", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Category", "field_id": "Category", "type": "*composed", "value": "^call", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Subject", "field_id": "Subject", "type": "*composed", "value": "^*users", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Service-Information>IN-Information>Real-Called-Number", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*handler", "handler_id": "*ccr_usage", "mandatory": true}, - {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "Subscription-Id>Subscription-Id-Data", "mandatory": true}, - ], - }, - ], -}, - -} \ No newline at end of file diff --git a/data/diameter/dict/huawei/base.xml b/data/diameter/dict/huawei/base.xml index 935896ad8..c88142a54 100644 --- a/data/diameter/dict/huawei/base.xml +++ b/data/diameter/dict/huawei/base.xml @@ -3,100 +3,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/tariffplans/tutorial/Attributes.csv b/data/tariffplans/tutorial/Attributes.csv index e5340f04d..2e8f6f686 100644 --- a/data/tariffplans/tutorial/Attributes.csv +++ b/data/tariffplans/tutorial/Attributes.csv @@ -15,5 +15,7 @@ cgrates.org,ATTR_1003_SESSIONAUTH,*sessions,*string:Account:1003,,Password,*any, cgrates.org,ATTR_1003_SESSIONAUTH,,,,RequestType,*any,*prepaid,true,, cgrates.org,ATTR_1003_SESSIONAUTH,,,,PaypalAccount,*any,cgrates@paypal.com,true,, cgrates.org,ATTR_1003_SESSIONAUTH,,,,LCRProfile,*any,premium_cli,true,, +cgrates.org,ATTR_1006_ALIAS,*any,*string:SubscriberId:1006,,Account,*any,1001,true,false,10 +cgrates.org,ATTR_1006_ALIAS,*any,,,RequestType,*any,*prepaid,true,, diff --git a/data/tariffplans/tutorial/Chargers.csv b/data/tariffplans/tutorial/Chargers.csv index e68860cdf..c270b5867 100644 --- a/data/tariffplans/tutorial/Chargers.csv +++ b/data/tariffplans/tutorial/Chargers.csv @@ -1,2 +1,2 @@ #Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight -cgrates.org,Charger1,*string:Account:1001,2014-07-29T15:00:00Z,*rated,ATTR_1001_SIMPLEAUTH,20 \ No newline at end of file +cgrates.org,DEFAULT,,,*default,*none,0 \ No newline at end of file diff --git a/data/tariffplans/tutorial/DestinationRates.csv b/data/tariffplans/tutorial/DestinationRates.csv index 1e7356366..d928fd878 100644 --- a/data/tariffplans/tutorial/DestinationRates.csv +++ b/data/tariffplans/tutorial/DestinationRates.csv @@ -3,4 +3,5 @@ DR_1001_20CNT,DST_1001,RT_20CNT,*up,4,0, DR_1002_20CNT,DST_1002,RT_20CNT,*up,4,0, DR_1003_MAXCOST_DISC,DST_1003,RT_1CNT_PER_SEC,*up,4,0.12,*disconnect DR_1001_10CNT,DST_1001,RT_10CNT,*up,4,0, +DR_SMS,*any,RT_SMS,*up,4,0, diff --git a/data/tariffplans/tutorial/Rates.csv b/data/tariffplans/tutorial/Rates.csv index c333bff3e..e735ee9aa 100644 --- a/data/tariffplans/tutorial/Rates.csv +++ b/data/tariffplans/tutorial/Rates.csv @@ -7,3 +7,4 @@ RT_40CNT,0.8,0.4,60s,30s,0s RT_40CNT,0,0.2,60s,10s,60s RT_1CNT,0,0.01,60s,60s,0s RT_1CNT_PER_SEC,0,0.01,1s,1s,0s +RT_SMS,0,0.01,1,1,0 diff --git a/data/tariffplans/tutorial/RatingPlans.csv b/data/tariffplans/tutorial/RatingPlans.csv index 4d0ec5a00..959af2ee4 100644 --- a/data/tariffplans/tutorial/RatingPlans.csv +++ b/data/tariffplans/tutorial/RatingPlans.csv @@ -3,4 +3,5 @@ RP_1001,DR_1002_20CNT,*any,10 RP_1001,DR_1003_MAXCOST_DISC,*any,10 RP_1002,DR_1001_20CNT,*any,10 RP_1002_LOW,DR_1001_10CNT,*any,10 -RP_1003,DR_1001_10CNT,*any,10 \ No newline at end of file +RP_1003,DR_1001_10CNT,*any,10 +RP_SMS,DR_SMS,*any,0 \ No newline at end of file diff --git a/data/tariffplans/tutorial/RatingProfiles.csv b/data/tariffplans/tutorial/RatingProfiles.csv index b828be471..bc6e99448 100644 --- a/data/tariffplans/tutorial/RatingProfiles.csv +++ b/data/tariffplans/tutorial/RatingProfiles.csv @@ -2,3 +2,4 @@ *out,cgrates.org,call,1001,2014-01-14T00:00:00Z,RP_1001,, *out,cgrates.org,call,1002,2014-01-14T00:00:00Z,RP_1002,, *out,cgrates.org,call,1003,2014-01-14T00:00:00Z,RP_1003,, +*out,cgrates.org,sms,*any,2014-01-14T00:00:00Z,RP_SMS,, diff --git a/engine/attributes.go b/engine/attributes.go index 3d6c78cca..0f350c189 100644 --- a/engine/attributes.go +++ b/engine/attributes.go @@ -201,13 +201,14 @@ func (alS *AttributeService) processEvent(args *AttrArgsProcessEvent) ( } rply.AlteredFields = append(rply.AlteredFields, fldName) } - for _, valIface := range rply.CGREvent.Event { - if valIface == interface{}(utils.MetaAttributes) { - return nil, utils.NewCGRError(utils.AttributeSv1ProcessEvent, - utils.AttributesNotFoundCaps, - utils.AttributesNotFound, - utils.AttributesNotFound) - } + } + for _, valIface := range rply.CGREvent.Event { + if valIface == interface{}(utils.MetaAttributes) { + return nil, utils.NewCGRError( + utils.AttributeSv1ProcessEvent, + utils.AttributesNotFound, + utils.AttributesNotFound, + utils.AttributesNotFound) } } return diff --git a/engine/cdrefwv_test.go b/engine/cdrefwv_test.go index 464b2a07d..701bf8108 100644 --- a/engine/cdrefwv_test.go +++ b/engine/cdrefwv_test.go @@ -254,13 +254,13 @@ func TestWriteCdr(t *testing.T) { var err error wrBuf := &bytes.Buffer{} cfg, _ := config.NewDefaultCGRConfig() - if hdrCfgFlds, err = config.FCTemplatesFromFCTemapltesJsonCfg(hdrJsnCfgFlds); err != nil { + if hdrCfgFlds, err = config.FCTemplatesFromFCTemplatesJsonCfg(hdrJsnCfgFlds); err != nil { t.Error(err) } - if contentCfgFlds, err = config.FCTemplatesFromFCTemapltesJsonCfg(contentJsnCfgFlds); err != nil { + if contentCfgFlds, err = config.FCTemplatesFromFCTemplatesJsonCfg(contentJsnCfgFlds); err != nil { t.Error(err) } - if trailerCfgFlds, err = config.FCTemplatesFromFCTemapltesJsonCfg(trailerJsnCfgFlds); err != nil { + if trailerCfgFlds, err = config.FCTemplatesFromFCTemplatesJsonCfg(trailerJsnCfgFlds); err != nil { t.Error(err) } cdreCfg := &config.CdreConfig{ diff --git a/glide.lock b/glide.lock index 0dde8a40d..a5c4d37c8 100644 --- a/glide.lock +++ b/glide.lock @@ -24,7 +24,7 @@ imports: - name: github.com/DisposaBoy/JsonConfigReader version: 5ea4d0ddac554439159cd6f191cb94a110d73352 - name: github.com/fiorix/go-diameter - version: d742356e99f1457ee1fb8ddc238c932e9f5ca87b + version: 16028e641c19a8dd67509053bc558d389258ff6d subpackages: - diam - diam/avp @@ -33,6 +33,8 @@ imports: - diam/sm - diam/sm/smparser - diam/sm/smpeer +- name: github.com/ishidawataru/sctp + version: 6e2cb1366111dcf547c13531e3a263a067715847 - name: github.com/go-sql-driver/mysql version: 99ff426eb706cffe92ff3d058e168b278cabf7c7 - name: github.com/gorhill/cronexpr diff --git a/glide.yaml b/glide.yaml index e7931b824..49258ca3d 100644 --- a/glide.yaml +++ b/glide.yaml @@ -14,6 +14,7 @@ import: - diam/datatype - diam/dict - diam/sm +- package: github.com/ishidawataru/sctp - package: github.com/go-sql-driver/mysql - package: github.com/gorhill/cronexpr - package: github.com/jinzhu/gorm diff --git a/sessions/sessions.go b/sessions/sessions.go index a4b6d4b86..3f51a772a 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -532,8 +532,10 @@ func (smg *SMGeneric) v2ForkSessions(tnt string, evStart *engine.SafEvent, Event: evStart.AsMapInterface(), } var chrgrs []*engine.ChrgSProcessEventReply - if err := smg.chargerS.Call(utils.ChargerSv1ProcessEvent, cgrEv, &chrgrs); err != nil && - err.Error() != utils.ErrNotFound.Error() { + if err := smg.chargerS.Call(utils.ChargerSv1ProcessEvent, cgrEv, &chrgrs); err != nil { + if err.Error() == utils.ErrNotFound.Error() { + return nil, utils.ErrNoActiveSession + } return nil, err } noneSession := []*SMGSession{ @@ -1909,7 +1911,7 @@ func (smg *SMGeneric) BiRPCv1InitiateSession(clnt rpcclient.RpcClientConnection, rply.MaxUsage = &maxUsage } } - if smg.thdS != nil && args.ProcessThresholds { + if args.ProcessThresholds { if smg.thdS == nil { return utils.NewErrNotConnected(utils.ThresholdS) } @@ -1924,7 +1926,7 @@ func (smg *SMGeneric) BiRPCv1InitiateSession(clnt rpcclient.RpcClientConnection, } rply.ThresholdIDs = &tIDs } - if smg.statS != nil && args.ProcessStats { + if args.ProcessStats { if smg.statS == nil { return utils.NewErrNotConnected(utils.StatService) } @@ -2133,7 +2135,7 @@ func (smg *SMGeneric) BiRPCv1TerminateSession(clnt rpcclient.RpcClientConnection return utils.NewErrResourceS(err) } } - if smg.thdS != nil && args.ProcessThresholds { + if args.ProcessThresholds { if smg.thdS == nil { return utils.NewErrNotConnected(utils.ThresholdS) } @@ -2147,7 +2149,10 @@ func (smg *SMGeneric) BiRPCv1TerminateSession(clnt rpcclient.RpcClientConnection fmt.Sprintf(" error: %s processing event %+v with ThresholdS.", err.Error(), thEv)) } } - if smg.statS != nil && args.ProcessStats { + if args.ProcessStats { + if smg.statS == nil { + return utils.NewErrNotConnected(utils.StatS) + } var statReply []string if err := smg.statS.Call(utils.StatSv1ProcessEvent, &engine.StatsArgsProcessEvent{CGREvent: args.CGREvent}, &statReply); err != nil && err.Error() != utils.ErrNotFound.Error() { @@ -2166,20 +2171,25 @@ func (smg *SMGeneric) BiRPCv1ProcessCDR(clnt rpcclient.RpcClientConnection, return smg.cdrsrv.Call(utils.CdrsV2ProcessCDR, cgrEv, reply) } -func NewV1ProcessEventArgs(resrc, acnts, attrs bool, +func NewV1ProcessEventArgs(resrc, acnts, attrs, thds, stats bool, cgrEv utils.CGREvent) *V1ProcessEventArgs { return &V1ProcessEventArgs{ AllocateResources: resrc, Debit: acnts, GetAttributes: attrs, + ProcessThresholds: thds, + ProcessStats: stats, CGREvent: cgrEv, } } type V1ProcessEventArgs struct { + GetAttributes bool AllocateResources bool Debit bool - GetAttributes bool + ProcessThresholds bool + ProcessStats bool + utils.CGREvent } @@ -2222,6 +2232,25 @@ func (smg *SMGeneric) BiRPCv1ProcessEvent(clnt rpcclient.RpcClientConnection, if args.CGREvent.ID == "" { args.CGREvent.ID = utils.GenUUID() } + if args.GetAttributes { + if smg.attrS == nil { + return utils.NewErrNotConnected(utils.AttributeS) + } + if args.CGREvent.Context == nil { // populate if not already in + args.CGREvent.Context = utils.StringPointer(utils.MetaSessionS) + } + attrArgs := &engine.AttrArgsProcessEvent{ + CGREvent: args.CGREvent, + } + var rplyEv engine.AttrSProcessEventReply + if err := smg.attrS.Call(utils.AttributeSv1ProcessEvent, + attrArgs, &rplyEv); err == nil { + args.CGREvent = *rplyEv.CGREvent + rply.Attributes = &rplyEv + } else if err.Error() != utils.ErrNotFound.Error() { + return utils.NewErrAttributeS(err) + } + } if args.AllocateResources { if smg.resS == nil { return utils.NewErrNotConnected(utils.ResourceS) @@ -2253,22 +2282,30 @@ func (smg *SMGeneric) BiRPCv1ProcessEvent(clnt rpcclient.RpcClientConnection, rply.MaxUsage = &maxUsage } } - if args.GetAttributes { - if smg.attrS == nil { - return utils.NewErrNotConnected(utils.AttributeS) + if args.ProcessThresholds { + if smg.thdS == nil { + return utils.NewErrNotConnected(utils.ThresholdS) } - if args.CGREvent.Context == nil { - args.CGREvent.Context = utils.StringPointer(utils.MetaSessionS) - } - attrArgs := &engine.AttrArgsProcessEvent{ + var tIDs []string + thEv := &engine.ArgsProcessEvent{ CGREvent: args.CGREvent, } - var rplyEv engine.AttrSProcessEventReply - if err = smg.attrS.Call(utils.AttributeSv1ProcessEvent, - attrArgs, &rplyEv); err != nil { - return utils.NewErrAttributeS(err) + if err := smg.thdS.Call(utils.ThresholdSv1ProcessEvent, thEv, &tIDs); err != nil && + err.Error() != utils.ErrNotFound.Error() { + utils.Logger.Warning( + fmt.Sprintf(" error: %s processing event %+v with ThresholdS.", err.Error(), thEv)) + } + } + if args.ProcessStats { + if smg.statS == nil { + return utils.NewErrNotConnected(utils.StatS) + } + var statReply []string + if err := smg.statS.Call(utils.StatSv1ProcessEvent, &engine.StatsArgsProcessEvent{CGREvent: args.CGREvent}, &statReply); err != nil && + err.Error() != utils.ErrNotFound.Error() { + utils.Logger.Warning( + fmt.Sprintf(" error: %s processing event %+v with StatS.", err.Error(), args.CGREvent)) } - rply.Attributes = &rplyEv } return nil } diff --git a/sessions/sessions_test.go b/sessions/sessions_test.go index def33ad71..7d47913cb 100755 --- a/sessions/sessions_test.go +++ b/sessions/sessions_test.go @@ -162,7 +162,7 @@ func TestSessionsNewV1ProcessEventArgs(t *testing.T) { GetAttributes: true, CGREvent: cgrEv, } - rply := NewV1ProcessEventArgs(true, true, true, cgrEv) + rply := NewV1ProcessEventArgs(true, true, true, false, false, cgrEv) if !reflect.DeepEqual(expected, rply) { t.Errorf("Expecting %+v, received: %+v", expected, rply) } @@ -171,7 +171,7 @@ func TestSessionsNewV1ProcessEventArgs(t *testing.T) { GetAttributes: true, CGREvent: cgrEv, } - rply = NewV1ProcessEventArgs(true, false, true, cgrEv) + rply = NewV1ProcessEventArgs(true, false, true, false, false, cgrEv) if !reflect.DeepEqual(expected, rply) { t.Errorf("Expecting %+v, received: %+v", expected, rply) } diff --git a/utils/consts.go b/utils/consts.go index 52fe315a1..20840fdc8 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -311,6 +311,7 @@ const ( HIERARCHY_SEP = ">" META_COMPOSED = "*composed" META_USAGE_DIFFERENCE = "*usage_difference" + MetaCCUsage = "*cc_usage" MetaString = "*string" NegativePrefix = "!" MatchStartPrefix = "^" @@ -519,6 +520,14 @@ const ( DynamicDataPrefix = "~" AttrValueSep = "=" ANDSep = "&" + PipeSep = "|" + MetaApp = "*app" + MetaAppID = "*appid" + MetaCmd = "*cmd" + MetaTemplate = "*template" + MetaCCA = "*cca" + OriginRealm = "OriginRealm" + ProductName = "ProductName" ) // Migrator Action diff --git a/utils/reflect.go b/utils/reflect.go index abd00bbe0..7744fbd87 100644 --- a/utils/reflect.go +++ b/utils/reflect.go @@ -21,6 +21,7 @@ package utils import ( "errors" "fmt" + "net" "reflect" "strconv" "time" @@ -202,10 +203,18 @@ func IfaceAsString(fld interface{}) (out string, err error) { return case int: return strconv.Itoa(fld.(int)), nil + case int32: + return strconv.FormatInt(int64(fld.(int32)), 10), nil case int64: return strconv.FormatInt(fld.(int64), 10), nil + case uint32: + return strconv.FormatUint(uint64(fld.(uint32)), 10), nil + case uint64: + return strconv.FormatUint(fld.(uint64), 10), nil case bool: return strconv.FormatBool(fld.(bool)), nil + case float32: + return strconv.FormatFloat(float64(fld.(float32)), 'f', -1, 64), nil case float64: return strconv.FormatFloat(fld.(float64), 'f', -1, 64), nil case []uint8: @@ -218,6 +227,8 @@ func IfaceAsString(fld interface{}) (out string, err error) { return fld.(time.Duration).String(), nil case time.Time: return fld.(time.Time).Format(time.RFC3339), nil + case net.IP: + return fld.(net.IP).String(), nil case string: return fld.(string), nil default: // Maybe we are lucky and the value converts to string diff --git a/utils/slice.go b/utils/slice.go index 6d8ebeae7..b7f77e1ba 100644 --- a/utils/slice.go +++ b/utils/slice.go @@ -83,3 +83,12 @@ func StripSlicePrefix(slc []string, nrItems int) []string { } return slc[nrItems:] } + +// SliceStringToIface converts slice of strings into a slice of interfaces +func SliceStringToIface(slc []string) (ifc []interface{}) { + ifc = make([]interface{}, len(slc)) + for i, itm := range slc { + ifc[i] = itm + } + return +}