<commit_before>// +build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/pkg/sysinfo"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// fillPlatformInfo fills the platform related info.
func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
	v.CgroupDriver = daemon.getCgroupDriver()
	v.CgroupVersion = "1"
	if sysInfo.CgroupUnified {
		v.CgroupVersion = "2"
	}

	v.MemoryLimit = sysInfo.MemoryLimit
	v.SwapLimit = sysInfo.SwapLimit
	v.KernelMemory = sysInfo.KernelMemory
	v.KernelMemoryTCP = sysInfo.KernelMemoryTCP
	v.OomKillDisable = sysInfo.OomKillDisable
	v.CPUCfsPeriod = sysInfo.CPUCfs
	v.CPUCfsQuota = sysInfo.CPUCfs
	v.CPUShares = sysInfo.CPUShares
	v.CPUSet = sysInfo.Cpuset
	v.PidsLimit = sysInfo.PidsLimit
	v.Runtimes = daemon.configStore.GetAllRuntimes()
	v.DefaultRuntime = daemon.configStore.GetDefaultRuntimeName()
	v.InitBinary = daemon.configStore.GetInitPath()

	defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, _, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
			v.RuncCommit.ID = "N/A"
		} else {
			v.RuncCommit.ID = commit
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
		v.RuncCommit.ID = "N/A"
	}

	// runc is now shipped as a separate package. Set "expected" to same value
	// as "ID" to prevent clients from reporting a version-mismatch
	v.RuncCommit.Expected = v.RuncCommit.ID

	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.ContainerdCommit.ID = rv.Revision
	} else {
		logrus.Warnf("failed to retrieve containerd version: %v", err)
		v.ContainerdCommit.ID = "N/A"
	}

	// containerd is now shipped as a separate package. Set "expected" to same
	// value as "ID" to prevent clients from reporting a version-mismatch
	v.ContainerdCommit.Expected = v.ContainerdCommit.ID

	// TODO is there still a need to check the expected version for tini?
	// if not, we can change this, and just set "Expected" to v.InitCommit.ID
	v.InitCommit.Expected = dockerversion.InitCommitID

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if _, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
			v.InitCommit.ID = "N/A"
		} else {
			v.InitCommit.ID = commit
			if len(dockerversion.InitCommitID) > len(commit) {
				v.InitCommit.Expected = dockerversion.InitCommitID[0:len(commit)]
			}
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
		v.InitCommit.ID = "N/A"
	}

	if v.CgroupDriver == cgroupNoneDriver {
		if v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. Systemd is required to enable cgroups in rootless-mode.")
		} else {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. To enable cgroups in rootless-mode, you need to boot the system in cgroup v2 mode.")
		}
	} else {
		if !v.MemoryLimit {
			v.Warnings = append(v.Warnings, "WARNING: No memory limit support")
		}
		if !v.SwapLimit {
			v.Warnings = append(v.Warnings, "WARNING: No swap limit support")
		}
		if !v.KernelMemoryTCP && v.CgroupVersion == "1" {
			// kernel memory is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No kernel memory TCP limit support")
		}
		if !v.OomKillDisable && v.CgroupVersion == "1" {
			// oom kill disable is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support")
		}
		if !v.CPUCfsQuota {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support")
		}
		if !v.CPUCfsPeriod {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support")
		}
		if !v.CPUShares {
			v.Warnings = append(v.Warnings, "WARNING: No cpu shares support")
		}
		if !v.CPUSet {
			v.Warnings = append(v.Warnings, "WARNING: No cpuset support")
		}
		// TODO add fields for these options in types.Info
		if !sysInfo.BlkioWeight && v.CgroupVersion == "2" {
			// blkio weight is not available on cgroup v1 since kernel 5.0.
			// Warning is not printed on cgroup v1, because there is no action user can take.
			// On cgroup v2, blkio weight is implemented using io.weight
			v.Warnings = append(v.Warnings, "WARNING: No io.weight support")
		}
		if !sysInfo.BlkioWeightDevice && v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: No io.weight (per device) support")
		}
		if !sysInfo.BlkioReadBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (rbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_bps_device support")
			}
		}
		if !sysInfo.BlkioWriteBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_bps_device support")
			}
		}
		if !sysInfo.BlkioReadIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (riops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_iops_device support")
			}
		}
		if !sysInfo.BlkioWriteIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wiops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_iops_device support")
			}
		}
	}
	if !v.IPv4Forwarding {
		v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
	}
	if !v.BridgeNfIptables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled")
	}
	if !v.BridgeNfIP6tables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled")
	}
}

func (daemon *Daemon) fillPlatformVersion(v *types.Version) {
	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.Components = append(v.Components, types.ComponentVersion{
			Name:    "containerd",
			Version: rv.Version,
			Details: map[string]string{
				"GitCommit": rv.Revision,
			},
		})
	}

	defaultRuntime := daemon.configStore.GetDefaultRuntimeName()
	defaultRuntimeBinary := daemon.configStore.GetRuntime(defaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, ver, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    defaultRuntime,
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
	}

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if ver, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    filepath.Base(defaultInitBinary),
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
	}
}

func fillDriverWarnings(v *types.Info) {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Data loop file" {
			msg := fmt.Sprintf("WARNING: %s: usage of loopback devices is "+
				"strongly discouraged for production use.\n         "+
				"Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.", v.Driver)

			v.Warnings = append(v.Warnings, msg)
			continue
		}
		if pair[0] == "Supports d_type" && pair[1] == "false" {
			backingFs := getBackingFs(v)

			msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", v.Driver, backingFs)
			if backingFs == "xfs" {
				msg += "         Reformat the filesystem with ftype=1 to enable d_type support.\n"
			}
			msg += "         Running without d_type support will not be supported in future releases."

			v.Warnings = append(v.Warnings, msg)
			continue
		}
	}
}

func getBackingFs(v *types.Info) string {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Backing Filesystem" {
			return pair[1]
		}
	}
	return ""
}

// parseInitVersion parses a Tini version string, and extracts the "version"
// and "git commit" from the output.
//
// Output example from `docker-init --version`:
//
//     tini version 0.18.0 - git.fec3683
func parseInitVersion(v string) (version string, commit string, err error) {
	parts := strings.Split(v, " - ")

	if len(parts) >= 2 {
		gitParts := strings.Split(strings.TrimSpace(parts[1]), ".")
		if len(gitParts) == 2 && gitParts[0] == "git" {
			commit = gitParts[1]
		}
	}
	parts[0] = strings.TrimSpace(parts[0])
	if strings.HasPrefix(parts[0], "tini version ") {
		version = strings.TrimPrefix(parts[0], "tini version ")
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return version, commit, err
}

// parseRuntimeVersion parses the output of `[runtime] --version` and extracts the
// "name", "version" and "git commit" from the output.
//
// Output example from `runc --version`:
//
//   runc version 1.0.0-rc5+dev
//   commit: 69663f0bd4b60df09991c08812a60108003fa340
//   spec: 1.0.0
func parseRuntimeVersion(v string) (runtime string, version string, commit string, err error) {
	lines := strings.Split(strings.TrimSpace(v), "\n")
	for _, line := range lines {
		if strings.Contains(line, "version") {
			s := strings.Split(line, "version")
			runtime = strings.TrimSpace(s[0])
			version = strings.TrimSpace(s[len(s)-1])
			continue
		}
		if strings.HasPrefix(line, "commit:") {
			commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
			continue
		}
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return runtime, version, commit, err
}

func (daemon *Daemon) cgroupNamespacesEnabled(sysInfo *sysinfo.SysInfo) bool {
	return sysInfo.CgroupNamespaces && containertypes.CgroupnsMode(daemon.configStore.CgroupNamespaceMode).IsPrivate()
}

// Rootless returns true if daemon is running in rootless mode
func (daemon *Daemon) Rootless() bool {
	return daemon.configStore.Rootless
}
<commit_msg>info: unset cgroup-related fields when CgroupDriver == none<commit_after>// +build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/pkg/sysinfo"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// fillPlatformInfo fills the platform related info.
func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
	v.CgroupDriver = daemon.getCgroupDriver()
	v.CgroupVersion = "1"
	if sysInfo.CgroupUnified {
		v.CgroupVersion = "2"
	}

	if v.CgroupDriver != cgroupNoneDriver {
		v.MemoryLimit = sysInfo.MemoryLimit
		v.SwapLimit = sysInfo.SwapLimit
		v.KernelMemory = sysInfo.KernelMemory
		v.KernelMemoryTCP = sysInfo.KernelMemoryTCP
		v.OomKillDisable = sysInfo.OomKillDisable
		v.CPUCfsPeriod = sysInfo.CPUCfs
		v.CPUCfsQuota = sysInfo.CPUCfs
		v.CPUShares = sysInfo.CPUShares
		v.CPUSet = sysInfo.Cpuset
		v.PidsLimit = sysInfo.PidsLimit
	}
	v.Runtimes = daemon.configStore.GetAllRuntimes()
	v.DefaultRuntime = daemon.configStore.GetDefaultRuntimeName()
	v.InitBinary = daemon.configStore.GetInitPath()

	defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, _, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
			v.RuncCommit.ID = "N/A"
		} else {
			v.RuncCommit.ID = commit
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
		v.RuncCommit.ID = "N/A"
	}

	// runc is now shipped as a separate package. Set "expected" to same value
	// as "ID" to prevent clients from reporting a version-mismatch
	v.RuncCommit.Expected = v.RuncCommit.ID

	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.ContainerdCommit.ID = rv.Revision
	} else {
		logrus.Warnf("failed to retrieve containerd version: %v", err)
		v.ContainerdCommit.ID = "N/A"
	}

	// containerd is now shipped as a separate package. Set "expected" to same
	// value as "ID" to prevent clients from reporting a version-mismatch
	v.ContainerdCommit.Expected = v.ContainerdCommit.ID

	// TODO is there still a need to check the expected version for tini?
	// if not, we can change this, and just set "Expected" to v.InitCommit.ID
	v.InitCommit.Expected = dockerversion.InitCommitID

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if _, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
			v.InitCommit.ID = "N/A"
		} else {
			v.InitCommit.ID = commit
			if len(dockerversion.InitCommitID) > len(commit) {
				v.InitCommit.Expected = dockerversion.InitCommitID[0:len(commit)]
			}
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
		v.InitCommit.ID = "N/A"
	}

	if v.CgroupDriver == cgroupNoneDriver {
		if v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. Systemd is required to enable cgroups in rootless-mode.")
		} else {
			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. To enable cgroups in rootless-mode, you need to boot the system in cgroup v2 mode.")
		}
	} else {
		if !v.MemoryLimit {
			v.Warnings = append(v.Warnings, "WARNING: No memory limit support")
		}
		if !v.SwapLimit {
			v.Warnings = append(v.Warnings, "WARNING: No swap limit support")
		}
		if !v.KernelMemoryTCP && v.CgroupVersion == "1" {
			// kernel memory is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No kernel memory TCP limit support")
		}
		if !v.OomKillDisable && v.CgroupVersion == "1" {
			// oom kill disable is not available for cgroup v2.
			// Warning is not printed on cgroup v2, because there is no action user can take.
			v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support")
		}
		if !v.CPUCfsQuota {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support")
		}
		if !v.CPUCfsPeriod {
			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support")
		}
		if !v.CPUShares {
			v.Warnings = append(v.Warnings, "WARNING: No cpu shares support")
		}
		if !v.CPUSet {
			v.Warnings = append(v.Warnings, "WARNING: No cpuset support")
		}
		// TODO add fields for these options in types.Info
		if !sysInfo.BlkioWeight && v.CgroupVersion == "2" {
			// blkio weight is not available on cgroup v1 since kernel 5.0.
			// Warning is not printed on cgroup v1, because there is no action user can take.
			// On cgroup v2, blkio weight is implemented using io.weight
			v.Warnings = append(v.Warnings, "WARNING: No io.weight support")
		}
		if !sysInfo.BlkioWeightDevice && v.CgroupVersion == "2" {
			v.Warnings = append(v.Warnings, "WARNING: No io.weight (per device) support")
		}
		if !sysInfo.BlkioReadBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (rbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_bps_device support")
			}
		}
		if !sysInfo.BlkioWriteBpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wbps) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_bps_device support")
			}
		}
		if !sysInfo.BlkioReadIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (riops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_iops_device support")
			}
		}
		if !sysInfo.BlkioWriteIOpsDevice {
			if v.CgroupVersion == "2" {
				v.Warnings = append(v.Warnings, "WARNING: No io.max (wiops) support")
			} else {
				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_iops_device support")
			}
		}
	}
	if !v.IPv4Forwarding {
		v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
	}
	if !v.BridgeNfIptables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled")
	}
	if !v.BridgeNfIP6tables {
		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled")
	}
}

func (daemon *Daemon) fillPlatformVersion(v *types.Version) {
	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
		v.Components = append(v.Components, types.ComponentVersion{
			Name:    "containerd",
			Version: rv.Version,
			Details: map[string]string{
				"GitCommit": rv.Revision,
			},
		})
	}

	defaultRuntime := daemon.configStore.GetDefaultRuntimeName()
	defaultRuntimeBinary := daemon.configStore.GetRuntime(defaultRuntime).Path
	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
		if _, ver, commit, err := parseRuntimeVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    defaultRuntime,
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
	}

	defaultInitBinary := daemon.configStore.GetInitPath()
	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
		if ver, commit, err := parseInitVersion(string(rv)); err != nil {
			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
		} else {
			v.Components = append(v.Components, types.ComponentVersion{
				Name:    filepath.Base(defaultInitBinary),
				Version: ver,
				Details: map[string]string{
					"GitCommit": commit,
				},
			})
		}
	} else {
		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
	}
}

func fillDriverWarnings(v *types.Info) {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Data loop file" {
			msg := fmt.Sprintf("WARNING: %s: usage of loopback devices is "+
				"strongly discouraged for production use.\n         "+
				"Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.", v.Driver)

			v.Warnings = append(v.Warnings, msg)
			continue
		}
		if pair[0] == "Supports d_type" && pair[1] == "false" {
			backingFs := getBackingFs(v)

			msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", v.Driver, backingFs)
			if backingFs == "xfs" {
				msg += "         Reformat the filesystem with ftype=1 to enable d_type support.\n"
			}
			msg += "         Running without d_type support will not be supported in future releases."

			v.Warnings = append(v.Warnings, msg)
			continue
		}
	}
}

func getBackingFs(v *types.Info) string {
	for _, pair := range v.DriverStatus {
		if pair[0] == "Backing Filesystem" {
			return pair[1]
		}
	}
	return ""
}

// parseInitVersion parses a Tini version string, and extracts the "version"
// and "git commit" from the output.
//
// Output example from `docker-init --version`:
//
//     tini version 0.18.0 - git.fec3683
func parseInitVersion(v string) (version string, commit string, err error) {
	parts := strings.Split(v, " - ")

	if len(parts) >= 2 {
		gitParts := strings.Split(strings.TrimSpace(parts[1]), ".")
		if len(gitParts) == 2 && gitParts[0] == "git" {
			commit = gitParts[1]
		}
	}
	parts[0] = strings.TrimSpace(parts[0])
	if strings.HasPrefix(parts[0], "tini version ") {
		version = strings.TrimPrefix(parts[0], "tini version ")
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return version, commit, err
}

// parseRuntimeVersion parses the output of `[runtime] --version` and extracts the
// "name", "version" and "git commit" from the output.
//
// Output example from `runc --version`:
//
//   runc version 1.0.0-rc5+dev
//   commit: 69663f0bd4b60df09991c08812a60108003fa340
//   spec: 1.0.0
func parseRuntimeVersion(v string) (runtime string, version string, commit string, err error) {
	lines := strings.Split(strings.TrimSpace(v), "\n")
	for _, line := range lines {
		if strings.Contains(line, "version") {
			s := strings.Split(line, "version")
			runtime = strings.TrimSpace(s[0])
			version = strings.TrimSpace(s[len(s)-1])
			continue
		}
		if strings.HasPrefix(line, "commit:") {
			commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
			continue
		}
	}
	if version == "" && commit == "" {
		err = errors.Errorf("unknown output format: %s", v)
	}
	return runtime, version, commit, err
}

func (daemon *Daemon) cgroupNamespacesEnabled(sysInfo *sysinfo.SysInfo) bool {
	return sysInfo.CgroupNamespaces && containertypes.CgroupnsMode(daemon.configStore.CgroupNamespaceMode).IsPrivate()
}

// Rootless returns true if daemon is running in rootless mode
func (daemon *Daemon) Rootless() bool {
	return daemon.configStore.Rootless
}
<|endoftext|>


<commit_before>package sockd

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"io"
	pseudoRand "math/rand"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/HouzuoGuo/laitos/daemon/common"
	"github.com/HouzuoGuo/laitos/daemon/dnsd"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/misc"
)

func WriteRand(conn net.Conn) {
	randBytesWritten := 0
	for i := 0; i < RandNum(0, 1, 2); i++ {
		randBuf := make([]byte, RandNum(70, 120, 200))
		if _, err := pseudoRand.Read(randBuf); err != nil {
			break
		}
		if err := conn.SetWriteDeadline(time.Now().Add(time.Duration(RandNum(330, 540, 880)) * time.Millisecond)); err != nil {
			break
		}
		if n, err := conn.Write(randBuf); err != nil && !strings.Contains(err.Error(), "closed") && !strings.Contains(err.Error(), "broken") {
			break
		} else {
			randBytesWritten += n
		}
	}
	if pseudoRand.Intn(100) < 3 {
		lalog.DefaultLogger.Info("sockd.TCP.WriteRand", conn.RemoteAddr().String(), nil, "wrote %d rand bytes", randBytesWritten)
	}
}

func TweakTCPConnection(conn *net.TCPConn) {
	_ = conn.SetNoDelay(true)
	_ = conn.SetKeepAlive(true)
	_ = conn.SetKeepAlivePeriod(60 * time.Second)
}

func PipeTCPConnection(fromConn, toConn net.Conn, doWriteRand bool) {
	defer func() {
		_ = toConn.Close()
	}()
	buf := make([]byte, MaxPacketSize)
	for {
		if misc.EmergencyLockDown {
			lalog.DefaultLogger.Warning("PipeTCPConnection", "", misc.ErrEmergencyLockDown, "")
			return
		} else if err := fromConn.SetReadDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err != nil {
			return
		}
		length, err := fromConn.Read(buf)
		if length > 0 {
			if err := toConn.SetWriteDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err != nil {
				return
			} else if _, err := toConn.Write(buf[:length]); err != nil {
				return
			}
		}
		if err != nil {
			if doWriteRand {
				WriteRand(fromConn)
			}
			return
		}
	}
}

type TCPDaemon struct {
	Address    string `json:"Address"`
	Password   string `json:"Password"`
	PerIPLimit int    `json:"PerIPLimit"`
	TCPPort    int    `json:"TCPPort"`

	DNSDaemon *dnsd.Daemon `json:"-"` // it is assumed to be already initialised

	cipher    *Cipher
	tcpServer *common.TCPServer
}

func (daemon *TCPDaemon) Initialise() error {
	daemon.cipher = &Cipher{}
	daemon.cipher.Initialise(daemon.Password)
	daemon.tcpServer = &common.TCPServer{
		ListenAddr:  daemon.Address,
		ListenPort:  daemon.TCPPort,
		AppName:     "sockd",
		App:         daemon,
		LimitPerSec: daemon.PerIPLimit,
	}
	daemon.tcpServer.Initialise()
	return nil
}

func (daemon *TCPDaemon) GetTCPStatsCollector() *misc.Stats {
	return common.SOCKDStatsTCP
}

func (daemon *TCPDaemon) HandleTCPConnection(logger lalog.Logger, ip string, client *net.TCPConn) {
	NewTCPCipherConnection(daemon, client, daemon.cipher.Copy(), logger).HandleTCPConnection()
}

func (daemon *TCPDaemon) StartAndBlock() error {
	return daemon.tcpServer.StartAndBlock()
}

func (daemon *TCPDaemon) Stop() {
	daemon.tcpServer.Stop()
}

type TCPCipherConnection struct {
	net.Conn
	*Cipher
	daemon            *TCPDaemon
	mutex             sync.Mutex
	readBuf, writeBuf []byte
	logger            lalog.Logger
}

func NewTCPCipherConnection(daemon *TCPDaemon, netConn net.Conn, cip *Cipher, logger lalog.Logger) *TCPCipherConnection {
	return &TCPCipherConnection{
		Conn:     netConn,
		daemon:   daemon,
		Cipher:   cip,
		readBuf:  make([]byte, MaxPacketSize),
		writeBuf: make([]byte, MaxPacketSize),
		logger:   logger,
	}
}

func (conn *TCPCipherConnection) Close() error {
	return conn.Conn.Close()
}

func (conn *TCPCipherConnection) Read(b []byte) (n int, err error) {
	if conn.DecryptionStream == nil {
		iv := make([]byte, conn.IVLength)
		if _, err = io.ReadFull(conn.Conn, iv); err != nil {
			return
		}
		conn.InitDecryptionStream(iv)
		if len(conn.IV) == 0 {
			conn.IV = iv
		}
	}

	cipherData := conn.readBuf
	if len(b) > len(cipherData) {
		cipherData = make([]byte, len(b))
	} else {
		cipherData = cipherData[:len(b)]
	}

	n, err = conn.Conn.Read(cipherData)
	if n > 0 {
		conn.Decrypt(b[0:n], cipherData[0:n])
	}
	return
}

func (conn *TCPCipherConnection) Write(buf []byte) (n int, err error) {
	conn.mutex.Lock()
	bufSize := len(buf)
	headerLen := len(buf) - bufSize

	var iv []byte
	if conn.EncryptionStream == nil {
		iv = conn.InitEncryptionStream()
	}

	cipherData := conn.writeBuf
	dataSize := len(buf) + len(iv)
	if dataSize > len(cipherData) {
		cipherData = make([]byte, dataSize)
	} else {
		cipherData = cipherData[:dataSize]
	}

	if iv != nil {
		copy(cipherData, iv)
	}

	conn.Encrypt(cipherData[len(iv):], buf)
	n, err = conn.Conn.Write(cipherData)

	if n >= headerLen {
		n -= headerLen
	}
	conn.mutex.Unlock()
	return
}

func (conn *TCPCipherConnection) ParseRequest() (destIP net.IP, destNoPort, destWithPort string, err error) {
	if err = conn.SetReadDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err != nil {
		conn.logger.MaybeMinorError(err)
		return
	}

	buf := make([]byte, 269)
	if _, err = io.ReadFull(conn, buf[:AddressTypeIndex+1]); err != nil {
		return
	}

	var reqStart, reqEnd int
	addrType := buf[AddressTypeIndex]
	maskedType := addrType & AddressTypeMask
	switch maskedType {
	case AddressTypeIPv4:
		reqStart, reqEnd = IPPacketIndex, IPPacketIndex+IPv4PacketLength
	case AddressTypeIPv6:
		reqStart, reqEnd = IPPacketIndex, IPPacketIndex+IPv6PacketLength
	case AddressTypeDM:
		if _, err = io.ReadFull(conn, buf[AddressTypeIndex+1:DMAddrLengthIndex+1]); err != nil {
			return
		}
		reqStart, reqEnd = DMAddrIndex, DMAddrIndex+int(buf[DMAddrLengthIndex])+DMAddrHeaderLength
	default:
		err = fmt.Errorf("TCPCipherConnection.ParseRequest: unknown mask type %d", maskedType)
		return
	}

	if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
		return
	}
	port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
	if port < 1 {
		err = fmt.Errorf("TCPCipherConnection.ParseRequest: invalid destination port %d", port)
		return
	}

	switch maskedType {
	case AddressTypeIPv4:
		destIP = buf[IPPacketIndex : IPPacketIndex+net.IPv4len]
		destNoPort = destIP.String()
		destWithPort = net.JoinHostPort(destIP.String(), strconv.Itoa(int(port)))
	case AddressTypeIPv6:
		destIP = buf[IPPacketIndex : IPPacketIndex+net.IPv6len]
		destNoPort = destIP.String()
		destWithPort = net.JoinHostPort(destIP.String(), strconv.Itoa(int(port)))
	case AddressTypeDM:
		dest := string(buf[DMAddrIndex : DMAddrIndex+int(buf[DMAddrLengthIndex])])
		destNoPort = dest
		destIP = net.ParseIP(dest)
		destWithPort = net.JoinHostPort(dest, strconv.Itoa(int(port)))
	}
	if strings.ContainsRune(destNoPort, 0) || strings.ContainsRune(destWithPort, 0) {
		err = fmt.Errorf("TCPCipherConnection.ParseRequest: destination must not contain NULL byte")
	}
	return
}

func (conn *TCPCipherConnection) WriteRandAndClose() {
	defer func() {
		_ = conn.Close()
	}()
	randBuf := make([]byte, RandNum(20, 70, 200))
	_, err := rand.Read(randBuf)
	if err != nil {
		conn.logger.Warning("WriteRandAndClose", conn.Conn.RemoteAddr().String(), err, "failed to get random bytes")
		return
	}
	if err := conn.SetWriteDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err != nil {
		conn.logger.Warning("WriteRandAndClose", conn.Conn.RemoteAddr().String(), err, "failed to write random bytes")
		return
	}
	if _, err := conn.Write(randBuf); err != nil {
		conn.logger.Warning("WriteRandAndClose", conn.Conn.RemoteAddr().String(), err, "failed to write random bytes")
		return
	}
}

func (conn *TCPCipherConnection) HandleTCPConnection() {
	remoteAddr := conn.RemoteAddr().String()
	destIP, destNoPort, destWithPort, err := conn.ParseRequest()
	if err != nil {
		conn.logger.Warning("HandleTCPConnection", remoteAddr, err, "failed to get destination address")
		conn.WriteRandAndClose()
		return
	}
	if strings.ContainsRune(destWithPort, 0) {
		conn.logger.Warning("HandleTCPConnection", remoteAddr, nil, "will not serve invalid destination address with 0 in it")
		conn.WriteRandAndClose()
		return
	}
	if destIP != nil && IsReservedAddr(destIP) {
		conn.logger.Info("HandleTCPConnection", remoteAddr, nil, "will not serve reserved address %s", destNoPort)
		_ = conn.Close()
		return
	}
	if conn.daemon.DNSDaemon.IsInBlacklist(destNoPort) {
		conn.logger.Info("HandleTCPConnection", remoteAddr, nil, "will not serve blacklisted address %s", destNoPort)
		_ = conn.Close()
		return
	}
	dest, err := net.DialTimeout("tcp", destWithPort, IOTimeoutSec*time.Second)
	if err != nil {
		conn.logger.Warning("HandleTCPConnection", remoteAddr, err, "failed to connect to destination \"%s\"", destWithPort)
		_ = conn.Close()
		return
	}
	TweakTCPConnection(conn.Conn.(*net.TCPConn))
	TweakTCPConnection(dest.(*net.TCPConn))
	go PipeTCPConnection(conn, dest, true)
	PipeTCPConnection(dest, conn, false)
}
<commit_msg>tweak socket pipe behaviours in sockd<commit_after>package sockd

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"io"
	pseudoRand "math/rand"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/HouzuoGuo/laitos/daemon/common"
	"github.com/HouzuoGuo/laitos/daemon/dnsd"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/misc"
)

// WriteRand writes up to 5 packets of random data to the connection, each packet contains up to 600 bytes of data.
func WriteRand(conn net.Conn) {
	randBytesWritten := 0
	for i := 0; i < RandNum(1, 2, 5); i++ {
		randBuf := make([]byte, RandNum(80, 210, 550))
		if _, err := pseudoRand.Read(randBuf); err != nil {
			break
		}
		if err := conn.SetWriteDeadline(time.Now().Add(time.Duration(RandNum(890, 1440, 2330)) * time.Millisecond)); err != nil {
			break
		}
		if n, err := conn.Write(randBuf); err != nil && !strings.Contains(err.Error(), "closed") && !strings.Contains(err.Error(), "broken") {
			break
		} else {
			randBytesWritten += n
		}
	}
	if pseudoRand.Intn(100) < 3 {
		lalog.DefaultLogger.Info("sockd.TCP.WriteRand", conn.RemoteAddr().String(), nil, "wrote %d rand bytes", randBytesWritten)
	}
}

// TweakTCPConnection tweaks the TCP connection settings for improved responsiveness.
func TweakTCPConnection(conn *net.TCPConn) {
	_ = conn.SetNoDelay(true)
	_ = conn.SetKeepAlive(true)
	_ = conn.SetKeepAlivePeriod(60 * time.Second)
	_ = conn.SetDeadline(time.Now().Add(time.Duration(IOTimeoutSec * time.Second)))
	_ = conn.SetLinger(5)
}

/*
ReadWithRetry makes at most 5 attempts at reading incoming data from the connection.
If data is partially read before an IO error occurs, then the connection will be closed.
*/
func ReadWithRetry(conn net.Conn, buf []byte) (n int, err error) {
	attempts := 0
	for ; attempts < 5; attempts++ {
		if err = conn.SetReadDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err == nil {
			if n, err = conn.Read(buf); err == nil {
				break
			} else if n > 0 {
				// IO error occurred after data is partially read, the data stream is now broken.
				_ = conn.Close()
				break
			}
		}
		// Sleep couple of seconds in between attempts
		time.Sleep(time.Second * time.Duration(attempts))
	}
	if pseudoRand.Intn(100) < 3 {
		lalog.DefaultLogger.Info("sockd.TCP.ReadWithRetry", conn.RemoteAddr().String(), err, "read %d amount of data in %d attempts", n, attempts+1)
	}
	return
}

/*
WriteWithRetry makes at most 5 attempts at writing the data into the connection.
If data is partially written before an IO error occurs, then the connection will be closed.
*/
func WriteWithRetry(conn net.Conn, buf []byte) (n int, err error) {
	attempts := 0
	for ; attempts < 5; attempts++ {
		if err = conn.SetWriteDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err == nil {
			if n, err = conn.Write(buf); err == nil {
				break
			} else if n > 0 {
				// IO error occurred after data is partially written, the data stream is now broken.
				_ = conn.Close()
				break
			}
		}
		// Sleep couple of seconds in between attempts
		time.Sleep(time.Second * time.Duration(attempts))
	}
	if pseudoRand.Intn(100) < 3 {
		lalog.DefaultLogger.Info("sockd.TCP.WriteWithRetry", conn.RemoteAddr().String(), err, "wrote %d amount of data in %d attempts", n, attempts+1)
	}
	return
}

func PipeTCPConnection(fromConn, toConn net.Conn, doWriteRand bool) {
	defer func() {
		_ = toConn.Close()
	}()
	buf := make([]byte, MaxPacketSize)
	for {
		if misc.EmergencyLockDown {
			lalog.DefaultLogger.Warning("PipeTCPConnection", "", misc.ErrEmergencyLockDown, "")
			return
		}
		length, err := ReadWithRetry(fromConn, buf)
		if err != nil {
			if doWriteRand {
				WriteRand(fromConn)
			}
			return
		}
		if length > 0 {
			if _, err := WriteWithRetry(toConn, buf[:length]); err != nil {
				return
			}
		}
	}
}

type TCPDaemon struct {
	Address    string `json:"Address"`
	Password   string `json:"Password"`
	PerIPLimit int    `json:"PerIPLimit"`
	TCPPort    int    `json:"TCPPort"`

	DNSDaemon *dnsd.Daemon `json:"-"` // it is assumed to be already initialised

	cipher    *Cipher
	tcpServer *common.TCPServer
}

func (daemon *TCPDaemon) Initialise() error {
	daemon.cipher = &Cipher{}
	daemon.cipher.Initialise(daemon.Password)
	daemon.tcpServer = &common.TCPServer{
		ListenAddr:  daemon.Address,
		ListenPort:  daemon.TCPPort,
		AppName:     "sockd",
		App:         daemon,
		LimitPerSec: daemon.PerIPLimit,
	}
	daemon.tcpServer.Initialise()
	return nil
}

func (daemon *TCPDaemon) GetTCPStatsCollector() *misc.Stats {
	return common.SOCKDStatsTCP
}

func (daemon *TCPDaemon) HandleTCPConnection(logger lalog.Logger, ip string, client *net.TCPConn) {
	NewTCPCipherConnection(daemon, client, daemon.cipher.Copy(), logger).HandleTCPConnection()
}

func (daemon *TCPDaemon) StartAndBlock() error {
	return daemon.tcpServer.StartAndBlock()
}

func (daemon *TCPDaemon) Stop() {
	daemon.tcpServer.Stop()
}

type TCPCipherConnection struct {
	net.Conn
	*Cipher
	daemon            *TCPDaemon
	mutex             sync.Mutex
	readBuf, writeBuf []byte
	logger            lalog.Logger
}

func NewTCPCipherConnection(daemon *TCPDaemon, netConn net.Conn, cip *Cipher, logger lalog.Logger) *TCPCipherConnection {
	return &TCPCipherConnection{
		Conn:     netConn,
		daemon:   daemon,
		Cipher:   cip,
		readBuf:  make([]byte, MaxPacketSize),
		writeBuf: make([]byte, MaxPacketSize),
		logger:   logger,
	}
}

func (conn *TCPCipherConnection) Close() error {
	return conn.Conn.Close()
}

func (conn *TCPCipherConnection) Read(b []byte) (n int, err error) {
	if conn.DecryptionStream == nil {
		iv := make([]byte, conn.IVLength)
		if _, err = io.ReadFull(conn.Conn, iv); err != nil {
			return
		}
		conn.InitDecryptionStream(iv)
		if len(conn.IV) == 0 {
			conn.IV = iv
		}
	}

	cipherData := conn.readBuf
	if len(b) > len(cipherData) {
		cipherData = make([]byte, len(b))
	} else {
		cipherData = cipherData[:len(b)]
	}

	n, err = conn.Conn.Read(cipherData)
	if n > 0 {
		conn.Decrypt(b[0:n], cipherData[0:n])
	}
	return
}

func (conn *TCPCipherConnection) Write(buf []byte) (n int, err error) {
	conn.mutex.Lock()
	bufSize := len(buf)
	headerLen := len(buf) - bufSize

	var iv []byte
	if conn.EncryptionStream == nil {
		iv = conn.InitEncryptionStream()
	}

	cipherData := conn.writeBuf
	dataSize := len(buf) + len(iv)
	if dataSize > len(cipherData) {
		cipherData = make([]byte, dataSize)
	} else {
		cipherData = cipherData[:dataSize]
	}

	if iv != nil {
		copy(cipherData, iv)
	}

	conn.Encrypt(cipherData[len(iv):], buf)
	n, err = conn.Conn.Write(cipherData)

	if n >= headerLen {
		n -= headerLen
	}
	conn.mutex.Unlock()
	return
}

func (conn *TCPCipherConnection) ParseRequest() (destIP net.IP, destNoPort, destWithPort string, err error) {
	if err = conn.SetReadDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err != nil {
		conn.logger.MaybeMinorError(err)
		return
	}

	buf := make([]byte, 269)
	if _, err = io.ReadFull(conn, buf[:AddressTypeIndex+1]); err != nil {
		return
	}

	var reqStart, reqEnd int
	addrType := buf[AddressTypeIndex]
	maskedType := addrType & AddressTypeMask
	switch maskedType {
	case AddressTypeIPv4:
		reqStart, reqEnd = IPPacketIndex, IPPacketIndex+IPv4PacketLength
	case AddressTypeIPv6:
		reqStart, reqEnd = IPPacketIndex, IPPacketIndex+IPv6PacketLength
	case AddressTypeDM:
		if _, err = io.ReadFull(conn, buf[AddressTypeIndex+1:DMAddrLengthIndex+1]); err != nil {
			return
		}
		reqStart, reqEnd = DMAddrIndex, DMAddrIndex+int(buf[DMAddrLengthIndex])+DMAddrHeaderLength
	default:
		err = fmt.Errorf("TCPCipherConnection.ParseRequest: unknown mask type %d", maskedType)
		return
	}

	if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
		return
	}
	port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
	if port < 1 {
		err = fmt.Errorf("TCPCipherConnection.ParseRequest: invalid destination port %d", port)
		return
	}

	switch maskedType {
	case AddressTypeIPv4:
		destIP = buf[IPPacketIndex : IPPacketIndex+net.IPv4len]
		destNoPort = destIP.String()
		destWithPort = net.JoinHostPort(destIP.String(), strconv.Itoa(int(port)))
	case AddressTypeIPv6:
		destIP = buf[IPPacketIndex : IPPacketIndex+net.IPv6len]
		destNoPort = destIP.String()
		destWithPort = net.JoinHostPort(destIP.String(), strconv.Itoa(int(port)))
	case AddressTypeDM:
		dest := string(buf[DMAddrIndex : DMAddrIndex+int(buf[DMAddrLengthIndex])])
		destNoPort = dest
		destIP = net.ParseIP(dest)
		destWithPort = net.JoinHostPort(dest, strconv.Itoa(int(port)))
	}
	if strings.ContainsRune(destNoPort, 0) || strings.ContainsRune(destWithPort, 0) {
		err = fmt.Errorf("TCPCipherConnection.ParseRequest: destination must not contain NULL byte")
	}
	return
}

func (conn *TCPCipherConnection) WriteRandAndClose() {
	defer func() {
		_ = conn.Close()
	}()
	randBuf := make([]byte, RandNum(20, 70, 200))
	_, err := rand.Read(randBuf)
	if err != nil {
		conn.logger.Warning("WriteRandAndClose", conn.Conn.RemoteAddr().String(), err, "failed to get random bytes")
		return
	}
	if err := conn.SetWriteDeadline(time.Now().Add(IOTimeoutSec * time.Second)); err != nil {
		conn.logger.Warning("WriteRandAndClose", conn.Conn.RemoteAddr().String(), err, "failed to write random bytes")
		return
	}
	if _, err := conn.Write(randBuf); err != nil {
		conn.logger.Warning("WriteRandAndClose", conn.Conn.RemoteAddr().String(), err, "failed to write random bytes")
		return
	}
}

func (conn *TCPCipherConnection) HandleTCPConnection() {
	remoteAddr := conn.RemoteAddr().String()
	destIP, destNoPort, destWithPort, err := conn.ParseRequest()
	if err != nil {
		conn.logger.Warning("HandleTCPConnection", remoteAddr, err, "failed to get destination address")
		conn.WriteRandAndClose()
		return
	}
	if strings.ContainsRune(destWithPort, 0) {
		conn.logger.Warning("HandleTCPConnection", remoteAddr, nil, "will not serve invalid destination address with 0 in it")
		conn.WriteRandAndClose()
		return
	}
	if destIP != nil && IsReservedAddr(destIP) {
		conn.logger.Info("HandleTCPConnection", remoteAddr, nil, "will not serve reserved address %s", destNoPort)
		_ = conn.Close()
		return
	}
	if conn.daemon.DNSDaemon.IsInBlacklist(destNoPort) {
		conn.logger.Info("HandleTCPConnection", remoteAddr, nil, "will not serve blacklisted address %s", destNoPort)
		_ = conn.Close()
		return
	}
	dest, err := net.DialTimeout("tcp", destWithPort, IOTimeoutSec*time.Second)
	if err != nil {
		conn.logger.Warning("HandleTCPConnection", remoteAddr, err, "failed to connect to destination \"%s\"", destWithPort)
		_ = conn.Close()
		return
	}
	TweakTCPConnection(conn.Conn.(*net.TCPConn))
	TweakTCPConnection(dest.(*net.TCPConn))
	go PipeTCPConnection(conn, dest, true)
	PipeTCPConnection(dest, conn, false)
}
<|endoftext|>


<commit_before>package poego

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
)

type Poego struct {
	client *http.Client
}

const (
	BaseUrl = "api.pathofexile.com"
	Scheme  = "http"
)

//Initialize the API with a http.client and base values
func NewApi() *Poego {
	return &Poego{
		client: &http.Client{},
	}
}

func (p *Poego) buildRequest(method, endpoint string, v url.Values) *http.Request {

	if v == nil {
		v = url.Values{}
	}

	u := &url.URL{
		Scheme:   Scheme,
		Path:     BaseUrl + endpoint,
		RawQuery: v.Encode(),
	}

	r, err := http.NewRequest(method, u.String(), nil)

	if err != nil {
		log.Fatal(err)
	}

	return r
}

func (p *Poego) makeRequest(r *http.Request, d interface{}) error {

	res, err := p.client.Do(r)

	if err != nil {
		log.Fatal(err)
	}

	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return handleApiError(res)
	}

	err = json.NewDecoder(res.Body).Decode(d)

	return err
}

func handleApiError(res *http.Response) error {

	var apiError ApiError
	err := json.NewDecoder(res.Body).Decode(&apiError)

	if err != nil {
		return fmt.Errorf("Status Code: %d\nMessage: Couldn't decode API error", res.StatusCode)
	}

	return fmt.Errorf("Status Code: %d\nPoE Error Code: %d\nMessage: %s", res.StatusCode, apiError.Error.Code, apiError.Error.Message)
}
<commit_msg>Add global documentation value to godocs<commit_after>//Package poego provides a wrapper of the Path of Exile API in Go

package poego

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
)

type Poego struct {
	client *http.Client
}

const (
	BaseUrl = "api.pathofexile.com"
	Scheme  = "http"
)

//Initialize the API with a http.client and base values
func NewApi() *Poego {
	return &Poego{
		client: &http.Client{},
	}
}

func (p *Poego) buildRequest(method, endpoint string, v url.Values) *http.Request {

	if v == nil {
		v = url.Values{}
	}

	u := &url.URL{
		Scheme:   Scheme,
		Path:     BaseUrl + endpoint,
		RawQuery: v.Encode(),
	}

	r, err := http.NewRequest(method, u.String(), nil)

	if err != nil {
		log.Fatal(err)
	}

	return r
}

func (p *Poego) makeRequest(r *http.Request, d interface{}) error {

	res, err := p.client.Do(r)

	if err != nil {
		log.Fatal(err)
	}

	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return handleApiError(res)
	}

	err = json.NewDecoder(res.Body).Decode(d)

	return err
}

func handleApiError(res *http.Response) error {

	var apiError ApiError
	err := json.NewDecoder(res.Body).Decode(&apiError)

	if err != nil {
		return fmt.Errorf("Status Code: %d\nMessage: Couldn't decode API error", res.StatusCode)
	}

	return fmt.Errorf("Status Code: %d\nPoE Error Code: %d\nMessage: %s", res.StatusCode, apiError.Error.Code, apiError.Error.Message)
}
<|endoftext|>


<commit_before>package geo

import "math"

// Point описывает координаты географической точки: долгота, широта. Именно такая последовательность
// выбрана только потому, что это наиболее близко представлению координат на плоскости: x и y, а не
// наоборот. Вторая причина — именно такой формат точек поддерживается в MongoDB, а данная
// библиотека сделана в первую очередь именно для работы с этой базой данных.
type Point [2]float64

// NewPoint возвращает новое описание точки с указанными координатами. Если координаты выходят
// за допустимый диапазон данных для географических координат, то возникает panic.
func NewPoint(lon, lat float64) Point {
	if lon < -180 || lon > 180 {
		panic("bad longitude")
	}
	if lat < -90 || lat > 90 {
		panic("bad latitude")
	}
	return Point{lon, lat}
}

// Longitude возвращает долготу точки.
func (p Point) Longitude() float64 {
	return p[0]
}

// Latitude возвращает широту точки.
func (p Point) Latitude() float64 {
	return p[1]
}

// IsZero возвращает true, если обе координаты точки равны нулю.
func (p Point) IsZero() bool {
	return p[0] == 0 && p[1] == 0
}

// Geo возвращает представление точки в формате GeoJSON.
func (p Point) Geo() *GeoJSON {
	if p.IsZero() {
		return nil
	}
	return &GeoJSON{
		Type:        "Point",
		Coordinates: p[:],
	}
}

const (
	EarthRadius float64 = 6378137.0 // радиус Земли в метрах
)

// Move возвращает новую точку, перемещенную от изначально на dist метров в направлении bearing
// в градусах.
func (p Point) Move(dist float64, bearing float64) Point {
	dr := dist / EarthRadius
	bearing = bearing * math.Pi / 180.0
	lon1 := p.Longitude() * math.Pi / 180.0
	lat1 := p.Latitude() * math.Pi / 180.0
	lat2_part1 := math.Sin(lat1) * math.Cos(dr)
	lat2_part2 := math.Cos(lat1) * math.Sin(dr) * math.Cos(bearing)
	lat2 := math.Asin(lat2_part1 + lat2_part2)
	lon2_part1 := math.Sin(bearing) * math.Sin(dr) * math.Cos(lat1)
	lon2_part2 := math.Cos(dr) - (math.Sin(lat1) * math.Sin(lat2))
	lon2 := lon1 + math.Atan2(lon2_part1, lon2_part2)
	lon2 = math.Mod((lon2+3*math.Pi), (2*math.Pi)) - math.Pi
	lon2 = lon2 * 180.0 / math.Pi
	lat2 = lat2 * 180.0 / math.Pi
	return NewPoint(lon2, lat2)
}

// BearingTo возвращает направление в градусах на указанную точку.
func (p Point) BearingTo(p2 Point) float64 {
	dLon := (p2.Longitude() - p.Longitude()) * math.Pi / 180.0
	lat1 := p.Latitude() * math.Pi / 180.0
	lat2 := p2.Latitude() * math.Pi / 180.0
	y := math.Sin(dLon) * math.Cos(lat2)
	x := math.Cos(lat1)*math.Sin(lat2) -
		math.Sin(lat1)*math.Cos(lat2)*math.Cos(dLon)
	return math.Atan2(y, x) * 180.0 / math.Pi
}

// Distance возвращает дистанцию между двумя точками в метрах.
func (p Point) Distance(p2 Point) float64 {
	dLon := (p2.Longitude() - p.Longitude()) * math.Pi / 180.0
	dLat := (p2.Latitude() - p.Latitude()) * math.Pi / 180.0
	lat1 := p.Latitude() * math.Pi / 180.0
	lat2 := p2.Latitude() * math.Pi / 180.0
	a := math.Sin(dLat/2)*math.Sin(dLat/2) +
		math.Sin(dLon/2)*math.Sin(dLon/2)*math.Cos(lat1)*math.Cos(lat2)
	return EarthRadius * 2 * math.Asin(math.Sqrt(a))
}

// Centroid возвращает цент окружности, содержащей все указанные точки.
func Centroid(points ...Point) Point {
	switch l := float64(len(points)); l {
	case 0:
		return Point{}
	case 1:
		return points[0]
	default:
		var lon, lat float64
		for _, point := range points {
			lon += point.Longitude()
			lat += point.Latitude()
		}
		return Point{lon / l, lat / l}
	}
}
<commit_msg>JSON представление<commit_after>package geo

import (
	"fmt"
	"math"
)

// Point описывает координаты географической точки: долгота, широта. Именно такая последовательность
// выбрана только потому, что это наиболее близко представлению координат на плоскости: x и y, а не
// наоборот. Вторая причина — именно такой формат точек поддерживается в MongoDB, а данная
// библиотека сделана в первую очередь именно для работы с этой базой данных.
type Point [2]float64

// NewPoint возвращает новое описание точки с указанными координатами. Если координаты выходят
// за допустимый диапазон данных для географических координат, то возникает panic.
func NewPoint(lon, lat float64) Point {
	if lon < -180 || lon > 180 {
		panic("bad longitude")
	}
	if lat < -90 || lat > 90 {
		panic("bad latitude")
	}
	return Point{lon, lat}
}

// Longitude возвращает долготу точки.
func (p Point) Longitude() float64 {
	return p[0]
}

// Latitude возвращает широту точки.
func (p Point) Latitude() float64 {
	return p[1]
}

// IsZero возвращает true, если обе координаты точки равны нулю.
func (p Point) IsZero() bool {
	return p[0] == 0 && p[1] == 0
}

// Geo возвращает представление точки в формате GeoJSON.
func (p Point) Geo() *GeoJSON {
	if p.IsZero() {
		return nil
	}
	return &GeoJSON{
		Type:        "Point",
		Coordinates: p[:],
	}
}

// MarshalJSON отдает форматированное представление JSON.
func (p Point) MarshalJSON() ([]byte, error) {
	if p.IsZero() {
		return []byte("null"), nil
	}
	return []byte(fmt.Sprintf("[%.6f, %.6f]", p.Longitude(), p.Latitude()))
}

const (
	EarthRadius float64 = 6378137.0 // радиус Земли в метрах
)

// Move возвращает новую точку, перемещенную от изначально на dist метров в направлении bearing
// в градусах.
func (p Point) Move(dist float64, bearing float64) Point {
	dr := dist / EarthRadius
	bearing = bearing * math.Pi / 180.0
	lon1 := p.Longitude() * math.Pi / 180.0
	lat1 := p.Latitude() * math.Pi / 180.0
	lat2_part1 := math.Sin(lat1) * math.Cos(dr)
	lat2_part2 := math.Cos(lat1) * math.Sin(dr) * math.Cos(bearing)
	lat2 := math.Asin(lat2_part1 + lat2_part2)
	lon2_part1 := math.Sin(bearing) * math.Sin(dr) * math.Cos(lat1)
	lon2_part2 := math.Cos(dr) - (math.Sin(lat1) * math.Sin(lat2))
	lon2 := lon1 + math.Atan2(lon2_part1, lon2_part2)
	lon2 = math.Mod((lon2+3*math.Pi), (2*math.Pi)) - math.Pi
	lon2 = lon2 * 180.0 / math.Pi
	lat2 = lat2 * 180.0 / math.Pi
	return NewPoint(lon2, lat2)
}

// BearingTo возвращает направление в градусах на указанную точку.
func (p Point) BearingTo(p2 Point) float64 {
	dLon := (p2.Longitude() - p.Longitude()) * math.Pi / 180.0
	lat1 := p.Latitude() * math.Pi / 180.0
	lat2 := p2.Latitude() * math.Pi / 180.0
	y := math.Sin(dLon) * math.Cos(lat2)
	x := math.Cos(lat1)*math.Sin(lat2) -
		math.Sin(lat1)*math.Cos(lat2)*math.Cos(dLon)
	return math.Atan2(y, x) * 180.0 / math.Pi
}

// Distance возвращает дистанцию между двумя точками в метрах.
func (p Point) Distance(p2 Point) float64 {
	dLon := (p2.Longitude() - p.Longitude()) * math.Pi / 180.0
	dLat := (p2.Latitude() - p.Latitude()) * math.Pi / 180.0
	lat1 := p.Latitude() * math.Pi / 180.0
	lat2 := p2.Latitude() * math.Pi / 180.0
	a := math.Sin(dLat/2)*math.Sin(dLat/2) +
		math.Sin(dLon/2)*math.Sin(dLon/2)*math.Cos(lat1)*math.Cos(lat2)
	return EarthRadius * 2 * math.Asin(math.Sqrt(a))
}

// Centroid возвращает цент окружности, содержащей все указанные точки.
func Centroid(points ...Point) Point {
	switch l := float64(len(points)); l {
	case 0:
		return Point{}
	case 1:
		return points[0]
	default:
		var lon, lat float64
		for _, point := range points {
			lon += point.Longitude()
			lat += point.Latitude()
		}
		return Point{lon / l, lat / l}
	}
}
<|endoftext|>


<commit_before>package framework

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/mitchellh/mapstructure"
)

// FieldData is the structure passed to the callback to handle a path
// containing the populated parameters for fields. This should be used
// instead of the raw (*vault.Request).Data to access data in a type-safe
// way.
type FieldData struct {
	Raw    map[string]interface{}
	Schema map[string]*FieldSchema
}

// Cycle through raw data and validate conversions in
// the schema, so we don't get an error/panic later when
// trying to get data out.  Data not in the schema is not
// an error at this point, so we don't worry about it.
func (d *FieldData) Validate() error {
	for field, value := range d.Raw {

		schema, ok := d.Schema[field]
		if !ok {
			continue
		}

		switch schema.Type {
		case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
			_, _, err := d.getPrimitive(field, schema)
			if err != nil {
				return fmt.Errorf("Error converting input %v for field %s: %s", value, field, err)
			}
		default:
			return fmt.Errorf("unknown field type %s for field %s",
				schema.Type, field)
		}
	}

	return nil
}

// Get gets the value for the given field. If the key is an invalid field,
// FieldData will panic. If you want a safer version of this method, use
// GetOk. If the field k is not set, the default value (if set) will be
// returned, otherwise the zero value will be returned.
func (d *FieldData) Get(k string) interface{} {
	schema, ok := d.Schema[k]
	if !ok {
		panic(fmt.Sprintf("field %s not in the schema", k))
	}

	value, ok := d.GetOk(k)
	if !ok {
		value = schema.DefaultOrZero()
	}

	return value
}

// GetOk gets the value for the given field. The second return value
// will be false if the key is invalid or the key is not set at all.
func (d *FieldData) GetOk(k string) (interface{}, bool) {
	schema, ok := d.Schema[k]
	if !ok {
		return nil, false
	}

	result, ok, err := d.GetOkErr(k)
	if err != nil {
		panic(fmt.Sprintf("error reading %s: %s", k, err))
	}

	if ok && result == nil {
		result = schema.DefaultOrZero()
	}

	return result, ok
}

// GetOkErr is the most conservative of all the Get methods. It returns
// whether key is set or not, but also an error value. The error value is
// non-nil if the field doesn't exist or there was an error parsing the
// field value.
func (d *FieldData) GetOkErr(k string) (interface{}, bool, error) {
	schema, ok := d.Schema[k]
	if !ok {
		return nil, false, fmt.Errorf("unknown field: %s", k)
	}

	switch schema.Type {
	case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
		return d.getPrimitive(k, schema)
	default:
		return nil, false,
			fmt.Errorf("unknown field type %s for field %s", schema.Type, k)
	}
}

func (d *FieldData) getPrimitive(
	k string, schema *FieldSchema) (interface{}, bool, error) {
	raw, ok := d.Raw[k]
	if !ok {
		return nil, false, nil
	}

	switch schema.Type {
	case TypeBool:
		var result bool
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeInt:
		var result int
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeString:
		var result string
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeMap:
		var result map[string]interface{}
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeDurationSecond:
		var result int
		switch inp := raw.(type) {
		case nil:
			return nil, false, nil
		case int:
			result = inp
		case float32:
			result = int(inp)
		case float64:
			result = int(inp)
		case string:
			// Look for a suffix otherwise its a plain second value
			if strings.HasSuffix(inp, "s") || strings.HasSuffix(inp, "m") || strings.HasSuffix(inp, "h") {
				dur, err := time.ParseDuration(inp)
				if err != nil {
					return nil, true, err
				}
				result = int(dur.Seconds())
			} else {
				// Plain integer
				val, err := strconv.ParseInt(inp, 10, 64)
				if err != nil {
					return nil, true, err
				}
				result = int(val)
			}

		default:
			return nil, false, fmt.Errorf("invalid input '%v'", raw)
		}
		return result, true, nil

	default:
		panic(fmt.Sprintf("Unknown type: %s", schema.Type))
	}
}
<commit_msg>Added GetDefaultOrZero method to FieldData<commit_after>package framework

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/mitchellh/mapstructure"
)

// FieldData is the structure passed to the callback to handle a path
// containing the populated parameters for fields. This should be used
// instead of the raw (*vault.Request).Data to access data in a type-safe
// way.
type FieldData struct {
	Raw    map[string]interface{}
	Schema map[string]*FieldSchema
}

// Cycle through raw data and validate conversions in
// the schema, so we don't get an error/panic later when
// trying to get data out.  Data not in the schema is not
// an error at this point, so we don't worry about it.
func (d *FieldData) Validate() error {
	for field, value := range d.Raw {

		schema, ok := d.Schema[field]
		if !ok {
			continue
		}

		switch schema.Type {
		case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
			_, _, err := d.getPrimitive(field, schema)
			if err != nil {
				return fmt.Errorf("Error converting input %v for field %s: %s", value, field, err)
			}
		default:
			return fmt.Errorf("unknown field type %s for field %s",
				schema.Type, field)
		}
	}

	return nil
}

// Get gets the value for the given field. If the key is an invalid field,
// FieldData will panic. If you want a safer version of this method, use
// GetOk. If the field k is not set, the default value (if set) will be
// returned, otherwise the zero value will be returned.
func (d *FieldData) Get(k string) interface{} {
	schema, ok := d.Schema[k]
	if !ok {
		panic(fmt.Sprintf("field %s not in the schema", k))
	}

	value, ok := d.GetOk(k)
	if !ok {
		value = schema.DefaultOrZero()
	}

	return value
}

// GetDefaultOrZero gets the default value set on the schema for the given
// field. If there is no default value set, the zero value of the type
// will be returned.
func (d *FieldData) GetDefaultOrZero(k string) interface{} {
	schema, ok := d.Schema[k]
	if !ok {
		panic(fmt.Sprintf("field %s not in the schema", k))
	}

	return schema.DefaultOrZero()
}

// GetOk gets the value for the given field. The second return value
// will be false if the key is invalid or the key is not set at all.
func (d *FieldData) GetOk(k string) (interface{}, bool) {
	schema, ok := d.Schema[k]
	if !ok {
		return nil, false
	}

	result, ok, err := d.GetOkErr(k)
	if err != nil {
		panic(fmt.Sprintf("error reading %s: %s", k, err))
	}

	if ok && result == nil {
		result = schema.DefaultOrZero()
	}

	return result, ok
}

// GetOkErr is the most conservative of all the Get methods. It returns
// whether key is set or not, but also an error value. The error value is
// non-nil if the field doesn't exist or there was an error parsing the
// field value.
func (d *FieldData) GetOkErr(k string) (interface{}, bool, error) {
	schema, ok := d.Schema[k]
	if !ok {
		return nil, false, fmt.Errorf("unknown field: %s", k)
	}

	switch schema.Type {
	case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
		return d.getPrimitive(k, schema)
	default:
		return nil, false,
			fmt.Errorf("unknown field type %s for field %s", schema.Type, k)
	}
}

func (d *FieldData) getPrimitive(
	k string, schema *FieldSchema) (interface{}, bool, error) {
	raw, ok := d.Raw[k]
	if !ok {
		return nil, false, nil
	}

	switch schema.Type {
	case TypeBool:
		var result bool
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeInt:
		var result int
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeString:
		var result string
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeMap:
		var result map[string]interface{}
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, true, err
		}
		return result, true, nil

	case TypeDurationSecond:
		var result int
		switch inp := raw.(type) {
		case nil:
			return nil, false, nil
		case int:
			result = inp
		case float32:
			result = int(inp)
		case float64:
			result = int(inp)
		case string:
			// Look for a suffix otherwise its a plain second value
			if strings.HasSuffix(inp, "s") || strings.HasSuffix(inp, "m") || strings.HasSuffix(inp, "h") {
				dur, err := time.ParseDuration(inp)
				if err != nil {
					return nil, true, err
				}
				result = int(dur.Seconds())
			} else {
				// Plain integer
				val, err := strconv.ParseInt(inp, 10, 64)
				if err != nil {
					return nil, true, err
				}
				result = int(val)
			}

		default:
			return nil, false, fmt.Errorf("invalid input '%v'", raw)
		}
		return result, true, nil

	default:
		panic(fmt.Sprintf("Unknown type: %s", schema.Type))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/url"
	"os"
	"os/signal"
	"time"

	"github.com/serbe/pool"
)

func findProxy() {
	var addedProxy int64
	debugmsg("Start find proxy")
	p := pool.New(numWorkers)
	p.SetTimeout(timeout)
	ml := getMapLink()
	mp := newMapProxy()
	list := getProxyListFromDB()
	mp.fillMapProxy(list)
	mp.loadProxyFromFile()

	debugmsg("load links", len(ml.values))
	debugmsg("load proxies", len(mp.values))
	debugmsg("start add to pool")
	p.SetTimeout(timeout)
	p.SetQuitTimeout(2000)
	for _, link := range ml.values {
		if useAddLink || useTestLink || link.Iterate && time.Since(link.UpdateAt) > time.Duration(1)*time.Hour {
			err := p.Add(link.Hostname, nil)
			if err == nil {
				addedProxy++
			}
			chkErr("findProxy p.Add", err)
		}
	}
	if addedProxy == 0 {
		debugmsg("not added tasks to pool")
		return
	}
	debugmsg("end add to pool, added", p.GetAddedTasks(), "links")
	debugmsg("start get from chan")
	for result := range p.ResultChan {
		if result.Error != nil {
			errmsg("result", result.Error)
			continue
		}
		ml.update(result.Hostname)
		links := ml.getNewLinksFromTask(result)
		num := mp.numOfNewProxyInTask(result)
		if num > 0 {
			debugmsg("find", num, "proxy in", result.Hostname)
			if link, ok := ml.get(result.Hostname); ok {
				link.Num = link.Num + num
				ml.set(link)
			}
		}
		if !useNoAddLinks {
			for _, l := range links {
				chkErr("findProxy add to pool", p.Add(l.Hostname, nil))
				debugmsg("add to pool", l.Hostname)
			}
		} else {
			debugmsg("find", len(links), "links in", result.Hostname)
		}
		addedProxy = addedProxy + num
	}
	if !useTestLink {
		debugmsg("save proxy")
		ml.saveAll()
	}
	debugmsg(addedProxy, "new proxy found")
	debugmsg("end findProxy")
}

func checkProxy() {
	debugmsg("start checkProxy")
	var (
		checked    int64
		totalProxy int64
		anonProxy  int64
		err        error
	)
	list := getProxyListFromDB()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listLen := len(list)
	debugmsg("load proxies", listLen)

breakCheckProxyLoop:
	for j := 0; j < listLen; {
		mp := newMapProxy()
		var r = 10000
		if j+10000 > listLen {
			r = listLen % 10000
		}
		for i := 0; i < r; i++ {
			mp.set(list[j])
			j++
		}
		p := pool.New(numWorkers)
		p.SetTimeout(timeout)
		targetURL := getTarget()
		myIP, err = getExternalIP()
		if err != nil {
			errmsg("checkProxy getExternalIP", err)
			return
		}
		debugmsg("start add to pool")
		for _, proxy := range mp.values {
			proxyURL, err := url.Parse(proxy.Hostname)
			chkErr("parse url", err)
			chkErr("add to pool", p.Add(targetURL, proxyURL))
		}
		debugmsg("end add to pool")
		p.EndWaitingTasks()
		if p.GetAddedTasks() == 0 {
			debugmsg("no task added to pool")
			return
		}
	checkProxyLoop:
		for {
			select {
			case task, ok := <-p.ResultChan:
				if !ok {
					debugmsg("break loop by close chan ResultChan")
					break checkProxyLoop
				}
				checked++
				proxy, isOk := mp.taskToProxy(task)
				if !isOk {
					continue
				}
				mp.set(proxy)
				if !useFUP {
					saveProxy(proxy)
				}
				if proxy.IsWork {
					if useFUP {
						saveProxy(proxy)
					}
					totalProxy++
					log.Printf("%d/%d/%d %-15v %-5v %-6v anon=%v\n",
						checked,
						listLen,
						totalProxy,
						task.Proxy.Hostname(),
						task.Proxy.Port(),
						task.Proxy.Scheme,
						proxy.IsAnon,
					)
					if proxy.IsAnon {
						anonProxy++
					}
				}
			case <-c:
				debugmsg("break loop by pressing ctrl+c")
				break breakCheckProxyLoop
			}
		}
	}
	log.Printf("%d is good\n", totalProxy)
	log.Printf("%d is anon\n", anonProxy)
	debugmsg("end checkProxy")
}
<commit_msg>less save<commit_after>package main

import (
	"log"
	"net/url"
	"os"
	"os/signal"
	"time"

	"github.com/serbe/pool"
)

func findProxy() {
	var addedProxy int64
	debugmsg("Start find proxy")
	p := pool.New(numWorkers)
	p.SetTimeout(timeout)
	ml := getMapLink()
	mp := newMapProxy()
	list := getProxyListFromDB()
	mp.fillMapProxy(list)
	mp.loadProxyFromFile()

	debugmsg("load links", len(ml.values))
	debugmsg("load proxies", len(mp.values))
	debugmsg("start add to pool")
	p.SetTimeout(timeout)
	p.SetQuitTimeout(2000)
	for _, link := range ml.values {
		if useAddLink || useTestLink || link.Iterate && time.Since(link.UpdateAt) > time.Duration(1)*time.Hour {
			err := p.Add(link.Hostname, nil)
			if err == nil {
				addedProxy++
			}
			chkErr("findProxy p.Add", err)
		}
	}
	if addedProxy == 0 {
		debugmsg("not added tasks to pool")
		return
	}
	debugmsg("end add to pool, added", p.GetAddedTasks(), "links")
	debugmsg("start get from chan")
	for result := range p.ResultChan {
		if result.Error != nil {
			errmsg("result", result.Error)
			continue
		}
		ml.update(result.Hostname)
		links := ml.getNewLinksFromTask(result)
		num := mp.numOfNewProxyInTask(result)
		if num > 0 {
			debugmsg("find", num, "proxy in", result.Hostname)
			if link, ok := ml.get(result.Hostname); ok {
				link.Num = link.Num + num
				ml.set(link)
			}
		}
		if !useNoAddLinks {
			for _, l := range links {
				chkErr("findProxy add to pool", p.Add(l.Hostname, nil))
				debugmsg("add to pool", l.Hostname)
			}
		} else {
			debugmsg("find", len(links), "links in", result.Hostname)
		}
		addedProxy = addedProxy + num
	}
	if !useTestLink {
		debugmsg("save proxy")
		ml.saveAll()
	}
	debugmsg(addedProxy, "new proxy found")
	debugmsg("end findProxy")
}

func checkProxy() {
	debugmsg("start checkProxy")
	var (
		checked    int64
		totalProxy int64
		anonProxy  int64
		err        error
	)
	list := getProxyListFromDB()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listLen := len(list)
	debugmsg("load proxies", listLen)

breakCheckProxyLoop:
	for j := 0; j < listLen; {
		mp := newMapProxy()
		var r = 10000
		if j+10000 > listLen {
			r = listLen % 10000
		}
		for i := 0; i < r; i++ {
			mp.set(list[j])
			j++
		}
		p := pool.New(numWorkers)
		p.SetTimeout(timeout)
		targetURL := getTarget()
		myIP, err = getExternalIP()
		if err != nil {
			errmsg("checkProxy getExternalIP", err)
			return
		}
		debugmsg("start add to pool")
		for _, proxy := range mp.values {
			proxyURL, err := url.Parse(proxy.Hostname)
			chkErr("parse url", err)
			chkErr("add to pool", p.Add(targetURL, proxyURL))
		}
		debugmsg("end add to pool")
		p.EndWaitingTasks()
		if p.GetAddedTasks() == 0 {
			debugmsg("no task added to pool")
			return
		}
	checkProxyLoop:
		for {
			select {
			case task, ok := <-p.ResultChan:
				if !ok {
					debugmsg("break loop by close chan ResultChan")
					break checkProxyLoop
				}
				checked++
				proxy, isOk := mp.taskToProxy(task)
				if !isOk {
					continue
				}
				mp.set(proxy)
				if !(useFUP || useTestScheme) {
					saveProxy(proxy)
				}
				if proxy.IsWork {
					if useFUP || useTestScheme {
						saveProxy(proxy)
					}
					totalProxy++
					log.Printf("%d/%d/%d %-15v %-5v %-6v anon=%v\n",
						checked,
						listLen,
						totalProxy,
						task.Proxy.Hostname(),
						task.Proxy.Port(),
						task.Proxy.Scheme,
						proxy.IsAnon,
					)
					if proxy.IsAnon {
						anonProxy++
					}
				}
			case <-c:
				debugmsg("break loop by pressing ctrl+c")
				break breakCheckProxyLoop
			}
		}
	}
	log.Printf("%d is good\n", totalProxy)
	log.Printf("%d is anon\n", anonProxy)
	debugmsg("end checkProxy")
}
<|endoftext|>


<commit_before>package candyjs

import "C"
import (
	"errors"
	"reflect"
)

// ErrUndefinedProperty is throw when a property for a given proxied object on
// javascript cannot be found, basically a valid method or field cannot found.
var ErrUndefinedProperty = errors.New("undefined property")

var p = &proxy{}

type proxy struct{}

func (p *proxy) has(t interface{}, k string) bool {
	_, err := p.getProperty(t, k)
	return err != ErrUndefinedProperty
}

func (p *proxy) get(t interface{}, k string, recv interface{}) (interface{}, error) {
	f, err := p.getProperty(t, k)
	if err != nil {
		return nil, err
	}

	return f.Interface(), nil
}

func (p *proxy) set(t interface{}, k string, v, recv interface{}) (bool, error) {
	f, err := p.getProperty(t, k)
	if err != nil {
		return false, err
	}

	if !f.CanSet() {
		return false, nil
	}

	value := reflect.Zero(f.Type())
	if v != nil {
		value = reflect.ValueOf(castNumberToGoType(f.Kind(), v))
	}

	f.Set(value)
	return true, nil
}

func (p *proxy) getProperty(t interface{}, key string) (reflect.Value, error) {
	v := reflect.ValueOf(t)
	r, found := p.getValueFromKind(key, v)
	if !found {
		return r, ErrUndefinedProperty
	}

	return r, nil
}

func (p *proxy) getValueFromKind(key string, v reflect.Value) (reflect.Value, bool) {
	var value reflect.Value
	var found bool
	switch v.Kind() {
	case reflect.Ptr:
		value, found = p.getValueFromKindPtr(key, v)
	case reflect.Struct:
		value, found = p.getValueFromKindStruct(key, v)
	case reflect.Map:
		value, found = p.getValueFromKindMap(key, v)
	}

	if !found {
		return p.getMethod(key, v)
	}

	return value, found
}

func (p *proxy) getValueFromKindPtr(key string, v reflect.Value) (reflect.Value, bool) {
	r, found := p.getMethod(key, v)
	if !found {
		return p.getValueFromKind(key, v.Elem())
	}

	return r, found
}

func (p *proxy) getValueFromKindStruct(key string, v reflect.Value) (reflect.Value, bool) {
	var r reflect.Value
	for _, name := range nameToGo(key) {
		r = v.FieldByName(name)
		if r.IsValid() {
			break
		}
	}

	return r, r.IsValid()
}

func (p *proxy) getValueFromKindMap(key string, v reflect.Value) (reflect.Value, bool) {
	keyValue := reflect.ValueOf(key)
	r := v.MapIndex(keyValue)

	return r, r.IsValid()
}

func (p *proxy) getMethod(key string, v reflect.Value) (reflect.Value, bool) {
	var r reflect.Value
	for _, name := range nameToGo(key) {
		r = v.MethodByName(name)
		if r.IsValid() {
			break
		}
	}

	return r, r.IsValid()
}

func (p *proxy) enumerate(t interface{}) (interface{}, error) {
	return p.getPropertyNames(t)
}

func (p *proxy) getPropertyNames(t interface{}) ([]string, error) {
	v := reflect.ValueOf(t)

	var names []string
	var err error
	switch v.Kind() {
	case reflect.Ptr:
		names, err = p.getPropertyNames(v.Elem().Interface())
		if err != nil {
			return nil, err
		}
	case reflect.Struct:
		cFields := v.NumField()
		for i := 0; i < cFields; i++ {
			fieldName := v.Type().Field(i).Name
			if !isExported(fieldName) {
				continue
			}

			names = append(names, nameToJavaScript(fieldName))
		}
	}

	mCount := v.NumMethod()
	for i := 0; i < mCount; i++ {
		methodName := v.Type().Method(i).Name
		if !isExported(methodName) {
			continue
		}

		names = append(names, nameToJavaScript(methodName))
	}

	return names, nil
}

func castNumberToGoType(k reflect.Kind, v interface{}) interface{} {
	switch k {
	case reflect.Int:
		v = int(v.(float64))
	case reflect.Int8:
		v = int8(v.(float64))
	case reflect.Int16:
		v = int16(v.(float64))
	case reflect.Int32:
		v = int32(v.(float64))
	case reflect.Int64:
		v = int64(v.(float64))
	case reflect.Uint:
		v = uint(v.(float64))
	case reflect.Uint8:
		v = uint8(v.(float64))
	case reflect.Uint16:
		v = uint16(v.(float64))
	case reflect.Uint32:
		v = uint32(v.(float64))
	case reflect.Uint64:
		v = uint64(v.(float64))
	case reflect.Float32:
		v = float32(v.(float64))
	}

	return v
}
<commit_msg>Added JSON marshalling to property setters.<commit_after>package candyjs

import "C"
import (
	"encoding/json"
	"errors"
	"reflect"
)

// ErrUndefinedProperty is throw when a property for a given proxied object on
// javascript cannot be found, basically a valid method or field cannot found.
var ErrUndefinedProperty = errors.New("undefined property")

var p = &proxy{}

type proxy struct{}

func (p *proxy) has(t interface{}, k string) bool {
	_, err := p.getProperty(t, k)
	return err != ErrUndefinedProperty
}

func (p *proxy) get(t interface{}, k string, recv interface{}) (interface{}, error) {
	f, err := p.getProperty(t, k)
	if err != nil {
		return nil, err
	}

	return f.Interface(), nil
}

func (p *proxy) set(t interface{}, k string, v, recv interface{}) (bool, error) {
	f, err := p.getProperty(t, k)
	if err != nil {
		return false, err
	}

	if !f.CanSet() {
		return false, nil
	}

	value := reflect.Zero(f.Type())
	if v != nil {
		v, err = convert(f, v)
		if err != nil {
			return false, nil
		}
		value = reflect.ValueOf(v)
	}

	f.Set(value)
	return true, nil
}

func (p *proxy) getProperty(t interface{}, key string) (reflect.Value, error) {
	v := reflect.ValueOf(t)
	r, found := p.getValueFromKind(key, v)
	if !found {
		return r, ErrUndefinedProperty
	}

	return r, nil
}

func (p *proxy) getValueFromKind(key string, v reflect.Value) (reflect.Value, bool) {
	var value reflect.Value
	var found bool
	switch v.Kind() {
	case reflect.Ptr:
		value, found = p.getValueFromKindPtr(key, v)
	case reflect.Struct:
		value, found = p.getValueFromKindStruct(key, v)
	case reflect.Map:
		value, found = p.getValueFromKindMap(key, v)
	}

	if !found {
		return p.getMethod(key, v)
	}

	return value, found
}

func (p *proxy) getValueFromKindPtr(key string, v reflect.Value) (reflect.Value, bool) {
	r, found := p.getMethod(key, v)
	if !found {
		return p.getValueFromKind(key, v.Elem())
	}

	return r, found
}

func (p *proxy) getValueFromKindStruct(key string, v reflect.Value) (reflect.Value, bool) {
	var r reflect.Value
	for _, name := range nameToGo(key) {
		r = v.FieldByName(name)
		if r.IsValid() {
			break
		}
	}

	return r, r.IsValid()
}

func (p *proxy) getValueFromKindMap(key string, v reflect.Value) (reflect.Value, bool) {
	keyValue := reflect.ValueOf(key)
	r := v.MapIndex(keyValue)

	return r, r.IsValid()
}

func (p *proxy) getMethod(key string, v reflect.Value) (reflect.Value, bool) {
	var r reflect.Value
	for _, name := range nameToGo(key) {
		r = v.MethodByName(name)
		if r.IsValid() {
			break
		}
	}

	return r, r.IsValid()
}

func (p *proxy) enumerate(t interface{}) (interface{}, error) {
	return p.getPropertyNames(t)
}

func (p *proxy) getPropertyNames(t interface{}) ([]string, error) {
	v := reflect.ValueOf(t)

	var names []string
	var err error
	switch v.Kind() {
	case reflect.Ptr:
		names, err = p.getPropertyNames(v.Elem().Interface())
		if err != nil {
			return nil, err
		}
	case reflect.Struct:
		cFields := v.NumField()
		for i := 0; i < cFields; i++ {
			fieldName := v.Type().Field(i).Name
			if !isExported(fieldName) {
				continue
			}

			names = append(names, nameToJavaScript(fieldName))
		}
	}

	mCount := v.NumMethod()
	for i := 0; i < mCount; i++ {
		methodName := v.Type().Method(i).Name
		if !isExported(methodName) {
			continue
		}

		names = append(names, nameToJavaScript(methodName))
	}

	return names, nil
}

func convert(t reflect.Value, value interface{}) (interface{}, error) {

	s := reflect.ValueOf(value)
	if t.Type() == s.Type() {
		return value, nil // no conversion required
	}

	value, ok := castNumberToGoType(t.Kind(), value)
	if ok {
		return value, nil
	}

	return convertUsingJSON(t, value)
}

func convertUsingJSON(t reflect.Value, value interface{}) (interface{}, error) {

	tv := reflect.New(t.Type()).Interface()
	js, err := json.Marshal(value)
	if err == nil {
		err = json.Unmarshal([]byte(js), tv)
	}
	if err != nil {
		return nil, err
	}

	return reflect.ValueOf(tv).Elem().Interface(), nil
}

func castNumberToGoType(k reflect.Kind, v interface{}) (interface{}, bool) {
	switch k {
	case reflect.Int:
		v = int(v.(float64))
	case reflect.Int8:
		v = int8(v.(float64))
	case reflect.Int16:
		v = int16(v.(float64))
	case reflect.Int32:
		v = int32(v.(float64))
	case reflect.Int64:
		v = int64(v.(float64))
	case reflect.Uint:
		v = uint(v.(float64))
	case reflect.Uint8:
		v = uint8(v.(float64))
	case reflect.Uint16:
		v = uint16(v.(float64))
	case reflect.Uint32:
		v = uint32(v.(float64))
	case reflect.Uint64:
		v = uint64(v.(float64))
	case reflect.Float32:
		v = float32(v.(float64))
	default:
		return v, false
	}

	return v, true
}
<|endoftext|>


<commit_before><commit_msg>[perf] Chunk writing ParamSets.<commit_after><|endoftext|>


<commit_before>package actor

import (
	"errors"
	"log"
	"sync"
	"time"
)

var (
	ErrTimeout = errors.New("timeout")
)

func NewFuture(timeout time.Duration) *Future {
	fut := &Future{cond: sync.NewCond(&sync.Mutex{})}

	ref := &FutureActorRef{f: fut}
	id := ProcessRegistry.getAutoId()

	pid, ok := ProcessRegistry.add(ref, id)
	if !ok {
		log.Printf("[ACTOR] Failed to register future actorref '%v'", id)
		log.Println(id)
	}

	fut.pid = pid
	fut.t = time.AfterFunc(timeout, func() {
		fut.err = ErrTimeout
		ref.Stop(pid)
	})

	return fut
}

type Future struct {
	done   bool
	pid    *PID
	cond   *sync.Cond
	result interface{}
	err    error
	t      *time.Timer
}

// PID to the backing actor for the Future result
func (f *Future) PID() *PID {
	return f.pid
}

// PipeTo starts a go routine and waits for the `Future.Result()`, then sends the result to the given `PID`
func (f *Future) PipeTo(pid *PID) {
	go func() {
		res, err := f.Result()
		if err != nil {
			pid.Tell(err)
		} else {
			pid.Tell(res)
		}
	}()
}

func (f *Future) wait() {
	f.cond.L.Lock()
	if !f.done {
		f.cond.Wait()
	}
	f.cond.L.Unlock()
}

func (f *Future) Result() (interface{}, error) {
	f.wait()
	return f.result, f.err
}

func (f *Future) Wait() error {
	f.wait()
	return f.err
}

// FutureActorRef is a struct carrying a response PID and a channel where the response is placed
type FutureActorRef struct {
	f *Future
}

func (ref *FutureActorRef) SendUserMessage(pid *PID, message interface{}, sender *PID) {
	ref.f.result = message
	ref.Stop(pid)
}

func (ref *FutureActorRef) SendSystemMessage(pid *PID, message SystemMessage) {
	ref.f.result = message
	ref.Stop(pid)
}

func (ref *FutureActorRef) Stop(pid *PID) {
	ref.f.cond.L.Lock()
	if ref.f.done {
		ref.f.cond.L.Unlock()
		return
	}

	ref.f.done = true
	ref.f.t.Stop()
	ProcessRegistry.remove(pid)

	ref.f.cond.L.Unlock()
	ref.f.cond.Signal()
}

func (ref *FutureActorRef) Watch(pid *PID)   {}
func (ref *FutureActorRef) UnWatch(pid *PID) {}
<commit_msg>update comment<commit_after>package actor

import (
	"errors"
	"log"
	"sync"
	"time"
)

var (
	ErrTimeout = errors.New("timeout")
)

func NewFuture(timeout time.Duration) *Future {
	fut := &Future{cond: sync.NewCond(&sync.Mutex{})}

	ref := &FutureActorRef{f: fut}
	id := ProcessRegistry.getAutoId()

	pid, ok := ProcessRegistry.add(ref, id)
	if !ok {
		log.Printf("[ACTOR] Failed to register future actorref '%v'", id)
		log.Println(id)
	}

	fut.pid = pid
	fut.t = time.AfterFunc(timeout, func() {
		fut.err = ErrTimeout
		ref.Stop(pid)
	})

	return fut
}

type Future struct {
	pid    *PID
	cond   *sync.Cond
	// protected by cond
	done   bool
	result interface{}
	err    error
	t      *time.Timer
}

// PID to the backing actor for the Future result
func (f *Future) PID() *PID {
	return f.pid
}

// PipeTo starts a go routine and waits for the `Future.Result()`, then sends the result to the given `PID`
func (f *Future) PipeTo(pid *PID) {
	go func() {
		res, err := f.Result()
		if err != nil {
			pid.Tell(err)
		} else {
			pid.Tell(res)
		}
	}()
}

func (f *Future) wait() {
	f.cond.L.Lock()
	if !f.done {
		f.cond.Wait()
	}
	f.cond.L.Unlock()
}

func (f *Future) Result() (interface{}, error) {
	f.wait()
	return f.result, f.err
}

func (f *Future) Wait() error {
	f.wait()
	return f.err
}

// FutureActorRef is a struct carrying a response PID and a channel where the response is placed
type FutureActorRef struct {
	f *Future
}

func (ref *FutureActorRef) SendUserMessage(pid *PID, message interface{}, sender *PID) {
	ref.f.result = message
	ref.Stop(pid)
}

func (ref *FutureActorRef) SendSystemMessage(pid *PID, message SystemMessage) {
	ref.f.result = message
	ref.Stop(pid)
}

func (ref *FutureActorRef) Stop(pid *PID) {
	ref.f.cond.L.Lock()
	if ref.f.done {
		ref.f.cond.L.Unlock()
		return
	}

	ref.f.done = true
	ref.f.t.Stop()
	ProcessRegistry.remove(pid)

	ref.f.cond.L.Unlock()
	ref.f.cond.Signal()
}

func (ref *FutureActorRef) Watch(pid *PID)   {}
func (ref *FutureActorRef) UnWatch(pid *PID) {}
<|endoftext|>


<commit_before>package bxmpp

import (
	"crypto/tls"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/jpillora/backoff"
	"github.com/matterbridge/go-xmpp"
	"github.com/rs/xid"
)

type Bxmpp struct {
	*bridge.Config

	startTime time.Time
	xc        *xmpp.Client
	xmppMap   map[string]string
	connected bool
	sync.RWMutex

	avatarMap map[string]string
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bxmpp{
		Config:    cfg,
		xmppMap:   make(map[string]string),
		avatarMap: make(map[string]string),
	}
}

func (b *Bxmpp) Connect() error {
	b.Log.Infof("Connecting %s", b.GetString("Server"))
	if err := b.createXMPP(); err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}

	b.Log.Info("Connection succeeded")
	go b.manageConnection()
	return nil
}

func (b *Bxmpp) Disconnect() error {
	return nil
}

func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
	if channel.Options.Key != "" {
		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
		b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil)
	} else {
		b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
	}
	return nil
}

func (b *Bxmpp) Send(msg config.Message) (string, error) {
	// should be fixed by using a cache instead of dropping
	if !b.Connected() {
		return "", fmt.Errorf("bridge %s not connected, dropping message %#v to bridge", b.Account, msg)
	}
	// ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}
	b.Log.Debugf("=> Receiving %#v", msg)

	if msg.Event == config.EventAvatarDownload {
		return b.cacheAvatar(&msg), nil
	}

	// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.Log.Debugf("=> Sending attachement message %#v", rmsg)
			if _, err := b.xc.Send(xmpp.Chat{
				Type:   "groupchat",
				Remote: rmsg.Channel + "@" + b.GetString("Muc"),
				Text:   rmsg.Username + rmsg.Text,
			}); err != nil {
				b.Log.WithError(err).Error("Unable to send message with share URL.")
			}
		}
		if len(msg.Extra["file"]) > 0 {
			return "", b.handleUploadFile(&msg)
		}
	}

	var msgReplaceID string
	msgID := xid.New().String()
	if msg.ID != "" {
		msgID = msg.ID
		msgReplaceID = msg.ID
	}
	// Post normal message.
	b.Log.Debugf("=> Sending message %#v", msg)
	if _, err := b.xc.Send(xmpp.Chat{
		Type:      "groupchat",
		Remote:    msg.Channel + "@" + b.GetString("Muc"),
		Text:      msg.Username + msg.Text,
		ID:        msgID,
		ReplaceID: msgReplaceID,
	}); err != nil {
		return "", err
	}
	return msgID, nil
}

func (b *Bxmpp) createXMPP() error {
	if !strings.Contains(b.GetString("Jid"), "@") {
		return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
	}
	tc := &tls.Config{
		ServerName:         strings.Split(b.GetString("Jid"), "@")[1],
		InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
	}

	xmpp.DebugWriter = b.Log.Writer()

	options := xmpp.Options{
		Host:                         b.GetString("Server"),
		User:                         b.GetString("Jid"),
		Password:                     b.GetString("Password"),
		NoTLS:                        true,
		StartTLS:                     true,
		TLSConfig:                    tc,
		Debug:                        b.GetBool("debug"),
		Session:                      true,
		Status:                       "",
		StatusMessage:                "",
		Resource:                     "",
		InsecureAllowUnencryptedAuth: false,
	}
	var err error
	b.xc, err = options.NewClient()
	return err
}

func (b *Bxmpp) manageConnection() {
	b.setConnected(true)
	initial := true
	bf := &backoff.Backoff{
		Min:    time.Second,
		Max:    5 * time.Minute,
		Jitter: true,
	}

	// Main connection loop. Each iteration corresponds to a successful
	// connection attempt and the subsequent handling of the connection.
	for {
		if initial {
			initial = false
		} else {
			b.Remote <- config.Message{
				Username: "system",
				Text:     "rejoin",
				Channel:  "",
				Account:  b.Account,
				Event:    config.EventRejoinChannels,
			}
		}

		if err := b.handleXMPP(); err != nil {
			b.Log.WithError(err).Error("Disconnected.")
			b.setConnected(false)
		}

		// Reconnection loop using an exponential back-off strategy. We
		// only break out of the loop if we have successfully reconnected.
		for {
			d := bf.Duration()
			b.Log.Infof("Reconnecting in %s.", d)
			time.Sleep(d)

			b.Log.Infof("Reconnecting now.")
			if err := b.createXMPP(); err == nil {
				b.setConnected(true)
				bf.Reset()
				break
			}
			b.Log.Warn("Failed to reconnect.")
		}
	}
}

func (b *Bxmpp) xmppKeepAlive() chan bool {
	done := make(chan bool)
	go func() {
		ticker := time.NewTicker(90 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				b.Log.Debugf("PING")
				if err := b.xc.PingC2S("", ""); err != nil {
					b.Log.Debugf("PING failed %#v", err)
				}
			case <-done:
				return
			}
		}
	}()
	return done
}

func (b *Bxmpp) handleXMPP() error {
	b.startTime = time.Now()

	done := b.xmppKeepAlive()
	defer close(done)

	for {
		m, err := b.xc.Recv()
		if err != nil {
			return err
		}

		switch v := m.(type) {
		case xmpp.Chat:
			if v.Type == "groupchat" {
				b.Log.Debugf("== Receiving %#v", v)

				// Skip invalid messages.
				if b.skipMessage(v) {
					continue
				}

				var event string
				if strings.Contains(v.Text, "has set the subject to:") {
					event = config.EventTopicChange
				}

				avatar := getAvatar(b.avatarMap, v.Remote, b.General)
				if avatar == "" {
					b.Log.Debugf("Requesting avatar data")
					b.xc.AvatarRequestData(v.Remote)
				}

				msgID := v.ID
				if v.ReplaceID != "" {
					msgID = v.ReplaceID
				}
				rmsg := config.Message{
					Username: b.parseNick(v.Remote),
					Text:     v.Text,
					Channel:  b.parseChannel(v.Remote),
					Account:  b.Account,
					Avatar:   avatar,
					UserID:   v.Remote,
					ID:       msgID,
					Event:    event,
				}

				// Check if we have an action event.
				var ok bool
				rmsg.Text, ok = b.replaceAction(rmsg.Text)
				if ok {
					rmsg.Event = config.EventUserAction
				}

				b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
				b.Log.Debugf("<= Message is %#v", rmsg)
				b.Remote <- rmsg
			}
		case xmpp.AvatarData:
			b.handleDownloadAvatar(v)
		case xmpp.Presence:
			// Do nothing.
		}
	}
}

func (b *Bxmpp) replaceAction(text string) (string, bool) {
	if strings.HasPrefix(text, "/me ") {
		return strings.Replace(text, "/me ", "", -1), true
	}
	return text, false
}

// handleUploadFile handles native upload of files
func (b *Bxmpp) handleUploadFile(msg *config.Message) error {
	var urlDesc string

	for _, file := range msg.Extra["file"] {
		fileInfo := file.(config.FileInfo)
		if fileInfo.Comment != "" {
			msg.Text += fileInfo.Comment + ": "
		}
		if fileInfo.URL != "" {
			msg.Text = fileInfo.URL
			if fileInfo.Comment != "" {
				msg.Text = fileInfo.Comment + ": " + fileInfo.URL
				urlDesc = fileInfo.Comment
			}
		}
		if _, err := b.xc.Send(xmpp.Chat{
			Type:   "groupchat",
			Remote: msg.Channel + "@" + b.GetString("Muc"),
			Text:   msg.Username + msg.Text,
		}); err != nil {
			return err
		}

		if fileInfo.URL != "" {
			if _, err := b.xc.SendOOB(xmpp.Chat{
				Type:    "groupchat",
				Remote:  msg.Channel + "@" + b.GetString("Muc"),
				Ooburl:  fileInfo.URL,
				Oobdesc: urlDesc,
			}); err != nil {
				b.Log.WithError(err).Warn("Failed to send share URL.")
			}
		}
	}
	return nil
}

func (b *Bxmpp) parseNick(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) > 0 {
		s = strings.Split(s[1], "/")
		if len(s) == 2 {
			return s[1] // nick
		}
	}
	return ""
}

func (b *Bxmpp) parseChannel(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) >= 2 {
		return s[0] // channel
	}
	return ""
}

// skipMessage skips messages that need to be skipped
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
	// skip messages from ourselves
	if b.parseNick(message.Remote) == b.GetString("Nick") {
		return true
	}

	// skip empty messages
	if message.Text == "" {
		return true
	}

	// skip subject messages
	if strings.Contains(message.Text, "</subject>") {
		return true
	}

	// do not show subjects on connect #732
	if strings.Contains(message.Text, "has set the subject to:") && time.Since(b.startTime) < time.Second*5 {
		return true
	}

	// skip delayed messages
	return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
}

func (b *Bxmpp) setConnected(state bool) {
	b.Lock()
	b.connected = state
	defer b.Unlock()
}

func (b *Bxmpp) Connected() bool {
	b.RLock()
	defer b.RUnlock()
	return b.connected
}
<commit_msg>Implement xep-0245 (xmpp). Closes #1137 (#1144)<commit_after>package bxmpp

import (
	"crypto/tls"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/jpillora/backoff"
	"github.com/matterbridge/go-xmpp"
	"github.com/rs/xid"
)

type Bxmpp struct {
	*bridge.Config

	startTime time.Time
	xc        *xmpp.Client
	xmppMap   map[string]string
	connected bool
	sync.RWMutex

	avatarMap map[string]string
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bxmpp{
		Config:    cfg,
		xmppMap:   make(map[string]string),
		avatarMap: make(map[string]string),
	}
}

func (b *Bxmpp) Connect() error {
	b.Log.Infof("Connecting %s", b.GetString("Server"))
	if err := b.createXMPP(); err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}

	b.Log.Info("Connection succeeded")
	go b.manageConnection()
	return nil
}

func (b *Bxmpp) Disconnect() error {
	return nil
}

func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
	if channel.Options.Key != "" {
		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
		b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil)
	} else {
		b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
	}
	return nil
}

func (b *Bxmpp) Send(msg config.Message) (string, error) {
	// should be fixed by using a cache instead of dropping
	if !b.Connected() {
		return "", fmt.Errorf("bridge %s not connected, dropping message %#v to bridge", b.Account, msg)
	}
	// ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}

	b.Log.Debugf("=> Receiving %#v", msg)

	if msg.Event == config.EventAvatarDownload {
		return b.cacheAvatar(&msg), nil
	}

	// Make a action /me of the message, prepend the username with it.
	// https://xmpp.org/extensions/xep-0245.html
	if msg.Event == config.EventUserAction {
		msg.Username = "/me " + msg.Username
	}

	// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.Log.Debugf("=> Sending attachement message %#v", rmsg)
			if _, err := b.xc.Send(xmpp.Chat{
				Type:   "groupchat",
				Remote: rmsg.Channel + "@" + b.GetString("Muc"),
				Text:   rmsg.Username + rmsg.Text,
			}); err != nil {
				b.Log.WithError(err).Error("Unable to send message with share URL.")
			}
		}
		if len(msg.Extra["file"]) > 0 {
			return "", b.handleUploadFile(&msg)
		}
	}

	var msgReplaceID string
	msgID := xid.New().String()
	if msg.ID != "" {
		msgID = msg.ID
		msgReplaceID = msg.ID
	}
	// Post normal message.
	b.Log.Debugf("=> Sending message %#v", msg)
	if _, err := b.xc.Send(xmpp.Chat{
		Type:      "groupchat",
		Remote:    msg.Channel + "@" + b.GetString("Muc"),
		Text:      msg.Username + msg.Text,
		ID:        msgID,
		ReplaceID: msgReplaceID,
	}); err != nil {
		return "", err
	}
	return msgID, nil
}

func (b *Bxmpp) createXMPP() error {
	if !strings.Contains(b.GetString("Jid"), "@") {
		return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
	}
	tc := &tls.Config{
		ServerName:         strings.Split(b.GetString("Jid"), "@")[1],
		InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
	}

	xmpp.DebugWriter = b.Log.Writer()

	options := xmpp.Options{
		Host:                         b.GetString("Server"),
		User:                         b.GetString("Jid"),
		Password:                     b.GetString("Password"),
		NoTLS:                        true,
		StartTLS:                     true,
		TLSConfig:                    tc,
		Debug:                        b.GetBool("debug"),
		Session:                      true,
		Status:                       "",
		StatusMessage:                "",
		Resource:                     "",
		InsecureAllowUnencryptedAuth: false,
	}
	var err error
	b.xc, err = options.NewClient()
	return err
}

func (b *Bxmpp) manageConnection() {
	b.setConnected(true)
	initial := true
	bf := &backoff.Backoff{
		Min:    time.Second,
		Max:    5 * time.Minute,
		Jitter: true,
	}

	// Main connection loop. Each iteration corresponds to a successful
	// connection attempt and the subsequent handling of the connection.
	for {
		if initial {
			initial = false
		} else {
			b.Remote <- config.Message{
				Username: "system",
				Text:     "rejoin",
				Channel:  "",
				Account:  b.Account,
				Event:    config.EventRejoinChannels,
			}
		}

		if err := b.handleXMPP(); err != nil {
			b.Log.WithError(err).Error("Disconnected.")
			b.setConnected(false)
		}

		// Reconnection loop using an exponential back-off strategy. We
		// only break out of the loop if we have successfully reconnected.
		for {
			d := bf.Duration()
			b.Log.Infof("Reconnecting in %s.", d)
			time.Sleep(d)

			b.Log.Infof("Reconnecting now.")
			if err := b.createXMPP(); err == nil {
				b.setConnected(true)
				bf.Reset()
				break
			}
			b.Log.Warn("Failed to reconnect.")
		}
	}
}

func (b *Bxmpp) xmppKeepAlive() chan bool {
	done := make(chan bool)
	go func() {
		ticker := time.NewTicker(90 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				b.Log.Debugf("PING")
				if err := b.xc.PingC2S("", ""); err != nil {
					b.Log.Debugf("PING failed %#v", err)
				}
			case <-done:
				return
			}
		}
	}()
	return done
}

func (b *Bxmpp) handleXMPP() error {
	b.startTime = time.Now()

	done := b.xmppKeepAlive()
	defer close(done)

	for {
		m, err := b.xc.Recv()
		if err != nil {
			return err
		}

		switch v := m.(type) {
		case xmpp.Chat:
			if v.Type == "groupchat" {
				b.Log.Debugf("== Receiving %#v", v)

				// Skip invalid messages.
				if b.skipMessage(v) {
					continue
				}

				var event string
				if strings.Contains(v.Text, "has set the subject to:") {
					event = config.EventTopicChange
				}

				avatar := getAvatar(b.avatarMap, v.Remote, b.General)
				if avatar == "" {
					b.Log.Debugf("Requesting avatar data")
					b.xc.AvatarRequestData(v.Remote)
				}

				msgID := v.ID
				if v.ReplaceID != "" {
					msgID = v.ReplaceID
				}
				rmsg := config.Message{
					Username: b.parseNick(v.Remote),
					Text:     v.Text,
					Channel:  b.parseChannel(v.Remote),
					Account:  b.Account,
					Avatar:   avatar,
					UserID:   v.Remote,
					ID:       msgID,
					Event:    event,
				}

				// Check if we have an action event.
				var ok bool
				rmsg.Text, ok = b.replaceAction(rmsg.Text)
				if ok {
					rmsg.Event = config.EventUserAction
				}

				b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
				b.Log.Debugf("<= Message is %#v", rmsg)
				b.Remote <- rmsg
			}
		case xmpp.AvatarData:
			b.handleDownloadAvatar(v)
		case xmpp.Presence:
			// Do nothing.
		}
	}
}

func (b *Bxmpp) replaceAction(text string) (string, bool) {
	if strings.HasPrefix(text, "/me ") {
		return strings.Replace(text, "/me ", "", -1), true
	}
	return text, false
}

// handleUploadFile handles native upload of files
func (b *Bxmpp) handleUploadFile(msg *config.Message) error {
	var urlDesc string

	for _, file := range msg.Extra["file"] {
		fileInfo := file.(config.FileInfo)
		if fileInfo.Comment != "" {
			msg.Text += fileInfo.Comment + ": "
		}
		if fileInfo.URL != "" {
			msg.Text = fileInfo.URL
			if fileInfo.Comment != "" {
				msg.Text = fileInfo.Comment + ": " + fileInfo.URL
				urlDesc = fileInfo.Comment
			}
		}
		if _, err := b.xc.Send(xmpp.Chat{
			Type:   "groupchat",
			Remote: msg.Channel + "@" + b.GetString("Muc"),
			Text:   msg.Username + msg.Text,
		}); err != nil {
			return err
		}

		if fileInfo.URL != "" {
			if _, err := b.xc.SendOOB(xmpp.Chat{
				Type:    "groupchat",
				Remote:  msg.Channel + "@" + b.GetString("Muc"),
				Ooburl:  fileInfo.URL,
				Oobdesc: urlDesc,
			}); err != nil {
				b.Log.WithError(err).Warn("Failed to send share URL.")
			}
		}
	}
	return nil
}

func (b *Bxmpp) parseNick(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) > 0 {
		s = strings.Split(s[1], "/")
		if len(s) == 2 {
			return s[1] // nick
		}
	}
	return ""
}

func (b *Bxmpp) parseChannel(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) >= 2 {
		return s[0] // channel
	}
	return ""
}

// skipMessage skips messages that need to be skipped
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
	// skip messages from ourselves
	if b.parseNick(message.Remote) == b.GetString("Nick") {
		return true
	}

	// skip empty messages
	if message.Text == "" {
		return true
	}

	// skip subject messages
	if strings.Contains(message.Text, "</subject>") {
		return true
	}

	// do not show subjects on connect #732
	if strings.Contains(message.Text, "has set the subject to:") && time.Since(b.startTime) < time.Second*5 {
		return true
	}

	// skip delayed messages
	return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
}

func (b *Bxmpp) setConnected(state bool) {
	b.Lock()
	b.connected = state
	defer b.Unlock()
}

func (b *Bxmpp) Connected() bool {
	b.RLock()
	defer b.RUnlock()
	return b.connected
}
<|endoftext|>