<commit_before>package main

import (
	"errors"
	"flag"
	"log"
	"os"
	"time"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	core "github.com/jbenet/go-ipfs/core"
	corehttp "github.com/jbenet/go-ipfs/core/corehttp"
	corerepo "github.com/jbenet/go-ipfs/core/corerepo"
	coreunix "github.com/jbenet/go-ipfs/core/coreunix"
	config "github.com/jbenet/go-ipfs/repo/config"
	fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
)

var (
	writable               = flag.Bool("writable", false, "enable writing objects (with POST, PUT and DELETE)")
	refreshAssetsInterval  = flag.Duration("refresh-assets-interval", 30*time.Second, "refresh assets")
	garbageCollectInterval = flag.Duration("gc-interval", 24*time.Hour, "frequency of repo garbage collection")
	assetsPath             = flag.String("assets-path", "", "if provided, periodically adds contents of path to IPFS")
	host                   = flag.String("host", "/ip4/0.0.0.0/tcp/8080", "override the HTTP host listening address")
	performGC              = flag.Bool("gc", false, "perform garbage collection")
	nBitsForKeypair        = flag.Int("b", 1024, "number of bits for keypair (if repo is uninitialized)")
)

func main() {
	flag.Parse()
	if *assetsPath == "" {
		log.Println("asset-path not provided. hosting gateway without file server functionality...")
	}
	if err := run(); err != nil {
		log.Println(err)
	}
}

func run() error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	repoPath, err := fsrepo.BestKnownPath()
	if err != nil {
		return err
	}

	if !fsrepo.IsInitialized(repoPath) {
		conf, err := config.Init(os.Stdout, *nBitsForKeypair)
		if err != nil {
			return err
		}
		if err := fsrepo.Init(repoPath, conf); err != nil {
			return err
		}
	}

	repo := fsrepo.At(repoPath)
	if err := repo.Open(); err != nil { // owned by node
		return err
	}

	node, err := core.NewIPFSNode(ctx, core.Online(repo))
	if err != nil {
		return err
	}
	defer node.Close()

	if *performGC {
		if err := runGarbageCollectorWorker(ctx, node); err != nil {
			return err
		}
	}

	if *assetsPath != "" {
		if err := runFileServerWorker(ctx, node); err != nil {
			return err
		}
	}

	opts := []corehttp.ServeOption{
		corehttp.GatewayOption(*writable),
	}
	return corehttp.ListenAndServe(node, *host, opts...)
}

func runGarbageCollectorWorker(ctx context.Context, node *core.IpfsNode) error {
	go func() {
		for _ = range time.Tick(*garbageCollectInterval) {
			if err := corerepo.GarbageCollect(node, ctx); err != nil {
				log.Println("failed to run garbage collection", err)
			}
		}
	}()
	return nil
}

func runFileServerWorker(ctx context.Context, node *core.IpfsNode) error {
	fi, err := os.Stat(*assetsPath)
	if err != nil {
		return err
	}
	if !fi.IsDir() {
		return errors.New("asset path must be a directory")
	}
	go func() {
		for _ = range time.Tick(*refreshAssetsInterval) {
			_, err := coreunix.AddR(node, *assetsPath)
			if err != nil {
				log.Println(err)
			}
		}
	}()
	return nil
}
<commit_msg>feat(gateway-fs) use blocklist in gateway binary<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"log"
	"os"
	"time"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	core "github.com/jbenet/go-ipfs/core"
	corehttp "github.com/jbenet/go-ipfs/core/corehttp"
	corerepo "github.com/jbenet/go-ipfs/core/corerepo"
	coreunix "github.com/jbenet/go-ipfs/core/coreunix"
	config "github.com/jbenet/go-ipfs/repo/config"
	fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
)

var (
	blocklistFilepath        = flag.String("blocklist", "", "keys that should not be served by the gateway")
	writable                 = flag.Bool("writable", false, "enable writing objects (with POST, PUT and DELETE)")
	refreshBlockListInterval = flag.Duration("refresh-blocklist-interval", 30*time.Second, "refresh blocklist")
	refreshAssetsInterval    = flag.Duration("refresh-assets-interval", 30*time.Second, "refresh assets")
	garbageCollectInterval   = flag.Duration("gc-interval", 24*time.Hour, "frequency of repo garbage collection")
	assetsPath               = flag.String("assets-path", "", "if provided, periodically adds contents of path to IPFS")
	host                     = flag.String("host", "/ip4/0.0.0.0/tcp/8080", "override the HTTP host listening address")
	performGC                = flag.Bool("gc", false, "perform garbage collection")
	nBitsForKeypair          = flag.Int("b", 1024, "number of bits for keypair (if repo is uninitialized)")
)

func main() {
	flag.Parse()
	if *assetsPath == "" {
		log.Println("asset-path not provided. hosting gateway without file server functionality...")
	}
	if err := run(); err != nil {
		log.Println(err)
	}
}

func run() error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	repoPath, err := fsrepo.BestKnownPath()
	if err != nil {
		return err
	}

	if !fsrepo.IsInitialized(repoPath) {
		conf, err := config.Init(os.Stdout, *nBitsForKeypair)
		if err != nil {
			return err
		}
		if err := fsrepo.Init(repoPath, conf); err != nil {
			return err
		}
	}

	repo := fsrepo.At(repoPath)
	if err := repo.Open(); err != nil { // owned by node
		return err
	}

	node, err := core.NewIPFSNode(ctx, core.Online(repo))
	if err != nil {
		return err
	}
	defer node.Close()

	if *performGC {
		if err := runGarbageCollectorWorker(ctx, node); err != nil {
			return err
		}
	}

	if *assetsPath != "" {
		if err := runFileServerWorker(ctx, node); err != nil {
			return err
		}
	}

	blocklist := &corehttp.BlockList{}
	gateway := corehttp.NewGateway(corehttp.GatewayConfig{
		Writable:  *writable,
		BlockList: blocklist,
	})

	if err := runBlockListWorker(blocklist, *blocklistFilepath); err != nil {
		return err
	}

	opts := []corehttp.ServeOption{
		gateway.ServeOption(),
	}
	return corehttp.ListenAndServe(node, *host, opts...)
}

func runGarbageCollectorWorker(ctx context.Context, node *core.IpfsNode) error {
	go func() {
		for _ = range time.Tick(*garbageCollectInterval) {
			if err := corerepo.GarbageCollect(node, ctx); err != nil {
				log.Println("failed to run garbage collection", err)
			}
		}
	}()
	return nil
}

func runFileServerWorker(ctx context.Context, node *core.IpfsNode) error {
	fi, err := os.Stat(*assetsPath)
	if err != nil {
		return err
	}
	if !fi.IsDir() {
		return errors.New("asset path must be a directory")
	}
	go func() {
		for _ = range time.Tick(*refreshAssetsInterval) {
			_, err := coreunix.AddR(node, *assetsPath)
			if err != nil {
				log.Println(err)
			}
		}
	}()
	return nil
}

func runBlockListWorker(blocklist *corehttp.BlockList, filepath string) error {
	if filepath == "" {
		return nil
	}
	go func() {
		for _ = range time.Tick(*refreshBlockListInterval) {
			log.Println("updating the blocklist...")
			func() { // in a func to allow defer f.Close()
				f, err := os.Open(filepath)
				if err != nil {
					log.Println(err)
				}
				defer f.Close()
				scanner := bufio.NewScanner(f)
				blocked := make(map[string]struct{}) // Implement using Bloom Filter hybrid if blocklist gets large
				for scanner.Scan() {
					t := scanner.Text()
					blocked[t] = struct{}{}
				}

				// If an error occurred, do not change the existing decider. This
				// is to avoid accidentally clearing the list if the deploy is
				// botched.
				if err := scanner.Err(); err != nil {
					log.Println(err)
				} else {
					blocklist.SetDecider(func(s string) bool {
						_, ok := blocked[s]
						return !ok
					})
					log.Printf("updated the blocklist (%d entries)", len(blocked))
				}
			}()
		}
	}()
	return nil
}
<|endoftext|>


<commit_before>package shell

import (
	"bufio"
	"fmt"
	"github.com/peterh/liner"
	"io"
	"os"
	"path"
	"pfi/sensorbee/sensorbee/client"
	"runtime"
	"strings"
)

// App is the application server of SensorBee.
type App struct {
	historyFn  string
	requester  *client.Requester
	commandMap map[string]Command
}

// SetUpCommands set up application. Commands are initialized with it.
func SetUpCommands(commands []Command) App {
	// get home directory for the history file
	histDir := os.Getenv("HOME")
	if runtime.GOOS == "windows" {
		histDir = path.Join(os.Getenv("APPDATA"), "PFN", "SensorBee")
		if err := os.MkdirAll(histDir, os.ModeDir); err != nil {
			fmt.Printf("failed to create application directory '%s': %s\n", histDir, err)
		}
	}

	app := App{
		historyFn:  path.Join(histDir, ".sensorbee_liner_history"),
		commandMap: map[string]Command{},
	}
	if len(commands) == 0 {
		return app
	}

	for _, cmd := range commands {
		cmd.Init()
		for _, v := range cmd.Name() {
			app.commandMap[v] = cmd
		}
	}
	return app
}

func (a *App) prompt(line *liner.State) {
	// create a function to read from the terminal
	// and output an appropriate prompt
	// (if `continued` is true, then the prompt
	// for a continued statement will be shown)
	getNextLine := func(continued bool) (string, error) {
		// create prompt
		promptStart := "(no topology)" + promptLineStart
		if currentTopology.name != "" {
			promptStart = fmt.Sprintf("(%s)%s", currentTopology.name,
				promptLineStart)
		}
		if continued {
			promptStart = promptLineContinue
		}

		// get line from terminal
		input, err := line.Prompt(promptStart)
		if err == nil && input != "" {
			line.AppendHistory(input)
		}
		return input, err
	}

	// continue as long as there is input
	for a.readStartOfNextCommand(getNextLine, true) {
	}
}

func (a *App) readStartOfNextCommand(getNextLine func(bool) (string, error), topLevel bool) bool {
	input, err := getNextLine(false)
	// if there is no next line, stop
	if err != nil {
		if err != io.EOF {
			fmt.Fprintf(os.Stderr, "error reading line: %v", err)
		}
		if topLevel {
			// there was an EOF control character, e.g., the
			// user pressed Ctrl+D. in order not to mess up the
			// terminal, write an additional newline character
			fmt.Println("")
		}
		return false
	}

	// if there is input, find the type of command that was input
	if input != "" {
		if strings.ToLower(input) == "exit" {
			if topLevel {
				fmt.Fprintln(os.Stdout, "SensorBee shell tool is closed")
			} else {
				fmt.Fprintln(os.Stdout, "exit from file processing")
			}
			return false
		}

		if strings.HasPrefix(input, "--") { // BQL comment
			return true
		}

		// detect which type of command we have
		in := strings.ToLower(strings.Split(input, " ")[0])
		if cmd, ok := a.commandMap[in]; ok {
			// continue to read until the end of the statement
			a.readCompleteCommand(cmd, input, getNextLine)
		} else {
			fmt.Fprintf(os.Stderr, "undefined command: %v\n", in)
		}
	}
	return true
}

func (a *App) readCompleteCommand(cmd Command, input string, getNextLine func(bool) (string, error)) {
	for {
		if input != "" {
			// feed string to the command we detected and check
			// if the command accepts this as valid continuation
			status, err := cmd.Input(input)
			switch status {
			case invalidCMD:
				fmt.Fprintf(os.Stderr, "input command is invalid: %v\n", err)
				return
			case preparedCMD:
				// if the statement is complete, evaluate it
				if fileCmd, ok := cmd.(*fileLoadCmd); ok {
					r := strings.NewReader(fileCmd.queries)
					b := bufio.NewReader(r)
					getNextLineInFile := func(continued bool) (string, error) {
						s, err := b.ReadString('\n')
						return strings.TrimSuffix(s, "\n"), err
					}
					// continue as long as there is input
					path := fileCmd.filePath
					fmt.Printf("-- process %s --\n", path)
					for a.readStartOfNextCommand(getNextLineInFile, false) {
					}
					fmt.Printf("-- end %s --\n", path)
					return
				}
				cmd.Eval(a.requester)
				return
			}
		}

		var err error
		input, err = getNextLine(true)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}
	}
}

// Run begins SensorBee command line tool to management SensorBee
// and execute BQL/UDF/UDTF statements.
func (a *App) Run(requester *client.Requester) {
	line := liner.NewLiner()
	defer line.Close()

	line.SetCompleter(func(line string) (c []string) {
		// set auto complete command, if need
		return
	})

	if f, err := os.Open(a.historyFn); err == nil {
		line.ReadHistory(f)
		f.Close()
	}

	fmt.Fprintln(os.Stdout, appRunMsg)
	a.requester = requester
	a.prompt(line)

	if f, err := os.Create(a.historyFn); err != nil {
		fmt.Fprintf(os.Stderr, "error writing history file: %v", err)
	} else {
		line.WriteHistory(f)
		f.Close()
	}
}

const (
	appRunMsg          = "SensorBee shell tool is started!"
	promptLineStart    = ">>> "
	promptLineContinue = "... "
)
<commit_msg>remove unnecessary messages about start/stop<commit_after>package shell

import (
	"bufio"
	"fmt"
	"github.com/peterh/liner"
	"io"
	"os"
	"path"
	"pfi/sensorbee/sensorbee/client"
	"runtime"
	"strings"
)

// App is the application server of SensorBee.
type App struct {
	historyFn  string
	requester  *client.Requester
	commandMap map[string]Command
}

// SetUpCommands set up application. Commands are initialized with it.
func SetUpCommands(commands []Command) App {
	// get home directory for the history file
	histDir := os.Getenv("HOME")
	if runtime.GOOS == "windows" {
		histDir = path.Join(os.Getenv("APPDATA"), "PFN", "SensorBee")
		if err := os.MkdirAll(histDir, os.ModeDir); err != nil {
			fmt.Printf("failed to create application directory '%s': %s\n", histDir, err)
		}
	}

	app := App{
		historyFn:  path.Join(histDir, ".sensorbee_liner_history"),
		commandMap: map[string]Command{},
	}
	if len(commands) == 0 {
		return app
	}

	for _, cmd := range commands {
		cmd.Init()
		for _, v := range cmd.Name() {
			app.commandMap[v] = cmd
		}
	}
	return app
}

func (a *App) prompt(line *liner.State) {
	// create a function to read from the terminal
	// and output an appropriate prompt
	// (if `continued` is true, then the prompt
	// for a continued statement will be shown)
	getNextLine := func(continued bool) (string, error) {
		// create prompt
		promptStart := "(no topology)" + promptLineStart
		if currentTopology.name != "" {
			promptStart = fmt.Sprintf("(%s)%s", currentTopology.name,
				promptLineStart)
		}
		if continued {
			promptStart = promptLineContinue
		}

		// get line from terminal
		input, err := line.Prompt(promptStart)
		if err == nil && input != "" {
			line.AppendHistory(input)
		}
		return input, err
	}

	// continue as long as there is input
	for a.readStartOfNextCommand(getNextLine, true) {
	}
}

func (a *App) readStartOfNextCommand(getNextLine func(bool) (string, error), topLevel bool) bool {
	input, err := getNextLine(false)
	// if there is no next line, stop
	if err != nil {
		if err != io.EOF {
			fmt.Fprintf(os.Stderr, "error reading line: %v", err)
		}
		if topLevel {
			// there was an EOF control character, e.g., the
			// user pressed Ctrl+D. in order not to mess up the
			// terminal, write an additional newline character
			fmt.Println("")
		}
		return false
	}

	// if there is input, find the type of command that was input
	if input != "" {
		if strings.ToLower(input) == "exit" {
			if !topLevel {
				fmt.Fprintln(os.Stdout, "exit from file processing")
			}
			return false
		}

		if strings.HasPrefix(input, "--") { // BQL comment
			return true
		}

		// detect which type of command we have
		in := strings.ToLower(strings.Split(input, " ")[0])
		if cmd, ok := a.commandMap[in]; ok {
			// continue to read until the end of the statement
			a.readCompleteCommand(cmd, input, getNextLine)
		} else {
			fmt.Fprintf(os.Stderr, "undefined command: %v\n", in)
		}
	}
	return true
}

func (a *App) readCompleteCommand(cmd Command, input string, getNextLine func(bool) (string, error)) {
	for {
		if input != "" {
			// feed string to the command we detected and check
			// if the command accepts this as valid continuation
			status, err := cmd.Input(input)
			switch status {
			case invalidCMD:
				fmt.Fprintf(os.Stderr, "input command is invalid: %v\n", err)
				return
			case preparedCMD:
				// if the statement is complete, evaluate it
				if fileCmd, ok := cmd.(*fileLoadCmd); ok {
					r := strings.NewReader(fileCmd.queries)
					b := bufio.NewReader(r)
					getNextLineInFile := func(continued bool) (string, error) {
						s, err := b.ReadString('\n')
						return strings.TrimSuffix(s, "\n"), err
					}
					// continue as long as there is input
					path := fileCmd.filePath
					fmt.Printf("-- process %s --\n", path)
					for a.readStartOfNextCommand(getNextLineInFile, false) {
					}
					fmt.Printf("-- end %s --\n", path)
					return
				}
				cmd.Eval(a.requester)
				return
			}
		}

		var err error
		input, err = getNextLine(true)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}
	}
}

// Run begins SensorBee command line tool to management SensorBee
// and execute BQL/UDF/UDTF statements.
func (a *App) Run(requester *client.Requester) {
	line := liner.NewLiner()
	defer line.Close()

	line.SetCompleter(func(line string) (c []string) {
		// set auto complete command, if need
		return
	})

	if f, err := os.Open(a.historyFn); err == nil {
		line.ReadHistory(f)
		f.Close()
	}

	a.requester = requester
	a.prompt(line)

	if f, err := os.Create(a.historyFn); err != nil {
		fmt.Fprintf(os.Stderr, "error writing history file: %v", err)
	} else {
		line.WriteHistory(f)
		f.Close()
	}
}

const (
	promptLineStart    = ">>> "
	promptLineContinue = "... "
)
<|endoftext|>


<commit_before>package controller

import (
	"strings"
	"sync"
)

//go:generate mockery -name=KubernetesResourceSupervisor -output=automock -outpkg=automock -case=underscore

// KubernetesResourceSupervisor validates if given Kubernetes resource can be modified by ServiceBindingUsage. If yes
// it can ensure that labels are present or deleted on previous validated resource.
type KubernetesResourceSupervisor interface {
	EnsureLabelsCreated(resourceNs, resourceName, usageName string, labels map[string]string) error
	EnsureLabelsDeleted(resourceNs, resourceName, usageName string) error
	GetInjectedLabels(resourceNs, resourceName, usageName string) (map[string]string, error)
}

// Kind represents Kubernetes Kind name
type Kind string

// TODO (pluggable SBU cleanup): consider removing normalized method
func (k Kind) normalized() string {
	return strings.ToLower(string(k))
}

// ResourceSupervisorAggregator aggregates all defined resources supervisors
type ResourceSupervisorAggregator struct {
	registered map[string]KubernetesResourceSupervisor
	mu         sync.RWMutex
}

// NewResourceSupervisorAggregator returns new instance of ResourceSupervisorAggregator
func NewResourceSupervisorAggregator() *ResourceSupervisorAggregator {
	return &ResourceSupervisorAggregator{
		registered: make(map[string]KubernetesResourceSupervisor),
	}
}

// Register adds new resource supervisor
func (f *ResourceSupervisorAggregator) Register(k Kind, supervisor KubernetesResourceSupervisor) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.registered[k.normalized()] = supervisor
	return nil
}

// Unregister removes resource supervisor
func (f *ResourceSupervisorAggregator) Unregister(k Kind) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	delete(f.registered, k.normalized())
	return nil
}

// Get returns supervisor for given kind
func (f *ResourceSupervisorAggregator) Get(k Kind) (KubernetesResourceSupervisor, error) {
	f.mu.RLock()
	defer f.mu.RUnlock()
	concreteSupervisor, exists := f.registered[k.normalized()]
	if !exists {
		return nil, NewNotFoundError("supervisor for kind %s was not found", k)
	}

	return concreteSupervisor, nil
}
<commit_msg>Delete ToLower from Kind registration (#189)<commit_after>package controller

import (
	"sync"
)

//go:generate mockery -name=KubernetesResourceSupervisor -output=automock -outpkg=automock -case=underscore

// KubernetesResourceSupervisor validates if given Kubernetes resource can be modified by ServiceBindingUsage. If yes
// it can ensure that labels are present or deleted on previous validated resource.
type KubernetesResourceSupervisor interface {
	EnsureLabelsCreated(resourceNs, resourceName, usageName string, labels map[string]string) error
	EnsureLabelsDeleted(resourceNs, resourceName, usageName string) error
	GetInjectedLabels(resourceNs, resourceName, usageName string) (map[string]string, error)
}

// Kind represents Kubernetes Kind name
type Kind string

// ResourceSupervisorAggregator aggregates all defined resources supervisors
type ResourceSupervisorAggregator struct {
	registered map[Kind]KubernetesResourceSupervisor
	mu         sync.RWMutex
}

// NewResourceSupervisorAggregator returns new instance of ResourceSupervisorAggregator
func NewResourceSupervisorAggregator() *ResourceSupervisorAggregator {
	return &ResourceSupervisorAggregator{
		registered: make(map[Kind]KubernetesResourceSupervisor),
	}
}

// Register adds new resource supervisor
func (f *ResourceSupervisorAggregator) Register(k Kind, supervisor KubernetesResourceSupervisor) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.registered[k] = supervisor
	return nil
}

// Unregister removes resource supervisor
func (f *ResourceSupervisorAggregator) Unregister(k Kind) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	delete(f.registered, k)
	return nil
}

// Get returns supervisor for given kind
func (f *ResourceSupervisorAggregator) Get(k Kind) (KubernetesResourceSupervisor, error) {
	f.mu.RLock()
	defer f.mu.RUnlock()
	concreteSupervisor, exists := f.registered[k]
	if !exists {
		return nil, NewNotFoundError("supervisor for kind %s was not found", k)
	}

	return concreteSupervisor, nil
}
<|endoftext|>


<commit_before>package mocknet

import (
	"container/list"
	"sync"

	process "github.com/jbenet/goprocess"
	ic "github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	ma "github.com/multiformats/go-multiaddr"
)

// conn represents one side's perspective of a
// live connection between two peers.
// it goes over a particular link.
type conn struct {
	notifLk sync.Mutex

	local  peer.ID
	remote peer.ID

	localAddr  ma.Multiaddr
	remoteAddr ma.Multiaddr

	localPrivKey ic.PrivKey
	remotePubKey ic.PubKey

	net     *peernet
	link    *link
	rconn   *conn // counterpart
	streams list.List
	proc    process.Process
	stat    network.Stat

	sync.RWMutex
}

func newConn(p process.Process, ln, rn *peernet, l *link, dir network.Direction) *conn {
	c := &conn{net: ln, link: l, proc: p}
	c.local = ln.peer
	c.remote = rn.peer
	c.stat = network.Stat{Direction: dir}

	c.localAddr = ln.ps.Addrs(ln.peer)[0]
	c.remoteAddr = rn.ps.Addrs(rn.peer)[0]

	c.localPrivKey = ln.ps.PrivKey(ln.peer)
	c.remotePubKey = rn.ps.PubKey(rn.peer)

	c.proc.AddChild(process.WithTeardown(c.teardown))
	return c
}

func (c *conn) Close() error {
	return c.proc.Close()
}

func (c *conn) teardown() error {
	for _, s := range c.allStreams() {
		s.Reset()
	}
	c.net.removeConn(c)

	c.notifLk.Lock()
	defer c.notifLk.Unlock()
	c.net.notifyAll(func(n network.Notifiee) {
		n.Disconnected(c.net, c)
	})
	return nil
}

func (c *conn) addStream(s *stream) {
	c.Lock()
	s.conn = c
	c.streams.PushBack(s)
	s.notifLk.Lock()
	defer s.notifLk.Unlock()
	c.Unlock()
	c.net.notifyAll(func(n network.Notifiee) {
		n.OpenedStream(c.net, s)
	})
}

func (c *conn) removeStream(s *stream) {
	c.Lock()
	for e := c.streams.Front(); e != nil; e = e.Next() {
		if s == e.Value {
			c.streams.Remove(e)
			break
		}
	}
	c.Unlock()

	s.notifLk.Lock()
	defer s.notifLk.Unlock()
	s.conn.net.notifyAll(func(n network.Notifiee) {
		n.ClosedStream(s.conn.net, s)
	})
}

func (c *conn) allStreams() []network.Stream {
	c.RLock()
	defer c.RUnlock()

	strs := make([]network.Stream, 0, c.streams.Len())
	for e := c.streams.Front(); e != nil; e = e.Next() {
		s := e.Value.(*stream)
		strs = append(strs, s)
	}
	return strs
}

func (c *conn) remoteOpenedStream(s *stream) {
	c.addStream(s)
	c.net.handleNewStream(s)
}

func (c *conn) openStream() *stream {
	sl, sr := newStreamPair()
	go c.rconn.remoteOpenedStream(sr)
	c.addStream(sl)
	return sl
}

func (c *conn) NewStream() (network.Stream, error) {
	log.Debugf("Conn.NewStreamWithProtocol: %s --> %s", c.local, c.remote)

	s := c.openStream()
	return s, nil
}

func (c *conn) GetStreams() []network.Stream {
	return c.allStreams()
}

// LocalMultiaddr is the Multiaddr on this side
func (c *conn) LocalMultiaddr() ma.Multiaddr {
	return c.localAddr
}

// LocalPeer is the Peer on our side of the connection
func (c *conn) LocalPeer() peer.ID {
	return c.local
}

// LocalPrivateKey is the private key of the peer on our side.
func (c *conn) LocalPrivateKey() ic.PrivKey {
	return c.localPrivKey
}

// RemoteMultiaddr is the Multiaddr on the remote side
func (c *conn) RemoteMultiaddr() ma.Multiaddr {
	return c.remoteAddr
}

// RemotePeer is the Peer on the remote side
func (c *conn) RemotePeer() peer.ID {
	return c.remote
}

// RemotePublicKey is the private key of the peer on our side.
func (c *conn) RemotePublicKey() ic.PubKey {
	return c.remotePubKey
}

// Stat returns metadata about the connection
func (c *conn) Stat() network.Stat {
	return c.stat
}
<commit_msg>fix(mock): fire disconnect notifications async like we do in the swarm<commit_after>package mocknet

import (
	"container/list"
	"sync"

	process "github.com/jbenet/goprocess"
	ic "github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	ma "github.com/multiformats/go-multiaddr"
)

// conn represents one side's perspective of a
// live connection between two peers.
// it goes over a particular link.
type conn struct {
	notifLk sync.Mutex

	local  peer.ID
	remote peer.ID

	localAddr  ma.Multiaddr
	remoteAddr ma.Multiaddr

	localPrivKey ic.PrivKey
	remotePubKey ic.PubKey

	net     *peernet
	link    *link
	rconn   *conn // counterpart
	streams list.List
	proc    process.Process
	stat    network.Stat

	sync.RWMutex
}

func newConn(p process.Process, ln, rn *peernet, l *link, dir network.Direction) *conn {
	c := &conn{net: ln, link: l, proc: p}
	c.local = ln.peer
	c.remote = rn.peer
	c.stat = network.Stat{Direction: dir}

	c.localAddr = ln.ps.Addrs(ln.peer)[0]
	c.remoteAddr = rn.ps.Addrs(rn.peer)[0]

	c.localPrivKey = ln.ps.PrivKey(ln.peer)
	c.remotePubKey = rn.ps.PubKey(rn.peer)

	c.proc.AddChild(process.WithTeardown(c.teardown))
	return c
}

func (c *conn) Close() error {
	return c.proc.Close()
}

func (c *conn) teardown() error {
	for _, s := range c.allStreams() {
		s.Reset()
	}
	c.net.removeConn(c)

	go func() {
		c.notifLk.Lock()
		defer c.notifLk.Unlock()
		c.net.notifyAll(func(n network.Notifiee) {
			n.Disconnected(c.net, c)
		})
	}()
	return nil
}

func (c *conn) addStream(s *stream) {
	c.Lock()
	s.conn = c
	c.streams.PushBack(s)
	s.notifLk.Lock()
	defer s.notifLk.Unlock()
	c.Unlock()
	c.net.notifyAll(func(n network.Notifiee) {
		n.OpenedStream(c.net, s)
	})
}

func (c *conn) removeStream(s *stream) {
	c.Lock()
	for e := c.streams.Front(); e != nil; e = e.Next() {
		if s == e.Value {
			c.streams.Remove(e)
			break
		}
	}
	c.Unlock()

	go func() {
		s.notifLk.Lock()
		defer s.notifLk.Unlock()
		s.conn.net.notifyAll(func(n network.Notifiee) {
			n.ClosedStream(s.conn.net, s)
		})
	}()
}

func (c *conn) allStreams() []network.Stream {
	c.RLock()
	defer c.RUnlock()

	strs := make([]network.Stream, 0, c.streams.Len())
	for e := c.streams.Front(); e != nil; e = e.Next() {
		s := e.Value.(*stream)
		strs = append(strs, s)
	}
	return strs
}

func (c *conn) remoteOpenedStream(s *stream) {
	c.addStream(s)
	c.net.handleNewStream(s)
}

func (c *conn) openStream() *stream {
	sl, sr := newStreamPair()
	go c.rconn.remoteOpenedStream(sr)
	c.addStream(sl)
	return sl
}

func (c *conn) NewStream() (network.Stream, error) {
	log.Debugf("Conn.NewStreamWithProtocol: %s --> %s", c.local, c.remote)

	s := c.openStream()
	return s, nil
}

func (c *conn) GetStreams() []network.Stream {
	return c.allStreams()
}

// LocalMultiaddr is the Multiaddr on this side
func (c *conn) LocalMultiaddr() ma.Multiaddr {
	return c.localAddr
}

// LocalPeer is the Peer on our side of the connection
func (c *conn) LocalPeer() peer.ID {
	return c.local
}

// LocalPrivateKey is the private key of the peer on our side.
func (c *conn) LocalPrivateKey() ic.PrivKey {
	return c.localPrivKey
}

// RemoteMultiaddr is the Multiaddr on the remote side
func (c *conn) RemoteMultiaddr() ma.Multiaddr {
	return c.remoteAddr
}

// RemotePeer is the Peer on the remote side
func (c *conn) RemotePeer() peer.ID {
	return c.remote
}

// RemotePublicKey is the private key of the peer on our side.
func (c *conn) RemotePublicKey() ic.PubKey {
	return c.remotePubKey
}

// Stat returns metadata about the connection
func (c *conn) Stat() network.Stat {
	return c.stat
}
<|endoftext|>


<commit_before>// Copyright 2020 The LUCI Authors.
//
// 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.

// TODO(crbug/1090540): remove after verification is done.
package rpc

import (
	"context"
	"net/http"
	"time"

	"google.golang.org/grpc/status"

	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/grpc/appstatus"
	"go.chromium.org/luci/grpc/prpc"
	"go.chromium.org/luci/server/auth"

	"go.chromium.org/luci/buildbucket/appengine/internal/search"
	"go.chromium.org/luci/buildbucket/appengine/model"
	pb "go.chromium.org/luci/buildbucket/proto"
)

// bbClientErr is tagged into errors caused by bb client creation.
var bbClientErr = errors.BoolTag{Key: errors.NewTagKey("BB client")}

type pyBuildBucketClient struct {
	client pb.BuildsClient
}

type searchResponseBundle struct {
	res *pb.SearchBuildsResponse
	err error
}

// newPyBBClient constructs a BuildBucket python client.
func newPyBBClient(ctx context.Context, host string) (*pyBuildBucketClient, error) {
	t, err := auth.GetRPCTransport(ctx, auth.AsCredentialsForwarder)
	if err != nil {
		return nil, errors.Annotate(err, "failed to get RPC transport to BB default service").Tag(bbClientErr).Err()
	}
	pClient := pb.NewBuildsPRPCClient(&prpc.Client{
		C:    &http.Client{Transport: t},
		Host: host,
	})
	return &pyBuildBucketClient{client: pClient}, nil
}

// verifySearch makes RPC calls to Py service, compares Py and Go results and returns Py results.
func verifySearch(ctx context.Context, req *pb.SearchBuildsRequest) (*pb.SearchBuildsResponse, error) {
	// create Py BB client.
	pyHost := "default-dot-cr-buildbucket.appspot.com"
	if ctx.Value("env") == "Dev" {
		pyHost = "default-dot-cr-buildbucket-dev.appspot.com"
	}
	pyClient, err := newPyBBClient(ctx, pyHost)
	if err != nil {
		return nil, err
	}

	// get Go results.
	goResCh := make(chan *searchResponseBundle)
	goCtx, cancelGoCtx := context.WithCancel(ctx)
	defer cancelGoCtx()
	go func() {
		defer close(goResCh)
		goRes, goErr := searchBuildsGoPath(goCtx, req)
		select {
		case <-goCtx.Done():
			logging.Debugf(goCtx, "search verification: go context is done. goCtx err: %v", goCtx.Err())
			return
		case goResCh <- &searchResponseBundle{res: goRes, err: goErr}:
		}
	}()

	// get Py results.
	logging.Debugf(ctx, "search verification: calling pyHost:%s", pyHost)
	pyRes, pyErr := pyClient.client.SearchBuilds(ctx, req)
	logging.Debugf(ctx, "search verification: successfully get Python response")

	// convert the gRPC error to the appstatus error.
	if pyErr != nil {
		if e, ok := status.FromError(pyErr); ok {
			pyErr = appstatus.Error(e.Code(), e.Message())
		} else {
			logging.Debugf(ctx, "search verification: not able to parse rpc errors from Py service - %v", pyErr)
		}
	}

	// compare search results.
	select {
	case goResBundle := <-goResCh:
		compareSearchResults(ctx, goResBundle, &searchResponseBundle{res: pyRes, err: pyErr})
	case <-time.After(5 * time.Second):
		cancelGoCtx()
	}
	return pyRes, pyErr
}

// compareSearchResults is to compareSearchResults py and go responses for SearchBuilds.
func compareSearchResults(ctx context.Context, goResBundle *searchResponseBundle, pyResBundle *searchResponseBundle) {
	if goResBundle == nil {
		logging.Warningf(ctx, "search verification: diff found - goResBundle is nil, while pyResBundle res is %s and err is %v", pyResBundle.res.String(), pyResBundle.err)
		return
	}
	// only compare error code as some go error messages are different from py.
	if goResBundle.err != nil || pyResBundle.err != nil {
		gStatus, gOk := appstatus.Get(goResBundle.err)
		pyStatus, pyOk := appstatus.Get(pyResBundle.err)
		if gOk && pyOk && gStatus.Code() == pyStatus.Code() {
			logging.Debugf(ctx, "search verification: no diff for err responses")
		} else {
			logging.Warningf(ctx, "search verification: diff found - err is not equal\n go - %v\n py - %v", goResBundle.err, pyResBundle.err)
		}
		return
	}

	var miss, extra []int64
	// using two-pointer algorithm, as build ids are sorted in ascending order.
	i, j := 0, 0
	for i < len(goResBundle.res.Builds) && j < len(pyResBundle.res.Builds) {
		if goResBundle.res.Builds[i].Id == pyResBundle.res.Builds[j].Id {
			i++
			j++
		} else if goResBundle.res.Builds[i].Id < pyResBundle.res.Builds[j].Id {
			extra = append(extra, goResBundle.res.Builds[i].Id)
			i++
		} else {
			miss = append(miss, pyResBundle.res.Builds[j].Id)
			j++
		}
	}
	for ; i < len(goResBundle.res.Builds); i++ {
		extra = append(extra, goResBundle.res.Builds[i].Id)
	}
	for ; j < len(pyResBundle.res.Builds); j++ {
		miss = append(miss, pyResBundle.res.Builds[i].Id)
	}

	if len(miss) == 0 && len(extra) == 0 {
		logging.Debugf(ctx, "search verification: no diff for normal responses")
	} else {
		logging.Warningf(ctx, "search verification: diff found - \n"+
			"Go is missing %v, and has extra %v \nids in Go: %v \nids in Py: %v",
			miss, extra, getBuildIDs(goResBundle.res.Builds), getBuildIDs(pyResBundle.res.Builds))
	}
}

func getBuildIDs(builds []*pb.Build) []int64 {
	ids := make([]int64, len(builds))
	for i, b := range builds {
		ids[i] = b.Id
	}
	return ids
}

// searchBuildsGoPath executes the normal Go search path.
// All code is copied from `SearchBuilds` function.
func searchBuildsGoPath(ctx context.Context, req *pb.SearchBuildsRequest) (*pb.SearchBuildsResponse, error) {
	logging.Debugf(ctx, "search verification: searching in Go")
	if err := validateSearch(req); err != nil {
		return nil, appstatus.BadRequest(err)
	}
	mask, err := getBuildsSubMask(req.GetFields())
	if err != nil {
		return nil, appstatus.BadRequest(errors.Annotate(err, "fields").Err())
	}

	rsp, err := search.NewQuery(req).Fetch(ctx)
	if err != nil {
		return nil, err
	}
	if err = model.LoadBuildBundles(ctx, rsp.Builds, mask); err != nil {
		return nil, err
	}
	return rsp, nil
}
<commit_msg>[buildbuck] Track time running on go SearchBuilds<commit_after>// Copyright 2020 The LUCI Authors.
//
// 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.

// TODO(crbug/1090540): remove after verification is done.
package rpc

import (
	"context"
	"net/http"
	"time"

	"google.golang.org/grpc/status"

	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/grpc/appstatus"
	"go.chromium.org/luci/grpc/prpc"
	"go.chromium.org/luci/server/auth"

	"go.chromium.org/luci/buildbucket/appengine/internal/search"
	"go.chromium.org/luci/buildbucket/appengine/model"
	pb "go.chromium.org/luci/buildbucket/proto"
)

// bbClientErr is tagged into errors caused by bb client creation.
var bbClientErr = errors.BoolTag{Key: errors.NewTagKey("BB client")}

type pyBuildBucketClient struct {
	client pb.BuildsClient
}

type searchResponseBundle struct {
	res *pb.SearchBuildsResponse
	err error
}

// newPyBBClient constructs a BuildBucket python client.
func newPyBBClient(ctx context.Context, host string) (*pyBuildBucketClient, error) {
	t, err := auth.GetRPCTransport(ctx, auth.AsCredentialsForwarder)
	if err != nil {
		return nil, errors.Annotate(err, "failed to get RPC transport to BB default service").Tag(bbClientErr).Err()
	}
	pClient := pb.NewBuildsPRPCClient(&prpc.Client{
		C:    &http.Client{Transport: t},
		Host: host,
	})
	return &pyBuildBucketClient{client: pClient}, nil
}

// verifySearch makes RPC calls to Py service, compares Py and Go results and returns Py results.
func verifySearch(ctx context.Context, req *pb.SearchBuildsRequest) (*pb.SearchBuildsResponse, error) {
	// create Py BB client.
	pyHost := "default-dot-cr-buildbucket.appspot.com"
	if ctx.Value("env") == "Dev" {
		pyHost = "default-dot-cr-buildbucket-dev.appspot.com"
	}
	pyClient, err := newPyBBClient(ctx, pyHost)
	if err != nil {
		return nil, err
	}

	// get Go results.
	goResCh := make(chan *searchResponseBundle)
	goCtx, cancelGoCtx := context.WithCancel(ctx)
	defer cancelGoCtx()
	go func() {
		defer close(goResCh)
		goRes, goErr := searchBuildsGoPath(goCtx, req)
		select {
		case <-goCtx.Done():
			logging.Debugf(goCtx, "search verification: go context is done. goCtx err: %v", goCtx.Err())
			return
		case goResCh <- &searchResponseBundle{res: goRes, err: goErr}:
		}
	}()

	// get Py results.
	logging.Debugf(ctx, "search verification: calling pyHost:%s", pyHost)
	pyStartedAt := clock.Now(ctx)
	pyRes, pyErr := pyClient.client.SearchBuilds(ctx, req)
	logDuration(ctx, pyStartedAt, "search verification: py RPC calls for SearchBuilds")

	// convert the gRPC error to the appstatus error.
	if pyErr != nil {
		if e, ok := status.FromError(pyErr); ok {
			pyErr = appstatus.Error(e.Code(), e.Message())
		} else {
			logging.Debugf(ctx, "search verification: not able to parse rpc errors from Py service - %v", pyErr)
		}
	}

	// compare search results.
	select {
	case goResBundle := <-goResCh:
		compareSearchResults(ctx, goResBundle, &searchResponseBundle{res: pyRes, err: pyErr})
	case <-time.After(5 * time.Second):
		cancelGoCtx()
	}
	return pyRes, pyErr
}

// compareSearchResults is to compareSearchResults py and go responses for SearchBuilds.
func compareSearchResults(ctx context.Context, goResBundle *searchResponseBundle, pyResBundle *searchResponseBundle) {
	if goResBundle == nil {
		logging.Warningf(ctx, "search verification: diff found - goResBundle is nil, while pyResBundle res is %s and err is %v", pyResBundle.res.String(), pyResBundle.err)
		return
	}
	// only compare error code as some go error messages are different from py.
	if goResBundle.err != nil || pyResBundle.err != nil {
		gStatus, gOk := appstatus.Get(goResBundle.err)
		pyStatus, pyOk := appstatus.Get(pyResBundle.err)
		if gOk && pyOk && gStatus.Code() == pyStatus.Code() {
			logging.Debugf(ctx, "search verification: no diff for err responses")
		} else {
			logging.Warningf(ctx, "search verification: diff found - err is not equal\n go - %v\n py - %v", goResBundle.err, pyResBundle.err)
		}
		return
	}

	var miss, extra []int64
	// using two-pointer algorithm, as build ids are sorted in ascending order.
	i, j := 0, 0
	for i < len(goResBundle.res.Builds) && j < len(pyResBundle.res.Builds) {
		if goResBundle.res.Builds[i].Id == pyResBundle.res.Builds[j].Id {
			i++
			j++
		} else if goResBundle.res.Builds[i].Id < pyResBundle.res.Builds[j].Id {
			extra = append(extra, goResBundle.res.Builds[i].Id)
			i++
		} else {
			miss = append(miss, pyResBundle.res.Builds[j].Id)
			j++
		}
	}
	for ; i < len(goResBundle.res.Builds); i++ {
		extra = append(extra, goResBundle.res.Builds[i].Id)
	}
	for ; j < len(pyResBundle.res.Builds); j++ {
		miss = append(miss, pyResBundle.res.Builds[i].Id)
	}

	if len(miss) == 0 && len(extra) == 0 {
		logging.Debugf(ctx, "search verification: no diff for normal responses")
	} else {
		logging.Warningf(ctx, "search verification: diff found - \n"+
			"Go is missing %v, and has extra %v \nids in Go: %v \nids in Py: %v",
			miss, extra, getBuildIDs(goResBundle.res.Builds), getBuildIDs(pyResBundle.res.Builds))
	}
}

func getBuildIDs(builds []*pb.Build) []int64 {
	ids := make([]int64, len(builds))
	for i, b := range builds {
		ids[i] = b.Id
	}
	return ids
}

// searchBuildsGoPath executes the normal Go search path.
// All code is copied from `SearchBuilds` function.
func searchBuildsGoPath(ctx context.Context, req *pb.SearchBuildsRequest) (*pb.SearchBuildsResponse, error) {
	startedAt := clock.Now(ctx)
	defer logDuration(ctx, startedAt, "search verification: Go path for SearchBuilds")
	logging.Debugf(ctx, "search verification: searching in Go")
	if err := validateSearch(req); err != nil {
		return nil, appstatus.BadRequest(err)
	}
	mask, err := getBuildsSubMask(req.GetFields())
	if err != nil {
		return nil, appstatus.BadRequest(errors.Annotate(err, "fields").Err())
	}

	rsp, err := search.NewQuery(req).Fetch(ctx)
	if err != nil {
		return nil, err
	}
	if err = model.LoadBuildBundles(ctx, rsp.Builds, mask); err != nil {
		return nil, err
	}
	return rsp, nil
}

func logDuration(ctx context.Context, start time.Time, msg string) {
	logging.Debugf(ctx, "%s took %s", msg, time.Since(start))
}
<|endoftext|>


<commit_before>package template

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"fmt"
	"io"
	"mime/multipart"
	"net/textproto"
	"strconv"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceCloudinitConfig() *schema.Resource {
	return &schema.Resource{
		Create: resourceCloudinitConfigCreate,
		Delete: resourceCloudinitConfigDelete,
		Exists: resourceCloudinitConfigExists,
		Read:   resourceCloudinitConfigRead,

		Schema: map[string]*schema.Schema{
			"part": &schema.Schema{
				Type:     schema.TypeList,
				Required: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"content_type": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
								value := v.(string)

								if _, supported := supportedContentTypes[value]; !supported {
									errors = append(errors, fmt.Errorf("Part has an unsupported content type: %s", v))
								}

								return
							},
						},
						"content": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"filename": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"merge_type": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},
			"gzip": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
				ForceNew: true,
			},
			"base64_encode": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
				ForceNew: true,
			},
			"rendered": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "rendered cloudinit configuration",
			},
		},
	}
}

func resourceCloudinitConfigCreate(d *schema.ResourceData, meta interface{}) error {
	rendered, err := renderCloudinitConfig(d)
	if err != nil {
		return err
	}

	d.Set("rendered", rendered)
	d.SetId(strconv.Itoa(hashcode.String(rendered)))
	return nil
}

func resourceCloudinitConfigDelete(d *schema.ResourceData, meta interface{}) error {
	d.SetId("")
	return nil
}

func resourceCloudinitConfigExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	rendered, err := renderCloudinitConfig(d)
	if err != nil {
		return false, err
	}

	return strconv.Itoa(hashcode.String(rendered)) == d.Id(), nil
}

func resourceCloudinitConfigRead(d *schema.ResourceData, meta interface{}) error {
	return nil
}

func renderCloudinitConfig(d *schema.ResourceData) (string, error) {
	gzipOutput := d.Get("gzip").(bool)
	base64Output := d.Get("base64_encode").(bool)

	partsValue, hasParts := d.GetOk("part")
	if !hasParts {
		return "", fmt.Errorf("No parts found in the cloudinit resource declaration")
	}

	cloudInitParts := make(cloudInitParts, len(partsValue.([]interface{})))
	for i, v := range partsValue.([]interface{}) {
		p := v.(map[string]interface{})

		part := cloudInitPart{}
		if p, ok := p["content_type"]; ok {
			part.ContentType = p.(string)
		}
		if p, ok := p["content"]; ok {
			part.Content = p.(string)
		}
		if p, ok := p["merge_type"]; ok {
			part.MergeType = p.(string)
		}
		if p, ok := p["filename"]; ok {
			part.Filename = p.(string)
		}
		cloudInitParts[i] = part
	}

	var buffer bytes.Buffer

	var err error
	if gzipOutput {
		gzipWriter := gzip.NewWriter(&buffer)
		err = renderPartsToWriter(cloudInitParts, gzipWriter)
		gzipWriter.Close()
	} else {
		err = renderPartsToWriter(cloudInitParts, &buffer)
	}
	if err != nil {
		return "", err
	}

	output := ""
	if base64Output {
		output = base64.StdEncoding.EncodeToString(buffer.Bytes())
	} else {
		output = buffer.String()
	}

	return output, nil
}

func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error {
	mimeWriter := multipart.NewWriter(writer)
	defer mimeWriter.Close()

	// we need to set the boundary explictly, otherwise the boundary is random
	// and this causes terraform to complain about the resource being different
	if err := mimeWriter.SetBoundary("MIMEBOUNDRY"); err != nil {
		return err
	}

	writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary())))

	for _, part := range parts {
		header := textproto.MIMEHeader{}
		if part.ContentType == "" {
			header.Set("Content-Type", "text/plain")
		} else {
			header.Set("Content-Type", part.ContentType)
		}

		if part.Filename != "" {
			header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, part.Filename))
		}

		if part.MergeType != "" {
			header.Set("X-Merge-Type", part.MergeType)
		}

		partWriter, err := mimeWriter.CreatePart(header)
		if err != nil {
			return err
		}

		_, err = partWriter.Write([]byte(part.Content))
		if err != nil {
			return err
		}
	}

	return nil
}

type cloudInitPart struct {
	ContentType string
	MergeType   string
	Filename    string
	Content     string
}

type cloudInitParts []cloudInitPart

// Support content types as specified by http://cloudinit.readthedocs.org/en/latest/topics/format.html
var supportedContentTypes = map[string]bool{
	"text/x-include-once-url":   true,
	"text/x-include-url":        true,
	"text/cloud-config-archive": true,
	"text/upstart-job":          true,
	"text/cloud-config":         true,
	"text/part-handler":         true,
	"text/x-shellscript":        true,
	"text/cloud-boothook":       true,
}
<commit_msg>Add headers to output<commit_after>package template

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"fmt"
	"io"
	"mime/multipart"
	"net/textproto"
	"strconv"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceCloudinitConfig() *schema.Resource {
	return &schema.Resource{
		Create: resourceCloudinitConfigCreate,
		Delete: resourceCloudinitConfigDelete,
		Exists: resourceCloudinitConfigExists,
		Read:   resourceCloudinitConfigRead,

		Schema: map[string]*schema.Schema{
			"part": &schema.Schema{
				Type:     schema.TypeList,
				Required: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"content_type": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
								value := v.(string)

								if _, supported := supportedContentTypes[value]; !supported {
									errors = append(errors, fmt.Errorf("Part has an unsupported content type: %s", v))
								}

								return
							},
						},
						"content": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"filename": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"merge_type": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},
			"gzip": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
				ForceNew: true,
			},
			"base64_encode": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
				ForceNew: true,
			},
			"rendered": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "rendered cloudinit configuration",
			},
		},
	}
}

func resourceCloudinitConfigCreate(d *schema.ResourceData, meta interface{}) error {
	rendered, err := renderCloudinitConfig(d)
	if err != nil {
		return err
	}

	d.Set("rendered", rendered)
	d.SetId(strconv.Itoa(hashcode.String(rendered)))
	return nil
}

func resourceCloudinitConfigDelete(d *schema.ResourceData, meta interface{}) error {
	d.SetId("")
	return nil
}

func resourceCloudinitConfigExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	rendered, err := renderCloudinitConfig(d)
	if err != nil {
		return false, err
	}

	return strconv.Itoa(hashcode.String(rendered)) == d.Id(), nil
}

func resourceCloudinitConfigRead(d *schema.ResourceData, meta interface{}) error {
	return nil
}

func renderCloudinitConfig(d *schema.ResourceData) (string, error) {
	gzipOutput := d.Get("gzip").(bool)
	base64Output := d.Get("base64_encode").(bool)

	partsValue, hasParts := d.GetOk("part")
	if !hasParts {
		return "", fmt.Errorf("No parts found in the cloudinit resource declaration")
	}

	cloudInitParts := make(cloudInitParts, len(partsValue.([]interface{})))
	for i, v := range partsValue.([]interface{}) {
		p := v.(map[string]interface{})

		part := cloudInitPart{}
		if p, ok := p["content_type"]; ok {
			part.ContentType = p.(string)
		}
		if p, ok := p["content"]; ok {
			part.Content = p.(string)
		}
		if p, ok := p["merge_type"]; ok {
			part.MergeType = p.(string)
		}
		if p, ok := p["filename"]; ok {
			part.Filename = p.(string)
		}
		cloudInitParts[i] = part
	}

	var buffer bytes.Buffer

	var err error
	if gzipOutput {
		gzipWriter := gzip.NewWriter(&buffer)
		err = renderPartsToWriter(cloudInitParts, gzipWriter)
		gzipWriter.Close()
	} else {
		err = renderPartsToWriter(cloudInitParts, &buffer)
	}
	if err != nil {
		return "", err
	}

	output := ""
	if base64Output {
		output = base64.StdEncoding.EncodeToString(buffer.Bytes())
	} else {
		output = buffer.String()
	}

	return output, nil
}

func renderPartsToWriter(parts cloudInitParts, writer io.Writer) error {
	mimeWriter := multipart.NewWriter(writer)
	defer mimeWriter.Close()

	// we need to set the boundary explictly, otherwise the boundary is random
	// and this causes terraform to complain about the resource being different
	if err := mimeWriter.SetBoundary("MIMEBOUNDRY"); err != nil {
		return err
	}

	writer.Write([]byte(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\n", mimeWriter.Boundary())))
	writer.Write([]byte("MIME-Version: 1.0\r\n"))

	for _, part := range parts {
		header := textproto.MIMEHeader{}
		if part.ContentType == "" {
			header.Set("Content-Type", "text/plain")
		} else {
			header.Set("Content-Type", part.ContentType)
		}

		header.Set("MIME-Version", "1.0")
		header.Set("Content-Transfer-Encoding", "7bit")

		if part.Filename != "" {
			header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, part.Filename))
		}

		if part.MergeType != "" {
			header.Set("X-Merge-Type", part.MergeType)
		}

		partWriter, err := mimeWriter.CreatePart(header)
		if err != nil {
			return err
		}

		_, err = partWriter.Write([]byte(part.Content))
		if err != nil {
			return err
		}
	}

	return nil
}

type cloudInitPart struct {
	ContentType string
	MergeType   string
	Filename    string
	Content     string
}

type cloudInitParts []cloudInitPart

// Support content types as specified by http://cloudinit.readthedocs.org/en/latest/topics/format.html
var supportedContentTypes = map[string]bool{
	"text/x-include-once-url":   true,
	"text/x-include-url":        true,
	"text/cloud-config-archive": true,
	"text/upstart-job":          true,
	"text/cloud-config":         true,
	"text/part-handler":         true,
	"text/x-shellscript":        true,
	"text/cloud-boothook":       true,
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"log"
	"strconv"

	"github.com/go-openapi/runtime/middleware"
	"github.com/go-openapi/strfmt"
	"github.com/influxdata/mrfusion"
	"github.com/influxdata/mrfusion/models"
	"golang.org/x/net/context"

	op "github.com/influxdata/mrfusion/restapi/operations"
)

func (h *Store) Explorations(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	mrExs, err := h.ExplorationStore.Query(ctx, mrfusion.UserID(uID))
	if err != nil {
		log.Printf("Error: Unknown response from store while querying UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unknown response from store while querying UserID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	exs := make([]*models.Exploration, len(mrExs))
	for _, e := range mrExs {
		rel := "self"
		href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID)
		exs = append(exs, &models.Exploration{
			Data:      e.Data,
			Name:      e.Name,
			UpdatedAt: strfmt.DateTime(e.UpdatedAt),
			CreatedAt: strfmt.DateTime(e.CreatedAt),
			Link: &models.Link{
				Rel:  &rel,
				Href: &href,
			},
		},
		)
	}
	res := &models.Explorations{
		Explorations: exs,
	}
	return op.NewGetSourcesIDUsersUserIDExplorationsOK().WithPayload(res)
}

func (h *Store) Exploration(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
	eID, err := strconv.Atoi(params.ExplorationID)
	if err != nil {
		log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID))
	if err != nil {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	if e.UserID != mrfusion.UserID(uID) {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	rel := "self"
	href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, eID)
	res := &models.Exploration{
		Name:      e.Name,
		Data:      e.Data,
		UpdatedAt: strfmt.DateTime(e.UpdatedAt),
		CreatedAt: strfmt.DateTime(e.CreatedAt),
		Link: &models.Link{
			Rel:  &rel,
			Href: &href,
		},
	}
	return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDOK().WithPayload(res)
}

func (h *Store) UpdateExploration(ctx context.Context, params op.PatchSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
	if params.Exploration == nil {
		log.Printf("Error: Exploration is nil")
		errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(400).WithPayload(errMsg)
	}

	eID, err := strconv.Atoi(params.ExplorationID)
	if err != nil {
		log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID))
	if err != nil || e.UserID != mrfusion.UserID(uID) {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	var ok bool
	if e.Data, ok = params.Exploration.Data.(string); !ok {
		log.Printf("Error: Exploration data is not a string")
		errMsg := &models.Error{Code: 400, Message: "Error: Exploration data is not a string"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(400).WithPayload(errMsg)
	}
	e.Name = params.Exploration.Name

	if err := h.ExplorationStore.Update(ctx, e); err != nil {
		log.Printf("Error: Failed to update Exploration: %v: %v", e, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Failed to update Exploration"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNoContent()
}

func (h *Store) NewExploration(ctx context.Context, params op.PostSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
	if params.Exploration == nil {
		log.Printf("Error: Exploration is nil")
		errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"}
		return op.NewPostSourcesIDUsersUserIDExplorationsDefault(400).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	// TODO: Check user if user exists.

	e := &mrfusion.Exploration{
		Name:   params.Exploration.Name,
		UserID: mrfusion.UserID(uID),
		Data:   params.Exploration.Data.(string),
	}

	e, err = h.ExplorationStore.Add(ctx, e)
	if err != nil {
		log.Printf("Error: Failed to save Exploration: %v: %v", e, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Failed to save Exploration"}
		return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	rel := "self"
	href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID)
	res := &models.Exploration{
		Name:      e.Name,
		Data:      e.Data,
		UpdatedAt: strfmt.DateTime(e.UpdatedAt),
		CreatedAt: strfmt.DateTime(e.CreatedAt),
		Link: &models.Link{
			Rel:  &rel,
			Href: &href,
		},
	}
	return op.NewPostSourcesIDUsersUserIDExplorationsCreated().WithLocation(href).WithPayload(res)

}

func (h *Store) DeleteExploration(ctx context.Context, params op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
	eID, err := strconv.Atoi(params.ExplorationID)
	if err != nil {
		log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID))
	if err != nil || e.UserID != mrfusion.UserID(uID) {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	if err := h.ExplorationStore.Delete(ctx, &mrfusion.Exploration{ID: mrfusion.ExplorationID(eID)}); err != nil {
		log.Printf("Error: Failed to delete Exploration: %v: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Failed to delete Exploration"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}
	return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNoContent()
}
<commit_msg>Fix explorations returning null<commit_after>package handlers

import (
	"fmt"
	"log"
	"strconv"

	"github.com/go-openapi/runtime/middleware"
	"github.com/go-openapi/strfmt"
	"github.com/influxdata/mrfusion"
	"github.com/influxdata/mrfusion/models"
	"golang.org/x/net/context"

	op "github.com/influxdata/mrfusion/restapi/operations"
)

func (h *Store) Explorations(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	mrExs, err := h.ExplorationStore.Query(ctx, mrfusion.UserID(uID))
	if err != nil {
		log.Printf("Error: Unknown response from store while querying UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unknown response from store while querying UserID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	exs := make([]*models.Exploration, len(mrExs))
	for i, e := range mrExs {
		rel := "self"
		href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID)
		exs[i] = &models.Exploration{
			Data:      e.Data,
			Name:      e.Name,
			UpdatedAt: strfmt.DateTime(e.UpdatedAt),
			CreatedAt: strfmt.DateTime(e.CreatedAt),
			Link: &models.Link{
				Rel:  &rel,
				Href: &href,
			},
		}
	}
	res := &models.Explorations{
		Explorations: exs,
	}
	return op.NewGetSourcesIDUsersUserIDExplorationsOK().WithPayload(res)
}

func (h *Store) Exploration(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
	eID, err := strconv.Atoi(params.ExplorationID)
	if err != nil {
		log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID))
	if err != nil {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	if e.UserID != mrfusion.UserID(uID) {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	rel := "self"
	href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, eID)
	res := &models.Exploration{
		Name:      e.Name,
		Data:      e.Data,
		UpdatedAt: strfmt.DateTime(e.UpdatedAt),
		CreatedAt: strfmt.DateTime(e.CreatedAt),
		Link: &models.Link{
			Rel:  &rel,
			Href: &href,
		},
	}
	return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDOK().WithPayload(res)
}

func (h *Store) UpdateExploration(ctx context.Context, params op.PatchSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
	if params.Exploration == nil {
		log.Printf("Error: Exploration is nil")
		errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(400).WithPayload(errMsg)
	}

	eID, err := strconv.Atoi(params.ExplorationID)
	if err != nil {
		log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID))
	if err != nil || e.UserID != mrfusion.UserID(uID) {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	var ok bool
	if e.Data, ok = params.Exploration.Data.(string); !ok {
		log.Printf("Error: Exploration data is not a string")
		errMsg := &models.Error{Code: 400, Message: "Error: Exploration data is not a string"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(400).WithPayload(errMsg)
	}
	e.Name = params.Exploration.Name

	if err := h.ExplorationStore.Update(ctx, e); err != nil {
		log.Printf("Error: Failed to update Exploration: %v: %v", e, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Failed to update Exploration"}
		return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNoContent()
}

func (h *Store) NewExploration(ctx context.Context, params op.PostSourcesIDUsersUserIDExplorationsParams) middleware.Responder {
	if params.Exploration == nil {
		log.Printf("Error: Exploration is nil")
		errMsg := &models.Error{Code: 400, Message: "Error: Exploration is nil"}
		return op.NewPostSourcesIDUsersUserIDExplorationsDefault(400).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	// TODO: Check user if user exists.

	e := &mrfusion.Exploration{
		Name:   params.Exploration.Name,
		UserID: mrfusion.UserID(uID),
		Data:   params.Exploration.Data.(string),
	}

	e, err = h.ExplorationStore.Add(ctx, e)
	if err != nil {
		log.Printf("Error: Failed to save Exploration: %v: %v", e, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Failed to save Exploration"}
		return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500).WithPayload(errMsg)
	}

	rel := "self"
	href := fmt.Sprintf("/chronograf/v1/sources/1/users/%d/explorations/%d", uID, e.ID)
	res := &models.Exploration{
		Name:      e.Name,
		Data:      e.Data,
		UpdatedAt: strfmt.DateTime(e.UpdatedAt),
		CreatedAt: strfmt.DateTime(e.CreatedAt),
		Link: &models.Link{
			Rel:  &rel,
			Href: &href,
		},
	}
	return op.NewPostSourcesIDUsersUserIDExplorationsCreated().WithLocation(href).WithPayload(res)

}

func (h *Store) DeleteExploration(ctx context.Context, params op.DeleteSourcesIDUsersUserIDExplorationsExplorationIDParams) middleware.Responder {
	eID, err := strconv.Atoi(params.ExplorationID)
	if err != nil {
		log.Printf("Error: Unable to convert ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert ExplorationID"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	uID, err := strconv.Atoi(params.UserID)
	if err != nil {
		log.Printf("Error: Unable to convert UserID: %s: %v", params.UserID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Unable to convert UserID"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}

	e, err := h.ExplorationStore.Get(ctx, mrfusion.ExplorationID(eID))
	if err != nil || e.UserID != mrfusion.UserID(uID) {
		log.Printf("Error: Unknown ExplorationID: %s: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 404, Message: "Error: Unknown ExplorationID"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg)
	}

	if err := h.ExplorationStore.Delete(ctx, &mrfusion.Exploration{ID: mrfusion.ExplorationID(eID)}); err != nil {
		log.Printf("Error: Failed to delete Exploration: %v: %v", params.ExplorationID, err)
		errMsg := &models.Error{Code: 500, Message: "Error: Failed to delete Exploration"}
		return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg)
	}
	return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNoContent()
}
<|endoftext|>


<commit_before>// Package gpio implements a timer/counter unit.
// Tested compatibility:
//   ATmega48/88/168
// Untested compatability:
//   ATtiny4/5/9/10
package timer

import (
    "fmt"
    "github.com/kierdavis/avr/emulator"
    "github.com/kierdavis/avr/hardware/gpio"
    "log"
)

// TODO: for a OSCx output, require corresponding DDR bit to be set to output

// TODO: thread-safety!!

// TODO: OCFy/TOV flags should not be cleared upon interrupt execution if the
// emulator's global interrupt enable flag is cleared

type Timer struct {
    em *emulator.Emulator
    digit uint
    controlA uint8
    controlB uint8
    count uint8
    compareValA uint8
    compareValB uint8
    interruptMask uint8
    interruptFlags uint8
    downwards bool // count direction
    ocPinStates [2]bool
    ocPinCallbacks [2]func(bool)
    logging bool
    inhibitCompareMatch bool // set when TCNT is written to prevent a compare match on the next clock
    excessTicks uint
}

func New(digit uint) (t *Timer) {
    return &Timer{
        digit: digit,
    }
}

func (t *Timer) SetLogging(enabled bool) {
    t.logging = enabled
}

func (t *Timer) AddTo(em *emulator.Emulator) {
    t.em = em

    em.RegisterPortByName(fmt.Sprintf("TCCR%dA", t.digit), tccra{t})
    em.RegisterPortByName(fmt.Sprintf("TCCR%dB", t.digit), tccrb{t})
    em.RegisterPortByName(fmt.Sprintf("TCNT%d", t.digit), tcnt{t})
    em.RegisterPortByName(fmt.Sprintf("OCR%dA", t.digit), ocra{t})
    em.RegisterPortByName(fmt.Sprintf("OCR%dB", t.digit), ocrb{t})
    em.RegisterPortByName(fmt.Sprintf("TIMSK%d", t.digit), timsk{t})
    em.RegisterPortByName(fmt.Sprintf("TIFR%d", t.digit), tifr{t})
}

// Connect an output-compare pin to a GPIO port by calling the GPIO's
// OverrideOutput method.
func (t *Timer) OverrideOCPin(ocPinNum uint, gpioPinNum uint, g *gpio.GPIO) {
    t.ocPinCallbacks[ocPinNum] = g.OverrideOutput(gpioPinNum)
}

// Note: in PWM modes, OCRA/OCRB do not exhibit a newly written value until the count overflows

// TODO: in PC-PWM mode, an OCRx bit may transition without a compare match for two reasons (see datasheet page 98)

func (t *Timer) Run(ticks uint) {
    var ticksIncr uint

    switch t.controlB & 0x07 {
    case 0: // disabled
        t.excessTicks = 0
        return
    case 1: // divider = 1
        ticksIncr = 1
    case 2: // divider = 8
        ticksIncr = 8
    case 3: // divider = 64
        ticksIncr = 64
    case 4: // divider = 256
        ticksIncr = 256
    case 5: // divider = 1024
        ticksIncr = 1024
    case 6, 7:
        panic("(*Timer).Run: external clock sources not implemented")
    }
    ticksExecuted := t.excessTicks

    for ticksExecuted < ticks {
        t.Tick()
        ticksExecuted += ticksIncr
    }

    t.excessTicks = ticksExecuted - ticks
}

// Tick the timer.
func (t *Timer) Tick() {
    // Get the waveform generation mode bits
    wgmA := t.controlA & 0x03 // bits 1 and 0
    wgmB := (t.controlB & 0x08) >> 1 // bit 3 (shifted to bit 2)
    wgm := wgmB | wgmA

    // Handle match-compare interrupts
    if t.inhibitCompareMatch {
        t.inhibitCompareMatch = false
    } else {
        if t.count == t.compareValA {
            t.setOCF(0)
        }
        if t.count == t.compareValB {
            t.setOCF(1)
        }
    }

    // Prepare to tick counter
    switch wgm {
    case 0: // Normal
        t.tickNormalMode()
    case 1: // Phase-correct PWM (TOP = 0xFF)
        t.tickPCPWMMode(0xFF)
    case 2: // Clear timer on compare
        t.tickCTCMode()
    case 3: // Fast PWM (TOP = 0xFF)
        t.tickFastPWMMode(0xFF)
    case 5: // Phase-correct PWM (TOP = OCRA)
        t.tickPCPWMMode(t.compareValA)
    case 7: // Fast PWM (TOP = OCRA)
        t.tickPCPWMMode(t.compareValA)
    }

    // Actually tick the counter
    if t.downwards {
        t.count--
    } else {
        t.count++
    }

    // Trigger interrupts, if possible
    if t.em != nil && t.em.InterruptsEnabled() {
        intName := ""
        if t.interruptFlags & 0x01 != 0 && t.interruptMask & 0x01 != 0 {
            t.interruptFlags &= 0xFE // clear flag
            intName = fmt.Sprintf("TIMER%d_OVF", t.digit)
        } else if t.interruptFlags & 0x02 != 0 && t.interruptMask & 0x02 != 0 {
            t.interruptFlags &= 0xFD // clear flag
            intName = fmt.Sprintf("TIMER%d_COMPA", t.digit)
        } else if t.interruptFlags & 0x04 != 0 && t.interruptMask & 0x04 != 0 {
            t.interruptFlags &= 0xFB // clear flag
            intName = fmt.Sprintf("TIMER%d_COMPB", t.digit)
        }

        if intName != "" {
            ok := t.em.InterruptByName(intName)
            if !ok && t.logging {
                log.Printf("[avr/hardware/timer:(*Timer).Tick] failed to trigger interrupt %s", intName)
            }
        }
    }
}

// Tick the timer in Normal mode.
func (t *Timer) tickNormalMode() {
    t.downwards = false
    if t.count == 0xFF { // Overflow
        t.setTOV()
    }

    t.checkOCPinNormalMode(0, t.compareValA)
    t.checkOCPinNormalMode(1, t.compareValB)
}

// Check for output-compare in normal mode.
func (t *Timer) checkOCPinNormalMode(ocPinNum uint, compareVal uint8) {
    if t.count == compareVal {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // toggle OCy
            t.toggleOCPin(ocPinNum)
        case 2: // clear OCy
            t.clearOCPin(ocPinNum)
        case 3: // set OCy
            t.setOCPin(ocPinNum)
        }
    }
}

// Tick the timer in phase-corrected PWM mode.
func (t *Timer) tickPCPWMMode(top uint8) {
    if t.downwards {
        if t.count == 0x00 { // Reached BOTTOM
            t.downwards = false // Begin counting upwards
            t.setTOV()
        }
    } else {
        if t.count == top { // Reached TOP
            t.downwards = true // Begin counting downwards
        }
    }

    t.checkOCPinPCPWMMode(0, t.compareValA)
    t.checkOCPinPCPWMMode(1, t.compareValB)
}

// TODO: there is a special case in the OC pin checking for both PWM modes when OCRy == TOP and COMxy1 is set (see datasheet page 101)

// Check for output-compare in phase-corrected PWM mode.
func (t *Timer) checkOCPinPCPWMMode(ocPinNum uint, compareVal uint8) {
    if t.count == compareVal {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // Toggle OCy (only on OC pin 0 with WGM2 bit set)
            if ocPinNum == 0 && (t.controlB & 0x80) != 0 {
                t.toggleOCPin(ocPinNum)
            }
        case 2: // Clear OCy if counting upwards or set OCy if counting downwards
            if t.downwards {
                t.setOCPin(ocPinNum)
            } else {
                t.clearOCPin(ocPinNum)
            }
        case 3: // Set OCy if counting upwards or clear OCy if counting downwards
            if t.downwards {
                t.clearOCPin(ocPinNum)
            } else {
                t.setOCPin(ocPinNum)
            }
        }
    }
}

// Tick the timer in clear-timer-on-compare mode.
func (t *Timer) tickCTCMode() {
    t.downwards = false

    if t.count == 0xFF { // Overflow
        t.setTOV()
    }

    if t.count == t.compareValA {
        // this tick should set counter to 0
        t.count = 0xFF
    }
}

// Tick the timer in fast PWM mode.
func (t *Timer) tickFastPWMMode(top uint8) {
    t.downwards = false

    if t.count == top {
        // this tick should set counter to 0
        t.count = 0xFF
        t.setTOV()
    }

    t.checkOCPinFastPWMMode(0, t.compareValA)
    t.checkOCPinFastPWMMode(1, t.compareValB)
}

// Check for output-compare in fast PWM mode.
func (t *Timer) checkOCPinFastPWMMode(ocPinNum uint, compareVal uint8) {
    // BOTTOM
    if t.count == 0x00 {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // Toggle OCy on compare match (only on OC pin 0 with WGM2 bit set)
            // do nothing
        case 2: // Clear OCy on compare match, set OCy at BOTTOM
            t.setOCPin(ocPinNum)
        case 3: // Set OCy on compare match, clear OCy at BOTTOM
            t.clearOCPin(ocPinNum)
        }
    }

    // Compare match
    if t.count == compareVal {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // Toggle OCy on compare match (only on OC pin 0 with WGM2 bit set)
            if ocPinNum == 0 && (t.controlB & 0x80) != 0 {
                t.toggleOCPin(ocPinNum)
            }
        case 2: // Clear OCy on compare match, set OCy at BOTTOM
            t.clearOCPin(ocPinNum)
        case 3: // Set OCy on compare match, clear OCy at BOTTOM
            t.setOCPin(ocPinNum)
        }
    }
}

// Toggle an output-compare pin.
func (t *Timer) toggleOCPin(ocPinNum uint) {
    t.ocPinStates[ocPinNum] = !t.ocPinStates[ocPinNum]
    t.updateOCPin(ocPinNum)
}

// Set an output-compare pin to low.
func (t *Timer) clearOCPin(ocPinNum uint) {
    if t.ocPinStates[ocPinNum] {
        t.ocPinStates[ocPinNum] = false
        t.updateOCPin(ocPinNum)
    }
}

// Set an output-compare pin to high.
func (t *Timer) setOCPin(ocPinNum uint) {
    if !t.ocPinStates[ocPinNum] {
        t.ocPinStates[ocPinNum] = true
        t.updateOCPin(ocPinNum)
    }
}

// Push the new status of an output-compare pin to the GPIO layer.
func (t *Timer) updateOCPin(ocPinNum uint) {
    callback := t.ocPinCallbacks[ocPinNum]
    if callback != nil {
        callback(t.ocPinStates[ocPinNum])
    }
}

// Set an output compare match (OCFy) flag.
func (t *Timer) setOCF(ocPinNum uint) {
    if ocPinNum == 0 { // A
        t.interruptFlags |= 0x02 // set flag
    } else { // B
        t.interruptFlags |= 0x04 // set flag
    }
}

// Set the timer overflow (TOV) flag.
func (t *Timer) setTOV() {
    t.interruptFlags |= 0x01 // set flag
}
<commit_msg>Update compatability comment<commit_after>// Package gpio implements a timer/counter unit.
// Tested compatibility:
//   ATmega48/88/168 (timer 0 only)
// Untested compatability:
//   ATmega48/88/168 (timers 1 and 2)
//   ATtiny4/5/9/10
package timer

import (
    "fmt"
    "github.com/kierdavis/avr/emulator"
    "github.com/kierdavis/avr/hardware/gpio"
    "log"
)

// TODO: for a OSCx output, require corresponding DDR bit to be set to output

// TODO: thread-safety!!

// TODO: OCFy/TOV flags should not be cleared upon interrupt execution if the
// emulator's global interrupt enable flag is cleared

type Timer struct {
    em *emulator.Emulator
    digit uint
    controlA uint8
    controlB uint8
    count uint8
    compareValA uint8
    compareValB uint8
    interruptMask uint8
    interruptFlags uint8
    downwards bool // count direction
    ocPinStates [2]bool
    ocPinCallbacks [2]func(bool)
    logging bool
    inhibitCompareMatch bool // set when TCNT is written to prevent a compare match on the next clock
    excessTicks uint
}

func New(digit uint) (t *Timer) {
    return &Timer{
        digit: digit,
    }
}

func (t *Timer) SetLogging(enabled bool) {
    t.logging = enabled
}

func (t *Timer) AddTo(em *emulator.Emulator) {
    t.em = em

    em.RegisterPortByName(fmt.Sprintf("TCCR%dA", t.digit), tccra{t})
    em.RegisterPortByName(fmt.Sprintf("TCCR%dB", t.digit), tccrb{t})
    em.RegisterPortByName(fmt.Sprintf("TCNT%d", t.digit), tcnt{t})
    em.RegisterPortByName(fmt.Sprintf("OCR%dA", t.digit), ocra{t})
    em.RegisterPortByName(fmt.Sprintf("OCR%dB", t.digit), ocrb{t})
    em.RegisterPortByName(fmt.Sprintf("TIMSK%d", t.digit), timsk{t})
    em.RegisterPortByName(fmt.Sprintf("TIFR%d", t.digit), tifr{t})
}

// Connect an output-compare pin to a GPIO port by calling the GPIO's
// OverrideOutput method.
func (t *Timer) OverrideOCPin(ocPinNum uint, gpioPinNum uint, g *gpio.GPIO) {
    t.ocPinCallbacks[ocPinNum] = g.OverrideOutput(gpioPinNum)
}

// Note: in PWM modes, OCRA/OCRB do not exhibit a newly written value until the count overflows

// TODO: in PC-PWM mode, an OCRx bit may transition without a compare match for two reasons (see datasheet page 98)

func (t *Timer) Run(ticks uint) {
    var ticksIncr uint

    switch t.controlB & 0x07 {
    case 0: // disabled
        t.excessTicks = 0
        return
    case 1: // divider = 1
        ticksIncr = 1
    case 2: // divider = 8
        ticksIncr = 8
    case 3: // divider = 64
        ticksIncr = 64
    case 4: // divider = 256
        ticksIncr = 256
    case 5: // divider = 1024
        ticksIncr = 1024
    case 6, 7:
        panic("(*Timer).Run: external clock sources not implemented")
    }
    ticksExecuted := t.excessTicks

    for ticksExecuted < ticks {
        t.Tick()
        ticksExecuted += ticksIncr
    }

    t.excessTicks = ticksExecuted - ticks
}

// Tick the timer.
func (t *Timer) Tick() {
    // Get the waveform generation mode bits
    wgmA := t.controlA & 0x03 // bits 1 and 0
    wgmB := (t.controlB & 0x08) >> 1 // bit 3 (shifted to bit 2)
    wgm := wgmB | wgmA

    // Handle match-compare interrupts
    if t.inhibitCompareMatch {
        t.inhibitCompareMatch = false
    } else {
        if t.count == t.compareValA {
            t.setOCF(0)
        }
        if t.count == t.compareValB {
            t.setOCF(1)
        }
    }

    // Prepare to tick counter
    switch wgm {
    case 0: // Normal
        t.tickNormalMode()
    case 1: // Phase-correct PWM (TOP = 0xFF)
        t.tickPCPWMMode(0xFF)
    case 2: // Clear timer on compare
        t.tickCTCMode()
    case 3: // Fast PWM (TOP = 0xFF)
        t.tickFastPWMMode(0xFF)
    case 5: // Phase-correct PWM (TOP = OCRA)
        t.tickPCPWMMode(t.compareValA)
    case 7: // Fast PWM (TOP = OCRA)
        t.tickPCPWMMode(t.compareValA)
    }

    // Actually tick the counter
    if t.downwards {
        t.count--
    } else {
        t.count++
    }

    // Trigger interrupts, if possible
    if t.em != nil && t.em.InterruptsEnabled() {
        intName := ""
        if t.interruptFlags & 0x01 != 0 && t.interruptMask & 0x01 != 0 {
            t.interruptFlags &= 0xFE // clear flag
            intName = fmt.Sprintf("TIMER%d_OVF", t.digit)
        } else if t.interruptFlags & 0x02 != 0 && t.interruptMask & 0x02 != 0 {
            t.interruptFlags &= 0xFD // clear flag
            intName = fmt.Sprintf("TIMER%d_COMPA", t.digit)
        } else if t.interruptFlags & 0x04 != 0 && t.interruptMask & 0x04 != 0 {
            t.interruptFlags &= 0xFB // clear flag
            intName = fmt.Sprintf("TIMER%d_COMPB", t.digit)
        }

        if intName != "" {
            ok := t.em.InterruptByName(intName)
            if !ok && t.logging {
                log.Printf("[avr/hardware/timer:(*Timer).Tick] failed to trigger interrupt %s", intName)
            }
        }
    }
}

// Tick the timer in Normal mode.
func (t *Timer) tickNormalMode() {
    t.downwards = false
    if t.count == 0xFF { // Overflow
        t.setTOV()
    }

    t.checkOCPinNormalMode(0, t.compareValA)
    t.checkOCPinNormalMode(1, t.compareValB)
}

// Check for output-compare in normal mode.
func (t *Timer) checkOCPinNormalMode(ocPinNum uint, compareVal uint8) {
    if t.count == compareVal {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // toggle OCy
            t.toggleOCPin(ocPinNum)
        case 2: // clear OCy
            t.clearOCPin(ocPinNum)
        case 3: // set OCy
            t.setOCPin(ocPinNum)
        }
    }
}

// Tick the timer in phase-corrected PWM mode.
func (t *Timer) tickPCPWMMode(top uint8) {
    if t.downwards {
        if t.count == 0x00 { // Reached BOTTOM
            t.downwards = false // Begin counting upwards
            t.setTOV()
        }
    } else {
        if t.count == top { // Reached TOP
            t.downwards = true // Begin counting downwards
        }
    }

    t.checkOCPinPCPWMMode(0, t.compareValA)
    t.checkOCPinPCPWMMode(1, t.compareValB)
}

// TODO: there is a special case in the OC pin checking for both PWM modes when OCRy == TOP and COMxy1 is set (see datasheet page 101)

// Check for output-compare in phase-corrected PWM mode.
func (t *Timer) checkOCPinPCPWMMode(ocPinNum uint, compareVal uint8) {
    if t.count == compareVal {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // Toggle OCy (only on OC pin 0 with WGM2 bit set)
            if ocPinNum == 0 && (t.controlB & 0x80) != 0 {
                t.toggleOCPin(ocPinNum)
            }
        case 2: // Clear OCy if counting upwards or set OCy if counting downwards
            if t.downwards {
                t.setOCPin(ocPinNum)
            } else {
                t.clearOCPin(ocPinNum)
            }
        case 3: // Set OCy if counting upwards or clear OCy if counting downwards
            if t.downwards {
                t.clearOCPin(ocPinNum)
            } else {
                t.setOCPin(ocPinNum)
            }
        }
    }
}

// Tick the timer in clear-timer-on-compare mode.
func (t *Timer) tickCTCMode() {
    t.downwards = false

    if t.count == 0xFF { // Overflow
        t.setTOV()
    }

    if t.count == t.compareValA {
        // this tick should set counter to 0
        t.count = 0xFF
    }
}

// Tick the timer in fast PWM mode.
func (t *Timer) tickFastPWMMode(top uint8) {
    t.downwards = false

    if t.count == top {
        // this tick should set counter to 0
        t.count = 0xFF
        t.setTOV()
    }

    t.checkOCPinFastPWMMode(0, t.compareValA)
    t.checkOCPinFastPWMMode(1, t.compareValB)
}

// Check for output-compare in fast PWM mode.
func (t *Timer) checkOCPinFastPWMMode(ocPinNum uint, compareVal uint8) {
    // BOTTOM
    if t.count == 0x00 {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // Toggle OCy on compare match (only on OC pin 0 with WGM2 bit set)
            // do nothing
        case 2: // Clear OCy on compare match, set OCy at BOTTOM
            t.setOCPin(ocPinNum)
        case 3: // Set OCy on compare match, clear OCy at BOTTOM
            t.clearOCPin(ocPinNum)
        }
    }

    // Compare match
    if t.count == compareVal {
        // Get COMxy bits
        shiftAmt := 6 - 2*ocPinNum // 0 => 6, 1 => 4
        com := (t.controlA >> shiftAmt) & 0x03
        switch com {
        case 0: // OCy disabled
            // do nothing
        case 1: // Toggle OCy on compare match (only on OC pin 0 with WGM2 bit set)
            if ocPinNum == 0 && (t.controlB & 0x80) != 0 {
                t.toggleOCPin(ocPinNum)
            }
        case 2: // Clear OCy on compare match, set OCy at BOTTOM
            t.clearOCPin(ocPinNum)
        case 3: // Set OCy on compare match, clear OCy at BOTTOM
            t.setOCPin(ocPinNum)
        }
    }
}

// Toggle an output-compare pin.
func (t *Timer) toggleOCPin(ocPinNum uint) {
    t.ocPinStates[ocPinNum] = !t.ocPinStates[ocPinNum]
    t.updateOCPin(ocPinNum)
}

// Set an output-compare pin to low.
func (t *Timer) clearOCPin(ocPinNum uint) {
    if t.ocPinStates[ocPinNum] {
        t.ocPinStates[ocPinNum] = false
        t.updateOCPin(ocPinNum)
    }
}

// Set an output-compare pin to high.
func (t *Timer) setOCPin(ocPinNum uint) {
    if !t.ocPinStates[ocPinNum] {
        t.ocPinStates[ocPinNum] = true
        t.updateOCPin(ocPinNum)
    }
}

// Push the new status of an output-compare pin to the GPIO layer.
func (t *Timer) updateOCPin(ocPinNum uint) {
    callback := t.ocPinCallbacks[ocPinNum]
    if callback != nil {
        callback(t.ocPinStates[ocPinNum])
    }
}

// Set an output compare match (OCFy) flag.
func (t *Timer) setOCF(ocPinNum uint) {
    if ocPinNum == 0 { // A
        t.interruptFlags |= 0x02 // set flag
    } else { // B
        t.interruptFlags |= 0x04 // set flag
    }
}

// Set the timer overflow (TOV) flag.
func (t *Timer) setTOV() {
    t.interruptFlags |= 0x01 // set flag
}
<|endoftext|>