<commit_before>package sensu

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/check"
	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/client"
	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/transport/rabbitmq"
)

const defaultRabbitMQURI string = "amqp://guest:guest@localhost:5672/%2F"

var errNoClientName = errors.New("No client name provided")

type configFlagSet struct {
	configFile string
	verbose    bool
}

type Config struct {
	flagSet *configFlagSet
	config  *configPayload
}

type configPayload struct {
	Client            *client.Client              `json:"client,omitempty"`
	Checks            []*check.Check              `json:"checks,omitempty"`
	RabbitMQURI       *string                     `json:"rabbitmq_uri,omitempty"`
	RabbitMQTransport []*rabbitmq.TransportConfig `json:"rabbitmq,omitempty"`
}

func fetchEnv(envs ...string) string {
	for _, env := range envs {
		if v := os.Getenv(env); v != "" {
			return v
		}
	}

	return ""
}

// split is a wrapper for strings.Split, which returns an empty array
// for empty string inputs instead of an array containing an empty string
func split(str string, token string) []string {
	if len(str) == 0 {
		return []string{}
	}

	return strings.Split(str, token)
}

func NewConfigFromFile(flagset *configFlagSet, configFile string) (*Config, error) {
	cfg := Config{flagset, &configPayload{}}

	if configFile != "" {
		buf, err := ioutil.ReadFile(configFile)

		if err != nil {
			return nil, err
		}

		if err := json.Unmarshal(buf, &cfg.config); err != nil {
			return nil, err
		}
	}

	if cfg.Client().Name == "" {
		return nil, errNoClientName
	}

	return &cfg, nil
}

func NewConfigFromFlagSet(flagset *configFlagSet) (*Config, error) {
	var file string

	if flagset != nil {
		file = flagset.configFile
	}

	return NewConfigFromFile(flagset, file)
}

func (c *Config) RabbitMQURI() string {
	if cfg := c.config; cfg != nil && cfg.RabbitMQURI != nil {
		return *cfg.RabbitMQURI
	} else if uri := fetchEnv("RABBITMQ_URI", "RABBITMQ_URL"); uri != "" {
		return uri
	}

	return defaultRabbitMQURI
}

// RabbitMQHAConfig superseeds RabbitMQURI() by first checking
// for a HA cluster configuration and then calling RabbitMQURI()
// if it can't find one
func (c *Config) RabbitMQHAConfig() ([]*rabbitmq.TransportConfig, error) {
	if cfg := c.config; cfg != nil && cfg.RabbitMQTransport != nil {
		return cfg.RabbitMQTransport, nil
	}

	config, err := rabbitmq.NewTransportConfig(c.RabbitMQURI())

	if err != nil {
		return []*rabbitmq.TransportConfig{}, err
	}

	return []*rabbitmq.TransportConfig{config}, nil
}

func (c *Config) Client() *client.Client {
	if cfg := c.config; cfg != nil && cfg.Client != nil {
		return cfg.Client
	}

	return &client.Client{
		Name:          os.Getenv("SENSU_CLIENT_NAME"),
		Address:       fetchEnv("SENSU_CLIENT_ADDRESS", "SENSU_ADDRESS"),
		Subscriptions: split(os.Getenv("SENSU_CLIENT_SUBSCRIPTIONS"), ","),
	}
}

func (c *Config) Checks() []*check.Check {
	if cfg := c.config; cfg != nil {
		return cfg.Checks
	}

	return []*check.Check{}
}
<commit_msg>add default subscription and remove duplicates<commit_after>package sensu

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strings"
	"fmt"

	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/check"
	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/client"
	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/transport/rabbitmq"
)

const defaultRabbitMQURI string = "amqp://guest:guest@localhost:5672/%2F"

var errNoClientName = errors.New("No client name provided")

type configFlagSet struct {
	configFile string
	verbose    bool
}

type Config struct {
	flagSet *configFlagSet
	config  *configPayload
}

type configPayload struct {
	Client            *client.Client              `json:"client,omitempty"`
	Checks            []*check.Check              `json:"checks,omitempty"`
	RabbitMQURI       *string                     `json:"rabbitmq_uri,omitempty"`
	RabbitMQTransport []*rabbitmq.TransportConfig `json:"rabbitmq,omitempty"`
}

func fetchEnv(envs ...string) string {
	for _, env := range envs {
		if v := os.Getenv(env); v != "" {
			return v
		}
	}

	return ""
}

// split is a wrapper for strings.Split, which returns an empty array
// for empty string inputs instead of an array containing an empty string
func split(str string, token string) []string {
	if len(str) == 0 {
		return []string{}
	}

	return strings.Split(str, token)
}

func NewConfigFromFile(flagset *configFlagSet, configFile string) (*Config, error) {
	cfg := Config{flagset, &configPayload{}}

	if configFile != "" {
		buf, err := ioutil.ReadFile(configFile)

		if err != nil {
			return nil, err
		}

		if err := json.Unmarshal(buf, &cfg.config); err != nil {
			return nil, err
		}
	}

	if cfg.Client().Name == "" {
		return nil, errNoClientName
	}

	// If we reached this point, we have a client name.
	// emulate ruby client behavior: add default subscription - client:name
	// Without at least one subscription sensu server will crash.
	cfg.config.Client.Subscriptions = removeDuplicates(
	    append(
	        cfg.config.Client.Subscriptions,
	        fmt.Sprintf("client:%s", cfg.config.Client.Name),
	    ),
	)

	return &cfg, nil
}

func NewConfigFromFlagSet(flagset *configFlagSet) (*Config, error) {
	var file string

	if flagset != nil {
		file = flagset.configFile
	}

	return NewConfigFromFile(flagset, file)
}

func (c *Config) RabbitMQURI() string {
	if cfg := c.config; cfg != nil && cfg.RabbitMQURI != nil {
		return *cfg.RabbitMQURI
	} else if uri := fetchEnv("RABBITMQ_URI", "RABBITMQ_URL"); uri != "" {
		return uri
	}

	return defaultRabbitMQURI
}

// RabbitMQHAConfig superseeds RabbitMQURI() by first checking
// for a HA cluster configuration and then calling RabbitMQURI()
// if it can't find one
func (c *Config) RabbitMQHAConfig() ([]*rabbitmq.TransportConfig, error) {
	if cfg := c.config; cfg != nil && cfg.RabbitMQTransport != nil {
		return cfg.RabbitMQTransport, nil
	}

	config, err := rabbitmq.NewTransportConfig(c.RabbitMQURI())

	if err != nil {
		return []*rabbitmq.TransportConfig{}, err
	}

	return []*rabbitmq.TransportConfig{config}, nil
}

func (c *Config) Client() *client.Client {
	if cfg := c.config; cfg != nil && cfg.Client != nil {
		return cfg.Client
	}

	return &client.Client{
		Name:          os.Getenv("SENSU_CLIENT_NAME"),
		Address:       fetchEnv("SENSU_CLIENT_ADDRESS", "SENSU_ADDRESS"),
		Subscriptions: split(os.Getenv("SENSU_CLIENT_SUBSCRIPTIONS"), ","),
	}
}

func (c *Config) Checks() []*check.Check {
	if cfg := c.config; cfg != nil {
		return cfg.Checks
	}

	return []*check.Check{}
}

// Removes duplicates from string slices
func removeDuplicates(xs []string) []string {

	temp := make(map[string]bool)
	result := []string{}

	for _, x := range xs {
	   temp[x] = true
	}

	for k, _ := range temp {
	  result = append(result, k)
	}

	return result
}
<|endoftext|>


<commit_before>package sensu

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/check"
	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/client"
)

const defaultRabbitMQURI string = "amqp://guest:guest@localhost:5672/%2f"

type configFlagSet struct {
	configFile string
	verbose    bool
}

type Config struct {
	flagSet *configFlagSet
	config  *configPayload
}

type configPayload struct {
	Client      *client.Client `json:"client,omitempty"`
	Checks      []*check.Check `json:"checks,omitempty"`
	RabbitMQURI *string        `json:"rabbitmq_uri,omitempty"`
}

func fetchEnv(envs ...string) string {
	for _, env := range envs {
		if v := os.Getenv(env); v != "" {
			return v
		}
	}

	return ""
}

// split is a wrapper for strings.Split, which returns an empty array
// for empty string inputs instead of an array containing an empty string
func split(str string, token string) []string {
	if len(str) == 0 {
		return []string{}
	}

	return strings.Split(str, token)
}

func NewConfigFromFile(flagset *configFlagSet, configFile string) (*Config, error) {
	var cfg = Config{flagset, &configPayload{}}
	buf, err := ioutil.ReadFile(configFile)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(buf, &cfg.config); err != nil {
		return nil, err
	}
	return &cfg, nil
}

func NewConfigFromFlagSet(flagset *configFlagSet) (*Config, error) {
	var cfg = Config{flagset, &configPayload{}}

	if flagset != nil && flagset.configFile != "" {
		buf, err := ioutil.ReadFile(flagset.configFile)

		if err != nil {
			return nil, err
		}

		if err := json.Unmarshal(buf, &cfg.config); err != nil {
			return nil, err
		}
		return &cfg, nil
	}
	return nil, errors.New("No config file is set")

}

func (c *Config) RabbitMQURI() string {
	if cfg := c.config; cfg != nil && cfg.RabbitMQURI != nil {
		return *cfg.RabbitMQURI
	} else if uri := fetchEnv("RABBITMQ_URI", "RABBITMQ_URL"); uri != "" {
		return uri
	}

	return defaultRabbitMQURI
}

func (c *Config) Client() *client.Client {
	if cfg := c.config; cfg != nil && cfg.Client != nil {
		return cfg.Client
	}

	return &client.Client{
		Name:          os.Getenv("SENSU_CLIENT_NAME"),
		Address:       fetchEnv("SENSU_CLIENT_ADDRESS", "SENSU_ADDRESS"),
		Subscriptions: split(os.Getenv("SENSU_CLIENT_SUBSCRIPTIONS"), ","),
	}
}

func (c *Config) Checks() []*check.Check {
	if cfg := c.config; cfg != nil {
		return cfg.Checks
	}

	return []*check.Check{}
}
<commit_msg>clean up duplicate code and remove error<commit_after>package sensu

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"strings"

	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/check"
	"github.com/upfluence/sensu-client-go/Godeps/_workspace/src/github.com/upfluence/sensu-go/sensu/client"
)

const defaultRabbitMQURI string = "amqp://guest:guest@localhost:5672/%2f"

type configFlagSet struct {
	configFile string
	verbose    bool
}

type Config struct {
	flagSet *configFlagSet
	config  *configPayload
}

type configPayload struct {
	Client      *client.Client `json:"client,omitempty"`
	Checks      []*check.Check `json:"checks,omitempty"`
	RabbitMQURI *string        `json:"rabbitmq_uri,omitempty"`
}

func fetchEnv(envs ...string) string {
	for _, env := range envs {
		if v := os.Getenv(env); v != "" {
			return v
		}
	}

	return ""
}

// split is a wrapper for strings.Split, which returns an empty array
// for empty string inputs instead of an array containing an empty string
func split(str string, token string) []string {
	if len(str) == 0 {
		return []string{}
	}

	return strings.Split(str, token)
}

func NewConfigFromFile(flagset *configFlagSet, configFile string) (*Config, error) {
	cfg := Config{flagset, &configPayload{}}
	buf, err := ioutil.ReadFile(configFile)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(buf, &cfg.config); err != nil {
		return nil, err
	}
	return &cfg, nil
}

func NewConfigFromFlagSet(flagset *configFlagSet) (*Config, error) {
	if flagset != nil && flagset.configFile != "" {
		cfg, err := NewConfigFromFile(flagset, flagset.configFile)
		if err != nil {
			return nil, err
		}
		return cfg, nil
	} else {
		var cfg = Config{flagset, &configPayload{}}
		return &cfg, nil
	}

}

func (c *Config) RabbitMQURI() string {
	if cfg := c.config; cfg != nil && cfg.RabbitMQURI != nil {
		return *cfg.RabbitMQURI
	} else if uri := fetchEnv("RABBITMQ_URI", "RABBITMQ_URL"); uri != "" {
		return uri
	}

	return defaultRabbitMQURI
}

func (c *Config) Client() *client.Client {
	if cfg := c.config; cfg != nil && cfg.Client != nil {
		return cfg.Client
	}

	return &client.Client{
		Name:          os.Getenv("SENSU_CLIENT_NAME"),
		Address:       fetchEnv("SENSU_CLIENT_ADDRESS", "SENSU_ADDRESS"),
		Subscriptions: split(os.Getenv("SENSU_CLIENT_SUBSCRIPTIONS"), ","),
	}
}

func (c *Config) Checks() []*check.Check {
	if cfg := c.config; cfg != nil {
		return cfg.Checks
	}

	return []*check.Check{}
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/flosch/pongo2"
)

func (s *Server) index(w http.ResponseWriter, r *http.Request) {
	s.render(w, r, "index.html", pongo2.Context{})
}
<commit_msg>Fetch accounts for display on the homepage.<commit_after>package server

import (
	"net/http"

	"github.com/ReformedDevs/anonbot/db"
	"github.com/flosch/pongo2"
)

func (s *Server) index(w http.ResponseWriter, r *http.Request) {
	accounts := []*db.Account{}
	if err := s.database.C.Find(&accounts).Error; err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	s.render(w, r, "index.html", pongo2.Context{
		"accounts": accounts,
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bufio"
	"encoding/csv"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/go-openapi/spec"
	"github.com/golang/glog"
)

var (
	openAPIFile = flag.String("openapi", "https://raw.githubusercontent.com/kubernetes/kubernetes/master/api/openapi-spec/swagger.json", "URL to openapi-spec of Kubernetes")
	minCoverage = flag.Int("minimum-coverage", 0, "This command fails if the number of covered APIs is less than this option ratio(percent)")
	restLog     = flag.String("restlog", "", "File path to REST API operation log of Kubernetes")
)

type apiData struct {
	Method string
	URL    string
}

type apiArray []apiData

func parseOpenAPI(rawdata []byte) apiArray {
	var swaggerSpec spec.Swagger
	var apisOpenapi apiArray

	err := swaggerSpec.UnmarshalJSON(rawdata)
	if err != nil {
		log.Fatal(err)
	}

	for path, pathItem := range swaggerSpec.Paths.Paths {
		// Some paths contain "/" at the end of swagger spec, here removes "/" for comparing them easily later.
		path = strings.TrimRight(path, "/")

		// Standard HTTP methods: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#path-item-object
		methods := []string{"get", "put", "post", "delete", "options", "head", "patch"}
		for _, method := range methods {
			methodSpec, err := pathItem.JSONLookup(method)
			if err != nil {
				log.Fatal(err)
			}
			t, ok := methodSpec.(*spec.Operation)
			if ok == false {
				log.Fatal("Failed to convert methodSpec.")
			}
			if t == nil {
				continue
			}
			method := strings.ToUpper(method)
			api := apiData{
				Method: method,
				URL:    path,
			}
			apisOpenapi = append(apisOpenapi, api)
		}
	}
	return apisOpenapi
}

func getOpenAPISpec(url string) apiArray {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return parseOpenAPI(bytes)
}

//   I0919 15:34:14.943642    6611 round_trippers.go:414] GET https://172.27.138.63:6443/api/v1/namespaces/kube-system/replicationcontrollers
var reAPILog = regexp.MustCompile(`round_trippers.go:\d+\] (GET|PUT|POST|DELETE|OPTIONS|HEAD|PATCH) (\S+)`)

func parseAPILog(fp io.Reader) apiArray {
	var apisLog apiArray
	var err error

	reader := bufio.NewReaderSize(fp, 4096)
	for line := ""; err == nil; line, err = reader.ReadString('\n') {
		result := reAPILog.FindSubmatch([]byte(line))
		if len(result) == 0 {
			continue
		}
		method := strings.ToUpper(string(result[1]))
		rawurl := string(result[2])
		parsedURL, err := url.Parse(rawurl)
		if err != nil {
			log.Fatal(err)
		}
		api := apiData{
			Method: method,
			URL:    parsedURL.Path,
		}
		apisLog = append(apisLog, api)
	}
	return apisLog
}

func getAPILog(restlog string) apiArray {
	var fp *os.File
	var err error

	fp, err = os.Open(restlog)
	if err != nil {
		log.Fatal(err)
	}
	defer fp.Close()

	return parseAPILog(fp)
}

var reOpenapi = regexp.MustCompile(`({\S+?})`)

func getTestedAPIs(apisOpenapi, apisLogs apiArray) apiArray {
	var found bool
	var apisTested apiArray

	for _, openapi := range apisOpenapi {
		regURL := reOpenapi.ReplaceAllLiteralString(openapi.URL, `[^/\s]+`) + `$`
		reg := regexp.MustCompile(regURL)
		found = false
		for _, log := range apisLogs {
			if openapi.Method != log.Method {
				continue
			}
			if !reg.MatchString(log.URL) {
				continue
			}
			found = true
			apisTested = append(apisTested, openapi)
			break
		}
		if found {
			continue
		}
	}
	return apisTested
}

func getTestedAPIsByLevel(negative bool, reg *regexp.Regexp, apisOpenapi, apisTested apiArray) (apiArray, apiArray) {
	var apisTestedByLevel apiArray
	var apisAllByLevel apiArray

	for _, openapi := range apisTested {
		if (negative == false && reg.MatchString(openapi.URL)) ||
			(negative == true && !reg.MatchString(openapi.URL)) {
			apisTestedByLevel = append(apisTestedByLevel, openapi)
		}
	}
	for _, openapi := range apisOpenapi {
		if (negative == false && reg.MatchString(openapi.URL)) ||
			(negative == true && !reg.MatchString(openapi.URL)) {
			apisAllByLevel = append(apisAllByLevel, openapi)
		}
	}
	return apisTestedByLevel, apisAllByLevel
}

type coverageData struct {
	Total    string
	Tested   string
	Untested string
	Coverage string
}

func getCoverageByLevel(apisTested, apisAll apiArray) coverageData {
	var coverage coverageData

	coverage.Total = fmt.Sprint(len(apisAll))
	coverage.Tested = fmt.Sprint(len(apisTested))
	coverage.Untested = fmt.Sprint(len(apisAll) - len(apisTested))
	coverage.Coverage = fmt.Sprint(100 * len(apisTested) / len(apisAll))

	return coverage
}

//NOTE: This is messy, but the regex doesn't support negative lookahead(?!) on golang.
//This is just a workaround.
var reNotStableAPI = regexp.MustCompile(`\S+(alpha|beta)\S+`)
var reAlphaAPI = regexp.MustCompile(`\S+alpha\S+`)
var reBetaAPI = regexp.MustCompile(`\S+beta\S+`)

func outputCoverage(apisOpenapi, apisTested apiArray) {
	apisTestedByStable, apisAllByStable := getTestedAPIsByLevel(true, reNotStableAPI, apisOpenapi, apisTested)
	apisTestedByAlpha, apisAllByAlpha := getTestedAPIsByLevel(false, reAlphaAPI, apisOpenapi, apisTested)
	apisTestedByBeta, apisAllByBeta := getTestedAPIsByLevel(false, reBetaAPI, apisOpenapi, apisTested)

	coverageAll := getCoverageByLevel(apisTested, apisOpenapi)
	coverageStable := getCoverageByLevel(apisTestedByStable, apisAllByStable)
	coverageAlpha := getCoverageByLevel(apisTestedByAlpha, apisAllByAlpha)
	coverageBeta := getCoverageByLevel(apisTestedByBeta, apisAllByBeta)

	records := [][]string{
		{"API", "TOTAL", "TESTED", "UNTESTED", "COVERAGE(%)"},
		{"ALL", coverageAll.Total, coverageAll.Tested, coverageAll.Untested, coverageAll.Coverage},
		{"STABLE", coverageStable.Total, coverageStable.Tested, coverageStable.Untested, coverageStable.Coverage},
		{"Alpha", coverageAlpha.Total, coverageAlpha.Tested, coverageAlpha.Untested, coverageAlpha.Coverage},
		{"Beta", coverageBeta.Total, coverageBeta.Tested, coverageBeta.Untested, coverageBeta.Coverage},
	}
	w := csv.NewWriter(os.Stdout)
	w.WriteAll(records)

	actualCoverage, _ := strconv.Atoi(coverageAll.Coverage)
	if *minCoverage > int(actualCoverage) {
		log.Fatalf("The API coverage(%d) is lower than the specified one(%d).", actualCoverage, *minCoverage)
	}
}

func main() {
	flag.Parse()
	if len(*restLog) == 0 {
		glog.Fatal("need to set '--restlog'")
	}

	apisOpenapi := getOpenAPISpec(*openAPIFile)
	apisLogs := getAPILog(*restLog)
	apisTested := getTestedAPIs(apisOpenapi, apisLogs)
	outputCoverage(apisOpenapi, apisTested)
}
<commit_msg>apicoverage: Add output-covered-apis option<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bufio"
	"encoding/csv"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/go-openapi/spec"
	"github.com/golang/glog"
)

var (
	openAPIFile       = flag.String("openapi", "https://raw.githubusercontent.com/kubernetes/kubernetes/master/api/openapi-spec/swagger.json", "URL to openapi-spec of Kubernetes")
	outputCoveredAPIs = flag.Bool("output-covered-apis", false, "Output the list of covered APIs")
	minCoverage       = flag.Int("minimum-coverage", 0, "This command fails if the number of covered APIs is less than this option ratio(percent)")
	restLog           = flag.String("restlog", "", "File path to REST API operation log of Kubernetes")
)

type apiData struct {
	Method string
	URL    string
}

type apiArray []apiData

func parseOpenAPI(rawdata []byte) apiArray {
	var swaggerSpec spec.Swagger
	var apisOpenapi apiArray

	err := swaggerSpec.UnmarshalJSON(rawdata)
	if err != nil {
		log.Fatal(err)
	}

	for path, pathItem := range swaggerSpec.Paths.Paths {
		// Some paths contain "/" at the end of swagger spec, here removes "/" for comparing them easily later.
		path = strings.TrimRight(path, "/")

		// Standard HTTP methods: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#path-item-object
		methods := []string{"get", "put", "post", "delete", "options", "head", "patch"}
		for _, method := range methods {
			methodSpec, err := pathItem.JSONLookup(method)
			if err != nil {
				log.Fatal(err)
			}
			t, ok := methodSpec.(*spec.Operation)
			if ok == false {
				log.Fatal("Failed to convert methodSpec.")
			}
			if t == nil {
				continue
			}
			method := strings.ToUpper(method)
			api := apiData{
				Method: method,
				URL:    path,
			}
			apisOpenapi = append(apisOpenapi, api)
		}
	}
	return apisOpenapi
}

func getOpenAPISpec(url string) apiArray {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return parseOpenAPI(bytes)
}

//   I0919 15:34:14.943642    6611 round_trippers.go:414] GET https://172.27.138.63:6443/api/v1/namespaces/kube-system/replicationcontrollers
var reAPILog = regexp.MustCompile(`round_trippers.go:\d+\] (GET|PUT|POST|DELETE|OPTIONS|HEAD|PATCH) (\S+)`)

func parseAPILog(fp io.Reader) apiArray {
	var apisLog apiArray
	var err error

	reader := bufio.NewReaderSize(fp, 4096)
	for line := ""; err == nil; line, err = reader.ReadString('\n') {
		result := reAPILog.FindSubmatch([]byte(line))
		if len(result) == 0 {
			continue
		}
		method := strings.ToUpper(string(result[1]))
		rawurl := string(result[2])
		parsedURL, err := url.Parse(rawurl)
		if err != nil {
			log.Fatal(err)
		}
		api := apiData{
			Method: method,
			URL:    parsedURL.Path,
		}
		apisLog = append(apisLog, api)
	}
	return apisLog
}

func getAPILog(restlog string) apiArray {
	var fp *os.File
	var err error

	fp, err = os.Open(restlog)
	if err != nil {
		log.Fatal(err)
	}
	defer fp.Close()

	return parseAPILog(fp)
}

var reOpenapi = regexp.MustCompile(`({\S+?})`)

func getTestedAPIs(apisOpenapi, apisLogs apiArray) apiArray {
	var found bool
	var apisTested apiArray

	for _, openapi := range apisOpenapi {
		regURL := reOpenapi.ReplaceAllLiteralString(openapi.URL, `[^/\s]+`) + `$`
		reg := regexp.MustCompile(regURL)
		found = false
		for _, log := range apisLogs {
			if openapi.Method != log.Method {
				continue
			}
			if !reg.MatchString(log.URL) {
				continue
			}
			found = true
			apisTested = append(apisTested, openapi)
			break
		}
		if found {
			continue
		}
	}
	return apisTested
}

func getTestedAPIsByLevel(negative bool, reg *regexp.Regexp, apisOpenapi, apisTested apiArray) (apiArray, apiArray) {
	var apisTestedByLevel apiArray
	var apisAllByLevel apiArray

	for _, openapi := range apisTested {
		if (negative == false && reg.MatchString(openapi.URL)) ||
			(negative == true && !reg.MatchString(openapi.URL)) {
			apisTestedByLevel = append(apisTestedByLevel, openapi)
		}
	}
	for _, openapi := range apisOpenapi {
		if (negative == false && reg.MatchString(openapi.URL)) ||
			(negative == true && !reg.MatchString(openapi.URL)) {
			apisAllByLevel = append(apisAllByLevel, openapi)
		}
	}
	return apisTestedByLevel, apisAllByLevel
}

type coverageData struct {
	Total    string
	Tested   string
	Untested string
	Coverage string
}

func getCoverageByLevel(apisTested, apisAll apiArray) coverageData {
	var coverage coverageData

	coverage.Total = fmt.Sprint(len(apisAll))
	coverage.Tested = fmt.Sprint(len(apisTested))
	coverage.Untested = fmt.Sprint(len(apisAll) - len(apisTested))
	coverage.Coverage = fmt.Sprint(100 * len(apisTested) / len(apisAll))

	return coverage
}

//NOTE: This is messy, but the regex doesn't support negative lookahead(?!) on golang.
//This is just a workaround.
var reNotStableAPI = regexp.MustCompile(`\S+(alpha|beta)\S+`)
var reAlphaAPI = regexp.MustCompile(`\S+alpha\S+`)
var reBetaAPI = regexp.MustCompile(`\S+beta\S+`)

func outputCoverage(apisOpenapi, apisTested apiArray) {
	apisTestedByStable, apisAllByStable := getTestedAPIsByLevel(true, reNotStableAPI, apisOpenapi, apisTested)
	apisTestedByAlpha, apisAllByAlpha := getTestedAPIsByLevel(false, reAlphaAPI, apisOpenapi, apisTested)
	apisTestedByBeta, apisAllByBeta := getTestedAPIsByLevel(false, reBetaAPI, apisOpenapi, apisTested)

	coverageAll := getCoverageByLevel(apisTested, apisOpenapi)
	coverageStable := getCoverageByLevel(apisTestedByStable, apisAllByStable)
	coverageAlpha := getCoverageByLevel(apisTestedByAlpha, apisAllByAlpha)
	coverageBeta := getCoverageByLevel(apisTestedByBeta, apisAllByBeta)

	records := [][]string{
		{"API", "TOTAL", "TESTED", "UNTESTED", "COVERAGE(%)"},
		{"ALL", coverageAll.Total, coverageAll.Tested, coverageAll.Untested, coverageAll.Coverage},
		{"STABLE", coverageStable.Total, coverageStable.Tested, coverageStable.Untested, coverageStable.Coverage},
		{"Alpha", coverageAlpha.Total, coverageAlpha.Tested, coverageAlpha.Untested, coverageAlpha.Coverage},
		{"Beta", coverageBeta.Total, coverageBeta.Tested, coverageBeta.Untested, coverageBeta.Coverage},
	}
	w := csv.NewWriter(os.Stdout)
	w.WriteAll(records)

	actualCoverage, _ := strconv.Atoi(coverageAll.Coverage)
	if *minCoverage > int(actualCoverage) {
		log.Fatalf("The API coverage(%d) is lower than the specified one(%d).", actualCoverage, *minCoverage)
	}
}

func main() {
	flag.Parse()
	if len(*restLog) == 0 {
		glog.Fatal("need to set '--restlog'")
	}

	apisOpenapi := getOpenAPISpec(*openAPIFile)
	apisLogs := getAPILog(*restLog)
	apisTested := getTestedAPIs(apisOpenapi, apisLogs)
	outputCoverage(apisOpenapi, apisTested)
	if *outputCoveredAPIs {
		for _, openapi := range apisTested {
			fmt.Printf("%s %s\n", openapi.Method, openapi.URL)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package server

import (
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	prometheusPort      = os.Getenv("PROMETHEUS_PORT")
	prometheusPath      = os.Getenv("PROMETHEUS_PATH")
	prometheusNamespace = os.Getenv("PROMETHEUS_NAMESPACE")
	prometheusSubsystem = os.Getenv("PROMETHEUS_SUBSYSTEM")
)

var (
	promDnssecOkCount        prometheus.Counter
	promExternalRequestCount *prometheus.CounterVec
	promRequestCount         *prometheus.CounterVec
	promErrorCount           *prometheus.CounterVec
	promCacheSize            *prometheus.GaugeVec
	promCacheMiss            *prometheus.CounterVec
)

func Metrics() {
	if prometheusPath == "" {
		prometheusPath = "/metrics"
	}
	if prometheusNamespace == "" {
		prometheusNamespace = "skydns"
	}

	promExternalRequestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_request_external_count",
		Help:      "Counter of external DNS requests.",
	}, []string{"type"}) // recursive, stub, lookup
	prometheus.MustRegister(promExternalRequestCount)

	promRequestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_request_count",
		Help:      "Counter of DNS requests made.",
	}, []string{"type"}) // udp, tcp
	prometheus.MustRegister(promRequestCount)

	promDnssecOkCount = prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_dnssec_ok_count",
		Help:      "Counter of DNSSEC requests.",
	})
	prometheus.MustRegister(promDnssecOkCount) // Maybe more bits here?

	promErrorCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_error_count",
		Help:      "Counter of DNS requests resulting in an error.",
	}, []string{"error"}) // nxdomain, nodata, truncated, refused
	prometheus.MustRegister(promErrorCount)

	// Caches
	promCacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "cache_total_size",
		Help:      "The total size of all elements in the cache.",
	}, []string{"type"}) // response, signature
	prometheus.MustRegister(promCacheSize)

	promCacheMiss = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_cache_miss_count",
		Help:      "Counter of DNS requests that result in a cache miss.",
	}, []string{"type"}) // response, signature
	prometheus.MustRegister(promCacheMiss)

	if prometheusPort == "" {
		return
	}

	_, err := strconv.Atoi(prometheusPort)
	if err != nil {
		return
	}

	http.Handle(prometheusPath, prometheus.Handler())
	go func() {
		log.Fatalf("skydns: %s", http.ListenAndServe(":"+prometheusPort, nil))
	}()
}

// Counter is the metric interface used by this package
type Counter interface {
	Inc(i int64)
}

type nopCounter struct{}

func (nopCounter) Inc(_ int64) {}

// These are the old stat variables defined by this package. This
// used by graphite.
var (
	// Pondering deletion in favor of the better and more
	// maintained (by me) prometheus reporting.

	StatsForwardCount     Counter = nopCounter{}
	StatsStubForwardCount Counter = nopCounter{}
	StatsLookupCount      Counter = nopCounter{}
	StatsRequestCount     Counter = nopCounter{}
	StatsDnssecOkCount    Counter = nopCounter{}
	StatsNameErrorCount   Counter = nopCounter{}
	StatsNoDataCount      Counter = nopCounter{}

	StatsDnssecCacheMiss Counter = nopCounter{}
)
<commit_msg>Slightly more verbose when doing metrics<commit_after>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package server

import (
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	prometheusPort      = os.Getenv("PROMETHEUS_PORT")
	prometheusPath      = os.Getenv("PROMETHEUS_PATH")
	prometheusNamespace = os.Getenv("PROMETHEUS_NAMESPACE")
	prometheusSubsystem = os.Getenv("PROMETHEUS_SUBSYSTEM")
)

var (
	promDnssecOkCount        prometheus.Counter
	promExternalRequestCount *prometheus.CounterVec
	promRequestCount         *prometheus.CounterVec
	promErrorCount           *prometheus.CounterVec
	promCacheSize            *prometheus.GaugeVec
	promCacheMiss            *prometheus.CounterVec
)

func Metrics() {
	if prometheusPath == "" {
		prometheusPath = "/metrics"
	}
	if prometheusSubsystem == "" {
		prometheusSubsystem = "skydns"
	}

	promExternalRequestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_request_external_count",
		Help:      "Counter of external DNS requests.",
	}, []string{"type"}) // recursive, stub, lookup
	prometheus.MustRegister(promExternalRequestCount)

	promRequestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_request_count",
		Help:      "Counter of DNS requests made.",
	}, []string{"type"}) // udp, tcp
	prometheus.MustRegister(promRequestCount)

	promDnssecOkCount = prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_dnssec_ok_count",
		Help:      "Counter of DNSSEC requests.",
	})
	prometheus.MustRegister(promDnssecOkCount) // Maybe more bits here?

	promErrorCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_error_count",
		Help:      "Counter of DNS requests resulting in an error.",
	}, []string{"error"}) // nxdomain, nodata, truncated, refused
	prometheus.MustRegister(promErrorCount)

	// Caches
	promCacheSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "cache_total_size",
		Help:      "The total size of all elements in the cache.",
	}, []string{"type"}) // response, signature
	prometheus.MustRegister(promCacheSize)

	promCacheMiss = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Subsystem: prometheusSubsystem,
		Name:      "dns_cache_miss_count",
		Help:      "Counter of DNS requests that result in a cache miss.",
	}, []string{"type"}) // response, signature
	prometheus.MustRegister(promCacheMiss)

	if prometheusPort == "" {
		return
	}

	_, err := strconv.Atoi(prometheusPort)
	if err != nil {
		log.Fatalf("skydns: bad port for prometheus: %s", prometheusPort)
	}

	http.Handle(prometheusPath, prometheus.Handler())
	go func() {
		log.Fatalf("skydns: %s", http.ListenAndServe(":"+prometheusPort, nil))
	}()
	log.Printf("skydns: metrics enabled on :%s%s", prometheusPort, prometheusPath)
}

// Counter is the metric interface used by this package
type Counter interface {
	Inc(i int64)
}

type nopCounter struct{}

func (nopCounter) Inc(_ int64) {}

// These are the old stat variables defined by this package. This
// used by graphite.
var (
	// Pondering deletion in favor of the better and more
	// maintained (by me) prometheus reporting.

	StatsForwardCount     Counter = nopCounter{}
	StatsStubForwardCount Counter = nopCounter{}
	StatsLookupCount      Counter = nopCounter{}
	StatsRequestCount     Counter = nopCounter{}
	StatsDnssecOkCount    Counter = nopCounter{}
	StatsNameErrorCount   Counter = nopCounter{}
	StatsNoDataCount      Counter = nopCounter{}

	StatsDnssecCacheMiss Counter = nopCounter{}
)
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"github.com/Comcast/webpa-common/health"
	"github.com/Comcast/webpa-common/logging"
	"net/http"
	_ "net/http/pprof"
	"sync"
	"time"
)

// serverExecutor is a local interface describing the set of methods the underlying
// server object must implement.
type serverExecutor interface {
	ListenAndServe() error
	ListenAndServeTLS(certificateFile, keyFile string) error
}

// webPA represents a server within the WebPA cluster.  It is used for both
// primary servers (e.g. petasos) and supporting, embedded servers such as pprof.
type webPA struct {
	name            string
	address         string
	serverExecutor  serverExecutor
	certificateFile string
	keyFile         string
	logger          logging.Logger
	once            sync.Once
}

// Run executes this WebPA server.  If both certificateFile and keyFile are non-empty, this method will start
// an HTTPS server using the configured certificate and key.  Otherwise, it will
// start an HTTP server.
//
// This method spawns a goroutine that actually executes the appropriate serverExecutor.ListenXXX method.
// The supplied sync.WaitGroup is incremented, and sync.WaitGroup.Done() is called when the
// spawned goroutine exits.
//
// Run is idemptotent.  It can only be execute once, and subsequent invocations have
// no effect.
func (w *webPA) Run(waitGroup *sync.WaitGroup) error {
	w.once.Do(func() {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			var err error
			w.logger.Info("Starting [%s]", w.name)
			if len(w.certificateFile) > 0 && len(w.keyFile) > 0 {
				err = w.serverExecutor.ListenAndServeTLS(w.certificateFile, w.keyFile)
			} else {
				err = w.serverExecutor.ListenAndServe()
			}

			w.logger.Error("%v", err)
		}()
	})

	return nil
}

// WebPABuilder implements the instantiation logic for each WebPA server component
type WebPABuilder struct {
	LoggerFactory  logging.LoggerFactory
	Configuration  *Configuration
	PrimaryHandler http.Handler
	PprofHandler   http.Handler
	HealthOptions  []health.Option
}

// PrimaryAddress returns the listen address for the primary server, i.e.
// the server that listens on c.Port.
func (builder *WebPABuilder) PrimaryAddress() string {
	port := builder.Configuration.Port
	if port < 1 {
		port = DefaultPort
	}

	return fmt.Sprintf(":%d", port)
}

// HealthAddress returns the listen address for the health server
func (builder *WebPABuilder) HealthAddress() string {
	port := builder.Configuration.HealthCheckPort
	if port < 1 {
		port = DefaultHealthCheckPort
	}

	return fmt.Sprintf(":%d", port)
}

// HealthCheckInterval returns the health check interval as
// a time.Duration, using the default if c.HCInterval is nonpositive.
func (builder *WebPABuilder) HealthCheckInterval() time.Duration {
	if builder.Configuration.HealthCheckInterval < 1 {
		return DefaultHealthCheckInterval
	} else {
		return time.Duration(builder.Configuration.HealthCheckInterval)
	}
}

// PprofAddress returns the listen address for the pprof server
func (builder *WebPABuilder) PprofAddress() string {
	port := builder.Configuration.PprofPort
	if port < 1 {
		port = DefaultPprofPort
	}

	return fmt.Sprintf(":%d", port)
}

// BuildPrimary returns a Runnable that will execute the primary server
func (builder *WebPABuilder) BuildPrimary() (Runnable, error) {
	name := builder.Configuration.ServerName
	address := builder.PrimaryAddress()
	logger, err := builder.LoggerFactory.NewLogger(name)
	if err != nil {
		return nil, err
	}

	return &webPA{
		name:            name,
		address:         address,
		logger:          logger,
		certificateFile: builder.Configuration.CertificateFile,
		keyFile:         builder.Configuration.KeyFile,
		serverExecutor: &http.Server{
			Addr:      address,
			Handler:   builder.PrimaryHandler,
			ConnState: logging.NewConnectionStateLogger(logger, name),
			ErrorLog:  logging.NewErrorLog(logger, name),
		},
	}, nil
}

// BuildHealth is a factory function for both the WebPA server that exposes health statistics
// and the underlying Health object, both of which are Runnable.
func (builder *WebPABuilder) BuildHealth() (Runnable, error) {
	name := builder.Configuration.ServerName + healthSuffix
	address := builder.HealthAddress()
	logger, err := builder.LoggerFactory.NewLogger(name)
	if err != nil {
		return nil, err
	}

	var runnables [2]Runnable
	healthHandler := health.New(builder.HealthCheckInterval(), logger, builder.HealthOptions...)
	runnables[0] = healthHandler

	runnables[1] = &webPA{
		name:    name,
		address: address,
		logger:  logger,
		serverExecutor: &http.Server{
			Addr:      address,
			Handler:   healthHandler,
			ConnState: logging.NewConnectionStateLogger(logger, name),
			ErrorLog:  logging.NewErrorLog(logger, name),
		},
	}

	return RunnableSet(runnables[0:2]), nil
}

// BuildPprof is a factory function for the pprof server defined in the configuration
func (builder *WebPABuilder) BuildPprof() (Runnable, error) {
	name := builder.Configuration.ServerName + pprofSuffix
	address := builder.PprofAddress()
	logger, err := builder.LoggerFactory.NewLogger(name)
	if err != nil {
		return nil, err
	}

	pprofHandler := builder.PprofHandler
	if pprofHandler == nil {
		// by default, net/http/pprof registers the handles
		// using the default server mux
		pprofHandler = http.DefaultServeMux
	}

	return &webPA{
		name:    name,
		address: address,
		logger:  logger,
		serverExecutor: &http.Server{
			Addr:      address,
			Handler:   pprofHandler,
			ConnState: logging.NewConnectionStateLogger(logger, name),
			ErrorLog:  logging.NewErrorLog(logger, name),
		},
	}, nil
}

// BuildAll returns a single Runnable that executes all server components produced
// by this builder: pprof, health, and the primary server
func (builder *WebPABuilder) BuildAll() (runnable Runnable, err error) {
	var runnables [3]Runnable
	runnables[0], err = builder.BuildPprof()
	if err != nil {
		return
	}

	runnables[1], err = builder.BuildHealth()
	if err != nil {
		return
	}

	runnables[2], err = builder.BuildPrimary()
	if err != nil {
		return
	}

	runnable = RunnableSet(runnables[0:3])
	return
}
<commit_msg>Provide a builder type for all the various server objects<commit_after>package server

import (
	"fmt"
	"github.com/Comcast/webpa-common/health"
	"github.com/Comcast/webpa-common/logging"
	"net/http"
	_ "net/http/pprof"
	"sync"
	"time"
)

// serverExecutor is a local interface describing the set of methods the underlying
// server object must implement.
type serverExecutor interface {
	ListenAndServe() error
	ListenAndServeTLS(certificateFile, keyFile string) error
}

// webPA represents a server within the WebPA cluster.  It is used for both
// primary servers (e.g. petasos) and supporting, embedded servers such as pprof.
type webPA struct {
	name            string
	address         string
	serverExecutor  serverExecutor
	certificateFile string
	keyFile         string
	logger          logging.Logger
	once            sync.Once
}

// Run executes this WebPA server.  If both certificateFile and keyFile are non-empty, this method will start
// an HTTPS server using the configured certificate and key.  Otherwise, it will
// start an HTTP server.
//
// This method spawns a goroutine that actually executes the appropriate serverExecutor.ListenXXX method.
// The supplied sync.WaitGroup is incremented, and sync.WaitGroup.Done() is called when the
// spawned goroutine exits.
//
// Run is idemptotent.  It can only be execute once, and subsequent invocations have
// no effect.
func (w *webPA) Run(waitGroup *sync.WaitGroup) error {
	w.once.Do(func() {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			var err error
			w.logger.Info("Starting [%s]", w.name)
			if len(w.certificateFile) > 0 && len(w.keyFile) > 0 {
				err = w.serverExecutor.ListenAndServeTLS(w.certificateFile, w.keyFile)
			} else {
				err = w.serverExecutor.ListenAndServe()
			}

			w.logger.Error("%v", err)
		}()
	})

	return nil
}

// WebPABuilder implements the instantiation logic for each WebPA server component.
// This builder type is the standard way to construct and start a WebPA server.
type WebPABuilder struct {
	// LoggerFactory is used to create logging.Logger objects for use in
	// each server
	LoggerFactory logging.LoggerFactory

	// Configuration is the parsed configuration data, normaly from a JSON configuration file
	Configuration *Configuration

	// PrimaryHandler is the http.Handler used for the primary server
	PrimaryHandler http.Handler

	// PprofHandler is the optional handler for pprof traffic.  If omitted, http.DefaultServeMux
	// will be used instead
	PprofHandler http.Handler

	// HealthOptions define what health stats this server exposes for tracking
	HealthOptions []health.Option
}

// PrimaryAddress returns the listen address for the primary server, i.e.
// the server that listens on c.Port.
func (builder *WebPABuilder) PrimaryAddress() string {
	port := builder.Configuration.Port
	if port < 1 {
		port = DefaultPort
	}

	return fmt.Sprintf(":%d", port)
}

// HealthAddress returns the listen address for the health server
func (builder *WebPABuilder) HealthAddress() string {
	port := builder.Configuration.HealthCheckPort
	if port < 1 {
		port = DefaultHealthCheckPort
	}

	return fmt.Sprintf(":%d", port)
}

// HealthCheckInterval returns the health check interval as
// a time.Duration, using the default if c.HCInterval is nonpositive.
func (builder *WebPABuilder) HealthCheckInterval() time.Duration {
	if builder.Configuration.HealthCheckInterval < 1 {
		return DefaultHealthCheckInterval
	} else {
		return time.Duration(builder.Configuration.HealthCheckInterval)
	}
}

// PprofAddress returns the listen address for the pprof server
func (builder *WebPABuilder) PprofAddress() string {
	port := builder.Configuration.PprofPort
	if port < 1 {
		port = DefaultPprofPort
	}

	return fmt.Sprintf(":%d", port)
}

// BuildPrimary returns a Runnable that will execute the primary server
func (builder *WebPABuilder) BuildPrimary() (Runnable, error) {
	name := builder.Configuration.ServerName
	address := builder.PrimaryAddress()
	logger, err := builder.LoggerFactory.NewLogger(name)
	if err != nil {
		return nil, err
	}

	return &webPA{
		name:            name,
		address:         address,
		logger:          logger,
		certificateFile: builder.Configuration.CertificateFile,
		keyFile:         builder.Configuration.KeyFile,
		serverExecutor: &http.Server{
			Addr:      address,
			Handler:   builder.PrimaryHandler,
			ConnState: logging.NewConnectionStateLogger(logger, name),
			ErrorLog:  logging.NewErrorLog(logger, name),
		},
	}, nil
}

// BuildHealth is a factory function for both the WebPA server that exposes health statistics
// and the underlying Health object, both of which are Runnable.
func (builder *WebPABuilder) BuildHealth() (Runnable, error) {
	name := builder.Configuration.ServerName + healthSuffix
	address := builder.HealthAddress()
	logger, err := builder.LoggerFactory.NewLogger(name)
	if err != nil {
		return nil, err
	}

	var runnables [2]Runnable
	healthHandler := health.New(builder.HealthCheckInterval(), logger, builder.HealthOptions...)
	runnables[0] = healthHandler

	runnables[1] = &webPA{
		name:    name,
		address: address,
		logger:  logger,
		serverExecutor: &http.Server{
			Addr:      address,
			Handler:   healthHandler,
			ConnState: logging.NewConnectionStateLogger(logger, name),
			ErrorLog:  logging.NewErrorLog(logger, name),
		},
	}

	return RunnableSet(runnables[0:2]), nil
}

// BuildPprof is a factory function for the pprof server defined in the configuration
func (builder *WebPABuilder) BuildPprof() (Runnable, error) {
	name := builder.Configuration.ServerName + pprofSuffix
	address := builder.PprofAddress()
	logger, err := builder.LoggerFactory.NewLogger(name)
	if err != nil {
		return nil, err
	}

	pprofHandler := builder.PprofHandler
	if pprofHandler == nil {
		// by default, net/http/pprof registers the handles
		// using the default server mux
		pprofHandler = http.DefaultServeMux
	}

	return &webPA{
		name:    name,
		address: address,
		logger:  logger,
		serverExecutor: &http.Server{
			Addr:      address,
			Handler:   pprofHandler,
			ConnState: logging.NewConnectionStateLogger(logger, name),
			ErrorLog:  logging.NewErrorLog(logger, name),
		},
	}, nil
}

// BuildAll returns a single Runnable that executes all server components produced
// by this builder: pprof, health, and the primary server
func (builder *WebPABuilder) BuildAll() (runnable Runnable, err error) {
	var runnables [3]Runnable
	runnables[0], err = builder.BuildPprof()
	if err != nil {
		return
	}

	runnables[1], err = builder.BuildHealth()
	if err != nil {
		return
	}

	runnables[2], err = builder.BuildPrimary()
	if err != nil {
		return
	}

	runnable = RunnableSet(runnables[0:3])
	return
}
<|endoftext|>


<commit_before>// Copyright 2016 Olivier Mengué. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license that
// can be found in the LICENSE file.

// Package jsonptr implements JSON Pointer (RFC 6901) lookup
package jsonptr

import (
	"fmt"
	"strconv"
	"strings"
)

type stringError string

func (err stringError) Error() string { return string(err) }

var (
	ErrSyntax   = stringError("invalid JSON pointer")
	ErrIndex    = stringError("invalid array index")
	ErrProperty = stringError("property not found")
)

// PtrError is the structured error for JSON Pointer parsing or navigation
// errors
type PtrError struct {
	// Ptr is the substring of the original pointer where the error occurred
	Ptr string
	// Err is one of ErrSyntax, ErrIndex, ErrProperty
	Err error
}

// Error implements the 'error' interface
func (e *PtrError) Error() string {
	return strconv.Quote(e.Ptr) + ": " + e.Err.Error()
}

func syntaxError(ptr string) *PtrError {
	return &PtrError{ptr, ErrSyntax}
}

func indexError(ptr string) *PtrError {
	return &PtrError{ptr, ErrIndex}
}

func propertyError(ptr string) *PtrError {
	return &PtrError{ptr, ErrProperty}
}

func docError(ptr string, doc interface{}) *PtrError {
	return &PtrError{ptr, fmt.Errorf("not an object or array but %T", doc)}
}

func arrayIndex(token string) (int, error) {
	if len(token) == 0 {
		return -1, ErrSyntax
	}
	if len(token) == 1 {
		if token[0] == '0' {
			return 0, nil
		}
		if token[0] == '-' {
			return -1, nil
		}
	}
	if token[0] < '1' {
		return -1, ErrSyntax
	}
	var n int
	const maxInt = (1 << (strconv.IntSize - 1)) - 1
	const cutoff = maxInt/10 + 1
	for i := 0; i < len(token); i++ {
		c := token[i]
		if c < '0' || c > '9' {
			return -1, ErrSyntax
		}
		if n >= cutoff {
			// Overflow
			return -1, ErrSyntax
		}
		n *= 10
		n1 := n + int(c-'0')
		if n1 < n || n1 > maxInt {
			// Overflow
			return -1, ErrSyntax
		}
		n = n1
	}
	return n, nil
}

// Get extracts a value from a JSON-like data tree
//
// In case of error a PtrError is returned
func Get(doc interface{}, ptr string) (interface{}, error) {
	if len(ptr) == 0 {
		return doc, nil
	}
	if ptr[0] != '/' {
		return nil, syntaxError(ptr)
	}
	cur := ptr[1:]
	p := int(1)
	for {
		q := strings.IndexByte(cur, '/')
		if q == -1 {
			q = len(cur)
		}
		p += q

		switch here := (doc).(type) {
		case map[string]interface{}:
			key, err := UnescapeString(cur[:q])
			if err != nil {
				return nil, &PtrError{ptr[:p], err}
			}
			var ok bool
			if doc, ok = here[key]; !ok {
				return nil, propertyError(ptr[:p])
			}
		case []interface{}:
			n, err := arrayIndex(cur[:q])
			if err != nil {
				return nil, &PtrError{ptr[:p], err}
			}
			if n < 0 || n >= len(here) {
				return nil, indexError(ptr[:p])
			}
			doc = here[n]
		default:
			return nil, docError(ptr[:p], doc)
		}
		if p >= len(ptr) {
			break
		}
		p++
		cur = ptr[p:]
	}

	return doc, nil
}

// Set modifies a JSON-like data tree
//
// In case of error a PtrError is returned
func Set(doc *interface{}, ptr string, value interface{}) error {
	if len(ptr) == 0 {
		*doc = value
		return nil
	}
	p := strings.LastIndexByte(ptr, '/')
	if p < 0 {
		return syntaxError(ptr)
	}
	prop := ptr[p+1:]
	parentPtr := ptr[:p]

	parent, err := Get(*doc, parentPtr)
	if err != nil {
		return err
	}

	switch parent := (parent).(type) {
	case map[string]interface{}:
		key, err := UnescapeString(prop)
		if err != nil {
			return &PtrError{ptr, err}
		}
		parent[key] = value
	case []interface{}:
		n, err := arrayIndex(prop)
		if err != nil {
			return &PtrError{ptr, err}

		}
		if n == -1 {
			n = len(parent)
		} else if n < len(parent) {
			parent[n] = value
			return nil
		}

		// if n > len(parent) {
		//	return &PtrError{ptr, ErrIndex}
		//}

		// TODO make+copy
		for i := n - len(parent) - 1; i > 0; i-- {
			parent = append(parent, nil)
		}
		parent = append(parent, value)
		// We appended beyond original len, so the slice changed so we have to
		// store the new one at the old place
		// No error can happen as we already parsed the pointer
		_ = Set(doc, parentPtr, parent)
	default:
		return docError(parentPtr, parent)
	}

	return nil
}
<commit_msg>doc: Improve package description<commit_after>// Copyright 2016 Olivier Mengué. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license that
// can be found in the LICENSE file.

// Package jsonptr implements JSON Pointer (RFC 6901) lookup. Fast and strong testsuite.
//
// Any part of a data tree made of []interface{} or map[string]interface{}
// can be dereferenced with a JSON Pointer.
package jsonptr

import (
	"fmt"
	"strconv"
	"strings"
)

type stringError string

func (err stringError) Error() string { return string(err) }

var (
	ErrSyntax   = stringError("invalid JSON pointer")
	ErrIndex    = stringError("invalid array index")
	ErrProperty = stringError("property not found")
)

// PtrError is the structured error for JSON Pointer parsing or navigation
// errors
type PtrError struct {
	// Ptr is the substring of the original pointer where the error occurred
	Ptr string
	// Err is one of ErrSyntax, ErrIndex, ErrProperty
	Err error
}

// Error implements the 'error' interface
func (e *PtrError) Error() string {
	return strconv.Quote(e.Ptr) + ": " + e.Err.Error()
}

func syntaxError(ptr string) *PtrError {
	return &PtrError{ptr, ErrSyntax}
}

func indexError(ptr string) *PtrError {
	return &PtrError{ptr, ErrIndex}
}

func propertyError(ptr string) *PtrError {
	return &PtrError{ptr, ErrProperty}
}

func docError(ptr string, doc interface{}) *PtrError {
	return &PtrError{ptr, fmt.Errorf("not an object or array but %T", doc)}
}

func arrayIndex(token string) (int, error) {
	if len(token) == 0 {
		return -1, ErrSyntax
	}
	if len(token) == 1 {
		if token[0] == '0' {
			return 0, nil
		}
		if token[0] == '-' {
			return -1, nil
		}
	}
	if token[0] < '1' {
		return -1, ErrSyntax
	}
	var n int
	const maxInt = (1 << (strconv.IntSize - 1)) - 1
	const cutoff = maxInt/10 + 1
	for i := 0; i < len(token); i++ {
		c := token[i]
		if c < '0' || c > '9' {
			return -1, ErrSyntax
		}
		if n >= cutoff {
			// Overflow
			return -1, ErrSyntax
		}
		n *= 10
		n1 := n + int(c-'0')
		if n1 < n || n1 > maxInt {
			// Overflow
			return -1, ErrSyntax
		}
		n = n1
	}
	return n, nil
}

// Get extracts a value from a JSON-like data tree
//
// In case of error a PtrError is returned
func Get(doc interface{}, ptr string) (interface{}, error) {
	if len(ptr) == 0 {
		return doc, nil
	}
	if ptr[0] != '/' {
		return nil, syntaxError(ptr)
	}
	cur := ptr[1:]
	p := int(1)
	for {
		q := strings.IndexByte(cur, '/')
		if q == -1 {
			q = len(cur)
		}
		p += q

		switch here := (doc).(type) {
		case map[string]interface{}:
			key, err := UnescapeString(cur[:q])
			if err != nil {
				return nil, &PtrError{ptr[:p], err}
			}
			var ok bool
			if doc, ok = here[key]; !ok {
				return nil, propertyError(ptr[:p])
			}
		case []interface{}:
			n, err := arrayIndex(cur[:q])
			if err != nil {
				return nil, &PtrError{ptr[:p], err}
			}
			if n < 0 || n >= len(here) {
				return nil, indexError(ptr[:p])
			}
			doc = here[n]
		default:
			return nil, docError(ptr[:p], doc)
		}
		if p >= len(ptr) {
			break
		}
		p++
		cur = ptr[p:]
	}

	return doc, nil
}

// Set modifies a JSON-like data tree
//
// In case of error a PtrError is returned
func Set(doc *interface{}, ptr string, value interface{}) error {
	if len(ptr) == 0 {
		*doc = value
		return nil
	}
	p := strings.LastIndexByte(ptr, '/')
	if p < 0 {
		return syntaxError(ptr)
	}
	prop := ptr[p+1:]
	parentPtr := ptr[:p]

	parent, err := Get(*doc, parentPtr)
	if err != nil {
		return err
	}

	switch parent := (parent).(type) {
	case map[string]interface{}:
		key, err := UnescapeString(prop)
		if err != nil {
			return &PtrError{ptr, err}
		}
		parent[key] = value
	case []interface{}:
		n, err := arrayIndex(prop)
		if err != nil {
			return &PtrError{ptr, err}

		}
		if n == -1 {
			n = len(parent)
		} else if n < len(parent) {
			parent[n] = value
			return nil
		}

		// if n > len(parent) {
		//	return &PtrError{ptr, ErrIndex}
		//}

		// TODO make+copy
		for i := n - len(parent) - 1; i > 0; i-- {
			parent = append(parent, nil)
		}
		parent = append(parent, value)
		// We appended beyond original len, so the slice changed so we have to
		// store the new one at the old place
		// No error can happen as we already parsed the pointer
		_ = Set(doc, parentPtr, parent)
	default:
		return docError(parentPtr, parent)
	}

	return nil
}
<|endoftext|>


<commit_before>// Package jsonrpc provides a JSON-RPC 2.0 client that sends JSON-RPC requests and receives JSON-RPC responses using HTTP.
package jsonrpc

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
)

const (
	jsonrpcVersion = "2.0"
	defaultID      = 1
)

// RPCClient sends JSON-RPC requests over HTTP to the provided JSON-RPC backend.
//
// RPCClient is created using the factory function NewClient().
type RPCClient interface {
	// Call is a very handy function to send a JSON-RPC request to the server endpoint.
	//
	// params can only be an array or an object, no primitive values.
	// So there are a few simple rules to notice:
	//
	// 1. no params: params field is omitted. e.g. Call("getinfo")
	//
	// 2. single params primitive value: value is wrapped in array. e.g. Call("getByID", 1423)
	//
	// 3. single params value array or object: value is unchanged. e.g. Call("storePerson", &Person{Name: "Alex"})
	//
	// 4. multiple params values: always wrapped in array. e.g. Call("setDetails", "Alex, 35, "Germany", true)
	//
	// Examples:
	//   Call("getinfo") -> {"method": "getinfo"}
	//   Call("getPersonId", 123) -> {"method": "getPersonId", "params": [123]}
	//   Call("setName", "Alex") -> {"method": "setName", "params": ["Alex"]}
	//   Call("setMale", true) -> {"method": "setMale", "params": [true]}
	//   Call("setNumbers", []int{1, 2, 3}) -> {"method": "setNumbers", "params": [1, 2, 3]}
	//   Call("setNumbers", 1, 2, 3) -> {"method": "setNumbers", "params": [1, 2, 3]}
	//   Call("savePerson", &Person{Name: "Alex", Age: 35}) -> {"method": "savePerson", "params": {"name": "Alex", "age": 35}}
	//   Call("setPersonDetails", "Alex", 35, "Germany") -> {"method": "setPersonDetails", "params": ["Alex", 35, "Germany"}}
	//
	// for more information, see the examples or the unit tests
	Call(method string, params ...interface{}) (*RPCResponse, error)

	// CallFor is a very handy function to send a JSON-RPC request to the server endpoint
	// and directly specify an object to store the response.
	//
	// out: will store the unmarshaled object, if request was successful.
	// should always be provided by references. can be nil even on success.
	//
	// method and params: see Call() function
	//
	// if the request was not successful or the rpc response returns an error,
	// error holds the error object. if it was an JSON-RPC error it can be casted
	// to *RPCError.
	//
	CallFor(out interface{}, method string, params ...interface{}) error
}

// RPCRequest represents a JSON-RPC request object.
//
// Method: string containing the method to be invoked
//
// Params: can be nil. if not must be an json array or object
//
// ID: may always set to 1 for single requests. Should be unique for every request in one batch request.
//
// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
//
// See: http://www.jsonrpc.org/specification#request_object
type RPCRequest struct {
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
	ID      uint        `json:"id,omitempty"`
	JSONRPC string      `json:"jsonrpc"`
}

// RPCResponse represents a JSON-RPC response object.
//
// Result: holds the result of the rpc call if no error occurred, nil otherwise. can be nil even on success.
//
// Error: holds an RPCError object if an error occurred. must be nil on success.
//
// ID: may always be 1 for single requests. should be unique for every request in one batch request.
//
// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
//
// See: http://www.jsonrpc.org/specification#response_object
type RPCResponse struct {
	JSONRPC string      `json:"jsonrpc"`
	Result  interface{} `json:"result,omitempty"`
	Error   *RPCError   `json:"error,omitempty"`
	ID      uint        `json:"id"`
}

// RPCError represents a JSON-RPC error object if an RPC error occurred.
//
// Code: holds the error code
//
// Message: holds a short error message
//
// Data: holds additional error data, may be nil
//
// See: http://www.jsonrpc.org/specification#error_object
type RPCError struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// Error function is provided to be used as error object.
func (e *RPCError) Error() string {
	return strconv.Itoa(e.Code) + ":" + e.Message
}

type rpcClient struct {
	endpoint      string
	httpClient    *http.Client
	customHeaders map[string]string
}

// RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient.
//
// HTTPClient: provide a custom http.Client (e.g. to set a proxy, or tls options)
//
// CustomHeaders: provide custom headers, e.g. to set BasicAuth
type RPCClientOpts struct {
	HTTPClient    *http.Client
	CustomHeaders map[string]string
}

// NewClient returns a new RPCClient instance with default configuration.
//
// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
func NewClient(endpoint string) RPCClient {
	return NewClientWithOpts(endpoint, nil)
}

// NewClientWithOpts returns a new RPCClient instance with custom configuration.
//
// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
//
// opts: RPCClientOpts provide custom configuration
func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient {
	rpcClient := &rpcClient{
		endpoint:      endpoint,
		httpClient:    &http.Client{},
		customHeaders: make(map[string]string),
	}

	if opts == nil {
		return rpcClient
	}

	if opts.HTTPClient != nil {
		rpcClient.httpClient = opts.HTTPClient
	}

	if opts.CustomHeaders != nil {
		for k, v := range opts.CustomHeaders {
			rpcClient.customHeaders[k] = v
		}
	}

	return rpcClient
}

func (client *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, error) {

	request := &RPCRequest{
		ID:      defaultID,
		Method:  method,
		Params:  transformParams(params...),
		JSONRPC: jsonrpcVersion,
	}

	return client.doCall(request)
}

func (client *rpcClient) CallFor(out interface{}, method string, params ...interface{}) error {
	rpcResponse, err := client.Call(method, params...)
	if err != nil {
		return err
	}

	if rpcResponse.Error != nil {
		return rpcResponse.Error
	}

	return rpcResponse.GetObject(out)
}

func (client *rpcClient) newRequest(req interface{}) (*http.Request, error) {

	body, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	request, err := http.NewRequest("POST", client.endpoint, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}

	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "application/json")

	// set default headers first, so that even content type and accept can be overwritten
	for k, v := range client.customHeaders {
		request.Header.Set(k, v)
	}

	return request, nil
}

func (client *rpcClient) doCall(RPCRequest *RPCRequest) (*RPCResponse, error) {

	httpRequest, err := client.newRequest(RPCRequest)
	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
	}
	httpResponse, err := client.httpClient.Do(httpRequest)
	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
	}
	defer httpResponse.Body.Close()

	var rpcResponse *RPCResponse
	decoder := json.NewDecoder(httpResponse.Body)
	decoder.DisallowUnknownFields()
	decoder.UseNumber()
	err = decoder.Decode(&rpcResponse)

	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
	}

	if rpcResponse == nil {
		return nil, fmt.Errorf("rpc call %v() on %v status code: %v. unable to decode body to rpc response object", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode)
	}

	return rpcResponse, nil
}

func transformParams(params ...interface{}) interface{} {
	var finalParams interface{}

	// if params was nil skip this and p stays nil
	if params != nil {
		switch len(params) {
		case 0: // no parameters were provided, do nothing so finalParam is nil and will be omitted
		case 1: // one param was provided, use it directly as is, or wrap primitive types in array
			if params[0] != nil {
				var typeOf reflect.Type

				// traverse until nil or not a pointer type
				for typeOf = reflect.TypeOf(params[0]); typeOf != nil && typeOf.Kind() == reflect.Ptr; typeOf = typeOf.Elem() {
				}

				if typeOf != nil {
					// now check if we can directly marshal the type or if it must be wrapped in an array
					switch typeOf.Kind() {
					// for these types we just do nothing, since value of p is already unwrapped from the array params
					case reflect.Struct:
						finalParams = params[0]
					case reflect.Array:
						finalParams = params[0]
					case reflect.Slice:
						finalParams = params[0]
					case reflect.Interface:
						finalParams = params[0]
					case reflect.Map:
						finalParams = params[0]
					default: // everything else must stay in an array (int, string, etc)
						finalParams = params
					}
				}
			} else {
				finalParams = params
			}
		default: // if more than one parameter was provided it should be treated as an array
			finalParams = params
		}
	}

	return finalParams
}

// GetInt converts the rpc response to an int64 and returns it.
//
// If result was not an integer an error is returned.
func (RPCResponse *RPCResponse) GetInt() (int64, error) {
	val, ok := RPCResponse.Result.(json.Number)
	if !ok {
		return 0, fmt.Errorf("could not parse int64 from %s", RPCResponse.Result)
	}

	i, err := val.Int64()
	if err != nil {
		return 0, err
	}

	return i, nil
}

// GetFloat converts the rpc response to float64 and returns it.
//
// If result was not an float64 an error is returned.
func (RPCResponse *RPCResponse) GetFloat() (float64, error) {
	val, ok := RPCResponse.Result.(json.Number)
	if !ok {
		return 0, fmt.Errorf("could not parse float64 from %s", RPCResponse.Result)
	}

	f, err := val.Float64()
	if err != nil {
		return 0, err
	}

	return f, nil
}

// GetBool converts the rpc response to a bool and returns it.
//
// If result was not a bool an error is returned.
func (RPCResponse *RPCResponse) GetBool() (bool, error) {
	val, ok := RPCResponse.Result.(bool)
	if !ok {
		return false, fmt.Errorf("could not parse bool from %s", RPCResponse.Result)
	}

	return val, nil
}

// GetString converts the rpc response to a string and returns it.
//
// If result was not a string an error is returned.
func (RPCResponse *RPCResponse) GetString() (string, error) {
	val, ok := RPCResponse.Result.(string)
	if !ok {
		return "", fmt.Errorf("could not parse string from %s", RPCResponse.Result)
	}

	return val, nil
}

// GetObject converts the rpc response to an arbitrary type.
//
// The function works as you would expect it from json.Unmarshal()
func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
	js, err := json.Marshal(RPCResponse.Result)
	if err != nil {
		return err
	}

	err = json.Unmarshal(js, toType)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>added HTTPError<commit_after>// Package jsonrpc provides a JSON-RPC 2.0 client that sends JSON-RPC requests and receives JSON-RPC responses using HTTP.
package jsonrpc

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
)

const (
	jsonrpcVersion = "2.0"
	defaultID      = 1
)

// RPCClient sends JSON-RPC requests over HTTP to the provided JSON-RPC backend.
//
// RPCClient is created using the factory function NewClient().
type RPCClient interface {
	// Call is a very handy function to send a JSON-RPC request to the server endpoint.
	//
	// params can only be an array or an object, no primitive values.
	// So there are a few simple rules to notice:
	//
	// 1. no params: params field is omitted. e.g. Call("getinfo")
	//
	// 2. single params primitive value: value is wrapped in array. e.g. Call("getByID", 1423)
	//
	// 3. single params value array or object: value is unchanged. e.g. Call("storePerson", &Person{Name: "Alex"})
	//
	// 4. multiple params values: always wrapped in array. e.g. Call("setDetails", "Alex, 35, "Germany", true)
	//
	// Examples:
	//   Call("getinfo") -> {"method": "getinfo"}
	//   Call("getPersonId", 123) -> {"method": "getPersonId", "params": [123]}
	//   Call("setName", "Alex") -> {"method": "setName", "params": ["Alex"]}
	//   Call("setMale", true) -> {"method": "setMale", "params": [true]}
	//   Call("setNumbers", []int{1, 2, 3}) -> {"method": "setNumbers", "params": [1, 2, 3]}
	//   Call("setNumbers", 1, 2, 3) -> {"method": "setNumbers", "params": [1, 2, 3]}
	//   Call("savePerson", &Person{Name: "Alex", Age: 35}) -> {"method": "savePerson", "params": {"name": "Alex", "age": 35}}
	//   Call("setPersonDetails", "Alex", 35, "Germany") -> {"method": "setPersonDetails", "params": ["Alex", 35, "Germany"}}
	//
	// for more information, see the examples or the unit tests
	Call(method string, params ...interface{}) (*RPCResponse, error)

	// CallFor is a very handy function to send a JSON-RPC request to the server endpoint
	// and directly specify an object to store the response.
	//
	// out: will store the unmarshaled object, if request was successful.
	// should always be provided by references. can be nil even on success.
	//
	// method and params: see Call() function
	//
	// if the request was not successful or the rpc response returns an error,
	// error holds the error object. if it was an JSON-RPC error it can be casted
	// to *RPCError.
	//
	CallFor(out interface{}, method string, params ...interface{}) error
}

// RPCRequest represents a JSON-RPC request object.
//
// Method: string containing the method to be invoked
//
// Params: can be nil. if not must be an json array or object
//
// ID: may always set to 1 for single requests. Should be unique for every request in one batch request.
//
// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
//
// See: http://www.jsonrpc.org/specification#request_object
type RPCRequest struct {
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
	ID      uint        `json:"id,omitempty"`
	JSONRPC string      `json:"jsonrpc"`
}

// RPCResponse represents a JSON-RPC response object.
//
// Result: holds the result of the rpc call if no error occurred, nil otherwise. can be nil even on success.
//
// Error: holds an RPCError object if an error occurred. must be nil on success.
//
// ID: may always be 1 for single requests. should be unique for every request in one batch request.
//
// JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0
//
// See: http://www.jsonrpc.org/specification#response_object
type RPCResponse struct {
	JSONRPC string      `json:"jsonrpc"`
	Result  interface{} `json:"result,omitempty"`
	Error   *RPCError   `json:"error,omitempty"`
	ID      uint        `json:"id"`
}

// RPCError represents a JSON-RPC error object if an RPC error occurred.
//
// Code: holds the error code
//
// Message: holds a short error message
//
// Data: holds additional error data, may be nil
//
// See: http://www.jsonrpc.org/specification#error_object
type RPCError struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// Error function is provided to be used as error object.
func (e *RPCError) Error() string {
	return strconv.Itoa(e.Code) + ":" + e.Message
}

// HTTPError represents a error that occurred on HTTP level.
//
// An error of type HTTPError is returned when a HTTP error occurred (status code)
// and the body could not be parsed to a valid RPCResponse object that holds a RPCError.
//
// Otherwise a RPCResponse object is returned with a RPCError field that is not nil.
type HTTPError struct {
	Code int
	err  error
}

// Error function is provided to be used as error object.
func (e *HTTPError) Error() string {
	return e.err.Error()
}

type rpcClient struct {
	endpoint      string
	httpClient    *http.Client
	customHeaders map[string]string
}

// RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient.
//
// HTTPClient: provide a custom http.Client (e.g. to set a proxy, or tls options)
//
// CustomHeaders: provide custom headers, e.g. to set BasicAuth
type RPCClientOpts struct {
	HTTPClient    *http.Client
	CustomHeaders map[string]string
}

// NewClient returns a new RPCClient instance with default configuration.
//
// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
func NewClient(endpoint string) RPCClient {
	return NewClientWithOpts(endpoint, nil)
}

// NewClientWithOpts returns a new RPCClient instance with custom configuration.
//
// endpoint: JSON-RPC service URL to which JSON-RPC requests are sent.
//
// opts: RPCClientOpts provide custom configuration
func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient {
	rpcClient := &rpcClient{
		endpoint:      endpoint,
		httpClient:    &http.Client{},
		customHeaders: make(map[string]string),
	}

	if opts == nil {
		return rpcClient
	}

	if opts.HTTPClient != nil {
		rpcClient.httpClient = opts.HTTPClient
	}

	if opts.CustomHeaders != nil {
		for k, v := range opts.CustomHeaders {
			rpcClient.customHeaders[k] = v
		}
	}

	return rpcClient
}

func (client *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, error) {

	request := &RPCRequest{
		ID:      defaultID,
		Method:  method,
		Params:  transformParams(params...),
		JSONRPC: jsonrpcVersion,
	}

	return client.doCall(request)
}

func (client *rpcClient) CallFor(out interface{}, method string, params ...interface{}) error {
	rpcResponse, err := client.Call(method, params...)
	if err != nil {
		return err
	}

	if rpcResponse.Error != nil {
		return rpcResponse.Error
	}

	return rpcResponse.GetObject(out)
}

func (client *rpcClient) newRequest(req interface{}) (*http.Request, error) {

	body, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	request, err := http.NewRequest("POST", client.endpoint, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}

	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "application/json")

	// set default headers first, so that even content type and accept can be overwritten
	for k, v := range client.customHeaders {
		request.Header.Set(k, v)
	}

	return request, nil
}

func (client *rpcClient) doCall(RPCRequest *RPCRequest) (*RPCResponse, error) {

	httpRequest, err := client.newRequest(RPCRequest)
	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
	}
	httpResponse, err := client.httpClient.Do(httpRequest)
	if err != nil {
		return nil, fmt.Errorf("rpc call %v() on %v: %v", RPCRequest.Method, httpRequest.URL.String(), err.Error())
	}
	defer httpResponse.Body.Close()

	var rpcResponse *RPCResponse
	decoder := json.NewDecoder(httpResponse.Body)
	decoder.DisallowUnknownFields()
	decoder.UseNumber()
	err = decoder.Decode(&rpcResponse)

	// parsing error
	if err != nil {
		// if we have some http error, return it
		if httpResponse.StatusCode >= 400 {
			return nil, &HTTPError{
				Code: httpResponse.StatusCode,
				err:  fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error()),
			}
		}
		return nil, fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %v", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error())
	}

	// response body empty
	if rpcResponse == nil {
		// if we have some http error, return it
		if httpResponse.StatusCode >= 400 {
			return nil, &HTTPError{
				Code: httpResponse.StatusCode,
				err:  fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode),
			}
		}
		return nil, fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode)
	}

	return rpcResponse, nil
}

func transformParams(params ...interface{}) interface{} {
	var finalParams interface{}

	// if params was nil skip this and p stays nil
	if params != nil {
		switch len(params) {
		case 0: // no parameters were provided, do nothing so finalParam is nil and will be omitted
		case 1: // one param was provided, use it directly as is, or wrap primitive types in array
			if params[0] != nil {
				var typeOf reflect.Type

				// traverse until nil or not a pointer type
				for typeOf = reflect.TypeOf(params[0]); typeOf != nil && typeOf.Kind() == reflect.Ptr; typeOf = typeOf.Elem() {
				}

				if typeOf != nil {
					// now check if we can directly marshal the type or if it must be wrapped in an array
					switch typeOf.Kind() {
					// for these types we just do nothing, since value of p is already unwrapped from the array params
					case reflect.Struct:
						finalParams = params[0]
					case reflect.Array:
						finalParams = params[0]
					case reflect.Slice:
						finalParams = params[0]
					case reflect.Interface:
						finalParams = params[0]
					case reflect.Map:
						finalParams = params[0]
					default: // everything else must stay in an array (int, string, etc)
						finalParams = params
					}
				}
			} else {
				finalParams = params
			}
		default: // if more than one parameter was provided it should be treated as an array
			finalParams = params
		}
	}

	return finalParams
}

// GetInt converts the rpc response to an int64 and returns it.
//
// If result was not an integer an error is returned.
func (RPCResponse *RPCResponse) GetInt() (int64, error) {
	val, ok := RPCResponse.Result.(json.Number)
	if !ok {
		return 0, fmt.Errorf("could not parse int64 from %s", RPCResponse.Result)
	}

	i, err := val.Int64()
	if err != nil {
		return 0, err
	}

	return i, nil
}

// GetFloat converts the rpc response to float64 and returns it.
//
// If result was not an float64 an error is returned.
func (RPCResponse *RPCResponse) GetFloat() (float64, error) {
	val, ok := RPCResponse.Result.(json.Number)
	if !ok {
		return 0, fmt.Errorf("could not parse float64 from %s", RPCResponse.Result)
	}

	f, err := val.Float64()
	if err != nil {
		return 0, err
	}

	return f, nil
}

// GetBool converts the rpc response to a bool and returns it.
//
// If result was not a bool an error is returned.
func (RPCResponse *RPCResponse) GetBool() (bool, error) {
	val, ok := RPCResponse.Result.(bool)
	if !ok {
		return false, fmt.Errorf("could not parse bool from %s", RPCResponse.Result)
	}

	return val, nil
}

// GetString converts the rpc response to a string and returns it.
//
// If result was not a string an error is returned.
func (RPCResponse *RPCResponse) GetString() (string, error) {
	val, ok := RPCResponse.Result.(string)
	if !ok {
		return "", fmt.Errorf("could not parse string from %s", RPCResponse.Result)
	}

	return val, nil
}

// GetObject converts the rpc response to an arbitrary type.
//
// The function works as you would expect it from json.Unmarshal()
func (RPCResponse *RPCResponse) GetObject(toType interface{}) error {
	js, err := json.Marshal(RPCResponse.Result)
	if err != nil {
		return err
	}

	err = json.Unmarshal(js, toType)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package irma

import (
	goerrors "errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strconv"

	"github.com/go-errors/errors"
	"github.com/privacybydesign/gabi/big"
	"github.com/privacybydesign/gabi/gabikeys"
)

type (
	// PrivateKeyRing provides access to a set of private keys.
	PrivateKeyRing interface {
		// Latest returns the private key with the highest counter for the specified issuer, if any,
		// or an error.
		Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error)

		// Get returns the specified private key, or an error.
		Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error)

		// Iterate executes the specified function on each private key of the specified issuer
		// present in the ring. The private keys are offered to the function in no particular order,
		// and the same key may be offered multiple times. Returns on the first error returned
		// by the function.
		Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error
	}

	// PrivateKeyRingFolder represents a folder on disk containing private keys with filenames
	// of the form scheme.issuer.xml and scheme.issuer.counter.xml.
	PrivateKeyRingFolder struct {
		path string
		conf *Configuration
	}

	// privateKeyRingScheme provides access to private keys present in a scheme.
	privateKeyRingScheme struct {
		conf *Configuration
	}

	// privateKeyRingMerge is a merge of multiple key rings into one, provides access to the
	// private keys of all of them.
	privateKeyRingMerge struct {
		rings []PrivateKeyRing
	}
)

var (
	ErrMissingPrivateKey = fmt.Errorf("issuer private key not found: %w", os.ErrNotExist)
)

func NewPrivateKeyRingFolder(path string, conf *Configuration) (*PrivateKeyRingFolder, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	ring := &PrivateKeyRingFolder{path, conf}
	for _, file := range files {
		filename := file.Name()
		issuerid, counter, err := ring.parseFilename(filename)
		if err != nil {
			return nil, err
		}
		if issuerid == nil {
			Logger.WithField("file", filename).Infof("Skipping non-private key file encountered in private keys path")
			continue
		}
		sk, err := ring.readFile(filename, *issuerid)
		if err != nil {
			return nil, err
		}
		if counter != nil && *counter != sk.Counter {
			return nil, errors.Errorf("private key %s has wrong counter %d in filename, should be %d", filename, counter, sk.Counter)
		}
	}
	return ring, nil
}

func (_ *PrivateKeyRingFolder) parseFilename(filename string) (*IssuerIdentifier, *uint, error) {
	// This regexp returns one of the following:
	// [ "foo.bar.xml", "foo.bar", "", "" ] in case of "foo.bar.xml"
	// [ "foo.bar.xml", "foo.bar", ".2", "2" ] in case of "foo.bar.2.xml"
	// nil in case of other files.
	matches := regexp.MustCompile(`^([^.]+\.[^.]+)(\.(\d+))?\.xml$`).FindStringSubmatch(filename)

	if len(matches) != 4 {
		return nil, nil, nil
	}
	issuerid := NewIssuerIdentifier(matches[1])
	if matches[3] == "" {
		return &issuerid, nil, nil
	}
	counter, err := strconv.ParseUint(matches[3], 10, 32)
	if err != nil {
		return nil, nil, err
	}
	c := uint(counter)
	return &issuerid, &c, nil
}

func (p *PrivateKeyRingFolder) readFile(filename string, id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	scheme := p.conf.SchemeManagers[id.SchemeManagerIdentifier()]
	if scheme == nil {
		return nil, errors.Errorf("Private key of issuer %s belongs to unknown scheme", id.String())
	}
	sk, err := gabikeys.NewPrivateKeyFromFile(filepath.Join(p.path, filename), scheme.Demo)
	if err != nil {
		return nil, err
	}
	if err = validatePrivateKey(id, sk, p.conf); err != nil {
		return nil, err
	}
	return sk, nil
}

func (p *PrivateKeyRingFolder) Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error) {
	sk, err := p.readFile(fmt.Sprintf("%s.%d.xml", id.String(), counter), id)
	if err != nil && !goerrors.Is(err, os.ErrNotExist) {
		return nil, err
	}
	if sk != nil {
		return sk, nil
	}
	sk, err = p.readFile(fmt.Sprintf("%s.xml", id.String()), id)
	if err != nil {
		return nil, err
	}
	if counter != sk.Counter {
		return nil, ErrMissingPrivateKey
	}
	return sk, nil
}

func (p *PrivateKeyRingFolder) Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	var sk *gabikeys.PrivateKey
	if err := p.Iterate(id, func(s *gabikeys.PrivateKey) error {
		if sk == nil || s.Counter > sk.Counter {
			sk = s
		}
		return nil
	}); err != nil {
		return nil, err
	}
	if sk == nil {
		return nil, ErrMissingPrivateKey
	}
	return sk, nil
}

func (p *PrivateKeyRingFolder) Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error {
	files, err := filepath.Glob(filepath.Join(p.path, fmt.Sprintf("%s.xml", id.String())))
	if err != nil {
		return err
	}
	filesWithCounter, err := filepath.Glob(filepath.Join(p.path, fmt.Sprintf("%s.*.xml", id.String())))
	if err != nil {
		return err
	}
	for _, file := range append(files, filesWithCounter...) {
		sk, err := p.readFile(filepath.Base(file), id)
		if err != nil {
			return err
		}
		if err = f(sk); err != nil {
			return err
		}
	}
	return nil
}

func newPrivateKeyRingScheme(conf *Configuration) (*privateKeyRingScheme, error) {
	ring := &privateKeyRingScheme{conf}
	if err := validatePrivateKeyRing(ring, conf); err != nil {
		return nil, err
	}
	return ring, nil
}

func (p *privateKeyRingScheme) counters(issuerid IssuerIdentifier) (i []uint, err error) {
	scheme := p.conf.SchemeManagers[issuerid.SchemeManagerIdentifier()]
	return matchKeyPattern(filepath.Join(scheme.path(), issuerid.Name(), "PrivateKeys", "*"))
}

func (p *privateKeyRingScheme) Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error) {
	schemeID := id.SchemeManagerIdentifier()
	scheme := p.conf.SchemeManagers[schemeID]
	if scheme == nil {
		return nil, errors.Errorf("Private key of issuer %s belongs to unknown scheme", id.String())
	}
	file := filepath.Join(scheme.path(), id.Name(), "PrivateKeys", strconv.FormatUint(uint64(counter), 10)+".xml")
	sk, err := gabikeys.NewPrivateKeyFromFile(file, scheme.Demo)
	if err != nil {
		return nil, err
	}
	if sk.Counter != counter {
		return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String())
	}
	if err = validatePrivateKey(id, sk, p.conf); err != nil {
		return nil, err
	}
	return sk, nil
}

func (p *privateKeyRingScheme) Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	counters, err := p.counters(id)
	if err != nil {
		return nil, err
	}
	if len(counters) == 0 {
		return nil, ErrMissingPrivateKey
	}
	return p.Get(id, counters[len(counters)-1])
}

func (p *privateKeyRingScheme) Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error {
	indices, err := p.counters(id)
	if err != nil {
		return err
	}
	for _, counter := range indices {
		sk, err := p.Get(id, counter)
		if err != nil {
			return err
		}
		if err = f(sk); err != nil {
			return err
		}
	}
	return nil
}

func (p *privateKeyRingMerge) Add(ring PrivateKeyRing) {
	p.rings = append(p.rings, ring)
}

func (p *privateKeyRingMerge) Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error) {
	for _, ring := range p.rings {
		sk, err := ring.Get(id, counter)
		if err == nil {
			return sk, nil
		}
		if !goerrors.Is(err, os.ErrNotExist) {
			return nil, err
		}
	}
	return nil, ErrMissingPrivateKey
}

func (p *privateKeyRingMerge) Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	var sk *gabikeys.PrivateKey
	for _, ring := range p.rings {
		s, err := ring.Latest(id)
		if err != nil && !goerrors.Is(err, os.ErrNotExist) {
			return nil, err
		}
		if s != nil && (sk == nil || s.Counter > sk.Counter) {
			sk = s
		}
	}
	if sk == nil {
		return nil, ErrMissingPrivateKey
	}
	return sk, nil
}

func (p *privateKeyRingMerge) Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error {
	for _, ring := range p.rings {
		if err := ring.Iterate(id, f); err != nil {
			return err
		}
	}
	return nil
}

func validatePrivateKey(issuerid IssuerIdentifier, sk *gabikeys.PrivateKey, conf *Configuration) error {
	if _, ok := conf.Issuers[issuerid]; !ok {
		return errors.Errorf("Private key %d of issuer %s belongs to an unknown issuer", sk.Counter, issuerid.String())
	}
	pk, err := conf.PublicKey(issuerid, sk.Counter)
	if err != nil {
		return err
	}
	if pk == nil {
		return errors.Errorf("Private key %d of issuer %s has no corresponding public key", sk.Counter, issuerid.String())
	}
	if new(big.Int).Mul(sk.P, sk.Q).Cmp(pk.N) != 0 {
		return errors.Errorf("Private key %d of issuer %s does not belong to corresponding public key", sk.Counter, issuerid.String())
	}
	if sk.RevocationSupported() != pk.RevocationSupported() {
		msg := fmt.Sprintf("revocation support of private key %d of issuer %s is not consistent with corresponding public key", sk.Counter, issuerid.String())
		if conf.SchemeManagers[issuerid.SchemeManagerIdentifier()].Demo {
			Logger.Warn(msg)
		} else {
			return errors.Errorf(msg)
		}
	}
	return nil
}

func validatePrivateKeyRing(ring PrivateKeyRing, conf *Configuration) error {
	for issuerid := range conf.Issuers {
		err := ring.Iterate(issuerid, func(sk *gabikeys.PrivateKey) error {
			return validatePrivateKey(issuerid, sk, conf)
		})
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>refactor: when searching for a private key with a specific filename, don't use Glob<commit_after>package irma

import (
	goerrors "errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strconv"

	"github.com/go-errors/errors"
	"github.com/privacybydesign/gabi/big"
	"github.com/privacybydesign/gabi/gabikeys"
	"github.com/privacybydesign/irmago/internal/common"
)

type (
	// PrivateKeyRing provides access to a set of private keys.
	PrivateKeyRing interface {
		// Latest returns the private key with the highest counter for the specified issuer, if any,
		// or an error.
		Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error)

		// Get returns the specified private key, or an error.
		Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error)

		// Iterate executes the specified function on each private key of the specified issuer
		// present in the ring. The private keys are offered to the function in no particular order,
		// and the same key may be offered multiple times. Returns on the first error returned
		// by the function.
		Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error
	}

	// PrivateKeyRingFolder represents a folder on disk containing private keys with filenames
	// of the form scheme.issuer.xml and scheme.issuer.counter.xml.
	PrivateKeyRingFolder struct {
		path string
		conf *Configuration
	}

	// privateKeyRingScheme provides access to private keys present in a scheme.
	privateKeyRingScheme struct {
		conf *Configuration
	}

	// privateKeyRingMerge is a merge of multiple key rings into one, provides access to the
	// private keys of all of them.
	privateKeyRingMerge struct {
		rings []PrivateKeyRing
	}
)

var (
	ErrMissingPrivateKey = fmt.Errorf("issuer private key not found: %w", os.ErrNotExist)
)

func NewPrivateKeyRingFolder(path string, conf *Configuration) (*PrivateKeyRingFolder, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	ring := &PrivateKeyRingFolder{path, conf}
	for _, file := range files {
		filename := file.Name()
		issuerid, counter, err := ring.parseFilename(filename)
		if err != nil {
			return nil, err
		}
		if issuerid == nil {
			Logger.WithField("file", filename).Infof("Skipping non-private key file encountered in private keys path")
			continue
		}
		sk, err := ring.readFile(filename, *issuerid)
		if err != nil {
			return nil, err
		}
		if counter != nil && *counter != sk.Counter {
			return nil, errors.Errorf("private key %s has wrong counter %d in filename, should be %d", filename, counter, sk.Counter)
		}
	}
	return ring, nil
}

func (_ *PrivateKeyRingFolder) parseFilename(filename string) (*IssuerIdentifier, *uint, error) {
	// This regexp returns one of the following:
	// [ "foo.bar.xml", "foo.bar", "", "" ] in case of "foo.bar.xml"
	// [ "foo.bar.xml", "foo.bar", ".2", "2" ] in case of "foo.bar.2.xml"
	// nil in case of other files.
	matches := regexp.MustCompile(`^([^.]+\.[^.]+)(\.(\d+))?\.xml$`).FindStringSubmatch(filename)

	if len(matches) != 4 {
		return nil, nil, nil
	}
	issuerid := NewIssuerIdentifier(matches[1])
	if matches[3] == "" {
		return &issuerid, nil, nil
	}
	counter, err := strconv.ParseUint(matches[3], 10, 32)
	if err != nil {
		return nil, nil, err
	}
	c := uint(counter)
	return &issuerid, &c, nil
}

func (p *PrivateKeyRingFolder) readFile(filename string, id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	scheme := p.conf.SchemeManagers[id.SchemeManagerIdentifier()]
	if scheme == nil {
		return nil, errors.Errorf("Private key of issuer %s belongs to unknown scheme", id.String())
	}
	sk, err := gabikeys.NewPrivateKeyFromFile(filepath.Join(p.path, filename), scheme.Demo)
	if err != nil {
		return nil, err
	}
	if err = validatePrivateKey(id, sk, p.conf); err != nil {
		return nil, err
	}
	return sk, nil
}

func (p *PrivateKeyRingFolder) Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error) {
	sk, err := p.readFile(fmt.Sprintf("%s.%d.xml", id.String(), counter), id)
	if err != nil && !goerrors.Is(err, os.ErrNotExist) {
		return nil, err
	}
	if sk != nil {
		return sk, nil
	}
	sk, err = p.readFile(fmt.Sprintf("%s.xml", id.String()), id)
	if err != nil {
		return nil, err
	}
	if counter != sk.Counter {
		return nil, ErrMissingPrivateKey
	}
	return sk, nil
}

func (p *PrivateKeyRingFolder) Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	var sk *gabikeys.PrivateKey
	if err := p.Iterate(id, func(s *gabikeys.PrivateKey) error {
		if sk == nil || s.Counter > sk.Counter {
			sk = s
		}
		return nil
	}); err != nil {
		return nil, err
	}
	if sk == nil {
		return nil, ErrMissingPrivateKey
	}
	return sk, nil
}

func (p *PrivateKeyRingFolder) Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error {
	files, err := filepath.Glob(filepath.Join(p.path, fmt.Sprintf("%s.*.xml", id.String())))
	if err != nil {
		return err
	}
	fileWithoutCounter := filepath.Join(p.path, fmt.Sprintf("%s.xml", id.String()))
	exists, err := common.PathExists(fileWithoutCounter)
	if err != nil {
		return err
	}
	if exists {
		files = append(files, fileWithoutCounter)
	}
	for _, file := range files {
		sk, err := p.readFile(filepath.Base(file), id)
		if err != nil {
			return err
		}
		if err = f(sk); err != nil {
			return err
		}
	}
	return nil
}

func newPrivateKeyRingScheme(conf *Configuration) (*privateKeyRingScheme, error) {
	ring := &privateKeyRingScheme{conf}
	if err := validatePrivateKeyRing(ring, conf); err != nil {
		return nil, err
	}
	return ring, nil
}

func (p *privateKeyRingScheme) counters(issuerid IssuerIdentifier) (i []uint, err error) {
	scheme := p.conf.SchemeManagers[issuerid.SchemeManagerIdentifier()]
	return matchKeyPattern(filepath.Join(scheme.path(), issuerid.Name(), "PrivateKeys", "*"))
}

func (p *privateKeyRingScheme) Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error) {
	schemeID := id.SchemeManagerIdentifier()
	scheme := p.conf.SchemeManagers[schemeID]
	if scheme == nil {
		return nil, errors.Errorf("Private key of issuer %s belongs to unknown scheme", id.String())
	}
	file := filepath.Join(scheme.path(), id.Name(), "PrivateKeys", strconv.FormatUint(uint64(counter), 10)+".xml")
	sk, err := gabikeys.NewPrivateKeyFromFile(file, scheme.Demo)
	if err != nil {
		return nil, err
	}
	if sk.Counter != counter {
		return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String())
	}
	if err = validatePrivateKey(id, sk, p.conf); err != nil {
		return nil, err
	}
	return sk, nil
}

func (p *privateKeyRingScheme) Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	counters, err := p.counters(id)
	if err != nil {
		return nil, err
	}
	if len(counters) == 0 {
		return nil, ErrMissingPrivateKey
	}
	return p.Get(id, counters[len(counters)-1])
}

func (p *privateKeyRingScheme) Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error {
	indices, err := p.counters(id)
	if err != nil {
		return err
	}
	for _, counter := range indices {
		sk, err := p.Get(id, counter)
		if err != nil {
			return err
		}
		if err = f(sk); err != nil {
			return err
		}
	}
	return nil
}

func (p *privateKeyRingMerge) Add(ring PrivateKeyRing) {
	p.rings = append(p.rings, ring)
}

func (p *privateKeyRingMerge) Get(id IssuerIdentifier, counter uint) (*gabikeys.PrivateKey, error) {
	for _, ring := range p.rings {
		sk, err := ring.Get(id, counter)
		if err == nil {
			return sk, nil
		}
		if !goerrors.Is(err, os.ErrNotExist) {
			return nil, err
		}
	}
	return nil, ErrMissingPrivateKey
}

func (p *privateKeyRingMerge) Latest(id IssuerIdentifier) (*gabikeys.PrivateKey, error) {
	var sk *gabikeys.PrivateKey
	for _, ring := range p.rings {
		s, err := ring.Latest(id)
		if err != nil && !goerrors.Is(err, os.ErrNotExist) {
			return nil, err
		}
		if s != nil && (sk == nil || s.Counter > sk.Counter) {
			sk = s
		}
	}
	if sk == nil {
		return nil, ErrMissingPrivateKey
	}
	return sk, nil
}

func (p *privateKeyRingMerge) Iterate(id IssuerIdentifier, f func(sk *gabikeys.PrivateKey) error) error {
	for _, ring := range p.rings {
		if err := ring.Iterate(id, f); err != nil {
			return err
		}
	}
	return nil
}

func validatePrivateKey(issuerid IssuerIdentifier, sk *gabikeys.PrivateKey, conf *Configuration) error {
	if _, ok := conf.Issuers[issuerid]; !ok {
		return errors.Errorf("Private key %d of issuer %s belongs to an unknown issuer", sk.Counter, issuerid.String())
	}
	pk, err := conf.PublicKey(issuerid, sk.Counter)
	if err != nil {
		return err
	}
	if pk == nil {
		return errors.Errorf("Private key %d of issuer %s has no corresponding public key", sk.Counter, issuerid.String())
	}
	if new(big.Int).Mul(sk.P, sk.Q).Cmp(pk.N) != 0 {
		return errors.Errorf("Private key %d of issuer %s does not belong to corresponding public key", sk.Counter, issuerid.String())
	}
	if sk.RevocationSupported() != pk.RevocationSupported() {
		msg := fmt.Sprintf("revocation support of private key %d of issuer %s is not consistent with corresponding public key", sk.Counter, issuerid.String())
		if conf.SchemeManagers[issuerid.SchemeManagerIdentifier()].Demo {
			Logger.Warn(msg)
		} else {
			return errors.Errorf(msg)
		}
	}
	return nil
}

func validatePrivateKeyRing(ring PrivateKeyRing, conf *Configuration) error {
	for issuerid := range conf.Issuers {
		err := ring.Iterate(issuerid, func(sk *gabikeys.PrivateKey) error {
			return validatePrivateKey(issuerid, sk, conf)
		})
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>