Tapir provides a framework for building Anonymous / metadata resistant Services
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
3.9 KiB

package tapir
import (
// Service defines the interface for a Tapir Service
type Service interface {
Init(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity)
Connect(hostname string, application Application) (bool, error)
Listen(application Application) error
GetConnection(connectionID string) (*Connection, error)
WaitForCapabilityOrClose(connectionID string, capability string) (*Connection, error)
// Connection defines a Tapir Connection
type Connection struct {
Hostname string
conn net.Conn
capabilities sync.Map
encrypted bool
key [32]byte
App Application
ID identity.Identity
Outbound bool
Closed bool
MaxLength int
// NewConnection creates a new Connection
func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) *Connection {
connection := new(Connection)
connection.Hostname = hostname
connection.conn = conn
connection.App = app
connection.ID = id
connection.Outbound = outbound
connection.MaxLength = 1024
go connection.App.Init(connection)
return connection
// SetHostname sets the hostname on the connection
func (c *Connection) SetHostname(hostname string) {
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.Hostname, hostname)
c.Hostname = hostname
// SetCapability sets a capability on the connection
func (c *Connection) SetCapability(name string) {
log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.Hostname, name)
c.capabilities.Store(name, true)
// HasCapability checks if the connection has a given capability
func (c *Connection) HasCapability(name string) bool {
_, ok := c.capabilities.Load(name)
return ok
// Close forcibly closes the connection
func (c *Connection) Close() {
// Expect blocks and reads a single Tapir packet , from the connection.
func (c *Connection) Expect() []byte {
buffer := make([]byte, c.MaxLength)
n, err := io.ReadFull(c.conn, buffer)
if n != c.MaxLength || err != nil {
log.Errorf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.Hostname, c.ID.Hostname(), n, err)
c.Closed = true
return []byte{}
if c.encrypted {
var decryptNonce [24]byte
copy(decryptNonce[:], buffer[:24])
decrypted, ok := secretbox.Open(nil, buffer[24:], &decryptNonce, &c.key)
if ok {
copy(buffer, decrypted)
} else {
log.Errorf("[%v -> %v] Error Decrypting Message On Wire", c.Hostname, c.ID.Hostname())
c.Closed = true
return []byte{}
len, _ := binary.Uvarint(buffer[0:2])
//cplog.Debugf("[%v -> %v] Wire Receive: (%d) %x", c.hostname, c.ID.Hostname(), len, buffer)
return buffer[2 : len+2]
// SetEncryptionKey turns on application-level encryption on the connection using the given key.
func (c *Connection) SetEncryptionKey(key [32]byte) {
c.key = key
c.encrypted = true
// Send writes a given message to a Tapir packet (of 1024 bytes in length).
func (c *Connection) Send(message []byte) {
buffer := make([]byte, c.MaxLength)
binary.PutUvarint(buffer[0:2], uint64(len(message)))
copy(buffer[2:], message)
if c.encrypted {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
// TODO: Surface is Error
c.Closed = true
// MaxLength - 40 = MaxLength - 24 nonce bytes and 16 auth tag.
encrypted := secretbox.Seal(nonce[:], buffer[0:c.MaxLength-40], &nonce, &c.key)
copy(buffer, encrypted[0:c.MaxLength])
log.Debugf("[%v -> %v] Wire Send %x", c.ID.Hostname(), c.Hostname, buffer)
_, err := c.conn.Write(buffer)
if err != nil {
c.Closed = true