mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-13 02:56:24 +05:00
484 lines
19 KiB
Go
484 lines
19 KiB
Go
/*
|
|
Real-time Charging System 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
|
|
|
|
/*
|
|
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/sessionmanager"
|
|
"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_CCR_SMG_EVENT_NAME = "*ccr_smg_event_name"
|
|
DIAMETER_CCR = "DIAMETER_CCR"
|
|
)
|
|
|
|
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) (reqType, reqNr, ccTime 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)
|
|
}
|
|
return reqType, reqNr, int(ccTimeFloat)
|
|
}
|
|
|
|
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
|
|
ccTime += int(dISecs) * reqNr
|
|
} else {
|
|
ccTime = int(dISecs)
|
|
}
|
|
return time.Duration(ccTime) * time.Second
|
|
}
|
|
|
|
// 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)
|
|
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 = 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
|
|
}
|
|
|
|
// 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
|
|
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"`
|
|
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
|
|
}
|
|
|
|
// Used when sending from client to agent
|
|
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
|
|
}
|
|
subscriptionIdType, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Subscription-Id-Type")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subscriptionIdData, err := m.Dictionary().FindAVP(m.Header.ApplicationID, "Subscription-Id-Data")
|
|
if 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(subscriptionIdType.Code, avp.Mbit, 0, datatype.Enumerated(subscriptionId.SubscriptionIdType)),
|
|
diam.NewAVP(subscriptionIdData.Code, avp.Mbit, 0, datatype.UTF8String(subscriptionId.SubscriptionIdData)),
|
|
}}); 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(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(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)),
|
|
},
|
|
}),
|
|
}}); err != nil {
|
|
return nil, err
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// 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]
|
|
}
|
|
|
|
// Follows the implementation in the StorCdr
|
|
func (self *CCR) passesFieldFilter(fieldFilter *utils.RSRField) bool {
|
|
if fieldFilter == nil {
|
|
return true
|
|
}
|
|
return fieldFilter.FilterPasses(self.eventFieldValue(utils.RSRFields{fieldFilter}))
|
|
}
|
|
|
|
// Handler for meta functions
|
|
func (self *CCR) metaHandler(tag, arg string) (string, error) {
|
|
switch tag {
|
|
case META_CCR_USAGE:
|
|
usage := usageFromCCR(self.CCRequestType, self.CCRequestNumber, self.RequestedServiceUnit.CCTime, self.debitInterval)
|
|
return strconv.FormatFloat(usage.Seconds(), 'f', -1, 64), nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func (self *CCR) eventFieldValue(fldTpl utils.RSRFields) string {
|
|
var outVal string
|
|
for _, rsrTpl := range fldTpl {
|
|
if rsrTpl.IsStatic() {
|
|
outVal += rsrTpl.ParseValue("")
|
|
} else {
|
|
hierarchyPath := strings.Split(rsrTpl.Id, utils.HIERARCHY_SEP)
|
|
hpIf := make([]interface{}, len(hierarchyPath))
|
|
for i, val := range hierarchyPath {
|
|
hpIf[i] = val
|
|
}
|
|
matchingAvps, err := self.diamMessage.FindAVPsWithPath(hpIf)
|
|
if err != nil || len(matchingAvps) == 0 {
|
|
utils.Logger.Warning(fmt.Sprintf("<Diameter> Cannot find AVP for field template with id: %s, ignoring.", rsrTpl.Id))
|
|
continue // Filter not matching
|
|
}
|
|
if matchingAvps[0].Data.Type() == diam.GroupedAVPType {
|
|
utils.Logger.Warning(fmt.Sprintf("<Diameter> Value for field template with id: %s is matching a group AVP, ignoring.", rsrTpl.Id))
|
|
continue // Not convertible, ignore
|
|
}
|
|
outVal += avpValAsString(matchingAvps[0])
|
|
}
|
|
}
|
|
return outVal
|
|
}
|
|
|
|
// Extracts data out of CCR into a SMGenericEvent based on the configured template
|
|
func (self *CCR) AsSMGenericEvent(cfgFlds []*config.CfgCdrField) (sessionmanager.SMGenericEvent, 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 {
|
|
var outVal string
|
|
var err error
|
|
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:
|
|
outVal, err = self.metaHandler(cfgFld.HandlerId, cfgFld.Layout)
|
|
if err != nil {
|
|
utils.Logger.Warning(fmt.Sprintf("<Diameter> Ignoring processing of metafunction: %s, error: %s", cfgFld.HandlerId, err.Error()))
|
|
}
|
|
case utils.META_COMPOSED:
|
|
outVal = self.eventFieldValue(cfgFld.Value)
|
|
}
|
|
fmtOut := outVal
|
|
if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil {
|
|
utils.Logger.Warning(fmt.Sprintf("<Diameter> Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error()))
|
|
return nil, err
|
|
}
|
|
if _, hasKey := outMap[cfgFld.FieldId]; !hasKey {
|
|
outMap[cfgFld.FieldId] = fmtOut
|
|
} else { // If already there, postpend
|
|
outMap[cfgFld.FieldId] += fmtOut
|
|
}
|
|
}
|
|
return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), 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
|
|
}
|