diff --git a/diam/pretty_dump.go b/diam/pretty_dump.go index db530ca..663547a 100644 --- a/diam/pretty_dump.go +++ b/diam/pretty_dump.go @@ -3,6 +3,7 @@ package diam import ( "bytes" "fmt" + "io" "net" "strings" "time" @@ -13,16 +14,16 @@ import ( ) func (m *Message) PrettyDump() string { - return prettyDumpWithDepth(m, 0) -} - -func prettyDumpWithDepth(m *Message, depth int) string { var b bytes.Buffer + prettyDumpMessage(&b, m, 0) + return b.String() +} +func prettyDumpMessage(w io.Writer, m *Message, depth int) { requestFlag, errorFlag, proxyFlag, retransmittedFlag := flagsToString(m.Header) // Print Header - fmt.Fprintf(&b, "%s(%d) %s(%d) %s%s%s%s %d, %d\n", + fmt.Fprintf(w, "%s(%d) %s(%d) %s%s%s%s %d, %d\n", cmdToString(m.Dictionary(), m.Header), m.Header.CommandCode, appIdToString(int(m.Header.ApplicationID)), @@ -35,58 +36,39 @@ func prettyDumpWithDepth(m *Message, depth int) string { m.Header.EndToEndID) // Print Titles - fmt.Fprintf(&b, " %-40s %8s %5s %s %s %s %-18s %s\n", + fmt.Fprintf(w, " %-40s %8s %5s %s %s %s %-18s %s\n", "AVP", "Vendor", "Code", "V", "M", "P", "Type", "Value") - indent := strings.Repeat(" ", max(0, depth)) - + // Print AVPs 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)) - } + prettyDumpAVP(w, m, a, depth) } - - return b.String() } -func groupedAVPToString(m *Message, a *AVP, depth int) string { - var b bytes.Buffer +func prettyDumpGroupedAVP(w io.Writer, m *Message, a *AVP, depth int) { + for _, ga := range a.Data.(*GroupedAVP).AVP { + prettyDumpAVP(w, m, ga, depth) + } +} +func prettyDumpAVP(w io.Writer, m *Message, a *AVP, depth int) { 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)) - } - } + avpName, avpType, avpData, isGrouped := avpToString(m, a) - return b.String() + fmt.Fprintf(w, " %-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 { + prettyDumpGroupedAVP(w, m, a, depth+1) + } } func cmdToString(dictionary *dict.Parser, header *Header) string { diff --git a/diam/pretty_dump_test.go b/diam/pretty_dump_test.go index 781a660..0d62175 100644 --- a/diam/pretty_dump_test.go +++ b/diam/pretty_dump_test.go @@ -1,9 +1,12 @@ package diam import ( + "bytes" "net" + "strings" "testing" "time" + "unicode" "github.com/fiorix/go-diameter/v4/diam/avp" "github.com/fiorix/go-diameter/v4/diam/datatype" @@ -33,7 +36,7 @@ func TestPrettyDump(t *testing.T) { 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))), + NewAVP(avp.StartTime, 0, 10415, datatype.Time(time.Unix(1377093974, 0))), }, }), }}) @@ -43,6 +46,92 @@ func TestPrettyDump(t *testing.T) { // New PrettyDump() print t.Logf("Message:\n%s", msg.PrettyDump()) +} + +func TestPrettyDumpAVP(t *testing.T) { + tests := []struct { + name string + avp *AVP + expected string + }{ + { + name: "Unsigned32", + avp: NewAVP(avp.VendorID, avp.Mbit, 0, datatype.Unsigned32(13)), + expected: " Vendor-Id 0 266 ✗ ✓ ✗ Unsigned32 13", + }, + { + name: "UTF8String", + avp: NewAVP(avp.SessionID, avp.Mbit, 0, datatype.UTF8String("abc-1234567")), + expected: " Session-Id 0 263 ✗ ✓ ✗ UTF8String abc-1234567", + }, + { + name: "Address", + avp: NewAVP(avp.HostIPAddress, avp.Mbit, 0, datatype.Address(net.ParseIP("10.1.0.1"))), + expected: " Host-IP-Address 0 257 ✗ ✓ ✗ Address 10.1.0.1", + }, + { + name: "AddressIPv6", + avp: NewAVP(avp.GGSNAddress, avp.Mbit, 10415, datatype.Address(net.ParseIP("2001:0db8::ff00:0042:8329"))), + expected: " GGSN-Address 10415 847 ✓ ✓ ✗ Address 2001:db8::ff00:42:8329", + }, + { + name: "Enumerated", + avp: NewAVP(avp.CCRequestType, avp.Mbit, 0, datatype.Enumerated(1)), + expected: " CC-Request-Type 0 416 ✗ ✓ ✗ Enumerated 1", + }, + { + name: "GroupedAVP", + avp: 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")), + }, + }), + expected: strings.Join([]string{ + " Multiple-Services-Credit-Control 0 456 ✗ ✓ ✗ Grouped", + " Service-Identifier 0 439 ✗ ✓ ✗ Unsigned32 7786", + " Rating-Group 0 432 ✗ ✓ ✗ Unsigned32 7786", + " TGPP-RAT-Type 10415 21 ✓ ✓ ✗ OctetString 1234", + }, "\n"), + }, + { + name: "NestedGroupedAVP", + avp: 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, 0, 10415, datatype.Time(time.Date(2023, 8, 21, 22, 06, 14, 0, time.UTC))), + }, + }), + }}), + expected: strings.Join([]string{ + " Service-Information 10415 873 ✓ ✓ ✗ Grouped", + " PS-Information 10415 874 ✓ ✓ ✗ Grouped", + " Called-Station-Id 0 30 ✗ ✓ ✗ UTF8String 10999", + " Start-Time 10415 2041 ✓ ✗ ✗ Time 2023-08-21 22:06:14 +0000 UTC", + }, "\n"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + msg := NewMessage(CreditControl, RequestFlag, CHARGING_CONTROL_APP_ID, 0xa8cc407d, 0xa8c1b2b4, dict.Default) + + var b bytes.Buffer + prettyDumpAVP(&b, msg, tc.avp, 0) + + lines := strings.Split(b.String(), "\n") + for i, line := range lines { + lines[i] = strings.TrimRightFunc(line, unicode.IsSpace) + } + actual := strings.Join(lines, "\n") + actual = strings.TrimSuffix(actual, "\n") - // TODO Maybe make PrettyDump() testable and assert the output + if actual != tc.expected { + t.Errorf("\nActual:\n%v\nExpected:\n%v\n", actual, tc.expected) + } + }) + } }