remote_envelope_test.go 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. package runtime
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. "net/url"
  8. "strings"
  9. "testing"
  10. "github.com/mhsanaei/3x-ui/v3/internal/util/wirecodec"
  11. )
  12. // TestRemoteSendsEnvelopeWhenNodeAdvertisesCap: once a node has advertised the
  13. // zstd capability (via a response header on any prior call), a large push is
  14. // sent zstd-compressed with an X-Config-Sha256 of the *uncompressed* body.
  15. func TestRemoteSendsEnvelopeWhenNodeAdvertisesCap(t *testing.T) {
  16. var capturedEnc, capturedHash string
  17. var capturedBody []byte
  18. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  19. w.Header().Set(wirecodec.CapsHeader, wirecodec.CapZstd) // advertise on every response
  20. if r.Method == http.MethodPost {
  21. capturedEnc = r.Header.Get("Content-Encoding")
  22. capturedHash = r.Header.Get(wirecodec.HashHeader)
  23. capturedBody, _ = io.ReadAll(r.Body)
  24. }
  25. w.Header().Set("Content-Type", "application/json")
  26. _, _ = w.Write([]byte(`{"success":true}`))
  27. }))
  28. defer srv.Close()
  29. r := NewRemote(nodeForPlainServer(t, srv, "verify", "tok"), nil)
  30. // Prime: a prior call learns the cap from the response header.
  31. if _, err := r.do(context.Background(), http.MethodGet, "ping", nil); err != nil {
  32. t.Fatalf("prime call: %v", err)
  33. }
  34. body := url.Values{}
  35. body.Set("settings", strings.Repeat("x", 4096))
  36. if _, err := r.do(context.Background(), http.MethodPost, "panel/api/inbounds/add", body); err != nil {
  37. t.Fatalf("push: %v", err)
  38. }
  39. if capturedEnc != wirecodec.EncodingZstd {
  40. t.Fatalf("Content-Encoding = %q, want %q", capturedEnc, wirecodec.EncodingZstd)
  41. }
  42. if len(capturedHash) != 64 {
  43. t.Fatalf("missing/short X-Config-Sha256: %q", capturedHash)
  44. }
  45. raw, err := wirecodec.Decompress(capturedBody, 1<<20)
  46. if err != nil {
  47. t.Fatalf("server could not decompress the body: %v", err)
  48. }
  49. if string(raw) != body.Encode() {
  50. t.Fatalf("decompressed body mismatch: %q != %q", string(raw), body.Encode())
  51. }
  52. if wirecodec.Sha256Hex(raw) != capturedHash {
  53. t.Fatal("X-Config-Sha256 does not match the decompressed body")
  54. }
  55. }
  56. // TestRemoteSendsPlainWhenNoCap: a node that never advertises the cap (old
  57. // build) receives a plain body — but the integrity hash is still attached
  58. // (harmless to old nodes, verified by new ones).
  59. func TestRemoteSendsPlainWhenNoCap(t *testing.T) {
  60. var capturedEnc, capturedHash string
  61. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  62. if r.Method == http.MethodPost {
  63. capturedEnc = r.Header.Get("Content-Encoding")
  64. capturedHash = r.Header.Get(wirecodec.HashHeader)
  65. }
  66. w.Header().Set("Content-Type", "application/json")
  67. _, _ = w.Write([]byte(`{"success":true}`))
  68. }))
  69. defer srv.Close()
  70. r := NewRemote(nodeForPlainServer(t, srv, "verify", "tok"), nil)
  71. body := url.Values{}
  72. body.Set("settings", strings.Repeat("x", 4096))
  73. if _, err := r.do(context.Background(), http.MethodPost, "panel/api/inbounds/add", body); err != nil {
  74. t.Fatalf("push: %v", err)
  75. }
  76. if capturedEnc != "" {
  77. t.Fatalf("a no-cap node must receive a plain body, got Content-Encoding=%q", capturedEnc)
  78. }
  79. if len(capturedHash) != 64 {
  80. t.Fatalf("integrity hash should always be sent, got %q", capturedHash)
  81. }
  82. }