This directory contains implementations of the SPOCP TCP protocol as specified in docs/draft-hedberg-spocp-tcp-00.txt.
- Implements the length-value (LV) encoding format
- Message encoding/decoding
- Response handling
- S-expression parsing integration
- TCP server with optional TLS support
- Dynamic rule loading from
.spocfiles - Automatic rule reloading
- Concurrent client handling
- Graceful shutdown
- TCP client with optional TLS support
- Simple API for QUERY, ADD, and RELOAD operations
- Connection management
# Build the server
go build -o spocpd ./cmd/spocpd
# Start with a rules directory
./spocpd -rules ./examples/rules
# With TLS
./spocpd -rules ./examples/rules \
-tls-cert server.crt \
-tls-key server.key
# With auto-reload every 5 minutes
./spocpd -rules ./examples/rules -reload 5m
# Build the client
go build -o spocp-client ./cmd/spocp-client
# Interactive mode
./spocp-client -addr localhost:6000
# Single query
./spocp-client -query '(4:http(4:page10:index.html)(6:action3:GET)(6:userid4:john))'
# Add a rule
./spocp-client -add '(4:http(4:page8:test.php)(6:action3:GET)(6:userid))'
# With TLS
./spocp-client -addr localhost:6000 -tls
Rule files must have a .spoc extension and contain canonical S-expressions, one per line.
Example (rules/http.spoc):
(4:http(4:page10:index.html)(6:action3:GET)(6:userid))
(4:http(4:page9:admin.php)(6:action)(6:userid5:admin))
Comments are supported (lines starting with #, //, or ;):
# HTTP access rules
(4:http(4:page10:index.html)(6:action3:GET)(6:userid))
// Admin access
(4:http(4:page9:admin.php)(6:action)(6:userid5:admin))
-addr string
Address to listen on (default ":6000")
-rules string
Directory containing .spoc rule files (required)
-tls-cert string
Path to TLS certificate file (optional)
-tls-key string
Path to TLS private key file (optional)
-reload duration
Auto-reload interval (e.g., 5m, 1h) - 0 to disable (default 0)
-addr string
Server address (default "localhost:6000")
-tls
Use TLS
-insecure
Skip TLS certificate verification
-query string
Execute single query and exit
-add string
Add single rule and exit
Check if a query matches any rule in the engine.
Request:
70:5:QUERY60:(4:http(4:page10:index.html)(6:action3:GET)(6:userid4:olav))
Response:
9:3:2002:Ok- Query matched11:3:4007:Denied- Query did not match
Add a new rule to the engine.
Request:
49:3:ADD41:(4:http(4:page)(6:action3:GET)(6:userid))
Response:
9:3:2002:Ok- Rule added successfully
Reload all rules from the rules directory (custom extension).
Request:
9:6:RELOAD
Response:
14:3:20010:Reloaded- Rules reloaded successfully
Close the connection gracefully.
Request:
8:6:LOGOUT
Response:
10:3:2033:Bye- Connection closing
Generate self-signed certificates for testing:
# Generate private key
openssl genrsa -out server.key 2048
# Generate certificate
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=localhost"
Start server with TLS:
./spocpd -rules ./examples/rules -tls-cert server.crt -tls-key server.key
Connect with client:
# With proper certificate
./spocp-client -tls
# Skip verification (testing only)
./spocp-client -tls -insecure
The server supports two modes of rule reloading:
# Reload every 5 minutes
./spocpd -rules ./examples/rules -reload 5m
Send a RELOAD command from the client:
./spocp-client
> reload
✓ Server rules reloaded
Or programmatically:
client.Reload()
package main
import (
"log"
"github.com/sirosfoundation/go-spocp/pkg/server"
)
func main() {
config := &server.Config{
Address: ":6000",
RulesDir: "./rules",
}
srv, err := server.NewServer(config)
if err != nil {
log.Fatal(err)
}
log.Fatal(srv.Serve())
}
package main
import (
"fmt"
"log"
"github.com/sirosfoundation/go-spocp/pkg/client"
)
func main() {
config := &client.Config{
Address: "localhost:6000",
}
c, err := client.NewClient(config)
if err != nil {
log.Fatal(err)
}
defer c.Close()
// Query
result, err := c.QueryString("(4:http(4:page10:index.html)(6:action3:GET)(6:userid4:john))")
if err != nil {
log.Fatal(err)
}
if result {
fmt.Println("Query matched!")
} else {
fmt.Println("Query denied")
}
// Add rule
err = c.AddString("(4:http(4:page8:new.html)(6:action3:GET)(6:userid))")
if err != nil {
log.Fatal(err)
}
}
- The server uses tag-based indexing for efficient rule matching
- Concurrent clients are handled in separate goroutines
- Rule reloading creates a new engine and swaps atomically (no downtime)
- Connection pooling is recommended for high-throughput applications
- Always use TLS in production - The protocol sends rules and queries in clear text
- Validate certificates - Don't use
-insecurein production - Firewall - Limit access to the SPOCP port
- Authentication - The protocol doesn't include authentication; use TLS client certificates or a reverse proxy
- Rule validation - Invalid rules are logged but don't crash the server
For complete protocol details, see:
docs/draft-hedberg-spocp-tcp-00.txt- TCP protocol specificationdocs/draft-hedberg-spocp-sexp-00.txt- S-expression format
- Check if server is running:
netstat -an | grep 6000 - Verify firewall rules
- Check server logs
- Verify certificate and key files exist
- Check certificate validity:
openssl x509 -in server.crt -text -noout - Ensure client and server TLS settings match
- Check file extension is
.spoc - Verify file permissions
- Check server logs for parse errors
- Validate S-expression syntax
- Large rulesets require more memory
- Consider splitting rules across multiple servers
- Monitor with
./spocpd -reload 0to disable auto-reload
Run protocol tests:
go test ./pkg/protocol
go test ./pkg/server
go test ./pkg/client
- FILE_LOADING.md - File format details
- ADAPTIVE_ENGINE.md - Engine performance
- OPTIMIZATION_SUMMARY.md - Performance tuning