<commit_before>package static

import (
	"io"
	"net/http"
	"os"
	"testing"
	"time"

	. "gopkg.in/go-playground/assert.v1"
)

// NOTES:
// - Run "go test" to run tests
// - Run "gocov test | gocov report" to report on test converage by file
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
//
// or
//
// -- may be a good idea to change to output path to somewherelike /tmp
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
//

var testDirFile *DirFile

func getGOPATH() string {
	gopath := os.Getenv("GOPATH")

	if len(gopath) == 0 {
		panic("$GOPATH environment is not setup correctly, ending transmission!")
	}

	return gopath
}

func TestMain(m *testing.M) {

	// setup
	testDirFile = &DirFile{
		Path:    "/static/test-files/teststart",
		Name:    "teststart",
		Size:    170,
		Mode:    os.FileMode(2147484141),
		ModTime: 1446650128,
		IsDir:   true,
		Compressed: `
`,
		Files: []*DirFile{{
			Path:    "/static/test-files/teststart/symlinkeddir",
			Name:    "symlinkeddir",
			Size:    136,
			Mode:    os.FileMode(2147484141),
			ModTime: 1446648191,
			IsDir:   true,
			Compressed: `
`,
			Files: []*DirFile{{
				Path:    "/static/test-files/teststart/symlinkeddir/realdir",
				Name:    "realdir",
				Size:    136,
				Mode:    os.FileMode(2147484141),
				ModTime: 1446648584,
				IsDir:   true,
				Compressed: `
`,
				Files: []*DirFile{{
					Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir",
					Name:    "doublesymlinkeddir",
					Size:    136,
					Mode:    os.FileMode(2147484141),
					ModTime: 1447163695,
					IsDir:   true,
					Compressed: `
`,
					Files: []*DirFile{{
						Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir/doublesymlinkedfile.txt",
						Name:    "doublesymlinkedfile.txt",
						Size:    5,
						Mode:    os.FileMode(420),
						ModTime: 1446648265,
						IsDir:   false,
						Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
						Files: []*DirFile{},
					},
						{
							Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir/triplesymlinkeddir",
							Name:    "triplesymlinkeddir",
							Size:    102,
							Mode:    os.FileMode(2147484141),
							ModTime: 1447163709,
							IsDir:   true,
							Compressed: `
`,
							Files: []*DirFile{{
								Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir/triplesymlinkeddir/triplefile.txt",
								Name:    "triplefile.txt",
								Size:    5,
								Mode:    os.FileMode(420),
								ModTime: 1447163511,
								IsDir:   false,
								Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
								Files: []*DirFile{},
							},
							},
						},
					},
				},
					{
						Path:    "/static/test-files/teststart/symlinkeddir/realdir/realdirfile.txt",
						Name:    "realdirfile.txt",
						Size:    5,
						Mode:    os.FileMode(420),
						ModTime: 1446648207,
						IsDir:   false,
						Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
						Files: []*DirFile{},
					},
				},
			},
				{
					Path:    "/static/test-files/teststart/symlinkeddir/symlinkeddirfile.txt",
					Name:    "symlinkeddirfile.txt",
					Size:    5,
					Mode:    os.FileMode(420),
					ModTime: 1446647769,
					IsDir:   false,
					Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
					Files: []*DirFile{},
				},
			},
		},
			{
				Path:    "/static/test-files/teststart/plainfile.txt",
				Name:    "plainfile.txt",
				Size:    10,
				Mode:    os.FileMode(420),
				ModTime: 1446650128,
				IsDir:   false,
				Compressed: `
H4sIAAAJbogA/ypIzMnMS0ksSeQCBAAA//9+mKzVCgAAAA==
`,
				Files: []*DirFile{},
			},
			{
				Path:    "/static/test-files/teststart/symlinkedfile.txt",
				Name:    "symlinkedfile.txt",
				Size:    5,
				Mode:    os.FileMode(420),
				ModTime: 1446647746,
				IsDir:   false,
				Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
				Files: []*DirFile{},
			},
		},
	}

	os.Exit(m.Run())

	// teardown
}

func TestStaticNew(t *testing.T) {

	config := &Config{
		UseStaticFiles: true,
		AbsPkgPath:     getGOPATH() + "/src/github.com/joeybloggs/statics",
	}

	staticFiles, err := New(config, testDirFile)
	Equal(t, err, nil)
	NotEqual(t, staticFiles, nil)

	go func(sf *Files) {

		http.Handle("/static/", http.StripPrefix("/", http.FileServer(sf.FS())))
		http.ListenAndServe("127.0.0.1:3006", nil)

	}(staticFiles)

	time.Sleep(1000)

	f, err := staticFiles.GetHTTPFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)

	fis, err := f.Readdir(-1)
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "not a directory")

	fi, err := f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "plainfile.txt")
	Equal(t, fi.Size(), int64(10))
	Equal(t, fi.IsDir(), false)
	Equal(t, fi.Mode(), os.FileMode(420))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	err = f.Close()
	Equal(t, err, nil)

	f, err = staticFiles.GetHTTPFile("/static/test-files/teststart")
	Equal(t, err, nil)

	fi, err = f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "teststart")
	Equal(t, fi.Size(), int64(170))
	Equal(t, fi.IsDir(), true)
	Equal(t, fi.Mode(), os.FileMode(2147484141))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	var j int

	for err != io.EOF {

		fis, err = f.Readdir(2)

		switch j {
		case 0:
			Equal(t, len(fis), 2)
		case 1:
			Equal(t, len(fis), 1)
		case 2:
			Equal(t, len(fis), 0)
		}

		j++
	}

	err = f.Close()
	Equal(t, err, nil)

	b, err := staticFiles.ReadFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)
	Equal(t, string(b), "palindata\n")

	b, err = staticFiles.ReadFile("nonexistantfile")
	NotEqual(t, err, nil)

	bs, err := staticFiles.ReadFiles("/static/test-files/teststart", false)
	Equal(t, err, nil)
	Equal(t, len(bs), 2)
	Equal(t, string(bs["/static/test-files/teststart/plainfile.txt"]), "palindata\n")
	Equal(t, string(bs["/static/test-files/teststart/symlinkedfile.txt"]), "data\n")

	bs, err = staticFiles.ReadFiles("/static/test-files/teststart", true)
	Equal(t, err, nil)
	Equal(t, len(bs), 6)

	bs, err = staticFiles.ReadFiles("nonexistantdir", false)
	NotEqual(t, err, nil)

	fis, err = staticFiles.ReadDir("/static/test-files/teststart")
	Equal(t, err, nil)
	Equal(t, len(fis), 3)
	Equal(t, fis[0].Name(), "plainfile.txt")
	Equal(t, fis[1].Name(), "symlinkeddir")
	Equal(t, fis[2].Name(), "symlinkedfile.txt")

	fis, err = staticFiles.ReadDir("nonexistantdir")
	NotEqual(t, err, nil)

	client := &http.Client{}

	req, err := http.NewRequest("GET", "http://127.0.0.1:3006/static/test-files/teststart/plainfile.txt", nil)
	Equal(t, err, nil)

	resp, err := client.Do(req)
	Equal(t, err, nil)
	Equal(t, resp.StatusCode, http.StatusOK)

	defer resp.Body.Close()
}

func TestLocalNew(t *testing.T) {

	config := &Config{
		UseStaticFiles: false,
		AbsPkgPath:     getGOPATH() + "/src/github.com/joeybloggs/statics",
	}

	staticFiles, err := New(config, testDirFile)
	Equal(t, err, nil)
	NotEqual(t, staticFiles, nil)

	f, err := staticFiles.GetHTTPFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)

	fis, err := f.Readdir(-1)
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "readdirent: invalid argument")

	fi, err := f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "plainfile.txt")
	Equal(t, fi.Size(), int64(10))
	Equal(t, fi.IsDir(), false)
	Equal(t, fi.Mode(), os.FileMode(420))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	err = f.Close()
	Equal(t, err, nil)

	f, err = staticFiles.GetHTTPFile("/static/test-files/teststart")
	Equal(t, err, nil)

	fi, err = f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "teststart")
	Equal(t, fi.Size(), int64(170))
	Equal(t, fi.IsDir(), true)
	Equal(t, fi.Mode(), os.FileMode(2147484141))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	var j int

	for err != io.EOF {

		fis, err = f.Readdir(2)

		switch j {
		case 0:
			Equal(t, len(fis), 2)
		case 1:
			Equal(t, len(fis), 1)
		case 2:
			Equal(t, len(fis), 0)
		}

		j++
	}

	err = f.Close()
	Equal(t, err, nil)

	b, err := staticFiles.ReadFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)
	Equal(t, string(b), "palindata\n")

	b, err = staticFiles.ReadFile("nonexistantfile")
	NotEqual(t, err, nil)

	bs, err := staticFiles.ReadFiles("/static/test-files/teststart", false)
	Equal(t, err, nil)
	Equal(t, len(bs), 2)
	Equal(t, string(bs["/static/test-files/teststart/plainfile.txt"]), "palindata\n")
	Equal(t, string(bs["/static/test-files/teststart/symlinkedfile.txt"]), "data\n")

	bs, err = staticFiles.ReadFiles("/static/test-files/teststart", true)
	Equal(t, err, nil)
	Equal(t, len(bs), 6)

	bs, err = staticFiles.ReadFiles("nonexistantdir", false)
	NotEqual(t, err, nil)

	fis, err = staticFiles.ReadDir("/static/test-files/teststart")
	Equal(t, err, nil)
	Equal(t, len(fis), 3)
	Equal(t, fis[0].Name(), "plainfile.txt")
	Equal(t, fis[1].Name(), "symlinkeddir")
	Equal(t, fis[2].Name(), "symlinkedfile.txt")

	fis, err = staticFiles.ReadDir("nonexistantdir")
	NotEqual(t, err, nil)
}

func TestBadLocalAbsPath(t *testing.T) {

	config := &Config{
		UseStaticFiles: false,
		AbsPkgPath:     "../github.com/joeybloggs/statics",
	}

	staticFiles, err := New(config, testDirFile)
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "AbsPkgPath is required when not using static files otherwise the static package has no idea where to grab local files from when your package is used from within another package.")
	Equal(t, staticFiles, nil)
}
<commit_msg>Add/Finish HTTP request tests<commit_after>package static

import (
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
	"time"

	. "gopkg.in/go-playground/assert.v1"
)

// NOTES:
// - Run "go test" to run tests
// - Run "gocov test | gocov report" to report on test converage by file
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
//
// or
//
// -- may be a good idea to change to output path to somewherelike /tmp
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
//

var testDirFile *DirFile

func getGOPATH() string {
	gopath := os.Getenv("GOPATH")

	if len(gopath) == 0 {
		panic("$GOPATH environment is not setup correctly, ending transmission!")
	}

	return gopath
}

func TestMain(m *testing.M) {

	// setup
	testDirFile = &DirFile{
		Path:    "/static/test-files/teststart",
		Name:    "teststart",
		Size:    170,
		Mode:    os.FileMode(2147484141),
		ModTime: 1446650128,
		IsDir:   true,
		Compressed: `
`,
		Files: []*DirFile{{
			Path:    "/static/test-files/teststart/symlinkeddir",
			Name:    "symlinkeddir",
			Size:    136,
			Mode:    os.FileMode(2147484141),
			ModTime: 1446648191,
			IsDir:   true,
			Compressed: `
`,
			Files: []*DirFile{{
				Path:    "/static/test-files/teststart/symlinkeddir/realdir",
				Name:    "realdir",
				Size:    136,
				Mode:    os.FileMode(2147484141),
				ModTime: 1446648584,
				IsDir:   true,
				Compressed: `
`,
				Files: []*DirFile{{
					Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir",
					Name:    "doublesymlinkeddir",
					Size:    136,
					Mode:    os.FileMode(2147484141),
					ModTime: 1447163695,
					IsDir:   true,
					Compressed: `
`,
					Files: []*DirFile{{
						Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir/doublesymlinkedfile.txt",
						Name:    "doublesymlinkedfile.txt",
						Size:    5,
						Mode:    os.FileMode(420),
						ModTime: 1446648265,
						IsDir:   false,
						Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
						Files: []*DirFile{},
					},
						{
							Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir/triplesymlinkeddir",
							Name:    "triplesymlinkeddir",
							Size:    102,
							Mode:    os.FileMode(2147484141),
							ModTime: 1447163709,
							IsDir:   true,
							Compressed: `
`,
							Files: []*DirFile{{
								Path:    "/static/test-files/teststart/symlinkeddir/realdir/doublesymlinkeddir/triplesymlinkeddir/triplefile.txt",
								Name:    "triplefile.txt",
								Size:    5,
								Mode:    os.FileMode(420),
								ModTime: 1447163511,
								IsDir:   false,
								Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
								Files: []*DirFile{},
							},
							},
						},
					},
				},
					{
						Path:    "/static/test-files/teststart/symlinkeddir/realdir/realdirfile.txt",
						Name:    "realdirfile.txt",
						Size:    5,
						Mode:    os.FileMode(420),
						ModTime: 1446648207,
						IsDir:   false,
						Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
						Files: []*DirFile{},
					},
				},
			},
				{
					Path:    "/static/test-files/teststart/symlinkeddir/symlinkeddirfile.txt",
					Name:    "symlinkeddirfile.txt",
					Size:    5,
					Mode:    os.FileMode(420),
					ModTime: 1446647769,
					IsDir:   false,
					Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
					Files: []*DirFile{},
				},
			},
		},
			{
				Path:    "/static/test-files/teststart/plainfile.txt",
				Name:    "plainfile.txt",
				Size:    10,
				Mode:    os.FileMode(420),
				ModTime: 1446650128,
				IsDir:   false,
				Compressed: `
H4sIAAAJbogA/ypIzMnMS0ksSeQCBAAA//9+mKzVCgAAAA==
`,
				Files: []*DirFile{},
			},
			{
				Path:    "/static/test-files/teststart/symlinkedfile.txt",
				Name:    "symlinkedfile.txt",
				Size:    5,
				Mode:    os.FileMode(420),
				ModTime: 1446647746,
				IsDir:   false,
				Compressed: `
H4sIAAAJbogA/0pJLEnkAgQAAP//gsXB5gUAAAA=
`,
				Files: []*DirFile{},
			},
		},
	}

	os.Exit(m.Run())

	// teardown
}

func TestStaticNew(t *testing.T) {

	config := &Config{
		UseStaticFiles: true,
		AbsPkgPath:     getGOPATH() + "/src/github.com/joeybloggs/statics",
	}

	staticFiles, err := New(config, testDirFile)
	Equal(t, err, nil)
	NotEqual(t, staticFiles, nil)

	go func(sf *Files) {

		http.Handle("/static/", http.StripPrefix("/", http.FileServer(sf.FS())))
		http.ListenAndServe("127.0.0.1:3006", nil)

	}(staticFiles)

	time.Sleep(1000)

	f, err := staticFiles.GetHTTPFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)

	fis, err := f.Readdir(-1)
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "not a directory")

	fi, err := f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "plainfile.txt")
	Equal(t, fi.Size(), int64(10))
	Equal(t, fi.IsDir(), false)
	Equal(t, fi.Mode(), os.FileMode(420))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	err = f.Close()
	Equal(t, err, nil)

	f, err = staticFiles.GetHTTPFile("/static/test-files/teststart")
	Equal(t, err, nil)

	fi, err = f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "teststart")
	Equal(t, fi.Size(), int64(170))
	Equal(t, fi.IsDir(), true)
	Equal(t, fi.Mode(), os.FileMode(2147484141))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	var j int

	for err != io.EOF {

		fis, err = f.Readdir(2)

		switch j {
		case 0:
			Equal(t, len(fis), 2)
		case 1:
			Equal(t, len(fis), 1)
		case 2:
			Equal(t, len(fis), 0)
		}

		j++
	}

	err = f.Close()
	Equal(t, err, nil)

	b, err := staticFiles.ReadFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)
	Equal(t, string(b), "palindata\n")

	b, err = staticFiles.ReadFile("nonexistantfile")
	NotEqual(t, err, nil)

	bs, err := staticFiles.ReadFiles("/static/test-files/teststart", false)
	Equal(t, err, nil)
	Equal(t, len(bs), 2)
	Equal(t, string(bs["/static/test-files/teststart/plainfile.txt"]), "palindata\n")
	Equal(t, string(bs["/static/test-files/teststart/symlinkedfile.txt"]), "data\n")

	bs, err = staticFiles.ReadFiles("/static/test-files/teststart", true)
	Equal(t, err, nil)
	Equal(t, len(bs), 6)

	bs, err = staticFiles.ReadFiles("nonexistantdir", false)
	NotEqual(t, err, nil)

	fis, err = staticFiles.ReadDir("/static/test-files/teststart")
	Equal(t, err, nil)
	Equal(t, len(fis), 3)
	Equal(t, fis[0].Name(), "plainfile.txt")
	Equal(t, fis[1].Name(), "symlinkeddir")
	Equal(t, fis[2].Name(), "symlinkedfile.txt")

	fis, err = staticFiles.ReadDir("nonexistantdir")
	NotEqual(t, err, nil)

	client := &http.Client{}

	req, err := http.NewRequest("GET", "http://127.0.0.1:3006/static/test-files/teststart/plainfile.txt", nil)
	Equal(t, err, nil)

	resp, err := client.Do(req)
	Equal(t, err, nil)
	Equal(t, resp.StatusCode, http.StatusOK)

	bytes, err := ioutil.ReadAll(resp.Body)
	Equal(t, err, nil)
	Equal(t, string(bytes), "palindata\n")

	defer resp.Body.Close()
}

func TestLocalNew(t *testing.T) {

	config := &Config{
		UseStaticFiles: false,
		AbsPkgPath:     getGOPATH() + "/src/github.com/joeybloggs/statics",
	}

	staticFiles, err := New(config, testDirFile)
	Equal(t, err, nil)
	NotEqual(t, staticFiles, nil)

	f, err := staticFiles.GetHTTPFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)

	fis, err := f.Readdir(-1)
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "readdirent: invalid argument")

	fi, err := f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "plainfile.txt")
	Equal(t, fi.Size(), int64(10))
	Equal(t, fi.IsDir(), false)
	Equal(t, fi.Mode(), os.FileMode(420))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	err = f.Close()
	Equal(t, err, nil)

	f, err = staticFiles.GetHTTPFile("/static/test-files/teststart")
	Equal(t, err, nil)

	fi, err = f.Stat()
	Equal(t, err, nil)
	Equal(t, fi.Name(), "teststart")
	Equal(t, fi.Size(), int64(170))
	Equal(t, fi.IsDir(), true)
	Equal(t, fi.Mode(), os.FileMode(2147484141))
	Equal(t, fi.ModTime(), time.Unix(1446650128, 0))
	NotEqual(t, fi.Sys(), nil)

	var j int

	for err != io.EOF {

		fis, err = f.Readdir(2)

		switch j {
		case 0:
			Equal(t, len(fis), 2)
		case 1:
			Equal(t, len(fis), 1)
		case 2:
			Equal(t, len(fis), 0)
		}

		j++
	}

	err = f.Close()
	Equal(t, err, nil)

	b, err := staticFiles.ReadFile("/static/test-files/teststart/plainfile.txt")
	Equal(t, err, nil)
	Equal(t, string(b), "palindata\n")

	b, err = staticFiles.ReadFile("nonexistantfile")
	NotEqual(t, err, nil)

	bs, err := staticFiles.ReadFiles("/static/test-files/teststart", false)
	Equal(t, err, nil)
	Equal(t, len(bs), 2)
	Equal(t, string(bs["/static/test-files/teststart/plainfile.txt"]), "palindata\n")
	Equal(t, string(bs["/static/test-files/teststart/symlinkedfile.txt"]), "data\n")

	bs, err = staticFiles.ReadFiles("/static/test-files/teststart", true)
	Equal(t, err, nil)
	Equal(t, len(bs), 6)

	bs, err = staticFiles.ReadFiles("nonexistantdir", false)
	NotEqual(t, err, nil)

	fis, err = staticFiles.ReadDir("/static/test-files/teststart")
	Equal(t, err, nil)
	Equal(t, len(fis), 3)
	Equal(t, fis[0].Name(), "plainfile.txt")
	Equal(t, fis[1].Name(), "symlinkeddir")
	Equal(t, fis[2].Name(), "symlinkedfile.txt")

	fis, err = staticFiles.ReadDir("nonexistantdir")
	NotEqual(t, err, nil)

	client := &http.Client{}

	req, err := http.NewRequest("GET", "http://127.0.0.1:3006/static/test-files/teststart/plainfile.txt", nil)
	Equal(t, err, nil)

	resp, err := client.Do(req)
	Equal(t, err, nil)
	Equal(t, resp.StatusCode, http.StatusOK)

	bytes, err := ioutil.ReadAll(resp.Body)
	Equal(t, err, nil)
	Equal(t, string(bytes), "palindata\n")

	defer resp.Body.Close()
}

func TestBadLocalAbsPath(t *testing.T) {

	config := &Config{
		UseStaticFiles: false,
		AbsPkgPath:     "../github.com/joeybloggs/statics",
	}

	staticFiles, err := New(config, testDirFile)
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "AbsPkgPath is required when not using static files otherwise the static package has no idea where to grab local files from when your package is used from within another package.")
	Equal(t, staticFiles, nil)
}
<|endoftext|>


<commit_before>package invoices

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
	"github.com/go-swagger/go-swagger/client"
	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/swag"

	strfmt "github.com/go-swagger/go-swagger/strfmt"
)

// NewGetInvoiceAsPDFParams creates a new GetInvoiceAsPDFParams object
// with the default values initialized.
func NewGetInvoiceAsPDFParams() *GetInvoiceAsPDFParams {
	var (
		orderDefault                     string = string("DESC")
		orderByDefault                   string = string("created")
		recordsDefault                   int32  = int32(10)
		showPlanOnlyWhenAmbiguousDefault bool   = bool(true)
		showZeroCostDefault              bool   = bool(true)
	)
	return &GetInvoiceAsPDFParams{
		Order:                     &orderDefault,
		OrderBy:                   &orderByDefault,
		Records:                   &recordsDefault,
		ShowPlanOnlyWhenAmbiguous: &showPlanOnlyWhenAmbiguousDefault,
		ShowZeroCost:              &showZeroCostDefault,
	}
}

/*GetInvoiceAsPDFParams contains all the parameters to send to the API endpoint
for the get invoice as p d f operation typically these are written to a http.Request
*/
type GetInvoiceAsPDFParams struct {

	/*ID
	  The ID of the invoice.

	*/
	ID string
	/*IncludeFooter*/
	IncludeFooter *bool
	/*IncludeRetired
	  Whether retired products should be returned.

	*/
	IncludeRetired *bool
	/*InclusiveEnd
	  Whether to present in the Invoice date range only those dates whose entirety are involved in the billing period. In other words: when true, this subtracts 1 from the "period end" date shown to the customer.

	*/
	InclusiveEnd *bool
	/*Offset
	  The offset from the first invoice to return.

	*/
	Offset *int32
	/*Order
	  Ihe direction of any ordering, either ASC or DESC.

	*/
	Order *string
	/*OrderBy
	  Specify a field used to order the result set.

	*/
	OrderBy *string
	/*Organizations
	  A list of organization-IDs used to restrict the scope of API calls.

	*/
	Organizations []string
	/*Records
	  The maximum number of invoices to return.

	*/
	Records *int32
	/*ShowPlanOnlyWhenAmbiguous
	  Whether to state which plan the Invoice lines came from, regardless of whether there's only one plan involved in this Invoice.

	*/
	ShowPlanOnlyWhenAmbiguous *bool
	/*ShowZeroCost
	  Whether to show zero-cost lines.

	*/
	ShowZeroCost *bool
	/*TierBreakdown
	  Whether to provide a breakdown of charge tiering.

	*/
	TierBreakdown *bool
}

// WithID adds the id to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithID(id string) *GetInvoiceAsPDFParams {
	o.ID = id
	return o
}

// WithIncludeFooter adds the includeFooter to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithIncludeFooter(includeFooter *bool) *GetInvoiceAsPDFParams {
	o.IncludeFooter = includeFooter
	return o
}

// WithIncludeRetired adds the includeRetired to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithIncludeRetired(includeRetired *bool) *GetInvoiceAsPDFParams {
	o.IncludeRetired = includeRetired
	return o
}

// WithInclusiveEnd adds the inclusiveEnd to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithInclusiveEnd(inclusiveEnd *bool) *GetInvoiceAsPDFParams {
	o.InclusiveEnd = inclusiveEnd
	return o
}

// WithOffset adds the offset to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOffset(offset *int32) *GetInvoiceAsPDFParams {
	o.Offset = offset
	return o
}

// WithOrder adds the order to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOrder(order *string) *GetInvoiceAsPDFParams {
	o.Order = order
	return o
}

// WithOrderBy adds the orderBy to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOrderBy(orderBy *string) *GetInvoiceAsPDFParams {
	o.OrderBy = orderBy
	return o
}

// WithOrganizations adds the organizations to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOrganizations(organizations []string) *GetInvoiceAsPDFParams {
	o.Organizations = organizations
	return o
}

// WithRecords adds the records to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithRecords(records *int32) *GetInvoiceAsPDFParams {
	o.Records = records
	return o
}

// WithShowPlanOnlyWhenAmbiguous adds the showPlanOnlyWhenAmbiguous to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithShowPlanOnlyWhenAmbiguous(showPlanOnlyWhenAmbiguous *bool) *GetInvoiceAsPDFParams {
	o.ShowPlanOnlyWhenAmbiguous = showPlanOnlyWhenAmbiguous
	return o
}

// WithShowZeroCost adds the showZeroCost to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithShowZeroCost(showZeroCost *bool) *GetInvoiceAsPDFParams {
	o.ShowZeroCost = showZeroCost
	return o
}

// WithTierBreakdown adds the tierBreakdown to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithTierBreakdown(tierBreakdown *bool) *GetInvoiceAsPDFParams {
	o.TierBreakdown = tierBreakdown
	return o
}

// WriteToRequest writes these params to a swagger request
func (o *GetInvoiceAsPDFParams) WriteToRequest(r client.Request, reg strfmt.Registry) error {

	var res []error

	// path param ID
	if err := r.SetPathParam("ID", o.ID); err != nil {
		return err
	}

	if o.IncludeFooter != nil {

		// query param include_footer
		var qrIncludeFooter bool
		if o.IncludeFooter != nil {
			qrIncludeFooter = *o.IncludeFooter
		}
		qIncludeFooter := swag.FormatBool(qrIncludeFooter)
		if qIncludeFooter != "" {
			if err := r.SetQueryParam("include_footer", qIncludeFooter); err != nil {
				return err
			}
		}

	}

	if o.IncludeRetired != nil {

		// query param include_retired
		var qrIncludeRetired bool
		if o.IncludeRetired != nil {
			qrIncludeRetired = *o.IncludeRetired
		}
		qIncludeRetired := swag.FormatBool(qrIncludeRetired)
		if qIncludeRetired != "" {
			if err := r.SetQueryParam("include_retired", qIncludeRetired); err != nil {
				return err
			}
		}

	}

	if o.InclusiveEnd != nil {

		// query param inclusive_end
		var qrInclusiveEnd bool
		if o.InclusiveEnd != nil {
			qrInclusiveEnd = *o.InclusiveEnd
		}
		qInclusiveEnd := swag.FormatBool(qrInclusiveEnd)
		if qInclusiveEnd != "" {
			if err := r.SetQueryParam("inclusive_end", qInclusiveEnd); err != nil {
				return err
			}
		}

	}

	if o.Offset != nil {

		// query param offset
		var qrOffset int32
		if o.Offset != nil {
			qrOffset = *o.Offset
		}
		qOffset := swag.FormatInt32(qrOffset)
		if qOffset != "" {
			if err := r.SetQueryParam("offset", qOffset); err != nil {
				return err
			}
		}

	}

	if o.Order != nil {

		// query param order
		var qrOrder string
		if o.Order != nil {
			qrOrder = *o.Order
		}
		qOrder := qrOrder
		if qOrder != "" {
			if err := r.SetQueryParam("order", qOrder); err != nil {
				return err
			}
		}

	}

	if o.OrderBy != nil {

		// query param order_by
		var qrOrderBy string
		if o.OrderBy != nil {
			qrOrderBy = *o.OrderBy
		}
		qOrderBy := qrOrderBy
		if qOrderBy != "" {
			if err := r.SetQueryParam("order_by", qOrderBy); err != nil {
				return err
			}
		}

	}

	valuesOrganizations := o.Organizations

	joinedOrganizations := swag.JoinByFormat(valuesOrganizations, "multi")
	// query array param organizations
	if err := r.SetQueryParam("organizations", joinedOrganizations...); err != nil {
		return err
	}

	if o.Records != nil {

		// query param records
		var qrRecords int32
		if o.Records != nil {
			qrRecords = *o.Records
		}
		qRecords := swag.FormatInt32(qrRecords)
		if qRecords != "" {
			if err := r.SetQueryParam("records", qRecords); err != nil {
				return err
			}
		}

	}

	if o.ShowPlanOnlyWhenAmbiguous != nil {

		// query param show_plan_only_when_ambiguous
		var qrShowPlanOnlyWhenAmbiguous bool
		if o.ShowPlanOnlyWhenAmbiguous != nil {
			qrShowPlanOnlyWhenAmbiguous = *o.ShowPlanOnlyWhenAmbiguous
		}
		qShowPlanOnlyWhenAmbiguous := swag.FormatBool(qrShowPlanOnlyWhenAmbiguous)
		if qShowPlanOnlyWhenAmbiguous != "" {
			if err := r.SetQueryParam("show_plan_only_when_ambiguous", qShowPlanOnlyWhenAmbiguous); err != nil {
				return err
			}
		}

	}

	if o.ShowZeroCost != nil {

		// query param show_zero_cost
		var qrShowZeroCost bool
		if o.ShowZeroCost != nil {
			qrShowZeroCost = *o.ShowZeroCost
		}
		qShowZeroCost := swag.FormatBool(qrShowZeroCost)
		if qShowZeroCost != "" {
			if err := r.SetQueryParam("show_zero_cost", qShowZeroCost); err != nil {
				return err
			}
		}

	}

	if o.TierBreakdown != nil {

		// query param tier_breakdown
		var qrTierBreakdown bool
		if o.TierBreakdown != nil {
			qrTierBreakdown = *o.TierBreakdown
		}
		qTierBreakdown := swag.FormatBool(qrTierBreakdown)
		if qTierBreakdown != "" {
			if err := r.SetQueryParam("tier_breakdown", qTierBreakdown); err != nil {
				return err
			}
		}

	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}
<commit_msg>regenerate<commit_after>package invoices

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
	"github.com/go-swagger/go-swagger/client"
	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/swag"

	strfmt "github.com/go-swagger/go-swagger/strfmt"
)

// NewGetInvoiceAsPDFParams creates a new GetInvoiceAsPDFParams object
// with the default values initialized.
func NewGetInvoiceAsPDFParams() *GetInvoiceAsPDFParams {
	var (
		orderDefault                     string = string("DESC")
		orderByDefault                   string = string("created")
		recordsDefault                   int32  = int32(10)
		showPlanOnlyWhenAmbiguousDefault bool   = bool(true)
		showZeroCostDefault              bool   = bool(true)
	)
	return &GetInvoiceAsPDFParams{
		Order:                     &orderDefault,
		OrderBy:                   &orderByDefault,
		Records:                   &recordsDefault,
		ShowPlanOnlyWhenAmbiguous: &showPlanOnlyWhenAmbiguousDefault,
		ShowZeroCost:              &showZeroCostDefault,
	}
}

/*GetInvoiceAsPDFParams contains all the parameters to send to the API endpoint
for the get invoice as p d f operation typically these are written to a http.Request
*/
type GetInvoiceAsPDFParams struct {

	/*ID
	  The ID of the invoice.

	*/
	ID string
	/*GroupLineItemsBy*/
	GroupLineItemsBy *string
	/*IncludeFooter*/
	IncludeFooter *bool
	/*IncludeRetired
	  Whether retired products should be returned.

	*/
	IncludeRetired *bool
	/*InclusiveEnd
	  Whether to present in the Invoice date range only those dates whose entirety are involved in the billing period. In other words: when true, this subtracts 1 from the "period end" date shown to the customer.

	*/
	InclusiveEnd *bool
	/*Offset
	  The offset from the first invoice to return.

	*/
	Offset *int32
	/*Order
	  Ihe direction of any ordering, either ASC or DESC.

	*/
	Order *string
	/*OrderBy
	  Specify a field used to order the result set.

	*/
	OrderBy *string
	/*Organizations
	  A list of organization-IDs used to restrict the scope of API calls.

	*/
	Organizations []string
	/*Records
	  The maximum number of invoices to return.

	*/
	Records *int32
	/*ShowPlanOnlyWhenAmbiguous
	  Whether to state which plan the Invoice lines came from, regardless of whether there's only one plan involved in this Invoice.

	*/
	ShowPlanOnlyWhenAmbiguous *bool
	/*ShowZeroCost
	  Whether to show zero-cost lines.

	*/
	ShowZeroCost *bool
	/*TierBreakdown
	  Whether to provide a breakdown of charge tiering.

	*/
	TierBreakdown *bool
}

// WithID adds the id to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithID(id string) *GetInvoiceAsPDFParams {
	o.ID = id
	return o
}

// WithGroupLineItemsBy adds the groupLineItemsBy to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithGroupLineItemsBy(groupLineItemsBy *string) *GetInvoiceAsPDFParams {
	o.GroupLineItemsBy = groupLineItemsBy
	return o
}

// WithIncludeFooter adds the includeFooter to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithIncludeFooter(includeFooter *bool) *GetInvoiceAsPDFParams {
	o.IncludeFooter = includeFooter
	return o
}

// WithIncludeRetired adds the includeRetired to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithIncludeRetired(includeRetired *bool) *GetInvoiceAsPDFParams {
	o.IncludeRetired = includeRetired
	return o
}

// WithInclusiveEnd adds the inclusiveEnd to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithInclusiveEnd(inclusiveEnd *bool) *GetInvoiceAsPDFParams {
	o.InclusiveEnd = inclusiveEnd
	return o
}

// WithOffset adds the offset to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOffset(offset *int32) *GetInvoiceAsPDFParams {
	o.Offset = offset
	return o
}

// WithOrder adds the order to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOrder(order *string) *GetInvoiceAsPDFParams {
	o.Order = order
	return o
}

// WithOrderBy adds the orderBy to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOrderBy(orderBy *string) *GetInvoiceAsPDFParams {
	o.OrderBy = orderBy
	return o
}

// WithOrganizations adds the organizations to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithOrganizations(organizations []string) *GetInvoiceAsPDFParams {
	o.Organizations = organizations
	return o
}

// WithRecords adds the records to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithRecords(records *int32) *GetInvoiceAsPDFParams {
	o.Records = records
	return o
}

// WithShowPlanOnlyWhenAmbiguous adds the showPlanOnlyWhenAmbiguous to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithShowPlanOnlyWhenAmbiguous(showPlanOnlyWhenAmbiguous *bool) *GetInvoiceAsPDFParams {
	o.ShowPlanOnlyWhenAmbiguous = showPlanOnlyWhenAmbiguous
	return o
}

// WithShowZeroCost adds the showZeroCost to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithShowZeroCost(showZeroCost *bool) *GetInvoiceAsPDFParams {
	o.ShowZeroCost = showZeroCost
	return o
}

// WithTierBreakdown adds the tierBreakdown to the get invoice as p d f params
func (o *GetInvoiceAsPDFParams) WithTierBreakdown(tierBreakdown *bool) *GetInvoiceAsPDFParams {
	o.TierBreakdown = tierBreakdown
	return o
}

// WriteToRequest writes these params to a swagger request
func (o *GetInvoiceAsPDFParams) WriteToRequest(r client.Request, reg strfmt.Registry) error {

	var res []error

	// path param ID
	if err := r.SetPathParam("ID", o.ID); err != nil {
		return err
	}

	if o.GroupLineItemsBy != nil {

		// query param group_line_items_by
		var qrGroupLineItemsBy string
		if o.GroupLineItemsBy != nil {
			qrGroupLineItemsBy = *o.GroupLineItemsBy
		}
		qGroupLineItemsBy := qrGroupLineItemsBy
		if qGroupLineItemsBy != "" {
			if err := r.SetQueryParam("group_line_items_by", qGroupLineItemsBy); err != nil {
				return err
			}
		}

	}

	if o.IncludeFooter != nil {

		// query param include_footer
		var qrIncludeFooter bool
		if o.IncludeFooter != nil {
			qrIncludeFooter = *o.IncludeFooter
		}
		qIncludeFooter := swag.FormatBool(qrIncludeFooter)
		if qIncludeFooter != "" {
			if err := r.SetQueryParam("include_footer", qIncludeFooter); err != nil {
				return err
			}
		}

	}

	if o.IncludeRetired != nil {

		// query param include_retired
		var qrIncludeRetired bool
		if o.IncludeRetired != nil {
			qrIncludeRetired = *o.IncludeRetired
		}
		qIncludeRetired := swag.FormatBool(qrIncludeRetired)
		if qIncludeRetired != "" {
			if err := r.SetQueryParam("include_retired", qIncludeRetired); err != nil {
				return err
			}
		}

	}

	if o.InclusiveEnd != nil {

		// query param inclusive_end
		var qrInclusiveEnd bool
		if o.InclusiveEnd != nil {
			qrInclusiveEnd = *o.InclusiveEnd
		}
		qInclusiveEnd := swag.FormatBool(qrInclusiveEnd)
		if qInclusiveEnd != "" {
			if err := r.SetQueryParam("inclusive_end", qInclusiveEnd); err != nil {
				return err
			}
		}

	}

	if o.Offset != nil {

		// query param offset
		var qrOffset int32
		if o.Offset != nil {
			qrOffset = *o.Offset
		}
		qOffset := swag.FormatInt32(qrOffset)
		if qOffset != "" {
			if err := r.SetQueryParam("offset", qOffset); err != nil {
				return err
			}
		}

	}

	if o.Order != nil {

		// query param order
		var qrOrder string
		if o.Order != nil {
			qrOrder = *o.Order
		}
		qOrder := qrOrder
		if qOrder != "" {
			if err := r.SetQueryParam("order", qOrder); err != nil {
				return err
			}
		}

	}

	if o.OrderBy != nil {

		// query param order_by
		var qrOrderBy string
		if o.OrderBy != nil {
			qrOrderBy = *o.OrderBy
		}
		qOrderBy := qrOrderBy
		if qOrderBy != "" {
			if err := r.SetQueryParam("order_by", qOrderBy); err != nil {
				return err
			}
		}

	}

	valuesOrganizations := o.Organizations

	joinedOrganizations := swag.JoinByFormat(valuesOrganizations, "multi")
	// query array param organizations
	if err := r.SetQueryParam("organizations", joinedOrganizations...); err != nil {
		return err
	}

	if o.Records != nil {

		// query param records
		var qrRecords int32
		if o.Records != nil {
			qrRecords = *o.Records
		}
		qRecords := swag.FormatInt32(qrRecords)
		if qRecords != "" {
			if err := r.SetQueryParam("records", qRecords); err != nil {
				return err
			}
		}

	}

	if o.ShowPlanOnlyWhenAmbiguous != nil {

		// query param show_plan_only_when_ambiguous
		var qrShowPlanOnlyWhenAmbiguous bool
		if o.ShowPlanOnlyWhenAmbiguous != nil {
			qrShowPlanOnlyWhenAmbiguous = *o.ShowPlanOnlyWhenAmbiguous
		}
		qShowPlanOnlyWhenAmbiguous := swag.FormatBool(qrShowPlanOnlyWhenAmbiguous)
		if qShowPlanOnlyWhenAmbiguous != "" {
			if err := r.SetQueryParam("show_plan_only_when_ambiguous", qShowPlanOnlyWhenAmbiguous); err != nil {
				return err
			}
		}

	}

	if o.ShowZeroCost != nil {

		// query param show_zero_cost
		var qrShowZeroCost bool
		if o.ShowZeroCost != nil {
			qrShowZeroCost = *o.ShowZeroCost
		}
		qShowZeroCost := swag.FormatBool(qrShowZeroCost)
		if qShowZeroCost != "" {
			if err := r.SetQueryParam("show_zero_cost", qShowZeroCost); err != nil {
				return err
			}
		}

	}

	if o.TierBreakdown != nil {

		// query param tier_breakdown
		var qrTierBreakdown bool
		if o.TierBreakdown != nil {
			qrTierBreakdown = *o.TierBreakdown
		}
		qTierBreakdown := swag.FormatBool(qrTierBreakdown)
		if qTierBreakdown != "" {
			if err := r.SetQueryParam("tier_breakdown", qTierBreakdown); err != nil {
				return err
			}
		}

	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2019 HenryYee.
//
// Licensed under the AGPL, Version 3.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.gnu.org/licenses/agpl-3.0.en.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package parser

import (
	"github.com/jinzhu/gorm"
	"github.com/pingcap/parser/types"
)

var FetchAuditRole AuditRole

type Record struct {
	SQL        string
	AffectRows int32
	Status     string
	Error      string
	Level      int32
}

type AuditRole struct {
	DMLInsertColumns               bool   //是否检查插入语句存在列名
	DMLMaxInsertRows               int    //inert语句最大多少个字段
	DMLWhere                       bool   //是否检查dml语句where条件
	DMLOrder                       bool   // 是否检查dml语句order条件
	DMLSelect                      bool   //是否检查dml语句有select语句
	DDLCheckTableComment           bool   //是否检查表注释
	DDlCheckColumnComment          bool   //是否检查列注释
	DDLCheckColumnNullable         bool   //是否检查ddl语句有null值
	DDLCheckColumnDefault          bool   //是否检查列默认值
	DDLTimeFieldDefault            bool   //检测时间字段是否拥有默认值
	DDLEnableAcrossDBRename        bool   //是否允许跨库表迁移
	DDLEnableAutoincrementInit     bool   //是否强制自增列初始值为1
	DDLEnableAutoIncrement         bool   //是否强制主键为自增列
	DDLEnableAutoincrementUnsigned bool   //是否检查自增列设置无符号标志unsigned
	DDLEnableDropTable             bool   //是否允许删除表
	DDLEnableDropDatabase          bool   //是否允许drop db
	DDLEnableNullIndexName         bool   //是否允许索引名为空
	DDLIndexNameSpec               bool   //是否开启索引名称规范  如开启 索引前缀必须以uniq/idx命名
	DDLMaxKeyParts                 uint   // 单个索引最多可以指定多少个字段
	DDLMaxKey                      uint   //单表最多可以指定索引数
	DDLMaxCharLength               uint   //char字段最大长度
	MaxTableNameLen                int    //表名最大长度
	MaxAffectRows                  uint   //最大影响行数
	MaxDDLAffectRows               uint   //最大影响行数
	SupportCharset                 string //允许的字符集范围
	SupportCollation               string //允许的排列顺序范围
	CheckIdentifier                bool   //是否检查关键词
	MustHaveColumns                string // 建表时必须拥有哪些字段以逗号分隔，值为空时不限制！
	DDLMultiToSubmit               bool   //是否允许一个工单内有多条DDL语句
	DDLPrimaryKeyMust              bool   //是否强制主键名为id
	DDLAllowColumnType             bool   // ddl语句允许更改字段类型
	DDLImplicitTypeConversion      bool
	DDLAllowPRINotInt              bool
	DDLColumnsMustHaveIndex        string // 如果表包含以下列，列必须有索引。可指定多个列,以逗号分隔.列类型可选.   格式: 列名 [列类型,可选],...
	DDLAllowChangeColumnPosition   bool   // ddl语句允许使用after/first
	IsOSC                          bool
	OscBinDir                      string // pt-osc path
	OscDropNewTable                bool
	OscDropOldTable                bool
	OscCheckReplicationFilters     bool
	OscCheckAlter                  bool
	OscAlterForeignKeysMethod      string
	OscMaxLag                      int
	OscRecursionMethod             string
	OscCheckInterval               int
	OscMaxThreadConnected          int
	OscMaxThreadRunning            int
	OscCriticalThreadConnected     int
	OscCriticalThreadRunning       int
	OscPrintSql                    bool
	OscChunkTime                   float32
	OscSize                        uint
	AllowCreateView                bool
	AllowCreatePartition           bool
	AllowSpecialType               bool
	AllowForeignKey                bool
	PRIRollBackErr                 bool
}

type IndexInfo struct {
	gorm.Model

	Table      string `gorm:"Column:Table"`
	NonUnique  int    `gorm:"Column:Non_unique"`
	IndexName  string `gorm:"Column:Key_name"`
	Seq        int    `gorm:"Column:Seq_in_index"`
	ColumnName string `gorm:"Column:Column_name"`
	IndexType  string `gorm:"Column:Index_type"`

	IsDeleted bool `gorm:"-"`
}

type FieldInfo struct {
	gorm.Model

	Field      string  `gorm:"Column:Field";json:"field"`
	Type       string  `gorm:"Column:Type";json:"type"`
	Collation  string  `gorm:"Column:Collation";json:"collation"`
	Null       string  `gorm:"Column:Null";json:"null"`
	Key        string  `gorm:"Column:Key";json:"key"`
	Default    *string `gorm:"Column:Default";json:"default"`
	Extra      string  `gorm:"Column:Extra";json:"extra"`
	Privileges string  `gorm:"Column:Privileges";json:"privileges"`
	Comment    string  `gorm:"Column:Comment";json:"comment"`

	IsDeleted bool `gorm:"-"`
	IsNew     bool `gorm:"-"`

	Tp *types.FieldType `gorm:"-"`
}
<commit_msg>add new rules<commit_after>// Copyright 2019 HenryYee.
//
// Licensed under the AGPL, Version 3.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.gnu.org/licenses/agpl-3.0.en.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package parser

import (
	"github.com/jinzhu/gorm"
	"github.com/pingcap/parser/types"
)

var FetchAuditRole AuditRole

type Record struct {
	SQL        string
	AffectRows int32
	Status     string
	Error      string
	Level      int32
}

type AuditRole struct {
	DMLInsertColumns               bool   //是否检查插入语句存在列名
	DMLMaxInsertRows               int    //inert语句最大多少个字段
	DMLWhere                       bool   //是否检查dml语句where条件
	DMLOrder                       bool   // 是否检查dml语句order条件
	DMLSelect                      bool   //是否检查dml语句有select语句
	DDLCheckTableComment           bool   //是否检查表注释
	DDlCheckColumnComment          bool   //是否检查列注释
	DDLCheckColumnNullable         bool   //是否检查ddl语句有null值
	DDLCheckColumnDefault          bool   //是否检查列默认值
	DDLTimeFieldDefault            bool   //检测时间字段是否拥有默认值
	DDLEnableAcrossDBRename        bool   //是否允许跨库表迁移
	DDLEnableAutoincrementInit     bool   //是否强制自增列初始值为1
	DDLEnableAutoIncrement         bool   //是否强制主键为自增列
	DDLEnableAutoincrementUnsigned bool   //是否检查自增列设置无符号标志unsigned
	DDLEnableDropTable             bool   //是否允许删除表
	DDLEnableDropDatabase          bool   //是否允许drop db
	DDLEnableNullIndexName         bool   //是否允许索引名为空
	DDLIndexNameSpec               bool   //是否开启索引名称规范  如开启 索引前缀必须以uniq/idx命名
	DDLMaxKeyParts                 uint   // 单个索引最多可以指定多少个字段
	DDLMaxKey                      uint   //单表最多可以指定索引数
	DDLMaxCharLength               uint   //char字段最大长度
	MaxTableNameLen                int    //表名最大长度
	MaxAffectRows                  uint   //最大影响行数
	MaxDDLAffectRows               uint   //最大影响行数
	SupportCharset                 string //允许的字符集范围
	SupportCollation               string //允许的排列顺序范围
	CheckIdentifier                bool   //是否检查关键词
	MustHaveColumns                string // 建表时必须拥有哪些字段以逗号分隔，值为空时不限制！
	DDLMultiToSubmit               bool   //是否允许一个工单内有多条DDL语句
	DDLPrimaryKeyMust              bool   //是否强制主键名为id
	DDLAllowColumnType             bool   // ddl语句允许更改字段类型
	DDLImplicitTypeConversion      bool
	DDLAllowPRINotInt              bool
	DDLEnableForeignKey            bool    // 允许外键
	DDLTablePrefix                 string  // 指定表名前缀
	DDLColumnsMustHaveIndex        string // 如果表包含以下列，列必须有索引。可指定多个列,以逗号分隔.列类型可选.   格式: 列名 [列类型,可选],...
	DDLAllowChangeColumnPosition   bool   // ddl语句允许使用after/first
	IsOSC                          bool
	OscBinDir                      string // pt-osc path
	OscDropNewTable                bool
	OscDropOldTable                bool
	OscCheckReplicationFilters     bool
	OscCheckAlter                  bool
	OscAlterForeignKeysMethod      string
	OscMaxLag                      int
	OscRecursionMethod             string
	OscCheckInterval               int
	OscMaxThreadConnected          int
	OscMaxThreadRunning            int
	OscCriticalThreadConnected     int
	OscCriticalThreadRunning       int
	OscPrintSql                    bool
	OscChunkTime                   float32
	OscSize                        uint
	AllowCreateView                bool
	AllowCreatePartition           bool
	AllowSpecialType               bool
	PRIRollBackErr                 bool
}

type IndexInfo struct {
	gorm.Model

	Table      string `gorm:"Column:Table"`
	NonUnique  int    `gorm:"Column:Non_unique"`
	IndexName  string `gorm:"Column:Key_name"`
	Seq        int    `gorm:"Column:Seq_in_index"`
	ColumnName string `gorm:"Column:Column_name"`
	IndexType  string `gorm:"Column:Index_type"`

	IsDeleted bool `gorm:"-"`
}

type FieldInfo struct {
	gorm.Model

	Field      string  `gorm:"Column:Field";json:"field"`
	Type       string  `gorm:"Column:Type";json:"type"`
	Collation  string  `gorm:"Column:Collation";json:"collation"`
	Null       string  `gorm:"Column:Null";json:"null"`
	Key        string  `gorm:"Column:Key";json:"key"`
	Default    *string `gorm:"Column:Default";json:"default"`
	Extra      string  `gorm:"Column:Extra";json:"extra"`
	Privileges string  `gorm:"Column:Privileges";json:"privileges"`
	Comment    string  `gorm:"Column:Comment";json:"comment"`

	IsDeleted bool `gorm:"-"`
	IsNew     bool `gorm:"-"`

	Tp *types.FieldType `gorm:"-"`
}
<|endoftext|>


<commit_before>package commands

import (
	"crypto/sha256"
	"fmt"
	"log"
	"strings"

	"github.com/bwmarrin/discordgo"
	"github.com/camd67/moebot/moebot_bot/util"
	"github.com/camd67/moebot/moebot_bot/util/db"
)

type RoleHandler struct {
	ComPrefix string
}

/*
Processes a guild role based on a list of allowed role names, and a requested role.
If StrictRole is true then the first role in the list of allowed roles is used if all roles are removed.
*/
func (r *RoleHandler) processGuildRole(allowedRoles []string, session *discordgo.Session, params []string, channel *discordgo.Channel, guild *discordgo.Guild, message *discordgo.Message, strictRole bool, sourceCommand string) {
	if len(params) == 0 || !util.StrContains(allowedRoles, params[0], util.CaseInsensitive) {
		session.ChannelMessageSend(channel.ID, message.Author.Mention()+" please provide one of the approved roles: "+strings.Join(allowedRoles, ", ")+". Did you perhaps mean `team` or `rank`?")
		return
	}

	// get the list of roles and find the one that matches the text
	var roleToAdd *discordgo.Role
	var allRolesToChange []string
	requestedRole := strings.ToUpper(params[0])
	for _, role := range guild.Roles {
		for _, roleToCheck := range allowedRoles {
			if strings.HasPrefix(strings.ToUpper(role.Name), strings.ToUpper(roleToCheck)) {
				allRolesToChange = append(allRolesToChange, role.ID)
			}
		}
		if strings.HasPrefix(strings.ToUpper(role.Name), requestedRole) {
			roleToAdd = role
		}
	}
	if roleToAdd == nil {
		log.Println("Unable to find role", message)
		session.ChannelMessageSend(channel.ID, "Sorry "+message.Author.Mention()+" I don't recognize that role...")
		return
	}
	guildMember, err := session.GuildMember(guild.ID, message.Author.ID)
	if err != nil {
		log.Println("Unable to find guild member", err)
		return
	}
	memberRoles := guildMember.Roles
	if strictRole && util.StrContains(memberRoles, roleToAdd.ID, util.CaseSensitive) && strings.HasPrefix(strings.ToUpper(roleToAdd.Name), requestedRole) {
		// we're in strict mode, and got a removal for the first role. prevent that
		session.ChannelMessageSend(channel.ID, "You've already got that role! You can change roles but can't remove them with this command.")
		return
	}
	dbServer, err := db.ServerQueryOrInsert(guild.ID)
	if err != nil {
		log.Println("There was a problem querying or inserting the server", err)
		session.ChannelMessageSend(channel.ID, "Sorry, there was a problem retrieving server details, please try again.")
		return
	}
	dbRole, err := db.RoleQueryOrInsert(db.Role{
		ServerId: dbServer.Id,
		RoleUid:  roleToAdd.ID,
	})

	if dbRole.ConfirmationMessage != "" && !util.StrContains(memberRoles, roleToAdd.ID, util.CaseSensitive) {
		if len(params) == 1 {
			r.sendConfirmationMessage(session, channel, dbRole, message.Author)
			return
		}
		if len(params) != 3 {
			session.ChannelMessageSend(channel.ID, "Sorry, you need to insert the correct confirmation code to access this role. Use `"+r.ComPrefix+" "+sourceCommand+"` to receive a DM containing detailed instructions.")
			return
		}
		if params[1] != dbRole.ConfirmationSecurityAnswer || params[2] != r.getRoleCode(roleToAdd.ID, message.Author.ID) {
			session.ChannelMessageSend(channel.ID, "Sorry, you need to insert the correct confirmation code to access this role.")
			return
		}
	}

	r.removeAllRoles(session, guildMember, allRolesToChange, guild)
	if util.StrContains(memberRoles, roleToAdd.ID, util.CaseSensitive) {
		session.GuildMemberRoleRemove(guild.ID, message.Author.ID, roleToAdd.ID)
		session.ChannelMessageSend(channel.ID, "Removed role: "+roleToAdd.Name+" for "+message.Author.Mention())
		log.Println("Removed role " + roleToAdd.Name + " to user: " + message.Author.Username)
	} else {
		session.GuildMemberRoleAdd(guild.ID, message.Author.ID, roleToAdd.ID)
		session.ChannelMessageSend(channel.ID, "Added role: "+roleToAdd.Name+" for "+message.Author.Mention())
		log.Println("Added role " + roleToAdd.Name + " to user: " + message.Author.Username)
	}
}

func (r *RoleHandler) removeAllRoles(session *discordgo.Session, member *discordgo.Member, rolesToRemove []string, guild *discordgo.Guild) {
	for _, roleToCheck := range rolesToRemove {
		if util.StrContains(member.Roles, roleToCheck, util.CaseSensitive) {
			session.GuildMemberRoleRemove(guild.ID, member.User.ID, roleToCheck)
		}
	}
}

func (r *RoleHandler) sendConfirmationMessage(session *discordgo.Session, channel *discordgo.Channel, role db.Role, user *discordgo.User) {
	userChannel, err := session.UserChannelCreate(user.ID)
	if err != nil {
		session.ChannelMessageSend(channel.ID, "Sorry, could not create a private message session. Please check your settings to allow direct messages from users on this server.")
	}
	session.ChannelMessageSend(userChannel.ID, fmt.Sprintf(role.ConfirmationMessage, r.getRoleCode(role.RoleUid, user.ID)))
}

func (r *RoleHandler) getRoleCode(roleUID, userUID string) string {
	hash := sha256.New()
	hash.Write([]byte(roleUID + userUID))
	return string(fmt.Sprintf("%x", hash.Sum(nil))[0:6])
}
<commit_msg>add message before sending DM to user<commit_after>package commands

import (
	"crypto/sha256"
	"fmt"
	"log"
	"strings"

	"github.com/bwmarrin/discordgo"
	"github.com/camd67/moebot/moebot_bot/util"
	"github.com/camd67/moebot/moebot_bot/util/db"
)

type RoleHandler struct {
	ComPrefix string
}

/*
Processes a guild role based on a list of allowed role names, and a requested role.
If StrictRole is true then the first role in the list of allowed roles is used if all roles are removed.
*/
func (r *RoleHandler) processGuildRole(allowedRoles []string, session *discordgo.Session, params []string, channel *discordgo.Channel, guild *discordgo.Guild, message *discordgo.Message, strictRole bool, sourceCommand string) {
	if len(params) == 0 || !util.StrContains(allowedRoles, params[0], util.CaseInsensitive) {
		session.ChannelMessageSend(channel.ID, message.Author.Mention()+" please provide one of the approved roles: "+strings.Join(allowedRoles, ", ")+". Did you perhaps mean `team` or `rank`?")
		return
	}

	// get the list of roles and find the one that matches the text
	var roleToAdd *discordgo.Role
	var allRolesToChange []string
	requestedRole := strings.ToUpper(params[0])
	for _, role := range guild.Roles {
		for _, roleToCheck := range allowedRoles {
			if strings.HasPrefix(strings.ToUpper(role.Name), strings.ToUpper(roleToCheck)) {
				allRolesToChange = append(allRolesToChange, role.ID)
			}
		}
		if strings.HasPrefix(strings.ToUpper(role.Name), requestedRole) {
			roleToAdd = role
		}
	}
	if roleToAdd == nil {
		log.Println("Unable to find role", message)
		session.ChannelMessageSend(channel.ID, "Sorry "+message.Author.Mention()+" I don't recognize that role...")
		return
	}
	guildMember, err := session.GuildMember(guild.ID, message.Author.ID)
	if err != nil {
		log.Println("Unable to find guild member", err)
		return
	}
	memberRoles := guildMember.Roles
	if strictRole && util.StrContains(memberRoles, roleToAdd.ID, util.CaseSensitive) && strings.HasPrefix(strings.ToUpper(roleToAdd.Name), requestedRole) {
		// we're in strict mode, and got a removal for the first role. prevent that
		session.ChannelMessageSend(channel.ID, "You've already got that role! You can change roles but can't remove them with this command.")
		return
	}
	dbServer, err := db.ServerQueryOrInsert(guild.ID)
	if err != nil {
		log.Println("There was a problem querying or inserting the server", err)
		session.ChannelMessageSend(channel.ID, "Sorry, there was a problem retrieving server details, please try again.")
		return
	}
	dbRole, err := db.RoleQueryOrInsert(db.Role{
		ServerId: dbServer.Id,
		RoleUid:  roleToAdd.ID,
	})

	if dbRole.ConfirmationMessage != "" && !util.StrContains(memberRoles, roleToAdd.ID, util.CaseSensitive) {
		if len(params) == 1 {
			r.sendConfirmationMessage(session, channel, dbRole, message.Author)
			session.ChannelMessageSend(channel.ID, message.Author.Mention()+" check your DM's for further instructions!")
			return
		}
		if len(params) != 3 {
			session.ChannelMessageSend(channel.ID, "Sorry, you need to insert the correct confirmation code to access this role. Use `"+r.ComPrefix+" "+sourceCommand+"` to receive a DM containing detailed instructions.")
			return
		}
		if params[1] != dbRole.ConfirmationSecurityAnswer || params[2] != r.getRoleCode(roleToAdd.ID, message.Author.ID) {
			session.ChannelMessageSend(channel.ID, "Sorry, you need to insert the correct confirmation code to access this role.")
			return
		}
	}

	r.removeAllRoles(session, guildMember, allRolesToChange, guild)
	if util.StrContains(memberRoles, roleToAdd.ID, util.CaseSensitive) {
		session.GuildMemberRoleRemove(guild.ID, message.Author.ID, roleToAdd.ID)
		session.ChannelMessageSend(channel.ID, "Removed role: "+roleToAdd.Name+" for "+message.Author.Mention())
		log.Println("Removed role " + roleToAdd.Name + " to user: " + message.Author.Username)
	} else {
		session.GuildMemberRoleAdd(guild.ID, message.Author.ID, roleToAdd.ID)
		session.ChannelMessageSend(channel.ID, "Added role: "+roleToAdd.Name+" for "+message.Author.Mention())
		log.Println("Added role " + roleToAdd.Name + " to user: " + message.Author.Username)
	}
}

func (r *RoleHandler) removeAllRoles(session *discordgo.Session, member *discordgo.Member, rolesToRemove []string, guild *discordgo.Guild) {
	for _, roleToCheck := range rolesToRemove {
		if util.StrContains(member.Roles, roleToCheck, util.CaseSensitive) {
			session.GuildMemberRoleRemove(guild.ID, member.User.ID, roleToCheck)
		}
	}
}

func (r *RoleHandler) sendConfirmationMessage(session *discordgo.Session, channel *discordgo.Channel, role db.Role, user *discordgo.User) {
	userChannel, err := session.UserChannelCreate(user.ID)
	if err != nil {
		session.ChannelMessageSend(channel.ID, "Sorry, could not create a private message session. Please check your settings to allow direct messages from users on this server.")
	}
	session.ChannelMessageSend(userChannel.ID, fmt.Sprintf(role.ConfirmationMessage, r.getRoleCode(role.RoleUid, user.ID)))
}

func (r *RoleHandler) getRoleCode(roleUID, userUID string) string {
	hash := sha256.New()
	hash.Write([]byte(roleUID + userUID))
	return string(fmt.Sprintf("%x", hash.Sum(nil))[0:6])
}
<|endoftext|>


<commit_before>package persistence

import (
	"fmt"
	"github.com/boltdb/bolt"
	"time"
)

type IPersistableItem interface{}

type IPersistableBoltItem interface {
	Bucket() string
	Key() []byte
	Value() []byte
}

type PersistableBoltItem struct {
	bucket string
	key    interface{}
	value  interface{}
}

// Make PersistableBoltItem methods, and make the BoltPersistence use it.

type IBoltPersister interface {
	/* A persistence interface that's tightly coupled to Bolt for now.
	Uses strings for keys, and marshalled JSON for values in all cases.
	Marshalling and unmarshalling of values will _not_ be attempted here.
	This API does not support nested buckets right now.
	Databases need to be closed by the consumer.
	*/

	// Close a bolt database
	CloseDB() error

	// Persist an item in a bucket
	Persist(bucket, key string, value []byte) error

	// Make sure a given key exists in a bucket
	Exists(bucket, key string) bool

	// Get the value from a given key + bucket.
	Get(bucket, key string) ([]byte, error)

	// Delete a key in a bucket
	// Delete(bucket, key string) error
}

type BoltPersistence struct {
	*bolt.DB
}

var _ IBoltPersister = (*BoltPersistence)(nil)

func CreateBoltPersister(databasePath string) (*BoltPersistence, error) {
	db, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return nil, fmt.Errorf("unable to open database: %s", err)
	}
	return &BoltPersistence{db}, nil
}

func (bp *BoltPersistence) createBucket(bucket string) error {
	err := bp.Update(func(tx *bolt.Tx) error {
		if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("unable to create bucket: %s", err)
	}
	return nil
}

func (bp *BoltPersistence) CloseDB() error {
	if err := bp.Close(); err != nil {
		return fmt.Errorf("error closing database: %s", err)
	}
	return nil
}

func (bp *BoltPersistence) Persist(bucket, key string, value []byte) error {
	if err := bp.createBucket(bucket); err != nil {
		return err
	}
	err := bp.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		if err := b.Put([]byte(key), value); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("unaable to persist item: %s", err)
	}
	return nil
}

func (bp *BoltPersistence) Exists(bucket, key string) bool {
	exists := true
	_ = bp.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		val := b.Get([]byte(key))
		if val == nil {
			exists = false
		}
		return nil
	})
	return exists
}

func (bp *BoltPersistence) Get(bucket, key string) ([]byte, error) {
	dst := []byte{}
	err := bp.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		val := b.Get([]byte(key))
		dst = make([]byte, len(val))
		copy(dst, val)
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("unable to get item: %s", err)
	}
	return dst, nil
}
<commit_msg>intermediate<commit_after>package persistence

import (
	"fmt"
	"github.com/boltdb/bolt"
	"time"
)

type IPersistableItem interface{}

type IPersistableBoltItem interface {
	Bucket() string
	Key() string
	Value() []byte
}

type PersistableBoltItem struct {
	bucket string
	key    string
	value  interface{} // Should be a JSON-marshallable item
}

func (pbi *PersistableBoltItem) Bucket() string { return pbi.bucket }
func (pbi *PersistableBoltItem) Key() string    { return pbi.key }
func (pbi *PersistableBoltItem) Value() []byte {

}

// Make PersistableBoltItem methods, and make the BoltPersistence use it.

type IBoltPersister interface {
	/* A persistence interface that's tightly coupled to Bolt for now.
	Uses strings for keys, and marshalled JSON for values in all cases.
	Marshalling and unmarshalling of values will _not_ be attempted here.
	This API does not support nested buckets right now.
	Databases need to be closed by the consumer.
	*/

	// Close a bolt database
	CloseDB() error

	// Persist an item in a bucket
	Persist(bucket, key string, value []byte) error

	// Make sure a given key exists in a bucket
	Exists(bucket, key string) bool

	// Get the value from a given key + bucket.
	Get(bucket, key string) ([]byte, error)

	// Delete a key in a bucket
	// Delete(bucket, key string) error
}

type BoltPersistence struct {
	*bolt.DB
}

var _ IBoltPersister = (*BoltPersistence)(nil)

func CreateBoltPersister(databasePath string) (*BoltPersistence, error) {
	db, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return nil, fmt.Errorf("unable to open database: %s", err)
	}
	return &BoltPersistence{db}, nil
}

func (bp *BoltPersistence) createBucket(bucket string) error {
	err := bp.Update(func(tx *bolt.Tx) error {
		if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("unable to create bucket: %s", err)
	}
	return nil
}

func (bp *BoltPersistence) CloseDB() error {
	if err := bp.Close(); err != nil {
		return fmt.Errorf("error closing database: %s", err)
	}
	return nil
}

func (bp *BoltPersistence) Persist(bucket, key string, value []byte) error {
	if err := bp.createBucket(bucket); err != nil {
		return err
	}
	err := bp.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		if err := b.Put([]byte(key), value); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("unaable to persist item: %s", err)
	}
	return nil
}

func (bp *BoltPersistence) Exists(bucket, key string) bool {
	exists := true
	_ = bp.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		val := b.Get([]byte(key))
		if val == nil {
			exists = false
		}
		return nil
	})
	return exists
}

func (bp *BoltPersistence) Get(bucket, key string) ([]byte, error) {
	dst := []byte{}
	err := bp.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		val := b.Get([]byte(key))
		dst = make([]byte, len(val))
		copy(dst, val)
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("unable to get item: %s", err)
	}
	return dst, nil
}
<|endoftext|>


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

package membuf

import (
	"crypto/rand"
	"testing"

	. "github.com/pingcap/check"
)

func init() {
	allocBufLen = 1024
}

type bufferSuite struct{}

var _ = Suite(&bufferSuite{})

func Test(t *testing.T) {
	TestingT(t)
}

type testAllocator struct {
	allocs int
	frees  int
}

func (t *testAllocator) Alloc(n int) []byte {
	t.allocs++
	return make([]byte, n)
}

func (t *testAllocator) Free(_ []byte) {
	t.frees++
}

func (*bufferSuite) TestBufferPool(c *C) {
	allocator := &testAllocator{}
	pool := NewPool(2, allocator)

	bytesBuf := pool.NewBuffer()
	bytesBuf.AllocBytes(256)
	c.Assert(allocator.allocs, Equals, 1)
	bytesBuf.AllocBytes(512)
	c.Assert(allocator.allocs, Equals, 1)
	bytesBuf.AllocBytes(257)
	c.Assert(allocator.allocs, Equals, 2)
	bytesBuf.AllocBytes(767)
	c.Assert(allocator.allocs, Equals, 2)

	c.Assert(allocator.frees, Equals, 0)
	bytesBuf.Destroy()
	c.Assert(allocator.frees, Equals, 0)

	bytesBuf = pool.NewBuffer()
	for i := 0; i < 6; i++ {
		bytesBuf.AllocBytes(512)
	}
	bytesBuf.Destroy()
	c.Assert(allocator.allocs, Equals, 3)
	c.Assert(allocator.frees, Equals, 1)
}

func (*bufferSuite) TestBufferIsolation(c *C) {
	bytesBuf := NewBuffer()
	defer bytesBuf.Destroy()

	b1 := bytesBuf.AllocBytes(16)
	b2 := bytesBuf.AllocBytes(16)
	c.Assert(cap(b1), Equals, len(b1))
	c.Assert(cap(b2), Equals, len(b2))

	_, err := rand.Read(b2)
	c.Assert(err, IsNil)
	b3 := append([]byte(nil), b2...)
	b1 = append(b1, 0, 1, 2, 3)
	c.Assert(b2, DeepEquals, b3)
}
<commit_msg>br/pkg/membuf: migrate test-infra to testify (#28210)<commit_after>// Copyright 2021 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package membuf

import (
	"crypto/rand"
	"testing"

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

func init() {
	allocBufLen = 1024
}

type testAllocator struct {
	allocs int
	frees  int
}

func (t *testAllocator) Alloc(n int) []byte {
	t.allocs++
	return make([]byte, n)
}

func (t *testAllocator) Free(_ []byte) {
	t.frees++
}

func TestBufferPool(t *testing.T) {
	t.Parallel()

	allocator := &testAllocator{}
	pool := NewPool(2, allocator)

	bytesBuf := pool.NewBuffer()
	bytesBuf.AllocBytes(256)
	require.Equal(t, 1, allocator.allocs)
	bytesBuf.AllocBytes(512)
	require.Equal(t, 1, allocator.allocs)
	bytesBuf.AllocBytes(257)
	require.Equal(t, 2, allocator.allocs)
	bytesBuf.AllocBytes(767)
	require.Equal(t, 2, allocator.allocs)

	require.Equal(t, 0, allocator.frees)
	bytesBuf.Destroy()
	require.Equal(t, 0, allocator.frees)

	bytesBuf = pool.NewBuffer()
	for i := 0; i < 6; i++ {
		bytesBuf.AllocBytes(512)
	}
	bytesBuf.Destroy()
	require.Equal(t, 3, allocator.allocs)
	require.Equal(t, 1, allocator.frees)
}

func TestBufferIsolation(t *testing.T) {
	t.Parallel()

	bytesBuf := NewBuffer()
	defer bytesBuf.Destroy()

	b1 := bytesBuf.AllocBytes(16)
	b2 := bytesBuf.AllocBytes(16)
	require.Equal(t, len(b1), cap(b1))
	require.Equal(t, len(b2), cap(b2))

	_, err := rand.Read(b2)
	require.NoError(t, err)
	b3 := append([]byte(nil), b2...)
	b1 = append(b1, 0, 1, 2, 3)
	require.Equal(t, b3, b2)
}
<|endoftext|>


<commit_before>package seccomp

import (
	"k8s.io/apimachinery/pkg/util/validation/field"
	"k8s.io/kubernetes/pkg/api"
)

// RunAsUserStrategy defines the interface for all uid constraint strategies.
type SeccompStrategy interface {
	// Generate creates the profile based on policy rules.
	Generate(pod *api.Pod) (string, error)
	// ValidatePod ensures that the specified values on the pod fall within the range
	// of the strategy.
	ValidatePod(pod *api.Pod) field.ErrorList
	// ValidateContainer ensures that the specified values on the container fall within
	// the range of the strategy.
	ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList
}
<commit_msg>Fixed the type definition<commit_after>package seccomp

import (
	"k8s.io/apimachinery/pkg/util/validation/field"
	"k8s.io/kubernetes/pkg/api"
)

// SeccompStrategy defines the interface for all seccomp constraint strategies.
type SeccompStrategy interface {
	// Generate creates the profile based on policy rules.
	Generate(pod *api.Pod) (string, error)
	// ValidatePod ensures that the specified values on the pod fall within the range
	// of the strategy.
	ValidatePod(pod *api.Pod) field.ErrorList
	// ValidateContainer ensures that the specified values on the container fall within
	// the range of the strategy.
	ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList
}
<|endoftext|>


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

package servers

import (
	"context"
	"fmt"
	"net"
	"time"

	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
	"github.com/scionproto/scion/go/lib/hostinfo"
	"github.com/scionproto/scion/go/lib/infra"
	"github.com/scionproto/scion/go/lib/infra/modules/itopo"
	"github.com/scionproto/scion/go/lib/infra/modules/segverifier"
	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/revcache"
	"github.com/scionproto/scion/go/lib/sciond"
	"github.com/scionproto/scion/go/lib/topology"
	"github.com/scionproto/scion/go/proto"
	"github.com/scionproto/scion/go/sciond/internal/fetcher"
)

const (
	// DefaultReplyTimeout is allocated to SCIOND handlers to reply back to the client.
	DefaultReplyTimeout = 2 * time.Second
	// DefaultWorkTimeout is allocated to SCIOND handlers work (e.g., network
	// traffic and crypto operations)
	DefaultWorkTimeout = 10 * time.Second
	DefaultEarlyReply  = 200 * time.Millisecond
	// DefaultServiceTTL is the TTL value for ServiceInfoReply objects,
	// expressed in seconds.
	DefaultServiceTTL uint32 = 300
)

type Handler interface {
	Handle(ctx context.Context, conn net.PacketConn, src net.Addr, pld *sciond.Pld)
}

// PathRequestHandler represents the shared global state for the handling of all
// PathRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each PathRequest it receives.
type PathRequestHandler struct {
	Fetcher *fetcher.Fetcher
}

func (h *PathRequestHandler) Handle(ctx context.Context, conn net.PacketConn, src net.Addr,
	pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[PathRequestHandler] Received request", "req", pld.PathReq)
	workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout)
	defer workCancelF()
	getPathsReply, err := h.Fetcher.GetPaths(workCtx, pld.PathReq, DefaultEarlyReply, logger)
	if err != nil {
		logger.Error("Unable to get paths", "err", err)
	}
	// Always reply, as the Fetcher will fill in the relevant error bits of the reply
	reply := &sciond.Pld{
		Id:        pld.Id,
		Which:     proto.SCIONDMsg_Which_pathReply,
		PathReply: getPathsReply,
	}
	b, err := proto.PackRoot(reply)
	if err != nil {
		// This is constructed locally, so it should always succeed. Otherwise,
		// it is a bug.
		panic(err)
	}
	ctx, cancelF := context.WithTimeout(ctx, DefaultReplyTimeout)
	defer cancelF()
	if _, err := conn.WriteTo(b, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
		return
	}
	logger.Debug("Replied with paths", "num_paths", len(getPathsReply.Entries))
	logger.Trace("Full reply", "paths", getPathsReply)
}

// ASInfoRequestHandler represents the shared global state for the handling of all
// ASInfoRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each ASInfoRequest it receives.
type ASInfoRequestHandler struct {
	TrustStore infra.TrustStore
}

func (h *ASInfoRequestHandler) Handle(ctx context.Context, conn net.PacketConn, src net.Addr,
	pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[ASInfoRequestHandler] Received request", "req", pld.AsInfoReq)
	workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout)
	defer workCancelF()
	// NOTE(scrye): Only support single-homed SCIONDs for now (returned slice
	// will at most contain one element).
	reqIA := pld.AsInfoReq.Isdas.IA()
	topo := itopo.Get()
	if reqIA.IsZero() {
		reqIA = topo.ISD_AS
	}
	asInfoReply := &sciond.ASInfoReply{}
	trcObj, err := h.TrustStore.GetValidTRC(workCtx, reqIA.I, nil)
	if err != nil {
		// FIXME(scrye): return a zero AS because the protocol doesn't
		// support errors, but we probably want to return an error here in
		// the future.
		asInfoReply.Entries = []sciond.ASInfoReplyEntry{}
	}
	if reqIA.IsZero() || reqIA.Equal(topo.ISD_AS) {
		// Requested AS is us
		asInfoReply.Entries = []sciond.ASInfoReplyEntry{
			{
				RawIsdas: topo.ISD_AS.IAInt(),
				Mtu:      uint16(topo.MTU),
				IsCore:   trcObj.CoreASes.Contains(topo.ISD_AS),
			},
		}
	} else {
		// Requested AS is not us
		asInfoReply.Entries = []sciond.ASInfoReplyEntry{
			{
				RawIsdas: reqIA.IAInt(),
				Mtu:      0,
				IsCore:   trcObj.CoreASes.Contains(reqIA),
			},
		}
	}
	reply := &sciond.Pld{
		Id:          pld.Id,
		Which:       proto.SCIONDMsg_Which_asInfoReply,
		AsInfoReply: asInfoReply,
	}
	b, err := proto.PackRoot(reply)
	if err != nil {
		panic(err)
	}
	conn.SetWriteDeadline(time.Now().Add(DefaultReplyTimeout))
	if _, err := conn.WriteTo(b, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
		return
	}
	logger.Trace("Sent reply", "asInfo", asInfoReply)
}

// IFInfoRequestHandler represents the shared global state for the handling of all
// IFInfoRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each IFInfoRequest it receives.
type IFInfoRequestHandler struct{}

func (h *IFInfoRequestHandler) Handle(ctx context.Context, conn net.PacketConn, src net.Addr,
	pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[IFInfoRequestHandler] Received request", "req", pld.IfInfoRequest)
	ifInfoRequest := pld.IfInfoRequest
	ifInfoReply := &sciond.IFInfoReply{}
	topo := itopo.Get()
	if len(ifInfoRequest.IfIDs) == 0 {
		// Reply with all the IFIDs we know
		for ifid, ifInfo := range topo.IFInfoMap {
			ifInfoReply.RawEntries = append(ifInfoReply.RawEntries, sciond.IFInfoReplyEntry{
				IfID:     ifid,
				HostInfo: hostinfo.FromTopoBRAddr(*ifInfo.InternalAddrs),
			})
		}
	} else {
		// Reply with only the IFIDs the client requested
		for _, ifid := range ifInfoRequest.IfIDs {
			ifInfo, ok := topo.IFInfoMap[ifid]
			if !ok {
				logger.Info("Received IF Info Request, but IFID not found", "ifid", ifid)
				continue
			}
			ifInfoReply.RawEntries = append(ifInfoReply.RawEntries, sciond.IFInfoReplyEntry{
				IfID:     ifid,
				HostInfo: hostinfo.FromTopoBRAddr(*ifInfo.InternalAddrs),
			})
		}
	}
	reply := &sciond.Pld{
		Id:          pld.Id,
		Which:       proto.SCIONDMsg_Which_ifInfoReply,
		IfInfoReply: ifInfoReply,
	}
	b, err := proto.PackRoot(reply)
	if err != nil {
		panic(err)
	}
	conn.SetWriteDeadline(time.Now().Add(DefaultReplyTimeout))
	if _, err := conn.WriteTo(b, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
		return
	}
	logger.Trace("Sent reply", "ifInfo", ifInfoReply)
}

// SVCInfoRequestHandler represents the shared global state for the handling of all
// SVCInfoRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each SVCInfoRequest it receives.
type SVCInfoRequestHandler struct{}

func (h *SVCInfoRequestHandler) Handle(ctx context.Context, conn net.PacketConn,
	src net.Addr, pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[SVCInfoRequestHandler] Received request", "req", pld.ServiceInfoRequest)
	svcInfoRequest := pld.ServiceInfoRequest
	svcInfoReply := &sciond.ServiceInfoReply{}
	topo := itopo.Get()
	for _, t := range svcInfoRequest.ServiceTypes {
		var hostInfos []hostinfo.HostInfo
		hostInfos = makeHostInfos(topo, t)
		replyEntry := sciond.ServiceInfoReplyEntry{
			ServiceType: t,
			Ttl:         DefaultServiceTTL,
			HostInfos:   hostInfos,
		}
		svcInfoReply.Entries = append(svcInfoReply.Entries, replyEntry)
	}
	reply := &sciond.Pld{
		Id:               pld.Id,
		Which:            proto.SCIONDMsg_Which_serviceInfoReply,
		ServiceInfoReply: svcInfoReply,
	}
	b, err := proto.PackRoot(reply)
	if err != nil {
		panic(err)
	}
	conn.SetWriteDeadline(time.Now().Add(DefaultReplyTimeout))
	if _, err := conn.WriteTo(b, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
		return
	}
	logger.Trace("Sent reply", "svcInfo", svcInfoReply)
}

func makeHostInfos(topo *topology.Topo, t proto.ServiceType) []hostinfo.HostInfo {
	var hostInfos []hostinfo.HostInfo
	addresses, err := topo.GetAllTopoAddrs(t)
	if err != nil {
		// FIXME(lukedirtwalker): inform client about this:
		// see https://github.com/scionproto/scion/issues/1673
		return hostInfos
	}
	for _, a := range addresses {
		hostInfos = append(hostInfos, hostinfo.FromTopoAddr(a))
	}
	return hostInfos
}

// RevNotificationHandler represents the shared global state for the handling of all
// RevNotification announcements. The SCIOND API spawns a goroutine with method Handle
// for each RevNotification it receives.
type RevNotificationHandler struct {
	RevCache   revcache.RevCache
	TrustStore infra.TrustStore
}

func (h *RevNotificationHandler) Handle(ctx context.Context, conn net.PacketConn,
	src net.Addr, pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[RevNotificationHandler] Received revocation",
		"notification", pld.RevNotification)
	workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout)
	defer workCancelF()
	revNotification := pld.RevNotification
	revReply := &sciond.RevReply{}
	revInfo, err := h.verifySRevInfo(workCtx, revNotification.SRevInfo)
	if err == nil {
		_, err = h.RevCache.Insert(workCtx, revNotification.SRevInfo)
		if err != nil {
			logger.Error("Failed to insert revocations", "err", err)
		}
	}
	switch {
	case isValid(err):
		revReply.Result = sciond.RevValid
	case isStale(err):
		revReply.Result = sciond.RevStale
	case isInvalid(err):
		revReply.Result = sciond.RevInvalid
	case isUnknown(err):
		revReply.Result = sciond.RevUnknown
	default:
		panic(fmt.Sprintf("unknown error type, err = %v", err))
	}
	reply := &sciond.Pld{
		Id:       pld.Id,
		Which:    proto.SCIONDMsg_Which_revReply,
		RevReply: revReply,
	}
	b, err := proto.PackRoot(reply)
	if err != nil {
		panic(err)
	}
	ctx, cancelF := context.WithTimeout(ctx, DefaultReplyTimeout)
	defer cancelF()
	conn.SetWriteDeadline(time.Now().Add(DefaultReplyTimeout))
	if _, err := conn.WriteTo(b, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
		return
	}
	logger.Trace("Sent reply", "revInfo", revInfo)
}

// verifySRevInfo first checks if the RevInfo can be extracted from sRevInfo,
// and immediately returns with an error if it cannot. Then, revocation
// verification is performed and the result is returned.
func (h *RevNotificationHandler) verifySRevInfo(ctx context.Context,
	sRevInfo *path_mgmt.SignedRevInfo) (*path_mgmt.RevInfo, error) {

	// Error out immediately if RevInfo is bad
	info, err := sRevInfo.RevInfo()
	if err != nil {
		return nil, common.NewBasicError("Unable to extract RevInfo", nil)
	}
	err = segverifier.VerifyRevInfo(ctx, h.TrustStore.NewVerifier(), nil, sRevInfo)
	return info, err
}

// isValid is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of valid.
func isValid(err error) bool {
	return err == nil
}

// isStale is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of stale.
func isStale(err error) bool {
	// FIXME(scrye): implement this once we have verification
	return false
}

// isInvalid is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of invalid.
func isInvalid(err error) bool {
	// FIXME(scrye): implement this once we have verification
	return false
}

// isUnknown is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of unknown.
func isUnknown(err error) bool {
	return err != nil
}
<commit_msg>sciond: Unify reply code (#2751)<commit_after>// Copyright 2018 ETH Zurich, Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package servers

import (
	"context"
	"fmt"
	"net"
	"time"

	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
	"github.com/scionproto/scion/go/lib/hostinfo"
	"github.com/scionproto/scion/go/lib/infra"
	"github.com/scionproto/scion/go/lib/infra/modules/itopo"
	"github.com/scionproto/scion/go/lib/infra/modules/segverifier"
	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/revcache"
	"github.com/scionproto/scion/go/lib/sciond"
	"github.com/scionproto/scion/go/lib/topology"
	"github.com/scionproto/scion/go/proto"
	"github.com/scionproto/scion/go/sciond/internal/fetcher"
)

const (
	// DefaultReplyTimeout is allocated to SCIOND handlers to reply back to the client.
	DefaultReplyTimeout = 2 * time.Second
	// DefaultWorkTimeout is allocated to SCIOND handlers work (e.g., network
	// traffic and crypto operations)
	DefaultWorkTimeout = 10 * time.Second
	DefaultEarlyReply  = 200 * time.Millisecond
	// DefaultServiceTTL is the TTL value for ServiceInfoReply objects,
	// expressed in seconds.
	DefaultServiceTTL uint32 = 300
)

type Handler interface {
	Handle(ctx context.Context, conn net.PacketConn, src net.Addr, pld *sciond.Pld)
}

// PathRequestHandler represents the shared global state for the handling of all
// PathRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each PathRequest it receives.
type PathRequestHandler struct {
	Fetcher *fetcher.Fetcher
}

func (h *PathRequestHandler) Handle(ctx context.Context, conn net.PacketConn, src net.Addr,
	pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[PathRequestHandler] Received request", "req", pld.PathReq)
	workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout)
	defer workCancelF()
	getPathsReply, err := h.Fetcher.GetPaths(workCtx, pld.PathReq, DefaultEarlyReply, logger)
	if err != nil {
		logger.Error("Unable to get paths", "err", err)
	}
	// Always reply, as the Fetcher will fill in the relevant error bits of the reply
	reply := &sciond.Pld{
		Id:        pld.Id,
		Which:     proto.SCIONDMsg_Which_pathReply,
		PathReply: getPathsReply,
	}
	if err := sendReply(reply, conn, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
	} else {
		logger.Debug("Replied with paths", "num_paths", len(getPathsReply.Entries))
		logger.Trace("Full reply", "paths", getPathsReply)
	}
}

// ASInfoRequestHandler represents the shared global state for the handling of all
// ASInfoRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each ASInfoRequest it receives.
type ASInfoRequestHandler struct {
	TrustStore infra.TrustStore
}

func (h *ASInfoRequestHandler) Handle(ctx context.Context, conn net.PacketConn, src net.Addr,
	pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[ASInfoRequestHandler] Received request", "req", pld.AsInfoReq)
	workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout)
	defer workCancelF()
	// NOTE(scrye): Only support single-homed SCIONDs for now (returned slice
	// will at most contain one element).
	reqIA := pld.AsInfoReq.Isdas.IA()
	topo := itopo.Get()
	if reqIA.IsZero() {
		reqIA = topo.ISD_AS
	}
	asInfoReply := &sciond.ASInfoReply{}
	trcObj, err := h.TrustStore.GetValidTRC(workCtx, reqIA.I, nil)
	if err != nil {
		// FIXME(scrye): return a zero AS because the protocol doesn't
		// support errors, but we probably want to return an error here in
		// the future.
		asInfoReply.Entries = []sciond.ASInfoReplyEntry{}
	}
	if reqIA.IsZero() || reqIA.Equal(topo.ISD_AS) {
		// Requested AS is us
		asInfoReply.Entries = []sciond.ASInfoReplyEntry{
			{
				RawIsdas: topo.ISD_AS.IAInt(),
				Mtu:      uint16(topo.MTU),
				IsCore:   trcObj.CoreASes.Contains(topo.ISD_AS),
			},
		}
	} else {
		// Requested AS is not us
		asInfoReply.Entries = []sciond.ASInfoReplyEntry{
			{
				RawIsdas: reqIA.IAInt(),
				Mtu:      0,
				IsCore:   trcObj.CoreASes.Contains(reqIA),
			},
		}
	}
	reply := &sciond.Pld{
		Id:          pld.Id,
		Which:       proto.SCIONDMsg_Which_asInfoReply,
		AsInfoReply: asInfoReply,
	}
	if err := sendReply(reply, conn, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
	} else {
		logger.Trace("Sent reply", "asInfo", asInfoReply)
	}
}

// IFInfoRequestHandler represents the shared global state for the handling of all
// IFInfoRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each IFInfoRequest it receives.
type IFInfoRequestHandler struct{}

func (h *IFInfoRequestHandler) Handle(ctx context.Context, conn net.PacketConn, src net.Addr,
	pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[IFInfoRequestHandler] Received request", "req", pld.IfInfoRequest)
	ifInfoRequest := pld.IfInfoRequest
	ifInfoReply := &sciond.IFInfoReply{}
	topo := itopo.Get()
	if len(ifInfoRequest.IfIDs) == 0 {
		// Reply with all the IFIDs we know
		for ifid, ifInfo := range topo.IFInfoMap {
			ifInfoReply.RawEntries = append(ifInfoReply.RawEntries, sciond.IFInfoReplyEntry{
				IfID:     ifid,
				HostInfo: hostinfo.FromTopoBRAddr(*ifInfo.InternalAddrs),
			})
		}
	} else {
		// Reply with only the IFIDs the client requested
		for _, ifid := range ifInfoRequest.IfIDs {
			ifInfo, ok := topo.IFInfoMap[ifid]
			if !ok {
				logger.Info("Received IF Info Request, but IFID not found", "ifid", ifid)
				continue
			}
			ifInfoReply.RawEntries = append(ifInfoReply.RawEntries, sciond.IFInfoReplyEntry{
				IfID:     ifid,
				HostInfo: hostinfo.FromTopoBRAddr(*ifInfo.InternalAddrs),
			})
		}
	}
	reply := &sciond.Pld{
		Id:          pld.Id,
		Which:       proto.SCIONDMsg_Which_ifInfoReply,
		IfInfoReply: ifInfoReply,
	}
	if err := sendReply(reply, conn, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
	} else {
		logger.Trace("Sent reply", "ifInfo", ifInfoReply)
	}
}

// SVCInfoRequestHandler represents the shared global state for the handling of all
// SVCInfoRequest queries. The SCIOND API spawns a goroutine with method Handle
// for each SVCInfoRequest it receives.
type SVCInfoRequestHandler struct{}

func (h *SVCInfoRequestHandler) Handle(ctx context.Context, conn net.PacketConn,
	src net.Addr, pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[SVCInfoRequestHandler] Received request", "req", pld.ServiceInfoRequest)
	svcInfoRequest := pld.ServiceInfoRequest
	svcInfoReply := &sciond.ServiceInfoReply{}
	topo := itopo.Get()
	for _, t := range svcInfoRequest.ServiceTypes {
		var hostInfos []hostinfo.HostInfo
		hostInfos = makeHostInfos(topo, t)
		replyEntry := sciond.ServiceInfoReplyEntry{
			ServiceType: t,
			Ttl:         DefaultServiceTTL,
			HostInfos:   hostInfos,
		}
		svcInfoReply.Entries = append(svcInfoReply.Entries, replyEntry)
	}
	reply := &sciond.Pld{
		Id:               pld.Id,
		Which:            proto.SCIONDMsg_Which_serviceInfoReply,
		ServiceInfoReply: svcInfoReply,
	}
	if err := sendReply(reply, conn, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
	} else {
		logger.Trace("Sent reply", "svcInfo", svcInfoReply)
	}
}

func makeHostInfos(topo *topology.Topo, t proto.ServiceType) []hostinfo.HostInfo {
	var hostInfos []hostinfo.HostInfo
	addresses, err := topo.GetAllTopoAddrs(t)
	if err != nil {
		// FIXME(lukedirtwalker): inform client about this:
		// see https://github.com/scionproto/scion/issues/1673
		return hostInfos
	}
	for _, a := range addresses {
		hostInfos = append(hostInfos, hostinfo.FromTopoAddr(a))
	}
	return hostInfos
}

// RevNotificationHandler represents the shared global state for the handling of all
// RevNotification announcements. The SCIOND API spawns a goroutine with method Handle
// for each RevNotification it receives.
type RevNotificationHandler struct {
	RevCache   revcache.RevCache
	TrustStore infra.TrustStore
}

func (h *RevNotificationHandler) Handle(ctx context.Context, conn net.PacketConn,
	src net.Addr, pld *sciond.Pld) {

	logger := log.FromCtx(ctx)
	logger.Debug("[RevNotificationHandler] Received revocation",
		"notification", pld.RevNotification)
	workCtx, workCancelF := context.WithTimeout(ctx, DefaultWorkTimeout)
	defer workCancelF()
	revNotification := pld.RevNotification
	revReply := &sciond.RevReply{}
	revInfo, err := h.verifySRevInfo(workCtx, revNotification.SRevInfo)
	if err == nil {
		_, err = h.RevCache.Insert(workCtx, revNotification.SRevInfo)
		if err != nil {
			logger.Error("Failed to insert revocations", "err", err)
		}
	}
	switch {
	case isValid(err):
		revReply.Result = sciond.RevValid
	case isStale(err):
		revReply.Result = sciond.RevStale
	case isInvalid(err):
		revReply.Result = sciond.RevInvalid
	case isUnknown(err):
		revReply.Result = sciond.RevUnknown
	default:
		panic(fmt.Sprintf("unknown error type, err = %v", err))
	}
	reply := &sciond.Pld{
		Id:       pld.Id,
		Which:    proto.SCIONDMsg_Which_revReply,
		RevReply: revReply,
	}
	if err := sendReply(reply, conn, src); err != nil {
		logger.Warn("Unable to reply to client", "client", src, "err", err)
	} else {
		logger.Trace("Sent reply", "revInfo", revInfo)
	}
}

// verifySRevInfo first checks if the RevInfo can be extracted from sRevInfo,
// and immediately returns with an error if it cannot. Then, revocation
// verification is performed and the result is returned.
func (h *RevNotificationHandler) verifySRevInfo(ctx context.Context,
	sRevInfo *path_mgmt.SignedRevInfo) (*path_mgmt.RevInfo, error) {

	// Error out immediately if RevInfo is bad
	info, err := sRevInfo.RevInfo()
	if err != nil {
		return nil, common.NewBasicError("Unable to extract RevInfo", nil)
	}
	err = segverifier.VerifyRevInfo(ctx, h.TrustStore.NewVerifier(), nil, sRevInfo)
	return info, err
}

// isValid is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of valid.
func isValid(err error) bool {
	return err == nil
}

// isStale is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of stale.
func isStale(err error) bool {
	// FIXME(scrye): implement this once we have verification
	return false
}

// isInvalid is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of invalid.
func isInvalid(err error) bool {
	// FIXME(scrye): implement this once we have verification
	return false
}

// isUnknown is a placeholder. It should return true if and only if revocation
// verification ended with an outcome of unknown.
func isUnknown(err error) bool {
	return err != nil
}

func sendReply(pld *sciond.Pld, conn net.PacketConn, src net.Addr) error {
	b, err := proto.PackRoot(pld)
	if err != nil {
		panic(err)
	}
	conn.SetWriteDeadline(time.Now().Add(DefaultReplyTimeout))
	_, err = conn.WriteTo(b, src)
	return err
}
<|endoftext|>