txthinking/brook · error

Expired request

Error message

Expired request

What it means

The stream server's handshake embeds a 4-byte big-endian Unix timestamp in the authenticated request; NewStreamServer rejects the request if the timestamp is more than 60 seconds in the past. This is a replay-protection window: old captured handshakes cannot be reused. A rejected handshake leaves the connection to be closed via WaitReadErr.

Source

Thrown at streamserver.go:90

	s.ca, err = cipher.NewGCM(cb)
	if err != nil {
		x.BP12.Put(s.cn)
		return nil, err
	}

	s.RB = x.BP2048.Get().([]byte)
	l, err := s.Read()
	if err != nil {
		x.BP12.Put(s.cn)
		x.BP2048.Put(s.RB)
		return nil, err
	}
	i := int64(binary.BigEndian.Uint32(s.RB[2+16 : 2+16+4]))
	if time.Now().Unix()-i > 60 {
		x.BP12.Put(s.cn)
		x.BP2048.Put(s.RB)
		WaitReadErr(s.Client)
		return nil, errors.New("Expired request")
	}
	if i%2 == 0 {
		s.network = "tcp"
	}
	if i%2 == 1 {
		s.network = "udp"
		s.Timeout = udptimeout
	}

	s.sn = x.BP12.Get().([]byte)
	if _, err := io.ReadFull(rand.Reader, s.sn); err != nil {
		x.BP12.Put(s.cn)
		x.BP2048.Put(s.RB)
		x.BP12.Put(s.sn)
		return nil, err
	}
	sk := x.BP32.Get().([]byte)
	if _, err := io.ReadFull(hkdf.New(sha256.New, password, s.sn, ServerHKDFInfo), sk); err != nil {

View on GitHub (pinned to 5cd13ef3b1)

Solutions

  1. Sync the client clock with NTP and retry - the timestamp must be within 60s of the server clock
  2. Build the handshake immediately before sending; never cache a prepared request for reuse
  3. Check for replayed connections (e.g. a proxy re-sending a captured handshake) and generate a fresh one
  4. If clock skew is unavoidable in the environment, increase the 60s window in the server code

Example fix

// before: handshake built once at startup and reused
var hs = buildHandshake() // timestamp frozen
conn.Write(hs)

// after: rebuild per connection so the timestamp is fresh
conn.Write(buildHandshake()) // timestamp = time.Now().Unix()
Defensive patterns

Strategy: retry

Validate before calling

func tsFresh(b []byte) bool {
	i := int64(binary.BigEndian.Uint32(b[2+16 : 2+16+4]))
	return time.Now().Unix()-i <= 60
}

Try / catch

for attempt := 0; attempt < 2; attempt++ {
	c, err := NewStreamClient(...) // rebuild handshake each attempt
	if err == nil { break }
	ntp.Sync() // clock may have drifted
}

Prevention

When it happens

Trigger: A client embeds a stale timestamp in its handshake (clock skew > 60s between client and server, or a replayed/captured handshake), so time.Now().Unix()-i > 60 on the server.

Common situations: Client machine clock drifting (VM suspend, RTC drift, wrong timezone/NTP disabled); replaying a recorded handshake; queuing a pre-built handshake for more than a minute before sending.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of txthinking/brook@5cd13ef3b1 (2026-09-06). Data as JSON: /api/errors/82538fb6d3e0da83. Report an issue: GitHub.