mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-17 22:29:55 +05:00
Diameter integration tests infrastructure added back
This commit is contained in:
186
agents/diam_it_test.go
Normal file
186
agents/diam_it_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// +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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
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 dmtClient *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) {
|
||||
dmtClient, 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", "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 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 := dmtClient.SendMessage(ccr); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
msg := dmtClient.ReceivedMessage(rplyTimeout)
|
||||
if msg == nil {
|
||||
t.Fatal("No message returned")
|
||||
}
|
||||
avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(avps) == 0 {
|
||||
t.Error("Result-Code")
|
||||
}
|
||||
eVal := "300"
|
||||
if val, err := diamAVPAsString(avps[0]); err != nil {
|
||||
t.Error(err)
|
||||
} else if val != eVal {
|
||||
t.Errorf("expecting: %s, received: <%s>", eVal, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,13 +133,13 @@ func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) {
|
||||
utils.Logger.Warning(
|
||||
fmt.Sprintf("<%s> error: %s processing message: %s",
|
||||
utils.DiameterAgent, err.Error(), m))
|
||||
writeOnConn(c, m.Answer(diam.UnableToDeliver))
|
||||
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.UnableToDeliver))
|
||||
writeOnConn(c, m.Answer(diam.UnableToComply))
|
||||
return
|
||||
}
|
||||
a := m.Answer(diam.Success)
|
||||
@@ -150,7 +150,7 @@ func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) {
|
||||
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.UnableToDeliver))
|
||||
writeOnConn(c, m.Answer(diam.UnableToComply))
|
||||
return
|
||||
}
|
||||
// find out the first itm which is not an attribute
|
||||
@@ -170,7 +170,7 @@ func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) {
|
||||
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.UnableToDeliver))
|
||||
writeOnConn(c, m.Answer(diam.UnableToComply))
|
||||
return
|
||||
}
|
||||
var apnd bool
|
||||
@@ -182,11 +182,11 @@ func (da *DiameterAgent) handleMessage(c diam.Conn, m *diam.Message) {
|
||||
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.UnableToDeliver))
|
||||
writeOnConn(c, m.Answer(diam.UnableToComply))
|
||||
return
|
||||
}
|
||||
}
|
||||
writeOnConn(c, m.Answer(diam.UnableToDeliver))
|
||||
writeOnConn(c, m.Answer(diam.UnableToComply))
|
||||
}
|
||||
|
||||
func (da *DiameterAgent) processRequest(reqProcessor *config.DARequestProcessor,
|
||||
@@ -216,7 +216,7 @@ func (da *DiameterAgent) processRequest(reqProcessor *config.DARequestProcessor,
|
||||
case utils.MetaDryRun:
|
||||
utils.Logger.Info(
|
||||
fmt.Sprintf("<%s> DRY_RUN, processorID: %s, CGREvent: %s",
|
||||
utils.DiameterAgent, reqProcessor.Id, utils.ToJSON(cgrEv)))
|
||||
utils.DiameterAgent, reqProcessor.ID, utils.ToJSON(cgrEv)))
|
||||
case utils.MetaAuth:
|
||||
authArgs := sessions.NewV1AuthorizeArgs(
|
||||
reqProcessor.Flags.HasKey(utils.MetaAttributes),
|
||||
@@ -21,7 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package agents
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -43,8 +42,6 @@ var (
|
||||
haRPC *rpc.Client
|
||||
httpC *http.Client // so we can cache the connection
|
||||
err error
|
||||
dataDir = flag.String("data_dir", "/usr/share/cgrates", "CGR data dir path here")
|
||||
waitRater = flag.Int("wait_rater", 100, "Number of miliseconds to wait for rater to start and cache")
|
||||
)
|
||||
|
||||
func TestHAitInitCfg(t *testing.T) {
|
||||
|
||||
@@ -110,6 +110,14 @@ func diamAVPAsIface(dAVP *diam.AVP) (val interface{}, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -82,7 +82,7 @@ func (da *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) (err e
|
||||
rp := new(DARequestProcessor)
|
||||
var haveID bool
|
||||
for _, rpSet := range da.RequestProcessors {
|
||||
if reqProcJsn.Id != nil && rpSet.Id == *reqProcJsn.Id {
|
||||
if reqProcJsn.Id != nil && rpSet.ID == *reqProcJsn.Id {
|
||||
rp = rpSet // Will load data into the one set
|
||||
haveID = true
|
||||
break
|
||||
@@ -101,7 +101,7 @@ func (da *DiameterAgentCfg) loadFromJsonCfg(jsnCfg *DiameterAgentJsonCfg) (err e
|
||||
|
||||
// One Diameter request processor configuration
|
||||
type DARequestProcessor struct {
|
||||
Id string
|
||||
ID string
|
||||
Tenant RSRParsers
|
||||
Filters []string
|
||||
Flags utils.StringMap
|
||||
@@ -116,7 +116,7 @@ func (dap *DARequestProcessor) loadFromJsonCfg(jsnCfg *DARequestProcessorJsnCfg)
|
||||
return nil
|
||||
}
|
||||
if jsnCfg.Id != nil {
|
||||
dap.Id = *jsnCfg.Id
|
||||
dap.ID = *jsnCfg.Id
|
||||
}
|
||||
if jsnCfg.Tenant != nil {
|
||||
dap.Tenant = NewRSRParsersMustCompile(*jsnCfg.Tenant, true)
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
|
||||
"cdrs": {
|
||||
"enabled": true,
|
||||
"cdrstats_conns": [
|
||||
{"address": "*internal"}
|
||||
],
|
||||
},
|
||||
|
||||
"attributes": {
|
||||
@@ -3,100 +3,6 @@
|
||||
<application id="4" type="auth">
|
||||
<!-- Diameter Credit Control Application -->
|
||||
<!-- http://tools.ietf.org/html/rfc4006 -->
|
||||
<command code="272" short="CC" name="Credit-Control">
|
||||
<request>
|
||||
<!-- http://tools.ietf.org/html/rfc4006#section-3.1 -->
|
||||
<rule avp="Session-Id" required="true" max="1" />
|
||||
<rule avp="Origin-Host" required="true" max="1" />
|
||||
<rule avp="Origin-Realm" required="true" max="1" />
|
||||
<rule avp="Destination-Host" required="false" max="1" />
|
||||
<rule avp="Destination-Realm" required="true" max="1" />
|
||||
<rule avp="Auth-Application-Id" required="true" max="1" />
|
||||
<rule avp="Service-Context-Id" required="true" max="1" />
|
||||
<rule avp="CC-Request-Type" required="true" max="1" />
|
||||
<rule avp="CC-Request-Number" required="true" max="1" />
|
||||
<rule avp="Event-Timestamp" required="false" max="1" />
|
||||
<rule avp="Subscription-Id" required="false" max="2" />
|
||||
<rule avp="Service-Identifier" required="false" max="1" />
|
||||
<rule avp="Termination-Cause" required="false" max="1" />
|
||||
<rule avp="Requested-Service-Unit" required="false" max="1" />
|
||||
<rule avp="Used-Service-Unit" required="false" max="10" />
|
||||
<rule avp="Service-Information" required="false" max="1" />
|
||||
<rule avp="Called-Station-Id" required="false" max="1" />
|
||||
<rule avp="3GPP-Charging-Characteristics" required="false" max="1" />
|
||||
<rule avp="3GPP-Charging-ID" required="false" max="1" />
|
||||
<rule avp="3GPP-QoS-Negotiated-Profile" required="false" max="1" />
|
||||
<rule avp="3GPP-IMSI" required="false" max="1" />
|
||||
<rule avp="3GPP-NSAPI" required="false" max="1" />
|
||||
<rule avp="3GPP-Selection-Mode" required="false" max="1" />
|
||||
<rule avp="Rulebase-Id" required="false" max="1" />
|
||||
<rule avp="Session-Start-Indicator" required="false" max="1" />
|
||||
<rule avp="Framed-IP-Address" required="false" max="1" />
|
||||
<rule avp="Framed-IPv6-Prefix" required="false" max="10" />
|
||||
<rule avp="Framed-Interface-ID" required="false" max="1" />
|
||||
<rule avp="3GPP-MS-TimeZone" required="false" max="1" />
|
||||
<rule avp="3GPP-CG-Address" required="false" max="1" />
|
||||
<rule avp="3GPP-SGSN-Address" required="false" max="1" />
|
||||
<rule avp="3GPP-GGSN-Address" required="false" max="1" />
|
||||
<rule avp="3GPP-IMSI-MCC-MNC" required="false" max="1" />
|
||||
<rule avp="3GPP-SGSN-MCC-MNC" required="false" max="1" />
|
||||
<rule avp="3GPP-GGSN-MCC-MNC" required="false" max="1" />
|
||||
<rule avp="3GPP-CG-IPv6-Address" required="false" max="1" />
|
||||
<rule avp="3GPP-GGSN-IPv6-Address" required="false" max="1" />
|
||||
<rule avp="3GPP-PDP-Type" required="false" max="1" />
|
||||
<rule avp="3GPP-Session-Stop-Indicator" required="false" max="1" />
|
||||
<rule avp="3GPP-SGSN-IPv6-Address" required="false" max="1" />
|
||||
<rule avp="Context-Type" required="false" max="1" />
|
||||
<rule avp="User-Location-Information" required="false" max="1" />
|
||||
<rule avp="Radio-Access-Technology" required="false" max="1" />
|
||||
<rule avp="User-Name" required="false" max="1" />
|
||||
<rule avp="CC-Sub-Session-Id" required="false" max="1" />
|
||||
<rule avp="Acct-Multi-Session-Id" required="false" max="1" />
|
||||
<rule avp="Origin-State-Id" required="false" max="1" />
|
||||
<rule avp="Requested-Action" required="false" max="1" />
|
||||
<rule avp="Multiple-Services-Indicator" required="false" max="1" />
|
||||
<rule avp="Multiple-Services-Credit-Control" required="false" max="10" />
|
||||
<rule avp="Service-Parameter-Info" required="false" max="1" />
|
||||
<rule avp="CC-Correlation-Id" required="false" max="1" />
|
||||
<rule avp="User-Equipment-Info" required="false" max="1" />
|
||||
<rule avp="Proxy-Info" required="false" max="1" />
|
||||
<rule avp="Route-Record" required="false" max="1" />
|
||||
</request>
|
||||
<answer>
|
||||
<!-- http://tools.ietf.org/html/rfc4006#section-3.2 -->
|
||||
<rule avp="Session-Id" required="true" max="1" />
|
||||
<rule avp="Result-Code" required="true" max="1" />
|
||||
<rule avp="Origin-Host" required="true" max="1" />
|
||||
<rule avp="Origin-Realm" required="true" max="1" />
|
||||
<rule avp="Auth-Application-Id" required="false" max="1" />
|
||||
<rule avp="CC-Request-Type" required="true" max="1" />
|
||||
<rule avp="CC-Request-Number" required="true" max="1" />
|
||||
<rule avp="Event-Timestamp" required="false" max="1" />
|
||||
<rule avp="Granted-Service-Unit" required="false" max="1" />
|
||||
<rule avp="Final-Unit-Indication" required="false" max="1" />
|
||||
<rule avp="Service-Information" required="false" max="1" />
|
||||
<rule avp="User-Name" required="false" max="1" />
|
||||
<rule avp="CC-Session-Failover" required="false" max="1" />
|
||||
<rule avp="CC-Sub-Session-Id" required="false" max="1" />
|
||||
<rule avp="Acct-Multi-Session-Id" required="false" max="1" />
|
||||
<rule avp="Origin-State-Id" required="false" max="1" />
|
||||
<rule avp="Multiple-Services-Credit-Control" required="false" max="10" />
|
||||
<rule avp="Cost-Information" required="false" max="1" />
|
||||
<rule avp="Check-Balance-Result" required="false" max="1" />
|
||||
<rule avp="Credit-Control-Failure-Handling" required="false" max="1" />
|
||||
<rule avp="Direct-Debiting-Failure-Handling" required="false" max="1" />
|
||||
<rule avp="Validity-Time" required="false" max="1" />
|
||||
<rule avp="Redirect-Host" required="false" max="1" />
|
||||
<rule avp="Redirect-Host-Usage" required="false" max="1" />
|
||||
<rule avp="Redirect-Max-Cache-Time" required="false" max="1" />
|
||||
<rule avp="Proxy-Info" required="false" max="1" />
|
||||
<rule avp="Route-Record" required="false" max="1" />
|
||||
<rule avp="Failed-AVP" required="false" max="10" />
|
||||
<rule avp="CC-Session-Failover" required="false" max="1" />
|
||||
<rule avp="Subscription-Id" required="false" max="2" />
|
||||
<rule avp="Rulebase-Id" required="false" max="1" />
|
||||
</answer>
|
||||
</command>
|
||||
<avp name="CC-Correlation-Id" code="411" must="-" may="P,M" must-not="V" may-encrypt="Y">
|
||||
<!-- http://tools.ietf.org/html/rfc4006#section-8.1 -->
|
||||
<data type="OctetString" />
|
||||
|
||||
@@ -244,7 +244,7 @@ func IfaceAsString(fld interface{}) (out string, err error) {
|
||||
case int64:
|
||||
return strconv.FormatInt(fld.(int64), 10), nil
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(fld.(int32)), 10), nil
|
||||
return strconv.FormatUint(uint64(fld.(uint32)), 10), nil
|
||||
case uint64:
|
||||
return strconv.FormatUint(fld.(uint64), 10), nil
|
||||
case bool:
|
||||
|
||||
Reference in New Issue
Block a user