oscar_conn_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package toc
  2. import (
  3. "bytes"
  4. "log/slog"
  5. "net"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/mk6i/open-oscar-server/wire"
  10. )
  11. func TestFLAPConn_readsWhileConsumerIsBusy(t *testing.T) {
  12. clientEnd, serverEnd := net.Pipe()
  13. server := wire.NewFlapClient(0, serverEnd, serverEnd)
  14. conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
  15. defer conn.Close()
  16. // nobody reads conn.Messages() yet, so each send returns only because the
  17. // connection keeps reading from the pipe
  18. for i := uint32(1); i <= 100; i++ {
  19. assert.NoError(t, server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted, RequestID: i},
  20. wire.SNAC_0x03_0x0C_BuddyDeparted{}))
  21. }
  22. for i := uint32(1); i <= 100; i++ {
  23. select {
  24. case msg := <-conn.Messages():
  25. assert.Equal(t, i, msg.Frame.RequestID)
  26. case <-time.After(time.Second):
  27. t.Fatalf("timed out waiting for SNAC %d", i)
  28. }
  29. }
  30. }
  31. // marshalSNAC returns the FLAP payload of a SNAC.
  32. func marshalSNAC(t *testing.T, frame wire.SNACFrame, body any) []byte {
  33. buf := &bytes.Buffer{}
  34. assert.NoError(t, wire.MarshalBE(frame, buf))
  35. assert.NoError(t, wire.MarshalBE(body, buf))
  36. return buf.Bytes()
  37. }
  38. func TestDecodeSNAC(t *testing.T) {
  39. cases := []struct {
  40. name string
  41. payload func(t *testing.T) []byte
  42. want wire.SNACMessage
  43. wantErr bool
  44. }{
  45. {
  46. name: "SNAC TOC handles",
  47. payload: func(t *testing.T) []byte {
  48. return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived, RequestID: 7},
  49. wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "them", WarningLevel: 10}})
  50. },
  51. want: newSNAC(wire.Buddy, wire.BuddyArrived, 7,
  52. wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "them", WarningLevel: 10}}),
  53. },
  54. {
  55. name: "relayed feedbag insert",
  56. payload: func(t *testing.T) []byte {
  57. return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  58. wire.SNAC_0x13_0x09_FeedbagUpdateItem{Items: []wire.FeedbagItem{{Name: "them"}}})
  59. },
  60. want: newSNAC(wire.Feedbag, wire.FeedbagInsertItem, 0,
  61. wire.SNAC_0x13_0x08_FeedbagInsertItem{Items: []wire.FeedbagItem{{Name: "them"}}}),
  62. },
  63. {
  64. name: "error in any food group",
  65. payload: func(t *testing.T) []byte {
  66. return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMErr, RequestID: 3},
  67. wire.SNACError{Code: wire.ErrorCodeNotLoggedOn})
  68. },
  69. want: newSNAC(wire.ICBM, wire.ICBMErr, 3, wire.SNACError{Code: wire.ErrorCodeNotLoggedOn}),
  70. },
  71. {
  72. name: "SNAC TOC doesn't handle has no body",
  73. payload: func(t *testing.T) []byte {
  74. return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Stats, SubGroup: wire.StatsSetMinReportInterval},
  75. wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{MinReportInterval: 1})
  76. },
  77. want: wire.SNACMessage{Frame: wire.SNACFrame{FoodGroup: wire.Stats, SubGroup: wire.StatsSetMinReportInterval}},
  78. },
  79. {
  80. name: "truncated SNAC frame",
  81. payload: func(t *testing.T) []byte {
  82. return []byte{0x00, 0x03}
  83. },
  84. wantErr: true,
  85. },
  86. {
  87. name: "truncated SNAC body",
  88. payload: func(t *testing.T) []byte {
  89. return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived}, []byte{0x05, 't'})
  90. },
  91. wantErr: true,
  92. },
  93. }
  94. for _, tc := range cases {
  95. t.Run(tc.name, func(t *testing.T) {
  96. got, err := decodeSNAC(tc.payload(t))
  97. if tc.wantErr {
  98. assert.Error(t, err)
  99. return
  100. }
  101. assert.NoError(t, err)
  102. assert.Equal(t, tc.want, got)
  103. })
  104. }
  105. }
  106. func TestFLAPConn(t *testing.T) {
  107. clientEnd, serverEnd := net.Pipe()
  108. server := wire.NewFlapClient(0, serverEnd, serverEnd)
  109. conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
  110. // SNACs sent to the server arrive intact
  111. go func() {
  112. assert.NoError(t, conn.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddBuddies, RequestID: 1},
  113. wire.SNAC_0x03_0x04_BuddyAddBuddies{}))
  114. }()
  115. frame := wire.SNACFrame{}
  116. assert.NoError(t, server.ReceiveSNAC(&frame, &wire.SNAC_0x03_0x04_BuddyAddBuddies{}))
  117. assert.Equal(t, wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddBuddies, RequestID: 1}, frame)
  118. // SNACs from the server are decoded and delivered in order
  119. assert.NoError(t, server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
  120. wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "first"}}))
  121. assert.NoError(t, server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
  122. wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "second"}}))
  123. for _, want := range []string{"first", "second"} {
  124. msg := <-conn.Messages()
  125. assert.Equal(t, want, msg.Body.(wire.SNAC_0x03_0x0C_BuddyDeparted).ScreenName)
  126. }
  127. // a signoff from the server closes the connection
  128. assert.NoError(t, server.NewSignoff(wire.TLVRestBlock{}))
  129. select {
  130. case <-conn.Closed():
  131. case <-time.After(time.Second):
  132. t.Fatal("connection didn't close after signoff")
  133. }
  134. _, err := server.ReceiveFLAP()
  135. assert.Error(t, err)
  136. }
  137. func TestFLAPConn_closesWhenConsumerFallsBehind(t *testing.T) {
  138. clientEnd, serverEnd := net.Pipe()
  139. server := wire.NewFlapClient(0, serverEnd, serverEnd)
  140. conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
  141. // nobody reads conn.Messages(), so its buffer fills up
  142. go func() {
  143. for {
  144. err := server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
  145. wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "them"}})
  146. if err != nil {
  147. return
  148. }
  149. }
  150. }()
  151. select {
  152. case <-conn.Closed():
  153. case <-time.After(5 * time.Second):
  154. t.Fatal("connection didn't close after its buffer filled up")
  155. }
  156. }
  157. func TestFLAPConn_Close(t *testing.T) {
  158. clientEnd, serverEnd := net.Pipe()
  159. conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
  160. conn.Close()
  161. conn.Close()
  162. _, err := wire.NewFlapClient(0, serverEnd, serverEnd).ReceiveFLAP()
  163. assert.Error(t, err)
  164. <-conn.Closed()
  165. }