<commit_before>package ssh2docker

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"sync"
	"syscall"

	"github.com/apex/log"
	"github.com/kr/pty"
	"github.com/moul/ssh2docker/pkg/envhelper"
	"github.com/moul/ssh2docker/pkg/ttyhelper"
	"golang.org/x/crypto/ssh"
)

var clientCounter = 0

// Client is one client connection
type Client struct {
	Idx        int
	ChannelIdx int
	Conn       *ssh.ServerConn
	Chans      <-chan ssh.NewChannel
	Reqs       <-chan *ssh.Request
	Server     *Server
	Pty, Tty   *os.File
	Config     *ClientConfig
	ClientID   string
}

type ClientConfig struct {
	ImageName              string                `json:"image-name,omitempty"`
	RemoteUser             string                `json:"remote-user,omitempty"`
	Env                    envhelper.Environment `json:"env,omitempty"`
	Command                []string              `json:"command,omitempty"`
	DockerRunArgs          []string              `json:"docker-run-args,omitempty"`
	User                   string                `json:"user,omitempty"`
	Keys                   []string              `json:"keys,omitempty"`
	AuthenticationMethod   string                `json:"authentication-method,omitempty"`
	AuthenticationComment  string                `json:"authentication-coment,omitempty"`
	EntryPoint             string                `json:"entrypoint,omitempty"`
	AuthenticationAttempts int                   `json:"authentication-attempts,omitempty"`
	Allowed                bool                  `json:"allowed,omitempty"`
	IsLocal                bool                  `json:"is_local,omitempty"`
}

// NewClient initializes a new client
func NewClient(conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, server *Server) *Client {
	client := Client{
		Idx:        clientCounter,
		ClientID:   conn.RemoteAddr().String(),
		ChannelIdx: 0,
		Conn:       conn,
		Chans:      chans,
		Reqs:       reqs,
		Server:     server,

		// Default ClientConfig, will be overwritten if a hook is used
		Config: &ClientConfig{
			ImageName:              strings.Replace(conn.User(), "_", "/", -1),
			RemoteUser:             "anonymous",
			AuthenticationMethod:   "noauth",
			AuthenticationComment:  "",
			AuthenticationAttempts: 0,
			Env:     envhelper.Environment{},
			Command: make([]string, 0),
		},
	}

	if server.LocalUser != "" {
		client.Config.IsLocal = client.Config.ImageName == server.LocalUser
	}

	if _, found := server.ClientConfigs[client.ClientID]; !found {
		server.ClientConfigs[client.ClientID] = client.Config
	}

	client.Config = server.ClientConfigs[conn.RemoteAddr().String()]
	client.Config.Env.ApplyDefaults()

	clientCounter++

	remoteAddr := strings.Split(client.ClientID, ":")
	log.Infof("Accepted %s for %s from %s port %s ssh2: %s", client.Config.AuthenticationMethod, conn.User(), remoteAddr[0], remoteAddr[1], client.Config.AuthenticationComment)
	return &client
}

// HandleRequests handles SSH requests
func (c *Client) HandleRequests() error {
	go func(in <-chan *ssh.Request) {
		for req := range in {
			log.Debugf("HandleRequest: %v", req)
			if req.WantReply {
				req.Reply(false, nil)
			}
		}
	}(c.Reqs)
	return nil
}

// HandleChannels handles SSH channels
func (c *Client) HandleChannels() error {
	for newChannel := range c.Chans {
		if err := c.HandleChannel(newChannel); err != nil {
			return err
		}
	}
	return nil
}

// HandleChannel handles one SSH channel
func (c *Client) HandleChannel(newChannel ssh.NewChannel) error {
	if newChannel.ChannelType() != "session" {
		log.Debugf("Unknown channel type: %s", newChannel.ChannelType())
		newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
		return nil
	}

	channel, requests, err := newChannel.Accept()
	if err != nil {
		log.Errorf("newChannel.Accept failed: %v", err)
		return err
	}
	c.ChannelIdx++
	log.Debugf("HandleChannel.channel (client=%d channel=%d): %v", c.Idx, c.ChannelIdx, channel)

	log.Debug("Creating pty...")
	f, tty, err := pty.Open()
	if err != nil {
		log.Errorf("pty.Open failed: %v", err)
		return nil
	}
	c.Tty = tty
	c.Pty = f

	c.HandleChannelRequests(channel, requests)

	return nil
}

func (c *Client) runCommand(channel ssh.Channel, entrypoint string, command []string) {
	var cmd *exec.Cmd
	var err error

	if c.Config.IsLocal {
		cmd = exec.Command(entrypoint, command...)
	} else {
		// checking if a container already exists for this user
		existingContainer := ""
		if !c.Server.NoJoin {
			cmd := exec.Command("docker", "ps", "--filter=label=ssh2docker", fmt.Sprintf("--filter=label=image=%s", c.Config.ImageName), fmt.Sprintf("--filter=label=user=%s", c.Config.RemoteUser), "--quiet", "--no-trunc")
			cmd.Env = c.Config.Env.List()
			buf, err := cmd.CombinedOutput()
			if err != nil {
				log.Warnf("docker ps ... failed: %v", err)
				return
			}
			existingContainer = strings.TrimSpace(string(buf))
		}

		// Opening Docker process
		if existingContainer != "" {
			// Attaching to an existing container
			args := []string{"exec", "-it", existingContainer}
			if entrypoint != "" {
				args = append(args, entrypoint)
			}
			args = append(args, command...)
			log.Debugf("Executing 'docker %s'", strings.Join(args, " "))
			cmd = exec.Command("docker", args...)
			cmd.Env = c.Config.Env.List()
		} else {
			// Creating and attaching to a new container
			args := []string{"run"}
			if len(c.Config.DockerRunArgs) > 0 {
				args = append(args, c.Config.DockerRunArgs...)
			} else {
				args = append(args, c.Server.DockerRunArgs...)
			}
			args = append(args, "--label=ssh2docker", fmt.Sprintf("--label=user=%s", c.Config.RemoteUser), fmt.Sprintf("--label=image=%s", c.Config.ImageName))
			if c.Config.User != "" {
				args = append(args, "-u", c.Config.User)
			}
			if entrypoint != "" {
				args = append(args, "--entrypoint", entrypoint)
			}
			args = append(args, c.Config.ImageName)
			args = append(args, command...)
			log.Debugf("Executing 'docker %s'", strings.Join(args, " "))
			cmd = exec.Command("docker", args...)
			cmd.Env = c.Config.Env.List()
		}
	}

	if c.Server.Banner != "" {
		banner := c.Server.Banner
		banner = strings.Replace(banner, "\r", "", -1)
		banner = strings.Replace(banner, "\n", "\n\r", -1)
		fmt.Fprintf(channel, "%s\n\r", banner)
	}

	cmd.Stdout = c.Tty
	cmd.Stdin = c.Tty
	cmd.Stderr = c.Tty
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setctty: true,
		Setsid:  true,
	}

	err = cmd.Start()
	if err != nil {
		log.Warnf("cmd.Start failed: %v", err)
		return
	}

	var once sync.Once
	close := func() {
		channel.Close()
		log.Infof("Received disconnect from %s: disconnected by user", c.ClientID)
	}

	go func() {
		io.Copy(channel, c.Pty)
		once.Do(close)
	}()

	go func() {
		io.Copy(c.Pty, channel)
		once.Do(close)
	}()

	go func() {
		if err := cmd.Wait(); err != nil {
			log.Warnf("cmd.Wait failed: %v", err)
		}
		once.Do(close)
	}()
}

// HandleChannelRequests handles channel requests
func (c *Client) HandleChannelRequests(channel ssh.Channel, requests <-chan *ssh.Request) {
	go func(in <-chan *ssh.Request) {
		defer c.Tty.Close()

		for req := range in {
			ok := false
			switch req.Type {
			case "shell":
				log.Debugf("HandleChannelRequests.req shell")
				if len(req.Payload) != 0 {
					break
				}
				ok = true

				entrypoint := ""
				if c.Config.EntryPoint != "" {
					entrypoint = c.Config.EntryPoint
				}

				var args []string
				if c.Config.Command != nil {
					args = c.Config.Command
				}

				if entrypoint == "" && len(args) == 0 {
					args = []string{c.Server.DefaultShell}
				}

				c.runCommand(channel, entrypoint, args)

			case "exec":
				command := string(req.Payload[4:])
				log.Debugf("HandleChannelRequests.req exec: %q", command)
				ok = true

				// FIXME: use a shell lexer to split the command
				args := strings.Split(command, " ")
				c.runCommand(channel, c.Config.EntryPoint, args)

			case "pty-req":
				ok = true
				termLen := req.Payload[3]
				c.Config.Env["TERM"] = string(req.Payload[4 : termLen+4])
				w, h := ttyhelper.ParseDims(req.Payload[termLen+4:])
				ttyhelper.SetWinsize(c.Pty.Fd(), w, h)
				log.Debugf("HandleChannelRequests.req pty-req: TERM=%q w=%q h=%q", c.Config.Env["TERM"], int(w), int(h))

			case "window-change":
				w, h := ttyhelper.ParseDims(req.Payload)
				ttyhelper.SetWinsize(c.Pty.Fd(), w, h)
				continue

			case "env":
				keyLen := req.Payload[3]
				key := string(req.Payload[4 : keyLen+4])
				valueLen := req.Payload[keyLen+7]
				value := string(req.Payload[keyLen+8 : keyLen+8+valueLen])
				log.Debugf("HandleChannelRequets.req 'env': %s=%q", key, value)
				c.Config.Env[key] = value

			default:
				log.Debugf("Unhandled request type: %q: %v", req.Type, req)
			}

			if req.WantReply {
				if !ok {
					log.Debugf("Declining %s request...", req.Type)
				}
				req.Reply(ok, nil)
			}
		}
	}(requests)
}
<commit_msg>Add dynamic tty<commit_after>package ssh2docker

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"sync"
	"syscall"

	"github.com/apex/log"
	"github.com/kr/pty"
	"github.com/moul/ssh2docker/pkg/envhelper"
	"github.com/moul/ssh2docker/pkg/ttyhelper"
	"golang.org/x/crypto/ssh"
)

var clientCounter = 0

// Client is one client connection
type Client struct {
	Idx        int
	ChannelIdx int
	Conn       *ssh.ServerConn
	Chans      <-chan ssh.NewChannel
	Reqs       <-chan *ssh.Request
	Server     *Server
	Pty, Tty   *os.File
	Config     *ClientConfig
	ClientID   string
}

type ClientConfig struct {
	ImageName              string                `json:"image-name,omitempty"`
	RemoteUser             string                `json:"remote-user,omitempty"`
	Env                    envhelper.Environment `json:"env,omitempty"`
	Command                []string              `json:"command,omitempty"`
	DockerRunArgs          []string              `json:"docker-run-args,omitempty"`
	User                   string                `json:"user,omitempty"`
	Keys                   []string              `json:"keys,omitempty"`
	AuthenticationMethod   string                `json:"authentication-method,omitempty"`
	AuthenticationComment  string                `json:"authentication-coment,omitempty"`
	EntryPoint             string                `json:"entrypoint,omitempty"`
	AuthenticationAttempts int                   `json:"authentication-attempts,omitempty"`
	Allowed                bool                  `json:"allowed,omitempty"`
	IsLocal                bool                  `json:"is-local,omitempty"`
	UseTTY                 bool                  `json:"use-tty,omitempty"`
}

// NewClient initializes a new client
func NewClient(conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, server *Server) *Client {
	client := Client{
		Idx:        clientCounter,
		ClientID:   conn.RemoteAddr().String(),
		ChannelIdx: 0,
		Conn:       conn,
		Chans:      chans,
		Reqs:       reqs,
		Server:     server,

		// Default ClientConfig, will be overwritten if a hook is used
		Config: &ClientConfig{
			ImageName:              strings.Replace(conn.User(), "_", "/", -1),
			RemoteUser:             "anonymous",
			AuthenticationMethod:   "noauth",
			AuthenticationComment:  "",
			AuthenticationAttempts: 0,
			Env:     envhelper.Environment{},
			Command: make([]string, 0),
		},
	}

	if server.LocalUser != "" {
		client.Config.IsLocal = client.Config.ImageName == server.LocalUser
	}

	if _, found := server.ClientConfigs[client.ClientID]; !found {
		server.ClientConfigs[client.ClientID] = client.Config
	}

	client.Config = server.ClientConfigs[conn.RemoteAddr().String()]
	client.Config.Env.ApplyDefaults()

	clientCounter++

	remoteAddr := strings.Split(client.ClientID, ":")
	log.Infof("Accepted %s for %s from %s port %s ssh2: %s", client.Config.AuthenticationMethod, conn.User(), remoteAddr[0], remoteAddr[1], client.Config.AuthenticationComment)
	return &client
}

// HandleRequests handles SSH requests
func (c *Client) HandleRequests() error {
	go func(in <-chan *ssh.Request) {
		for req := range in {
			log.Debugf("HandleRequest: %v", req)
			if req.WantReply {
				req.Reply(false, nil)
			}
		}
	}(c.Reqs)
	return nil
}

// HandleChannels handles SSH channels
func (c *Client) HandleChannels() error {
	for newChannel := range c.Chans {
		if err := c.HandleChannel(newChannel); err != nil {
			return err
		}
	}
	return nil
}

// HandleChannel handles one SSH channel
func (c *Client) HandleChannel(newChannel ssh.NewChannel) error {
	if newChannel.ChannelType() != "session" {
		log.Debugf("Unknown channel type: %s", newChannel.ChannelType())
		newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
		return nil
	}

	channel, requests, err := newChannel.Accept()
	if err != nil {
		log.Errorf("newChannel.Accept failed: %v", err)
		return err
	}
	c.ChannelIdx++
	log.Debugf("HandleChannel.channel (client=%d channel=%d)", c.Idx, c.ChannelIdx)

	log.Debug("Creating pty...")
	c.Pty, c.Tty, err = pty.Open()
	if err != nil {
		log.Errorf("pty.Open failed: %v", err)
		return nil
	}

	c.HandleChannelRequests(channel, requests)

	return nil
}

func (c *Client) runCommand(channel ssh.Channel, entrypoint string, command []string) {
	var cmd *exec.Cmd
	var err error

	defer channel.Close()
	if c.Config.IsLocal {
		cmd = exec.Command(entrypoint, command...)
	} else {
		// checking if a container already exists for this user
		existingContainer := ""
		if !c.Server.NoJoin {
			cmd = exec.Command("docker", "ps", "--filter=label=ssh2docker", fmt.Sprintf("--filter=label=image=%s", c.Config.ImageName), fmt.Sprintf("--filter=label=user=%s", c.Config.RemoteUser), "--quiet", "--no-trunc")
			cmd.Env = c.Config.Env.List()
			buf, err := cmd.CombinedOutput()
			if err != nil {
				log.Warnf("docker ps ... failed: %v", err)
				return
			}
			existingContainer = strings.TrimSpace(string(buf))
		}

		// Opening Docker process
		if existingContainer != "" {
			// Attaching to an existing container
			args := []string{"exec", "-i", existingContainer}
			if c.Config.UseTTY {
				args = []string{"exec", "-it", existingContainer}
			}
			if entrypoint != "" {
				args = append(args, entrypoint)
			}
			args = append(args, command...)
			log.Debugf("Executing 'docker %s'", strings.Join(args, " "))
			cmd = exec.Command("docker", args...)
			cmd.Env = c.Config.Env.List()
		} else {
			// Creating and attaching to a new container
			args := []string{"run"}
			if len(c.Config.DockerRunArgs) > 0 {
				args = append(args, c.Config.DockerRunArgs...)
			} else {
				args = append(args, c.Server.DockerRunArgs...)
			}
			args = append(args, "--label=ssh2docker", fmt.Sprintf("--label=user=%s", c.Config.RemoteUser), fmt.Sprintf("--label=image=%s", c.Config.ImageName))
			if c.Config.User != "" {
				args = append(args, "-u", c.Config.User)
			}
			if entrypoint != "" {
				args = append(args, "--entrypoint", entrypoint)
			}
			args = append(args, c.Config.ImageName)
			args = append(args, command...)
			log.Debugf("Executing 'docker %s'", strings.Join(args, " "))
			cmd = exec.Command("docker", args...)
			cmd.Env = c.Config.Env.List()
		}
	}

	if c.Server.Banner != "" {
		banner := c.Server.Banner
		banner = strings.Replace(banner, "\r", "", -1)
		banner = strings.Replace(banner, "\n", "\n\r", -1)
		fmt.Fprintf(channel, "%s\n\r", banner)
	}

	cmd.Stdout = channel
	cmd.Stdin = channel
	cmd.Stderr = channel
	var wg sync.WaitGroup

	if c.Config.UseTTY {
		cmd.Stdout = c.Tty
		cmd.Stdin = c.Tty
		cmd.Stderr = c.Tty

		wg.Add(1)
		go func() {
			io.Copy(channel, c.Pty)
			wg.Done()
		}()
		wg.Add(1)
		go func() {
			io.Copy(c.Pty, channel)
			wg.Done()
		}()
		defer wg.Wait()
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setctty: c.Config.UseTTY,
		Setsid:  true,
	}

	err = cmd.Start()
	if err != nil {
		log.Warnf("cmd.Start failed: %v", err)
		return
	}

	if err := cmd.Wait(); err != nil {
		log.Warnf("cmd.Wait failed: %v", err)
	}
	log.Debugf("cmd.Wait done")
}

// HandleChannelRequests handles channel requests
func (c *Client) HandleChannelRequests(channel ssh.Channel, requests <-chan *ssh.Request) {
	go func(in <-chan *ssh.Request) {
		defer c.Tty.Close()

		for req := range in {
			ok := false
			switch req.Type {
			case "shell":
				log.Debugf("HandleChannelRequests.req shell")
				if len(req.Payload) != 0 {
					break
				}
				ok = true

				entrypoint := ""
				if c.Config.EntryPoint != "" {
					entrypoint = c.Config.EntryPoint
				}

				var args []string
				if c.Config.Command != nil {
					args = c.Config.Command
				}

				if entrypoint == "" && len(args) == 0 {
					args = []string{c.Server.DefaultShell}
				}

				c.runCommand(channel, entrypoint, args)

			case "exec":
				command := string(req.Payload[4:])
				log.Debugf("HandleChannelRequests.req exec: %q", command)
				ok = true

				// FIXME: use a shell lexer to split the command
				args := strings.Split(command, " ")
				c.runCommand(channel, c.Config.EntryPoint, args)

			case "pty-req":
				ok = true
				c.Config.UseTTY = true
				termLen := req.Payload[3]
				c.Config.Env["TERM"] = string(req.Payload[4 : termLen+4])
				w, h := ttyhelper.ParseDims(req.Payload[termLen+4:])
				ttyhelper.SetWinsize(c.Pty.Fd(), w, h)
				log.Debugf("HandleChannelRequests.req pty-req: TERM=%q w=%q h=%q", c.Config.Env["TERM"], int(w), int(h))

			case "window-change":
				w, h := ttyhelper.ParseDims(req.Payload)
				ttyhelper.SetWinsize(c.Pty.Fd(), w, h)
				continue

			case "env":
				keyLen := req.Payload[3]
				key := string(req.Payload[4 : keyLen+4])
				valueLen := req.Payload[keyLen+7]
				value := string(req.Payload[keyLen+8 : keyLen+8+valueLen])
				log.Debugf("HandleChannelRequets.req 'env': %s=%q", key, value)
				c.Config.Env[key] = value

			default:
				log.Debugf("Unhandled request type: %q: %v", req.Type, req)
			}

			if req.WantReply {
				if !ok {
					log.Debugf("Declining %s request...", req.Type)
				}
				req.Reply(ok, nil)
			}
		}
	}(requests)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/subtle"
	"encoding/binary"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"

	"github.com/yawning/chacha20"
	"golang.org/x/crypto/ed25519"
)

// DefaultClientVersion - Default client version
const DefaultClientVersion = byte(4)

// Client - Client data
type Client struct {
	conf    Conf
	reader  *bufio.Reader
	writer  *bufio.Writer
	version byte
}

func (client *Client) copyOperation(h1 []byte) {
	conf, reader, writer := client.conf, client.reader, client.writer
	content, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		log.Fatal(err)
	}
	nonce := make([]byte, 24)
	if _, err = rand.Read(nonce); err != nil {
		log.Fatal(err)
	}
	cipher, err := chacha20.NewCipher(conf.EncryptSk, nonce)
	if err != nil {
		log.Fatal(err)
	}
	opcode := byte('S')
	ciphertextWithNonce := make([]byte, 24+len(content))
	copy(ciphertextWithNonce, nonce)
	ciphertext := ciphertextWithNonce[24:]
	cipher.XORKeyStream(ciphertext, content)
	signature := ed25519.Sign(conf.SignSk, ciphertextWithNonce)
	h2 := auth2store(conf, client.version, h1, opcode, conf.EncryptSkID, signature)
	writer.WriteByte(opcode)
	writer.Write(h2)
	ciphertextWithNonceLen := uint64(len(ciphertextWithNonce))
	binary.Write(writer, binary.LittleEndian, ciphertextWithNonceLen)
	writer.Write(conf.EncryptSkID)
	writer.Write(signature)
	writer.Write(ciphertextWithNonce)
	if err := writer.Flush(); err != nil {
		log.Fatal(err)
	}
	rbuf := make([]byte, 32)
	if _, err = io.ReadFull(reader, rbuf); err != nil {
		log.Fatal(err)
	}
	h3 := rbuf
	wh3 := auth3store(conf, client.version, h2)
	if subtle.ConstantTimeCompare(wh3, h3) != 1 {
		log.Fatal("Incorrect authentication code")
	}
	fmt.Println("Sent")
}

func (client *Client) pasteOperation(h1 []byte, isMove bool) {
	conf, reader, writer := client.conf, client.reader, client.writer
	opcode := byte('G')
	if isMove {
		opcode = byte('M')
	}
	h2 := auth2get(conf, client.version, h1, opcode)
	writer.WriteByte(opcode)
	writer.Write(h2)
	if err := writer.Flush(); err != nil {
		log.Fatal(err)
	}
	rbuf := make([]byte, 112)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		log.Fatal(err)
	}
	h3 := rbuf[0:32]
	ciphertextWithNonceLen := binary.LittleEndian.Uint64(rbuf[32:40])
	encryptSkID := rbuf[40:48]
	_ = encryptSkID
	signature := rbuf[48:112]
	wh3 := auth3get(conf, client.version, h2, encryptSkID, signature)
	if subtle.ConstantTimeCompare(wh3, h3) != 1 {
		log.Fatal("Incorrect authentication code")
	}
	if bytes.Equal(conf.EncryptSkID, encryptSkID) == false {
		wEncryptSkIDStr := binary.LittleEndian.Uint64(conf.EncryptSkID)
		encryptSkIDStr := binary.LittleEndian.Uint64(encryptSkID)
		log.Fatal(fmt.Sprintf("Configured key ID is %v but content was encrypted using key ID %v",
			wEncryptSkIDStr, encryptSkIDStr))
	}
	ciphertextWithNonce := make([]byte, ciphertextWithNonceLen)
	if _, err := io.ReadFull(reader, ciphertextWithNonce); err != nil {
		log.Fatal(err)
	}
	if ed25519.Verify(conf.SignPk, ciphertextWithNonce, signature) != true {
		log.Fatal("Signature doesn't verify")
	}
	nonce := ciphertextWithNonce[0:24]
	cipher, err := chacha20.NewCipher(conf.EncryptSk, nonce)
	if err != nil {
		log.Fatal(err)
	}
	ciphertext := ciphertextWithNonce[24:]
	cipher.XORKeyStream(ciphertext, ciphertext)
	content := ciphertext
	binary.Write(os.Stdout, binary.LittleEndian, content)
}

// RunClient - Process a client query
func RunClient(conf Conf, isCopy bool, isMove bool) {
	conn, err := net.Dial("tcp", conf.Connect)
	if err != nil {
		log.Fatal(fmt.Sprintf("Unable to connect to %v - Is a Piknik server running on that host?",
			conf.Connect))
	}
	defer conn.Close()
	reader, writer := bufio.NewReader(conn), bufio.NewWriter(conn)
	client := Client{
		conf:    conf,
		reader:  reader,
		writer:  writer,
		version: DefaultClientVersion,
	}
	r := make([]byte, 32)
	if _, err = rand.Read(r); err != nil {
		log.Fatal(err)
	}
	h0 := auth0(conf, client.version, r)
	writer.Write([]byte{client.version})
	writer.Write(r)
	writer.Write(h0)
	if err := writer.Flush(); err != nil {
		log.Fatal(err)
	}
	rbuf := make([]byte, 65)
	if nbread, err := io.ReadFull(reader, rbuf); err != nil {
		if nbread < 2 {
			log.Fatal("The server rejected the connection - Please retry later")
		} else {
			log.Fatal("The server doesn't support this protocol")
		}
	}
	if serverVersion := rbuf[0]; serverVersion != client.version {
		log.Fatal(fmt.Sprintf("Incompatible server version (client version: %v - server version: %v)",
			client.version, serverVersion))
	}
	r2 := rbuf[1:33]
	h1 := rbuf[33:65]
	wh1 := auth1(conf, client.version, h0, r2)
	if subtle.ConstantTimeCompare(wh1, h1) != 1 {
		log.Fatal("Incorrect authentication code")
	}
	if isCopy {
		client.copyOperation(h1)
	} else {
		client.pasteOperation(h1, isMove)
	}
}
<commit_msg>Print "Sent" on stderr<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/subtle"
	"encoding/binary"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"

	"github.com/yawning/chacha20"
	"golang.org/x/crypto/ed25519"
)

// DefaultClientVersion - Default client version
const DefaultClientVersion = byte(4)

// Client - Client data
type Client struct {
	conf    Conf
	reader  *bufio.Reader
	writer  *bufio.Writer
	version byte
}

func (client *Client) copyOperation(h1 []byte) {
	conf, reader, writer := client.conf, client.reader, client.writer
	content, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		log.Fatal(err)
	}
	nonce := make([]byte, 24)
	if _, err = rand.Read(nonce); err != nil {
		log.Fatal(err)
	}
	cipher, err := chacha20.NewCipher(conf.EncryptSk, nonce)
	if err != nil {
		log.Fatal(err)
	}
	opcode := byte('S')
	ciphertextWithNonce := make([]byte, 24+len(content))
	copy(ciphertextWithNonce, nonce)
	ciphertext := ciphertextWithNonce[24:]
	cipher.XORKeyStream(ciphertext, content)
	signature := ed25519.Sign(conf.SignSk, ciphertextWithNonce)
	h2 := auth2store(conf, client.version, h1, opcode, conf.EncryptSkID, signature)
	writer.WriteByte(opcode)
	writer.Write(h2)
	ciphertextWithNonceLen := uint64(len(ciphertextWithNonce))
	binary.Write(writer, binary.LittleEndian, ciphertextWithNonceLen)
	writer.Write(conf.EncryptSkID)
	writer.Write(signature)
	writer.Write(ciphertextWithNonce)
	if err := writer.Flush(); err != nil {
		log.Fatal(err)
	}
	rbuf := make([]byte, 32)
	if _, err = io.ReadFull(reader, rbuf); err != nil {
		log.Fatal(err)
	}
	h3 := rbuf
	wh3 := auth3store(conf, client.version, h2)
	if subtle.ConstantTimeCompare(wh3, h3) != 1 {
		log.Fatal("Incorrect authentication code")
	}
	os.Stderr.WriteString("Sent\n")
}

func (client *Client) pasteOperation(h1 []byte, isMove bool) {
	conf, reader, writer := client.conf, client.reader, client.writer
	opcode := byte('G')
	if isMove {
		opcode = byte('M')
	}
	h2 := auth2get(conf, client.version, h1, opcode)
	writer.WriteByte(opcode)
	writer.Write(h2)
	if err := writer.Flush(); err != nil {
		log.Fatal(err)
	}
	rbuf := make([]byte, 112)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		log.Fatal(err)
	}
	h3 := rbuf[0:32]
	ciphertextWithNonceLen := binary.LittleEndian.Uint64(rbuf[32:40])
	encryptSkID := rbuf[40:48]
	_ = encryptSkID
	signature := rbuf[48:112]
	wh3 := auth3get(conf, client.version, h2, encryptSkID, signature)
	if subtle.ConstantTimeCompare(wh3, h3) != 1 {
		log.Fatal("Incorrect authentication code")
	}
	if bytes.Equal(conf.EncryptSkID, encryptSkID) == false {
		wEncryptSkIDStr := binary.LittleEndian.Uint64(conf.EncryptSkID)
		encryptSkIDStr := binary.LittleEndian.Uint64(encryptSkID)
		log.Fatal(fmt.Sprintf("Configured key ID is %v but content was encrypted using key ID %v",
			wEncryptSkIDStr, encryptSkIDStr))
	}
	ciphertextWithNonce := make([]byte, ciphertextWithNonceLen)
	if _, err := io.ReadFull(reader, ciphertextWithNonce); err != nil {
		log.Fatal(err)
	}
	if ed25519.Verify(conf.SignPk, ciphertextWithNonce, signature) != true {
		log.Fatal("Signature doesn't verify")
	}
	nonce := ciphertextWithNonce[0:24]
	cipher, err := chacha20.NewCipher(conf.EncryptSk, nonce)
	if err != nil {
		log.Fatal(err)
	}
	ciphertext := ciphertextWithNonce[24:]
	cipher.XORKeyStream(ciphertext, ciphertext)
	content := ciphertext
	binary.Write(os.Stdout, binary.LittleEndian, content)
}

// RunClient - Process a client query
func RunClient(conf Conf, isCopy bool, isMove bool) {
	conn, err := net.Dial("tcp", conf.Connect)
	if err != nil {
		log.Fatal(fmt.Sprintf("Unable to connect to %v - Is a Piknik server running on that host?",
			conf.Connect))
	}
	defer conn.Close()
	reader, writer := bufio.NewReader(conn), bufio.NewWriter(conn)
	client := Client{
		conf:    conf,
		reader:  reader,
		writer:  writer,
		version: DefaultClientVersion,
	}
	r := make([]byte, 32)
	if _, err = rand.Read(r); err != nil {
		log.Fatal(err)
	}
	h0 := auth0(conf, client.version, r)
	writer.Write([]byte{client.version})
	writer.Write(r)
	writer.Write(h0)
	if err := writer.Flush(); err != nil {
		log.Fatal(err)
	}
	rbuf := make([]byte, 65)
	if nbread, err := io.ReadFull(reader, rbuf); err != nil {
		if nbread < 2 {
			log.Fatal("The server rejected the connection - Please retry later")
		} else {
			log.Fatal("The server doesn't support this protocol")
		}
	}
	if serverVersion := rbuf[0]; serverVersion != client.version {
		log.Fatal(fmt.Sprintf("Incompatible server version (client version: %v - server version: %v)",
			client.version, serverVersion))
	}
	r2 := rbuf[1:33]
	h1 := rbuf[33:65]
	wh1 := auth1(conf, client.version, h0, r2)
	if subtle.ConstantTimeCompare(wh1, h1) != 1 {
		log.Fatal("Incorrect authentication code")
	}
	if isCopy {
		client.copyOperation(h1)
	} else {
		client.pasteOperation(h1, isMove)
	}
}
<|endoftext|>


<commit_before>package wundergo

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	apiUrl = "https://a.wunderlist.com/api/v1"
)

var NewLogger = func() Logger {
	return NewPrintlnLogger()
}

var NewHTTPHelper = func(accessToken string, clientID string) HTTPHelper {
	return NewOauthClientHTTPHelper(accessToken, clientID)
}

var NewJSONHelper = func() JSONHelper {
	return NewDefaultJSONHelper()
}

type Client interface {
	User() (*User, error)
	UpdateUser(user User) (*User, error)
	Users() (*[]User, error)
	UsersForListID(listId uint) (*[]User, error)
	Lists() (*[]List, error)
	List(listID uint) (*List, error)
	ListTaskCount(listID uint) (*ListTaskCount, error)
	CreateList(listTitle string) (*List, error)
	UpdateList(list List) (*List, error)
	DeleteList(list List) error
	NotesForListID(listID uint) (*[]Note, error)
	NotesForTaskID(taskID uint) (*[]Note, error)
}

type OauthClient struct {
	httpHelper HTTPHelper
	logger     Logger
	jsonHelper JSONHelper
}

func NewOauthClient(accessToken string, clientID string) *OauthClient {
	return &OauthClient{
		httpHelper: NewHTTPHelper(accessToken, clientID),
		logger:     NewLogger(),
		jsonHelper: NewJSONHelper(),
	}
}

func (c OauthClient) User() (*User, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/user", apiUrl))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	u, err := c.jsonHelper.Unmarshal(b, &User{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return u.(*User), nil
}

func (c OauthClient) readResponseBody(resp *http.Response) ([]byte, error) {
	if resp.Body == nil {
		return nil, errors.New(fmt.Sprintf("Nil body on http response: %v", resp))
	}

	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return b, nil
}

func (c OauthClient) UpdateUser(user User) (*User, error) {
	body := []byte(fmt.Sprintf("revision=%d&name=%s", user.Revision, user.Name))
	resp, err := c.httpHelper.Put(fmt.Sprintf("%s/user", apiUrl), body)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	u, err := c.jsonHelper.Unmarshal(b, &User{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return u.(*User), nil
}

func (c OauthClient) Users() (*[]User, error) {
	return c.UsersForListID(0)
}

func (c OauthClient) UsersForListID(listId uint) (*[]User, error) {
	var resp *http.Response
	var err error

	if listId > 0 {
		resp, err = c.httpHelper.Get(fmt.Sprintf("%s/users?list_id=%d", apiUrl, listId))
	} else {
		resp, err = c.httpHelper.Get(fmt.Sprintf("%s/users", apiUrl))
	}

	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	u, err := c.jsonHelper.Unmarshal(b, &([]User{}))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return u.(*[]User), nil
}

func (c OauthClient) Lists() (*[]List, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/lists", apiUrl))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &([]List{}))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*[]List), nil
}

func (c OauthClient) List(listID uint) (*List, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/lists/%d", apiUrl, listID))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &List{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*List), nil
}

func (c OauthClient) ListTaskCount(listID uint) (*ListTaskCount, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/lists/tasks_count?list_id=%d", apiUrl, listID))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &ListTaskCount{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*ListTaskCount), nil
}

func (c OauthClient) CreateList(listTitle string) (*List, error) {
	body := []byte(fmt.Sprintf(`{"title":"%s"}`, listTitle))

	resp, err := c.httpHelper.Post(fmt.Sprintf("%s/lists", apiUrl), body)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusCreated {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusCreated))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &List{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*List), nil
}

func (c OauthClient) UpdateList(list List) (*List, error) {
	body, err := c.jsonHelper.Marshal(list)
	if err != nil {
		return nil, err
	}

	resp, err := c.httpHelper.Patch(fmt.Sprintf("%s/lists/%d", apiUrl, list.ID), body)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &List{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*List), nil
}

func (c OauthClient) DeleteList(list List) error {
	resp, err := c.httpHelper.Delete(fmt.Sprintf("%s/lists/%d?revision=%d", apiUrl, list.ID, list.Revision))

	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusNoContent {
		return errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusNoContent))
	}

	_, err = c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return err
	}
	return nil
}

func (c OauthClient) NotesForListID(listID uint) (*[]Note, error) {
	var resp *http.Response
	var err error

	if listID == 0 {
		return nil, errors.New("listID must be > 0")
	}

	resp, err = c.httpHelper.Get(fmt.Sprintf("%s/notes?list_id=%d", apiUrl, listID))

	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	n, err := c.jsonHelper.Unmarshal(b, &[]Note{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return n.(*[]Note), nil
}

func (c OauthClient) NotesForTaskID(taskID uint) (*[]Note, error) {
	var resp *http.Response
	var err error

	if taskID == 0 {
		return nil, errors.New("taskID must be > 0")
	}

	resp, err = c.httpHelper.Get(fmt.Sprintf("%s/notes?task_id=%d", apiUrl, taskID))

	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	n, err := c.jsonHelper.Unmarshal(b, &[]Note{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return n.(*[]Note), nil
}
<commit_msg>Refactor Id variables to be ID.<commit_after>package wundergo

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	apiUrl = "https://a.wunderlist.com/api/v1"
)

var NewLogger = func() Logger {
	return NewPrintlnLogger()
}

var NewHTTPHelper = func(accessToken string, clientID string) HTTPHelper {
	return NewOauthClientHTTPHelper(accessToken, clientID)
}

var NewJSONHelper = func() JSONHelper {
	return NewDefaultJSONHelper()
}

type Client interface {
	User() (*User, error)
	UpdateUser(user User) (*User, error)
	Users() (*[]User, error)
	UsersForListID(listID uint) (*[]User, error)
	Lists() (*[]List, error)
	List(listID uint) (*List, error)
	ListTaskCount(listID uint) (*ListTaskCount, error)
	CreateList(listTitle string) (*List, error)
	UpdateList(list List) (*List, error)
	DeleteList(list List) error
	NotesForListID(listID uint) (*[]Note, error)
	NotesForTaskID(taskID uint) (*[]Note, error)
}

type OauthClient struct {
	httpHelper HTTPHelper
	logger     Logger
	jsonHelper JSONHelper
}

func NewOauthClient(accessToken string, clientID string) *OauthClient {
	return &OauthClient{
		httpHelper: NewHTTPHelper(accessToken, clientID),
		logger:     NewLogger(),
		jsonHelper: NewJSONHelper(),
	}
}

func (c OauthClient) User() (*User, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/user", apiUrl))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	u, err := c.jsonHelper.Unmarshal(b, &User{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return u.(*User), nil
}

func (c OauthClient) readResponseBody(resp *http.Response) ([]byte, error) {
	if resp.Body == nil {
		return nil, errors.New(fmt.Sprintf("Nil body on http response: %v", resp))
	}

	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return b, nil
}

func (c OauthClient) UpdateUser(user User) (*User, error) {
	body := []byte(fmt.Sprintf("revision=%d&name=%s", user.Revision, user.Name))
	resp, err := c.httpHelper.Put(fmt.Sprintf("%s/user", apiUrl), body)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	u, err := c.jsonHelper.Unmarshal(b, &User{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return u.(*User), nil
}

func (c OauthClient) Users() (*[]User, error) {
	return c.UsersForListID(0)
}

func (c OauthClient) UsersForListID(listID uint) (*[]User, error) {
	var resp *http.Response
	var err error

	if listID > 0 {
		resp, err = c.httpHelper.Get(fmt.Sprintf("%s/users?list_id=%d", apiUrl, listID))
	} else {
		resp, err = c.httpHelper.Get(fmt.Sprintf("%s/users", apiUrl))
	}

	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	u, err := c.jsonHelper.Unmarshal(b, &([]User{}))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return u.(*[]User), nil
}

func (c OauthClient) Lists() (*[]List, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/lists", apiUrl))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &([]List{}))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*[]List), nil
}

func (c OauthClient) List(listID uint) (*List, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/lists/%d", apiUrl, listID))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &List{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*List), nil
}

func (c OauthClient) ListTaskCount(listID uint) (*ListTaskCount, error) {
	resp, err := c.httpHelper.Get(fmt.Sprintf("%s/lists/tasks_count?list_id=%d", apiUrl, listID))
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &ListTaskCount{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*ListTaskCount), nil
}

func (c OauthClient) CreateList(listTitle string) (*List, error) {
	body := []byte(fmt.Sprintf(`{"title":"%s"}`, listTitle))

	resp, err := c.httpHelper.Post(fmt.Sprintf("%s/lists", apiUrl), body)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusCreated {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusCreated))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &List{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*List), nil
}

func (c OauthClient) UpdateList(list List) (*List, error) {
	body, err := c.jsonHelper.Marshal(list)
	if err != nil {
		return nil, err
	}

	resp, err := c.httpHelper.Patch(fmt.Sprintf("%s/lists/%d", apiUrl, list.ID), body)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	l, err := c.jsonHelper.Unmarshal(b, &List{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return l.(*List), nil
}

func (c OauthClient) DeleteList(list List) error {
	resp, err := c.httpHelper.Delete(fmt.Sprintf("%s/lists/%d?revision=%d", apiUrl, list.ID, list.Revision))

	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusNoContent {
		return errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusNoContent))
	}

	_, err = c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return err
	}
	return nil
}

func (c OauthClient) NotesForListID(listID uint) (*[]Note, error) {
	var resp *http.Response
	var err error

	if listID == 0 {
		return nil, errors.New("listID must be > 0")
	}

	resp, err = c.httpHelper.Get(fmt.Sprintf("%s/notes?list_id=%d", apiUrl, listID))

	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	n, err := c.jsonHelper.Unmarshal(b, &[]Note{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return n.(*[]Note), nil
}

func (c OauthClient) NotesForTaskID(taskID uint) (*[]Note, error) {
	var resp *http.Response
	var err error

	if taskID == 0 {
		return nil, errors.New("taskID must be > 0")
	}

	resp, err = c.httpHelper.Get(fmt.Sprintf("%s/notes?task_id=%d", apiUrl, taskID))

	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(fmt.Sprintf("Unexpected %s - expected %s", resp.StatusCode, http.StatusOK))
	}

	b, err := c.readResponseBody(resp)
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}

	n, err := c.jsonHelper.Unmarshal(b, &[]Note{})
	if err != nil {
		c.logger.LogLine(fmt.Sprintf("response: %v", resp))
		return nil, err
	}
	return n.(*[]Note), nil
}
<|endoftext|>


<commit_before>package guber

import (
	"crypto/tls"
	"net/http"
)

// Client describes behavior of the root Kubernetes client object.
type Client interface {
	// Namespaces returns a NamespaceCollection.
	Namespaces() NamespaceCollection

	// Events returns a EventCollection.
	Events(namespace string) EventCollection

	// Secrets returns a SecretCollection.
	Secrets(namespace string) SecretCollection

	// Services returns a ServiceCollection.
	Services(namespace string) ServiceCollection

	// ReplicationControllers returns a ReplicationControllerCollection.
	ReplicationControllers(namespace string) ReplicationControllerCollection

	// Pods returns a PodCollection.
	Pods(namespace string) PodCollection

	// Nodes returns a NodeCollection.
	Nodes() NodeCollection
}

var (
	defaultAPIGroup   = "api"
	defaultAPIVersion = "v1"
)

type Entity interface {
}

// CollectionMeta holds info required by all Kubernetes Resources defined.
type CollectionMeta struct {
	DomainName string // empty unless something like ThirdPartyResource
	APIGroup   string // usually "api"
	APIVersion string // usually "v1"
	APIName    string // e.g. "replicationcontrollers"
	Kind       string // e.g. "ReplicationController"
}

// Collection defines an interface for collections of Kubernetes resources.
type Collection interface {
	Meta() *CollectionMeta
}

// RealClient implements Client.
type RealClient struct {
	Host     string
	Username string
	Password string
	http     *http.Client
}

// NewClient creates a new Client.
func NewClient(host string, user string, pass string, insecureHTTPS bool) Client {
	httpClient := new(http.Client)
	if insecureHTTPS {
		httpClient.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
	}
	return &RealClient{host, user, pass, httpClient}
}

// Get performs a GET request against a Client object.
func (c *RealClient) Get() *Request {
	return &Request{client: c, method: "GET"}
}

// Post performs a POST request against a Client object.
func (c *RealClient) Post() *Request {
	return &Request{client: c, method: "POST"}
}

// Patch performs a PATCH request against a Client object.
func (c *RealClient) Patch() *Request {
	return &Request{
		client: c,
		method: "PATCH",
		headers: map[string]string{
			"Content-Type": "application/merge-patch+json",
		},
	}
}

// Delete performs a DELETE request against a Client object.
func (c *RealClient) Delete() *Request {
	return &Request{client: c, method: "DELETE"}
}

// Namespaces returns a Namespaces object from a Client object.
func (c *RealClient) Namespaces() NamespaceCollection {
	return &Namespaces{c}
}

// Events returns a Events object from a Client object.
func (c *RealClient) Events(namespace string) EventCollection {
	return &Events{c, namespace}
}

// Secrets returns a Secrets object from a Client object.
func (c *RealClient) Secrets(namespace string) SecretCollection {
	return &Secrets{c, namespace}
}

// Services returns a Services object from a Client object.
func (c *RealClient) Services(namespace string) ServiceCollection {
	return &Services{c, namespace}
}

// ReplicationControllers returns a ReplicationControllers object from a Client object.
func (c *RealClient) ReplicationControllers(namespace string) ReplicationControllerCollection {
	return &ReplicationControllers{c, namespace}
}

// Pods returns a Pods object from a Client object.
func (c *RealClient) Pods(namespace string) PodCollection {
	return &Pods{c, namespace}
}

// Namespaces returns a Nodes object from a Client object.
func (c *RealClient) Nodes() NodeCollection {
	return &Nodes{c}
}
<commit_msg>set http client timeout<commit_after>package guber

import (
	"crypto/tls"
	"net/http"
	"time"
)

// Client describes behavior of the root Kubernetes client object.
type Client interface {
	// Namespaces returns a NamespaceCollection.
	Namespaces() NamespaceCollection

	// Events returns a EventCollection.
	Events(namespace string) EventCollection

	// Secrets returns a SecretCollection.
	Secrets(namespace string) SecretCollection

	// Services returns a ServiceCollection.
	Services(namespace string) ServiceCollection

	// ReplicationControllers returns a ReplicationControllerCollection.
	ReplicationControllers(namespace string) ReplicationControllerCollection

	// Pods returns a PodCollection.
	Pods(namespace string) PodCollection

	// Nodes returns a NodeCollection.
	Nodes() NodeCollection
}

var (
	defaultAPIGroup   = "api"
	defaultAPIVersion = "v1"
)

type Entity interface {
}

// CollectionMeta holds info required by all Kubernetes Resources defined.
type CollectionMeta struct {
	DomainName string // empty unless something like ThirdPartyResource
	APIGroup   string // usually "api"
	APIVersion string // usually "v1"
	APIName    string // e.g. "replicationcontrollers"
	Kind       string // e.g. "ReplicationController"
}

// Collection defines an interface for collections of Kubernetes resources.
type Collection interface {
	Meta() *CollectionMeta
}

// RealClient implements Client.
type RealClient struct {
	Host     string
	Username string
	Password string
	http     *http.Client
}

// NewClient creates a new Client.
func NewClient(host string, user string, pass string, insecureHTTPS bool) Client {
	httpClient := &http.Client{
		Timeout: 30 * time.Second,
	}
	if insecureHTTPS {
		httpClient.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
	}
	return &RealClient{host, user, pass, httpClient}
}

// Get performs a GET request against a Client object.
func (c *RealClient) Get() *Request {
	return &Request{client: c, method: "GET"}
}

// Post performs a POST request against a Client object.
func (c *RealClient) Post() *Request {
	return &Request{client: c, method: "POST"}
}

// Patch performs a PATCH request against a Client object.
func (c *RealClient) Patch() *Request {
	return &Request{
		client: c,
		method: "PATCH",
		headers: map[string]string{
			"Content-Type": "application/merge-patch+json",
		},
	}
}

// Delete performs a DELETE request against a Client object.
func (c *RealClient) Delete() *Request {
	return &Request{client: c, method: "DELETE"}
}

// Namespaces returns a Namespaces object from a Client object.
func (c *RealClient) Namespaces() NamespaceCollection {
	return &Namespaces{c}
}

// Events returns a Events object from a Client object.
func (c *RealClient) Events(namespace string) EventCollection {
	return &Events{c, namespace}
}

// Secrets returns a Secrets object from a Client object.
func (c *RealClient) Secrets(namespace string) SecretCollection {
	return &Secrets{c, namespace}
}

// Services returns a Services object from a Client object.
func (c *RealClient) Services(namespace string) ServiceCollection {
	return &Services{c, namespace}
}

// ReplicationControllers returns a ReplicationControllers object from a Client object.
func (c *RealClient) ReplicationControllers(namespace string) ReplicationControllerCollection {
	return &ReplicationControllers{c, namespace}
}

// Pods returns a Pods object from a Client object.
func (c *RealClient) Pods(namespace string) PodCollection {
	return &Pods{c, namespace}
}

// Namespaces returns a Nodes object from a Client object.
func (c *RealClient) Nodes() NodeCollection {
	return &Nodes{c}
}
<|endoftext|>


<commit_before>// +build freebsd netbsd openbsd dragonfly

package machineid

import (
	"bytes"
	"os"
)

const hostidPath = "/etc/hostid"

// machineID returns the uuid specified at `/etc/hostid`.
// If the returned value is empty, the uuid from a call to `kenv -q smbios.system.uuid` is returned.
// If there is an error an empty string is returned.
func machineID() (string, error) {
	id, err := readHostid()
	if err != nil {
		// try fallback
		id, err = readKenv()
	}
	if err != nil {
		return "", err
	}
	return id, nil
}

func readHostid() (string, error) {
	buf, err := readFile(hostidPath)
	if err != nil {
		return "", err
	}
	return trim(string(buf)), nil
}

func readKenv() (string, error) {
	buf := &bytes.Buffer{}
	err := run(buf, os.Stderr, "kenv", "-q", "smbios.system.uuid")
	if err != nil {
		return "", err
	}
	return trim(buf.String()), nil
}
<commit_msg>makes it work on solaris/smartOS/illumos<commit_after>// +build freebsd netbsd openbsd dragonfly solaris

package machineid

import (
	"bytes"
	"os"
)

const hostidPath = "/etc/hostid"

// machineID returns the uuid specified at `/etc/hostid`.
// If the returned value is empty, the uuid from a call to `kenv -q smbios.system.uuid` is returned.
// If there is an error an empty string is returned.
func machineID() (string, error) {
	id, err := readHostid()
	if err != nil {
		// try fallback
		id, err = readKenv()
	}
	if err != nil {
		return "", err
	}
	return id, nil
}

func readHostid() (string, error) {
	buf, err := readFile(hostidPath)
	if err != nil {
		return "", err
	}
	return trim(string(buf)), nil
}

func readKenv() (string, error) {
	buf := &bytes.Buffer{}
	err := run(buf, os.Stderr, "kenv", "-q", "smbios.system.uuid")
	if err != nil {
		return "", err
	}
	return trim(buf.String()), nil
}
<|endoftext|>


<commit_before>package gochrome

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/gorilla/websocket"
)

var TabNotFound = errors.New("Tab not found")

type Command struct {
	Id     int        `json:"id"`
	Method string     `json:"method"`
	Params Parameters `json:"params"`
}

type Parameters map[string]interface{}

type Tab struct {
	Description          string `json:"description"`
	DevtoolsFrontendUrl  string `json:"devtoolsFrontendUrl"`
	FaviconUrl           string `json:"faviconUrl"`
	Id                   string `json:"id"`
	Title                string `json:"title"`
	Type                 string `json:"type"`
	Url                  string `json:"url"`
	WebSocketDebuggerUrl string `json:"webSocketDebuggerUrl"`
}

type Chrome struct {
	c              *websocket.Conn
	NetworkHandler func(Message)
	Loaded         chan bool
}

type Message struct {
	Method string `json:"method"`
	Params map[string]interface{}
}

type Result struct {
	Id     int                    `json:"id"`
	Error  map[string]interface{} `json:"error"`
	Result map[string]interface{} `json:"result"`
}

func New(url string, tab int, nh func(Message)) (*Chrome, error) {
	url, err := getTab(url, tab)
	c, err := newClient(url)
	if err != nil {
		return nil, err
	}
	loaded := make(chan bool)
	ch := &Chrome{c, nh, loaded}
	go ch.readMessages()
	return ch, err
}

func (ch *Chrome) Send(co Command) error {
	message, err := json.Marshal(co)
	if err != nil {
		return err
	}
	err = ch.c.WriteMessage(1, message)
	return err
}

func (ch *Chrome) SendSync(co Command) (Result, error) {
	message, err := json.Marshal(co)
	if err != nil {
		return Result{}, err
	}
	err = ch.c.WriteMessage(1, message)
	for {
		_, r, err := ch.c.ReadMessage()
		if err != nil {
			continue
		}

		res := Result{}
		err = json.Unmarshal(r, &res)
		if err != nil {
			fmt.Println(err)
			continue
		}

		if res.Id == co.Id {
			return res, nil
		}

	}
}

func (ch *Chrome) readMessages() {
	for {
		_, r, err := ch.c.ReadMessage()
		if err != nil {
			continue
		}

		m := Message{}
		err = json.Unmarshal(r, &m)
		if err != nil {
			fmt.Println(err)
			continue
		}

		if m.Method == "Page.loadEventFired" {
			ch.Loaded <- true
			close(ch.Loaded)
			break
		}

		if strings.HasPrefix(m.Method, "Network.") {
			go ch.NetworkHandler(m)
		}

	}

}

func (ch *Chrome) Close() error {
	return ch.c.Close()
}

func getTab(url string, tab int) (string, error) {
	resp, err := http.Get(url + "/json")
	if err != nil {
		return "", err
	}
	body, err := ioutil.ReadAll(resp.Body)

	resp.Body.Close()

	t := []Tab{}
	err = json.Unmarshal(body, &t)
	if err != nil {
		return "", err
	}
	if len(t) < tab {
		return "", TabNotFound
	}

	return t[tab].WebSocketDebuggerUrl, nil
}

func newClient(url string) (*websocket.Conn, error) {
	r, _ := http.NewRequest("GET", url, nil)
	r.Header.Add("Content-Type", "application/json")
	c, _, err := websocket.DefaultDialer.Dial(url, r.Header)
	if err != nil {
		return nil, err
	}
	return c, nil
}
<commit_msg>Remove SendSync. Add On and Off methods.<commit_after>package gochrome

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/gorilla/websocket"
)

var TabNotFound = errors.New("Tab not found")

type Command struct {
	Id     int        `json:"id"`
	Method string     `json:"method"`
	Params Parameters `json:"params"`
}

type Parameters map[string]interface{}

type Tab struct {
	Description          string `json:"description"`
	DevtoolsFrontendUrl  string `json:"devtoolsFrontendUrl"`
	FaviconUrl           string `json:"faviconUrl"`
	Id                   string `json:"id"`
	Title                string `json:"title"`
	Type                 string `json:"type"`
	Url                  string `json:"url"`
	WebSocketDebuggerUrl string `json:"webSocketDebuggerUrl"`
}

type Chrome struct {
	c         *websocket.Conn
	listeners map[string][]chan Message
}

type Message struct {
	Method string `json:"method"`
	Params map[string]interface{}
}

type Result struct {
	Id     int                    `json:"id"`
	Error  map[string]interface{} `json:"error"`
	Result map[string]interface{} `json:"result"`
}

func New(url string, tab int) (*Chrome, error) {
	url, err := getTab(url, tab)
	c, err := newClient(url)
	if err != nil {
		return nil, err
	}
	ch := &Chrome{c, make(map[string][]chan Message, 0)}
	go ch.readMessages()
	return ch, err
}

func (ch *Chrome) Send(co Command) error {
	message, err := json.Marshal(co)
	if err != nil {
		return err
	}
	err = ch.c.WriteMessage(1, message)
	return err
}

func (ch *Chrome) readMessages() {
	for {
		_, r, err := ch.c.ReadMessage()
		if err != nil {
			continue
		}

		m := Message{}
		err = json.Unmarshal(r, &m)
		if err != nil {
			fmt.Println(err)
			continue
		}
		go ch.broadMsgs(m)
	}
}

// Runs in its own goroutine, to brodacst msgs
func (ch *Chrome) broadMsgs(m Message) {
	lc, ok := ch.listeners[m.Method]
	if !ok {
		return
	}

	for c := range lc {
		lc[c] <- m
	}
}

// Listen on a certain event
func (ch *Chrome) On(e string, c chan Message) (ok bool) {
	if _, ok := ch.listeners[e]; ok {
		ch.listeners[e] = append(ch.listeners[e], c)
		return true
	}
	ch.listeners[e] = append(make([]chan Message, 0), c)
	return true
}

// Remove a listener
func (ch *Chrome) Off(e string, c chan Message) {
	lc, ok := ch.listeners[e]
	if !ok {
		return
	}

	for i := range lc {
		if lc[i] == c {
			lc = append(lc[:i], lc[i+1:]...)
			break
		}
	}
}

func (ch *Chrome) Close() error {
	return ch.c.Close()
}

func getTab(url string, tab int) (string, error) {
	resp, err := http.Get(url + "/json")
	if err != nil {
		return "", err
	}
	body, err := ioutil.ReadAll(resp.Body)

	resp.Body.Close()

	t := []Tab{}
	err = json.Unmarshal(body, &t)
	if err != nil {
		return "", err
	}
	if len(t) < tab {
		return "", TabNotFound
	}

	return t[tab].WebSocketDebuggerUrl, nil
}

func newClient(url string) (*websocket.Conn, error) {
	r, _ := http.NewRequest("GET", url, nil)
	r.Header.Add("Content-Type", "application/json")
	c, _, err := websocket.DefaultDialer.Dial(url, r.Header)
	if err != nil {
		return nil, err
	}
	return c, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Collins
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package steven

import (
	"bytes"
	"image"
	"image/png"
	"io/ioutil"
	"math"

	"github.com/go-gl/mathgl/mgl32"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/resource"
)

var (
	cloudOffset float64
	clouds      []*cloud
	cloudImage  *image.NRGBA
)

type cloud struct {
	*render.StaticModel

	used     bool
	prevUsed bool
	x, y     int
}

func tickClouds(delta float64) {
	if Client != nil && Client.WorldType != wtOverworld {
		for _, c := range clouds {
			c.Free()
		}
		clouds = nil
		return
	}
	if cloudImage == nil {
		f, err := resource.Open("minecraft", "textures/environment/clouds.png")
		if err != nil {
			cloudImage = image.NewNRGBA(image.Rect(0, 0, 256, 256))
		} else {
			defer f.Close()
			img, err := png.Decode(f)
			if err != nil {
				panic(err)
			}
			i, ok := img.(*image.NRGBA)
			if !ok {
				i = convertImage(img)
			}
			cloudImage = i
		}

		var buf bytes.Buffer
		png.Encode(&buf, cloudImage)
		ioutil.WriteFile("test.png", buf.Bytes(), 0777)
	}
	for _, c := range clouds {
		c.used = false
	}

	cloudOffset += delta

	for x := -12; x <= 12; x++ {
		for y := -12; y <= 12; y++ {
			fx, fy := float64(x)/256.0, float64(y)/256.0
			fx += -math.Floor(Client.X/12.0) / 256.0
			fy += -math.Floor(Client.Z/12.0) / 256.0
			fy += cloudOffset / 500.0 / 256
			c := getCloud(
				math.Mod(1+math.Mod(fx, 1), 1),
				math.Mod(1+math.Mod(fy, 1), 1),
			)
			if c == nil {
				continue
			}
			c.Y = -128
			c.X = -float32(math.Floor((Client.X-float64(x*12))/12) * 12)
			c.Z = float32(math.Floor((Client.Z-float64(y*12))/12)*12) + float32(math.Mod(cloudOffset/500.0, 1)*12)
			c.Radius = 20
			c.Matrix[0] = mgl32.Translate3D(-c.X, c.Y, c.Z)
			c.SkyLight = 15
		}
	}

	for _, c := range clouds {
		c.prevUsed = c.used
		if !c.used {
			c.X = 0
			c.Z = 0
			c.Y = 9999
			c.Radius = 0.01
		}
	}
}

func getCloud(x, y float64) *cloud {
	px, py := int(256*x)%255, int(256*y)%255

	sx := cloudImage.Bounds().Dx() / 256
	sy := cloudImage.Bounds().Dy() / 256
	var ok bool
check:
	for xx := 0; xx < sx; xx++ {
		for yy := 0; yy < sy; yy++ {
			col := cloudImage.NRGBAAt(xx+px, yy+py)
			if col.A > 20 {
				ok = true
				break check
			}
		}
	}
	if !ok {
		return nil
	}

	var c *cloud
	for _, cl := range clouds {
		if cl.used {
			continue
		}
		// Find a existing match
		if cl.x == px && cl.y == py {
			c = cl
			break
		}
		// Keep a reference to the last unused one
		// incase we need a fallback
		if !cl.prevUsed {
			c = cl
		}
	}
	if c == nil {
		// Have to steal an existing one or create a new one
		for _, cl := range clouds {
			if !cl.used {
				c = cl
				break
			}
		}
		if c == nil {
			tex := render.GetTexture("environment/clouds")
			data := appendBox(nil, -6, -2, -6, 12, 4, 12, [6]render.TextureInfo{tex, tex, tex, tex, tex, tex})
			c = &cloud{StaticModel: render.NewStaticModel([][]*render.StaticVertex{data})}
			c.Colors[0] = [4]float32{1.0, 1.0, 1.0, 1.0}
			clouds = append(clouds, c)
		}
	}

	if c.x != px || c.y != py {
		tex := render.RelativeTexture(render.GetTexture("environment/clouds"), 256, 256).
			Sub(px, py, 1, 1)
		for _, v := range c.Verts {
			v.Texture = tex
		}
		c.Refresh()
		c.x = px
		c.y = py
	}
	c.used = true
	return c
}
<commit_msg>steven: make clouds fade in and out<commit_after>// Copyright 2015 Matthew Collins
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package steven

import (
	"bytes"
	"image"
	"image/png"
	"io/ioutil"
	"math"

	"github.com/go-gl/mathgl/mgl32"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/resource"
)

var (
	cloudOffset float64
	clouds      []*cloud
	cloudImage  *image.NRGBA
)

type cloud struct {
	*render.StaticModel

	used     bool
	prevUsed bool
	x, y     int
}

func tickClouds(delta float64) {
	if Client != nil && Client.WorldType != wtOverworld {
		for _, c := range clouds {
			c.Free()
		}
		clouds = nil
		return
	}
	if cloudImage == nil {
		f, err := resource.Open("minecraft", "textures/environment/clouds.png")
		if err != nil {
			cloudImage = image.NewNRGBA(image.Rect(0, 0, 256, 256))
		} else {
			defer f.Close()
			img, err := png.Decode(f)
			if err != nil {
				panic(err)
			}
			i, ok := img.(*image.NRGBA)
			if !ok {
				i = convertImage(img)
			}
			cloudImage = i
		}

		var buf bytes.Buffer
		png.Encode(&buf, cloudImage)
		ioutil.WriteFile("test.png", buf.Bytes(), 0777)
	}
	for _, c := range clouds {
		c.used = false
	}

	cloudOffset += delta

	for x := -12; x <= 12; x++ {
		for y := -12; y <= 12; y++ {
			fx, fy := float64(x)/256.0, float64(y)/256.0
			fx += -math.Floor(Client.X/12.0) / 256.0
			fy += -math.Floor(Client.Z/12.0) / 256.0
			fy += cloudOffset / 500.0 / 256
			c := getCloud(
				math.Mod(1+math.Mod(fx, 1), 1),
				math.Mod(1+math.Mod(fy, 1), 1),
			)
			if c == nil {
				continue
			}
			c.Y = -128
			c.X = -float32(math.Floor((Client.X-float64(x*12))/12) * 12)
			c.Z = float32(math.Floor((Client.Z-float64(y*12))/12)*12) + float32(math.Mod(cloudOffset/500.0, 1)*12)
			c.Radius = 20
			c.Matrix[0] = mgl32.Translate3D(-c.X, c.Y, c.Z)
			c.Colors[0][3] = float32(math.Max(math.Min(
				math.Min(1.0-(math.Abs(float64(c.Z)-Client.Z)/12-11), 1.0-(math.Abs(float64(-c.X)-Client.X)/12-11)),
				1.0), 0.0))
			c.SkyLight = 15
		}
	}

	for _, c := range clouds {
		c.prevUsed = c.used
		if !c.used {
			c.X = 0
			c.Z = 0
			c.Y = 9999
			c.Radius = 0.01
		}
	}
}

func getCloud(x, y float64) *cloud {
	px, py := int(256*x)%255, int(256*y)%255

	sx := cloudImage.Bounds().Dx() / 256
	sy := cloudImage.Bounds().Dy() / 256
	var ok bool
check:
	for xx := 0; xx < sx; xx++ {
		for yy := 0; yy < sy; yy++ {
			col := cloudImage.NRGBAAt(xx+px, yy+py)
			if col.A > 20 {
				ok = true
				break check
			}
		}
	}
	if !ok {
		return nil
	}

	var c *cloud
	for _, cl := range clouds {
		if cl.used {
			continue
		}
		// Find a existing match
		if cl.x == px && cl.y == py {
			c = cl
			break
		}
		// Keep a reference to the last unused one
		// incase we need a fallback
		if !cl.prevUsed {
			c = cl
		}
	}
	if c == nil {
		// Have to steal an existing one or create a new one
		for _, cl := range clouds {
			if !cl.used {
				c = cl
				break
			}
		}
		if c == nil {
			tex := render.GetTexture("environment/clouds")
			data := appendBox(nil, -6, -2, -6, 12, 4, 12, [6]render.TextureInfo{tex, tex, tex, tex, tex, tex})
			c = &cloud{StaticModel: render.NewStaticModel([][]*render.StaticVertex{data})}
			c.Colors[0] = [4]float32{1.0, 1.0, 1.0, 1.0}
			clouds = append(clouds, c)
		}
	}

	if c.x != px || c.y != py {
		tex := render.RelativeTexture(render.GetTexture("environment/clouds"), 256, 256).
			Sub(px, py, 1, 1)
		for _, v := range c.Verts {
			v.Texture = tex
		}
		c.Refresh()
		c.x = px
		c.y = py
	}
	c.used = true
	return c
}
<|endoftext|>


<commit_before>package mqttclient

import (
	"crypto/tls"
	"errors"
	"fmt"
	mqtt "github.com/clearblade/mqtt_parsing"
	"io"
	mrand "math/rand"
	"net"
	"sync"
	"time"
)

type Client struct {

	//this is all clearblade specific
	SystemKey    string
	SystemSecret string
	AuthToken    string
	Clientid     string
	//this will usually be occupied
	//by a net.Conn
	C                   io.ReadWriteCloser
	internalOutgoingBuf chan []byte
	Timeout             time.Duration
	//thou shalt type channels consumed by others
	ClientErrorBuffer chan error

	internalErrorBuffer chan *errWrap
	shutdown_reader     chan struct{}
	shutdown_writer     chan struct{}
	//introduce a sync write mode?
	last_timeout_reccd time.Time

	shutting_down            bool
	got_connack              chan struct{}
	msg_store                *storage
	subscriptions            *outgoing_topics
	waiting_for_subscription *subscription_store
	//TODO:redesign around a thread-local
	//rng
	rando      *mrand.Rand
	randomut   *sync.RWMutex
	resetTimer chan bool
}

var (
	Verbose bool
)

//Start connects to the mqtt broker. It does not send the connect packet. Use the SendConnect function for that.
func (c *Client) Start(addr string, ssl *tls.Config) error {
	var con net.Conn
	var err error
	if ssl != nil {
		con, err = tls.Dial("tcp", addr, ssl)
	} else {
		con, err = net.Dial("tcp", addr)
	}
	if err != nil {
		return err
	}
	c.C = con

	go c.connectionWriter()
	go c.connectionListener()
	go c.errorTree()
	return nil
}

//NewClient allocates a new client. It is supplied with the (in order of appearance)
//Token, SystemKey,SystemSecret,Clientid, and the mqtt timeout
//Note that the following combinations of (Token|SystemKey|SystemSecret) are allowed
//(Token && SystemKey), (SystemKey && SystemSecret)
func NewClient(tok, sk, ss, cid string, timeout int) *Client {
	client := &Client{
		msg_store:                newStorage(),
		waiting_for_subscription: newSubscriptionStore(),
		subscriptions:            newOutgoingTopics(),
		SystemSecret:             ss,
		SystemKey:                sk,
		AuthToken:                tok,
		Clientid:                 cid,
		Timeout:                  time.Duration(timeout) * time.Second,
		//internalOutgoingBuf:      make(chan []byte, 30),
		internalOutgoingBuf: make(chan []byte),
		ClientErrorBuffer:   make(chan error, 10),
		internalErrorBuffer: make(chan *errWrap, 2),
		shutdown_reader:     make(chan struct{}, 1),
		shutdown_writer:     make(chan struct{}, 1),
		rando:               mrand.New(mrand.NewSource(time.Now().UnixNano())),
		got_connack:         make(chan struct{}, 1),
		randomut:            new(sync.RWMutex),
		resetTimer:          make(chan bool, 1),
	}
	return client
}

//sendMessage is an internal function that acts as a central point
//of failure for all of the message sending channels
//sort of like a fan-in, except this simply allows us to do
// all of the error handling logic in one place
func (c *Client) sendMessage(m mqtt.Message) error {
	_, err := c.C.Write(m.Encode())
	if err != nil {
		c.internalErrorBuffer <- &errWrap{
			err:      err,
			reciever: _CON_WRITER,
		}
		return err
	}
	select {
	case c.resetTimer <- true:
	default:
	}
	return nil
}

//connectionWriter is an internal function. it essentially sits in a goroutine and writes to the connection
//whenever it recieves a message over the channel
func (c *Client) connectionWriter() {
	if c.C == nil {
		return
	}
	for {
		select {
		case out := <-c.internalOutgoingBuf:
			_, err := c.C.Write(out)
			if err != nil {
				c.internalErrorBuffer <- &errWrap{
					err:      err,
					reciever: _CON_WRITER,
				}
				return
			}
		case <-c.shutdown_writer:
			return
		}
	}
}

//connectionListener is another function that sits on a goroutine.
//DecodePacket blocks until it reads a complete mqtt packet
func (c *Client) connectionListener() {
	if c.C == nil {
		return
	}
	mch, ech, shutdown := make(chan mqtt.Message, 10), make(chan error, 1), false
	//we have to establish an internal chain of goroutines here
	//otherwise we couldn't shutdown the listener on demand
	//since it's really hard to coordinate all the shutting down
	//when a connection drops
	//we're waiting for the connection listener to simply fail
	//this allows us to handle it a bit more gracefully
	//in order to shut down via channels directly we'd have to
	//wait for the read to fail anyway.
	go func(m chan mqtt.Message, e chan error) {
		for {
			msg, err := mqtt.DecodePacket(c.C)
			if err != nil {
				shutdown = true
				e <- err
				return
			}
			m <- msg
		}
	}(mch, ech)

	//lastTimeoutTime := time.Now()
	heardFromServer := true
	for {
		//timeDiff := time.Now().Sub(lastTimeoutTime)
		select {
		case <-c.resetTimer:
		case msg := <-mch:
			heardFromServer = true
			c.dispatch(msg)
		case e := <-ech:
			if !shutdown {
				c.internalErrorBuffer <- &errWrap{
					err:      e,
					reciever: _CON_READER,
				}
			}
		case <-c.shutdown_reader:
			shutdown = true
			return
		case <-time.After(c.Timeout /*- timeDiff*/):
			if !heardFromServer {
				for done := false; !done; {
					select {
					case msg := <-mch:
						heardFromServer = true
						c.dispatch(msg)
					default:
						done = true
					}
				}
			}
			if heardFromServer {
				c.sendMessage(&mqtt.Pingreq{})
				heardFromServer = false
				//lastTimeoutTime = time.Now()
			} else {
				c.Shutdown(true)
			}
		}
	}
}

//this is the "business logic" of the client. it decides how each packet mutates the
//internal state of the client
func (c *Client) dispatch(msg mqtt.Message) {
	//for example we make a decision if the client sees this request or not
	//or if we have to send another message in a flow
	c.last_timeout_reccd = time.Now()
	switch msg.Type() {
	case mqtt.CONNECT:
		//shouldn't happen?
	case mqtt.CONNACK:
		if msg.(*mqtt.Connack).ReturnCode != 0 {
			c.internalErrorBuffer <- &errWrap{
				err:      fmt.Errorf("Got return type %d instead of 0\n", msg.(*mqtt.Connack).ReturnCode),
				reciever: _OTHER,
			}
		} else {
			c.got_connack <- struct{}{}
		}
	case mqtt.PUBLISH:
		pubMsg := msg.(*mqtt.Publish)
		c.subscriptions.relay_message(pubMsg, pubMsg.Topic.Whole)
		switch msg.(*mqtt.Publish).Header.QOS {
		case 1:
			c.sendMessage(&mqtt.Puback{
				MessageId: pubMsg.MessageId,
			})
		case 2:
			c.sendMessage(&mqtt.Pubrec{
				MessageId: pubMsg.MessageId,
			})
		}
	case mqtt.PUBACK:
		//TODO:handle resend
	case mqtt.PUBREC:
		//discard, store the fact that it was recieved
		c.sendMessage(&mqtt.Pubrel{
			MessageId: msg.(*mqtt.Pubrec).MessageId,
			Header: &mqtt.StaticHeader{
				DUP:    false,
				Retain: false,
				QOS:    1,
			}})
	case mqtt.PUBREL:
		//this shouldn't have happened
	case mqtt.SUBSCRIBE:
		//this is not supposed to happen
	case mqtt.SUBACK:
		//the subscribe call blocks, so we need to forward the message
		//along that the subscribe was acknowleged so we can return
		//control flow to the parent program
		//of course, the problem is that a suback does not have
		//the subscriptions in it by name
		//but it does have the same message id
		//so we have to retrieve that and then match up the subscribe
		//TODO:NOTE THAT WE ARE ONLY USING ONE TOPIC PER SUBSCRIBE MESSSAGE
		//THIS LOGIC WILL NEED TWEAKING IF THAT CHANGES
		msg := c.msg_store.getEntry(msg.(*mqtt.Suback).MessageId)
		if msg == nil {
			//this is a bad thing to happen
			return
		}
		sub, ok := msg.(*mqtt.Subscribe)
		if !ok {
			//this is a worse thing to happen
			return
		}
		//TODO: BUG:: IF WE MAKE MULTISUBSCRIPTION, THIS WILL BREAK
		//we've now released control flow in that channel
		//also it'll allocate the userside channel and all that good stuff
		c.waiting_for_subscription.relay_message(msg, sub.Subscriptions[0].Topic.Whole)

	case mqtt.UNSUBSCRIBE:
		//this shouldn't happen
	case mqtt.UNSUBACK:
		//not gorgeous, but it do the thing
		msg := c.msg_store.getEntry(msg.(*mqtt.Unsuback).MessageId)
		unsub, ok := msg.(*mqtt.Unsubscribe)
		if !ok {
			return
		}
		c.subscriptions.remove_subscription(unsub.Topics[0].Topic.Whole)
		//TODO: do something besides prepend UNSUBSCRIBE# (the hash makes it an invalid mqtt topic, which should prevent collisions) to the front of the topic
		//but also doesn't allocate another big-ole map wrapper
		err := c.waiting_for_subscription.relay_message(msg, "UNSUBSCRIBE#"+unsub.Topics[0].Topic.Whole)
		if err != nil {
			c.ClientErrorBuffer <- err
		}
	case mqtt.PINGREQ:
		//shouldn't happen
	case mqtt.PINGRESP:
		//pingresp will reset the counter elsewhere
	case mqtt.DISCONNECT:
		//shouldn't happen
	default:
		c.ClientErrorBuffer <- fmt.Errorf("Invalid mqtt type recieved %+v", msg)
	}
}

//errorTree is the goroutine that sits on it's own goroutine and waits for
//a message to be recieved on c.internalErrorBuffer. It's our "in case of emergency break glass"
//way of reporting an error, and shutting the entire thing down
func (c *Client) errorTree() {
	//we still need to shutdown the listeners anyway
	//at least write will probably not error out

	//since you're reading the text of this fn, prepare your face for some exposition on how this mechanism works
	//so, we've spread reading and writing to the conn (or whatever) across goroutines, this is great
	//high speed low drag
	//but what happens if one goroutine encounters an error? the goroutines don't know about each other, so what do we do?
	//well, writing, and reading from to a closed connection is an error condition. so if one goroutine dies, then the other
	//will be taken down with it
	//we also use this mechanism for a regular shutdown of the client's connection, simply crashing them both and releasing the resources
	e := <-c.internalErrorBuffer
	if e.reciever != _CON_READER {
		c.shutdown_reader <- struct{}{}
	}
	if e.reciever != _CON_WRITER {
		c.shutdown_writer <- struct{}{}
	}
	if c.C != nil {
		c.C.Close()
	}
	if e.reciever != _REGULAR_SHUTDOWN {
		c.ClientErrorBuffer <- fmt.Errorf("Shutting down: Recieved error %v\n", e.err.Error())
	}
}

//Shutdown sends a disconnect packet (if asked), and then disconnects from the broker after a set time limit
func (c *Client) Shutdown(sendDisconnect bool) error {
	var err error
	if sendDisconnect {
		e := SendDisconnect(c)
		if e != nil {
			//don't return here, wait to finish the flow
			err = errors.New("While sending disconnect: " + e.Error() + "\nNote:connection was shut down anyway")
		}
		<-time.After(time.Second)
	}
	c.internalErrorBuffer <- &errWrap{reciever: _REGULAR_SHUTDOWN}
	return err
}

func (c *Client) randoPerm(i int) []int {
	c.randomut.RLock()
	order := c.rando.Perm(i)
	c.randomut.RUnlock()
	return order
}

func (c *Client) getInt() int {
	c.randomut.RLock()
	num := c.rando.Int()
	c.randomut.RUnlock()
	return num
}
<commit_msg>send pingreq after timeout<commit_after>package mqttclient

import (
	"crypto/tls"
	"errors"
	"fmt"
	mqtt "github.com/clearblade/mqtt_parsing"
	"io"
	mrand "math/rand"
	"net"
	"sync"
	"time"
)

type Client struct {

	//this is all clearblade specific
	SystemKey    string
	SystemSecret string
	AuthToken    string
	Clientid     string
	//this will usually be occupied
	//by a net.Conn
	C                   io.ReadWriteCloser
	internalOutgoingBuf chan []byte
	Timeout             time.Duration
	//thou shalt type channels consumed by others
	ClientErrorBuffer chan error

	internalErrorBuffer chan *errWrap
	shutdown_reader     chan struct{}
	shutdown_writer     chan struct{}
	//introduce a sync write mode?
	last_timeout_reccd time.Time

	shutting_down            bool
	got_connack              chan struct{}
	msg_store                *storage
	subscriptions            *outgoing_topics
	waiting_for_subscription *subscription_store
	//TODO:redesign around a thread-local
	//rng
	rando      *mrand.Rand
	randomut   *sync.RWMutex
	resetTimer chan bool
}

var (
	Verbose bool
)

//Start connects to the mqtt broker. It does not send the connect packet. Use the SendConnect function for that.
func (c *Client) Start(addr string, ssl *tls.Config) error {
	var con net.Conn
	var err error
	if ssl != nil {
		con, err = tls.Dial("tcp", addr, ssl)
	} else {
		con, err = net.Dial("tcp", addr)
	}
	if err != nil {
		return err
	}
	c.C = con

	go c.connectionWriter()
	go c.connectionListener()
	go c.errorTree()
	return nil
}

//NewClient allocates a new client. It is supplied with the (in order of appearance)
//Token, SystemKey,SystemSecret,Clientid, and the mqtt timeout
//Note that the following combinations of (Token|SystemKey|SystemSecret) are allowed
//(Token && SystemKey), (SystemKey && SystemSecret)
func NewClient(tok, sk, ss, cid string, timeout int) *Client {
	client := &Client{
		msg_store:                newStorage(),
		waiting_for_subscription: newSubscriptionStore(),
		subscriptions:            newOutgoingTopics(),
		SystemSecret:             ss,
		SystemKey:                sk,
		AuthToken:                tok,
		Clientid:                 cid,
		Timeout:                  time.Duration(timeout) * time.Second,
		//internalOutgoingBuf:      make(chan []byte, 30),
		internalOutgoingBuf: make(chan []byte),
		ClientErrorBuffer:   make(chan error, 10),
		internalErrorBuffer: make(chan *errWrap, 2),
		shutdown_reader:     make(chan struct{}, 1),
		shutdown_writer:     make(chan struct{}, 1),
		rando:               mrand.New(mrand.NewSource(time.Now().UnixNano())),
		got_connack:         make(chan struct{}, 1),
		randomut:            new(sync.RWMutex),
		resetTimer:          make(chan bool, 1),
	}
	return client
}

//sendMessage is an internal function that acts as a central point
//of failure for all of the message sending channels
//sort of like a fan-in, except this simply allows us to do
// all of the error handling logic in one place
func (c *Client) sendMessage(m mqtt.Message) error {
	_, err := c.C.Write(m.Encode())
	if err != nil {
		c.internalErrorBuffer <- &errWrap{
			err:      err,
			reciever: _CON_WRITER,
		}
		return err
	}
	select {
	case c.resetTimer <- true:
	default:
	}
	return nil
}

//connectionWriter is an internal function. it essentially sits in a goroutine and writes to the connection
//whenever it recieves a message over the channel
func (c *Client) connectionWriter() {
	if c.C == nil {
		return
	}
	for {
		select {
		case out := <-c.internalOutgoingBuf:
			_, err := c.C.Write(out)
			if err != nil {
				c.internalErrorBuffer <- &errWrap{
					err:      err,
					reciever: _CON_WRITER,
				}
				return
			}
		case <-c.shutdown_writer:
			return
		}
	}
}

//connectionListener is another function that sits on a goroutine.
//DecodePacket blocks until it reads a complete mqtt packet
func (c *Client) connectionListener() {
	if c.C == nil {
		return
	}
	mch, ech, shutdown := make(chan mqtt.Message, 10), make(chan error, 1), false
	//we have to establish an internal chain of goroutines here
	//otherwise we couldn't shutdown the listener on demand
	//since it's really hard to coordinate all the shutting down
	//when a connection drops
	//we're waiting for the connection listener to simply fail
	//this allows us to handle it a bit more gracefully
	//in order to shut down via channels directly we'd have to
	//wait for the read to fail anyway.
	go func(m chan mqtt.Message, e chan error) {
		for {
			msg, err := mqtt.DecodePacket(c.C)
			if err != nil {
				shutdown = true
				e <- err
				return
			}
			m <- msg
		}
	}(mch, ech)

	heardFromServer := true
	for {
		select {
		case <-c.resetTimer:
		case msg := <-mch:
			heardFromServer = true
			c.dispatch(msg)
		case e := <-ech:
			if !shutdown {
				c.internalErrorBuffer <- &errWrap{
					err:      e,
					reciever: _CON_READER,
				}
			}
		case <-c.shutdown_reader:
			shutdown = true
			return
		case <-time.After(c.Timeout):
			heardFromServer = false
		}
		if !heardFromServer {
			c.sendMessage(&mqtt.Pingreq{})
		}
	}
}

//this is the "business logic" of the client. it decides how each packet mutates the
//internal state of the client
func (c *Client) dispatch(msg mqtt.Message) {
	//for example we make a decision if the client sees this request or not
	//or if we have to send another message in a flow
	c.last_timeout_reccd = time.Now()
	switch msg.Type() {
	case mqtt.CONNECT:
		//shouldn't happen?
	case mqtt.CONNACK:
		if msg.(*mqtt.Connack).ReturnCode != 0 {
			c.internalErrorBuffer <- &errWrap{
				err:      fmt.Errorf("Got return type %d instead of 0\n", msg.(*mqtt.Connack).ReturnCode),
				reciever: _OTHER,
			}
		} else {
			c.got_connack <- struct{}{}
		}
	case mqtt.PUBLISH:
		pubMsg := msg.(*mqtt.Publish)
		c.subscriptions.relay_message(pubMsg, pubMsg.Topic.Whole)
		switch msg.(*mqtt.Publish).Header.QOS {
		case 1:
			c.sendMessage(&mqtt.Puback{
				MessageId: pubMsg.MessageId,
			})
		case 2:
			c.sendMessage(&mqtt.Pubrec{
				MessageId: pubMsg.MessageId,
			})
		}
	case mqtt.PUBACK:
		//TODO:handle resend
	case mqtt.PUBREC:
		//discard, store the fact that it was recieved
		c.sendMessage(&mqtt.Pubrel{
			MessageId: msg.(*mqtt.Pubrec).MessageId,
			Header: &mqtt.StaticHeader{
				DUP:    false,
				Retain: false,
				QOS:    1,
			}})
	case mqtt.PUBREL:
		//this shouldn't have happened
	case mqtt.SUBSCRIBE:
		//this is not supposed to happen
	case mqtt.SUBACK:
		//the subscribe call blocks, so we need to forward the message
		//along that the subscribe was acknowleged so we can return
		//control flow to the parent program
		//of course, the problem is that a suback does not have
		//the subscriptions in it by name
		//but it does have the same message id
		//so we have to retrieve that and then match up the subscribe
		//TODO:NOTE THAT WE ARE ONLY USING ONE TOPIC PER SUBSCRIBE MESSSAGE
		//THIS LOGIC WILL NEED TWEAKING IF THAT CHANGES
		msg := c.msg_store.getEntry(msg.(*mqtt.Suback).MessageId)
		if msg == nil {
			//this is a bad thing to happen
			return
		}
		sub, ok := msg.(*mqtt.Subscribe)
		if !ok {
			//this is a worse thing to happen
			return
		}
		//TODO: BUG:: IF WE MAKE MULTISUBSCRIPTION, THIS WILL BREAK
		//we've now released control flow in that channel
		//also it'll allocate the userside channel and all that good stuff
		c.waiting_for_subscription.relay_message(msg, sub.Subscriptions[0].Topic.Whole)

	case mqtt.UNSUBSCRIBE:
		//this shouldn't happen
	case mqtt.UNSUBACK:
		//not gorgeous, but it do the thing
		msg := c.msg_store.getEntry(msg.(*mqtt.Unsuback).MessageId)
		unsub, ok := msg.(*mqtt.Unsubscribe)
		if !ok {
			return
		}
		c.subscriptions.remove_subscription(unsub.Topics[0].Topic.Whole)
		//TODO: do something besides prepend UNSUBSCRIBE# (the hash makes it an invalid mqtt topic, which should prevent collisions) to the front of the topic
		//but also doesn't allocate another big-ole map wrapper
		err := c.waiting_for_subscription.relay_message(msg, "UNSUBSCRIBE#"+unsub.Topics[0].Topic.Whole)
		if err != nil {
			c.ClientErrorBuffer <- err
		}
	case mqtt.PINGREQ:
		//shouldn't happen
	case mqtt.PINGRESP:
		//pingresp will reset the counter elsewhere
	case mqtt.DISCONNECT:
		//shouldn't happen
	default:
		c.ClientErrorBuffer <- fmt.Errorf("Invalid mqtt type recieved %+v", msg)
	}
}

//errorTree is the goroutine that sits on it's own goroutine and waits for
//a message to be recieved on c.internalErrorBuffer. It's our "in case of emergency break glass"
//way of reporting an error, and shutting the entire thing down
func (c *Client) errorTree() {
	//we still need to shutdown the listeners anyway
	//at least write will probably not error out

	//since you're reading the text of this fn, prepare your face for some exposition on how this mechanism works
	//so, we've spread reading and writing to the conn (or whatever) across goroutines, this is great
	//high speed low drag
	//but what happens if one goroutine encounters an error? the goroutines don't know about each other, so what do we do?
	//well, writing, and reading from to a closed connection is an error condition. so if one goroutine dies, then the other
	//will be taken down with it
	//we also use this mechanism for a regular shutdown of the client's connection, simply crashing them both and releasing the resources
	e := <-c.internalErrorBuffer
	if e.reciever != _CON_READER {
		c.shutdown_reader <- struct{}{}
	}
	if e.reciever != _CON_WRITER {
		c.shutdown_writer <- struct{}{}
	}
	if c.C != nil {
		c.C.Close()
	}
	if e.reciever != _REGULAR_SHUTDOWN {
		c.ClientErrorBuffer <- fmt.Errorf("Shutting down: Recieved error %v\n", e.err.Error())
	}
}

//Shutdown sends a disconnect packet (if asked), and then disconnects from the broker after a set time limit
func (c *Client) Shutdown(sendDisconnect bool) error {
	var err error
	if sendDisconnect {
		e := SendDisconnect(c)
		if e != nil {
			//don't return here, wait to finish the flow
			err = errors.New("While sending disconnect: " + e.Error() + "\nNote:connection was shut down anyway")
		}
		<-time.After(time.Second)
	}
	c.internalErrorBuffer <- &errWrap{reciever: _REGULAR_SHUTDOWN}
	return err
}

func (c *Client) randoPerm(i int) []int {
	c.randomut.RLock()
	order := c.rando.Perm(i)
	c.randomut.RUnlock()
	return order
}

func (c *Client) getInt() int {
	c.randomut.RLock()
	num := c.rando.Int()
	c.randomut.RUnlock()
	return num
}
<|endoftext|>