Skip to content

Node Application API documentation

Erich Kästner edited this page Apr 8, 2019 · 2 revisions

App Programming Interface

App programming interface (located within the skywire/pkg/app module) should expose methods for Apps to connect to a piped connection, perform handshake and exchange data with remote nodes.

App interface should expose following methods:

// Addr implements net.Addr for App connections.
type Addr struct {
	PubKey transport.PubKey
	Port   uint16
}

// LoopAddr stores addressing parameters of a loop package.
type LoopAddr struct {
	Port   uint16
	Remote Addr
}

// Packet represents message exchanged between App and Node.
type Packet struct {
	Addr    *LoopAddr
	Payload []byte
}

// Config defines configuration parameters for an App
type Config struct {
	AppName         string
	AppVersion      string
	ProtocolVersion string
}

// Setup sets up an app using default pair of pipes and performs handshake.
func Setup(config *Config) (*App, error) {}

// Accept awaits for incoming loop confirmation request from a Node and
// returns net.Conn for a received loop.
func (app *App) Accept() (net.Conn, error) {}

// Dial sends create loop request to a Node and returns net.Conn for created loop.
func (app *App) Dial(raddr *Addr) (net.Conn, error) {}

// Addr returns empty Addr, implements net.Listener.
func (app *App) Addr() net.Addr {}

// Close implements io.Closer for an App.
func (app *App) Close() error {}

App to Node Communication protocol

Communication between Node and an App happens over the piped connection using binary multiplexed protocol.

The following is the expected format of a App Packet:

| Packet Len | Type   | Message ID | JSON Body |
| 2 bytes    | 1 byte | 1 byte     | ~         |
  • Packet Len specifies the total packet length in bytes (exclusive of the Packet Len field).
  • Type specifies the App Packet Type.
  • Message ID specifies multiplexing ID of a message, response for this message should contain the same ID.
  • JSON Body is the packet body (in JSON format) that is unique depending on the packet type.

App Packet Types Summary:

Type Name
0x00 Init
0x01 CreateLoop
0x02 ConfirmLoop
0x03 Send
0x04 Close
0xfe ResponseFailure
0xff ResponseSuccess

0x00 Init

Sent by an App to a Node. This packet is used to handshake connection between an App and a Node. Node will typically check if app is allowed by the config file and which port should be statically allocated it.

JSON Body:

{
    "app-name": "foo",
    "app-version": "0.0.1",
    "protocol-version": "0.0.1"
}

Response:

  • ResponseFailure with error.
  • ResponseSuccess without body.

0x01 CreateLoop

Sent by an App to a Node. This packet is used to open new Loop to a remote Node.

JSON Body:

{
    "pk": "<remote-pk>",
    "port": <remote-port>
}

Response:

  • ResponseFailure with error.
  • ResponseSuccess with
    {
        "pk": "<local-pk>",
        "port": <local-port>
    }

0x02 ConfirmLoop

Sent by a Node to an App to notify about request to open new Loop from a remote Node

JSON Body:

[
    {
        "pk": "<local-pk>",
        "port": <local-port>
    },
    {
        "pk": "<remote-pk>",
        "port": <remote-port>
    }
]

Response:

  • ResponseFailure with error.
  • ResponseSuccess with empty body.

0x03 Send

Sent by a Node and an App. This message is used to exchange messages through a previously established Loop.

JSON Body:

{
    "addr": {
        "port": <local-port>,
        "remote": {
            "pk": "<remote-pk>",
            "port": <remote-port>
        }
    },
    "payload": "<binary-data>"
}

Response:

  • ResponseFailure with error.
  • ResponseSuccess with empty body.

0x04 Close

Sent by a Node and an App. App uses this message to notify about closed Loop. Node sends this message after remote node is requested to close established Loop.

JSON Body:

{
    "port": <local-port>,
    "remote": {
        "pk": "<remote-pk>",
        "port": <remote-port>
    }
}

Response:

  • ResponseFailure with error.
  • ResponseSuccess with empty body.

App Example

Simple ping-pong client and server apps can be implemented in such way:

Server:

package server

import (
	"log"

	"github.com/watercompany/skywire/pkg/app"
)

func main() {
    // Open connection with Node
	helloworldApp, err := app.Setup(&app.Config{AppName: "helloworld-server", AppVersion: "1.0", ProtocolVersion: "0.0.1"})
	if err != nil {
		log.Fatal("Setup failure: ", err)
	}
	defer helloworldApp.Close()

	log.Println("listening for incoming connections")
    // Start listening loop
	for {
        // Wait for new Loop
		conn, err := helloworldApp.Accept()
		if err != nil {
			log.Fatal("Failed to accept conn: ", err)
		}

		log.Println("got new connection from:", conn.RemoteAddr())
        // Handle incoming connection
		go func() {
			buf := make([]byte, 4)
			if _, err := conn.Read(buf); err != nil {
				log.Println("Failed to read remote data: ", err)
			}

			log.Printf("Message from %s: %s", conn.RemoteAddr().String(), string(buf))
			if _, err := conn.Write([]byte("pong")); err != nil {
				log.Println("Failed to write to a remote node: ", err)
			}
		}()
	}
}

Client:

package server

import (
	"log"
	"os"

	"github.com/watercompany/skywire/pkg/app"
"github.com/watercompany/skywire/pkg/cipher"
)

func main() {
    // Open connection with Node
	helloworldApp, err := app.Setup(&app.Config{AppName: "helloworld-client", AppVersion: "1.0", ProtocolVersion: "0.0.1"})
	if err != nil {
		log.Fatal("Setup failure: ", err)
	}
	defer helloworldApp.Close()

    // Read remote PK from stdin
	remotePK := cipher.PubKey{}
	if err := remotePK.UnmarshalText([]byte(os.Args[1])); err != nil {
		log.Fatal("Failed to construct PubKey: ", err, os.Args[1])
	}

    // Dial to remote Node
	conn, err := helloworldApp.Dial(&app.Addr{PubKey: remotePK, Port: 10})
	if err != nil {
		log.Fatal("Failed to open remote conn: ", err)
	}

    // Send payload
	if _, err := conn.Write([]byte("ping")); err != 
		log.Fatal("Failed to write to a remote node: ", err)
	}

    // Receive payload
	buf := make([]byte, 4)
	if _, err = conn.Read(buf); err != nil {
		log.Fatal("Failed to read remote data: ", err)
	}

	log.Printf("Message from %s: %s", conn.RemoteAddr().String(), string(buf))
}
Clone this wiki locally