From 0a1a458c96b3b6fb08166d7ee3511cc4e4f4c5f6 Mon Sep 17 00:00:00 2001 From: DanB Date: Sun, 22 Nov 2015 21:07:18 +0100 Subject: [PATCH] Diameter CCR and CCA message implementation, bidirectional communication between client and agent --- agents/dmtagent.go | 59 ++--- agents/dmtagent_it_test.go | 12 +- agents/dmtclient.go | 28 +- agents/libdmt.go | 335 ++++++++++++++++++++---- agents/libdmt_test.go | 12 +- config/config_defaults.go | 2 +- config/config_json_test.go | 2 +- data/conf/samples/dmtagent/cgrates.json | 1 + data/diameter/dict/huawei/vodafone.xml | 53 ---- 9 files changed, 352 insertions(+), 152 deletions(-) delete mode 100644 data/diameter/dict/huawei/vodafone.xml diff --git a/agents/dmtagent.go b/agents/dmtagent.go index c10281a73..fd635af3b 100644 --- a/agents/dmtagent.go +++ b/agents/dmtagent.go @@ -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(" Received CCR message from %s:\n%s", c.RemoteAddr(), m)) + //utils.Logger.Warning(fmt.Sprintf(" 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(" 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(" 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(" 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(" 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(" 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 { - 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) } diff --git a/agents/dmtagent_it_test.go b/agents/dmtagent_it_test.go index fa53f5755..a84ac0fbb 100644 --- a/agents/dmtagent_it_test.go +++ b/agents/dmtagent_it_test.go @@ -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) } diff --git a/agents/dmtclient.go b/agents/dmtclient.go index c3343379a..36f3320a1 100644 --- a/agents/dmtclient.go +++ b/agents/dmtclient.go @@ -19,15 +19,17 @@ along with this program. If not, see 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(" 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(" Received unexpected message from %s:\n%s", c.RemoteAddr(), m)) +} diff --git a/agents/libdmt.go b/agents/libdmt.go index bcc861e51..320e82c85 100644 --- a/agents/libdmt.go +++ b/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(" 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) (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 diff --git a/agents/libdmt_test.go b/agents/libdmt_test.go index 00d5864c8..90862711e 100644 --- a/agents/libdmt_test.go +++ b/agents/libdmt_test.go @@ -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) } } diff --git a/config/config_defaults.go b/config/config_defaults.go index 4abe84d98..64ac5d0f8 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -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 diff --git a/config/config_json_test.go b/config/config_json_test.go index c641ff1d9..a6091c107 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -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"), diff --git a/data/conf/samples/dmtagent/cgrates.json b/data/conf/samples/dmtagent/cgrates.json index b60803e90..5a71f17fb 100644 --- a/data/conf/samples/dmtagent/cgrates.json +++ b/data/conf/samples/dmtagent/cgrates.json @@ -54,6 +54,7 @@ "diameter_agent": { "enabled": true, // enables the diameter agent: "listen": "127.0.0.1:3868", // address where to listen for diameter requests + //"dictionaries_dir": "", // path towards directory holding additional dictionaries to load "request_processors": [ { "id": "*default", // Identifier of this processor diff --git a/data/diameter/dict/huawei/vodafone.xml b/data/diameter/dict/huawei/vodafone.xml deleted file mode 100644 index 57670744f..000000000 --- a/data/diameter/dict/huawei/vodafone.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file