diff --git a/agents/agentreq.go b/agents/agentreq.go index 9354af2a0..4fc2d4182 100644 --- a/agents/agentreq.go +++ b/agents/agentreq.go @@ -204,7 +204,11 @@ func (aReq *AgentRequest) ParseField( return "", fmt.Errorf("invalid debitInterval <%s> to %s", strVal3, utils.MetaCCUsage) } - return usedCCTime + time.Duration(debitItvl.Nanoseconds()*reqNr), nil + mltpl := reqNr - 2 // init and terminate will be ignored + if mltpl < 0 { + mltpl = 0 + } + return usedCCTime + time.Duration(debitItvl.Nanoseconds()*mltpl), nil } if err != nil { diff --git a/agents/diam_it_test.go b/agents/diam_it_test.go index a54d5afe7..42c05bcc1 100644 --- a/agents/diam_it_test.go +++ b/agents/diam_it_test.go @@ -263,7 +263,7 @@ func TestDiamItCCRInit(t *testing.T) { m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ AVP: []*diam.AVP{ - diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(600))}}) + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ AVP: []*diam.AVP{ diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(0))}}) @@ -305,7 +305,7 @@ func TestDiamItCCRInit(t *testing.T) { t.Errorf("expecting: %s, received: <%s>", eVal, val) } // Result-Code - eVal = "900" // 15 mins of session + eVal = "300" // 5 mins of session if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, dict.UndefinedVendorID); err != nil { t.Error(err) @@ -317,3 +317,154 @@ func TestDiamItCCRInit(t *testing.T) { t.Errorf("expecting: %s, received: <%s>", eVal, val) } } + +func TestDiamItCCRUpdate(t *testing.T) { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("192.168.1.1")) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(2)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(2)) + m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("voice@DiamItCCRInit")) + m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 4, 14, 57, 20, 0, time.UTC))) + m.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1006")), // Subscription-Id-Data + }}) + m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) + m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(300))}}) + m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String("1006")), // Calling-Party-Address + diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String("1002")), // Called-Party-Address + diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String("1002")), // Real-Called-Number + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // Charge-Flow-Type + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-Vlr-Number + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-CellID-Or-SAI + diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String("")), // Bearer-Capability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")), // Call-Reference-Number + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("")), // MSC-Address + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(0)), // Time-Zone + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("")), // Called-Party-NP + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("")), // SSP-Time + }, + }), + }}) + if err := diamClnt.SendMessage(m); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2001" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + // Result-Code + eVal = "300" // 5 mins of session + if avps, err := msg.FindAVPsWithPath([]interface{}{"Granted-Service-Unit", "CC-Time"}, + dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } +} + +func TestDiamItCCRTerminate(t *testing.T) { + m := diam.NewRequest(diam.CreditControl, 4, nil) + m.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")) + m.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("192.168.1.1")) + m.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.AuthApplicationID, avp.Mbit, 0, datatype.Unsigned32(4)) + m.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(3)) + m.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(3)) + m.NewAVP(avp.DestinationHost, avp.Mbit, 0, datatype.DiameterIdentity("CGR-DA")) + m.NewAVP(avp.DestinationRealm, avp.Mbit, 0, datatype.DiameterIdentity("cgrates.org")) + m.NewAVP(avp.ServiceContextID, avp.Mbit, 0, datatype.UTF8String("voice@DiamItCCRInit")) + m.NewAVP(avp.EventTimestamp, avp.Mbit, 0, datatype.Time(time.Date(2018, 10, 4, 15, 12, 20, 0, time.UTC))) + m.NewAVP(avp.SubscriptionID, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(450, avp.Mbit, 0, datatype.Enumerated(0)), // Subscription-Id-Type + diam.NewAVP(444, avp.Mbit, 0, datatype.UTF8String("1006")), // Subscription-Id-Data + }}) + m.NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(0)) + m.NewAVP(avp.RequestedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(0))}}) + m.NewAVP(avp.UsedServiceUnit, avp.Mbit, 0, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(420, avp.Mbit, 0, datatype.Unsigned32(250))}}) + m.NewAVP(873, avp.Mbit, 10415, &diam.GroupedAVP{ + AVP: []*diam.AVP{ + diam.NewAVP(20300, avp.Mbit, 2011, &diam.GroupedAVP{ // IN-Information + AVP: []*diam.AVP{ + diam.NewAVP(831, avp.Mbit, 10415, datatype.UTF8String("1006")), // Calling-Party-Address + diam.NewAVP(832, avp.Mbit, 10415, datatype.UTF8String("1002")), // Called-Party-Address + diam.NewAVP(20327, avp.Mbit, 2011, datatype.UTF8String("1002")), // Real-Called-Number + diam.NewAVP(20339, avp.Mbit, 2011, datatype.Unsigned32(0)), // Charge-Flow-Type + diam.NewAVP(20302, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-Vlr-Number + diam.NewAVP(20303, avp.Mbit, 2011, datatype.UTF8String("")), // Calling-CellID-Or-SAI + diam.NewAVP(20313, avp.Mbit, 2011, datatype.UTF8String("")), // Bearer-Capability + diam.NewAVP(20321, avp.Mbit, 2011, datatype.UTF8String("bb97be2b9f37c2be9614fff71c8b1d08b1acbff8")), // Call-Reference-Number + diam.NewAVP(20322, avp.Mbit, 2011, datatype.UTF8String("")), // MSC-Address + diam.NewAVP(20324, avp.Mbit, 2011, datatype.Unsigned32(0)), // Time-Zone + diam.NewAVP(20385, avp.Mbit, 2011, datatype.UTF8String("")), // Called-Party-NP + diam.NewAVP(20386, avp.Mbit, 2011, datatype.UTF8String("")), // SSP-Time + }, + }), + }}) + if err := diamClnt.SendMessage(m); err != nil { + t.Error(err) + } + msg := diamClnt.ReceivedMessage(rplyTimeout) + if msg == nil { + t.Fatal("No message returned") + } + // Result-Code + eVal := "2001" + if avps, err := msg.FindAVPsWithPath([]interface{}{"Result-Code"}, dict.UndefinedVendorID); err != nil { + t.Error(err) + } else if len(avps) == 0 { + t.Error("Missing AVP") + } else if val, err := diamAVPAsString(avps[0]); err != nil { + t.Error(err) + } else if val != eVal { + t.Errorf("expecting: %s, received: <%s>", eVal, val) + } + time.Sleep(time.Duration(*waitRater) * time.Millisecond) + var cdrs []*engine.CDR + args := utils.RPCCDRsFilter{RunIDs: []string{utils.MetaRaw}} + if err := apierRpc.Call(utils.CdrsV1GetCDRs, args, &cdrs); err != nil { + t.Error("Unexpected error: ", err.Error()) + } else if len(cdrs) != 1 { + t.Error("Unexpected number of CDRs returned: ", len(cdrs)) + } else { + if cdrs[0].Usage != 550*time.Second { + t.Errorf("Unexpected Usage CDR: %+v", cdrs[0]) + } + } + +} diff --git a/data/conf/samples/diamagent/voice.json b/data/conf/samples/diamagent/voice.json index 036da0570..bf085e906 100644 --- a/data/conf/samples/diamagent/voice.json +++ b/data/conf/samples/diamagent/voice.json @@ -19,7 +19,8 @@ "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~*req.Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*constant", "value": "15m"}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", + "value": "~*req.Requested-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, ], @@ -40,12 +41,16 @@ {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "~*req.Session-Id", "mandatory": true}, {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*attributes"}, + {"tag": "Category", "field_id": "Category", "type": "*constant", "value": "call"}, {"tag": "Account", "field_id": "Account", "type": "*constant", "value": "*attributes"}, {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~*req.Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*ccr_usage", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*composed", + "value": "~*req.Requested-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*composed", + "value": "~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, ], @@ -60,7 +65,7 @@ "id": "VoiceTerminate", "filters": ["*string:*vars.*cmd:CCR", "*string:*req.CC-Request-Type:3", "*prefix:*req.Service-Context-Id:voice"], - "flags": ["*terminate", "*accounts", "*attributes"], + "flags": ["*terminate", "*accounts", "*attributes", "*cdrs"], "request_fields":[ {"tag": "TOR", "field_id": "ToR", "type": "*constant", "value": "*voice"}, {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", @@ -71,7 +76,10 @@ "value": "~*req.Service-Information.IN-Information.Real-Called-Number", "mandatory": true}, {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "~*req.Event-Timestamp", "mandatory": true}, - {"tag": "Usage", "field_id": "Usage", "type": "*ccr_usage", "mandatory": true}, + {"tag": "Usage", "field_id": "Usage", "type": "*cc_usage", "mandatory": true, + "value": "~*req.CC-Request-Number;~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/;5m"}, + {"tag": "LastUsed", "field_id": "LastUsed", "type": "*composed", + "value": "~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/", "mandatory": true}, {"tag": "SubscriberID", "field_id": "SubscriberId", "type": "*composed", "value": "~*req.Subscription-Id.Subscription-Id-Data", "mandatory": true}, ],