/* 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 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 */ package utils import ( "encoding/hex" "encoding/json" "fmt" "math/rand" "net" "strconv" "strings" "time" "github.com/cgrates/sipingo" "github.com/nyaruka/phonenumbers" ) // DataConverters groups together multiple converters, // executing optimized conversions type DataConverters []DataConverter // ConvertString converts from and to string func (dcs DataConverters) ConvertString(in string) (out string, err error) { outIface := interface{}(in) for _, cnv := range dcs { if outIface, err = cnv.Convert(outIface); err != nil { return } } return IfaceAsString(outIface), nil } // DataConverter represents functions which should convert input into output type DataConverter interface { Convert(interface{}) (interface{}, error) } // NewDataConverter is a factory of converters func NewDataConverter(params string) (conv DataConverter, err error) { switch { case params == MetaDurationSeconds: return NewDurationSecondsConverter(EmptyString) case params == MetaDurationNanoseconds: return NewDurationNanosecondsConverter(EmptyString) case strings.HasPrefix(params, MetaRound): if len(params) == len(MetaRound) { // no extra params, defaults implied return NewRoundConverter(EmptyString) } return NewRoundConverter(params[len(MetaRound)+1:]) case strings.HasPrefix(params, MetaMultiply): if len(params) == len(MetaMultiply) { // no extra params, defaults implied return NewMultiplyConverter(EmptyString) } return NewMultiplyConverter(params[len(MetaMultiply)+1:]) case strings.HasPrefix(params, MetaDivide): if len(params) == len(MetaDivide) { // no extra params, defaults implied return NewDivideConverter(EmptyString) } return NewDivideConverter(params[len(MetaDivide)+1:]) case params == MetaDuration: return NewDurationConverter(EmptyString) case params == MetaIP2Hex: return new(IP2HexConverter), nil case params == MetaString2Hex: return new(String2HexConverter), nil case params == MetaSIPURIHost: return new(SIPURIHostConverter), nil case params == MetaSIPURIUser: return new(SIPURIUserConverter), nil case params == MetaSIPURIMethod: return new(SIPURIMethodConverter), nil case params == MetaUnixTime: return new(UnixTimeConverter), nil case params == MetaLen: return new(LengthConverter), nil case strings.HasPrefix(params, MetaLibPhoneNumber): if len(params) == len(MetaLibPhoneNumber) { return NewPhoneNumberConverter(EmptyString) } return NewPhoneNumberConverter(params[len(MetaLibPhoneNumber)+1:]) case strings.HasPrefix(params, MetaTimeString): layout := time.RFC3339 if len(params) > len(MetaTimeString) { // no extra params, defaults implied layout = params[len(MetaTimeString)+1:] } return NewTimeStringConverter(layout), nil case strings.HasPrefix(params, MetaRandom): if len(params) == len(MetaRandom) { // no extra params, defaults implied return NewRandomConverter(EmptyString) } return NewRandomConverter(params[len(MetaRandom)+1:]) default: return nil, fmt.Errorf("unsupported converter definition: <%s>", params) } } func NewDataConverterMustCompile(params string) (conv DataConverter) { var err error if conv, err = NewDataConverter(params); err != nil { panic(fmt.Sprintf("parsing: <%s>, error: %s", params, err.Error())) } return } func NewDurationSecondsConverter(params string) (hdlr DataConverter, err error) { return new(DurationSecondsConverter), nil } // DurationSecondsConverter converts duration into seconds encapsulated in float64 type DurationSecondsConverter struct{} func (mS *DurationSecondsConverter) Convert(in interface{}) ( out interface{}, err error) { var inDur time.Duration if inDur, err = IfaceAsDuration(in); err != nil { return nil, err } out = inDur.Seconds() return } func NewDurationNanosecondsConverter(params string) ( hdlr DataConverter, err error) { return new(DurationNanosecondsConverter), nil } // DurationNanosecondsConverter converts duration into nanoseconds encapsulated in int64 type DurationNanosecondsConverter struct{} func (mS *DurationNanosecondsConverter) Convert(in interface{}) ( out interface{}, err error) { var inDur time.Duration if inDur, err = IfaceAsDuration(in); err != nil { return nil, err } out = inDur.Nanoseconds() return } func NewRoundConverter(params string) (hdlr DataConverter, err error) { rc := new(RoundConverter) var paramsSplt []string if params != EmptyString { paramsSplt = strings.Split(params, InInFieldSep) } switch len(paramsSplt) { case 0: rc.Method = MetaRoundingMiddle case 1: if rc.Decimals, err = strconv.Atoi(paramsSplt[0]); err != nil { return nil, fmt.Errorf("%s converter needs integer as decimals, have: <%s>", MetaRound, paramsSplt[0]) } rc.Method = MetaRoundingMiddle case 2: rc.Method = paramsSplt[1] if rc.Decimals, err = strconv.Atoi(paramsSplt[0]); err != nil { return nil, fmt.Errorf("%s converter needs integer as decimals, have: <%s>", MetaRound, paramsSplt[0]) } default: return nil, fmt.Errorf("unsupported %s converter parameters: <%s>", MetaRound, params) } return rc, nil } // RoundConverter will round floats type RoundConverter struct { Decimals int Method string } func (rnd *RoundConverter) Convert(in interface{}) (out interface{}, err error) { var inFloat float64 if inFloat, err = IfaceAsFloat64(in); err != nil { return } out = Round(inFloat, rnd.Decimals, rnd.Method) return } func NewMultiplyConverter(constructParams string) (hdlr DataConverter, err error) { if constructParams == EmptyString { return nil, ErrMandatoryIeMissingNoCaps } var val float64 if val, err = strconv.ParseFloat(constructParams, 64); err != nil { return } return &MultiplyConverter{Value: val}, nil } // MultiplyConverter multiplies input with value in params // encapsulates the output as float64 value type MultiplyConverter struct { Value float64 } func (m *MultiplyConverter) Convert(in interface{}) (out interface{}, err error) { var inFloat64 float64 if inFloat64, err = IfaceAsFloat64(in); err != nil { return nil, err } out = inFloat64 * m.Value return } func NewDivideConverter(constructParams string) (hdlr DataConverter, err error) { if constructParams == EmptyString { return nil, ErrMandatoryIeMissingNoCaps } var val float64 if val, err = strconv.ParseFloat(constructParams, 64); err != nil { return } return &DivideConverter{Value: val}, nil } // DivideConverter divides input with value in params // encapsulates the output as float64 value type DivideConverter struct { Value float64 } func (m *DivideConverter) Convert(in interface{}) (out interface{}, err error) { var inFloat64 float64 if inFloat64, err = IfaceAsFloat64(in); err != nil { return nil, err } out = inFloat64 / m.Value return } func NewDurationConverter(params string) (hdlr DataConverter, err error) { return new(DurationConverter), nil } // DurationConverter converts duration into seconds encapsulated in float64 type DurationConverter struct{} func (mS *DurationConverter) Convert(in interface{}) ( out interface{}, err error) { return IfaceAsDuration(in) } // NewPhoneNumberConverter create a new phoneNumber converter // If the format isn't specify by default we use NATIONAL // Possible fromats are : E164(0) , INTERNATIONAL(1) , NATIONAL(2) ,RFC3966(3) // Also ContryCode needs to be specified func NewPhoneNumberConverter(params string) ( pbDC DataConverter, err error) { lc := new(PhoneNumberConverter) var paramsSplt []string if params != EmptyString { paramsSplt = strings.Split(params, InInFieldSep) } switch len(paramsSplt) { case 2: lc.CountryCode = paramsSplt[0] frm, err := strconv.Atoi(paramsSplt[1]) if err != nil { return nil, err } lc.Format = phonenumbers.PhoneNumberFormat(frm) case 1: lc.CountryCode = paramsSplt[0] lc.Format = 2 default: return nil, fmt.Errorf("unsupported %s converter parameters: <%s>", MetaLibPhoneNumber, params) } return lc, nil } // PhoneNumberConverter converts type PhoneNumberConverter struct { CountryCode string Format phonenumbers.PhoneNumberFormat } func (lc *PhoneNumberConverter) Convert(in interface{}) (out interface{}, err error) { num, err := phonenumbers.Parse(IfaceAsString(in), lc.CountryCode) if err != nil { return nil, err } return phonenumbers.Format(num, lc.Format), nil } // IP2HexConverter will transform ip to hex type IP2HexConverter struct{} // Convert implements DataConverter interface func (*IP2HexConverter) Convert(in interface{}) (out interface{}, err error) { var ip net.IP switch val := in.(type) { case string: ip = net.ParseIP(val) case net.IP: ip = val default: src := IfaceAsString(in) ip = net.ParseIP(src) } hx := hex.EncodeToString([]byte(ip)) if len(hx) < 8 { return hx, nil } return "0x" + string([]byte(hx)[len(hx)-8:]), nil } // SIPURIHostConverter will return the type SIPURIHostConverter struct{} // Convert implements DataConverter interface func (*SIPURIHostConverter) Convert(in interface{}) (out interface{}, err error) { val := IfaceAsString(in) return sipingo.HostFrom(val), nil } // SIPURIUserConverter will return the type SIPURIUserConverter struct{} // Convert implements DataConverter interface func (*SIPURIUserConverter) Convert(in interface{}) (out interface{}, err error) { val := IfaceAsString(in) return sipingo.UserFrom(val), nil } // SIPURIMethodConverter will return the type SIPURIMethodConverter struct{} // Convert implements DataConverter interface func (*SIPURIMethodConverter) Convert(in interface{}) (out interface{}, err error) { val := IfaceAsString(in) return sipingo.MethodFrom(val), nil } func NewTimeStringConverter(params string) (hdlr DataConverter) { return &TimeStringConverter{Layout: params} } type TimeStringConverter struct { Layout string } // Convert implements DataConverter interface func (tS *TimeStringConverter) Convert(in interface{}) ( out interface{}, err error) { tm, err := ParseTimeDetectLayout(in.(string), EmptyString) if err != nil { return nil, err } return tm.Format(tS.Layout), nil } // String2HexConverter will transform the string to hex type String2HexConverter struct{} // Convert implements DataConverter interface func (*String2HexConverter) Convert(in interface{}) (o interface{}, err error) { var out string if out = hex.EncodeToString([]byte(IfaceAsString(in))); len(out) == 0 { o = out return } o = "0x" + out return } // UnixTimeConverter converts the interface in the unix time type UnixTimeConverter struct{} // Convert implements DataConverter interface func (tS *UnixTimeConverter) Convert(in interface{}) ( out interface{}, err error) { var tm time.Time if tm, err = ParseTimeDetectLayout(in.(string), EmptyString); err != nil { return } out = tm.Unix() return } func NewRandomConverter(params string) (dflr DataConverter, err error) { randConv := &RandomConverter{} if params == EmptyString { dflr = randConv return } sls := strings.Split(params, InInFieldSep) switch len(sls) { case 2: if sls[0] != EmptyString { if randConv.begin, err = strconv.Atoi(sls[0]); err != nil { return } } if sls[1] != EmptyString { if randConv.end, err = strconv.Atoi(sls[1]); err != nil { return } } case 1: if randConv.begin, err = strconv.Atoi(sls[0]); err != nil { return } } dflr = randConv return } type RandomConverter struct { begin, end int } // Convert implements DataConverter interface func (rC *RandomConverter) Convert(in interface{}) ( out interface{}, err error) { if rC.begin == 0 { if rC.end == 0 { return rand.Int(), nil } else { return rand.Intn(rC.end), nil } } else { if rC.end == 0 { return rand.Int() + rC.begin, nil } else { return RandomInteger(rC.begin, rC.end), nil } } } // LengthConverter converts the interface in the unix time type LengthConverter struct{} // Convert implements DataConverter interface func (LengthConverter) Convert(in interface{}) (out interface{}, err error) { src := IfaceAsString(in) if strings.HasPrefix(src, IdxStart) && strings.HasSuffix(src, IdxEnd) { // it has a similar structure to a json marshaled slice var slice []interface{} if err := json.Unmarshal([]byte(src), &slice); err == nil { // no error when unmarshal safe to asume that this is a slice return len(slice), nil } } return len(src), nil }