forked from fiorix/go-diameter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
package diam | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"net" | ||
"strings" | ||
"time" | ||
|
||
"github.com/fiorix/go-diameter/v4/diam/avp" | ||
"github.com/fiorix/go-diameter/v4/diam/datatype" | ||
"github.com/fiorix/go-diameter/v4/diam/dict" | ||
) | ||
|
||
func (m *Message) PrettyDump() string { | ||
return prettyDumpWithDepth(m, 0) | ||
} | ||
|
||
func prettyDumpWithDepth(m *Message, depth int) string { | ||
var b bytes.Buffer | ||
|
||
requestFlag, errorFlag, proxyFlag, retransmittedFlag := flagsToString(m.Header) | ||
|
||
// Print Header | ||
fmt.Fprintf(&b, "%s(%d) %s(%d) %s%s%s%s %d, %d\n", | ||
cmdToString(m.Dictionary(), m.Header), | ||
m.Header.CommandCode, | ||
appIdToString(int(m.Header.ApplicationID)), | ||
m.Header.ApplicationID, | ||
requestFlag, | ||
errorFlag, | ||
proxyFlag, | ||
retransmittedFlag, | ||
m.Header.HopByHopID, | ||
m.Header.EndToEndID) | ||
|
||
// Print Titles | ||
fmt.Fprintf(&b, " %-40s %8s %5s %s %s %s %-18s %s\n", | ||
"AVP", "Vendor", "Code", "V", "M", "P", "Type", "Value") | ||
|
||
indent := strings.Repeat(" ", max(0, depth)) | ||
|
||
for _, a := range m.AVP { | ||
avpName, avpType, avpData, isGrouped := avpToString(m, a) | ||
|
||
// Print AVPs | ||
fmt.Fprintf(&b, " %-40s %8d %5d %s %s %s %-18s %s\n", | ||
indent+avpName, | ||
a.VendorID, | ||
a.Code, | ||
boolToSymbol(a.Flags&avp.Vbit == avp.Vbit), | ||
boolToSymbol(a.Flags&avp.Mbit == avp.Mbit), | ||
boolToSymbol(a.Flags&avp.Pbit == avp.Pbit), | ||
avpType, | ||
avpData) | ||
|
||
if isGrouped { | ||
fmt.Fprintf(&b, "%s", groupedAVPToString(m, a, depth+1)) | ||
} | ||
} | ||
|
||
return b.String() | ||
} | ||
|
||
func groupedAVPToString(m *Message, a *AVP, depth int) string { | ||
var b bytes.Buffer | ||
|
||
indent := strings.Repeat(" ", max(0, depth)) | ||
|
||
for _, ga := range a.Data.(*GroupedAVP).AVP { | ||
avpName, avpType, avpData, isGrouped := avpToString(m, ga) | ||
|
||
// Print Grouped AVPs | ||
fmt.Fprintf(&b, " %-40s %8d %5d %s %s %s %-18s %s\n", | ||
indent+avpName, | ||
ga.VendorID, | ||
ga.Code, | ||
boolToSymbol(a.Flags&avp.Vbit == avp.Vbit), | ||
boolToSymbol(a.Flags&avp.Mbit == avp.Mbit), | ||
boolToSymbol(a.Flags&avp.Pbit == avp.Pbit), | ||
avpType, | ||
avpData) | ||
|
||
if isGrouped { | ||
fmt.Fprintf(&b, "%s", groupedAVPToString(m, ga, depth+1)) | ||
} | ||
} | ||
|
||
return b.String() | ||
} | ||
|
||
func cmdToString(dictionary *dict.Parser, header *Header) string { | ||
if dictCMD, err := dictionary.FindCommand( | ||
header.ApplicationID, | ||
header.CommandCode, | ||
); err != nil { | ||
return "Unknown" | ||
} else { | ||
return dictCMD.Name | ||
} | ||
} | ||
|
||
func appIdToString(appId int) string { | ||
switch appId { | ||
case BASE_APP_ID: | ||
return "Common" | ||
case NETWORK_ACCESS_APP_ID: | ||
return "Network-Access" | ||
case BASE_ACCOUNTING_APP_ID: | ||
return "Accounting" | ||
case CHARGING_CONTROL_APP_ID: | ||
return "Charging-Control" | ||
//case TGPP_APP_ID: | ||
// return "TGPP_APP_ID" | ||
case GX_CHARGING_CONTROL_APP_ID: | ||
return "Gx" | ||
case TGPP_S6A_APP_ID: | ||
return "S6A" | ||
case TGPP_SWX_APP_ID: | ||
return "SWX" | ||
case DIAMETER_SY_APP_ID: | ||
return "Sy" | ||
default: | ||
return "Unknown" | ||
} | ||
} | ||
|
||
func flagsToString(header *Header) (string, string, string, string) { | ||
var requestFlag string | ||
if header.CommandFlags&RequestFlag == RequestFlag { | ||
requestFlag = "request" | ||
} else { | ||
requestFlag = "answer" | ||
} | ||
|
||
var errorFlag string | ||
if header.CommandFlags&ErrorFlag == ErrorFlag { | ||
errorFlag = "error" | ||
} else { | ||
errorFlag = "" | ||
} | ||
|
||
var proxyFlag string | ||
if header.CommandFlags&ProxiableFlag == ProxiableFlag { | ||
proxyFlag = "proxiable" | ||
} else { | ||
proxyFlag = "" | ||
} | ||
|
||
var retransmittedFlag string | ||
if header.CommandFlags&RetransmittedFlag == RetransmittedFlag { | ||
retransmittedFlag = "retransmitted" | ||
} else { | ||
retransmittedFlag = "" | ||
} | ||
|
||
return requestFlag, errorFlag, proxyFlag, retransmittedFlag | ||
} | ||
|
||
func avpToString(m *Message, a *AVP) (string, string, string, bool) { | ||
|
||
var avpName string | ||
var avpType string | ||
var avpData string | ||
var isGrouped bool | ||
|
||
if dictAVP, err := m.Dictionary().FindAVPWithVendor( | ||
m.Header.ApplicationID, | ||
a.Code, | ||
a.VendorID, | ||
); err != nil { | ||
avpName = "Unknown" | ||
avpType = "Unknown" | ||
avpData = a.Data.String() | ||
isGrouped = false | ||
} else if a.Data.Type() == GroupedAVPType { | ||
avpName = dictAVP.Name | ||
avpType = "Grouped" | ||
avpData = "" | ||
isGrouped = true | ||
} else { | ||
for k, v := range datatype.Available { | ||
if v == a.Data.Type() { | ||
avpType = k | ||
break | ||
} | ||
} | ||
avpName = dictAVP.Name | ||
avpData = dataValueToString(a.Data) | ||
isGrouped = false | ||
} | ||
|
||
return avpName, avpType, avpData, isGrouped | ||
} | ||
|
||
func dataValueToString(data datatype.Type) string { | ||
|
||
switch data.Type() { | ||
case datatype.Integer32Type, | ||
datatype.Integer64Type, | ||
datatype.Unsigned32Type, | ||
datatype.Unsigned64Type, | ||
datatype.EnumeratedType: | ||
return fmt.Sprintf("%d", data) | ||
|
||
case datatype.Float32Type, | ||
datatype.Float64Type: | ||
return fmt.Sprintf("%0.4f", data) | ||
|
||
case datatype.OctetStringType: | ||
return string(data.(datatype.OctetString)) | ||
|
||
case datatype.UTF8StringType: | ||
return string(data.(datatype.UTF8String)) | ||
|
||
case datatype.DiameterIdentityType: | ||
return string(data.(datatype.DiameterIdentity)) | ||
|
||
case datatype.DiameterURIType: | ||
return string(data.(datatype.DiameterURI)) | ||
|
||
case datatype.IPFilterRuleType: | ||
return string(data.(datatype.IPFilterRule)) | ||
|
||
case datatype.QoSFilterRuleType: | ||
return string(data.(datatype.QoSFilterRule)) | ||
|
||
case datatype.TimeType: | ||
return fmt.Sprintf("%s", time.Time(data.(datatype.Time))) | ||
|
||
case datatype.AddressType: | ||
addr := string(data.(datatype.Address)) | ||
if ip4 := net.IP(addr).To4(); ip4 != nil { | ||
return fmt.Sprintf("%s", net.IP(addr)) | ||
} | ||
if ip6 := net.IP(addr).To16(); ip6 != nil { | ||
return fmt.Sprintf("%s", net.IP(addr)) | ||
} | ||
return fmt.Sprintf("%#v, %#v", addr[2:], addr[:2]) | ||
|
||
case datatype.IPv4Type: | ||
addr := string(data.(datatype.IPv4)) | ||
return fmt.Sprintf("%s", net.IP(addr)) | ||
|
||
case datatype.IPv6Type: | ||
addr := string(data.(datatype.IPv6)) | ||
return fmt.Sprintf("%s", net.IP(addr)) | ||
} | ||
|
||
return data.String() | ||
} | ||
|
||
func boolToSymbol(flag bool) string { | ||
if flag { | ||
return "\u2713" // ✓ | ||
} | ||
return "\u2717" // ✗ | ||
} | ||
|
||
func max(x, y int) int { | ||
if x > y { | ||
return x | ||
} | ||
return y | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package diam | ||
|
||
import ( | ||
"net" | ||
"testing" | ||
"time" | ||
|
||
"github.com/fiorix/go-diameter/v4/diam/avp" | ||
"github.com/fiorix/go-diameter/v4/diam/datatype" | ||
"github.com/fiorix/go-diameter/v4/diam/dict" | ||
) | ||
|
||
func TestPrettyDump(t *testing.T) { | ||
|
||
msg := NewMessage(CreditControl, RequestFlag, CHARGING_CONTROL_APP_ID, 0xa8cc407d, 0xa8c1b2b4, dict.Default) | ||
msg.NewAVP(avp.OriginHost, avp.Mbit, 0, datatype.DiameterIdentity("test")) | ||
msg.NewAVP(avp.OriginRealm, avp.Mbit, 0, datatype.DiameterIdentity("localhost")) | ||
msg.NewAVP(avp.HostIPAddress, avp.Mbit, 0, datatype.Address(net.ParseIP("10.1.0.1"))) | ||
msg.NewAVP(avp.VendorID, avp.Mbit, 0, datatype.Unsigned32(13)) | ||
msg.NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("sess;123456789")) | ||
msg.NewAVP(avp.OriginStateID, avp.Mbit, 0, datatype.Unsigned32(1397760650)) | ||
msg.NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(1)) | ||
msg.NewAVP(avp.CCRequestNumber, avp.Mbit, 0, datatype.Unsigned32(1000)) | ||
msg.NewAVP(avp.MultipleServicesCreditControl, avp.Mbit, 0, &GroupedAVP{ | ||
AVP: []*AVP{ | ||
NewAVP(avp.ServiceIdentifier, avp.Mbit, 0, datatype.Unsigned32(7786)), | ||
NewAVP(avp.RatingGroup, avp.Mbit, 0, datatype.Unsigned32(7786)), | ||
NewAVP(avp.TGPPRATType, avp.Mbit, 10415, datatype.OctetString("1234")), | ||
}, | ||
}) | ||
msg.NewAVP(avp.ServiceInformation, avp.Mbit, 10415, &GroupedAVP{ | ||
AVP: []*AVP{ | ||
NewAVP(avp.PSInformation, avp.Mbit, 10415, &GroupedAVP{ | ||
AVP: []*AVP{ | ||
NewAVP(avp.CalledStationID, avp.Mbit, 0, datatype.UTF8String("10999")), | ||
NewAVP(avp.StartTime, avp.Mbit, 10415, datatype.Time(time.Unix(1377093974, 0))), | ||
}, | ||
}), | ||
}}) | ||
|
||
// Existing String() print | ||
t.Logf("Message:\n%s", msg) | ||
|
||
// New PrettyDump() print | ||
t.Logf("Message:\n%s", msg.PrettyDump()) | ||
|
||
// TODO Maybe make PrettyDump() testable and assert the output | ||
} |