<commit_before>/*
 * Copyright (c) 2017 AlexRuzin (stan.ruzin@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package govfs

import (
    "testing"
    "time"
    "os"
    "io"
    _"bytes"
    "bytes"
)

const FS_DATABASE_FILE string = "test_db"

func TestIOSanity(t *testing.T) {
    /*
     * This test will generate the raw fs stream file, along with some contents
     *  that will be later loaded by the TestFSReader() method
     */
    out("[+] Running Standard I/O Sanity Test...")

    /* Remove the test database if it exists */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    if _, err := os.Stat(filename); os.IsExist(err) {
        os.Remove(filename)
    }

    var header = CreateDatabase(filename, 0)
    if header == nil {
        drive_fail("TEST1: Failed to obtain header", t)
    }
    out("[+] Test 1 PASS")

    // The root file "/" must at least exist
    if file, err := header.Create("/"); file != nil && err == nil {
        drive_fail("TEST2: Failed to return root handle", t)
    }
    out("[+] Test 2 PASS")

    /*
     * Try to delete the root file "/"
     */
    if header.Delete("/") == nil {
        drive_fail("TEST3: Cannot delete root -- critical", t)
    }
    out("[+] Test 3 PASS")

    /*
     * Attempt to write to a nonexistant file
     */
    var data = []byte{ 1, 2 }
    if header.Write("/folder5/folder5/file5", data) == nil {
        drive_fail("TEST4: Cannot write to a nonexistant file", t)
    }
    out("[+] Test 4 PASS")

    /*
     * Create empty file file9
     */
    if file, err := header.Create("/folder5/folder4/folder2/file9"); file == nil || err != nil {
        drive_fail("TEST4.1: file9 cannot be created", t)
    }
    out("[+] Test 4.1 PASS")

    /*
     * Attempt to create a new file0
     */
    if file, err := header.Create("/folder0/folder0/file0"); file == nil || err != nil {
        drive_fail("TEST5.0: file0 cannot be created", t)
    }
    out("[+] Test 5.0 PASS")

    /*
     * Attempt to create a new file0, this will fail since it should already exist
     */
    if file, err := header.Create("/folder0/folder0/file0"); file != nil && err == nil {
        drive_fail("TEST5.1: file0 cannot be created twice", t)
    }
    out("[+] Test 5.1 PASS")

    /*
     * Write some data into file0
     */
    data = []byte{ 1, 2, 3, 4 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST6: Failed to write data in file0", t)
    }
    out("[+] Test 6 PASS")

    /*
     * Check that the size of file0 is 4
     */
    if k, _ := header.get_file_size("/folder0/folder0/file0"); k != uint(len(data)) {
        drive_fail("TEST6.1: The size of data does not match", t)
    }
    out("[+] Test 6.1 PASS")

    /*
     * Attempt to create a new file3
     */
    if file, err := header.Create("/folder1/folder0/file3"); file == nil || err != nil {
        drive_fail("TEST7: file3 cannot be created", t)
    }
    out("[+] Test 7 PASS")

    /*
     * Write some data into file3
     */
    var data2 = []byte{ 1, 2, 3, 4, 5, 6, 7 }
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8: Failed to write data in file3", t)
    }
    out("[+] Test 8 PASS")

    /*
     * Write some data into file3
     */
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8.1: Failed to write data in file3", t)
    }
    out("[+] Test 8.1 PASS")

    /*
     * Read the written data from file0 and compare
     */
    output_data, _ := header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) || header.t_size - 7 /* len(file3) */ != uint(len(data)) {
        drive_fail("TEST9: Failed to read data from file0", t)
    }
    out("[+] Test 9 PASS")

    /*
     * Read the written data from file3 and compare
     */
    output_data, _ = header.Read("/folder1/folder0/file3")
    if output_data == nil || len(output_data) != len(data2) || header.t_size - 4 /* len(file0) */ != uint(len(data2)) {
        drive_fail("TEST10: Failed to read data from file3", t)
    }
    out("[+] Test 10 PASS")

    /*
     * Write other data to file0
     */
    data = []byte{ 1, 2, 3 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST11: Failed to write data in file1", t)
    }
    out("[+] Test 11 PASS")

    /*
     * Read the new data from file0
     */
    output_data, _ = header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) {
        drive_fail("TEST12: Failed to read data from file1", t)
    }
    out("[+] Test 12 PASS")

    /*
     * Attempt to create a new file5. This will be a blank file
     */
    if file, err := header.Create("/folder2/file7"); file == nil || err != nil {
        drive_fail("TEST13: file3 cannot be created", t)
    }
    out("[+] Test 13 PASS")

    /*
     * Delete file0 -- complete this
     */
    // FIXME/ADDME

    /*
     * Create just a folder
     */
    if file, err := header.Create("/folder2/file5/"); file == nil || err != nil {
        drive_fail("TEST15: folder file5 cannot be created", t)
    }
    out("[+] Test 15 PASS")

    /*
     * Tests the Reader interface
     */
    reader, err := header.NewReader("/folder0/folder0/file0")
    if err != nil {
        drive_fail("TEST15.1: Failed to create Reader", t)
    }

    file0data := make([]byte, reader.Len())
    data_read, err := reader.Read(file0data)
    if data_read != len(data) || err != io.EOF || bytes.Compare(file0data, data) != 0 {
        drive_fail("TEST15.2: Failed to read from NewReader", t)
    }
    out("[+] Test 15.1, 15.2 PASS")

    /*
     * Print out files
     */
    file_list := header.get_file_list()
    for _, e := range file_list {
        out(e)
    }

    /*
     * Unmount/commit database to file
     */
    if header.unmount_db() != nil {
        drive_fail("TEST16: Failed to commit database", t)
    }
    out("[+] Test 16 PASS. Raw FS stream written to: " + header.filename)

    time.Sleep(10000)
}

func TestFSReader(t *testing.T) {
    /*
     * Read in FS_DATABASE_FILE and do basic tests
     */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    out("[+] Loading Raw FS stream file: " + filename)

    /* Remove the test database if it exists */
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        drive_fail("error: Standard raw fs stream " + filename + " does not exist", t)
    }

    var header = CreateDatabase(filename, 0)
    if header == nil {
        drive_fail("TEST1: Failed to obtain header", t)
    }
    out("[+] Test 1 PASS (Loaded FS stream)")
}

func gen_raw_filename(suffix string) string {
    return os.Getenv("TEMP") + "\\" + suffix + ".db"
}

func drive_fail(output string, t *testing.T) {
    t.Errorf(output)
    t.FailNow()
}
<commit_msg>Reader() interface verifies that EOF is not returned when there is insufficient space in the input vector<commit_after>/*
 * Copyright (c) 2017 AlexRuzin (stan.ruzin@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package govfs

import (
    "testing"
    "time"
    "os"
    "io"
    _"bytes"
    "bytes"
)

const FS_DATABASE_FILE string = "test_db"

func TestIOSanity(t *testing.T) {
    /*
     * This test will generate the raw fs stream file, along with some contents
     *  that will be later loaded by the TestFSReader() method
     */
    out("[+] Running Standard I/O Sanity Test...")

    /* Remove the test database if it exists */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    if _, err := os.Stat(filename); os.IsExist(err) {
        os.Remove(filename)
    }

    var header = CreateDatabase(filename, 0)
    if header == nil {
        drive_fail("TEST1: Failed to obtain header", t)
    }
    out("[+] Test 1 PASS")

    // The root file "/" must at least exist
    if file, err := header.Create("/"); file != nil && err == nil {
        drive_fail("TEST2: Failed to return root handle", t)
    }
    out("[+] Test 2 PASS")

    /*
     * Try to delete the root file "/"
     */
    if header.Delete("/") == nil {
        drive_fail("TEST3: Cannot delete root -- critical", t)
    }
    out("[+] Test 3 PASS")

    /*
     * Attempt to write to a nonexistant file
     */
    var data = []byte{ 1, 2 }
    if header.Write("/folder5/folder5/file5", data) == nil {
        drive_fail("TEST4: Cannot write to a nonexistant file", t)
    }
    out("[+] Test 4 PASS")

    /*
     * Create empty file file9
     */
    if file, err := header.Create("/folder5/folder4/folder2/file9"); file == nil || err != nil {
        drive_fail("TEST4.1: file9 cannot be created", t)
    }
    out("[+] Test 4.1 PASS")

    /*
     * Attempt to create a new file0
     */
    if file, err := header.Create("/folder0/folder0/file0"); file == nil || err != nil {
        drive_fail("TEST5.0: file0 cannot be created", t)
    }
    out("[+] Test 5.0 PASS")

    /*
     * Attempt to create a new file0, this will fail since it should already exist
     */
    if file, err := header.Create("/folder0/folder0/file0"); file != nil && err == nil {
        drive_fail("TEST5.1: file0 cannot be created twice", t)
    }
    out("[+] Test 5.1 PASS")

    /*
     * Write some data into file0
     */
    data = []byte{ 1, 2, 3, 4 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST6: Failed to write data in file0", t)
    }
    out("[+] Test 6 PASS")

    /*
     * Check that the size of file0 is 4
     */
    if k, _ := header.get_file_size("/folder0/folder0/file0"); k != uint(len(data)) {
        drive_fail("TEST6.1: The size of data does not match", t)
    }
    out("[+] Test 6.1 PASS")

    /*
     * Attempt to create a new file3
     */
    if file, err := header.Create("/folder1/folder0/file3"); file == nil || err != nil {
        drive_fail("TEST7: file3 cannot be created", t)
    }
    out("[+] Test 7 PASS")

    /*
     * Write some data into file3
     */
    var data2 = []byte{ 1, 2, 3, 4, 5, 6, 7 }
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8: Failed to write data in file3", t)
    }
    out("[+] Test 8 PASS")

    /*
     * Write some data into file3
     */
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8.1: Failed to write data in file3", t)
    }
    out("[+] Test 8.1 PASS")

    /*
     * Read the written data from file0 and compare
     */
    output_data, _ := header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) || header.t_size - 7 /* len(file3) */ != uint(len(data)) {
        drive_fail("TEST9: Failed to read data from file0", t)
    }
    out("[+] Test 9 PASS")

    /*
     * Read the written data from file3 and compare
     */
    output_data, _ = header.Read("/folder1/folder0/file3")
    if output_data == nil || len(output_data) != len(data2) || header.t_size - 4 /* len(file0) */ != uint(len(data2)) {
        drive_fail("TEST10: Failed to read data from file3", t)
    }
    out("[+] Test 10 PASS")

    /*
     * Write other data to file0
     */
    data = []byte{ 1, 2, 3 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST11: Failed to write data in file1", t)
    }
    out("[+] Test 11 PASS")

    /*
     * Read the new data from file0
     */
    output_data, _ = header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) {
        drive_fail("TEST12: Failed to read data from file1", t)
    }
    out("[+] Test 12 PASS")

    /*
     * Attempt to create a new file5. This will be a blank file
     */
    if file, err := header.Create("/folder2/file7"); file == nil || err != nil {
        drive_fail("TEST13: file3 cannot be created", t)
    }
    out("[+] Test 13 PASS")

    /*
     * Delete file0 -- complete this
     */
    // FIXME/ADDME

    /*
     * Create just a folder
     */
    if file, err := header.Create("/folder2/file5/"); file == nil || err != nil {
        drive_fail("TEST15: folder file5 cannot be created", t)
    }
    out("[+] Test 15 PASS")

    /*
     * Tests the Reader interface
     */
    reader, err := header.NewReader("/folder0/folder0/file0")
    if err != nil {
        drive_fail("TEST15.1: Failed to create Reader", t)
    }

    file0data := make([]byte, 3)
    data_read, err := reader.Read(file0data)
    if data_read != len(data) || err != io.EOF || bytes.Compare(file0data, data) != 0 {
        drive_fail("TEST15.2: Failed to read from NewReader", t)
    }

    /* Test the reader interface again */
    file0data = make([]byte, 1)
    data_read, err = reader.Read(file0data)
    if data_read != 1 || err != nil || file0data[0] != 1 {
        drive_fail("TEST15.3: Invalid Reader interface behaviour", t)
    }
    out("[+] Test 15.1, 15.2, 15.3 PASS")

    /*
     * Print out files
     */
    file_list := header.get_file_list()
    for _, e := range file_list {
        out(e)
    }

    /*
     * Unmount/commit database to file
     */
    if err := header.unmount_db(); err != nil {
        drive_fail("TEST16: Failed to commit database", t)
    }
    out("[+] Test 16 PASS. Raw FS stream written to: " + header.filename)

    time.Sleep(10000)
}

func TestFSReader(t *testing.T) {
    /*
     * Read in FS_DATABASE_FILE and do basic tests
     */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    out("[+] Loading Raw FS stream file: " + filename)

    /* Remove the test database if it exists */
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        drive_fail("error: Standard raw fs stream " + filename + " does not exist", t)
    }

    var header = CreateDatabase(filename, 0)
    if header == nil {
        drive_fail("TEST1: Failed to obtain header", t)
    }
    out("[+] Test 1 PASS (Loaded FS stream)")
}

func gen_raw_filename(suffix string) string {
    return os.Getenv("TEMP") + "\\" + suffix + ".db"
}

func drive_fail(output string, t *testing.T) {
    t.Errorf(output)
    t.FailNow()
}
<|endoftext|>


<commit_before>package dfm

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"

	"github.com/benjamincaldwell/dfm/utilities"
	"github.com/benjamincaldwell/go-printer"
	"github.com/benjamincaldwell/go-sh"
	"github.com/spf13/afero"
	"github.com/urfave/cli"
)

var (
	dryRun        bool
	verbose       bool
	force         bool
	overwrite     bool
	noDfmrcCreate bool
	destDir       string

	// Version represents the current version of the dfm cli --> Set by build flags
	Version string
	// BuildDate is the date the current version was built --> Set by build flags
	BuildDate string

	// ErrNoHomeEnv is the error that is returned when HOME environment variable is not set
	ErrNoHomeEnv = errors.New("HOME environment variable not set")
	// ErrNoConfigFile is the error that is returned when the configuration file could not be found
	ErrNoConfigFile = errors.New("Could not find a configuration file")

	// Fs is the afero filesystem used when making file system manipulations
	Fs = afero.NewOsFs()

	defaultConfigFiles = []string{"dfm.yml", "$HOME/.dotfiles/dfm.yml", "$HOME/dotfiles/dfm.yml", "$HOME/dfm.yml", "$HOME/.dfm.yml"}
)

// set bootstrap env on clone???

// Execute kicks of dfm command
func Execute() {

	var configFile string
	var config *Configuration

	cli.VersionFlag = cli.BoolFlag{
		Name:  "version, V",
		Usage: "print only the version",
	}

	fmt.Printf("%+v\n", os.Args)

	app := cli.NewApp()
	app.Name = "dfm"
	app.Usage = "an easy way to manage dotfiles"

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:        "verbose, v",
			Usage:       "verbose output",
			Destination: &verbose,
		},
		cli.BoolFlag{
			Name:        "dryrun",
			Usage:       "prints changes",
			Destination: &dryRun,
		},
		cli.BoolFlag{
			Name:  "force, f",
			Usage: "force",
		},
		cli.BoolFlag{
			Name:  "overwrite",
			Usage: "overwrite existing files or folders when linking",
		},
		cli.BoolFlag{
			Name:        "noDfmrcCreate",
			Usage:       "",
			Destination: &noDfmrcCreate,
		},
		cli.StringFlag{
			Name:        "config, c",
			Usage:       "sets location of dfm config file or url with config file. Defaults to ~/.dfm.yml.",
			Destination: &configFile,
		},
		cli.StringFlag{
			Name:        "destdir",
			Usage:       "sets the destination directory. Overwrites destdir in configuration file",
			Destination: &destDir,
		},
	}

	app.Version = Version
	app.Author = "Benjamin Caldwell"

	app.Commands = []cli.Command{
		{
			Name:      "install",
			ShortName: "i",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				installAction(c.Args(), config)
			},
		},
		{
			Name:      "update",
			ShortName: "u",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				err := updateAction(c.Args(), config)
				utilities.ErrorCheck(err, "fetch updates")
			},
		},
		{
			Name:      "upgrade",
			ShortName: "up",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				err := updateAction(c.Args(), config)
				if utilities.ErrorCheck(err, "fetch updates") {
					return
				}
				installAction(c.Args(), config)
			},
		},
		{
			Name:      "compile",
			Aliases:   []string{"templates"},
			ShortName: "c",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)

			},
		},
		{
			Name:      "git",
			ShortName: "up",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				gitAction(c.Args(), config)
			},
		},
		{
			Name:      "path",
			ShortName: "up",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				fmt.Print(config.SrcDir)
			},
		},
	}

	if err := app.Run(os.Args); err != nil && !noDfmrcCreate {
		printer.Fail("Unexpected failure:", err)
		os.Exit(1)
	}

	// if config != nil {
	// 	createDfmrc(config.homeDir, config.configFile, config.SrcDir)
	// }
}

func setenv(config *Configuration) {
	os.Setenv("DFM_SRC_DIR", config.SrcDir)
	os.Setenv("DFM_DEST_DIR", config.DestDir)
	os.Setenv("DFM_REPO", config.Repo)
}

func getConfig(configFile string) (config *Configuration) {
	var err error
	config = &Configuration{}
	printer.Verbose = verbose
	sh.DryRun = dryRun

	printFlagOptions()

	config.configFile = configFile

	config.homeDir, err = detectHomeDir()
	utilities.FatalErrorCheck(err, "determining user's homeDir")

	config.configFile, err = detectConfigFile(config.configFile, config.homeDir)
	utilities.FatalErrorCheck(err, "determining configuration file")

	err = config.Parse(config.configFile)
	utilities.FatalErrorCheck(err, fmt.Sprintf("Unable to parse configuration file: %s", configFile))

	config.SetDefaults()
	if destDir != "" {
		config.DestDir = destDir
	}

	if _, err := Fs.Stat(config.SrcDir); os.IsNotExist(err) {
		err = cloneRepo(config.Repo, config.SrcDir)
		utilities.FatalErrorCheck(err, "Unable to clone desired repo")
	}
	setenv(config)
	return config
}

func detectHomeDir() (homeDir string, err error) {
	homeDir = os.Getenv("HOME")
	if homeDir == "" {
		err = ErrNoHomeEnv
	}
	return
}

func printFlagOptions() {
	printer.VerboseInfoBar("Running in verbose mode")
	if dryRun {
		printer.VerboseInfoBar("Running in dryrun mode")
	}
	if force {
		printer.VerboseInfoBar("Running in force mode")
	}
	if overwrite {
		printer.VerboseInfoBar("Running in overwrite mode")
	}
}

func detectConfigFile(configFileFlag, homeDir string) (configFile string, err error) {
	var configURL string
	rcFile := determineRcFile(homeDir)

	if configFileFlag == "" {
		if _, err := Fs.Stat(rcFile); err == nil {
			dat, err := ioutil.ReadFile(rcFile)
			utilities.FatalErrorCheck(err, "Couldn't read .dfmrc file")
			configFile = string(dat)
		} else {
			configFile, err = detectDefaultConfigFileLocation()
			printer.VerboseWarning("config file not specified. Defaulting to %s", configFile)
		}
	} else {
		if _, err := Fs.Stat(configFileFlag); os.IsNotExist(err) {
			r, _ := regexp.Compile(`(?i)^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$`)
			if r.MatchString(configFileFlag) {
				configURL = configFileFlag
				printer.VerboseInfoBar("Fetching config from %s", configURL)
				file, err := afero.TempFile(Fs, "", "dfm-")
				utilities.ErrorCheck(err, "creating temp file")
				defer Fs.Remove(file.Name())
				configFile = file.Name()

				err = utilities.DownloadToFile(configURL, configFile)
				utilities.ErrorCheck(err, "downloading configuration file")
			}
		} else {
			configFile = configFileFlag
		}
	}

	printer.VerboseInfoBar("Using configuration file located at %s", configFile)
	return configFile, err
}

func createDfmrc(homeDir, configFile, scrDir string) {
	// offer to create dfmrc file at the end if it doesnt exist
	rcFile := determineRcFile(homeDir)
	if _, err := Fs.Stat(rcFile); os.IsNotExist(err) {
		tempDir := afero.GetTempDir(Fs, "")
		if path.Dir(scrDir) != tempDir {
			configPrediction := path.Join(scrDir, "dfm.yml")
			if _, err := Fs.Stat(configPrediction); err == nil {
				configFile = configPrediction
			}
		}

		for _, file := range defaultConfigFiles {
			file = os.ExpandEnv(file)
			if path.Clean(path.Dir(scrDir)) == path.Clean(file) {
				return
			}
		}
		if path.Base(scrDir) == ".dotfiles" {
			return
		}
		fmt.Println()
		if utilities.AskForConfirmation("set default configuration to " + configFile + "?") {
			err := ioutil.WriteFile(rcFile, []byte(configFile), 0644)
			utilities.ErrorCheck(err, "writing ~/.dfmrc file")
		}
	}
}

func determineRcFile(homeDir string) string {
	return path.Join(homeDir, ".dfmrc")
}

func cloneRepo(repo, srcDir string) error {
	printer.VerboseInfoBar("cloning %s to %s", repo, srcDir)
	output, err := sh.Command("git", "clone", repo, srcDir).Output()
	if err != nil {
		printer.InfoBar(string(output))
		printer.Fail("Failed to clone repo %s with %s", repo, err)
	}
	return err
}

func detectDefaultConfigFileLocation() (string, error) {
	for _, file := range defaultConfigFiles {
		file = os.ExpandEnv(file)
		if _, err := Fs.Stat(file); err == nil {
			return file, nil
		}
	}
	return "", ErrNoConfigFile
}
<commit_msg>remove log v0.2.1<commit_after>package dfm

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"

	"github.com/benjamincaldwell/dfm/utilities"
	"github.com/benjamincaldwell/go-printer"
	"github.com/benjamincaldwell/go-sh"
	"github.com/spf13/afero"
	"github.com/urfave/cli"
)

var (
	dryRun        bool
	verbose       bool
	force         bool
	overwrite     bool
	noDfmrcCreate bool
	destDir       string

	// Version represents the current version of the dfm cli --> Set by build flags
	Version string
	// BuildDate is the date the current version was built --> Set by build flags
	BuildDate string

	// ErrNoHomeEnv is the error that is returned when HOME environment variable is not set
	ErrNoHomeEnv = errors.New("HOME environment variable not set")
	// ErrNoConfigFile is the error that is returned when the configuration file could not be found
	ErrNoConfigFile = errors.New("Could not find a configuration file")

	// Fs is the afero filesystem used when making file system manipulations
	Fs = afero.NewOsFs()

	defaultConfigFiles = []string{"dfm.yml", "$HOME/.dotfiles/dfm.yml", "$HOME/dotfiles/dfm.yml", "$HOME/dfm.yml", "$HOME/.dfm.yml"}
)

// set bootstrap env on clone???

// Execute kicks of dfm command
func Execute() {

	var configFile string
	var config *Configuration

	cli.VersionFlag = cli.BoolFlag{
		Name:  "version, V",
		Usage: "print only the version",
	}

	app := cli.NewApp()
	app.Name = "dfm"
	app.Usage = "an easy way to manage dotfiles"

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:        "verbose, v",
			Usage:       "verbose output",
			Destination: &verbose,
		},
		cli.BoolFlag{
			Name:        "dryrun",
			Usage:       "prints changes",
			Destination: &dryRun,
		},
		cli.BoolFlag{
			Name:  "force, f",
			Usage: "force",
		},
		cli.BoolFlag{
			Name:  "overwrite",
			Usage: "overwrite existing files or folders when linking",
		},
		cli.BoolFlag{
			Name:        "noDfmrcCreate",
			Usage:       "",
			Destination: &noDfmrcCreate,
		},
		cli.StringFlag{
			Name:        "config, c",
			Usage:       "sets location of dfm config file or url with config file. Defaults to ~/.dfm.yml.",
			Destination: &configFile,
		},
		cli.StringFlag{
			Name:        "destdir",
			Usage:       "sets the destination directory. Overwrites destdir in configuration file",
			Destination: &destDir,
		},
	}

	app.Version = Version
	app.Author = "Benjamin Caldwell"

	app.Commands = []cli.Command{
		{
			Name:      "install",
			ShortName: "i",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				installAction(c.Args(), config)
			},
		},
		{
			Name:      "update",
			ShortName: "u",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				err := updateAction(c.Args(), config)
				utilities.ErrorCheck(err, "fetch updates")
			},
		},
		{
			Name:      "upgrade",
			ShortName: "up",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				err := updateAction(c.Args(), config)
				if utilities.ErrorCheck(err, "fetch updates") {
					return
				}
				installAction(c.Args(), config)
			},
		},
		{
			Name:      "compile",
			Aliases:   []string{"templates"},
			ShortName: "c",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)

			},
		},
		{
			Name:      "git",
			ShortName: "up",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				gitAction(c.Args(), config)
			},
		},
		{
			Name:      "path",
			ShortName: "up",
			Usage:     "process each tasks and excuses them",
			Action: func(c *cli.Context) {
				config = getConfig(configFile)
				fmt.Print(config.SrcDir)
			},
		},
	}

	if err := app.Run(os.Args); err != nil && !noDfmrcCreate {
		printer.Fail("Unexpected failure:", err)
		os.Exit(1)
	}

	// if config != nil {
	// 	createDfmrc(config.homeDir, config.configFile, config.SrcDir)
	// }
}

func setenv(config *Configuration) {
	os.Setenv("DFM_SRC_DIR", config.SrcDir)
	os.Setenv("DFM_DEST_DIR", config.DestDir)
	os.Setenv("DFM_REPO", config.Repo)
}

func getConfig(configFile string) (config *Configuration) {
	var err error
	config = &Configuration{}
	printer.Verbose = verbose
	sh.DryRun = dryRun

	printFlagOptions()

	config.configFile = configFile

	config.homeDir, err = detectHomeDir()
	utilities.FatalErrorCheck(err, "determining user's homeDir")

	config.configFile, err = detectConfigFile(config.configFile, config.homeDir)
	utilities.FatalErrorCheck(err, "determining configuration file")

	err = config.Parse(config.configFile)
	utilities.FatalErrorCheck(err, fmt.Sprintf("Unable to parse configuration file: %s", configFile))

	config.SetDefaults()
	if destDir != "" {
		config.DestDir = destDir
	}

	if _, err := Fs.Stat(config.SrcDir); os.IsNotExist(err) {
		err = cloneRepo(config.Repo, config.SrcDir)
		utilities.FatalErrorCheck(err, "Unable to clone desired repo")
	}
	setenv(config)
	return config
}

func detectHomeDir() (homeDir string, err error) {
	homeDir = os.Getenv("HOME")
	if homeDir == "" {
		err = ErrNoHomeEnv
	}
	return
}

func printFlagOptions() {
	printer.VerboseInfoBar("Running in verbose mode")
	if dryRun {
		printer.VerboseInfoBar("Running in dryrun mode")
	}
	if force {
		printer.VerboseInfoBar("Running in force mode")
	}
	if overwrite {
		printer.VerboseInfoBar("Running in overwrite mode")
	}
}

func detectConfigFile(configFileFlag, homeDir string) (configFile string, err error) {
	var configURL string
	rcFile := determineRcFile(homeDir)

	if configFileFlag == "" {
		if _, err := Fs.Stat(rcFile); err == nil {
			dat, err := ioutil.ReadFile(rcFile)
			utilities.FatalErrorCheck(err, "Couldn't read .dfmrc file")
			configFile = string(dat)
		} else {
			configFile, err = detectDefaultConfigFileLocation()
			printer.VerboseWarning("config file not specified. Defaulting to %s", configFile)
		}
	} else {
		if _, err := Fs.Stat(configFileFlag); os.IsNotExist(err) {
			r, _ := regexp.Compile(`(?i)^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$`)
			if r.MatchString(configFileFlag) {
				configURL = configFileFlag
				printer.VerboseInfoBar("Fetching config from %s", configURL)
				file, err := afero.TempFile(Fs, "", "dfm-")
				utilities.ErrorCheck(err, "creating temp file")
				defer Fs.Remove(file.Name())
				configFile = file.Name()

				err = utilities.DownloadToFile(configURL, configFile)
				utilities.ErrorCheck(err, "downloading configuration file")
			}
		} else {
			configFile = configFileFlag
		}
	}

	printer.VerboseInfoBar("Using configuration file located at %s", configFile)
	return configFile, err
}

func createDfmrc(homeDir, configFile, scrDir string) {
	// offer to create dfmrc file at the end if it doesnt exist
	rcFile := determineRcFile(homeDir)
	if _, err := Fs.Stat(rcFile); os.IsNotExist(err) {
		tempDir := afero.GetTempDir(Fs, "")
		if path.Dir(scrDir) != tempDir {
			configPrediction := path.Join(scrDir, "dfm.yml")
			if _, err := Fs.Stat(configPrediction); err == nil {
				configFile = configPrediction
			}
		}

		for _, file := range defaultConfigFiles {
			file = os.ExpandEnv(file)
			if path.Clean(path.Dir(scrDir)) == path.Clean(file) {
				return
			}
		}
		if path.Base(scrDir) == ".dotfiles" {
			return
		}
		fmt.Println()
		if utilities.AskForConfirmation("set default configuration to " + configFile + "?") {
			err := ioutil.WriteFile(rcFile, []byte(configFile), 0644)
			utilities.ErrorCheck(err, "writing ~/.dfmrc file")
		}
	}
}

func determineRcFile(homeDir string) string {
	return path.Join(homeDir, ".dfmrc")
}

func cloneRepo(repo, srcDir string) error {
	printer.VerboseInfoBar("cloning %s to %s", repo, srcDir)
	output, err := sh.Command("git", "clone", repo, srcDir).Output()
	if err != nil {
		printer.InfoBar(string(output))
		printer.Fail("Failed to clone repo %s with %s", repo, err)
	}
	return err
}

func detectDefaultConfigFileLocation() (string, error) {
	for _, file := range defaultConfigFiles {
		file = os.ExpandEnv(file)
		if _, err := Fs.Stat(file); err == nil {
			return file, nil
		}
	}
	return "", ErrNoConfigFile
}
<|endoftext|>


<commit_before>package httpd

import (
	"bufio"
	"fmt"
	"github.com/Symantec/Dominator/lib/format"
	"github.com/Symantec/Dominator/lib/image"
	"io"
	"net/http"
)

var timeFormat string = "02 Jan 2006 15:04:05.99 MST"

func (s state) showImageHandler(w http.ResponseWriter, req *http.Request) {
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	imageName := req.URL.RawQuery
	fmt.Fprintf(writer, "<title>image %s</title>\n", imageName)
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "
")
	image := s.imageDataBase.GetImage(imageName)
	if image == nil {
		fmt.Fprintf(writer, "Image: %s UNKNOWN!\n", imageName)
		return
	}
	fmt.Fprintf(writer, "Information for image: %s
\n", imageName)
	fmt.Fprintln(writer, "
")
	fmt.Fprintf(writer, "Data size: %s
\n",
		imageName, format.FormatBytes(image.FileSystem.TotalDataBytes))
	fmt.Fprintf(writer, "Number of data inodes: %d
\n",
		image.FileSystem.NumRegularInodes)
	if numInodes := image.FileSystem.NumComputedRegularInodes(); numInodes > 0 {
		fmt.Fprintf(writer,
			"Number of computed inodes: %d
\n",
			imageName, numInodes)
	}
	if image.Filter == nil {
		fmt.Fprintln(writer, "Image has no filter: sparse image
")
	} else if len(image.Filter.FilterLines) < 1 {
		fmt.Fprintln(writer,
			"Filter has 0 lines (empty filter: full coverage)
")
	} else {
		fmt.Fprintf(writer,
			"Filter has %d lines
\n",
			imageName, len(image.Filter.FilterLines))
	}
	if image.Triggers == nil || len(image.Triggers.Triggers) < 1 {
		fmt.Fprintln(writer, "Image has no triggers
")
	} else {
		fmt.Fprintf(writer,
			"Number of triggers: %d
\n",
			imageName, len(image.Triggers.Triggers))
	}
	if !image.ExpiresAt.IsZero() {
		fmt.Fprintf(writer, "Expires at: %s
\n",
			image.ExpiresAt.Format(timeFormat))
	}
	showAnnotation(writer, image.ReleaseNotes, imageName, "Release notes",
		"listReleaseNotes")
	showAnnotation(writer, image.BuildLog, imageName, "Build log",
		"listBuildLog")
	if image.CreatedBy != "" {
		fmt.Fprintf(writer, "Created by: %s\n
", image.CreatedBy)
	}
	fmt.Fprintln(writer, "")
}

func showAnnotation(writer io.Writer, annotation *image.Annotation,
	imageName string, linkName string, baseURL string) {
	if annotation == nil {
		return
	}
	var url string
	if annotation.URL != "" {
		url = annotation.URL
	} else {
		url = baseURL + "?" + imageName
	}
	fmt.Fprintf(writer, "%s
\n", url, linkName)
}
<commit_msg>Show image expiry in local time zone.<commit_after>package httpd

import (
	"bufio"
	"fmt"
	"github.com/Symantec/Dominator/lib/format"
	"github.com/Symantec/Dominator/lib/image"
	"io"
	"net/http"
	"time"
)

var timeFormat string = "02 Jan 2006 15:04:05.99 MST"

func (s state) showImageHandler(w http.ResponseWriter, req *http.Request) {
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	imageName := req.URL.RawQuery
	fmt.Fprintf(writer, "<title>image %s</title>\n", imageName)
	fmt.Fprintln(writer, "")
	fmt.Fprintln(writer, "
")
	image := s.imageDataBase.GetImage(imageName)
	if image == nil {
		fmt.Fprintf(writer, "Image: %s UNKNOWN!\n", imageName)
		return
	}
	fmt.Fprintf(writer, "Information for image: %s
\n", imageName)
	fmt.Fprintln(writer, "
")
	fmt.Fprintf(writer, "Data size: %s
\n",
		imageName, format.FormatBytes(image.FileSystem.TotalDataBytes))
	fmt.Fprintf(writer, "Number of data inodes: %d
\n",
		image.FileSystem.NumRegularInodes)
	if numInodes := image.FileSystem.NumComputedRegularInodes(); numInodes > 0 {
		fmt.Fprintf(writer,
			"Number of computed inodes: %d
\n",
			imageName, numInodes)
	}
	if image.Filter == nil {
		fmt.Fprintln(writer, "Image has no filter: sparse image
")
	} else if len(image.Filter.FilterLines) < 1 {
		fmt.Fprintln(writer,
			"Filter has 0 lines (empty filter: full coverage)
")
	} else {
		fmt.Fprintf(writer,
			"Filter has %d lines
\n",
			imageName, len(image.Filter.FilterLines))
	}
	if image.Triggers == nil || len(image.Triggers.Triggers) < 1 {
		fmt.Fprintln(writer, "Image has no triggers
")
	} else {
		fmt.Fprintf(writer,
			"Number of triggers: %d
\n",
			imageName, len(image.Triggers.Triggers))
	}
	if !image.ExpiresAt.IsZero() {
		fmt.Fprintf(writer, "Expires at: %s
\n",
			image.ExpiresAt.In(time.Local).Format(timeFormat))
	}
	showAnnotation(writer, image.ReleaseNotes, imageName, "Release notes",
		"listReleaseNotes")
	showAnnotation(writer, image.BuildLog, imageName, "Build log",
		"listBuildLog")
	if image.CreatedBy != "" {
		fmt.Fprintf(writer, "Created by: %s\n
", image.CreatedBy)
	}
	fmt.Fprintln(writer, "")
}

func showAnnotation(writer io.Writer, annotation *image.Annotation,
	imageName string, linkName string, baseURL string) {
	if annotation == nil {
		return
	}
	var url string
	if annotation.URL != "" {
		url = annotation.URL
	} else {
		url = baseURL + "?" + imageName
	}
	fmt.Fprintf(writer, "%s
\n", url, linkName)
}
<|endoftext|>


<commit_before>package balancer

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

type mockIPTables struct {
	t *testing.T

	// Map from "table-name chain-name" to lists of rules
	chains map[string][][]string
}

var builtinChains = []string{
	"nat PREROUTING",
	"filter FORWARD",
	"filter INPUT",
}

func newMockIPTables(t *testing.T) mockIPTables {
	m := mockIPTables{
		t:      t,
		chains: make(map[string][][]string),
	}

	for _, c := range builtinChains {
		m.chains[c] = make([][]string, 0)
	}

	return m
}

type mockExitError bool

func (e mockExitError) Success() bool { return bool(e) }
func (e mockExitError) Error() string { return "mockExitError" }

func (m mockIPTables) key(args []string) string {
	return fmt.Sprintf("%s %s", args[1], args[3])
}

func (m mockIPTables) error(msg ...interface{}) ([]byte, error) {
	return ([]byte)(fmt.Sprint(msg)), mockExitError(false)
}

func (m mockIPTables) cmd(args []string) ([]byte, error) {
	require.True(m.t, len(args) >= 4)
	require.Equal(m.t, "-t", args[0])

	if len(args[2]) != 2 || args[2][0] != '-' ||
		!strings.ContainsRune("NXFIAD", rune(args[2][1])) {
		m.t.Log("Unknown iptables option ", args[2])
		m.t.Fail()
		return m.error("Unknown option ", args[2])
	}

	k := m.key(args)
	if args[2] == "-N" {
		if _, present := m.chains[k]; present {
			return m.error("iptables: Chain already exists.")
		}

		if len(args) > 4 {
			return m.error("Bad argument '", args[4], "'")
		}

		m.chains[k] = make([][]string, 0)
		return nil, nil
	}

	if _, present := m.chains[k]; !present {
		return m.error("iptables: No chain/target/match by that name.")
	}

	switch args[2] {
	case "-X":
		if len(args) > 4 {
			return m.error("Bad argument '", args[4], "'")
		}

		delete(m.chains, k)

	case "-F":
		if len(args) > 4 {
			return m.error("Bad argument '", args[4], "'")
		}

		m.chains[k] = m.chains[k][:0]

	case "-I":
		// no rulenum support needed for now

		m.chains[k] = append([][]string{args[4:]}, m.chains[k]...)

	case "-A":
		// no rulenum support needed for now

		m.chains[k] = append(m.chains[k], args[4:])

	case "-D":
		for i, r := range m.chains[k] {
			if reflect.DeepEqual(args[4:], r) {
				m.chains[k] = append(m.chains[k][:i], m.chains[k][i+1:]...)
				return nil, nil
			}
		}

		return m.error("iptables: Bad rule (does a matching rule exist in that chain?).")
	}

	return nil, nil
}
<commit_msg>Fix balancer tests<commit_after>package balancer

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

type mockIPTables struct {
	t *testing.T

	// Map from "table-name chain-name" to lists of rules
	chains map[string][][]string
}

var builtinChains = []string{
	"nat PREROUTING",
	"nat OUTPUT",
	"filter FORWARD",
	"filter INPUT",
}

func newMockIPTables(t *testing.T) mockIPTables {
	m := mockIPTables{
		t:      t,
		chains: make(map[string][][]string),
	}

	for _, c := range builtinChains {
		m.chains[c] = make([][]string, 0)
	}

	return m
}

type mockExitError bool

func (e mockExitError) Success() bool { return bool(e) }
func (e mockExitError) Error() string { return "mockExitError" }

func (m mockIPTables) key(args []string) string {
	return fmt.Sprintf("%s %s", args[1], args[3])
}

func (m mockIPTables) error(msg ...interface{}) ([]byte, error) {
	return ([]byte)(fmt.Sprint(msg)), mockExitError(false)
}

func (m mockIPTables) cmd(args []string) ([]byte, error) {
	require.True(m.t, len(args) >= 4)
	require.Equal(m.t, "-t", args[0])

	if len(args[2]) != 2 || args[2][0] != '-' ||
		!strings.ContainsRune("NXFIAD", rune(args[2][1])) {
		m.t.Log("Unknown iptables option ", args[2])
		m.t.Fail()
		return m.error("Unknown option ", args[2])
	}

	k := m.key(args)
	if args[2] == "-N" {
		if _, present := m.chains[k]; present {
			return m.error("iptables: Chain already exists.")
		}

		if len(args) > 4 {
			return m.error("Bad argument '", args[4], "'")
		}

		m.chains[k] = make([][]string, 0)
		return nil, nil
	}

	if _, present := m.chains[k]; !present {
		return m.error("iptables: No chain/target/match by that name.")
	}

	switch args[2] {
	case "-X":
		if len(args) > 4 {
			return m.error("Bad argument '", args[4], "'")
		}

		delete(m.chains, k)

	case "-F":
		if len(args) > 4 {
			return m.error("Bad argument '", args[4], "'")
		}

		m.chains[k] = m.chains[k][:0]

	case "-I":
		// no rulenum support needed for now

		m.chains[k] = append([][]string{args[4:]}, m.chains[k]...)

	case "-A":
		// no rulenum support needed for now

		m.chains[k] = append(m.chains[k], args[4:])

	case "-D":
		for i, r := range m.chains[k] {
			if reflect.DeepEqual(args[4:], r) {
				m.chains[k] = append(m.chains[k][:i], m.chains[k][i+1:]...)
				return nil, nil
			}
		}

		return m.error("iptables: Bad rule (does a matching rule exist in that chain?).")
	}

	return nil, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/cloud/pubsub"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

var (
	apiTokens []string
)

func init() {
	apiTokens = nil
	http.HandleFunc("/", postHandler)
}

func mustGetenv(ctx context.Context, k string) string {
	v := os.Getenv(k)
	if v == "" {
		log.Criticalf(ctx, "%s environment variable not set.", k)
	}
	return v
}

func verifyApiToken(token string) error {
	for _, x := range apiTokens {
		if x == token {
			return nil
		}
	}
	return errors.New("invalid api token.")
}

type Input struct {
	ApiToken string `json:"api_token"`
	Logs     []struct {
		Type       string      `json:"type"`
		Attributes interface{} `json:"attributes"`
	} `json:"logs"`
}

type Output struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

func postHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	output := Output{false, "something wrong."}
	code := 500

	defer func() {
		outjson, e := json.Marshal(output)
		if e != nil {
			log.Errorf(ctx, e.Error())
		}
		w.Header().Set("Content-Type", "application/json")
		if code == 200 {
			fmt.Fprint(w, string(outjson))
		} else {
			http.Error(w, string(outjson), code)
		}
	}()

	if r.Method != "POST" {
		output.Message = "only POST method method was accepted"
		code = 404
		return
	}

	body, e := ioutil.ReadAll(r.Body)
	if e != nil {
		output.Message = e.Error()
		return
	}
	input := Input{}
	e = json.Unmarshal(body, &input)
	if e != nil {
		output.Message = e.Error()
		log.Errorf(ctx, e.Error())
		code = 400
		return
	}

	if apiTokens == nil {
		apiTokens = strings.Split(mustGetenv(ctx, "API_TOKEN"), ",")
	}
	e = verifyApiToken(input.ApiToken)
	if e != nil {
		output.Message = e.Error()
		code = 401
		return
	}

	client, err := pubsub.NewClient(ctx, mustGetenv(ctx, "GCLOUD_PROJECT"))
	if err != nil {
		log.Criticalf(ctx, err.Error())
	}
	topic := client.Topic(mustGetenv(ctx, "PUBSUB_TOPIC"))

	for _, entry := range input.Logs {
		json, e := json.Marshal(entry)
		if e != nil {
			output.Message = e.Error()
			log.Errorf(ctx, e.Error())
			code = 400
			return
		}
		msg := &pubsub.Message{
			Data: []byte(json),
		}
		log.Infof(ctx, "publish data = %v", string(msg.Data))
		if _, err := topic.Publish(ctx, msg); err != nil {
			output.Message = fmt.Sprintf("Could not publish message: %v", err)
			code = 500
			return
		}
	}

	output.Success = true
	output.Message = "ok"
	code = 200
	return
}
<commit_msg>log publish error message.<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"google.golang.org/cloud/pubsub"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

var (
	apiTokens []string
)

func init() {
	apiTokens = nil
	http.HandleFunc("/", postHandler)
}

func mustGetenv(ctx context.Context, k string) string {
	v := os.Getenv(k)
	if v == "" {
		log.Criticalf(ctx, "%s environment variable not set.", k)
	}
	return v
}

func verifyApiToken(token string) error {
	for _, x := range apiTokens {
		if x == token {
			return nil
		}
	}
	return errors.New("invalid api token.")
}

type Input struct {
	ApiToken string `json:"api_token"`
	Logs     []struct {
		Type       string      `json:"type"`
		Attributes interface{} `json:"attributes"`
	} `json:"logs"`
}

type Output struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

func postHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	output := Output{false, "something wrong."}
	code := 500

	defer func() {
		outjson, e := json.Marshal(output)
		if e != nil {
			log.Errorf(ctx, e.Error())
		}
		w.Header().Set("Content-Type", "application/json")
		if code == 200 {
			fmt.Fprint(w, string(outjson))
		} else {
			http.Error(w, string(outjson), code)
		}
	}()

	if r.Method != "POST" {
		output.Message = "only POST method method was accepted"
		code = 404
		return
	}

	body, e := ioutil.ReadAll(r.Body)
	if e != nil {
		output.Message = e.Error()
		return
	}
	input := Input{}
	e = json.Unmarshal(body, &input)
	if e != nil {
		output.Message = e.Error()
		log.Errorf(ctx, e.Error())
		code = 400
		return
	}

	if apiTokens == nil {
		apiTokens = strings.Split(mustGetenv(ctx, "API_TOKEN"), ",")
	}
	e = verifyApiToken(input.ApiToken)
	if e != nil {
		output.Message = e.Error()
		code = 401
		return
	}

	client, err := pubsub.NewClient(ctx, mustGetenv(ctx, "GCLOUD_PROJECT"))
	if err != nil {
		log.Criticalf(ctx, err.Error())
	}
	topic := client.Topic(mustGetenv(ctx, "PUBSUB_TOPIC"))

	for _, entry := range input.Logs {
		json, e := json.Marshal(entry)
		if e != nil {
			output.Message = e.Error()
			log.Errorf(ctx, e.Error())
			code = 400
			return
		}
		msg := &pubsub.Message{
			Data: []byte(json),
		}
		log.Infof(ctx, "publish data = %v", string(msg.Data))
		if _, err := topic.Publish(ctx, msg); err != nil {
			output.Message = fmt.Sprintf("Could not publish message: %v", err)
			log.Errorf(ctx, "Could not publish message: %v", err)
			code = 500
			return
		}
	}

	output.Success = true
	output.Message = "ok"
	code = 200
	return
}
<|endoftext|>


<commit_before>// IPv4 DHCP Library for Parsing and Creating DHCP Packets, along with basic DHCP server functionality
//
// Author: http://richard.warburton.it/
//
// Copyright: 2014 Skagerrak Software - http://www.skagerraksoftware.com/
package dhcp4

import (
	"net"
	"time"
)

type Option struct {
	Code  OptionCode
	Value []byte
}
type OptionCode byte
type OpCode byte
type MessageType byte // Option 53

// A DHCP packet
type Packet []byte

func (p Packet) OpCode() OpCode { return OpCode(p[0]) }
func (p Packet) HType() byte    { return p[1] }
func (p Packet) HLen() byte     { return p[2] }
func (p Packet) Hops() byte     { return p[3] }
func (p Packet) XId() []byte    { return p[4:8] }
func (p Packet) Secs() []byte   { return p[8:10] } // Never Used?
func (p Packet) Flags() []byte  { return p[10:12] }
func (p Packet) CIAddr() net.IP { return net.IP(p[12:16]) }
func (p Packet) YIAddr() net.IP { return net.IP(p[16:20]) }
func (p Packet) SIAddr() net.IP { return net.IP(p[20:24]) }
func (p Packet) GIAddr() net.IP { return net.IP(p[24:28]) }
func (p Packet) CHAddr() net.HardwareAddr {
	hLen := p.HLen()
	if hLen > 16 { // Prevent chaddr exceeding p boundary
		hLen = 16
	}
	return net.HardwareAddr(p[28 : 28+hLen]) // max endPos 44
}

// 192 bytes of zeros BOOTP legacy

// BOOTP legacy
func (p Packet) SName() []byte { return trimNull(p[44:108]) }

// BOOTP legacy
func (p Packet) File() []byte { return trimNull(p[108:236]) }

func trimNull(d []byte) []byte {
	for i, v := range d {
		if v == 0 {
			return d[:i]
		}
	}
	return d
}

func (p Packet) Cookie() []byte { return p[236:240] }
func (p Packet) Options() []byte {
	if len(p) > 240 {
		return p[240:]
	}
	return nil
}

func (p Packet) Broadcast() bool { return p.Flags()[0] > 127 }

func (p Packet) SetBroadcast(broadcast bool) {
	if p.Broadcast() != broadcast {
		p.Flags()[0] ^= 128
	}
}

func (p Packet) SetOpCode(c OpCode) { p[0] = byte(c) }
func (p Packet) SetCHAddr(a net.HardwareAddr) {
	copy(p[28:44], a)
	p[2] = byte(len(a))
}
func (p Packet) SetHType(hType byte)     { p[1] = hType }
func (p Packet) SetCookie(cookie []byte) { copy(p.Cookie(), cookie) }
func (p Packet) SetHops(hops byte)       { p[3] = hops }
func (p Packet) SetXId(xId []byte)       { copy(p.XId(), xId) }
func (p Packet) SetSecs(secs []byte)     { copy(p.Secs(), secs) }
func (p Packet) SetFlags(flags []byte)   { copy(p.Flags(), flags) }
func (p Packet) SetCIAddr(ip net.IP)     { copy(p.CIAddr(), ip.To4()) }
func (p Packet) SetYIAddr(ip net.IP)     { copy(p.YIAddr(), ip.To4()) }
func (p Packet) SetSIAddr(ip net.IP)     { copy(p.SIAddr(), ip.To4()) }
func (p Packet) SetGIAddr(ip net.IP)     { copy(p.GIAddr(), ip.To4()) }

// BOOTP legacy
func (p Packet) SetSName(sName []byte) {
	copy(p[44:108], sName)
	if len(sName) < 64 {
		p[44+len(sName)] = 0
	}
}

// BOOTP legacy
func (p Packet) SetFile(file []byte) {
	copy(p[108:236], file)
	if len(file) < 128 {
		p[108+len(file)] = 0
	}
}

// Map of DHCP options
type Options map[OptionCode][]byte

// Parses the packet's options into an Options map
func (p Packet) ParseOptions() Options {
	opts := p.Options()
	options := make(Options, 10)
	for len(opts) >= 2 && OptionCode(opts[0]) != End {
		if OptionCode(opts[0]) == Pad {
			opts = opts[1:]
			continue
		}
		size := int(opts[1])
		if len(opts) < 2+size {
			break
		}
		options[OptionCode(opts[0])] = opts[2 : 2+size]
		opts = opts[2+size:]
	}
	return options
}

func NewPacket(opCode OpCode) Packet {
	p := make(Packet, 241)
	p.SetOpCode(opCode)
	p.SetHType(1) // Ethernet
	p.SetCookie([]byte{99, 130, 83, 99})
	p[240] = byte(End)
	return p
}

// Appends a DHCP option to the end of a packet
func (p *Packet) AddOption(o OptionCode, value []byte) {
	*p = append((*p)[:len(*p)-1], []byte{byte(o), byte(len(value))}...) // Strip off End, Add OptionCode and Length
	*p = append(*p, value...)                                           // Add Option Value
	*p = append(*p, byte(End))                                          // Add on new End
}

// Removes all options from packet.
func (p *Packet) StripOptions() {
	*p = append((*p)[:240], byte(End))
}

// Creates a request packet that a Client would send to a server.
func RequestPacket(mt MessageType, chAddr net.HardwareAddr, cIAddr net.IP, xId []byte, broadcast bool, options []Option) Packet {
	p := NewPacket(BootRequest)
	p.SetCHAddr(chAddr)
	p.SetXId(xId)
	if cIAddr != nil {
		p.SetCIAddr(cIAddr)
	}
	p.SetBroadcast(broadcast)
	p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
	for _, o := range options {
		p.AddOption(o.Code, o.Value)
	}
	p.PadToMinSize()
	return p
}

// ReplyPacket creates a reply packet that a Server would send to a client.
// It uses the req Packet param to copy across common/necessary fields to
// associate the reply the request.
func ReplyPacket(req Packet, mt MessageType, serverId, yIAddr net.IP, leaseDuration time.Duration, options []Option) Packet {
	p := NewPacket(BootReply)
	p.SetXId(req.XId())
	p.SetFlags(req.Flags())
	p.SetYIAddr(yIAddr)
	p.SetGIAddr(req.GIAddr())
	p.SetCHAddr(req.CHAddr())
	p.SetSecs(req.Secs())
	p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
	p.AddOption(OptionServerIdentifier, []byte(serverId))
	p.AddOption(OptionIPAddressLeaseTime, OptionsLeaseTime(leaseDuration))
	for _, o := range options {
		p.AddOption(o.Code, o.Value)
	}
	p.PadToMinSize()
	return p
}

// PadToMinSize pads a packet so that when sent over UDP, the entire packet,
// is 300 bytes (BOOTP min), to be compatible with really old devices.
var padder [272]byte

func (p *Packet) PadToMinSize() {
	if n := len(*p); n < 272 {
		*p = append(*p, padder[:272-n]...)
	}
}

// OpCodes
const (
	BootRequest OpCode = 1 // From Client
	BootReply   OpCode = 2 // From Server
)

// DHCP Message Type 53
const (
	Discover MessageType = 1 // Broadcast Packet From Client - Can I have an IP?
	Offer    MessageType = 2 // Broadcast From Server - Here's an IP
	Request  MessageType = 3 // Broadcast From Client - I'll take that IP (Also start for renewals)
	Decline  MessageType = 4 // Broadcast From Client - Sorry I can't use that IP
	ACK      MessageType = 5 // From Server, Yes you can have that IP
	NAK      MessageType = 6 // From Server, No you cannot have that IP
	Release  MessageType = 7 // From Client, I don't need that IP anymore
	Inform   MessageType = 8 // From Client, I have this IP and there's nothing you can do about it
)

// DHCP Options
const (
	End                          OptionCode = 255
	Pad                          OptionCode = 0
	OptionSubnetMask             OptionCode = 1
	OptionTimeOffset             OptionCode = 2
	OptionRouter                 OptionCode = 3
	OptionTimeServer             OptionCode = 4
	OptionNameServer             OptionCode = 5
	OptionDomainNameServer       OptionCode = 6
	OptionLogServer              OptionCode = 7
	OptionCookieServer           OptionCode = 8
	OptionLPRServer              OptionCode = 9
	OptionImpressServer          OptionCode = 10
	OptionResourceLocationServer OptionCode = 11
	OptionHostName               OptionCode = 12
	OptionBootFileSize           OptionCode = 13
	OptionMeritDumpFile          OptionCode = 14
	OptionDomainName             OptionCode = 15
	OptionSwapServer             OptionCode = 16
	OptionRootPath               OptionCode = 17
	OptionExtensionsPath         OptionCode = 18

	// IP Layer Parameters per Host
	OptionIPForwardingEnableDisable          OptionCode = 19
	OptionNonLocalSourceRoutingEnableDisable OptionCode = 20
	OptionPolicyFilter                       OptionCode = 21
	OptionMaximumDatagramReassemblySize      OptionCode = 22
	OptionDefaultIPTimeToLive                OptionCode = 23
	OptionPathMTUAgingTimeout                OptionCode = 24
	OptionPathMTUPlateauTable                OptionCode = 25

	// IP Layer Parameters per Interface
	OptionInterfaceMTU              OptionCode = 26
	OptionAllSubnetsAreLocal        OptionCode = 27
	OptionBroadcastAddress          OptionCode = 28
	OptionPerformMaskDiscovery      OptionCode = 29
	OptionMaskSupplier              OptionCode = 30
	OptionPerformRouterDiscovery    OptionCode = 31
	OptionRouterSolicitationAddress OptionCode = 32
	OptionStaticRoute               OptionCode = 33

	// Link Layer Parameters per Interface
	OptionTrailerEncapsulation            OptionCode = 34
	OptionARPCacheTimeout                 OptionCode = 35
	OptionEthernetEncapsulation           OptionCode = 36

	// TCP Parameters
	OptionTCPDefaultTTL        OptionCode = 37
	OptionTCPKeepaliveInterval OptionCode = 38
	OptionTCPKeepaliveGarbage  OptionCode = 39

	// Application and Service Parameters
	OptionNetworkInformationServiceDomain            OptionCode = 40
	OptionNetworkInformationServers                  OptionCode = 41
	OptionNetworkTimeProtocolServers                 OptionCode = 42
	OptionVendorSpecificInformation                  OptionCode = 43
	OptionNetBIOSOverTCPIPNameServer                 OptionCode = 44
	OptionNetBIOSOverTCPIPDatagramDistributionServer OptionCode = 45
	OptionNetBIOSOverTCPIPNodeType                   OptionCode = 46
	OptionNetBIOSOverTCPIPScope                      OptionCode = 47
	OptionXWindowSystemFontServer                    OptionCode = 48
	OptionXWindowSystemDisplayManager                OptionCode = 49
	OptionNetworkInformationServicePlusDomain        OptionCode = 64
	OptionNetworkInformationServicePlusServers       OptionCode = 65
	OptionMobileIPHomeAgent                          OptionCode = 68
	OptionSimpleMailTransportProtocol                OptionCode = 69
	OptionPostOfficeProtocolServer                   OptionCode = 70
	OptionNetworkNewsTransportProtocol               OptionCode = 71
	OptionDefaultWorldWideWebServer                  OptionCode = 72
	OptionDefaultFingerServer                        OptionCode = 73
	OptionDefaultInternetRelayChatServer             OptionCode = 74
	OptionStreetTalkServer                           OptionCode = 75
	OptionStreetTalkDirectoryAssistance              OptionCode = 76

	OptionRelayAgentInformation OptionCode = 82

	// DHCP Extensions
	OptionRequestedIPAddress     OptionCode = 50
	OptionIPAddressLeaseTime     OptionCode = 51
	OptionOverload               OptionCode = 52
	OptionDHCPMessageType        OptionCode = 53
	OptionServerIdentifier       OptionCode = 54
	OptionParameterRequestList   OptionCode = 55
	OptionMessage                OptionCode = 56
	OptionMaximumDHCPMessageSize OptionCode = 57
	OptionRenewalTimeValue       OptionCode = 58
	OptionRebindingTimeValue     OptionCode = 59
	OptionVendorClassIdentifier  OptionCode = 60
	OptionClientIdentifier       OptionCode = 61

	OptionTFTPServerName OptionCode = 66
	OptionBootFileName   OptionCode = 67

	OptionClientArchitecture OptionCode = 93

	OptionTZPOSIXString    OptionCode = 100
	OptionTZDatabaseString OptionCode = 101

	OptionClasslessRouteFormat OptionCode = 121
)

/* Notes
A DHCP server always returns its own address in the 'server identifier' option.
DHCP defines a new 'client identifier' option that is used to pass an explicit client identifier to a DHCP server.
*/
<commit_msg>Add OptionUserClass DHCP option<commit_after>// IPv4 DHCP Library for Parsing and Creating DHCP Packets, along with basic DHCP server functionality
//
// Author: http://richard.warburton.it/
//
// Copyright: 2014 Skagerrak Software - http://www.skagerraksoftware.com/
package dhcp4

import (
	"net"
	"time"
)

type Option struct {
	Code  OptionCode
	Value []byte
}
type OptionCode byte
type OpCode byte
type MessageType byte // Option 53

// A DHCP packet
type Packet []byte

func (p Packet) OpCode() OpCode { return OpCode(p[0]) }
func (p Packet) HType() byte    { return p[1] }
func (p Packet) HLen() byte     { return p[2] }
func (p Packet) Hops() byte     { return p[3] }
func (p Packet) XId() []byte    { return p[4:8] }
func (p Packet) Secs() []byte   { return p[8:10] } // Never Used?
func (p Packet) Flags() []byte  { return p[10:12] }
func (p Packet) CIAddr() net.IP { return net.IP(p[12:16]) }
func (p Packet) YIAddr() net.IP { return net.IP(p[16:20]) }
func (p Packet) SIAddr() net.IP { return net.IP(p[20:24]) }
func (p Packet) GIAddr() net.IP { return net.IP(p[24:28]) }
func (p Packet) CHAddr() net.HardwareAddr {
	hLen := p.HLen()
	if hLen > 16 { // Prevent chaddr exceeding p boundary
		hLen = 16
	}
	return net.HardwareAddr(p[28 : 28+hLen]) // max endPos 44
}

// 192 bytes of zeros BOOTP legacy

// BOOTP legacy
func (p Packet) SName() []byte { return trimNull(p[44:108]) }

// BOOTP legacy
func (p Packet) File() []byte { return trimNull(p[108:236]) }

func trimNull(d []byte) []byte {
	for i, v := range d {
		if v == 0 {
			return d[:i]
		}
	}
	return d
}

func (p Packet) Cookie() []byte { return p[236:240] }
func (p Packet) Options() []byte {
	if len(p) > 240 {
		return p[240:]
	}
	return nil
}

func (p Packet) Broadcast() bool { return p.Flags()[0] > 127 }

func (p Packet) SetBroadcast(broadcast bool) {
	if p.Broadcast() != broadcast {
		p.Flags()[0] ^= 128
	}
}

func (p Packet) SetOpCode(c OpCode) { p[0] = byte(c) }
func (p Packet) SetCHAddr(a net.HardwareAddr) {
	copy(p[28:44], a)
	p[2] = byte(len(a))
}
func (p Packet) SetHType(hType byte)     { p[1] = hType }
func (p Packet) SetCookie(cookie []byte) { copy(p.Cookie(), cookie) }
func (p Packet) SetHops(hops byte)       { p[3] = hops }
func (p Packet) SetXId(xId []byte)       { copy(p.XId(), xId) }
func (p Packet) SetSecs(secs []byte)     { copy(p.Secs(), secs) }
func (p Packet) SetFlags(flags []byte)   { copy(p.Flags(), flags) }
func (p Packet) SetCIAddr(ip net.IP)     { copy(p.CIAddr(), ip.To4()) }
func (p Packet) SetYIAddr(ip net.IP)     { copy(p.YIAddr(), ip.To4()) }
func (p Packet) SetSIAddr(ip net.IP)     { copy(p.SIAddr(), ip.To4()) }
func (p Packet) SetGIAddr(ip net.IP)     { copy(p.GIAddr(), ip.To4()) }

// BOOTP legacy
func (p Packet) SetSName(sName []byte) {
	copy(p[44:108], sName)
	if len(sName) < 64 {
		p[44+len(sName)] = 0
	}
}

// BOOTP legacy
func (p Packet) SetFile(file []byte) {
	copy(p[108:236], file)
	if len(file) < 128 {
		p[108+len(file)] = 0
	}
}

// Map of DHCP options
type Options map[OptionCode][]byte

// Parses the packet's options into an Options map
func (p Packet) ParseOptions() Options {
	opts := p.Options()
	options := make(Options, 10)
	for len(opts) >= 2 && OptionCode(opts[0]) != End {
		if OptionCode(opts[0]) == Pad {
			opts = opts[1:]
			continue
		}
		size := int(opts[1])
		if len(opts) < 2+size {
			break
		}
		options[OptionCode(opts[0])] = opts[2 : 2+size]
		opts = opts[2+size:]
	}
	return options
}

func NewPacket(opCode OpCode) Packet {
	p := make(Packet, 241)
	p.SetOpCode(opCode)
	p.SetHType(1) // Ethernet
	p.SetCookie([]byte{99, 130, 83, 99})
	p[240] = byte(End)
	return p
}

// Appends a DHCP option to the end of a packet
func (p *Packet) AddOption(o OptionCode, value []byte) {
	*p = append((*p)[:len(*p)-1], []byte{byte(o), byte(len(value))}...) // Strip off End, Add OptionCode and Length
	*p = append(*p, value...)                                           // Add Option Value
	*p = append(*p, byte(End))                                          // Add on new End
}

// Removes all options from packet.
func (p *Packet) StripOptions() {
	*p = append((*p)[:240], byte(End))
}

// Creates a request packet that a Client would send to a server.
func RequestPacket(mt MessageType, chAddr net.HardwareAddr, cIAddr net.IP, xId []byte, broadcast bool, options []Option) Packet {
	p := NewPacket(BootRequest)
	p.SetCHAddr(chAddr)
	p.SetXId(xId)
	if cIAddr != nil {
		p.SetCIAddr(cIAddr)
	}
	p.SetBroadcast(broadcast)
	p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
	for _, o := range options {
		p.AddOption(o.Code, o.Value)
	}
	p.PadToMinSize()
	return p
}

// ReplyPacket creates a reply packet that a Server would send to a client.
// It uses the req Packet param to copy across common/necessary fields to
// associate the reply the request.
func ReplyPacket(req Packet, mt MessageType, serverId, yIAddr net.IP, leaseDuration time.Duration, options []Option) Packet {
	p := NewPacket(BootReply)
	p.SetXId(req.XId())
	p.SetFlags(req.Flags())
	p.SetYIAddr(yIAddr)
	p.SetGIAddr(req.GIAddr())
	p.SetCHAddr(req.CHAddr())
	p.SetSecs(req.Secs())
	p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
	p.AddOption(OptionServerIdentifier, []byte(serverId))
	p.AddOption(OptionIPAddressLeaseTime, OptionsLeaseTime(leaseDuration))
	for _, o := range options {
		p.AddOption(o.Code, o.Value)
	}
	p.PadToMinSize()
	return p
}

// PadToMinSize pads a packet so that when sent over UDP, the entire packet,
// is 300 bytes (BOOTP min), to be compatible with really old devices.
var padder [272]byte

func (p *Packet) PadToMinSize() {
	if n := len(*p); n < 272 {
		*p = append(*p, padder[:272-n]...)
	}
}

// OpCodes
const (
	BootRequest OpCode = 1 // From Client
	BootReply   OpCode = 2 // From Server
)

// DHCP Message Type 53
const (
	Discover MessageType = 1 // Broadcast Packet From Client - Can I have an IP?
	Offer    MessageType = 2 // Broadcast From Server - Here's an IP
	Request  MessageType = 3 // Broadcast From Client - I'll take that IP (Also start for renewals)
	Decline  MessageType = 4 // Broadcast From Client - Sorry I can't use that IP
	ACK      MessageType = 5 // From Server, Yes you can have that IP
	NAK      MessageType = 6 // From Server, No you cannot have that IP
	Release  MessageType = 7 // From Client, I don't need that IP anymore
	Inform   MessageType = 8 // From Client, I have this IP and there's nothing you can do about it
)

// DHCP Options
const (
	End                          OptionCode = 255
	Pad                          OptionCode = 0
	OptionSubnetMask             OptionCode = 1
	OptionTimeOffset             OptionCode = 2
	OptionRouter                 OptionCode = 3
	OptionTimeServer             OptionCode = 4
	OptionNameServer             OptionCode = 5
	OptionDomainNameServer       OptionCode = 6
	OptionLogServer              OptionCode = 7
	OptionCookieServer           OptionCode = 8
	OptionLPRServer              OptionCode = 9
	OptionImpressServer          OptionCode = 10
	OptionResourceLocationServer OptionCode = 11
	OptionHostName               OptionCode = 12
	OptionBootFileSize           OptionCode = 13
	OptionMeritDumpFile          OptionCode = 14
	OptionDomainName             OptionCode = 15
	OptionSwapServer             OptionCode = 16
	OptionRootPath               OptionCode = 17
	OptionExtensionsPath         OptionCode = 18

	// IP Layer Parameters per Host
	OptionIPForwardingEnableDisable          OptionCode = 19
	OptionNonLocalSourceRoutingEnableDisable OptionCode = 20
	OptionPolicyFilter                       OptionCode = 21
	OptionMaximumDatagramReassemblySize      OptionCode = 22
	OptionDefaultIPTimeToLive                OptionCode = 23
	OptionPathMTUAgingTimeout                OptionCode = 24
	OptionPathMTUPlateauTable                OptionCode = 25

	// IP Layer Parameters per Interface
	OptionInterfaceMTU              OptionCode = 26
	OptionAllSubnetsAreLocal        OptionCode = 27
	OptionBroadcastAddress          OptionCode = 28
	OptionPerformMaskDiscovery      OptionCode = 29
	OptionMaskSupplier              OptionCode = 30
	OptionPerformRouterDiscovery    OptionCode = 31
	OptionRouterSolicitationAddress OptionCode = 32
	OptionStaticRoute               OptionCode = 33

	// Link Layer Parameters per Interface
	OptionTrailerEncapsulation            OptionCode = 34
	OptionARPCacheTimeout                 OptionCode = 35
	OptionEthernetEncapsulation           OptionCode = 36

	// TCP Parameters
	OptionTCPDefaultTTL        OptionCode = 37
	OptionTCPKeepaliveInterval OptionCode = 38
	OptionTCPKeepaliveGarbage  OptionCode = 39

	// Application and Service Parameters
	OptionNetworkInformationServiceDomain            OptionCode = 40
	OptionNetworkInformationServers                  OptionCode = 41
	OptionNetworkTimeProtocolServers                 OptionCode = 42
	OptionVendorSpecificInformation                  OptionCode = 43
	OptionNetBIOSOverTCPIPNameServer                 OptionCode = 44
	OptionNetBIOSOverTCPIPDatagramDistributionServer OptionCode = 45
	OptionNetBIOSOverTCPIPNodeType                   OptionCode = 46
	OptionNetBIOSOverTCPIPScope                      OptionCode = 47
	OptionXWindowSystemFontServer                    OptionCode = 48
	OptionXWindowSystemDisplayManager                OptionCode = 49
	OptionNetworkInformationServicePlusDomain        OptionCode = 64
	OptionNetworkInformationServicePlusServers       OptionCode = 65
	OptionMobileIPHomeAgent                          OptionCode = 68
	OptionSimpleMailTransportProtocol                OptionCode = 69
	OptionPostOfficeProtocolServer                   OptionCode = 70
	OptionNetworkNewsTransportProtocol               OptionCode = 71
	OptionDefaultWorldWideWebServer                  OptionCode = 72
	OptionDefaultFingerServer                        OptionCode = 73
	OptionDefaultInternetRelayChatServer             OptionCode = 74
	OptionStreetTalkServer                           OptionCode = 75
	OptionStreetTalkDirectoryAssistance              OptionCode = 76

	OptionRelayAgentInformation OptionCode = 82

	// DHCP Extensions
	OptionRequestedIPAddress     OptionCode = 50
	OptionIPAddressLeaseTime     OptionCode = 51
	OptionOverload               OptionCode = 52
	OptionDHCPMessageType        OptionCode = 53
	OptionServerIdentifier       OptionCode = 54
	OptionParameterRequestList   OptionCode = 55
	OptionMessage                OptionCode = 56
	OptionMaximumDHCPMessageSize OptionCode = 57
	OptionRenewalTimeValue       OptionCode = 58
	OptionRebindingTimeValue     OptionCode = 59
	OptionVendorClassIdentifier  OptionCode = 60
	OptionClientIdentifier       OptionCode = 61

	OptionTFTPServerName OptionCode = 66
	OptionBootFileName   OptionCode = 67

	OptionUserClass OptionCode = 77

	OptionClientArchitecture OptionCode = 93

	OptionTZPOSIXString    OptionCode = 100
	OptionTZDatabaseString OptionCode = 101

	OptionClasslessRouteFormat OptionCode = 121
)

/* Notes
A DHCP server always returns its own address in the 'server identifier' option.
DHCP defines a new 'client identifier' option that is used to pass an explicit client identifier to a DHCP server.
*/
<|endoftext|>


<commit_before>package gameboygo

import "github.com/veandco/go-sdl2/sdl"
import "unsafe"
import "fmt"
const(
	WHITE		uint8 = 255
	LIGHT_GRAY	uint8 = 170
	DARK_GRAY	uint8 = 85
	BLACK		uint8 = 0

	SCAN_CICLES int   = 456

	LCD_STAT_HBLANK 	 uint8 = 0x00
	LCD_STAT_VBLANK 	 uint8 = 0x01
	LCD_STAT_OAM_RAM 	 uint8 = 0x02
	LCD_STAT_DATA2DRIVER uint8 = 0x03

	WIDTH		int   = 160
	HEIGHT		int   = 144
	PITCH   	int   = WIDTH * 4
	BUFFER_SIZE int   = WIDTH*HEIGHT*4
)
var colors = [4]uint8 {WHITE,		//00
					   LIGHT_GRAY,	//01
					   DARK_GRAY,	//10
					   BLACK,}		//11

//var pixels [BUFFER_SIZE]uint8
//var p_pixels unsafe.Pointer = unsafe.Pointer(&pixels)
var pitch int = PITCH
var p_pixels unsafe.Pointer
/*
  Bit 7 - LCD Display Enable             (0=Off, 1=On)
  Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
  Bit 5 - Window Display Enable          (0=Off, 1=On)
  Bit 4 - BG & Window Tile Data Select   (0=8800-97FF, 1=8000-8FFF)
  Bit 3 - BG Tile Map Display Select     (0=9800-9BFF, 1=9C00-9FFF)
  Bit 2 - OBJ (Sprite) Size              (0=8x8, 1=8x16)
  Bit 1 - OBJ (Sprite) Display Enable    (0=Off, 1=On)
  Bit 0 - BG Display (for CGB see below) (0=Off, 1=On)
*/
var LcdControl = &ram[0xFF40]

/*
  Bit 6 - LYC=LY Coincidence Interrupt (1=Enable) (Read/Write)
  Bit 5 - Mode 2 OAM Interrupt         (1=Enable) (Read/Write)
  Bit 4 - Mode 1 V-Blank Interrupt     (1=Enable) (Read/Write)
  Bit 3 - Mode 0 H-Blank Interrupt     (1=Enable) (Read/Write)
  Bit 2 - Coincidence Flag  (0:LYC<>LY, 1:LYC=LY) (Read Only)
  Bit 1-0 - Mode Flag       (Mode 0-3, see below) (Read Only)
            0: During H-Blank
            1: During V-Blank
            2: During Searching OAM-RAM
            3: During Transfering Data to LCD Driver
*/
var LcdStatus = &ram[0xFF41]

/*
Specifies the position in the 256x256 pixels BG map (32x32 tiles) which is to be displayed at the upper/left LCD display position.
Values in range from 0-255 may be used for X/Y each, the video controller automatically wraps back to the upper (left) position in BG map when drawing exceeds the lower (right) border of the BG map area.
*/
var ScY = &ram[0xFF42]
var ScX = &ram[0xFF43]

/*
The LY indicates the vertical line to which the present data is transferred to the LCD Driver. The LY can take on any value between 0 through 153. The values between 144 and 153 indicate the V-Blank period. Writing will reset the counter.
*/
var Ly  = &ram[0xFF44]

/*
The gameboy permanently compares the value of the LYC and LY registers. When both values are identical, the coincident bit in the STAT register becomes set, and (if enabled) a STAT interrupt is requested.
*/
var LyC = &ram[0xFF45]

/*
Specifies the upper/left positions of the Window area. (The window is an alternate background area which can be displayed above of the normal background. OBJs (sprites) may be still displayed above or behinf the window, just as for normal BG.)
The window becomes visible (if enabled) when positions are set in range WX=0..166, WY=0..143. A postion of WX=7, WY=0 locates the window at upper left, it is then completly covering normal background.
*/
var Wy  = &ram[0xFF4A]
var Wx  = &ram[0xFF4B]

/*
This register assigns gray shades to the color numbers of the BG and Window tiles.
  Bit 7-6 - Shade for Color Number 3
  Bit 5-4 - Shade for Color Number 2
  Bit 3-2 - Shade for Color Number 1
  Bit 1-0 - Shade for Color Number 0
*/
var Bgp = &ram[0xFF47]

/*
This register assigns gray shades for sprite palette 0. It works exactly as BGP (FF47), except that the lower two bits aren't used because sprite data 00 is transparent.
*/
var Obp0 = &ram[0xFF48]

/*
This register assigns gray shades for sprite palette 1. It works exactly as BGP (FF47), except that the lower two bits aren't used because sprite data 00 is transparent.
*/
var Obp1 = &ram[0xFF49]

var LastScanLine int = 0
func UpdateGPU(renderer *sdl.Renderer, tex *sdl.Texture) {
	if (*LcdControl & 0x80) == 0{
		//display off
		//must clean Ly and set mode 1
		LastScanLine = 0
		*Ly = 0
		setLcdStatMode(LCD_STAT_VBLANK)
		return
	}
	if *Ly >= 144 {
		if ((*LcdStatus & 0x10) != 0) && ((*LcdStatus & 0x03) != LCD_STAT_VBLANK) {
			//mode 1: During V-Blank
			setInterruptsFlag(LCD_STAT)
		}
		setLcdStatMode(LCD_STAT_VBLANK)
	} else{
		//maybe i should redo all this CicleCounter thing
		var modeCicles = CicleCounter % SCAN_CICLES
		if modeCicles < 80{
			//mode 2: 80 cicles of the 456
			if ((*LcdStatus & 0x20) != 0) && ((*LcdStatus & 0x03) != LCD_STAT_OAM_RAM) {
				//if this interrupt is enabled and just changed mode
				setInterruptsFlag(LCD_STAT)
			}
			setLcdStatMode(LCD_STAT_OAM_RAM)
		} else if modeCicles < (80 + 172) {
			//mode 3: 172 cicles of the 456
			setLcdStatMode(LCD_STAT_DATA2DRIVER)
		} else{
			//mode 0: remaining cicles
			if ((*LcdStatus & 0x08) != 0) && ((*LcdStatus & 0x03) != LCD_STAT_HBLANK) {
				//if this interrupt is enabled and just changed mode
				setInterruptsFlag(LCD_STAT)
			}
			setLcdStatMode(LCD_STAT_HBLANK)
		}
	}

	if (*Ly == *LyC) {
		//Bit 2 - Coincidence Flag  (0:LYC<>LY, 1:LYC=LY) (Read Only)
		if (*LcdStatus & 0x40) != 0 && (*LcdStatus & 0x04) == 0{
			setInterruptsFlag(LCD_STAT)
		}
		*LcdStatus |= 0x04
	} else{
		*LcdStatus &= 0xFB
	}
	if (int(CicleCounter/SCAN_CICLES) - LastScanLine) >= 1{
		//next scanline
		LastScanLine = int(CicleCounter/SCAN_CICLES)
		*Ly += 1
		if *Ly == 144 {
			//VBLANK
			setInterruptsFlag(V_BLANK)
		} else if *Ly > 153 {
			//end VBLANK
			*Ly = 255
			tex.Unlock()
			renderer.Copy(tex,nil,nil)
			renderer.Present()
		} else if *Ly < 144{
			tex.Lock(nil, &p_pixels, &pitch)
			DrawLine(renderer)
		}
	}
}
func DrawLine(renderer *sdl.Renderer) {
	if (*LcdControl & 0x01) != 0 { //draw background
		var signed bool
		var tileMap uint16
		var tileData uint16
		var currentTile uint8
		var drawWindow bool
		if (*LcdControl & 0x10) == 0{  //Bit 4 - BG & Window Tile Data Select   (0=8800-97FF, 1=8000-8FFF)
			tileData = 0x8800
			signed = true
		} else{
			tileData = 0x8000
			signed = false
		}
		if ((*LcdControl & 0x20) != 0) && (*Ly > *Wy) {      //Bit 5 - Window Display Enable(0=Off, 1=On)
			//drawing window
			fmt.Printf("Window Y= %d\n", *Ly)
			drawWindow = true
			if (*LcdControl & 0x40) == 0 { //Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
				tileMap = 0x9800
			} else {
				tileMap = 0x9C00
			}
			currentTile = *Ly - *Wy
		} else{
			drawWindow = false
			if (*LcdControl & 0x08) == 0 { //Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
				tileMap = 0x9800
			} else{
				tileMap = 0x9C00
			}
			currentTile = *ScY + *Ly
		}
		/*
		tiles are 8*8 pixels
		Background Tile Map contains the numbers of tiles to be displayed. It is organized
 		as 32 rows of 32 bytes each. Each byte contains a number of a tile to be displayed
 		*/
		var tileLine uint16 = (uint16(currentTile)/8)*32
		var x uint8
		var i uint8
		for i = 0; i < 160; i++ {
			if drawWindow && (i >= *Wx) { // window
				x = i - *Wx
			} else{
				x = i + *ScX
			}
			var tileNumAddress uint16 = tileMap + tileLine + (uint16(x)/8)
			var tileAddr uint16
			if signed {
				tileAddr = tileData + (uint16( int16(int8(ram[tileNumAddress])) + 128 ) * 16) //maybe this works
			} else{
				tileAddr = tileData + (uint16(ram[tileNumAddress]) * 16)
			}
			var l uint16 = (uint16(currentTile) % 8) * 2 //line of the tile that we are drawing: each tile has 8 lines and each line is 2 bytes
			var data0 = ram[tileAddr + l + 0]
			var data1 = ram[tileAddr + l + 1]
			var c = getPixelColor(data0, data1, 7-(x%8))
			pos := ((int(*Ly)*WIDTH)*4) + (int(i)*4)
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 0] = c
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 1] = c
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 2] = c
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 3] = 255
			/*
			pixels[pos + 0] = c   //R
			pixels[pos + 1] = c   //G
			pixels[pos + 2] = c   //B
			pixels[pos + 3] = 255 //A
			*/
		}
	}
	if (*LcdControl & 0x02) != 0 { //draw sprites
		//TODO
	}
	/*
	var tmp_p unsafe.Pointer
	tex.Lock(nil, &tmp_p, &pitch)
	copy(((*[WIDTH*HEIGHT*4]uint8)(tmp_p))[int(*Ly)*160*4:(int(*Ly)*160*4)+160*4], pixels[int(*Ly)*160*4:(int(*Ly)*160*4)+160*4])
	tex.Unlock()
	//
	tex.Update(nil, p_pixels, PITCH)
	renderer.Copy(tex,nil,nil)
	renderer.Present()
	*/
}

func getPixelColor(lower, upper, bitNum uint8) uint8{
	var mask uint8 = (1 << bitNum)
	if bitNum < 2 {
		return getColor(((upper & mask) << (1-bitNum)) | ((lower & mask) >> bitNum))
	}
	return getColor((upper & mask) >> (bitNum - 1) | ((lower & mask) >> bitNum))
}

func getColor(code uint8) uint8{
	return colors[((*Bgp & (0x03 << (2*code))) >> (2*code))]
}

func setLcdStatMode(mode uint8) {
	*LcdStatus &= 0xFC //clean last mode
	*LcdStatus |= mode //set
}
<commit_msg>drawing sprites<commit_after>package gameboygo

import "github.com/veandco/go-sdl2/sdl"
import "unsafe"
import "fmt"
const(
	WHITE		uint8 = 255
	LIGHT_GRAY	uint8 = 170
	DARK_GRAY	uint8 = 85
	BLACK		uint8 = 0

	SCAN_CICLES int   = 456

	LCD_STAT_HBLANK 	 uint8 = 0x00
	LCD_STAT_VBLANK 	 uint8 = 0x01
	LCD_STAT_OAM_RAM 	 uint8 = 0x02
	LCD_STAT_DATA2DRIVER uint8 = 0x03

	WIDTH		int   = 160
	HEIGHT		int   = 144
	PITCH   	int   = WIDTH * 4
	BUFFER_SIZE int   = WIDTH*HEIGHT*4
)
var colors = [4]uint8 {WHITE,		//00
					   LIGHT_GRAY,	//01
					   DARK_GRAY,	//10
					   BLACK,}		//11
//var pixels [BUFFER_SIZE]uint8
//var p_pixels unsafe.Pointer = unsafe.Pointer(&pixels)
var pitch int = PITCH
var p_pixels unsafe.Pointer
/*
  Bit 7 - LCD Display Enable             (0=Off, 1=On)
  Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
  Bit 5 - Window Display Enable          (0=Off, 1=On)
  Bit 4 - BG & Window Tile Data Select   (0=8800-97FF, 1=8000-8FFF)
  Bit 3 - BG Tile Map Display Select     (0=9800-9BFF, 1=9C00-9FFF)
  Bit 2 - OBJ (Sprite) Size              (0=8x8, 1=8x16)
  Bit 1 - OBJ (Sprite) Display Enable    (0=Off, 1=On)
  Bit 0 - BG Display (for CGB see below) (0=Off, 1=On)
*/
var LcdControl = &ram[0xFF40]

/*
  Bit 6 - LYC=LY Coincidence Interrupt (1=Enable) (Read/Write)
  Bit 5 - Mode 2 OAM Interrupt         (1=Enable) (Read/Write)
  Bit 4 - Mode 1 V-Blank Interrupt     (1=Enable) (Read/Write)
  Bit 3 - Mode 0 H-Blank Interrupt     (1=Enable) (Read/Write)
  Bit 2 - Coincidence Flag  (0:LYC<>LY, 1:LYC=LY) (Read Only)
  Bit 1-0 - Mode Flag       (Mode 0-3, see below) (Read Only)
            0: During H-Blank
            1: During V-Blank
            2: During Searching OAM-RAM
            3: During Transfering Data to LCD Driver
*/
var LcdStatus = &ram[0xFF41]

/*
Specifies the position in the 256x256 pixels BG map (32x32 tiles) which is to be displayed at the upper/left LCD display position.
Values in range from 0-255 may be used for X/Y each, the video controller automatically wraps back to the upper (left) position in BG map when drawing exceeds the lower (right) border of the BG map area.
*/
var ScY = &ram[0xFF42]
var ScX = &ram[0xFF43]

/*
The LY indicates the vertical line to which the present data is transferred to the LCD Driver. The LY can take on any value between 0 through 153. The values between 144 and 153 indicate the V-Blank period. Writing will reset the counter.
*/
var Ly  = &ram[0xFF44]

/*
The gameboy permanently compares the value of the LYC and LY registers. When both values are identical, the coincident bit in the STAT register becomes set, and (if enabled) a STAT interrupt is requested.
*/
var LyC = &ram[0xFF45]

/*
Specifies the upper/left positions of the Window area. (The window is an alternate background area which can be displayed above of the normal background. OBJs (sprites) may be still displayed above or behinf the window, just as for normal BG.)
The window becomes visible (if enabled) when positions are set in range WX=0..166, WY=0..143. A postion of WX=7, WY=0 locates the window at upper left, it is then completly covering normal background.
*/
var Wy  = &ram[0xFF4A]
var Wx  = &ram[0xFF4B]

/*
This register assigns gray shades to the color numbers of the BG and Window tiles.
  Bit 7-6 - Shade for Color Number 3
  Bit 5-4 - Shade for Color Number 2
  Bit 3-2 - Shade for Color Number 1
  Bit 1-0 - Shade for Color Number 0
*/
var Bgp = &ram[0xFF47]

/*
This register assigns gray shades for sprite palette 0. It works exactly as BGP (FF47), except that the lower two bits aren't used because sprite data 00 is transparent.
*/
var Obp0 = &ram[0xFF48]

/*
This register assigns gray shades for sprite palette 1. It works exactly as BGP (FF47), except that the lower two bits aren't used because sprite data 00 is transparent.
*/
var Obp1 = &ram[0xFF49]

var LastScanLine int = 0
func UpdateGPU(renderer *sdl.Renderer, tex *sdl.Texture) {
	if (*LcdControl & 0x80) == 0{
		//display off
		//must clean Ly and set mode 1
		LastScanLine = 0
		*Ly = 0
		setLcdStatMode(LCD_STAT_VBLANK)
		return
	}
	if *Ly >= 144 {
		if ((*LcdStatus & 0x10) != 0) && ((*LcdStatus & 0x03) != LCD_STAT_VBLANK) {
			//mode 1: During V-Blank
			setInterruptsFlag(LCD_STAT)
		}
		setLcdStatMode(LCD_STAT_VBLANK)
	} else{
		//maybe i should redo all this CicleCounter thing
		var modeCicles = CicleCounter % SCAN_CICLES
		if modeCicles < 80{
			//mode 2: 80 cicles of the 456
			if ((*LcdStatus & 0x20) != 0) && ((*LcdStatus & 0x03) != LCD_STAT_OAM_RAM) {
				//if this interrupt is enabled and just changed mode
				setInterruptsFlag(LCD_STAT)
			}
			setLcdStatMode(LCD_STAT_OAM_RAM)
		} else if modeCicles < (80 + 172) {
			//mode 3: 172 cicles of the 456
			setLcdStatMode(LCD_STAT_DATA2DRIVER)
		} else{
			//mode 0: remaining cicles
			if ((*LcdStatus & 0x08) != 0) && ((*LcdStatus & 0x03) != LCD_STAT_HBLANK) {
				//if this interrupt is enabled and just changed mode
				setInterruptsFlag(LCD_STAT)
			}
			setLcdStatMode(LCD_STAT_HBLANK)
		}
	}

	if (*Ly == *LyC) {
		//Bit 2 - Coincidence Flag  (0:LYC<>LY, 1:LYC=LY) (Read Only)
		if (*LcdStatus & 0x40) != 0 && (*LcdStatus & 0x04) == 0{
			setInterruptsFlag(LCD_STAT)
		}
		*LcdStatus |= 0x04
	} else{
		*LcdStatus &= 0xFB
	}
	if (int(CicleCounter/SCAN_CICLES) - LastScanLine) >= 1{
		//next scanline
		LastScanLine = int(CicleCounter/SCAN_CICLES)
		*Ly += 1
		if *Ly == 144 {
			//VBLANK
			setInterruptsFlag(V_BLANK)
		} else if *Ly > 153 {
			//end VBLANK
			*Ly = 255
			tex.Unlock()
			renderer.Copy(tex,nil,nil)
			renderer.Present()
		} else if *Ly < 144{
			tex.Lock(nil, &p_pixels, &pitch)
			DrawLine(renderer)
		}
	}
}
func DrawLine(renderer *sdl.Renderer) {
	if (*LcdControl & 0x01) != 0 { //draw background
		var signed bool
		var tileMap uint16
		var tileData uint16
		var currentTile uint8
		var drawWindow bool
		if (*LcdControl & 0x10) == 0{  //Bit 4 - BG & Window Tile Data Select   (0=8800-97FF, 1=8000-8FFF)
			tileData = 0x8800
			signed = true
		} else{
			tileData = 0x8000
			signed = false
		}
		if ((*LcdControl & 0x20) != 0) && (*Ly > *Wy) {      //Bit 5 - Window Display Enable(0=Off, 1=On)
			//drawing window
			drawWindow = true
			if (*LcdControl & 0x40) == 0 { //Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
				tileMap = 0x9800
			} else {
				tileMap = 0x9C00
			}
			currentTile = *Ly - *Wy
		} else{
			drawWindow = false
			if (*LcdControl & 0x08) == 0 { //Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
				tileMap = 0x9800
			} else{
				tileMap = 0x9C00
			}
			currentTile = *ScY + *Ly
		}
		/*
		tiles are 8*8 pixels
		Background Tile Map contains the numbers of tiles to be displayed. It is organized
 		as 32 rows of 32 bytes each. Each byte contains a number of a tile to be displayed
 		*/
		var tileLine uint16 = (uint16(currentTile)/8)*32
		var x uint8
		var i uint8
		for i = 0; i < 160; i++ {
			if drawWindow && (i >= (*Wx-7)) { // window
				x = i - (*Wx-7)
			} else{
				x = i + *ScX
			}
			var tileNumAddress uint16 = tileMap + tileLine + (uint16(x)/8)
			var tileAddr uint16
			if signed {
				tileAddr = tileData + (uint16( int16(int8(ram[tileNumAddress])) + 128 ) * 16) //maybe this works
			} else{
				tileAddr = tileData + (uint16(ram[tileNumAddress]) * 16)
			}
			var l uint16 = (uint16(currentTile) % 8) * 2 //line of the tile that we are drawing: each tile has 8 lines and each line is 2 bytes
			var data0 = ram[tileAddr + l + 0]
			var data1 = ram[tileAddr + l + 1]
			var c = getPixelColor(data0, data1, 7-(x%8), *Bgp)
			pos := ((int(*Ly)*WIDTH)*4) + (int(i)*4)
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 0] = c
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 1] = c
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 2] = c
			(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 3] = 255
			/*
			pixels[pos + 0] = c   //R
			pixels[pos + 1] = c   //G
			pixels[pos + 2] = c   //B
			pixels[pos + 3] = 255 //A
			*/
		}
	}
	if (*LcdControl & 0x02) != 0 { //draw sprites
		var sy uint8 = 8 + ((*LcdControl & 0x04) << 1)
		/*
		if (*LcdControl & 0x04) == 0{
			//Bit 2 - OBJ (Sprite) Size(0=8x8, 1=8x16)
			sy = 16
		}
		*/
		/*
		Byte0  Y position on the screen
  		Byte1  X position on the screen
  		Byte2  Pattern number 0-255 (Unlike some tile
         numbers, sprite pattern numbers are unsigned.
         LSB is ignored (treated as 0) in 8x16 mode.)
  		Byte3  Flags
		*/
		var i uint16
		for i = 0; i < 40; i++ { //all 40 sprites at 0xFE00 (4 bytes per sprite)
			var spriteY     uint8 = ram[0xFE00 + (i*4) + 0] - 16
			var spriteX     uint8 = ram[0xFE00 + (i*4) + 1] -  8
			var spriteNum   uint8 = ram[0xFE00 + (i*4) + 2] //LSB = 0 when 8x16
			var spriteFlags uint8 = ram[0xFE00 + (i*4) + 3]

			if !((*Ly >= spriteY) && (*Ly < (spriteY+sy))) {
				//sprite not in current scanline
				continue
			}
			var line uint8 = *Ly - spriteY
			if (spriteFlags & 0x40) != 0 {
				//yFlip: read from bottom
				line = (^line) & 0x07
			}

			//palette --> (0=OBP0(0xFF48), 1=OBP1(0xFF49))
			var palette uint8 = ram[0xFF48 + ((uint16(spriteFlags) & 0x10) >> 4) ]

			var spriteDataAddr uint16 = 0x8000 + (uint16(spriteNum) * 16) + (uint16(line)*2)
			var data0 = ram[spriteDataAddr + 0]
			var data1 = ram[spriteDataAddr + 1]
			var Xpixel uint8
			for Xpixel = 7; Xpixel != 255 ; Xpixel-- {
				//draw all pixels from this line
				var bit uint8 = Xpixel
				if (spriteFlags & 0x20) != 0 {
					//X flip (0=Normal, 1=Horizontally mirrored)
					bit = (^Xpixel) & 0x07
				}
				color := getPixelColor(data0, data1, bit, palette)
				if color == WHITE{
					//the white color in sprites is transparent
					continue
				}
				pos :=  (int(*Ly)*WIDTH*4) + (int(spriteX) + int((^Xpixel)&0x07)) * 4
				(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 0] = color
				(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 1] = color
				(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 2] = color
				(*[BUFFER_SIZE]uint8)(p_pixels)[pos + 3] = 255
			}
		}
	}
}

func getPixelColor(lower, upper, bitNum uint8, palette uint8) uint8{
	var mask uint8 = (1 << bitNum)
	if bitNum < 2 {
		return getColor(((upper & mask) << (1-bitNum)) | ((lower & mask) >> bitNum), palette)
	}
	return getColor((upper & mask) >> (bitNum - 1) | ((lower & mask) >> bitNum), palette)
}

func getColor(code uint8, palette uint8) uint8{
	return colors[((palette & (0x03 << (2*code))) >> (2*code))]
}

func setLcdStatMode(mode uint8) {
	*LcdStatus &= 0xFC //clean last mode
	*LcdStatus |= mode //set
}
<|endoftext|>


<commit_before>package dilemma

import (
	"fmt"
	"os"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	// Empty means key code information is not applicable
	Empty Key = iota
	up
	down
	enter
	// CtrlC means CTRL-C was pressed.
	// Usually this means the user wants to send SIGINT.
	CtrlC
)

const (
	exitNo exitStatus = iota
	exitYes
)

const (
	helpNo helpStatus = iota
	helpYes
)

// Key represents keys pressed by the user.
type Key int

type input struct {
	key Key
	err error
}

type exitStatus int

type helpStatus int

// Config holds the configuration to display a list of options
// for a user to select.
type Config struct {
	Title   string
	Options []string
	Help    string
}

func invertColours() {
	fmt.Print("\033[7m")
}

func resetStyle() {
	fmt.Print("\033[0m")
}

func moveUp() {
	fmt.Print("\033[1A")
}

func clearLine() {
	fmt.Print("\033[2K\r")
}

func hideCursor() {
	fmt.Print("\033[?25l")
}

func showCursor() {
	fmt.Print("\033[?25h")
}

func lineCount(s string) int {
	var count int
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			count++
		}
	}
	return count + 1 // also count the first line
}

func inputLoop(keyPresses chan<- input, exitAck chan exitStatus) {
	buf := make([]byte, 128)
	for {
		n, err := os.Stdin.Read(buf)
		if err != nil {
			keyPresses <- input{key: Empty, err: err}
			return
		}
		bufstr := string(buf[:n])
		switch {
		case bufstr == "\033[A":
			keyPresses <- input{key: up}
		case bufstr == "\033[B":
			keyPresses <- input{key: down}
		case bufstr == "\x0D":
			keyPresses <- input{key: enter}
		case bufstr == "\x03":
			keyPresses <- input{key: CtrlC}
		default:
			keyPresses <- input{key: Empty}
		}
		if exitYes == <-exitAck {
			return
		}
	}
}

// Prompt asks the user to select an option from the list. The selected option
// is returned in the first return value. The second return value is set to
// Empty unless the user presses CTRL-C (indicating she wants to signal SIGINT)
// in which case the value will be CtrlC.
func Prompt(config Config) (string, Key, error) {
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		return "", Empty, err
	}
	defer terminal.Restore(0, oldState)

	hideCursor()
	defer showCursor()

	// ensure we always exit with the cursor at the beginning of the line so the
	// terminal prompt prints in the expected place
	defer func() {
		fmt.Print("\r")
	}()

	keyPresses := make(chan input, 1)
	exitAck := make(chan exitStatus)
	go inputLoop(keyPresses, exitAck)

	var selectionIndex int

	draw := func(help helpStatus) {
		fmt.Println(config.Title)
		fmt.Print("\r")
		for i, v := range config.Options {
			fmt.Print("  ")
			if i == selectionIndex {
				invertColours()
			}
			fmt.Printf("%s\n", v)
			if i == selectionIndex {
				resetStyle()
			}
			fmt.Print("\r")
		}
		if help == helpYes {
			fmt.Print(config.Help)
		}
	}

	clear := func(help helpStatus) {
		lines := lineCount(config.Title) + len(config.Options)

		if help == helpYes {
			lines = lines + lineCount(config.Help)
		} else {
			// the last line is an empty line but a line nonetheless
			lines = lines + 1
		}

		// since we're on one of the lines already move up one less
		for i := 0; i < lines-1; i++ {
			clearLine()
			moveUp()
		}
	}

	redraw := func() func(helpStatus) {
		var showHelp helpStatus
		return func(help helpStatus) {
			clear(showHelp)
			showHelp = help
			draw(showHelp)
		}
	}()

	draw(helpNo)

	for {
		input := <-keyPresses
		if input.err != nil {
			redraw(helpNo) // to clear help
			return "", Empty, input.err
		}
		switch input.key {
		case enter:
			exitAck <- exitYes
			redraw(helpNo) // to clear help
			return config.Options[selectionIndex], Empty, nil
		case CtrlC:
			exitAck <- exitYes
			redraw(helpNo) // to clear help
			return "", CtrlC, nil
		case up:
			selectionIndex = ((selectionIndex - 1) + len(config.Options)) % len(config.Options)
			redraw(helpNo)
		case down:
			selectionIndex = ((selectionIndex + 1) + len(config.Options)) % len(config.Options)
			redraw(helpNo)
		case Empty:
			redraw(helpYes)
		}
		exitAck <- exitNo
	}
}
<commit_msg>package doc<commit_after>// Package dilemma allows creating a TTY selection prompt.
package dilemma

import (
	"fmt"
	"os"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	// Empty means key code information is not applicable
	Empty Key = iota
	up
	down
	enter
	// CtrlC means CTRL-C was pressed.
	// Usually this means the user wants to send SIGINT.
	CtrlC
)

const (
	exitNo exitStatus = iota
	exitYes
)

const (
	helpNo helpStatus = iota
	helpYes
)

// Key represents keys pressed by the user.
type Key int

type input struct {
	key Key
	err error
}

type exitStatus int

type helpStatus int

// Config holds the configuration to display a list of options
// for a user to select.
type Config struct {
	Title   string
	Options []string
	Help    string
}

func invertColours() {
	fmt.Print("\033[7m")
}

func resetStyle() {
	fmt.Print("\033[0m")
}

func moveUp() {
	fmt.Print("\033[1A")
}

func clearLine() {
	fmt.Print("\033[2K\r")
}

func hideCursor() {
	fmt.Print("\033[?25l")
}

func showCursor() {
	fmt.Print("\033[?25h")
}

func lineCount(s string) int {
	var count int
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			count++
		}
	}
	return count + 1 // also count the first line
}

func inputLoop(keyPresses chan<- input, exitAck chan exitStatus) {
	buf := make([]byte, 128)
	for {
		n, err := os.Stdin.Read(buf)
		if err != nil {
			keyPresses <- input{key: Empty, err: err}
			return
		}
		bufstr := string(buf[:n])
		switch {
		case bufstr == "\033[A":
			keyPresses <- input{key: up}
		case bufstr == "\033[B":
			keyPresses <- input{key: down}
		case bufstr == "\x0D":
			keyPresses <- input{key: enter}
		case bufstr == "\x03":
			keyPresses <- input{key: CtrlC}
		default:
			keyPresses <- input{key: Empty}
		}
		if exitYes == <-exitAck {
			return
		}
	}
}

// Prompt asks the user to select an option from the list. The selected option
// is returned in the first return value. The second return value is set to
// Empty unless the user presses CTRL-C (indicating she wants to signal SIGINT)
// in which case the value will be CtrlC.
func Prompt(config Config) (string, Key, error) {
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		return "", Empty, err
	}
	defer terminal.Restore(0, oldState)

	hideCursor()
	defer showCursor()

	// ensure we always exit with the cursor at the beginning of the line so the
	// terminal prompt prints in the expected place
	defer func() {
		fmt.Print("\r")
	}()

	keyPresses := make(chan input, 1)
	exitAck := make(chan exitStatus)
	go inputLoop(keyPresses, exitAck)

	var selectionIndex int

	draw := func(help helpStatus) {
		fmt.Println(config.Title)
		fmt.Print("\r")
		for i, v := range config.Options {
			fmt.Print("  ")
			if i == selectionIndex {
				invertColours()
			}
			fmt.Printf("%s\n", v)
			if i == selectionIndex {
				resetStyle()
			}
			fmt.Print("\r")
		}
		if help == helpYes {
			fmt.Print(config.Help)
		}
	}

	clear := func(help helpStatus) {
		lines := lineCount(config.Title) + len(config.Options)

		if help == helpYes {
			lines = lines + lineCount(config.Help)
		} else {
			// the last line is an empty line but a line nonetheless
			lines = lines + 1
		}

		// since we're on one of the lines already move up one less
		for i := 0; i < lines-1; i++ {
			clearLine()
			moveUp()
		}
	}

	redraw := func() func(helpStatus) {
		var showHelp helpStatus
		return func(help helpStatus) {
			clear(showHelp)
			showHelp = help
			draw(showHelp)
		}
	}()

	draw(helpNo)

	for {
		input := <-keyPresses
		if input.err != nil {
			redraw(helpNo) // to clear help
			return "", Empty, input.err
		}
		switch input.key {
		case enter:
			exitAck <- exitYes
			redraw(helpNo) // to clear help
			return config.Options[selectionIndex], Empty, nil
		case CtrlC:
			exitAck <- exitYes
			redraw(helpNo) // to clear help
			return "", CtrlC, nil
		case up:
			selectionIndex = ((selectionIndex - 1) + len(config.Options)) % len(config.Options)
			redraw(helpNo)
		case down:
			selectionIndex = ((selectionIndex + 1) + len(config.Options)) % len(config.Options)
			redraw(helpNo)
		case Empty:
			redraw(helpYes)
		}
		exitAck <- exitNo
	}
}
<|endoftext|>