mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
700 lines
24 KiB
Go
700 lines
24 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package sessions
|
|
|
|
import (
|
|
"errors"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cgrates/birpc/context"
|
|
"github.com/cgrates/cgrates/attributes"
|
|
"github.com/cgrates/cgrates/config"
|
|
"github.com/cgrates/cgrates/engine"
|
|
"github.com/cgrates/cgrates/routes"
|
|
"github.com/cgrates/cgrates/utils"
|
|
jwt "github.com/dgrijalva/jwt-go"
|
|
)
|
|
|
|
var unratedReqs = engine.MapEvent{
|
|
utils.MetaPostpaid: struct{}{},
|
|
utils.MetaPseudoPrepaid: struct{}{},
|
|
utils.MetaRated: struct{}{},
|
|
}
|
|
|
|
var authReqs = engine.MapEvent{
|
|
utils.MetaPrepaid: struct{}{},
|
|
utils.MetaPseudoPrepaid: struct{}{},
|
|
}
|
|
|
|
var methodFlags = map[string][]string{
|
|
utils.MetaAuthorize: {
|
|
utils.MetaAccounts,
|
|
utils.MetaAttributes,
|
|
utils.MetaChargers,
|
|
utils.MetaIPs,
|
|
utils.MetaResources,
|
|
utils.MetaRoutes,
|
|
utils.MetaStats,
|
|
utils.MetaThresholds,
|
|
},
|
|
utils.MetaInitiate: {
|
|
utils.MetaInitiate,
|
|
utils.MetaAttributes,
|
|
utils.MetaChargers,
|
|
utils.MetaIPs,
|
|
utils.MetaResources,
|
|
utils.MetaStats,
|
|
utils.MetaThresholds,
|
|
},
|
|
utils.MetaUpdate: {
|
|
utils.MetaUpdate,
|
|
utils.MetaAttributes,
|
|
},
|
|
utils.MetaTerminate: {
|
|
utils.MetaTerminate,
|
|
utils.MetaIPs,
|
|
utils.MetaResources,
|
|
utils.MetaStats,
|
|
utils.MetaThresholds,
|
|
},
|
|
}
|
|
|
|
// ApplyFlags sets relevant flags in opts based on request type and processor flags.
|
|
func ApplyFlags(reqType string, flags utils.FlagsWithParams, opts map[string]any) {
|
|
if flagList, exists := methodFlags[reqType]; exists {
|
|
for _, flag := range flagList {
|
|
if flags.Has(flag) {
|
|
opts[flag] = flags.GetBool(flag)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// BiRPCClient is the interface implemented by Agents which are able to
|
|
// communicate bidirectionally with SessionS and remote Communication Switch
|
|
type BiRPCClient interface {
|
|
V1DisconnectSession(*context.Context, utils.CGREvent, *string) error
|
|
V1GetActiveSessionIDs(*context.Context, string, *[]*SessionID) error
|
|
V1AlterSession(*context.Context, utils.CGREvent, *string) error
|
|
V1DisconnectPeer(*context.Context, *utils.DPRArgs, *string) error
|
|
V1WarnDisconnect(*context.Context, map[string]any, *string) error
|
|
}
|
|
|
|
// GetSetOptsOriginID will populate the originID key if not present and return it
|
|
func GetSetOptsOriginID(ev engine.MapEvent, opt map[string]any) (originID string) {
|
|
fldIface, has := opt[utils.MetaOriginID]
|
|
if has {
|
|
originID = utils.IfaceAsString(fldIface)
|
|
} else {
|
|
originID = utils.Sha1(ev.GetStringIgnoreErrors(utils.OriginID),
|
|
ev.GetStringIgnoreErrors(utils.OriginHost))
|
|
opt[utils.MetaOriginID] = originID
|
|
}
|
|
return
|
|
}
|
|
|
|
func getFlagIDs(flag string) []string {
|
|
flagWithIDs := strings.Split(flag, utils.InInFieldSep)
|
|
if len(flagWithIDs) <= 1 {
|
|
return nil
|
|
}
|
|
return strings.Split(flagWithIDs[1], utils.ANDSep)
|
|
}
|
|
|
|
// ProcessedStirIdentity the structure that keeps all the header information
|
|
type ProcessedStirIdentity struct {
|
|
Tokens []string
|
|
SigningStr string
|
|
Signature string
|
|
Header *utils.PASSporTHeader
|
|
Payload *utils.PASSporTPayload
|
|
}
|
|
|
|
// NewProcessedIdentity creates a proccessed header
|
|
func NewProcessedIdentity(identity string) (pi *ProcessedStirIdentity, err error) {
|
|
pi = new(ProcessedStirIdentity)
|
|
hdrtoken := strings.Split(utils.RemoveWhiteSpaces(identity), utils.InfieldSep)
|
|
|
|
if len(hdrtoken) == 1 {
|
|
err = errors.New("missing parts of the message header")
|
|
return
|
|
}
|
|
pi.Tokens = hdrtoken[1:]
|
|
btoken := utils.SplitPath(hdrtoken[0], utils.NestingSep[0], -1)
|
|
if len(btoken) != 3 {
|
|
err = errors.New("wrong header format")
|
|
return
|
|
}
|
|
pi.SigningStr = btoken[0] + utils.NestingSep + btoken[1]
|
|
pi.Signature = btoken[2]
|
|
|
|
pi.Header = new(utils.PASSporTHeader)
|
|
if err = utils.DecodeBase64JSON(btoken[0], pi.Header); err != nil {
|
|
return
|
|
}
|
|
pi.Payload = new(utils.PASSporTPayload)
|
|
err = utils.DecodeBase64JSON(btoken[1], pi.Payload)
|
|
return
|
|
}
|
|
|
|
// VerifyHeader returns if the header is corectly populated
|
|
func (pi *ProcessedStirIdentity) VerifyHeader() (isValid bool) {
|
|
var x5u string
|
|
for _, pair := range pi.Tokens {
|
|
ptoken := strings.Split(pair, utils.AttrValueSep)
|
|
if len(ptoken) != 2 {
|
|
continue
|
|
}
|
|
switch ptoken[0] {
|
|
case utils.STIRAlgField:
|
|
if ptoken[1] != utils.STIRAlg {
|
|
return false
|
|
}
|
|
case utils.STIRPptField:
|
|
if ptoken[1] != utils.STIRPpt &&
|
|
ptoken[1] != "\""+utils.STIRPpt+"\"" {
|
|
return false
|
|
}
|
|
case utils.STIRInfoField:
|
|
lenParamInfo := len(ptoken[1])
|
|
if lenParamInfo <= 2 {
|
|
return false
|
|
}
|
|
x5u = ptoken[1]
|
|
if x5u[0] == '<' && x5u[lenParamInfo-1] == '>' {
|
|
x5u = x5u[1 : lenParamInfo-1]
|
|
}
|
|
}
|
|
}
|
|
|
|
return pi.Header.Alg == utils.STIRAlg &&
|
|
pi.Header.Ppt == utils.STIRPpt &&
|
|
pi.Header.Typ == utils.STIRTyp &&
|
|
pi.Header.X5u == x5u
|
|
}
|
|
|
|
// VerifySignature returns if the signature is valid
|
|
func (pi *ProcessedStirIdentity) VerifySignature(ctx *context.Context, timeoutVal time.Duration) (err error) {
|
|
var pubkey any
|
|
var ok bool
|
|
if pubkey, ok = engine.Cache.Get(utils.CacheSTIR, pi.Header.X5u); !ok {
|
|
if pubkey, err = utils.NewECDSAPubKey(pi.Header.X5u, timeoutVal); err != nil {
|
|
if errCh := engine.Cache.Set(ctx, utils.CacheSTIR, pi.Header.X5u, nil,
|
|
nil, false, utils.NonTransactional); errCh != nil {
|
|
return errCh
|
|
}
|
|
return
|
|
}
|
|
if errCh := engine.Cache.Set(ctx, utils.CacheSTIR, pi.Header.X5u, pubkey,
|
|
nil, false, utils.NonTransactional); errCh != nil {
|
|
return errCh
|
|
}
|
|
}
|
|
|
|
sigMethod := jwt.GetSigningMethod(pi.Header.Alg)
|
|
return sigMethod.Verify(pi.SigningStr, pi.Signature, pubkey)
|
|
|
|
}
|
|
|
|
// VerifyPayload returns if the payload is corectly populated
|
|
func (pi *ProcessedStirIdentity) VerifyPayload(originatorTn, originatorURI, destinationTn, destinationURI string,
|
|
hdrMaxDur time.Duration, attest utils.StringSet) (err error) {
|
|
if !attest.Has(utils.MetaAny) && !attest.Has(pi.Payload.ATTest) {
|
|
return errors.New("wrong attest level")
|
|
}
|
|
if hdrMaxDur >= 0 && time.Now().After(time.Unix(pi.Payload.IAT, 0).Add(hdrMaxDur)) {
|
|
return errors.New("expired payload")
|
|
}
|
|
if originatorURI != utils.EmptyString {
|
|
if originatorURI != pi.Payload.Orig.URI {
|
|
return errors.New("wrong originatorURI")
|
|
}
|
|
} else if originatorTn != pi.Payload.Orig.Tn {
|
|
return errors.New("wrong originatorTn")
|
|
}
|
|
if destinationURI != utils.EmptyString {
|
|
if !slices.Contains(pi.Payload.Dest.URI, destinationURI) {
|
|
return errors.New("wrong destinationURI")
|
|
}
|
|
} else if !slices.Contains(pi.Payload.Dest.Tn, destinationTn) {
|
|
return errors.New("wrong destinationTn")
|
|
}
|
|
return
|
|
}
|
|
|
|
// NewSTIRIdentity returns the identiy for stir header
|
|
func NewSTIRIdentity(ctx *context.Context, header *utils.PASSporTHeader, payload *utils.PASSporTPayload, prvkeyPath string, timeout time.Duration) (identity string, err error) {
|
|
var prvKey any
|
|
var ok bool
|
|
if prvKey, ok = engine.Cache.Get(utils.CacheSTIR, prvkeyPath); !ok {
|
|
if prvKey, err = utils.NewECDSAPrvKey(prvkeyPath, timeout); err != nil {
|
|
if errCh := engine.Cache.Set(ctx, utils.CacheSTIR, prvkeyPath, nil,
|
|
nil, false, utils.NonTransactional); errCh != nil {
|
|
return utils.EmptyString, errCh
|
|
}
|
|
return
|
|
}
|
|
if errCh := engine.Cache.Set(ctx, utils.CacheSTIR, prvkeyPath, prvKey,
|
|
nil, false, utils.NonTransactional); errCh != nil {
|
|
return utils.EmptyString, errCh
|
|
}
|
|
}
|
|
var headerStr, payloadStr string
|
|
if headerStr, err = utils.EncodeBase64JSON(header); err != nil {
|
|
return
|
|
}
|
|
if payloadStr, err = utils.EncodeBase64JSON(payload); err != nil {
|
|
return
|
|
}
|
|
identity = headerStr + utils.NestingSep + payloadStr
|
|
|
|
sigMethod := jwt.GetSigningMethod(header.Alg)
|
|
var signature string
|
|
if signature, err = sigMethod.Sign(identity, prvKey); err != nil {
|
|
return
|
|
}
|
|
identity += utils.NestingSep + signature
|
|
identity += utils.STIRExtraInfoPrefix + header.X5u + utils.STIRExtraInfoSuffix
|
|
return
|
|
}
|
|
|
|
// AuthStirShaken autentificates the given identity using STIR/SHAKEN
|
|
func AuthStirShaken(ctx *context.Context, identity, originatorTn, originatorURI, destinationTn, destinationURI string,
|
|
attest utils.StringSet, hdrMaxDur time.Duration) (err error) {
|
|
var pi *ProcessedStirIdentity
|
|
if pi, err = NewProcessedIdentity(identity); err != nil {
|
|
return
|
|
}
|
|
if !pi.VerifyHeader() {
|
|
return errors.New("wrong header")
|
|
}
|
|
if err = pi.VerifySignature(ctx, config.CgrConfig().GeneralCfg().ReplyTimeout); err != nil {
|
|
return
|
|
}
|
|
return pi.VerifyPayload(originatorTn, originatorURI, destinationTn, destinationURI, hdrMaxDur, attest)
|
|
}
|
|
|
|
// V1STIRAuthenticateArgs are the arguments for STIRAuthenticate API
|
|
type V1STIRAuthenticateArgs struct {
|
|
Attest []string // what attest levels are allowed
|
|
DestinationTn string // the expected destination telephone number
|
|
DestinationURI string // the expected destination URI; if this is populated the DestinationTn is ignored
|
|
Identity string // the identity header
|
|
OriginatorTn string // the expected originator telephone number
|
|
OriginatorURI string // the expected originator URI; if this is populated the OriginatorTn is ignored
|
|
PayloadMaxDuration string // the duration the payload is valid after it's creation
|
|
APIOpts map[string]any
|
|
}
|
|
|
|
// V1STIRIdentityArgs are the arguments for STIRIdentity API
|
|
type V1STIRIdentityArgs struct {
|
|
Payload *utils.PASSporTPayload // the STIR payload
|
|
PublicKeyPath string // the path to the public key used in the header
|
|
PrivateKeyPath string // the private key path
|
|
OverwriteIAT bool // if true the IAT from payload is overwrited with the present unix timestamp
|
|
APIOpts map[string]any
|
|
}
|
|
|
|
// getDerivedEvents returns only the *raw event if derivedReply flag is not specified
|
|
func getDerivedEvents(events map[string]*utils.CGREvent, derivedReply bool) map[string]*utils.CGREvent {
|
|
if derivedReply {
|
|
return events
|
|
}
|
|
return map[string]*utils.CGREvent{
|
|
utils.MetaRaw: events[utils.MetaRaw],
|
|
}
|
|
}
|
|
|
|
// V1ProcessEventReply is the reply for the ProcessEvent API
|
|
type V1ProcessEventReply struct {
|
|
AccountSUsage map[string]time.Duration `json:",omitempty"`
|
|
RateSCost map[string]float64 `json:",omitempty"`
|
|
ResourceAllocation map[string]string `json:",omitempty"`
|
|
IPsAllocation map[string]*utils.AllocatedIP `json:",omitempty"`
|
|
Attributes map[string]*attributes.AttrSProcessEventReply `json:",omitempty"`
|
|
RouteProfiles map[string]routes.SortedRoutesList `json:",omitempty"`
|
|
ThresholdIDs map[string][]string `json:",omitempty"`
|
|
StatQueueIDs map[string][]string `json:",omitempty"`
|
|
STIRIdentity map[string]string `json:",omitempty"`
|
|
}
|
|
|
|
// AsNavigableMap is part of engine.NavigableMapper interface
|
|
func (v1Rply *V1ProcessEventReply) AsNavigableMap() map[string]*utils.DataNode {
|
|
cgrReply := make(map[string]*utils.DataNode)
|
|
if v1Rply.AccountSUsage != nil {
|
|
usage := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, v := range v1Rply.AccountSUsage {
|
|
usage.Map[k] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapMaxUsage] = usage
|
|
}
|
|
if v1Rply.ResourceAllocation != nil {
|
|
res := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, v := range v1Rply.ResourceAllocation {
|
|
res.Map[k] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapResourceAllocation] = res
|
|
}
|
|
if v1Rply.Attributes != nil {
|
|
atts := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, att := range v1Rply.Attributes {
|
|
attrs := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for _, altered := range att.AlteredFields {
|
|
for _, fldName := range altered.Fields {
|
|
fldName = strings.TrimPrefix(fldName, utils.MetaReq+utils.NestingSep)
|
|
if att.CGREvent.HasField(fldName) {
|
|
attrs.Map[fldName] = utils.NewLeafNode(att.CGREvent.Event[fldName])
|
|
}
|
|
}
|
|
}
|
|
atts.Map[k] = attrs
|
|
}
|
|
cgrReply[utils.CapAttributes] = atts
|
|
}
|
|
if v1Rply.RouteProfiles != nil {
|
|
routes := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, route := range v1Rply.RouteProfiles {
|
|
routes.Map[k] = route.AsNavigableMap()
|
|
}
|
|
cgrReply[utils.CapRouteProfiles] = routes
|
|
}
|
|
if v1Rply.ThresholdIDs != nil {
|
|
th := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, thr := range v1Rply.ThresholdIDs {
|
|
thIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(thr))}
|
|
for i, v := range thr {
|
|
thIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
th.Map[k] = thIDs
|
|
}
|
|
cgrReply[utils.CapThresholds] = th
|
|
}
|
|
if v1Rply.StatQueueIDs != nil {
|
|
st := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, sts := range v1Rply.StatQueueIDs {
|
|
stIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(sts))}
|
|
for i, v := range sts {
|
|
stIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
st.Map[k] = stIDs
|
|
}
|
|
cgrReply[utils.CapStatQueues] = st
|
|
}
|
|
if v1Rply.RateSCost != nil {
|
|
costs := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, cost := range v1Rply.RateSCost {
|
|
costs.Map[k] = utils.NewLeafNode(cost)
|
|
}
|
|
}
|
|
if v1Rply.STIRIdentity != nil {
|
|
stir := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for k, v := range v1Rply.STIRIdentity {
|
|
stir.Map[k] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.OptsStirIdentity] = stir
|
|
}
|
|
return cgrReply
|
|
}
|
|
|
|
// V1ProcessMessageReply is the reply for the ProcessMessage API
|
|
type V1ProcessMessageReply struct {
|
|
MaxUsage *time.Duration `json:",omitempty"`
|
|
ResourceAllocation *string `json:",omitempty"`
|
|
Attributes *attributes.AttrSProcessEventReply `json:",omitempty"`
|
|
RouteProfiles routes.SortedRoutesList `json:",omitempty"`
|
|
ThresholdIDs *[]string `json:",omitempty"`
|
|
StatQueueIDs *[]string `json:",omitempty"`
|
|
|
|
needsMaxUsage bool // for gob encoding only
|
|
}
|
|
|
|
// SetMaxUsageNeeded used by agent that use the reply as NavigableMapper
|
|
// only used for gob encoding
|
|
func (v1Rply *V1ProcessMessageReply) SetMaxUsageNeeded(getMaxUsage bool) {
|
|
if v1Rply == nil {
|
|
return
|
|
}
|
|
v1Rply.needsMaxUsage = getMaxUsage
|
|
}
|
|
|
|
// AsNavigableMap is part of engine.NavigableMapper interface
|
|
func (v1Rply *V1ProcessMessageReply) AsNavigableMap() map[string]*utils.DataNode {
|
|
cgrReply := make(map[string]*utils.DataNode)
|
|
if v1Rply.MaxUsage != nil {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(*v1Rply.MaxUsage)
|
|
} else if v1Rply.needsMaxUsage {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(0)
|
|
}
|
|
if v1Rply.ResourceAllocation != nil {
|
|
cgrReply[utils.CapResourceAllocation] = utils.NewLeafNode(*v1Rply.ResourceAllocation)
|
|
}
|
|
if v1Rply.Attributes != nil {
|
|
attrs := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
|
|
for _, altered := range v1Rply.Attributes.AlteredFields {
|
|
for _, fldName := range altered.Fields {
|
|
fldName = strings.TrimPrefix(fldName, utils.MetaReq+utils.NestingSep)
|
|
if v1Rply.Attributes.CGREvent.HasField(fldName) {
|
|
attrs.Map[fldName] = utils.NewLeafNode(v1Rply.Attributes.CGREvent.Event[fldName])
|
|
}
|
|
}
|
|
}
|
|
cgrReply[utils.CapAttributes] = attrs
|
|
}
|
|
if v1Rply.RouteProfiles != nil {
|
|
cgrReply[utils.CapRouteProfiles] = v1Rply.RouteProfiles.AsNavigableMap()
|
|
}
|
|
if v1Rply.ThresholdIDs != nil {
|
|
thIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(*v1Rply.ThresholdIDs))}
|
|
for i, v := range *v1Rply.ThresholdIDs {
|
|
thIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapThresholds] = thIDs
|
|
}
|
|
if v1Rply.StatQueueIDs != nil {
|
|
stIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(*v1Rply.StatQueueIDs))}
|
|
for i, v := range *v1Rply.StatQueueIDs {
|
|
stIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapStatQueues] = stIDs
|
|
}
|
|
return cgrReply
|
|
}
|
|
|
|
// V1AuthorizeReply are options available in auth reply
|
|
type V1AuthorizeReply struct {
|
|
Attributes *attributes.AttrSProcessEventReply `json:",omitempty"`
|
|
ResourceAllocation *string `json:",omitempty"`
|
|
AllocatedIP *utils.AllocatedIP `json:",omitempty"`
|
|
MaxUsage *utils.Decimal `json:",omitempty"`
|
|
RouteProfiles routes.SortedRoutesList `json:",omitempty"`
|
|
ThresholdIDs *[]string `json:",omitempty"`
|
|
StatQueueIDs *[]string `json:",omitempty"`
|
|
|
|
needsMaxUsage bool // for gob encoding only
|
|
}
|
|
|
|
// SetMaxUsageNeeded used by agent that use the reply as NavigableMapper
|
|
// only used for gob encoding
|
|
func (r *V1AuthorizeReply) SetMaxUsageNeeded(getMaxUsage bool) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.needsMaxUsage = getMaxUsage
|
|
}
|
|
|
|
// AsNavigableMap is part of engine.NavigableMapper interface
|
|
func (r *V1AuthorizeReply) AsNavigableMap() map[string]*utils.DataNode {
|
|
cgrReply := make(map[string]*utils.DataNode)
|
|
if r.Attributes != nil {
|
|
attrs := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for _, altered := range r.Attributes.AlteredFields {
|
|
for _, fldName := range altered.Fields {
|
|
fldName = strings.TrimPrefix(fldName, utils.MetaReq+utils.NestingSep)
|
|
if r.Attributes.CGREvent.HasField(fldName) {
|
|
attrs.Map[fldName] = utils.NewLeafNode(r.Attributes.CGREvent.Event[fldName])
|
|
}
|
|
}
|
|
}
|
|
cgrReply[utils.CapAttributes] = attrs
|
|
}
|
|
if r.AllocatedIP != nil {
|
|
cgrReply[utils.AllocatedIPField] = &utils.DataNode{
|
|
Type: utils.NMMapType,
|
|
Map: r.AllocatedIP.AsNavigableMap(),
|
|
}
|
|
}
|
|
if r.ResourceAllocation != nil {
|
|
cgrReply[utils.CapResourceAllocation] = utils.NewLeafNode(*r.ResourceAllocation)
|
|
}
|
|
if r.MaxUsage != nil {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(r.MaxUsage)
|
|
} else if r.needsMaxUsage {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(0)
|
|
}
|
|
|
|
if r.RouteProfiles != nil {
|
|
nm := r.RouteProfiles.AsNavigableMap()
|
|
cgrReply[utils.CapRouteProfiles] = nm
|
|
}
|
|
if r.ThresholdIDs != nil {
|
|
thIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(*r.ThresholdIDs))}
|
|
for i, v := range *r.ThresholdIDs {
|
|
thIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapThresholds] = thIDs
|
|
}
|
|
if r.StatQueueIDs != nil {
|
|
stIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(*r.StatQueueIDs))}
|
|
for i, v := range *r.StatQueueIDs {
|
|
stIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapStatQueues] = stIDs
|
|
}
|
|
return cgrReply
|
|
}
|
|
|
|
// V1AuthorizeReplyWithDigest contains return options for auth with digest
|
|
type V1AuthorizeReplyWithDigest struct {
|
|
AttributesDigest *string
|
|
ResourceAllocation *string
|
|
MaxUsage int64 // special treat returning time.Duration.Nanoseconds()
|
|
RoutesDigest *string
|
|
Thresholds *string
|
|
StatQueues *string
|
|
}
|
|
|
|
// V1InitSessionReply are options for initialization reply
|
|
type V1InitSessionReply struct {
|
|
Attributes *attributes.AttrSProcessEventReply `json:",omitempty"`
|
|
ResourceAllocation *string `json:",omitempty"`
|
|
AllocatedIP *utils.AllocatedIP `json:",omitempty"`
|
|
MaxUsage *time.Duration `json:",omitempty"`
|
|
ThresholdIDs *[]string `json:",omitempty"`
|
|
StatQueueIDs *[]string `json:",omitempty"`
|
|
|
|
needsMaxUsage bool // for gob encoding only
|
|
}
|
|
|
|
// SetMaxUsageNeeded used by agent that use the reply as NavigableMapper
|
|
// only used for gob encoding
|
|
func (r *V1InitSessionReply) SetMaxUsageNeeded(getMaxUsage bool) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.needsMaxUsage = getMaxUsage
|
|
}
|
|
|
|
// AsNavigableMap is part of engine.NavigableMapper interface
|
|
func (r *V1InitSessionReply) AsNavigableMap() map[string]*utils.DataNode {
|
|
cgrReply := make(map[string]*utils.DataNode)
|
|
if r.Attributes != nil {
|
|
attrs := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for _, altered := range r.Attributes.AlteredFields {
|
|
for _, fldName := range altered.Fields {
|
|
fldName = strings.TrimPrefix(fldName, utils.MetaReq+utils.NestingSep)
|
|
if r.Attributes.CGREvent.HasField(fldName) {
|
|
attrs.Map[fldName] = utils.NewLeafNode(r.Attributes.CGREvent.Event[fldName])
|
|
}
|
|
}
|
|
}
|
|
cgrReply[utils.CapAttributes] = attrs
|
|
}
|
|
if r.AllocatedIP != nil {
|
|
cgrReply[utils.AllocatedIPField] = &utils.DataNode{
|
|
Type: utils.NMMapType,
|
|
Map: r.AllocatedIP.AsNavigableMap(),
|
|
}
|
|
}
|
|
if r.ResourceAllocation != nil {
|
|
cgrReply[utils.CapResourceAllocation] = utils.NewLeafNode(*r.ResourceAllocation)
|
|
}
|
|
if r.MaxUsage != nil {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(*r.MaxUsage)
|
|
} else if r.needsMaxUsage {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(0)
|
|
}
|
|
|
|
if r.ThresholdIDs != nil {
|
|
thIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(*r.ThresholdIDs))}
|
|
for i, v := range *r.ThresholdIDs {
|
|
thIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapThresholds] = thIDs
|
|
}
|
|
if r.StatQueueIDs != nil {
|
|
stIDs := &utils.DataNode{Type: utils.NMSliceType, Slice: make([]*utils.DataNode, len(*r.StatQueueIDs))}
|
|
for i, v := range *r.StatQueueIDs {
|
|
stIDs.Slice[i] = utils.NewLeafNode(v)
|
|
}
|
|
cgrReply[utils.CapStatQueues] = stIDs
|
|
}
|
|
return cgrReply
|
|
}
|
|
|
|
// V1InitReplyWithDigest is the formated reply
|
|
type V1InitReplyWithDigest struct {
|
|
AttributesDigest *string
|
|
ResourceAllocation *string
|
|
MaxUsage float64
|
|
Thresholds *string
|
|
StatQueues *string
|
|
}
|
|
|
|
// V1UpdateSessionReply contains options for session update reply
|
|
type V1UpdateSessionReply struct {
|
|
Attributes *attributes.AttrSProcessEventReply `json:",omitempty"`
|
|
MaxUsage *time.Duration `json:",omitempty"`
|
|
|
|
needsMaxUsage bool // for gob encoding only
|
|
}
|
|
|
|
// SetMaxUsageNeeded used by agent that use the reply as NavigableMapper
|
|
// only used for gob encoding
|
|
func (v1Rply *V1UpdateSessionReply) SetMaxUsageNeeded(getMaxUsage bool) {
|
|
if v1Rply == nil {
|
|
return
|
|
}
|
|
v1Rply.needsMaxUsage = getMaxUsage
|
|
}
|
|
|
|
// AsNavigableMap is part of engine.NavigableMapper interface
|
|
func (v1Rply *V1UpdateSessionReply) AsNavigableMap() map[string]*utils.DataNode {
|
|
cgrReply := make(map[string]*utils.DataNode)
|
|
if v1Rply.Attributes != nil {
|
|
attrs := &utils.DataNode{Type: utils.NMMapType, Map: make(map[string]*utils.DataNode)}
|
|
for _, altered := range v1Rply.Attributes.AlteredFields {
|
|
for _, fldName := range altered.Fields {
|
|
fldName = strings.TrimPrefix(fldName, utils.MetaReq+utils.NestingSep)
|
|
if v1Rply.Attributes.CGREvent.HasField(fldName) {
|
|
attrs.Map[fldName] = utils.NewLeafNode(v1Rply.Attributes.CGREvent.Event[fldName])
|
|
}
|
|
}
|
|
}
|
|
cgrReply[utils.CapAttributes] = attrs
|
|
}
|
|
if v1Rply.MaxUsage != nil {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(*v1Rply.MaxUsage)
|
|
} else if v1Rply.needsMaxUsage {
|
|
cgrReply[utils.CapMaxUsage] = utils.NewLeafNode(0)
|
|
}
|
|
return cgrReply
|
|
}
|
|
|
|
// ArgsReplicateSessions used to specify wich Session to replicate over the given connections
|
|
type ArgsReplicateSessions struct {
|
|
Passive bool
|
|
ConnIDs []string
|
|
APIOpts map[string]any
|
|
Tenant string
|
|
}
|
|
|
|
// getMaxUsageFromRuns will return maxUsage with the lowest one from maxAbstractsRuns
|
|
func getMaxUsageFromRuns(maxAbstractsRuns map[string]*utils.Decimal) (maxUsage *utils.Decimal) {
|
|
for _, maxAbstr := range maxAbstractsRuns {
|
|
if maxUsage == nil ||
|
|
maxUsage.Compare(maxAbstr) == 1 {
|
|
maxUsage = maxAbstr
|
|
}
|
|
}
|
|
return
|
|
}
|