hello world

This commit is contained in:
2020-09-07 18:31:49 -04:00
commit ac267ea920
22 changed files with 1422 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
gotoopie

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

80
backend/client.go Normal file
View File

@ -0,0 +1,80 @@
package backend
import (
"bytes"
"crypto/tls"
"encoding/json"
"net/http"
"strconv"
"sync"
)
const jsonrpcVersion string = "2.0"
const scheme = "https://"
// Client is an I2PControl client
type Client struct {
Config *Options
MsgCount int
mutex sync.RWMutex
}
// Options is atype alias of I2PControl
type Options = I2PControl
// NewClient creates a new I2PControl session with the configuration options Options
func NewClient(opts *Options) *Client {
c := new(Client)
c.Config = opts
return c
}
// Request contains a JSONRPC2.0 I2PControl request
type Request struct {
ID string `json:"id"`
Method string `json:"method"`
Params map[string]interface{} `json:"params"`
JSONRPC string `json:"jsonrpc"`
}
// Reply contains a Request reply
type Reply struct {
ID string `json:"id"`
Result map[string]interface{} `json:"result"`
JSONRPC string `json:"jsonrpc"`
}
// Send sends a JSONRPC2.0 I2PControl Request down the wire and returns a Result
func (c *Client) Send(method string, params map[string]interface{}) (*Reply, error) {
c.mutex.Lock()
c.MsgCount++
params["Token"] = c.Config.Token
c.mutex.Unlock()
return c.buildRequest(Request{
ID: strconv.Itoa(c.MsgCount),
Method: method,
Params: params,
JSONRPC: jsonrpcVersion,
})
}
func (c *Client) buildRequest(request Request) (*Reply, error) {
data, err := json.Marshal(request)
if err != nil {
return &Reply{}, err
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
req, err := http.NewRequest("POST", scheme+c.Config.Address+":"+c.Config.Port, bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
r := new(Reply)
if err != nil {
return r, err
}
defer resp.Body.Close()
json.NewDecoder(resp.Body).Decode(&r)
return r, nil
}

462
backend/helpers.go Normal file
View File

@ -0,0 +1,462 @@
package backend
import (
"fmt"
)
const apiVersion float64 = 1 // The version of the I2PControl API used by the client
const nonEmptyString = "x"
const nonEmptyint = 1
// Authenticate creates and returns an authentication token used for further communication.
func (c *Client) Authenticate() error {
reply, err := c.Send("Authenticate", map[string]interface{}{
"API": apiVersion,
"Password": c.Config.Password,
})
if err != nil || reply.Result == nil {
return fmt.Errorf("Authenticate failed: %w", err)
}
c.Config.Token = reply.Result["Token"].(string)
return err
}
// Echo echoes the value of the echo key, used for debugging and testing.
func (c *Client) Echo(echo string) (string, error) {
reply, err := c.Send("Echo", map[string]interface{}{
"Echo": echo,
})
if err != nil || reply.Result == nil {
return "", fmt.Errorf("Echo failed: %w", err)
}
return reply.Result["Result"].(string), err
}
// GetRate fetches rateStat from router statManager. Creates stat if not already created.
func (c *Client) GetRate(stat string, period int) (int, error) {
reply, err := c.Send("GetRate", map[string]interface{}{
"Stat": stat,
"Period": float64(period),
})
if err != nil || reply.Result == nil {
return 0, fmt.Errorf("GetRate failed: %w", err)
}
return int(reply.Result["Result"].(float64)), err
}
// I2PControl wrappers
// SetAddress sets a new listen address for I2PControl (only 127.0.0.1 and 0.0.0.0 are implemented in I2PControl currently).
func (c *Client) SetAddress(address string) (bool, bool, error) {
reply, err := c.I2PControl(I2PControl{
Address: address,
})
if err != nil {
return false, false, fmt.Errorf("SetAddress failed: %w", err)
}
return reply.RestartNeeded, reply.SettingsSaved, err
}
// SetPassword ets a new password for I2PControl, all Authentication tokens will be revoked.
func (c *Client) SetPassword(password string) (bool, bool, error) {
reply, err := c.I2PControl(I2PControl{
Password: password,
})
if err != nil {
return false, false, fmt.Errorf("SetPassword failed: %w", err)
}
return reply.RestartNeeded, reply.SettingsSaved, err
}
// SetPort switches which port I2PControl will listen for connections on.
func (c *Client) SetPort(port string) (bool, bool, error) {
reply, err := c.I2PControl(I2PControl{
Port: port,
})
if err != nil {
return false, false, fmt.Errorf("SetPort failed: %w", err)
}
return reply.RestartNeeded, reply.SettingsSaved, err
}
// RouterInfo wrappers
// RouterStatus returns what the status of the router is.
func (c *Client) RouterStatus() (string, error) {
reply, err := c.RouterInfo(RouterInfo{
Status: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("RouterStatus failed: %w", err)
}
return reply.Status, err
}
// RouterUptime returns what the uptime of the router is in ms.
func (c *Client) RouterUptime() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
Uptime: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("RouterUptime failed: %w", err)
}
return reply.Uptime, err
}
// RouterVersion returns what version of I2P the router is running.
func (c *Client) RouterVersion() (string, error) {
reply, err := c.RouterInfo(RouterInfo{
Version: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("RouterVersion failed: %w", err)
}
return reply.Version, err
}
// NetBwInbound1s returns the 1 second average inbound bandwidth in Bps.
func (c *Client) NetBwInbound1s() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetBwInbound1s: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetBwInbound1s failed: %w", err)
}
return reply.NetBwInbound1s, err
}
// NetBwInbound15s returns the 15 second average inbound bandwidth in Bps.
func (c *Client) NetBwInbound15s() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetBwInbound15s: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetBwInbound15s failed: %w", err)
}
return reply.NetBwInbound15s, err
}
// NetBwOutbound1s returns the 1 second average outbound bandwidth in Bps.
func (c *Client) NetBwOutbound1s() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetBwOutbound1s: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetBwOutbound1s failed: %w", err)
}
return reply.NetBwOutbound1s, err
}
// NetBwOutbound15s returns the 15 second average outbound bandwidth in Bps
func (c *Client) NetBwOutbound15s() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetBwOutbound15s: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetBwOutbound15s failed: %w", err)
}
return reply.NetBwOutbound15s, err
}
// NetStatus returns what the current network status is.
func (c *Client) NetStatus() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetStatus: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetStatus failed: %w", err)
}
return reply.NetStatus, err
}
// NetTunnelsParticipating returns how many tunnels on the I2P net are we participating in.
func (c *Client) NetTunnelsParticipating() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetTunnelsParticipating: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetTunnelsParticipating failed: %w", err)
}
return reply.NetTunnelsParticipating, err
}
// NetDBActivePeers returns how many tunnels on the I2P net are we participating in.
func (c *Client) NetDBActivePeers() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetDBActivePeers: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetDBActivePeers failed: %w", err)
}
return reply.NetDBActivePeers, err
}
// NetDBFastPeers returns how many peers are considered 'fast'.
func (c *Client) NetDBFastPeers() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetDBFastPeers: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetDBFastPeers failed: %w", err)
}
return reply.NetDBFastPeers, err
}
// NetDBHighCapacityPeers returns how many peers are considered 'high capacity'.
func (c *Client) NetDBHighCapacityPeers() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetDBHighCapacityPeers: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetDBHighCapacityPeers failed: %w", err)
}
return reply.NetDBHighCapacityPeers, err
}
// NetDBIsReseeding returns is the router reseeding hosts to its NetDB?
func (c *Client) NetDBIsReseeding() (bool, error) {
reply, err := c.RouterInfo(RouterInfo{
NetDBIsReseeding: true,
})
if err != nil {
return false, fmt.Errorf("NetDBIsReseeding failed: %w", err)
}
return reply.NetDBIsReseeding, err
}
// NetDBKnownPeers returns how many peers are known to us (listed in our NetDB).
func (c *Client) NetDBKnownPeers() (int, error) {
reply, err := c.RouterInfo(RouterInfo{
NetDBKnownPeers: nonEmptyint,
})
if err != nil {
return 0, fmt.Errorf("NetDBKnownPeers failed: %w", err)
}
return reply.NetDBKnownPeers, err
}
// RouterManager wrappers
// BlockingFindUpdates initiates a search for signed updates.
func (c *Client) BlockingFindUpdates() (bool, error) {
reply, err := c.RouterManager(RouterManager{
FindUpdates: true,
})
if err != nil {
return false, fmt.Errorf("BlockingFindUpdates failed: %w", err)
}
return reply.FindUpdates, err
}
// Reseed initiates a router reseed, fetching peers into our NetDB from a remote host.
func (c *Client) Reseed() error {
_, err := c.RouterManager(RouterManager{
Reseed: "",
})
if err != nil {
return fmt.Errorf("Reseed failed: %w", err)
}
return err
}
// Restart restarts the router.
func (c *Client) Restart() error {
_, err := c.RouterManager(RouterManager{
Restart: "",
})
if err != nil {
return fmt.Errorf("Restart failed: %w", err)
}
return err
}
// RestartGraceful restarts the router gracefully (waits for participating tunnels to expire).
func (c *Client) RestartGraceful() error {
_, err := c.RouterManager(RouterManager{
RestartGraceful: "",
})
if err != nil {
return fmt.Errorf("RestartGraceful failed: %w", err)
}
return err
}
// Shutdown shuts down the router.
func (c *Client) Shutdown() error {
_, err := c.RouterManager(RouterManager{
Shutdown: "",
})
if err != nil {
return fmt.Errorf("Shutdown failed: %w", err)
}
return err
}
// ShutdownGraceful shuts down the router.
func (c *Client) ShutdownGraceful() error {
_, err := c.RouterManager(RouterManager{
ShutdownGraceful: "",
})
if err != nil {
return fmt.Errorf("ShutdownGraceful failed: %w", err)
}
return err
}
// Update initiates a router update from signed sources.
func (c *Client) Update() error {
_, err := c.RouterManager(RouterManager{
Update: "",
})
if err != nil {
return fmt.Errorf("Update failed: %w", err)
}
return err
}
// NetworkSetting wrappers
// NTCPPort gets/sets the port used for the TCP transport.
func (c *Client) NTCPPort(port string) (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NTCPPort: port,
})
if err != nil {
return "", fmt.Errorf("NTCPPort failed: %w", err)
}
return reply.NTCPPort, err
}
// NTCPHostname gets/sets hostname is used for the TCP transport.
func (c *Client) NTCPHostname(hostname string) (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NTCPHostname: hostname,
})
if err != nil {
return "", fmt.Errorf("NTCPHostname failed: %w", err)
}
return reply.NTCPHostname, err
}
// NTCPAutoIP use automatically detected ip for TCP transport.
func (c *Client) NTCPAutoIP(setting string) (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NTCPAutoIP: setting,
})
if err != nil {
return "", fmt.Errorf("NTCPAutoIP failed: %w", err)
}
return reply.NTCPAutoIP, err
}
// SSUPort what port is used for the UDP transport.
func (c *Client) SSUPort(port string) (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
SSUPort: port,
})
if err != nil {
return "", fmt.Errorf("SSUPort failed: %w", err)
}
return reply.SSUPort, err
}
// SSUHostname what hostname is used for the UDP transport.
func (c *Client) SSUHostname(hostname string) (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
SSUHostname: hostname,
})
if err != nil {
return "", fmt.Errorf("SSUHostname failed: %w", err)
}
return reply.SSUHostname, err
}
// SSUAutoIP which methods should be used for detecting the ip address of the UDP transport.
func (c *Client) SSUAutoIP(setting string) (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
SSUAutoIP: setting,
})
if err != nil {
return "", fmt.Errorf("SSUAutoIP failed: %w", err)
}
return reply.SSUAutoIP, err
}
// SSUDetectedIP what ip has been detected by the UDP transport.
func (c *Client) SSUDetectedIP() (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
SSUDetectedIP: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("SSUDetectedIP failed: %w", err)
}
return reply.SSUDetectedIP, nil
}
// NetUPNP is UPnP enabled?
func (c *Client) NetUPNP() (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NetUPNP: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("NetUPNP failed: %w", err)
}
return reply.SSUDetectedIP, nil
}
// NetBWShare how many percent of bandwidth is usable for participating tunnels.
func (c *Client) NetBWShare() (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NetBWShare: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("NetBWShare failed: %w", err)
}
return reply.NetBWShare, nil
}
// NetBWIn how many KB/s of inbound bandwidth is allowed.
func (c *Client) NetBWIn() (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NetBWIn: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("NetBWIn failed: %w", err)
}
return reply.NetBWIn, nil
}
// NetBWOut How many KB/s of outbound bandwidth is allowed.
func (c *Client) NetBWOut() (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NetBWOut: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("NetBWOut failed: %w", err)
}
return reply.NetBWOut, nil
}
// NetLaptopMode is laptop mode enabled (change router identity and UDP port when IP changes).
func (c *Client) NetLaptopMode() (string, error) {
reply, err := c.NetworkSetting(NetworkSetting{
NetLaptopMode: nonEmptyString,
})
if err != nil {
return "", fmt.Errorf("NetLaptopMode failed: %w", err)
}
return reply.NetLaptopMode, nil
}
// AdvancedSettings wrappers
// GetAll lists key value options for AdvancedSettings
func (c *Client) GetAll() (map[string]interface{}, error) {
reply, err := c.Send("AdvancedSettings", map[string]interface{}{
"getAll": nonEmptyString,
})
if err != nil || reply.Result == nil {
return nil, fmt.Errorf("GetAll failed: %w", err)
}
return reply.Result, err
}

55
backend/i2pcontrol.go Normal file
View File

@ -0,0 +1,55 @@
package backend
import "fmt"
// I2PControl manages I2PControl. Ports, passwords and the like.
type I2PControl struct {
Address string `json:",omitempty"` // Sets a new listen address for I2PControl (only 127.0.0.1 and 0.0.0.0 are implemented in I2PControl currently).
Password string `json:",omitempty"` // Sets a new password for I2PControl, all Authentication tokens will be revoked.
Port string `json:",omitempty"` // Switches which port I2PControl will listen for connections on.
Token string `json:",omitempty"`
}
// I2PControlReply contains an I2PControl reply.
type I2PControlReply struct {
*I2PControl
SettingsSaved bool // Returns true if any changes were made.
RestartNeeded bool // Returns true if any changes requiring a restart to take effect were made.
}
// I2PControl manages I2PControl. Ports, passwords and the like.
func (c *Client) I2PControl(i2pcontrol I2PControl) (I2PControlReply, error) {
params := make(map[string]interface{})
if i2pcontrol.Address != "" {
params["i2pcontrol.address"] = i2pcontrol.Address
}
if i2pcontrol.Password != "" {
params["i2pcontrol.password"] = i2pcontrol.Password
}
if i2pcontrol.Port != "" {
params["i2pcontrol.port"] = i2pcontrol.Port
}
reply, err := c.Send("I2PControl", params)
result := I2PControlReply{}
if err != nil || reply.Result == nil {
return result, fmt.Errorf("I2PControl failed: %w", err)
}
if reply.Result["i2pcontrol.address"] != nil {
result.Address = reply.Result["Address"].(string)
}
if reply.Result["i2pcontrol.password"] != nil {
result.Address = reply.Result["i2pcontrol.password"].(string)
}
if reply.Result["i2pcontrol.port"] != nil {
result.Address = reply.Result["i2pcontrol.port"].(string)
}
if reply.Result["SettingsSaved"] == "true" {
result.SettingsSaved = true
}
if reply.Result["RestartNeeded"] == "true" {
result.RestartNeeded = true
}
return result, err
}

111
backend/networksetting.go Normal file
View File

@ -0,0 +1,111 @@
package backend
import "fmt"
// NetworkSetting fetches or sets various network related settings. Ports, addresses etc.
type NetworkSetting struct {
NTCPPort string // What port is used for the TCP transport.
NTCPHostname string // What hostname is used for the TCP transport.
NTCPAutoIP string // Use automatically detected ip for TCP transport.
SSUPort string // What port is used for the UDP transport.
SSUHostname string // What hostname is used for the UDP transport.
SSUAutoIP string // Which methods should be used for detecting the ip address of the UDP transport.
SSUDetectedIP string // What ip has been detected by the UDP transport.
NetUPNP string // Is UPnP enabled.
NetBWShare string // How many percent of bandwidth is usable for participating tunnels.
NetBWIn string // How many KB/s of inbound bandwidth is allowed.
NetBWOut string // How many KB/s of outbound bandwidth is allowed.
NetLaptopMode string // Is laptop mode enabled (change router identity and UDP port when IP changes).
}
// NetworkSettingReply contains a NetworkSetting reply.
type NetworkSettingReply struct {
*NetworkSetting
SettingsSaved bool // Have the provided settings been saved.
RestartNeeded bool // Is a restart needed for the new settings to be used.
}
// NetworkSetting fetches or sets various network related settings. Ports, addresses etc.
func (c *Client) NetworkSetting(ns NetworkSetting) (NetworkSettingReply, error) {
params := make(map[string]interface{})
if ns.NTCPPort != "" {
params["i2p.router.net.ntcp.port"] = ns.NTCPPort
}
if ns.NTCPHostname != "" {
params["i2p.router.net.ntcp.hostname"] = ns.NTCPHostname
}
if ns.NTCPAutoIP != "" {
params["i2p.router.net.ntcp.autoip"] = ns.NTCPAutoIP
}
if ns.SSUPort != "" {
params["i2p.router.net.ssu.port"] = ns.SSUPort
}
if ns.SSUHostname != "" {
params["i2p.router.net.ssu.hostname"] = ns.SSUHostname
}
if ns.SSUAutoIP != "" {
params["i2p.router.net.ssu.autoip"] = ns.SSUAutoIP
}
if ns.SSUDetectedIP != "" {
params["i2p.router.net.ssu.detectedip"] = ns.SSUDetectedIP
}
if ns.NetUPNP != "" {
params["i2p.router.net.upnp"] = ns.NetUPNP
}
if ns.NetBWShare != "" {
params["i2p.router.net.bw.share"] = ns.NetBWShare
}
if ns.NetBWIn != "" {
params["i2p.router.net.bw.in"] = ns.NetBWIn
}
if ns.NetBWOut != "" {
params["i2p.router.net.bw.out"] = ns.NetBWOut
}
if ns.NetLaptopMode != "" {
params["i2p.router.net.laptopmode"] = ns.NetLaptopMode
}
reply, err := c.Send("NetworkSetting", params)
result := NetworkSettingReply{}
if err != nil || reply.Result == nil {
return result, fmt.Errorf("NetworkSetting failed: %w", err)
}
if reply.Result["i2p.router.net.ntcp.port"] != "" {
result.NTCPPort = reply.Result["i2p.router.net.ntcp.port"].(string)
}
if reply.Result["i2p.router.net.ntcp.hostname"] != "" {
result.NTCPHostname = reply.Result["i2p.router.net.ntcp.hostname"].(string)
}
if reply.Result["i2p.router.net.ntcp.autoip"] != "" {
result.NTCPAutoIP = reply.Result["i2p.router.net.ntcp.autoip"].(string)
}
if reply.Result["i2p.router.net.ssu.port"] != "" {
result.SSUPort = reply.Result["i2p.router.net.ssu.port"].(string)
}
if reply.Result["i2p.router.net.ssu.hostname"] != "" {
result.SSUHostname = reply.Result["i2p.router.net.ssu.hostname"].(string)
}
if reply.Result["i2p.router.net.ssu.autoip"] != "" {
result.SSUAutoIP = reply.Result["i2p.router.net.ssu.autoip"].(string)
}
if reply.Result["i2p.router.net.ssu.detectedip"] != "" {
result.SSUDetectedIP = reply.Result["i2p.router.net.ssu.detectedip"].(string)
}
if reply.Result["i2p.router.net.upnp"] != "" {
result.NetUPNP = reply.Result["i2p.router.net.upnp"].(string)
}
if reply.Result["i2p.router.net.bw.share"] != "" {
result.NetBWShare = reply.Result["i2p.router.net.bw.share"].(string)
}
if reply.Result["i2p.router.net.bw.in"] != "" {
result.NetBWIn = reply.Result["i2p.router.net.bw.in"].(string)
}
if reply.Result["i2p.router.net.bw.out"] != "" {
result.NetBWOut = reply.Result["i2p.router.net.bw.out"].(string)
}
if reply.Result["i2p.router.net.laptopmode"] != "" {
result.NetLaptopMode = reply.Result["i2p.router.net.laptopmode"].(string)
}
return result, err
}

127
backend/routerinfo.go Normal file
View File

@ -0,0 +1,127 @@
package backend
import (
"fmt"
)
// RouterInfo fetches basic information about the I2P router. Uptime, version etc.
type RouterInfo struct {
Status string
Uptime int
Version string
NetBwInbound1s int
NetBwInbound15s int
NetBwOutbound1s int
NetBwOutbound15s int
NetStatus int
NetTunnelsParticipating int
NetDBActivePeers int
NetDBFastPeers int
NetDBHighCapacityPeers int
NetDBIsReseeding bool
NetDBKnownPeers int
}
// RouterInfo fetches basic information about the I2P router. Uptime, version etc.
func (c *Client) RouterInfo(ri RouterInfo) (RouterInfo, error) {
params := make(map[string]interface{})
if ri.Status != "" {
params["i2p.router.status"] = nil
}
if ri.Uptime != 0 {
params["i2p.router.uptime"] = nil
}
if ri.Version != "" {
params["i2p.router.version"] = nil
}
if ri.NetBwInbound1s != 0 {
params["i2p.router.net.bw.inbound.1s"] = nil
}
if ri.NetBwInbound15s != 0 {
params["i2p.router.net.bw.inbound.15s"] = nil
}
if ri.NetBwOutbound1s != 0 {
params["i2p.router.net.bw.outbound.1s"] = nil
}
if ri.NetBwOutbound15s != 0 {
params["i2p.router.net.bw.outbound.15s"] = nil
}
if ri.NetStatus != 0 {
params["i2p.router.net.status"] = nil
}
if ri.NetTunnelsParticipating != 0 {
params["i2p.router.net.tunnels.participating"] = nil
}
if ri.NetDBActivePeers != 0 {
params["i2p.router.netdb.activepeers"] = nil
}
if ri.NetDBFastPeers != 0 {
params["i2p.router.netdb.fastpeers"] = nil
}
if ri.NetDBHighCapacityPeers != 0 {
params["i2p.router.netdb.highcapacitypeers"] = nil
}
if ri.NetDBIsReseeding != false {
params["i2p.router.netdb.isreseeding"] = nil
}
if ri.NetDBKnownPeers != 0 {
params["i2p.router.netdb.knownpeers"] = nil
}
reply, err := c.Send("RouterInfo", params)
result := RouterInfo{}
if err != nil || reply.Result == nil {
return result, fmt.Errorf("RouterInfo failed: %w", err)
}
if reply.Result["i2p.router.status"] != nil {
result.Status = reply.Result["i2p.router.status"].(string)
}
if reply.Result["i2p.router.uptime"] != nil {
result.Uptime = int(reply.Result["i2p.router.uptime"].(float64))
}
if reply.Result["i2p.router.version"] != nil {
result.Version = reply.Result["i2p.router.version"].(string)
}
if reply.Result["i2p.router.net.bw.inbound.1s"] != nil {
result.NetBwInbound1s = int(reply.Result["i2p.router.net.bw.inbound.1s"].(float64))
}
if reply.Result["i2p.router.net.bw.inbound.15s"] != nil {
result.NetBwInbound15s = int(reply.Result["i2p.router.net.bw.inbound.15s"].(float64))
}
if reply.Result["i2p.router.net.bw.outbound.1s"] != nil {
result.NetBwOutbound1s = int(reply.Result["i2p.router.net.bw.outbound.1s"].(float64))
}
if reply.Result["i2p.router.net.bw.outbound.15s"] != nil {
result.NetBwOutbound15s = int(reply.Result["i2p.router.net.bw.outbound.15s"].(float64))
}
if reply.Result["i2p.router.net.status"] != nil {
result.NetStatus = int(reply.Result["i2p.router.net.status"].(float64))
}
if reply.Result["i2p.router.net.tunnels.participating"] != nil {
result.NetTunnelsParticipating = int(reply.Result["i2p.router.net.tunnels.participating"].(float64))
}
if reply.Result["i2p.router.netdb.activepeers"] != nil {
result.NetDBActivePeers = int(reply.Result["i2p.router.netdb.activepeers"].(float64))
}
if reply.Result["i2p.router.netdb.fastpeers"] != nil {
result.NetDBFastPeers = int(reply.Result["i2p.router.netdb.fastpeers"].(float64))
}
if reply.Result["i2p.router.netdb.highcapacitypeers"] != nil {
result.NetDBHighCapacityPeers = int(reply.Result["i2p.router.netdb.highcapacitypeers"].(float64))
}
if reply.Result["i2p.router.netdb.isreseeding"] != "" {
if reply.Result["i2p.router.netdb.isreseeding"] == "true" {
result.NetDBIsReseeding = true
}
}
if reply.Result["i2p.router.netdb.knownpeers"] != nil {
result.NetDBKnownPeers = int(reply.Result["i2p.router.netdb.knownpeers"].(float64))
}
return result, err
}

71
backend/routermanager.go Normal file
View File

@ -0,0 +1,71 @@
package backend
import "fmt"
// RouterManager manages I2P router restart/shutdown.
type RouterManager struct {
FindUpdates bool // Blocking. Initiates a search for signed updates.
Reseed string // Initiates a router reseed, fetching peers into our NetDB from a remote host.
Restart string // Restarts the router.
RestartGraceful string // Restarts the router gracefully (waits for participating tunnels to expire).
Shutdown string // Shuts down the router.
ShutdownGraceful string // Shuts down the router gracefully (waits for participating tunnels to expire).
Update string // Initiates a router update from signed sources.
}
// RouterManager manages I2P router restart/shutdown.
func (c *Client) RouterManager(rm RouterManager) (RouterManager, error) {
params := make(map[string]interface{})
if rm.FindUpdates != false {
params["FindUpdates"] = nil
}
if rm.Reseed != "" {
params["Reseed"] = nil
}
if rm.Restart != "" {
params["Restart"] = nil
}
if rm.RestartGraceful != "" {
params["RestartGraceful"] = nil
}
if rm.Shutdown != "" {
params["Shutdown"] = nil
}
if rm.ShutdownGraceful != "" {
params["ShutdownGraceful"] = nil
}
if rm.Update != "" {
params["Update"] = nil
}
reply, err := c.Send("RouterManager", params)
result := RouterManager{}
if err != nil || reply.Result == nil {
return result, fmt.Errorf("RouterManager failed: %w", err)
}
if reply.Result["FindUpdates"] != "" {
if reply.Result["FindUpdates"] == "true" {
result.FindUpdates = true
}
}
if reply.Result["Reseed"] != nil {
result.Reseed = reply.Result["Reseed"].(string)
}
if reply.Result["Restart"] != nil {
result.Restart = reply.Result["Restart"].(string)
}
if reply.Result["RestartGraceful"] != nil {
result.RestartGraceful = reply.Result["RestartGraceful"].(string)
}
if reply.Result["Shutdown"] != nil {
result.Shutdown = reply.Result["Shutdown"].(string)
}
if reply.Result["ShutdownGraceful"] != nil {
result.ShutdownGraceful = reply.Result["ShutdownGraceful"].(string)
}
if reply.Result["Update"] != nil {
result.Update = reply.Result["Update"].(string)
}
return result, err
}

View File

@ -0,0 +1,56 @@
package frontend
import (
"log"
"fyne.io/fyne/theme"
"fyne.io/fyne/widget"
)
func renderI2PApplications() *widget.AccordionItem {
return widget.NewAccordionItem("I2P Applications",
widget.NewVBox(
&widget.Button{
Text: "I2P Email",
Icon: theme.MailComposeIcon(),
Alignment: widget.ButtonAlignLeading,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
&widget.Button{
Text: "Torrent",
Icon: theme.DownloadIcon(),
Alignment: widget.ButtonAlignLeading,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
&widget.Button{
Text: "Web Server",
Icon: theme.ComputerIcon(),
Alignment: widget.ButtonAlignLeading,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
&widget.Button{
Text: "Address Book",
Icon: theme.FileApplicationIcon(),
Alignment: widget.ButtonAlignLeading,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
&widget.Button{
Text: "I2P Network Manager",
Icon: theme.MoveDownIcon(),
Alignment: widget.ButtonAlignLeading,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
&widget.Button{
Text: "Plugins",
Icon: theme.FileVideoIcon(),
Alignment: widget.ButtonAlignLeading,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
))
}

15
frontend/bundled.go Normal file

File diff suppressed because one or more lines are too long

30
frontend/content.go Normal file
View File

@ -0,0 +1,30 @@
package frontend
import (
"fyne.io/fyne"
"fyne.io/fyne/canvas"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
)
func (f *frontend) renderContent() *fyne.Container {
return fyne.NewContainerWithLayout(
layout.NewVBoxLayout(),
renderLogoImage(),
fyne.NewContainerWithLayout(layout.NewCenterLayout(), f.vbox),
f.renderAccordionMenus(),
layout.NewSpacer())
}
func renderLogoImage() *canvas.Image {
l := canvas.NewImageFromResource(resourceLogoPng)
l.FillMode = canvas.ImageFillContain
l.SetMinSize(fyne.NewSize(172, 172))
return l
}
func (f *frontend) renderAccordionMenus() *widget.AccordionContainer {
ac := widget.NewAccordionContainer()
ac.Append(renderI2PApplications())
return ac
}

28
frontend/footer.go Normal file
View File

@ -0,0 +1,28 @@
package frontend
import (
"log"
"fyne.io/fyne"
"fyne.io/fyne/layout"
"fyne.io/fyne/theme"
"fyne.io/fyne/widget"
)
func (f *frontend) renderFooter() *fyne.Container {
return fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(2),
&widget.Button{
Text: "Router Console",
Icon: theme.ViewRefreshIcon(),
Alignment: widget.ButtonAlignCenter,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
},
&widget.Button{
Text: "Settings",
Icon: theme.SettingsIcon(),
Alignment: widget.ButtonAlignCenter,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
})
}

63
frontend/frontend.go Normal file
View File

@ -0,0 +1,63 @@
package frontend
import (
"log"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
"github.com/kpetku/gotoopie/backend"
)
type frontend struct {
client *backend.Client
app fyne.App
window fyne.Window
vbox *widget.Box
}
// Start launches the GUI frontend.
func Start() {
f := startFrontendAndBackend()
err := f.client.Authenticate()
if err != nil {
log.Printf("Error authenticating: %s", err)
}
f.startGUI()
}
func startFrontendAndBackend() *frontend {
f := new(frontend)
f.client = backend.NewClient(&backend.Options{
Address: "localhost",
Port: "7650",
Password: "itoopie",
})
return f
}
func (f *frontend) startGUI() {
f.vbox = widget.NewVBox()
f.app = app.New()
f.window = f.app.NewWindow("gotoopie")
f.renderNetworkStatus()
f.renderPeers()
f.renderOther()
f.renderAccordionMenus()
header := f.renderHeader()
content := f.renderContent()
footer := f.renderFooter()
layout := layout.NewBorderLayout(header, footer, nil, nil)
f.window.SetContent(fyne.NewContainerWithLayout(layout, header, footer, content))
fyne.CurrentApp().Settings().SetTheme(newToopieTheme())
f.window.SetIcon(resourceLogoPng)
f.window.Resize(fyne.NewSize(400, 800))
f.window.SetFixedSize(true)
f.window.ShowAndRun()
}

4
frontend/gen.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
fyne bundle -package frontend ./resources/curve.png > bundled.go
fyne bundle -package frontend -append ./resources/logo.png >> bundled.go

38
frontend/header.go Normal file
View File

@ -0,0 +1,38 @@
package frontend
import (
"log"
"fyne.io/fyne"
"fyne.io/fyne/canvas"
"fyne.io/fyne/layout"
"fyne.io/fyne/theme"
"fyne.io/fyne/widget"
)
func (f *frontend) renderHeader() *fyne.Container {
top := fyne.NewContainerWithLayout(layout.NewGridLayout(1),
renderToolbar(),
fyne.NewContainerWithLayout(layout.NewCenterLayout(), renderBackgroundImage(), &widget.Button{
Text: "Disconnect",
Icon: theme.VisibilityOffIcon(),
Alignment: widget.ButtonAlignCenter,
IconPlacement: 0,
OnTapped: func() { log.Println("Not yet implemented") },
}))
return top
}
func renderToolbar() *widget.Toolbar {
return widget.NewToolbar(
widget.NewToolbarSpacer(),
widget.NewToolbarAction(theme.HelpIcon(), func() { log.Println("Not yet implemented") }),
)
}
func renderBackgroundImage() *canvas.Image {
b := canvas.NewImageFromResource(resourceCurvePng)
b.FillMode = canvas.ImageFillStretch
b.SetMinSize(fyne.NewSize(400, 50))
return b
}

48
frontend/networkstatus.go Normal file
View File

@ -0,0 +1,48 @@
package frontend
import (
"fyne.io/fyne"
"fyne.io/fyne/widget"
)
func (f *frontend) renderNetworkStatus() error {
ns, err := f.client.NetStatus()
if err != nil {
return err
}
var out string
switch ns {
case 0:
out = "Network OK"
case 1:
out = "Testing Network"
case 2:
out = "Firewalled"
case 3:
out = "Hidden"
case 4:
out = "Warn: Firewalled and fast"
case 5:
out = "Warn: Firewalled and floodfill"
case 6:
out = "Warn: Firewalled with inbound TCP"
case 7:
out = "Warn: Firewalled with UDP disabled"
case 8:
out = "Error: I2CP"
case 9:
out = " Error: Clock Skew"
case 10:
out = "Error: Private TCP address"
case 11:
out = "Error: Symmetric NAT"
case 12:
out = "Error: UDP port in use"
case 13:
out = "Error: No active peers check connection and firewall"
case 14:
out = "Error: UDP disabled and TCP unset"
}
f.vbox.Append(widget.NewLabelWithStyle(out, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}))
return nil
}

40
frontend/other.go Normal file
View File

@ -0,0 +1,40 @@
package frontend
import (
"strconv"
"time"
"fyne.io/fyne/widget"
)
func (f *frontend) renderOther() error {
var u string
version, err := f.client.RouterVersion()
if err != nil {
return err
}
uptime, err := f.client.RouterUptime()
if err != nil {
return err
}
hours := uptime / 3600000
days := (uptime / 86400000) % 7
weeks := uptime / (86400000 * 7)
if hours < 48 {
u = time.Duration(int64(uptime / 3600000)).String()
} else if hours > 48 {
u = strconv.Itoa(days) + " days"
} else if days > 13 {
u = strconv.Itoa(weeks) + " weeks"
}
status, err := f.client.RouterStatus()
if err != nil {
return err
}
f.vbox.Append(widget.NewGroup("Other",
widget.NewLabel("Version: "+version),
widget.NewLabel("Uptime: "+u),
widget.NewLabel("Status: "+status),
))
return nil
}

36
frontend/peers.go Normal file
View File

@ -0,0 +1,36 @@
package frontend
import (
"strconv"
"fyne.io/fyne/widget"
)
func (f *frontend) renderPeers() error {
knownPeers, err := f.client.NetDBKnownPeers()
if err != nil {
return err
}
activePeers, err := f.client.NetDBActivePeers()
if err != nil {
return err
}
fastPeers, err := f.client.NetDBFastPeers()
if err != nil {
return err
}
participating, err := f.client.NetTunnelsParticipating()
if err != nil {
return err
}
f.vbox.Append(
widget.NewGroup("Peers",
widget.NewLabel("Active: "+strconv.Itoa(activePeers)+"/"+strconv.Itoa(knownPeers)),
widget.NewLabel("Fast: "+strconv.Itoa(fastPeers)+"/"+strconv.Itoa(knownPeers)),
widget.NewLabel("Active: "+strconv.Itoa(knownPeers)),
))
f.vbox.Append(widget.NewGroup("Tunnels",
widget.NewLabel("Participating: "+strconv.Itoa(participating)),
))
return nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
frontend/resources/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

110
frontend/theme.go Normal file
View File

@ -0,0 +1,110 @@
package frontend
import (
"image/color"
"fyne.io/fyne"
"fyne.io/fyne/theme"
)
type toopieTheme struct{}
func (toopieTheme) BackgroundColor() color.Color {
return &color.RGBA{R: 0xf8, G: 0xf9, B: 0xfa, A: 0xff}
}
func (toopieTheme) ButtonColor() color.Color {
return &color.RGBA{R: 0x36, G: 0x3a, B: 0x68, A: 0xff}
}
func (toopieTheme) DisabledButtonColor() color.Color {
return &color.RGBA{R: 0xe7, G: 0xe7, B: 0xe7, A: 0xff}
}
func (toopieTheme) HyperlinkColor() color.Color {
return &color.RGBA{R: 0x1b, G: 0x1d, B: 0x34, A: 0xff}
}
func (toopieTheme) TextColor() color.Color {
return &color.RGBA{R: 0x86, G: 0x8e, B: 0x96, A: 0xff}
}
func (toopieTheme) DisabledTextColor() color.Color {
return &color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff}
}
func (toopieTheme) IconColor() color.Color {
return &color.RGBA{R: 0x62, G: 0xa8, B: 0xe6, A: 0xff}
}
func (toopieTheme) DisabledIconColor() color.Color {
return &color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff}
}
func (toopieTheme) PlaceHolderColor() color.Color {
return &color.RGBA{R: 0x88, G: 0x88, B: 0x88, A: 0xff}
}
func (toopieTheme) PrimaryColor() color.Color {
return &color.RGBA{R: 0x52, G: 0x6b, B: 0xce, A: 0xff}
}
func (toopieTheme) HoverColor() color.Color {
return &color.RGBA{R: 0x62, G: 0x80, B: 0xe6, A: 0xff}
}
func (toopieTheme) FocusColor() color.Color {
return &color.RGBA{R: 0x9f, G: 0xa8, B: 0xda, A: 0xff}
}
func (toopieTheme) ScrollBarColor() color.Color {
return &color.RGBA{R: 0x0, G: 0x0, B: 0x0, A: 0x99}
}
func (toopieTheme) ShadowColor() color.Color {
return &color.RGBA{R: 0x0, G: 0x0, B: 0x0, A: 0x33}
}
func (toopieTheme) TextSize() int {
return 12
}
func (toopieTheme) TextFont() fyne.Resource {
return theme.DefaultTextFont()
}
func (toopieTheme) TextBoldFont() fyne.Resource {
return theme.DefaultTextBoldFont()
}
func (toopieTheme) TextItalicFont() fyne.Resource {
return theme.DefaultTextItalicFont()
}
func (toopieTheme) TextBoldItalicFont() fyne.Resource {
return theme.DefaultTextBoldItalicFont()
}
func (toopieTheme) TextMonospaceFont() fyne.Resource {
return theme.DefaultTextMonospaceFont()
}
func (toopieTheme) Padding() int {
return 1
}
func (toopieTheme) IconInlineSize() int {
return 35
}
func (toopieTheme) ScrollBarSize() int {
return 10
}
func (toopieTheme) ScrollBarSmallSize() int {
return 4
}
func newToopieTheme() fyne.Theme {
return &toopieTheme{}
}

19
main.go Normal file
View File

@ -0,0 +1,19 @@
package main
/* Copyright © 2020 The gootopie authors
* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
import (
"log"
"github.com/kpetku/gotoopie/frontend"
)
func main() {
log.Printf("Starting frontend")
frontend.Start()
}