Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

beacon

A minimal, allocation-conscious UDP broadcast discovery package for Go.

Beacon provides a simple primitive for discovering services on a local network. It is built entirely around standard library components, using pure functions to handle broadcasting a probe and listening for replies. It manages the network lifecycle explicitly, without long-lived background workers, hidden state, or forced serialization formats.

Design

  • Standard Library Only: Beacon relies exclusively on net and context. There are no external dependencies.
  • Zero Read Allocations: You provide the byte buffers. Beacon performs zero hidden allocations during the read loop.
  • Pure Functions: Control flow is explicit. The package does not manage long-lived state or persistent background daemon workers. The only concurrency used is strictly bound to the execution lifecycle of the function to handle context cancellation.
  • Agnostic Payloads: Beacon transports raw bytes. Serialization, handshakes, and retry logic are left to the caller.

Installation

go get lowbit.dev/beacon

Usage

Beacon exposes exactly two pure functions: Seek and Serve.

The Responder

The responder blocks and listens for incoming UDP broadcasts. It evaluates incoming bytes purely on the stack using a caller-provided validation function.

package main

import (
	"bytes"
	"context"
	"log"
	
	"lowbit.dev/beacon"
)

func main() {
	ctx := context.Background()
	
	// Pre-allocate a single buffer for the entire lifecycle of the server.
	buf := make([]byte, 1024)
	
	// Define your probe and payload.
	expectedProbe := []byte("PING")
	myAddress := []byte("192.168.1.50:8080")

	// The validation function allows you to reject bad probes cheaply.
	isValid := func(probe []byte) bool {
		return bytes.Equal(probe, expectedProbe)
	}

	log.Println("Listening for broadcasts on :9999...")
	
	// Serve blocks until the context is canceled or an unhandled network error occurs.
	err := beacon.Serve(ctx, ":9999", buf, isValid, myAddress)
	if err != nil {
		log.Fatalf("Server exited: %v", err)
	}
}

The Seeker

The seeker broadcasts a payload to a target address (typically the broadcast address of the local network) and waits for a reply. Context is used for timeouts, forcing the network read to unblock cleanly.

package main

import (
	"context"
	"errors"
	"log"
	"time"

	"lowbit.dev/beacon"
)

func main() {
	// Enforce a strict timeout on the discovery process.
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	probe := []byte("PING")
	buf := make([]byte, 1024)

	// Broadcast to the local network on the agreed port.
	n, addr, err := beacon.Seek(ctx, "255.255.255.255:9999", probe, buf)
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			log.Fatal("Discovery timed out: no manager found.")
		}
		log.Fatalf("Discovery failed: %v", err)
	}

	payload := string(buf[:n])
	log.Printf("Found manager at %s. Payload: %s\n", addr.String(), payload)
}

Error Handling

Beacon exposes all failure modes using standard Go sentinel errors. You can inspect failures using errors.Is().

  • beacon.ErrInvalidAddr
  • beacon.ErrListen
  • beacon.ErrWriteProbe
  • beacon.ErrReadProbe
  • beacon.ErrWriteReply
  • beacon.ErrReadResponse

If Serve encounters a write error while attempting to reply to a client, it will return beacon.ErrWriteReply. The loop will exit, and it is up to the caller to decide how to handle the failure (e.g., wrap Serve in a for loop to retry, log the error, or crash the application).

About

A minimal, UDP broadcast discovery package for Go.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages