mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
Diameter CCR and CCA message implementation, bidirectional communication between client and agent
This commit is contained in:
@@ -20,16 +20,12 @@ package agents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cgrates/cgrates/config"
|
||||
"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/dict"
|
||||
"github.com/fiorix/go-diameter/diam/sm"
|
||||
)
|
||||
|
||||
@@ -37,7 +33,7 @@ func NewDiameterAgent(cgrCfg *config.CGRConfig, smg *rpcclient.RpcClient) (*Diam
|
||||
da := &DiameterAgent{cgrCfg: cgrCfg, smg: smg}
|
||||
dictsDir := cgrCfg.DiameterAgentCfg().DictionariesDir
|
||||
if len(dictsDir) != 0 {
|
||||
if err := da.loadDictionaries(dictsDir); err != nil {
|
||||
if err := loadDictionaries(dictsDir, "DiameterAgent"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -70,44 +66,33 @@ func (self *DiameterAgent) handlers() diam.Handler {
|
||||
}
|
||||
|
||||
func (self *DiameterAgent) handleCCR(c diam.Conn, m *diam.Message) {
|
||||
utils.Logger.Warning(fmt.Sprintf("<DiameterAgent> Received CCR message from %s:\n%s", c.RemoteAddr(), m))
|
||||
//utils.Logger.Warning(fmt.Sprintf("<DiameterAgent> Received CCR message from %s:\n%s", c.RemoteAddr(), m))
|
||||
var ccr CCR
|
||||
if err := m.Unmarshal(&ccr); err != nil {
|
||||
utils.Logger.Err(fmt.Sprintf("<DiameterAgent> Unmarshaling message: %s, error: %s", m, err))
|
||||
return
|
||||
}
|
||||
ccr.diamMessage = m // Save it for later searches inside AVPs
|
||||
//utils.Logger.Debug(fmt.Sprintf("CCR unrmashaled: %+v", ccr))
|
||||
cca := NewCCAFromCCR(&ccr)
|
||||
cca.OriginHost = self.cgrCfg.DiameterAgentCfg().OriginHost
|
||||
cca.OriginRealm = self.cgrCfg.DiameterAgentCfg().OriginRealm
|
||||
cca.GrantedServiceUnit.CCTime = 300
|
||||
cca.ResultCode = diam.Success
|
||||
if dmtA, err := cca.AsDiameterMessage(); err != nil {
|
||||
utils.Logger.Err(fmt.Sprintf("<DiameterAgent> Failed to convert cca as diameter message, error: %s", err.Error()))
|
||||
return
|
||||
} else if _, err := dmtA.WriteTo(c); err != nil {
|
||||
utils.Logger.Err(fmt.Sprintf("<DiameterAgent> Failed to write message to %s: %s\n%s\n", c.RemoteAddr(), err, dmtA))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (self *DiameterAgent) handleALL(c diam.Conn, m *diam.Message) {
|
||||
utils.Logger.Warning(fmt.Sprintf("<DiameterAgent> Received unexpected message from %s:\n%s", c.RemoteAddr(), m))
|
||||
}
|
||||
|
||||
func (self *DiameterAgent) loadDictionaries(dictsDir string) error {
|
||||
fi, err := os.Stat(dictsDir)
|
||||
if err != nil {
|
||||
if strings.HasSuffix(err.Error(), "no such file or directory") {
|
||||
return fmt.Errorf("<DiameterAgent> Invalid dictionaries folder: <%s>", dictsDir)
|
||||
}
|
||||
return err
|
||||
} else if !fi.IsDir() { // If config dir defined, needs to exist
|
||||
return fmt.Errorf("<DiameterAgent> 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 {
|
||||
if err := dict.Default.LoadFile(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (self *DiameterAgent) ListenAndServe() error {
|
||||
return diam.ListenAndServe(self.cgrCfg.DiameterAgentCfg().Listen, self.handlers(), nil)
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ func TestDmtAgentSendCCR(t *testing.T) {
|
||||
if !*testIntegration {
|
||||
return
|
||||
}
|
||||
dmtClient, err := NewDiameterClient(daCfg.DiameterAgentCfg().Listen, "UNIT_TEST",
|
||||
daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId, daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION)
|
||||
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)
|
||||
}
|
||||
@@ -94,10 +94,14 @@ func TestDmtAgentSendCCR(t *testing.T) {
|
||||
AccId: "dsafdsaf", CdrHost: "192.168.1.1", CdrSource: utils.UNIT_TEST, ReqType: utils.META_RATED, Direction: "*out",
|
||||
Tenant: "cgrates.org", Category: "call", Account: "1001", Subject: "1001", Destination: "1002", Supplier: "SUPPL1",
|
||||
SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: utils.DEFAULT_RUNID,
|
||||
Usage: time.Duration(10) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", Rated: true,
|
||||
Usage: time.Duration(10) * time.Second, Pdd: time.Duration(7) * time.Second, ExtraFields: map[string]string{"Service-Context-Id": "voice@huawei.com"}, Cost: 1.01, RatedAccount: "dan", RatedSubject: "dans", Rated: true,
|
||||
}
|
||||
m := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId,
|
||||
ccr := storedCdrToCCR(cdr, "UNIT_TEST", daCfg.DiameterAgentCfg().OriginRealm, daCfg.DiameterAgentCfg().VendorId,
|
||||
daCfg.DiameterAgentCfg().ProductName, utils.DIAMETER_FIRMWARE_REVISION, time.Duration(300)*time.Second, true)
|
||||
m, err := ccr.AsDiameterMessage()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := dmtClient.SendMessage(m); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -19,15 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
package agents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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/sm"
|
||||
)
|
||||
|
||||
func NewDiameterClient(addr, originHost, originRealm string, vendorId int, productName string, firmwareRev int) (*DiameterClient, error) {
|
||||
func NewDiameterClient(addr, originHost, originRealm string, vendorId int, productName string, firmwareRev int, dictsDir string) (*DiameterClient, error) {
|
||||
cfg := &sm.Settings{
|
||||
OriginHost: datatype.DiameterIdentity(originHost),
|
||||
OriginRealm: datatype.DiameterIdentity(originRealm),
|
||||
@@ -35,24 +37,34 @@ func NewDiameterClient(addr, originHost, originRealm string, vendorId int, produ
|
||||
ProductName: datatype.UTF8String(productName),
|
||||
FirmwareRevision: datatype.Unsigned32(firmwareRev),
|
||||
}
|
||||
handlers := sm.New(cfg)
|
||||
dSM := sm.New(cfg)
|
||||
go func() {
|
||||
for err := range dSM.ErrorReports() {
|
||||
utils.Logger.Err(fmt.Sprintf("<DiameterClient> StateMachine error: %+v", err))
|
||||
}
|
||||
}()
|
||||
cli := &sm.Client{
|
||||
Handler: handlers,
|
||||
Handler: dSM,
|
||||
MaxRetransmits: 3,
|
||||
RetransmitInterval: time.Second,
|
||||
EnableWatchdog: true,
|
||||
WatchdogInterval: 5 * time.Second,
|
||||
AcctApplicationID: []*diam.AVP{
|
||||
// Advertise that we want support for both
|
||||
// Accounting applications 4 and 999.
|
||||
diam.NewAVP(avp.AcctApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)), // RFC 4006
|
||||
},
|
||||
}
|
||||
if len(dictsDir) != 0 {
|
||||
if err := loadDictionaries(dictsDir, "DiameterClient"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
conn, err := cli.Dial(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DiameterClient{conn: conn, handlers: handlers}, nil
|
||||
dc := &DiameterClient{conn: conn, handlers: dSM}
|
||||
dSM.HandleFunc("ALL", dc.handleALL)
|
||||
return dc, nil
|
||||
}
|
||||
|
||||
type DiameterClient struct {
|
||||
@@ -64,3 +76,7 @@ func (self *DiameterClient) SendMessage(m *diam.Message) error {
|
||||
_, err := m.WriteTo(self.conn)
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *DiameterClient) handleALL(c diam.Conn, m *diam.Message) {
|
||||
utils.Logger.Warning(fmt.Sprintf("<DiameterClient> Received unexpected message from %s:\n%s", c.RemoteAddr(), m))
|
||||
}
|
||||
|
||||
335
agents/libdmt.go
335
agents/libdmt.go
@@ -23,22 +23,60 @@ Build various type of packets here
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
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("<DiameterAgent> Invalid dictionaries folder: <%s>", dictsDir)
|
||||
}
|
||||
return err
|
||||
} else if !fi.IsDir() { // If config dir defined, needs to exist
|
||||
return fmt.Errorf("<DiameterAgent> 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) (int, int, int) {
|
||||
usageSecs := usage.Seconds()
|
||||
@@ -61,7 +99,7 @@ func disectUsageForCCR(usage time.Duration, debitInterval time.Duration, callEnd
|
||||
return reqType, reqNr, int(ccTime)
|
||||
}
|
||||
|
||||
func getUsageFromCCR(reqType, reqNr, ccTime int, debitIterval time.Duration) time.Duration {
|
||||
func usageFromCCR(reqType, reqNr, ccTime int, debitIterval time.Duration) time.Duration {
|
||||
dISecs := debitIterval.Seconds()
|
||||
if reqType == 3 {
|
||||
reqNr -= 1 // decrease request number to reach the real number
|
||||
@@ -70,56 +108,265 @@ func getUsageFromCCR(reqType, reqNr, ccTime int, debitIterval time.Duration) tim
|
||||
return time.Duration(ccTime) * time.Second
|
||||
}
|
||||
|
||||
func storedCdrToCCR(cdr *engine.StoredCdr, originHost, originRealm string, vendorId int, productName string, firmwareRev int, debitInterval time.Duration, callEnded bool) *diam.Message {
|
||||
// Utility function to convert from StoredCdr to CCR struct
|
||||
func storedCdrToCCR(cdr *engine.StoredCdr, originHost, originRealm string, vendorId int, productName string,
|
||||
firmwareRev int, debitInterval time.Duration, callEnded bool) *CCR {
|
||||
sid := "session;" + strconv.Itoa(int(rand.Uint32()))
|
||||
reqType, reqNr, ccTime := disectUsageForCCR(cdr.Usage, debitInterval, callEnded)
|
||||
m := diam.NewRequest(272, 4, nil)
|
||||
m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String(sid))
|
||||
m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity(originHost))
|
||||
m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity(originRealm))
|
||||
m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity(originHost))
|
||||
m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity(originRealm))
|
||||
m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4))
|
||||
m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("voice@huawei.com"))
|
||||
m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(reqType))
|
||||
m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Enumerated(reqNr))
|
||||
m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(cdr.AnswerTime))
|
||||
m.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(cdr.Account)),
|
||||
}})
|
||||
m.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("20921006232651")),
|
||||
}})
|
||||
m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0))
|
||||
m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(avp.CCTime, avp.Mbit, 0, datatype.Unsigned32(ccTime))}})
|
||||
ccr := &CCR{SessionId: sid, 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.RequestedServiceUnit.CCTime = ccTime
|
||||
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
|
||||
}
|
||||
|
||||
// CallControl Request
|
||||
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"`
|
||||
ServiceIdentifier int `avp:"Service-Identifier"`
|
||||
RequestedServiceUnit struct {
|
||||
CCTime int `avp:"CC-Time"`
|
||||
} `avp:"Requested-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
|
||||
}
|
||||
|
||||
// ToDo: do it with reflect in the future
|
||||
func (self *CCR) AsDiameterMessage() (*diam.Message, error) {
|
||||
m := diam.NewRequest(diam.CreditControl, 4, nil)
|
||||
if _, err := m.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String(self.SessionId)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Origin-Host", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Origin-Realm", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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("Auth-Application-Id", avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)); 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("CC-Request-Type", avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("CC-Request-Number", avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Event-Timestamp", avp.Mbit, 0, datatype.Time(self.EventTimestamp)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
/*
|
||||
m.NewAVP(avp.ServiceInformation, avp.Mbit, 0, &diam.GroupedAVP{
|
||||
subscriptionIdType, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Subscription-Id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscriptionIdData, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Subscription-Id-Data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(subscriptionIdType.Code, avp.Mbit, 0, datatype.Enumerated(0)),
|
||||
diam.NewAVP(subscriptionIdData.Code, avp.Mbit, 0, datatype.UTF8String(cdr.Account)),
|
||||
}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Subscription-Id", avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(subscriptionIdType.Code, avp.Mbit, 0, datatype.Enumerated(1)),
|
||||
diam.NewAVP(subscriptionIdData.Code, avp.Mbit, 0, datatype.UTF8String("20921006232651")),
|
||||
}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
if _, err := m.NewAVP("Service-Identifier", avp.Mbit, 0, datatype.Unsigned32(self.ServiceIdentifier)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ccTimeAvp, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "CC-Time")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Requested-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(20300, avp.Mbit, 0, &diam.GroupedAVP{ // IN-Information
|
||||
diam.NewAVP(ccTimeAvp.Code, avp.Mbit, 0, datatype.Unsigned32(self.RequestedServiceUnit.CCTime))}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inInformation, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "IN-Information")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
callingPartyAddress, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Calling-Party-Address")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
calledPartyAddress, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Called-Party-Address")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
realCalledNumber, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Real-Called-Number")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chargeFlowType, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Charge-Flow-Type")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
callingVlrNumber, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Calling-Vlr-Number")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
callingCellIdOrSai, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Calling-CellID-Or-SAI")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bearerCapability, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Bearer-Capability")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
callReferenceNumber, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Call-Reference-Number")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mscAddress, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "MSC-Address")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timeZone, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Time-Zone")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
calledPartyNP, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Called-Party-NP")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sspTime, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "SSP-Time")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := m.NewAVP("Service-Information", avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(inInformation.Code, avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(avp.CallingPartyAddress, avp.Mbit, 0, datatype.UTF8String(cdr.Account)),
|
||||
diam.NewAVP(avp.CalledPartyAddress, avp.Mbit, 0, datatype.UTF8String(cdr.Destination)),
|
||||
diam.NewAVP(20327, avp.Mbit, 0, datatype.UTF8String(cdr.Destination)), // Real-Called-Number
|
||||
diam.NewAVP(20339, avp.Mbit, 0, datatype.Unsigned32(0)), // Charge-Flow-Type
|
||||
diam.NewAVP(20302, avp.Mbit, 0, datatype.UTF8String("33657954968")), // Calling-Vlr-Number
|
||||
diam.NewAVP(20303, avp.Mbit, 0, datatype.UTF8String("31901485301525")), // Calling-CellID-Or-SAI
|
||||
diam.NewAVP(avp.BearerCapability, avp.Mbit, 0, datatype.UTF8String("31901485301525")),
|
||||
diam.NewAVP(20321, avp.Mbit, 0, datatype.UTF8String("31901485301525")), // Call-Reference-Number
|
||||
diam.NewAVP(avp.MSCAddress, avp.Mbit, 0, datatype.UTF8String("")),
|
||||
diam.NewAVP(20324, avp.Mbit, 0, datatype.UTF8String("0")), // Time-Zone
|
||||
diam.NewAVP(20385, avp.Mbit, 0, datatype.UTF8String("")), // Called-Party-NP
|
||||
diam.NewAVP(20386, avp.Mbit, 0, datatype.UTF8String("20091020120101")), // SSP-Time
|
||||
diam.NewAVP(callingPartyAddress.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.CallingPartyAddress)),
|
||||
diam.NewAVP(calledPartyAddress.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.CalledPartyAddress)),
|
||||
diam.NewAVP(realCalledNumber.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.RealCalledNumber)),
|
||||
diam.NewAVP(chargeFlowType.Code, avp.Mbit, 0, datatype.Unsigned32(self.ServiceInformation.INInformation.ChargeFlowType)),
|
||||
diam.NewAVP(callingVlrNumber.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.CallingVlrNumber)),
|
||||
diam.NewAVP(callingCellIdOrSai.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.CallingCellIDOrSAI)),
|
||||
diam.NewAVP(bearerCapability.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.BearerCapability)),
|
||||
diam.NewAVP(callReferenceNumber.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.CallReferenceNumber)),
|
||||
diam.NewAVP(mscAddress.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.MSCAddress)),
|
||||
diam.NewAVP(timeZone.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.TimeZone)),
|
||||
diam.NewAVP(calledPartyNP.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.CalledPartyNP)),
|
||||
diam.NewAVP(sspTime.Code, avp.Mbit, 0, datatype.UTF8String(self.ServiceInformation.INInformation.SSPTime)),
|
||||
},
|
||||
}),
|
||||
}})
|
||||
*/
|
||||
return m
|
||||
}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func NewCCAFromCCR(ccr *CCR) *CCA {
|
||||
return &CCA{SessionId: ccr.SessionId, AuthApplicationId: ccr.AuthApplicationId, CCRequestType: ccr.CCRequestType, CCRequestNumber: ccr.CCRequestNumber,
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
// Call Control Answer
|
||||
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"`
|
||||
diamMessage *diam.Message
|
||||
}
|
||||
|
||||
// Converts itself into DiameterMessage
|
||||
func (self *CCA) AsDiameterMessage() (*diam.Message, error) {
|
||||
if _, err := self.diamMessage.NewAVP("Session-Id", avp.Mbit, 0, datatype.UTF8String(self.SessionId)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP("Origin-Host", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginHost)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP("Origin-Realm", avp.Mbit, 0, datatype.DiameterIdentity(self.OriginRealm)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP("Auth-Application-Id", avp.Mbit, 0, datatype.Unsigned32(self.AuthApplicationId)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP("CC-Request-Type", avp.Mbit, 0, datatype.Enumerated(self.CCRequestType)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP("CC-Request-Number", avp.Mbit, 0, datatype.Enumerated(self.CCRequestNumber)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP(avp.ResultCode, avp.Mbit, 0, datatype.Unsigned32(self.ResultCode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ccTimeAvp, err := self.diamMessage.Dictionary().FindAVP(self.diamMessage.Header.ApplicationID, "CC-Time")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := self.diamMessage.NewAVP("Granted-Service-Unit", avp.Mbit, 0, &diam.GroupedAVP{
|
||||
AVP: []*diam.AVP{
|
||||
diam.NewAVP(ccTimeAvp.Code, avp.Mbit, 0, datatype.Unsigned32(self.GrantedServiceUnit.CCTime))}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.diamMessage, nil
|
||||
|
||||
}
|
||||
|
||||
// Extracts the value out of a specific field in diameter message, able to go into multiple layers in the form of field1>field2>field3
|
||||
|
||||
@@ -42,20 +42,20 @@ func TestDisectUsageForCCR(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestGetUsageFromCCR(t *testing.T) {
|
||||
if usage := getUsageFromCCR(1, 0, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second {
|
||||
func TestUsageFromCCR(t *testing.T) {
|
||||
if usage := usageFromCCR(1, 0, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second {
|
||||
t.Error(usage)
|
||||
}
|
||||
if usage := getUsageFromCCR(2, 0, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second {
|
||||
if usage := usageFromCCR(2, 0, 300, time.Duration(300)*time.Second); usage != time.Duration(300)*time.Second {
|
||||
t.Error(usage)
|
||||
}
|
||||
if usage := getUsageFromCCR(2, 3, 300, time.Duration(300)*time.Second); usage != time.Duration(1200)*time.Second {
|
||||
if usage := usageFromCCR(2, 3, 300, time.Duration(300)*time.Second); usage != time.Duration(1200)*time.Second {
|
||||
t.Error(usage)
|
||||
}
|
||||
if usage := getUsageFromCCR(3, 4, 35, time.Duration(300)*time.Second); usage != time.Duration(935)*time.Second {
|
||||
if usage := usageFromCCR(3, 4, 35, time.Duration(300)*time.Second); usage != time.Duration(935)*time.Second {
|
||||
t.Error(usage)
|
||||
}
|
||||
if usage := getUsageFromCCR(3, 1, 35, time.Duration(300)*time.Second); usage != time.Duration(35)*time.Second {
|
||||
if usage := usageFromCCR(3, 1, 35, time.Duration(300)*time.Second); usage != time.Duration(35)*time.Second {
|
||||
t.Error(usage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ const CGRATES_CFG_JSON = `
|
||||
"dictionaries_dir": "/usr/share/cgrates/diameter/dict/", // path towards directory holding additional dictionaries to load
|
||||
"sm_generic": "internal", // connection towards SMG component for session management
|
||||
"timezone": "", // timezone for timestamps where not specified, empty for general defaults <""|UTC|Local|$IANA_TZ_DB>
|
||||
"origin_host": "diameter-agent", // diameter Origin-Host AVP used in replies
|
||||
"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
|
||||
|
||||
@@ -421,7 +421,7 @@ func TestDiameterAgentJsonCfg(t *testing.T) {
|
||||
Dictionaries_dir: utils.StringPointer("/usr/share/cgrates/diameter/dict/"),
|
||||
Sm_generic: utils.StringPointer("internal"),
|
||||
Timezone: utils.StringPointer(""),
|
||||
Origin_host: utils.StringPointer("diameter-agent"),
|
||||
Origin_host: utils.StringPointer("CGR-DA"),
|
||||
Origin_realm: utils.StringPointer("cgrates.org"),
|
||||
Vendor_id: utils.IntPointer(0),
|
||||
Product_name: utils.StringPointer("CGRateS"),
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"diameter_agent": {
|
||||
"enabled": true, // enables the diameter agent: <true|false>
|
||||
"listen": "127.0.0.1:3868", // address where to listen for diameter requests <x.y.z.y:1234>
|
||||
//"dictionaries_dir": "", // path towards directory holding additional dictionaries to load
|
||||
"request_processors": [
|
||||
{
|
||||
"id": "*default", // Identifier of this processor
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<diameter>
|
||||
<application id="4">
|
||||
<vendor id="12645" name="Vodafone" />
|
||||
<avp name="Reporting-Reason" code="261" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="Enumerated">
|
||||
<item code="0" name="THRESHOLD" />
|
||||
<item code="1" name="QHT" />
|
||||
<item code="2" name="FINAL" />
|
||||
<item code="3" name="QUOTA_EXHAUSTED" />
|
||||
<item code="4" name="VALIDITY_TIME" />
|
||||
<item code="5" name="OTHER_QUOTA_TYPE" />
|
||||
<item code="6" name="RATING_CONDITION_CHANGE" />
|
||||
<item code="7" name="FORCED_REAUTHORISATION" />
|
||||
<item code="8" name="POOL_EXHAUSTED" />
|
||||
</data>
|
||||
</avp>
|
||||
<avp name="Quota-Holding-Time" code="258" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="Unsigned32" />
|
||||
</avp>
|
||||
<avp name="Quota-Consumption-Time" code="257" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="Unsigned32" />
|
||||
</avp>
|
||||
<avp name="Rulebase-Id" code="262" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="UTF8String" />
|
||||
</avp>
|
||||
<avp name="Rulebase-Id" code="262" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="UTF8String" />
|
||||
</avp>
|
||||
<avp name="Context-Type" code="256" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="Enumerated">
|
||||
<item code="0" name="PRIMARY" />
|
||||
<item code="1" name="SECONDARY" />
|
||||
</data>
|
||||
</avp>
|
||||
<avp name="Radio-Access-Technology" code="260" must="V" may="P,M" must-not="-" may-encrypt="N">
|
||||
<data type="Enumerated">
|
||||
<item code="1" name="UTRAN" />
|
||||
<item code="2" name="GERAN" />
|
||||
<item code="3" name="WLAN" />
|
||||
</data>
|
||||
</avp>
|
||||
<avp name="User-Location-Information" code="267" must="M" may="-" must-not="V" may-encrypt="Y">
|
||||
<data type="OctetString" />
|
||||
</avp>
|
||||
<avp name="Time-Of-First-Usage" code="263" must="M" may="-" must-not="V" may-encrypt="Y">
|
||||
<data type="Time" />
|
||||
</avp>
|
||||
<avp name="Time-Of-Last-Usage" code="266" must="M" may="-" must-not="V" may-encrypt="Y">
|
||||
<data type="Time" />
|
||||
</avp>
|
||||
</application>
|
||||
</diameter>
|
||||
Reference in New Issue
Block a user