Skip to content

Commit

Permalink
add diam.Message.PrettyDump()
Browse files Browse the repository at this point in the history
  • Loading branch information
lwlee2608 authored and fiorix committed Nov 16, 2023
1 parent 121fcc7 commit 845be29
Show file tree
Hide file tree
Showing 2 changed files with 313 additions and 0 deletions.
265 changes: 265 additions & 0 deletions diam/pretty_dump.go
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
}
48 changes: 48 additions & 0 deletions diam/pretty_dump_test.go
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
}

0 comments on commit 845be29

Please sign in to comment.