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