<commit_before>package docker

import (
	"context"
	"fmt"
	"github.com/ViBiOh/docker-deploy/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/strslice"
	"gopkg.in/yaml.v2"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const minMemory = 16777216
const maxMemory = 805306368
const defaultTag = `:latest`
const deploySuffix = `_deploy`
const linkSeparator = `:`

var imageTag = regexp.MustCompile(`^\S*?:\S+$`)
var commandSplit = regexp.MustCompile(`["']([^"']+)["']|(\S+)`)

type dockerComposeService struct {
	Image       string
	Command     string
	Environment map[string]string
	Labels      map[string]string
	Links       []string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

type deployedService struct {
	ID   string
	Name string
}

func getConfig(service *dockerComposeService, loggedUser *user, appName string) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	if service.Labels == nil {
		service.Labels = make(map[string]string)
	}

	service.Labels[ownerLabel] = loggedUser.username
	service.Labels[appLabel] = appName

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if service.Command != `` {
		config.Cmd = strslice.StrSlice{}
		for _, args := range commandSplit.FindAllStringSubmatch(service.Command, -1) {
			config.Cmd = append(config.Cmd, args[1]+args[2])
		}
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    minMemory,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		if service.MemoryLimit <= maxMemory {
			hostConfig.Resources.Memory = service.MemoryLimit
		} else {
			hostConfig.Resources.Memory = maxMemory
		}
	}

	return &hostConfig
}

func getNetworkConfig(service *dockerComposeService, deployedServices *map[string]deployedService) *network.NetworkingConfig {
	traefikConfig := network.EndpointSettings{}

	for _, link := range service.Links {
		linkParts := strings.Split(link, linkSeparator)

		target := linkParts[0]
		if linkedService, ok := (&deployedServices)[target]; ok {
			target = linkedService.Name
		}

		alias := linkParts[0]
		if len(linkParts) > 1 {
			alias = linkParts[1]
		}

		traefikConfig.Links = append(traefikConfig.Links, target+linkSeparator+alias)
	}

	return &network.NetworkingConfig{
		EndpointsConfig: map[string]*network.EndpointSettings{
			`traefik`: &traefikConfig,
		},
	}
}

func pullImage(image string, loggedUser *user) error {
	if !imageTag.MatchString(image) {
		image = image + defaultTag
	}

	log.Print(loggedUser.username + ` starts pulling for ` + image)
	pull, err := docker.ImagePull(context.Background(), image, types.ImagePullOptions{})
	if err != nil {
		return fmt.Errorf(`Error while pulling image: %v`, err)
	}

	readBody(pull)
	log.Print(loggedUser.username + ` ends pulling for ` + image)
	return nil
}

func cleanContainers(containers *[]types.Container, loggedUser *user) {
	for _, container := range *containers {
		log.Print(loggedUser.username + ` stops ` + strings.Join(container.Names, `, `))
		stopContainer(container.ID)
		log.Print(loggedUser.username + ` rm ` + strings.Join(container.Names, `, `))
		rmContainer(container.ID)
	}
}

func renameDeployedContainers(containers *map[string]deployedService) error {
	for _, service := range *containers {
		if err := docker.ContainerRename(context.Background(), service.ID, strings.TrimSuffix(service.Name, deploySuffix)); err != nil {
			return fmt.Errorf(`Error while renaming container %s: %v`, service.Name, err)
		}
	}

	return nil
}

func getServiceFullName(appName string, serviceName string) string {
	return appName + `_` + serviceName + deploySuffix
}

func createAppHandler(w http.ResponseWriter, loggedUser *user, appName []byte, composeFile []byte) {
	if len(appName) == 0 || len(composeFile) == 0 {
		http.Error(w, `An application name and a compose file are required`, http.StatusBadRequest)
		return
	}

	compose := dockerCompose{}
	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, fmt.Errorf(`Error while unmarshalling compose file: %v`, err))
		return
	}

	appNameStr := string(appName)
	log.Print(loggedUser.username + ` deploys ` + appNameStr)

	ownerContainers, err := listContainers(loggedUser, &appNameStr)
	if err != nil {
		errorHandler(w, err)
		return
	}

	deployedServices := make(map[string]deployedService)
	for serviceName, service := range compose.Services {
		if err := pullImage(service.Image, loggedUser); err != nil {
			errorHandler(w, err)
			return
		}

		serviceFullName := getServiceFullName(appNameStr, serviceName)
		log.Print(loggedUser.username + ` starts ` + serviceFullName)

		id, err := docker.ContainerCreate(context.Background(), getConfig(&service, loggedUser, appNameStr), getHostConfig(&service), getNetworkConfig(&service, &deployedServices), serviceFullName)
		if err != nil {
			errorHandler(w, fmt.Errorf(`Error while creating container: %v`, err))
			return
		}

		startContainer(id.ID)
		deployedServices[serviceName] = deployedService{ID: id.ID, Name: serviceFullName}
	}

	log.Print(`Waiting 5 seconds for containers to start...`)
	time.Sleep(5 * time.Second)

	cleanContainers(&ownerContainers, loggedUser)
	if err := renameDeployedContainers(&deployedServices); err != nil {
		errorHandler(w, err)
		return
	}

	jsonHttp.ResponseJSON(w, results{deployedServices})
}
<commit_msg>Update compose.go<commit_after>package docker

import (
	"context"
	"fmt"
	"github.com/ViBiOh/docker-deploy/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/strslice"
	"gopkg.in/yaml.v2"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const minMemory = 16777216
const maxMemory = 805306368
const defaultTag = `:latest`
const deploySuffix = `_deploy`
const linkSeparator = `:`

var imageTag = regexp.MustCompile(`^\S*?:\S+$`)
var commandSplit = regexp.MustCompile(`["']([^"']+)["']|(\S+)`)

type dockerComposeService struct {
	Image       string
	Command     string
	Environment map[string]string
	Labels      map[string]string
	Links       []string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

type deployedService struct {
	ID   string
	Name string
}

func getConfig(service *dockerComposeService, loggedUser *user, appName string) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	if service.Labels == nil {
		service.Labels = make(map[string]string)
	}

	service.Labels[ownerLabel] = loggedUser.username
	service.Labels[appLabel] = appName

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if service.Command != `` {
		config.Cmd = strslice.StrSlice{}
		for _, args := range commandSplit.FindAllStringSubmatch(service.Command, -1) {
			config.Cmd = append(config.Cmd, args[1]+args[2])
		}
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    minMemory,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		if service.MemoryLimit <= maxMemory {
			hostConfig.Resources.Memory = service.MemoryLimit
		} else {
			hostConfig.Resources.Memory = maxMemory
		}
	}

	return &hostConfig
}

func getNetworkConfig(service *dockerComposeService, deployedServices *map[string]deployedService) *network.NetworkingConfig {
	traefikConfig := network.EndpointSettings{}

	for _, link := range service.Links {
		linkParts := strings.Split(link, linkSeparator)

		target := linkParts[0]
		if linkedService, ok := (*deployedServices)[target]; ok {
			target = linkedService.Name
		}

		alias := linkParts[0]
		if len(linkParts) > 1 {
			alias = linkParts[1]
		}

		traefikConfig.Links = append(traefikConfig.Links, target+linkSeparator+alias)
	}

	return &network.NetworkingConfig{
		EndpointsConfig: map[string]*network.EndpointSettings{
			`traefik`: &traefikConfig,
		},
	}
}

func pullImage(image string, loggedUser *user) error {
	if !imageTag.MatchString(image) {
		image = image + defaultTag
	}

	log.Print(loggedUser.username + ` starts pulling for ` + image)
	pull, err := docker.ImagePull(context.Background(), image, types.ImagePullOptions{})
	if err != nil {
		return fmt.Errorf(`Error while pulling image: %v`, err)
	}

	readBody(pull)
	log.Print(loggedUser.username + ` ends pulling for ` + image)
	return nil
}

func cleanContainers(containers *[]types.Container, loggedUser *user) {
	for _, container := range *containers {
		log.Print(loggedUser.username + ` stops ` + strings.Join(container.Names, `, `))
		stopContainer(container.ID)
		log.Print(loggedUser.username + ` rm ` + strings.Join(container.Names, `, `))
		rmContainer(container.ID)
	}
}

func renameDeployedContainers(containers *map[string]deployedService) error {
	for _, service := range *containers {
		if err := docker.ContainerRename(context.Background(), service.ID, strings.TrimSuffix(service.Name, deploySuffix)); err != nil {
			return fmt.Errorf(`Error while renaming container %s: %v`, service.Name, err)
		}
	}

	return nil
}

func getServiceFullName(appName string, serviceName string) string {
	return appName + `_` + serviceName + deploySuffix
}

func createAppHandler(w http.ResponseWriter, loggedUser *user, appName []byte, composeFile []byte) {
	if len(appName) == 0 || len(composeFile) == 0 {
		http.Error(w, `An application name and a compose file are required`, http.StatusBadRequest)
		return
	}

	compose := dockerCompose{}
	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, fmt.Errorf(`Error while unmarshalling compose file: %v`, err))
		return
	}

	appNameStr := string(appName)
	log.Print(loggedUser.username + ` deploys ` + appNameStr)

	ownerContainers, err := listContainers(loggedUser, &appNameStr)
	if err != nil {
		errorHandler(w, err)
		return
	}

	deployedServices := make(map[string]deployedService)
	for serviceName, service := range compose.Services {
		if err := pullImage(service.Image, loggedUser); err != nil {
			errorHandler(w, err)
			return
		}

		serviceFullName := getServiceFullName(appNameStr, serviceName)
		log.Print(loggedUser.username + ` starts ` + serviceFullName)

		id, err := docker.ContainerCreate(context.Background(), getConfig(&service, loggedUser, appNameStr), getHostConfig(&service), getNetworkConfig(&service, &deployedServices), serviceFullName)
		if err != nil {
			errorHandler(w, fmt.Errorf(`Error while creating container: %v`, err))
			return
		}

		startContainer(id.ID)
		deployedServices[serviceName] = deployedService{ID: id.ID, Name: serviceFullName}
	}

	log.Print(`Waiting 5 seconds for containers to start...`)
	time.Sleep(5 * time.Second)

	cleanContainers(&ownerContainers, loggedUser)
	if err := renameDeployedContainers(&deployedServices); err != nil {
		errorHandler(w, err)
		return
	}

	jsonHttp.ResponseJSON(w, results{deployedServices})
}
<|endoftext|>


<commit_before>// Copyright ©2016 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
 * Cephes Math Library Release 2.4:  March,1996
 * Copyright 1984, 1996 by Stephen L. Moshier
 */

/*
 * Adapted from scipy's cephes unity.c
 */

package cephes

import "math"

const (
	invSqrt2 float64 = 1 / math.Sqrt2
	pi4      float64 = math.Pi / 4
)

/* Coefficients for log(1+x) = x - x**2/2 + x**3 P(x)/Q(x)
 * 1/sqrt(2) <= x < sqrt(2)
 * Theoretical peak relative error = 2.32e-20
 */
var lP = []float64{
	4.5270000862445199635215E-5,
	4.9854102823193375972212E-1,
	6.5787325942061044846969E0,
	2.9911919328553073277375E1,
	6.0949667980987787057556E1,
	5.7112963590585538103336E1,
	2.0039553499201281259648E1,
}

var lQ = []float64{
	1.5062909083469192043167E1,
	8.3047565967967209469434E1,
	2.2176239823732856465394E2,
	3.0909872225312059774938E2,
	2.1642788614495947685003E2,
	6.0118660497603843919306E1,
}

// log1p computes log(1 + x)
func log1p(x float64) float64 {
	z := 1 + x
	if z < invSqrt2 || z > math.Sqrt2 {
		return math.Log(z)
	}
	z = x * x
	z = -0.5*z + x*(z*polevl(x, lP, 6)/p1evl(x, lQ, 6))
	return x + z
}

// log1pmx computes log(1 + x) - x
func log1pmx(x float64) float64 {
	if math.Abs(x) < 0.5 {
		xfac := x
		res := 0.0

		var term float64
		for n := 2; n < maxIter; n++ {
			xfac *= -x
			term = xfac / float64(n)
			res += term
			if math.Abs(term) < machEp*math.Abs(res) {
				break
			}
		}
		return res
	}
	return log1p(x) - x
}

/*  e^x =  1 + 2x P(x^2)/( Q(x^2) - P(x^2) )
 * -0.5 <= x <= 0.5
 */

var eP = []float64{
	1.2617719307481059087798E-4,
	3.0299440770744196129956E-2,
	9.9999999999999999991025E-1,
}

var eQ = []float64{
	3.0019850513866445504159E-6,
	2.5244834034968410419224E-3,
	2.2726554820815502876593E-1,
	2.0000000000000000000897E0,
}

// expm1 computes expm1(x) = exp(x) - 1
func expm1(x float64) float64 {
	if math.IsInf(x, 0) {
		if math.IsNaN(x) || x > 0 {
			return x
		}
		return -1
	}
	if x < -0.5 || x > 0.5 {
		return math.Exp(x) - 1
	}
	xx := x * x
	r := x * polevl(xx, eP, 2)
	r = r / (polevl(xx, eQ, 3) - r)
	return r + r
}

var coscof = []float64{
	4.7377507964246204691685E-14,
	-1.1470284843425359765671E-11,
	2.0876754287081521758361E-9,
	-2.7557319214999787979814E-7,
	2.4801587301570552304991E-5,
	-1.3888888888888872993737E-3,
	4.1666666666666666609054E-2,
}

// cosm1 computes cosm1(x) = cos(x) - 1
func cosm1(x float64) float64 {
	if x < -pi4 || x > pi4 {
		return math.Cos(x) - 1
	}
	xx := x * x
	xx = -0.5*xx + xx*xx*polevl(xx, coscof, 6)
	return xx
}

// lgam1pTayler computes lgam(x + 1) around x = 0 using its Taylor series.
func lgam1pTaylor(x float64) float64 {
	if x == 0 {
		return 0
	}
	res := -math.E * x
	xfac := -x
	for n := 2; n < 42; n++ {
		nf := float64(n)
		xfac *= -x
		coeff := Zeta(nf, 1) * xfac / nf
		res += coeff
		if math.Abs(coeff) < machEp*math.Abs(res) {
			break
		}
	}

	return res
}

// lgam1p computes lgam(x + 1).
func lgam1p(x float64) float64 {
	if math.Abs(x) <= 0.5 {
		return lgam1pTaylor(x)
	} else if math.Abs(x-1) < 0.5 {
		return math.Log(x) + lgam1pTaylor(x-1)
	}
	return lgam(x + 1)
}
<commit_msg>Reverting Euler's Constant<commit_after>// Copyright ©2016 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
 * Cephes Math Library Release 2.4:  March,1996
 * Copyright 1984, 1996 by Stephen L. Moshier
 */

/*
 * Adapted from scipy's cephes unity.c
 */

package cephes

import "math"

const (
	invSqrt2 float64 = 1 / math.Sqrt2
	pi4      float64 = math.Pi / 4
	euler    float64 = 0.577215664901532860606512090082402431 // Euler constant
)

/* Coefficients for log(1+x) = x - x**2/2 + x**3 P(x)/Q(x)
 * 1/sqrt(2) <= x < sqrt(2)
 * Theoretical peak relative error = 2.32e-20
 */
var lP = []float64{
	4.5270000862445199635215E-5,
	4.9854102823193375972212E-1,
	6.5787325942061044846969E0,
	2.9911919328553073277375E1,
	6.0949667980987787057556E1,
	5.7112963590585538103336E1,
	2.0039553499201281259648E1,
}

var lQ = []float64{
	1.5062909083469192043167E1,
	8.3047565967967209469434E1,
	2.2176239823732856465394E2,
	3.0909872225312059774938E2,
	2.1642788614495947685003E2,
	6.0118660497603843919306E1,
}

// log1p computes log(1 + x)
func log1p(x float64) float64 {
	z := 1 + x
	if z < invSqrt2 || z > math.Sqrt2 {
		return math.Log(z)
	}
	z = x * x
	z = -0.5*z + x*(z*polevl(x, lP, 6)/p1evl(x, lQ, 6))
	return x + z
}

// log1pmx computes log(1 + x) - x
func log1pmx(x float64) float64 {
	if math.Abs(x) < 0.5 {
		xfac := x
		res := 0.0

		var term float64
		for n := 2; n < maxIter; n++ {
			xfac *= -x
			term = xfac / float64(n)
			res += term
			if math.Abs(term) < machEp*math.Abs(res) {
				break
			}
		}
		return res
	}
	return log1p(x) - x
}

/*  e^x =  1 + 2x P(x^2)/( Q(x^2) - P(x^2) )
 * -0.5 <= x <= 0.5
 */

var eP = []float64{
	1.2617719307481059087798E-4,
	3.0299440770744196129956E-2,
	9.9999999999999999991025E-1,
}

var eQ = []float64{
	3.0019850513866445504159E-6,
	2.5244834034968410419224E-3,
	2.2726554820815502876593E-1,
	2.0000000000000000000897E0,
}

// expm1 computes expm1(x) = exp(x) - 1
func expm1(x float64) float64 {
	if math.IsInf(x, 0) {
		if math.IsNaN(x) || x > 0 {
			return x
		}
		return -1
	}
	if x < -0.5 || x > 0.5 {
		return math.Exp(x) - 1
	}
	xx := x * x
	r := x * polevl(xx, eP, 2)
	r = r / (polevl(xx, eQ, 3) - r)
	return r + r
}

var coscof = []float64{
	4.7377507964246204691685E-14,
	-1.1470284843425359765671E-11,
	2.0876754287081521758361E-9,
	-2.7557319214999787979814E-7,
	2.4801587301570552304991E-5,
	-1.3888888888888872993737E-3,
	4.1666666666666666609054E-2,
}

// cosm1 computes cosm1(x) = cos(x) - 1
func cosm1(x float64) float64 {
	if x < -pi4 || x > pi4 {
		return math.Cos(x) - 1
	}
	xx := x * x
	xx = -0.5*xx + xx*xx*polevl(xx, coscof, 6)
	return xx
}

// lgam1pTayler computes lgam(x + 1) around x = 0 using its Taylor series.
func lgam1pTaylor(x float64) float64 {
	if x == 0 {
		return 0
	}
	res := -euler * x
	xfac := -x
	for n := 2; n < 42; n++ {
		nf := float64(n)
		xfac *= -x
		coeff := Zeta(nf, 1) * xfac / nf
		res += coeff
		if math.Abs(coeff) < machEp*math.Abs(res) {
			break
		}
	}

	return res
}

// lgam1p computes lgam(x + 1).
func lgam1p(x float64) float64 {
	if math.Abs(x) <= 0.5 {
		return lgam1pTaylor(x)
	} else if math.Abs(x-1) < 0.5 {
		return math.Log(x) + lgam1pTaylor(x-1)
	}
	return lgam(x + 1)
}
<|endoftext|>


<commit_before>package ndpcmd

import (
	"fmt"
	"io"
	"log"
	"net"
	"strings"

	"github.com/mdlayher/ndp"
)

func printMessage(ll *log.Logger, m ndp.Message, from net.IP) {
	switch m := m.(type) {
	case *ndp.NeighborAdvertisement:
		printNA(ll, m, from)
	case *ndp.NeighborSolicitation:
		printNS(ll, m, from)
	case *ndp.RouterAdvertisement:
		printRA(ll, m, from)
	case *ndp.RouterSolicitation:
		printRS(ll, m, from)
	default:
		ll.Printf("%s %#v", from, m)
	}
}

func printRA(ll *log.Logger, ra *ndp.RouterAdvertisement, from net.IP) {
	var flags []string
	if ra.ManagedConfiguration {
		flags = append(flags, "managed")
	}
	if ra.OtherConfiguration {
		flags = append(flags, "other")
	}
	if ra.MobileIPv6HomeAgent {
		flags = append(flags, "mobile")
	}
	if ra.NeighborDiscoveryProxy {
		flags = append(flags, "proxy")
	}

	var s strings.Builder
	writef(&s, "router advertisement from: %s:\n", from)

	if ra.CurrentHopLimit > 0 {
		writef(&s, "  - hop limit:        %d\n", ra.CurrentHopLimit)
	}
	if len(flags) > 0 {
		writef(&s, "  - flags:            [%s]\n", strings.Join(flags, ", "))
	}

	writef(&s, "  - preference:       %s\n", ra.RouterSelectionPreference)

	if ra.RouterLifetime > 0 {
		writef(&s, "  - router lifetime:  %s\n", ra.RouterLifetime)
	}
	if ra.ReachableTime != 0 {
		writef(&s, "  - reachable time:   %s\n", ra.ReachableTime)
	}
	if ra.RetransmitTimer != 0 {
		writef(&s, "  - retransmit timer: %s\n", ra.RetransmitTimer)
	}

	_, _ = s.WriteString(optionsString(ra.Options))

	ll.Print(s.String())
}

func printRS(ll *log.Logger, rs *ndp.RouterSolicitation, from net.IP) {
	s := fmt.Sprintf(
		rsFormat,
		from.String(),
	)

	ll.Print(s + optionsString(rs.Options))
}

const rsFormat = "router solicitation from %s:\n"

func printNA(ll *log.Logger, na *ndp.NeighborAdvertisement, from net.IP) {
	s := fmt.Sprintf(
		naFormat,
		from.String(),
		na.Router,
		na.Solicited,
		na.Override,
		na.TargetAddress.String(),
	)

	ll.Print(s + optionsString(na.Options))
}

const naFormat = `neighbor advertisement from %s:
  - router:         %t
  - solicited:      %t
  - override:       %t
  - target address: %s
`

func printNS(ll *log.Logger, ns *ndp.NeighborSolicitation, from net.IP) {
	s := fmt.Sprintf(
		nsFormat,
		from.String(),
		ns.TargetAddress.String(),
	)

	ll.Print(s + optionsString(ns.Options))
}

const nsFormat = `neighbor solicitation from %s:
  - target address: %s
`

func optionsString(options []ndp.Option) string {
	if len(options) == 0 {
		return ""
	}

	var s strings.Builder
	s.WriteString("  - options:\n")

	for _, o := range options {
		writef(&s, "    - %s\n", optStr(o))
	}

	return s.String()
}

func optStr(o ndp.Option) string {
	switch o := o.(type) {
	case *ndp.LinkLayerAddress:
		dir := "source"
		if o.Direction == ndp.Target {
			dir = "target"
		}

		return fmt.Sprintf("%s link-layer address: %s", dir, o.Addr.String())
	case *ndp.MTU:
		return fmt.Sprintf("MTU: %d", *o)
	case *ndp.PrefixInformation:
		var flags []string
		if o.OnLink {
			flags = append(flags, "on-link")
		}
		if o.AutonomousAddressConfiguration {
			flags = append(flags, "autonomous")
		}

		return fmt.Sprintf("prefix information: %s/%d, flags: [%s], valid: %s, preferred: %s",
			o.Prefix.String(),
			o.PrefixLength,
			strings.Join(flags, ", "),
			o.ValidLifetime,
			o.PreferredLifetime,
		)
	case *ndp.RawOption:
		return fmt.Sprintf("type: %03d, value: %v", o.Type, o.Value)
	case *ndp.RouteInformation:
		return fmt.Sprintf("route information: %s/%d, preference: %s, lifetime: %s",
			o.Prefix.String(),
			o.PrefixLength,
			o.Preference.String(),
			o.RouteLifetime,
		)
	case *ndp.RecursiveDNSServer:
		var ss []string
		for _, s := range o.Servers {
			ss = append(ss, s.String())
		}
		servers := strings.Join(ss, ", ")

		return fmt.Sprintf("recursive DNS servers: lifetime: %s, servers: %s", o.Lifetime, servers)
	case *ndp.DNSSearchList:
		return fmt.Sprintf("DNS search list: lifetime: %s, domain names: %s", o.Lifetime, strings.Join(o.DomainNames, ", "))
	default:
		panic(fmt.Sprintf("unrecognized option: %v", o))
	}
}

func writef(sw io.StringWriter, format string, a ...interface{}) {
	_, _ = sw.WriteString(fmt.Sprintf(format, a...))
}
<commit_msg>internal/ndpcmd: print ndp.CaptivePortal options<commit_after>package ndpcmd

import (
	"fmt"
	"io"
	"log"
	"net"
	"strings"

	"github.com/mdlayher/ndp"
)

func printMessage(ll *log.Logger, m ndp.Message, from net.IP) {
	switch m := m.(type) {
	case *ndp.NeighborAdvertisement:
		printNA(ll, m, from)
	case *ndp.NeighborSolicitation:
		printNS(ll, m, from)
	case *ndp.RouterAdvertisement:
		printRA(ll, m, from)
	case *ndp.RouterSolicitation:
		printRS(ll, m, from)
	default:
		ll.Printf("%s %#v", from, m)
	}
}

func printRA(ll *log.Logger, ra *ndp.RouterAdvertisement, from net.IP) {
	var flags []string
	if ra.ManagedConfiguration {
		flags = append(flags, "managed")
	}
	if ra.OtherConfiguration {
		flags = append(flags, "other")
	}
	if ra.MobileIPv6HomeAgent {
		flags = append(flags, "mobile")
	}
	if ra.NeighborDiscoveryProxy {
		flags = append(flags, "proxy")
	}

	var s strings.Builder
	writef(&s, "router advertisement from: %s:\n", from)

	if ra.CurrentHopLimit > 0 {
		writef(&s, "  - hop limit:        %d\n", ra.CurrentHopLimit)
	}
	if len(flags) > 0 {
		writef(&s, "  - flags:            [%s]\n", strings.Join(flags, ", "))
	}

	writef(&s, "  - preference:       %s\n", ra.RouterSelectionPreference)

	if ra.RouterLifetime > 0 {
		writef(&s, "  - router lifetime:  %s\n", ra.RouterLifetime)
	}
	if ra.ReachableTime != 0 {
		writef(&s, "  - reachable time:   %s\n", ra.ReachableTime)
	}
	if ra.RetransmitTimer != 0 {
		writef(&s, "  - retransmit timer: %s\n", ra.RetransmitTimer)
	}

	_, _ = s.WriteString(optionsString(ra.Options))

	ll.Print(s.String())
}

func printRS(ll *log.Logger, rs *ndp.RouterSolicitation, from net.IP) {
	s := fmt.Sprintf(
		rsFormat,
		from.String(),
	)

	ll.Print(s + optionsString(rs.Options))
}

const rsFormat = "router solicitation from %s:\n"

func printNA(ll *log.Logger, na *ndp.NeighborAdvertisement, from net.IP) {
	s := fmt.Sprintf(
		naFormat,
		from.String(),
		na.Router,
		na.Solicited,
		na.Override,
		na.TargetAddress.String(),
	)

	ll.Print(s + optionsString(na.Options))
}

const naFormat = `neighbor advertisement from %s:
  - router:         %t
  - solicited:      %t
  - override:       %t
  - target address: %s
`

func printNS(ll *log.Logger, ns *ndp.NeighborSolicitation, from net.IP) {
	s := fmt.Sprintf(
		nsFormat,
		from.String(),
		ns.TargetAddress.String(),
	)

	ll.Print(s + optionsString(ns.Options))
}

const nsFormat = `neighbor solicitation from %s:
  - target address: %s
`

func optionsString(options []ndp.Option) string {
	if len(options) == 0 {
		return ""
	}

	var s strings.Builder
	s.WriteString("  - options:\n")

	for _, o := range options {
		writef(&s, "    - %s\n", optStr(o))
	}

	return s.String()
}

func optStr(o ndp.Option) string {
	switch o := o.(type) {
	case *ndp.LinkLayerAddress:
		dir := "source"
		if o.Direction == ndp.Target {
			dir = "target"
		}

		return fmt.Sprintf("%s link-layer address: %s", dir, o.Addr.String())
	case *ndp.MTU:
		return fmt.Sprintf("MTU: %d", *o)
	case *ndp.PrefixInformation:
		var flags []string
		if o.OnLink {
			flags = append(flags, "on-link")
		}
		if o.AutonomousAddressConfiguration {
			flags = append(flags, "autonomous")
		}

		return fmt.Sprintf("prefix information: %s/%d, flags: [%s], valid: %s, preferred: %s",
			o.Prefix.String(),
			o.PrefixLength,
			strings.Join(flags, ", "),
			o.ValidLifetime,
			o.PreferredLifetime,
		)
	case *ndp.RawOption:
		return fmt.Sprintf("type: %03d, value: %v", o.Type, o.Value)
	case *ndp.RouteInformation:
		return fmt.Sprintf("route information: %s/%d, preference: %s, lifetime: %s",
			o.Prefix.String(),
			o.PrefixLength,
			o.Preference.String(),
			o.RouteLifetime,
		)
	case *ndp.RecursiveDNSServer:
		var ss []string
		for _, s := range o.Servers {
			ss = append(ss, s.String())
		}
		servers := strings.Join(ss, ", ")

		return fmt.Sprintf("recursive DNS servers: lifetime: %s, servers: %s", o.Lifetime, servers)
	case *ndp.DNSSearchList:
		return fmt.Sprintf("DNS search list: lifetime: %s, domain names: %s", o.Lifetime, strings.Join(o.DomainNames, ", "))
	case *ndp.CaptivePortal:
		return fmt.Sprintf("captive portal: %s", *o)
	default:
		panic(fmt.Sprintf("unrecognized option: %v", o))
	}
}

func writef(sw io.StringWriter, format string, a ...interface{}) {
	_, _ = sw.WriteString(fmt.Sprintf(format, a...))
}
<|endoftext|>


<commit_before>// Test suite builders for each of the variants.
package runner

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/cucumber/godog"
	pb "github.com/ii/xds-test-harness/api/adapter"
	"github.com/ii/xds-test-harness/internal/types"
	"github.com/rs/zerolog/log"
)

type Suite struct {
	Variant     types.Variant
	Runner      *Runner
	Aggregated  bool
	Incremental bool
	TestWriting bool
	Buffer      bytes.Buffer
	Tags        string
	TestSuite   godog.TestSuite
}

func (s *Suite) StartRunner(node, adapter, target string) error {
	s.Runner = FreshRunner()
	s.Runner.NodeID = node
	s.Runner.Aggregated = s.Aggregated

	if err := s.Runner.ConnectClient("target", target); err != nil {
		log.Fatal().
			Msgf("error connecting to target: %v", err)
	}
	if err := s.Runner.ConnectClient("adapter", adapter); err != nil {
		log.Fatal().
			Msgf("error connecting to adapter: %v", err)
	}

	log.Info().
		Msgf("Connected to target at %s and adapter at %s\n", target, adapter)

	if s.Runner.Aggregated {
		log.Info().
			Msgf("Tests will be run via a single aggregated streams")
	} else {
		log.Info().
			Msgf("Tests will be run via separate, non-aggregated streams")
	}
	return nil
}

func (s *Suite) SetTags(base string) error {
	tagList := []string{}
	variants := strings.Split(string(s.Variant), " ")

	if len(variants) < 1 {
		err := fmt.Errorf("No variant type found to create tags from. This means the suite was not initialized properly.")
		return err
	}
	for _, tag := range variants {
		tag = "@" + tag
		tagList = append(tagList, tag)
	}
	if base != "" {
		tagList = append(tagList, base)
	}
	s.Tags = strings.Join(tagList, " && ")
	return nil
}

func (s *Suite) ConfigureSuite() error {
	initScenario := func(ctx *godog.ScenarioContext) {
		ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
			log.Debug().Msg("Creating Fresh Runner!")
			s.Runner = FreshRunner(s.Runner)
			return ctx, nil
		})
		ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
			c := pb.NewAdapterClient(s.Runner.Adapter.Conn)
			clearRequest := &pb.ClearRequest{Node: s.Runner.NodeID}
			clear, err := c.ClearState(context.Background(), clearRequest)
			if err != nil {
				log.Err(err).
					Msg("Couldn't clear state")
			}
			log.Debug().
				Msgf("Clearing State: %v\n", clear.Response)
			return ctx, nil
		})
		s.Runner.LoadSteps(ctx)
	}

	godogOpts := godog.Options{
		ShowStepDefinitions: false,
		Randomize:           0,
		StopOnFailure:       false,
		Strict:              false,
		NoColors:            false,
		Tags:                s.Tags,
		Format:              "pretty",
		Concurrency:         0,
	}
	if !s.TestWriting { // default is ppretty output to stdout. Only use default when writing tests, otherwise print to our special buffer.
		outputFile := variantToOutputFile(s.Variant)
		godogOpts.Format = "xds,cucumber:" + outputFile
		godogOpts.Output = &s.Buffer
	}

	suite := godog.TestSuite{
		Name:                fmt.Sprintf("xds Test Suite [%v]", s.Variant),
		ScenarioInitializer: initScenario,
		Options:             &godogOpts,
	}

	s.TestSuite = suite
	return nil
}

func (s *Suite) Run(adapter, target, nodeId, tags string) (err error, results types.VariantResults) {
	if err = s.StartRunner(nodeId, adapter, target); err != nil {
		return
	}
	if err = s.SetTags(tags); err != nil {
		return
	}
	if err = s.ConfigureSuite(); err != nil {
		return
	}

	s.TestSuite.Run()
	if s.TestWriting {
		return err, types.VariantResults{}
	}
	vr := types.VariantResults{}
	if err = json.Unmarshal([]byte(s.Buffer.String()), &vr); err != nil {
		err = fmt.Errorf("Error unmarshalling test results: %v\n", err)
		return err, results
	}
	results = vr
	results.Name = string(s.Variant)
	return err, results
}

func NewSotwNonAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.SotwNonAggregated,
		Aggregated:  false,
		Incremental: false,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}
}

func NewSotwAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.SotwAggregated,
		Aggregated:  true,
		Incremental: false,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}

}

func NewIncrementalNonAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.IncrementalNonAggregated,
		Aggregated:  false,
		Incremental: true,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}

}

func NewIncrementalAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.IncrementalAggregated,
		Aggregated:  true,
		Incremental: true,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}
}

func NewSuite(variant types.Variant, testWriting bool) *Suite {
	switch variant {
	case types.SotwNonAggregated:
		return NewSotwNonAggregatedSuite(testWriting)
	case types.SotwAggregated:
		return NewSotwAggregatedSuite(testWriting)
	case types.IncrementalNonAggregated:
		return NewIncrementalNonAggregatedSuite(testWriting)
	case types.IncrementalAggregated:
		return NewIncrementalAggregatedSuite(testWriting)
	default:
		return nil
	}
}

func UpdateResults(current types.Results, variantResults types.VariantResults) types.Results {
	return types.Results{
		Total:            current.Total + int64(variantResults.Total),
		Passed:           current.Passed + int64(variantResults.Passed),
		Failed:           current.Failed + int64(variantResults.Failed),
		Skipped:          current.Skipped + int64(variantResults.Skipped),
		Undefined:        current.Undefined + int64(variantResults.Undefined),
		Pending:          current.Pending + int64(variantResults.Pending),
		Variants:         append(current.Variants, variantResults.Name),
		ResultsByVariant: append(current.ResultsByVariant, variantResults),
	}
}

func variantToOutputFile(v types.Variant) string {
	parts := strings.Split(string(v), " ")
	fileName := strings.Join(parts, "-")
	return fileName + ".json"
}

type SuiteConfig struct {
	adapter string
	target  string
	nodeId  string
	tags    string
}
<commit_msg>return err instead of logging, to centralize logs<commit_after>// Test suite builders for each of the variants.
package runner

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/cucumber/godog"
	pb "github.com/ii/xds-test-harness/api/adapter"
	"github.com/ii/xds-test-harness/internal/types"
	"github.com/rs/zerolog/log"
)

type Suite struct {
	Variant     types.Variant
	Runner      *Runner
	Aggregated  bool
	Incremental bool
	TestWriting bool
	Buffer      bytes.Buffer
	Tags        string
	TestSuite   godog.TestSuite
}

func (s *Suite) StartRunner(node, adapter, target string) error {
	s.Runner = FreshRunner()
	s.Runner.NodeID = node
	s.Runner.Aggregated = s.Aggregated

	if err := s.Runner.ConnectClient("target", target); err != nil {
		return fmt.Errorf("Cannot connect to target: %v", err)
	}
	if err := s.Runner.ConnectClient("adapter", adapter); err != nil {
		return fmt.Errorf("Cannot connect to adapter: %v", err)
	}
	log.Info().
		Msgf("Connected to target at %s and adapter at %s\n", target, adapter)

	if s.Runner.Aggregated {
		log.Info().
			Msgf("Tests will be run via an aggregated streams")
	} else {
		log.Info().
			Msgf("Tests will be run via separate streams")
	}
	return nil
}

func (s *Suite) SetTags(base string) error {
	tagList := []string{}
	variants := strings.Split(string(s.Variant), " ")

	if len(variants) < 1 {
		err := fmt.Errorf("No variant type found to create tags from. This means the suite was not initialized properly.")
		return err
	}
	for _, tag := range variants {
		tag = "@" + tag
		tagList = append(tagList, tag)
	}
	if base != "" {
		tagList = append(tagList, base)
	}
	s.Tags = strings.Join(tagList, " && ")
	return nil
}

func (s *Suite) ConfigureSuite() {
	initScenario := func(ctx *godog.ScenarioContext) {
		ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
			log.Debug().
				Msg("Creating Fresh Runner!")
			s.Runner = FreshRunner(s.Runner)
			return ctx, nil
		})
		ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
			c := pb.NewAdapterClient(s.Runner.Adapter.Conn)
			clearRequest := &pb.ClearRequest{Node: s.Runner.NodeID}
			clear, err := c.ClearState(context.Background(), clearRequest)
			if err != nil {
				log.Err(err).
					Msg("Couldn't clear state")
			}
			log.Debug().
				Msgf("Clearing State: %v\n", clear.Response)
			return ctx, nil
		})
		s.Runner.LoadSteps(ctx)
	}

	godogOpts := godog.Options{
		ShowStepDefinitions: false,
		Randomize:           0,
		StopOnFailure:       false,
		Strict:              false,
		NoColors:            false,
		Tags:                s.Tags,
		Format:              "pretty",
		Concurrency:         0,
	}
	if !s.TestWriting { // default is pretty output to stdout.
		// Only use default when writing tests, otherwise print to our special buffer.
		outputFile := variantToOutputFile(s.Variant)
		godogOpts.Format = "xds,cucumber:" + outputFile
		godogOpts.Output = &s.Buffer
	}

	suite := godog.TestSuite{
		Name:                fmt.Sprintf("xds Test Suite [%v]", s.Variant),
		ScenarioInitializer: initScenario,
		Options:             &godogOpts,
	}

	s.TestSuite = suite
}

func (s *Suite) Run() (results types.VariantResults, err error) {
	s.TestSuite.Run()
	if s.TestWriting {
		return results, err
	}

	if err = json.Unmarshal([]byte(s.Buffer.String()), &results); err != nil {
		err = fmt.Errorf("Error unmarshalling test results: %v\n", err)
		return results, err
	}

	results.Name = string(s.Variant)
	return results, err
}

func NewSotwNonAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.SotwNonAggregated,
		Aggregated:  false,
		Incremental: false,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}
}

func NewSotwAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.SotwAggregated,
		Aggregated:  true,
		Incremental: false,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}

}

func NewIncrementalNonAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.IncrementalNonAggregated,
		Aggregated:  false,
		Incremental: true,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}

}

func NewIncrementalAggregatedSuite(testWriting bool) *Suite {
	return &Suite{
		Variant:     types.IncrementalAggregated,
		Aggregated:  true,
		Incremental: true,
		TestWriting: testWriting,
		Buffer:      *bytes.NewBuffer(nil),
	}
}

func NewSuite(variant types.Variant, testWriting bool) *Suite {
	switch variant {
	case types.SotwNonAggregated:
		return NewSotwNonAggregatedSuite(testWriting)
	case types.SotwAggregated:
		return NewSotwAggregatedSuite(testWriting)
	case types.IncrementalNonAggregated:
		return NewIncrementalNonAggregatedSuite(testWriting)
	case types.IncrementalAggregated:
		return NewIncrementalAggregatedSuite(testWriting)
	default:
		return nil
	}
}

func UpdateResults(current types.Results, variantResults types.VariantResults) types.Results {
	return types.Results{
		Total:            current.Total + int64(variantResults.Total),
		Passed:           current.Passed + int64(variantResults.Passed),
		Failed:           current.Failed + int64(variantResults.Failed),
		Skipped:          current.Skipped + int64(variantResults.Skipped),
		Undefined:        current.Undefined + int64(variantResults.Undefined),
		Pending:          current.Pending + int64(variantResults.Pending),
		Variants:         append(current.Variants, variantResults.Name),
		ResultsByVariant: append(current.ResultsByVariant, variantResults),
	}
}

func variantToOutputFile(v types.Variant) string {
	parts := strings.Split(string(v), " ")
	fileName := strings.Join(parts, "-")
	return fileName + ".json"
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package token

import (
	"errors"
	"fmt"
	"io"
	"reflect"

	"github.com/issue9/is"
	"golang.org/x/text/message"

	"github.com/caixw/apidoc/v7/core"
	"github.com/caixw/apidoc/v7/internal/locale"
	"github.com/caixw/apidoc/v7/internal/node"
)

// Decoder 实现从 p 中解码内容到当前对象的值
type Decoder interface {
	// 从 p 中读取内容并实例化到当前对象中
	//
	// 必须要同时从 p 中读取相应的 EndElement 才能返回。
	// end 表示 EndElement.End 的值。
	//
	// NOTE: 如果是自闭合标签，则不会调用该接口。
	//
	// 接口应该只返回 *core.SyntaxError 作为错误对象。
	DecodeXML(p *Parser, start *StartElement) (end *EndElement, err error)
}

// AttrDecoder 实现从 attr 中解码内容到当前对象的值
type AttrDecoder interface {
	// 解析属性值
	//
	// 接口应该只返回 *core.SyntaxError 作为错误对象。
	DecodeXMLAttr(p *Parser, attr *Attribute) error
}

// Sanitizer 用于验证和修改对象中的数据
type Sanitizer interface {
	// 验证数据是否正确
	//
	// 可以通过 p.NewError 和 p.WithError 返回 *core.SyntaxError 类型的错误
	Sanitize(p *Parser) error
}

var (
	attrDecoderType = reflect.TypeOf((*AttrDecoder)(nil)).Elem()
	decoderType     = reflect.TypeOf((*Decoder)(nil)).Elem()
	sanitizerType   = reflect.TypeOf((*Sanitizer)(nil)).Elem()
)

type decoder struct {
	h      *core.MessageHandler
	p      *Parser
	prefix string // 命名空间所表示的前缀
}

func (d *decoder) message(t core.MessageType, start, end core.Position, field string, key message.Reference, v ...interface{}) bool {
	d.h.Message(t, d.p.NewError(start, end, field, key, v...))
	return false
}

func (d *decoder) error(err error) bool {
	d.h.Error(err)
	return false
}

// Decode 将 p 中的 XML 内容解码至 v 对象中
//
// namespace 如果不为空表示当前的 xml 所在的命名空间，只有该命名空间的元素才会被正确识别。
func Decode(h *core.MessageHandler, p *Parser, v interface{}, namespace string) {
	d := &decoder{
		h: h,
		p: p,
	}

	var hasRoot bool
	for {
		t, r, err := p.Token()
		if errors.Is(err, io.EOF) {
			return
		} else if err != nil {
			h.Error(err)
			return
		}

		switch elem := t.(type) {
		case *StartElement:
			if hasRoot { // 多个根元素
				d.message(core.Erro, elem.Start, elem.End, "", locale.ErrInvalidXML)
				return
			}
			hasRoot = true

			d.prefix = findPrefix(elem, namespace)
			if !d.decodeElement(elem, node.ParseValue(reflect.ValueOf(v))) {
				return
			}
		case *Comment, *String, *Instruction: // 忽略注释和普通的文本内容
		default:
			d.message(core.Erro, r.Start, r.End, "", locale.ErrInvalidXML)
			return
		}
	}
}

func findPrefix(start *StartElement, namespace string) string {
	for _, attr := range start.Attributes {
		if attr.Name.Prefix.Value == "xmlns" && attr.Value.Value == namespace {
			return attr.Name.Local.Value
		}
	}
	return ""
}

func (d *decoder) decode(n *node.Node, start *StartElement) *EndElement {
	d.decodeAttributes(n, start)

	if start.Close {
		d.checkOmitempty(n, start.Start, start.End)
		return nil
	}

	if end, ok := d.decodeElements(n); ok {
		d.checkOmitempty(n, start.Start, end.End)
		return end
	}
	return nil
}

// 判断 omitempty 属性
func (d *decoder) checkOmitempty(n *node.Node, start, end core.Position) {
	for _, attr := range n.Attributes {
		if canNotEmpty(attr) {
			d.message(core.Erro, start, end, attr.Name, locale.ErrRequired)
		}
	}
	for _, elem := range n.Elements {
		if canNotEmpty(elem) {
			d.message(core.Erro, start, end, elem.Name, locale.ErrRequired)
		}
	}
	if n.CData != nil && canNotEmpty(n.CData) {
		d.message(core.Erro, start, end, "cdata", locale.ErrRequired)
	}
	if n.Content != nil && canNotEmpty(n.Content) {
		d.message(core.Erro, start, end, "content", locale.ErrRequired)
	}
}

// 当前表示的值必须是一个非空值
func canNotEmpty(v *node.Value) bool {
	return v.Name != "" && // cdata 和 content 在未初始化时 name 字段为空值
		!v.Omitempty &&
		(!v.CanInterface() || is.Empty(v.Interface(), true))
}

// 将 start 的属性内容解码到 obj.Attributes 之中
func (d *decoder) decodeAttributes(n *node.Node, start *StartElement) {
	if start == nil {
		return
	}

	for _, attr := range start.Attributes {
		item, found := n.Attribute(attr.Name.Local.Value)
		if !found || d.prefix != attr.Name.Prefix.Value { // 未找到或是命名空间不匹配
			continue
		}
		v := node.GetRealValue(item.Value)
		v.Set(reflect.New(v.Type()).Elem())

		var impl bool
		if item.CanInterface() && item.Type().Implements(attrDecoderType) {
			if err := item.Interface().(AttrDecoder).DecodeXMLAttr(d.p, attr); err != nil {
				d.error(err)
			}
			impl = true
		} else if item.CanAddr() {
			pv := item.Addr()
			if pv.CanInterface() && pv.Type().Implements(attrDecoderType) {
				if err := pv.Interface().(AttrDecoder).DecodeXMLAttr(d.p, attr); err != nil {
					d.error(err)
				}
				impl = true
			}
		}

		if !impl {
			panic(fmt.Sprintf("当前属性 %s 未实现 AttrDecoder 接口", attr.Name))
		}

		if err := setAttributeValue(item.Value, item.Usage, d.p, attr); err != nil {
			d.error(err)
		}
	}
}

func (d *decoder) decodeElements(n *node.Node) (end *EndElement, ok bool) {
	for {
		t, r, err := d.p.Token()
		if errors.Is(err, io.EOF) {
			// 应该只有 EndElement 才能返回，否则就不完整的 XML
			return nil, d.message(core.Erro, d.p.Position().Position, d.p.Position().Position, "", locale.ErrInvalidXML)
		} else if err != nil {
			return nil, d.error(err)
		}

		switch elem := t.(type) {
		case *EndElement: // 找到当前对象的结束标签
			if (elem.Name.Local.Value == n.Value.Name) && (elem.Name.Prefix.Value == d.prefix) {
				return elem, true
			}
			return nil, d.message(core.Erro, elem.Start, elem.End, n.Value.Name, locale.ErrNotFoundEndTag)
		case *CData:
			if n.CData != nil {
				setContentValue(n.CData.Value, reflect.ValueOf(elem))
			}
		case *String:
			if n.Content != nil {
				setContentValue(n.Content.Value, reflect.ValueOf(elem))
			}
		case *StartElement:
			item, found := n.Element(elem.Name.Local.Value)
			if !found || d.prefix != elem.Name.Prefix.Value {
				if err := findEndElement(d.p, elem); err != nil {
					return nil, d.error(err)
				}

				d.message(core.Warn, elem.Name.Start, elem.Name.End, elem.Name.String(), locale.ErrInvalidTag)
				break // 忽略不存在的子元素
			}
			if !d.decodeElement(elem, item) {
				return nil, false
			}
		case *Comment: // 忽略注释内容
		default:
			return nil, d.message(core.Erro, r.Start, r.End, "", locale.ErrInvalidXML)
		}
	}
}

func setContentValue(target, source reflect.Value) {
	target = node.GetRealValue(target)
	source = node.GetRealValue(source)

	st := source.Type()
	num := st.NumField()
	for i := 0; i < num; i++ {
		name := st.Field(i).Name
		target.FieldByName(name).
			Set(source.Field(i))
	}
}

func (d *decoder) decodeElement(start *StartElement, v *node.Value) (ok bool) {
	v.Value = node.GetRealValue(v.Value)
	k := v.Kind()
	switch {
	case k == reflect.Ptr, k == reflect.Func, k == reflect.Chan, k == reflect.Array, node.IsPrimitive(v.Value):
		panic(fmt.Sprintf("%s 是无效的类型", v.Value.Type()))
	case k == reflect.Slice:
		return d.decodeSlice(start, v)
	}

	end, impl, err := callDecodeXML(v.Value, d.p, start)
	if !impl {
		end = d.decode(node.New(start.Name.Local.Value, v.Value), start)
	}
	if err != nil {
		return d.error(err)
	}

	if err := setTagValue(v.Value, v.Usage, d.p, start, end); err != nil {
		return d.error(err)
	}
	return true
}

func (d *decoder) decodeSlice(start *StartElement, slice *node.Value) (ok bool) {
	// 不相配，表示当前元素找不到与之相配的元素，需要忽略这个元素，
	// 所以要过滤与 start 想匹配的结束符号才算结束。
	if !start.Close && (start.Name.Local.Value != slice.Name) {
		if err := findEndElement(d.p, start); err != nil {
			return d.error(err)
		}
	}

	elem := reflect.New(slice.Type().Elem()).Elem()
	if elem.Kind() == reflect.Ptr && elem.IsNil() {
		elem.Set(reflect.New(elem.Type().Elem()))
	}

	end, impl, err := callDecodeXML(elem, d.p, start)
	if !impl {
		if node.IsPrimitive(elem) {
			panic(fmt.Sprintf("%s:%s 必须实现 Decoder 接口", slice.Name, elem.Type()))
		}
		end = d.decode(node.New(start.Name.Local.Value, elem), start)
	}
	if err != nil {
		return d.error(err)
	}

	if err = setTagValue(elem, slice.Usage, d.p, start, end); err != nil {
		return d.error(err)
	}
	slice.Value.Set(reflect.Append(slice.Value, elem))
	return true
}

// 调用 v 的 DecodeXML 接口方法
//
// 当 impl 为 true 时，err 表示的是 DecodeXML 接口返回的错误，否则 err 永远为 nil
func callDecodeXML(v reflect.Value, p *Parser, start *StartElement) (end *EndElement, impl bool, err error) {
	if v.CanInterface() && v.Type().Implements(decoderType) {
		if !start.Close {
			end, err = v.Interface().(Decoder).DecodeXML(p, start)
		}
		return end, true, err
	} else if v.CanAddr() {
		pv := v.Addr()
		if pv.CanInterface() && pv.Type().Implements(decoderType) {
			if !start.Close {
				end, err = pv.Interface().(Decoder).DecodeXML(p, start)
			}
			return end, true, err
		}
	}
	return nil, false, nil
}

// 找到与 start 相对应的结束符号位置
func findEndElement(p *Parser, start *StartElement) error {
	if start.Close {
		return nil
	}

	level := 0
	for {
		t, _, err := p.Token()
		if errors.Is(err, io.EOF) {
			return p.NewError(start.Start, start.End, start.Name.String(), locale.ErrNotFoundEndTag) // 找不到相配的结束符号
		} else if err != nil {
			return err
		}

		switch elem := t.(type) {
		case *StartElement:
			if elem.Name.Equal(start.Name) {
				level++
			}
		case *EndElement:
			if level == 0 && start.Match(elem) {
				return nil
			}
			level--
		}
	}
}

func setTagValue(v reflect.Value, usage string, p *Parser, start *StartElement, end *EndElement) error {
	v = node.GetRealValue(v)
	if v.Kind() != reflect.Struct {
		panic(fmt.Sprintf("无效的 kind 类型: %s:%s", v.Type(), v.Kind()))
	}

	setFieldValue(v, usageKeyName, usage)
	setFieldValue(v, elementTagName, start.Name)
	if end == nil {
		setFieldValue(v, rangeName, start.Range)
	} else {
		setFieldValue(v, rangeName, core.Range{Start: start.Start, End: end.End})
		setFieldValue(v, elementTagEndName, end.Name)
	}

	// Sanitize 在最后调用，可以保证 Sanitize 调用中可以取 v.Range 的值
	return callSanitizer(v, p)
}

func setAttributeValue(v reflect.Value, usage string, p *Parser, attr *Attribute) error {
	v = node.GetRealValue(v)
	if v.Kind() != reflect.Struct {
		panic(fmt.Sprintf("无效的 kind 类型: %s:%s", v.Type(), v.Kind()))
	}

	setFieldValue(v, rangeName, attr.Range)
	setFieldValue(v, usageKeyName, usage)
	setFieldValue(v, attributeNameName, attr.Name)

	return callSanitizer(v, p)
}

func callSanitizer(v reflect.Value, p *Parser) error {
	if v.CanInterface() && v.Type().Implements(sanitizerType) {
		return v.Interface().(Sanitizer).Sanitize(p)
	} else if v.CanAddr() {
		pv := v.Addr()
		if pv.CanInterface() && pv.Type().Implements(sanitizerType) {
			return pv.Interface().(Sanitizer).Sanitize(p)
		}
	}
	return nil
}

func setFieldValue(v reflect.Value, name string, value interface{}) {
	vv := v.FieldByName(name)
	if vv.CanSet() {
		vv.Set(reflect.ValueOf(value))
	}
}
<commit_msg>fix(internal/token): 调用 Sanitizer 接口返回的错误不再中断解析<commit_after>// SPDX-License-Identifier: MIT

package token

import (
	"errors"
	"fmt"
	"io"
	"reflect"

	"github.com/issue9/is"
	"golang.org/x/text/message"

	"github.com/caixw/apidoc/v7/core"
	"github.com/caixw/apidoc/v7/internal/locale"
	"github.com/caixw/apidoc/v7/internal/node"
)

// Decoder 实现从 p 中解码内容到当前对象的值
type Decoder interface {
	// 从 p 中读取内容并实例化到当前对象中
	//
	// 必须要同时从 p 中读取相应的 EndElement 才能返回。
	// end 表示 EndElement.End 的值。
	//
	// NOTE: 如果是自闭合标签，则不会调用该接口。
	//
	// 接口应该只返回 *core.SyntaxError 作为错误对象。
	DecodeXML(p *Parser, start *StartElement) (end *EndElement, err error)
}

// AttrDecoder 实现从 attr 中解码内容到当前对象的值
type AttrDecoder interface {
	// 解析属性值
	//
	// 接口应该只返回 *core.SyntaxError 作为错误对象。
	DecodeXMLAttr(p *Parser, attr *Attribute) error
}

// Sanitizer 用于验证和修改对象中的数据
type Sanitizer interface {
	// 验证数据是否正确
	//
	// 可以通过 p.NewError 和 p.WithError 返回 *core.SyntaxError 类型的错误
	Sanitize(p *Parser) error
}

var (
	attrDecoderType = reflect.TypeOf((*AttrDecoder)(nil)).Elem()
	decoderType     = reflect.TypeOf((*Decoder)(nil)).Elem()
	sanitizerType   = reflect.TypeOf((*Sanitizer)(nil)).Elem()
)

type decoder struct {
	h      *core.MessageHandler
	p      *Parser
	prefix string // 命名空间所表示的前缀
}

func (d *decoder) message(t core.MessageType, start, end core.Position, field string, key message.Reference, v ...interface{}) bool {
	d.h.Message(t, d.p.NewError(start, end, field, key, v...))
	return false
}

func (d *decoder) error(err error) bool {
	d.h.Error(err)
	return false
}

// Decode 将 p 中的 XML 内容解码至 v 对象中
//
// namespace 如果不为空表示当前的 xml 所在的命名空间，只有该命名空间的元素才会被正确识别。
func Decode(h *core.MessageHandler, p *Parser, v interface{}, namespace string) {
	d := &decoder{
		h: h,
		p: p,
	}

	var hasRoot bool
	for {
		t, r, err := p.Token()
		if errors.Is(err, io.EOF) {
			return
		} else if err != nil {
			h.Error(err)
			return
		}

		switch elem := t.(type) {
		case *StartElement:
			if hasRoot { // 多个根元素
				d.message(core.Erro, elem.Start, elem.End, "", locale.ErrInvalidXML)
				return
			}
			hasRoot = true

			d.prefix = findPrefix(elem, namespace)
			if !d.decodeElement(elem, node.ParseValue(reflect.ValueOf(v))) {
				return
			}
		case *Comment, *String, *Instruction: // 忽略注释和普通的文本内容
		default:
			d.message(core.Erro, r.Start, r.End, "", locale.ErrInvalidXML)
			return
		}
	}
}

func findPrefix(start *StartElement, namespace string) string {
	for _, attr := range start.Attributes {
		if attr.Name.Prefix.Value == "xmlns" && attr.Value.Value == namespace {
			return attr.Name.Local.Value
		}
	}
	return ""
}

func (d *decoder) decode(n *node.Node, start *StartElement) *EndElement {
	d.decodeAttributes(n, start)

	if start.Close {
		d.checkOmitempty(n, start.Start, start.End)
		return nil
	}

	if end, ok := d.decodeElements(n); ok {
		d.checkOmitempty(n, start.Start, end.End)
		return end
	}
	return nil
}

// 判断 omitempty 属性
func (d *decoder) checkOmitempty(n *node.Node, start, end core.Position) {
	for _, attr := range n.Attributes {
		if canNotEmpty(attr) {
			d.message(core.Erro, start, end, attr.Name, locale.ErrRequired)
		}
	}
	for _, elem := range n.Elements {
		if canNotEmpty(elem) {
			d.message(core.Erro, start, end, elem.Name, locale.ErrRequired)
		}
	}
	if n.CData != nil && canNotEmpty(n.CData) {
		d.message(core.Erro, start, end, "cdata", locale.ErrRequired)
	}
	if n.Content != nil && canNotEmpty(n.Content) {
		d.message(core.Erro, start, end, "content", locale.ErrRequired)
	}
}

// 当前表示的值必须是一个非空值
func canNotEmpty(v *node.Value) bool {
	return v.Name != "" && // cdata 和 content 在未初始化时 name 字段为空值
		!v.Omitempty &&
		(!v.CanInterface() || is.Empty(v.Interface(), true))
}

// 将 start 的属性内容解码到 obj.Attributes 之中
func (d *decoder) decodeAttributes(n *node.Node, start *StartElement) {
	if start == nil {
		return
	}

	for _, attr := range start.Attributes {
		item, found := n.Attribute(attr.Name.Local.Value)
		if !found || d.prefix != attr.Name.Prefix.Value { // 未找到或是命名空间不匹配
			continue
		}
		v := node.GetRealValue(item.Value)
		v.Set(reflect.New(v.Type()).Elem())

		var impl bool
		if item.CanInterface() && item.Type().Implements(attrDecoderType) {
			if err := item.Interface().(AttrDecoder).DecodeXMLAttr(d.p, attr); err != nil {
				d.error(err)
			}
			impl = true
		} else if item.CanAddr() {
			pv := item.Addr()
			if pv.CanInterface() && pv.Type().Implements(attrDecoderType) {
				if err := pv.Interface().(AttrDecoder).DecodeXMLAttr(d.p, attr); err != nil {
					d.error(err)
				}
				impl = true
			}
		}

		if !impl {
			panic(fmt.Sprintf("当前属性 %s 未实现 AttrDecoder 接口", attr.Name))
		}

		d.setAttributeValue(item.Value, item.Usage, attr)
	}
}

func (d *decoder) decodeElements(n *node.Node) (end *EndElement, ok bool) {
	for {
		t, r, err := d.p.Token()
		if errors.Is(err, io.EOF) {
			// 应该只有 EndElement 才能返回，否则就不完整的 XML
			return nil, d.message(core.Erro, d.p.Position().Position, d.p.Position().Position, "", locale.ErrInvalidXML)
		} else if err != nil {
			return nil, d.error(err)
		}

		switch elem := t.(type) {
		case *EndElement: // 找到当前对象的结束标签
			if (elem.Name.Local.Value == n.Value.Name) && (elem.Name.Prefix.Value == d.prefix) {
				return elem, true
			}
			return nil, d.message(core.Erro, elem.Start, elem.End, n.Value.Name, locale.ErrNotFoundEndTag)
		case *CData:
			if n.CData != nil {
				setContentValue(n.CData.Value, reflect.ValueOf(elem))
			}
		case *String:
			if n.Content != nil {
				setContentValue(n.Content.Value, reflect.ValueOf(elem))
			}
		case *StartElement:
			item, found := n.Element(elem.Name.Local.Value)
			if !found || d.prefix != elem.Name.Prefix.Value {
				if err := findEndElement(d.p, elem); err != nil {
					return nil, d.error(err)
				}

				d.message(core.Warn, elem.Name.Start, elem.Name.End, elem.Name.String(), locale.ErrInvalidTag)
				break // 忽略不存在的子元素
			}
			if !d.decodeElement(elem, item) {
				return nil, false
			}
		case *Comment: // 忽略注释内容
		default:
			return nil, d.message(core.Erro, r.Start, r.End, "", locale.ErrInvalidXML)
		}
	}
}

func setContentValue(target, source reflect.Value) {
	target = node.GetRealValue(target)
	source = node.GetRealValue(source)

	st := source.Type()
	num := st.NumField()
	for i := 0; i < num; i++ {
		name := st.Field(i).Name
		target.FieldByName(name).
			Set(source.Field(i))
	}
}

func (d *decoder) decodeElement(start *StartElement, v *node.Value) (ok bool) {
	v.Value = node.GetRealValue(v.Value)
	k := v.Kind()
	switch {
	case k == reflect.Ptr, k == reflect.Func, k == reflect.Chan, k == reflect.Array, node.IsPrimitive(v.Value):
		panic(fmt.Sprintf("%s 是无效的类型", v.Value.Type()))
	case k == reflect.Slice:
		return d.decodeSlice(start, v)
	}

	end, impl, err := callDecodeXML(v.Value, d.p, start)
	if !impl {
		end = d.decode(node.New(start.Name.Local.Value, v.Value), start)
	}
	if err != nil {
		return d.error(err)
	}

	d.setTagValue(v.Value, v.Usage, start, end)
	return true
}

func (d *decoder) decodeSlice(start *StartElement, slice *node.Value) (ok bool) {
	// 不相配，表示当前元素找不到与之相配的元素，需要忽略这个元素，
	// 所以要过滤与 start 想匹配的结束符号才算结束。
	if !start.Close && (start.Name.Local.Value != slice.Name) {
		if err := findEndElement(d.p, start); err != nil {
			return d.error(err)
		}
	}

	elem := reflect.New(slice.Type().Elem()).Elem()
	if elem.Kind() == reflect.Ptr && elem.IsNil() {
		elem.Set(reflect.New(elem.Type().Elem()))
	}

	end, impl, err := callDecodeXML(elem, d.p, start)
	if !impl {
		if node.IsPrimitive(elem) {
			panic(fmt.Sprintf("%s:%s 必须实现 Decoder 接口", slice.Name, elem.Type()))
		}
		end = d.decode(node.New(start.Name.Local.Value, elem), start)
	}
	if err != nil {
		return d.error(err)
	}

	d.setTagValue(elem, slice.Usage, start, end)
	slice.Value.Set(reflect.Append(slice.Value, elem))
	return true
}

// 调用 v 的 DecodeXML 接口方法
//
// 当 impl 为 true 时，err 表示的是 DecodeXML 接口返回的错误，否则 err 永远为 nil
func callDecodeXML(v reflect.Value, p *Parser, start *StartElement) (end *EndElement, impl bool, err error) {
	if v.CanInterface() && v.Type().Implements(decoderType) {
		if !start.Close {
			end, err = v.Interface().(Decoder).DecodeXML(p, start)
		}
		return end, true, err
	} else if v.CanAddr() {
		pv := v.Addr()
		if pv.CanInterface() && pv.Type().Implements(decoderType) {
			if !start.Close {
				end, err = pv.Interface().(Decoder).DecodeXML(p, start)
			}
			return end, true, err
		}
	}
	return nil, false, nil
}

// 找到与 start 相对应的结束符号位置
func findEndElement(p *Parser, start *StartElement) error {
	if start.Close {
		return nil
	}

	level := 0
	for {
		t, _, err := p.Token()
		if errors.Is(err, io.EOF) {
			return p.NewError(start.Start, start.End, start.Name.String(), locale.ErrNotFoundEndTag) // 找不到相配的结束符号
		} else if err != nil {
			return err
		}

		switch elem := t.(type) {
		case *StartElement:
			if elem.Name.Equal(start.Name) {
				level++
			}
		case *EndElement:
			if level == 0 && start.Match(elem) {
				return nil
			}
			level--
		}
	}
}

func (d *decoder) setTagValue(v reflect.Value, usage string, start *StartElement, end *EndElement) {
	v = node.GetRealValue(v)
	if v.Kind() != reflect.Struct {
		panic(fmt.Sprintf("无效的 kind 类型: %s:%s", v.Type(), v.Kind()))
	}

	setFieldValue(v, usageKeyName, usage)
	setFieldValue(v, elementTagName, start.Name)
	if end == nil {
		setFieldValue(v, rangeName, start.Range)
	} else {
		setFieldValue(v, rangeName, core.Range{Start: start.Start, End: end.End})
		setFieldValue(v, elementTagEndName, end.Name)
	}

	// Sanitize 在最后调用，可以保证 Sanitize 调用中可以取 v.Range 的值
	if err := callSanitizer(v, d.p); err != nil {
		d.error(err)
	}
}

func (d *decoder) setAttributeValue(v reflect.Value, usage string, attr *Attribute) {
	v = node.GetRealValue(v)
	if v.Kind() != reflect.Struct {
		panic(fmt.Sprintf("无效的 kind 类型: %s:%s", v.Type(), v.Kind()))
	}

	setFieldValue(v, rangeName, attr.Range)
	setFieldValue(v, usageKeyName, usage)
	setFieldValue(v, attributeNameName, attr.Name)

	if err := callSanitizer(v, d.p); err != nil {
		d.error(err)
	}
}

func callSanitizer(v reflect.Value, p *Parser) error {
	if v.CanInterface() && v.Type().Implements(sanitizerType) {
		return v.Interface().(Sanitizer).Sanitize(p)
	} else if v.CanAddr() {
		pv := v.Addr()
		if pv.CanInterface() && pv.Type().Implements(sanitizerType) {
			return pv.Interface().(Sanitizer).Sanitize(p)
		}
	}
	return nil
}

func setFieldValue(v reflect.Value, name string, value interface{}) {
	vv := v.FieldByName(name)
	if vv.CanSet() {
		vv.Set(reflect.ValueOf(value))
	}
}
<|endoftext|>


<commit_before>package main

import (
	_ "github.com/docker/docker/autogen/winresources/docker"
)
<commit_msg>Windows: work around Go 1.6.2/Nano Server TP5 issue<commit_after>package main

import (
	"sync/atomic"

	_ "github.com/docker/docker/autogen/winresources/docker"
)

//go:cgo_import_dynamic main.dummy CommandLineToArgvW%2 "shell32.dll"

var dummy uintptr

func init() {
	// Ensure that this import is not removed by the linker. This is used to
	// ensure that shell32.dll is loaded by the system loader, preventing
	// go#15286 from triggering on Nano Server TP5.
	atomic.LoadUintptr(&dummy)
}
<|endoftext|>


<commit_before>package glisp

import (
	"errors"
	"bytes"
)

var WrongNargs error = errors.New("wrong number of arguments")

type GlispFunction []Instruction
type GlispUserFunction func(*Glisp, string, []Sexp) (Sexp, error)

func (f GlispFunction) SexpString() string {
	return "function"
}

func (f GlispUserFunction) SexpString() string {
	return "user_function"
}

func CompareFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	res, err := Compare(args[0], args[1])
	if err != nil {
		return SexpNull, err
	}

	cond := false
	switch name {
	case "<":
		cond = res < 0
	case ">":
		cond = res > 0
	case "<=":
		cond = res <= 0
	case ">=":
		cond = res >= 0
	case "=":
		cond = res == 0
	case "not=":
		cond = res != 0
	}

	return SexpBool(cond), nil
}

func BinaryIntFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	var op IntegerOp
	switch name {
	case "sll":
		op = ShiftLeft
	case "sra":
		op = ShiftRightArith
	case "srl":
		op = ShiftRightLog
	case "mod":
		op = Modulo
	}

	return IntegerDo(op, args[0], args[1])
}

func BitwiseFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	var op IntegerOp
	switch name {
	case "bit-and":
		op = BitAnd
	case "bit-or":
		op = BitOr
	case "bit-xor":
		op = BitXor
	}

	accum := args[0]
	var err error

	for _, expr := range args[1:] {
		accum, err = IntegerDo(op, accum, expr)
		if err != nil {
			return SexpNull, err
		}
	}
	return accum, nil
}

func NumericFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) < 1 {
		return SexpNull, WrongNargs
	}

	var err error
	accum := args[0]
	var op NumericOp
	switch name {
	case "+":
		op = Add
	case "-":
		op = Sub
	case "*":
		op = Mult
	case "/":
		op = Div
	}

	for _, expr := range args[1:] {
		accum, err = NumericDo(op, accum, expr)
		if err != nil {
			return SexpNull, err
		}
	}
	return accum, nil
}

func ReadFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 1 {
		return SexpNull, WrongNargs
	}
	str := ""
	switch t := args[0].(type) {
	case SexpStr:
		str = string(t)
	default:
		return SexpNull, WrongType
	}
	lexer := NewLexerFromStream(bytes.NewBuffer([]byte(str)))
	parser := Parser{lexer, env}
	return ParseExpression(&parser)
}

var BuiltinFunctions = map[string]GlispUserFunction {
	"<" : CompareFunction,
	">" : CompareFunction,
	"<=": CompareFunction,
	">=": CompareFunction,
	"=" : CompareFunction,
	"not=": CompareFunction,
	"sll": BinaryIntFunction,
	"sra": BinaryIntFunction,
	"srl": BinaryIntFunction,
	"mod": BinaryIntFunction,
	"+": NumericFunction,
	"-": NumericFunction,
	"*": NumericFunction,
	"/": NumericFunction,
	"bit-and": BitwiseFunction,
	"bit-or": BitwiseFunction,
	"bit-xor": BitwiseFunction,
	"read": ReadFunction,
}
<commit_msg>add list manip functions<commit_after>package glisp

import (
	"errors"
	"bytes"
)

var WrongNargs error = errors.New("wrong number of arguments")

type GlispFunction []Instruction
type GlispUserFunction func(*Glisp, string, []Sexp) (Sexp, error)

func (f GlispFunction) SexpString() string {
	return "function"
}

func (f GlispUserFunction) SexpString() string {
	return "user_function"
}

func CompareFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	res, err := Compare(args[0], args[1])
	if err != nil {
		return SexpNull, err
	}

	cond := false
	switch name {
	case "<":
		cond = res < 0
	case ">":
		cond = res > 0
	case "<=":
		cond = res <= 0
	case ">=":
		cond = res >= 0
	case "=":
		cond = res == 0
	case "not=":
		cond = res != 0
	}

	return SexpBool(cond), nil
}

func BinaryIntFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	var op IntegerOp
	switch name {
	case "sll":
		op = ShiftLeft
	case "sra":
		op = ShiftRightArith
	case "srl":
		op = ShiftRightLog
	case "mod":
		op = Modulo
	}

	return IntegerDo(op, args[0], args[1])
}

func BitwiseFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	var op IntegerOp
	switch name {
	case "bit-and":
		op = BitAnd
	case "bit-or":
		op = BitOr
	case "bit-xor":
		op = BitXor
	}

	accum := args[0]
	var err error

	for _, expr := range args[1:] {
		accum, err = IntegerDo(op, accum, expr)
		if err != nil {
			return SexpNull, err
		}
	}
	return accum, nil
}

func NumericFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) < 1 {
		return SexpNull, WrongNargs
	}

	var err error
	accum := args[0]
	var op NumericOp
	switch name {
	case "+":
		op = Add
	case "-":
		op = Sub
	case "*":
		op = Mult
	case "/":
		op = Div
	}

	for _, expr := range args[1:] {
		accum, err = NumericDo(op, accum, expr)
		if err != nil {
			return SexpNull, err
		}
	}
	return accum, nil
}

func ConsFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 2 {
		return SexpNull, WrongNargs
	}

	return SexpPair{args[0], args[1]}, nil
}

func FirstFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 1 {
		return SexpNull, WrongNargs
	}

	switch expr := args[0].(type) {
	case SexpPair:
		return expr.head, nil
	case SexpArray:
		return expr[0], nil
	}

	return SexpNull, WrongType
}

func RestFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 1 {
		return SexpNull, WrongNargs
	}

	switch expr := args[0].(type) {
	case SexpPair:
		return expr.tail, nil
	case SexpArray:
		if len(expr) == 0 {
			return expr, nil
		}
		return expr[1:], nil
	case SexpSentinel:
		if expr == SexpNull {
			return SexpNull, nil
		}
	}

	return SexpNull, WrongType
}

func ReadFunction(env *Glisp, name string, args []Sexp) (Sexp, error) {
	if len(args) != 1 {
		return SexpNull, WrongNargs
	}
	str := ""
	switch t := args[0].(type) {
	case SexpStr:
		str = string(t)
	default:
		return SexpNull, WrongType
	}
	lexer := NewLexerFromStream(bytes.NewBuffer([]byte(str)))
	parser := Parser{lexer, env}
	return ParseExpression(&parser)
}

var BuiltinFunctions = map[string]GlispUserFunction {
	"<" : CompareFunction,
	">" : CompareFunction,
	"<=": CompareFunction,
	">=": CompareFunction,
	"=" : CompareFunction,
	"not=": CompareFunction,
	"sll": BinaryIntFunction,
	"sra": BinaryIntFunction,
	"srl": BinaryIntFunction,
	"mod": BinaryIntFunction,
	"+": NumericFunction,
	"-": NumericFunction,
	"*": NumericFunction,
	"/": NumericFunction,
	"bit-and": BitwiseFunction,
	"bit-or": BitwiseFunction,
	"bit-xor": BitwiseFunction,
	"read": ReadFunction,
	"cons": ConsFunction,
	"first": FirstFunction,
	"rest": RestFunction,
}
<|endoftext|>


<commit_before>// Copyright © 2017 Aaron Donovan <amdonov@gmail.com>
//
// 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 sp

import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/amdonov/lite-idp/saml"
	log "github.com/sirupsen/logrus"
)

// ArtifactCallback is called by the service provider following success retrieval of a SAML assertion
type ArtifactCallback func(w http.ResponseWriter, r *http.Request, state []byte, assertion *saml.Assertion)

func (sp *serviceProvider) ArtifactFunc(callback ArtifactCallback) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			log.Error(err)
			i.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		state, err := sp.retrieveState(r)
		if err != nil {
			i.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		// call the IdP to get the SAML assertion
		assertion, err := sp.resolveArtifact(r.Form.Get("SAMLart"))
		if err != nil {
			i.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		// allow the application to write the response
		callback(w, r, state, assertion)
	}
}

func (sp *serviceProvider) retrieveState(r *http.Request) (state []byte, err error) {
	// retrieve the relayState from our cache
	stateID := r.Form.Get("RelayState")
	if stateID == "" {
		log.Info("received a request without RelayState")
		return nil, errors.New("identity provider did not return RelayState")
	}
	if sp.stateCache == nil {
		state = []byte(stateID)
	} else {
		state, err = sp.stateCache.Get(stateID)
		if err != nil {
			log.Info("RelayState not found in cache")
			return nil, errors.New("provided RelayState is invalid")
		}
	}
	return state, nil
}

func (sp *serviceProvider) resolveArtifact(artifact string) (*saml.Assertion, error) {
	request, err := sp.buildResolveRequest(artifact)
	if err != nil {
		return nil, err
	}
	post, err := http.NewRequest(http.MethodPost, sp.configuration.IDPArtifactEndpoint, request)
	if err != nil {
		return nil, err
	}
	post.Header.Add("Content-Type", "text/xml")
	post.Header.Add("SOAPAction", "http://www.oasis-open.org/committees/security")
	resp, err := sp.client.Do(post)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code from artifact resolve request %d", resp.StatusCode)
	}
	decoder := xml.NewDecoder(resp.Body)
	response := &saml.ArtifactResponseEnvelope{}
	if err := decoder.Decode(response); err != nil {
		return nil, err
	}
	assertion := response.Body.ArtifactResponse.Response.Assertion
	if assertion == nil {
		// TODO check the rest of the response for an error
		// Write it out for now until we know what we're looking
		encoder := xml.NewEncoder(os.Stderr)
		encoder.Encode(response)
		return nil, errors.New("check logs assertion was nil")
	}
	assertion.RawXML = response.Body.ArtifactResponse.Response.RawAssertion
	return assertion, nil
}

func (sp *serviceProvider) buildResolveRequest(artifact string) (io.Reader, error) {
	resolve := saml.ArtifactResolveEnvelope{
		Body: saml.ArtifactResolveBody{
			ArtifactResolve: saml.ArtifactResolve{
				RequestAbstractType: saml.RequestAbstractType{
					ID:           saml.NewID(),
					IssueInstant: time.Now().UTC(),
					Issuer:       sp.configuration.EntityID,
					Version:      "2.0",
				},
				Artifact: artifact,
			},
		},
	}
	signature, err := sp.signer.CreateSignature(resolve.Body.ArtifactResolve)
	if err != nil {
		return nil, err
	}
	resolve.Body.ArtifactResolve.Signature = signature
	var b bytes.Buffer
	encoder := xml.NewEncoder(&b)
	err = encoder.Encode(resolve)
	if err != nil {
		return nil, err
	}
	return bytes.NewReader(b.Bytes()), nil
}
<commit_msg>reverted find/replace error<commit_after>// Copyright © 2017 Aaron Donovan <amdonov@gmail.com>
//
// 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 sp

import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/amdonov/lite-idp/saml"
	log "github.com/sirupsen/logrus"
)

// ArtifactCallback is called by the service provider following success retrieval of a SAML assertion
type ArtifactCallback func(w http.ResponseWriter, r *http.Request, state []byte, assertion *saml.Assertion)

func (sp *serviceProvider) ArtifactFunc(callback ArtifactCallback) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			log.Error(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		state, err := sp.retrieveState(r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		// call the IdP to get the SAML assertion
		assertion, err := sp.resolveArtifact(r.Form.Get("SAMLart"))
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		// allow the application to write the response
		callback(w, r, state, assertion)
	}
}

func (sp *serviceProvider) retrieveState(r *http.Request) (state []byte, err error) {
	// retrieve the relayState from our cache
	stateID := r.Form.Get("RelayState")
	if stateID == "" {
		log.Info("received a request without RelayState")
		return nil, errors.New("identity provider did not return RelayState")
	}
	if sp.stateCache == nil {
		state = []byte(stateID)
	} else {
		state, err = sp.stateCache.Get(stateID)
		if err != nil {
			log.Info("RelayState not found in cache")
			return nil, errors.New("provided RelayState is invalid")
		}
	}
	return state, nil
}

func (sp *serviceProvider) resolveArtifact(artifact string) (*saml.Assertion, error) {
	request, err := sp.buildResolveRequest(artifact)
	if err != nil {
		return nil, err
	}
	post, err := http.NewRequest(http.MethodPost, sp.configuration.IDPArtifactEndpoint, request)
	if err != nil {
		return nil, err
	}
	post.Header.Add("Content-Type", "text/xml")
	post.Header.Add("SOAPAction", "http://www.oasis-open.org/committees/security")
	resp, err := sp.client.Do(post)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code from artifact resolve request %d", resp.StatusCode)
	}
	decoder := xml.NewDecoder(resp.Body)
	response := &saml.ArtifactResponseEnvelope{}
	if err := decoder.Decode(response); err != nil {
		return nil, err
	}
	assertion := response.Body.ArtifactResponse.Response.Assertion
	if assertion == nil {
		// TODO check the rest of the response for an error
		// Write it out for now until we know what we're looking
		encoder := xml.NewEncoder(os.Stderr)
		encoder.Encode(response)
		return nil, errors.New("check logs assertion was nil")
	}
	assertion.RawXML = response.Body.ArtifactResponse.Response.RawAssertion
	return assertion, nil
}

func (sp *serviceProvider) buildResolveRequest(artifact string) (io.Reader, error) {
	resolve := saml.ArtifactResolveEnvelope{
		Body: saml.ArtifactResolveBody{
			ArtifactResolve: saml.ArtifactResolve{
				RequestAbstractType: saml.RequestAbstractType{
					ID:           saml.NewID(),
					IssueInstant: time.Now().UTC(),
					Issuer:       sp.configuration.EntityID,
					Version:      "2.0",
				},
				Artifact: artifact,
			},
		},
	}
	signature, err := sp.signer.CreateSignature(resolve.Body.ArtifactResolve)
	if err != nil {
		return nil, err
	}
	resolve.Body.ArtifactResolve.Signature = signature
	var b bytes.Buffer
	encoder := xml.NewEncoder(&b)
	err = encoder.Encode(resolve)
	if err != nil {
		return nil, err
	}
	return bytes.NewReader(b.Bytes()), nil
}
<|endoftext|>


<commit_before>/**
 * @file scoreboard_test.go
 * @author Mikhail Klementyev jollheef<AT>riseup.net
 * @license GNU AGPLv3
 * @date November, 2015
 * @brief test scoreboard
 */

package scoreboard

import (
	"database/sql"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"testing"
	"time"

	"github.com/jollheef/henhouse/db"
	"github.com/jollheef/henhouse/game"
	"golang.org/x/net/websocket"
)

const dbPath = "user=postgres dbname=henhouse_test sslmode=disable"

func TestDurationToHMS(*testing.T) {

	t := time.Hour + time.Minute + time.Second
	real := "01:01:01"
	result := durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = time.Hour + time.Minute + time.Second + 100*time.Nanosecond
	real = "01:01:01"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 23*time.Hour + 13*time.Minute + 100*time.Nanosecond
	real = "23:13:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 0
	real = "00:00:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 15*time.Second + 100*time.Nanosecond
	real = "00:00:15"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 15 * time.Hour
	real = "15:00:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 15 * time.Minute
	real = "00:15:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}
}

func testMatch(pattern, s string) {
	matched, err := regexp.MatchString(pattern, s)
	if err != nil {
		panic(err)
	}
	if !matched {
		panic(s)
	}
}

func testNotMatch(pattern, s string) {
	matched, err := regexp.MatchString(pattern, s)
	if err != nil {
		panic(err)
	}
	if matched {
		panic(s)
	}
}

func TestGetInfo(*testing.T) {

	database, err := db.InitDatabase(dbPath)
	if err != nil {
		panic(err)
	}

	defer database.Close()

	startTime := time.Now().Add(time.Second)
	endTime := startTime.Add(time.Second)

	game, err := game.NewGame(database, startTime, endTime)
	if err != nil {
		panic(err)
	}

	gameShim = &game

	info := getInfo()

	testMatch(contestNotStarted, info)

	time.Sleep(time.Second)

	info = getInfo()

	testMatch(contestRunning, info)

	time.Sleep(time.Second)

	info = getInfo()

	testMatch(contestCompleted, info)
}

func matchBody(url, pattern string) {

	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	testMatch(pattern, string(body))
}

func addTestData(database *sql.DB, nteams, ncategories, ntasks int,
	validFlag string) (err error) {

	for i := 0; i < nteams; i++ {

		team := db.Team{255, fmt.Sprintf("team%d", i),
			"e", "d", "l", false}

		err = db.AddTeam(database, &team)
		if err != nil {
			panic(err)
		}
	}

	for i := 0; i < ncategories; i++ {

		category := db.Category{Name: fmt.Sprintf("category%d", i)}

		err = db.AddCategory(database, &category)
		if err != nil {
			return
		}

		for i := 0; i < ntasks; i++ {

			task := db.Task{
				Name:          fmt.Sprintf("task%d", i),
				Flag:          validFlag,
				CategoryID:    category.ID,
				Price:         500,
				MaxSharePrice: 500,
				MinSharePrice: 100,
				Shared:        true,
				Opened:        false,
			}

			err = db.AddTask(database, &task)
			if err != nil {
				return
			}
		}
	}

	return
}

func checkAvailability(database *sql.DB, scoreboardURL, originURL,
	infoURL string) (err error) {

	var msg = make([]byte, 4096)

	ws, err := websocket.Dial(scoreboardURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch("Team", string(msg))

	ws.Close()

	ws, err = websocket.Dial(infoURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch(contestRunning, string(msg))

	ws.Close()

	return
}

func solveTasks(game *game.Game, validFlag string, start, end int) (err error) {
	var solved bool
	for i := start; i < end; i++ {
		solved, err = game.Solve(i, i, validFlag)
		if err != nil {
			return
		}
		if !solved {
			err = errors.New("solve task failed")
			return
		}
		time.Sleep(time.Second)
	}
	return
}

func checkScoreboard(database *sql.DB, game *game.Game, addr, validFlag string,
	nteams, ncategories, ntasks int) (err error) {

	originURL := "http://localhost/"

	authEnabled = false

	// Invalid id => get the fu^W scoreboard
	matchBody("http://"+addr+"/task?id=kekeke", "Scoreboard")

	matchBody("http://"+addr+"/", "DOCTYPE html")

	matchBody("http://"+addr+"/tasks.html", "DOCTYPE html")

	// Scoreboard wwwPath is "" => must be not found
	matchBody("http://"+addr+"/news.html", "not found")

	infoURL := "ws://" + addr + "/info"

	ws, err := websocket.Dial(infoURL, "", originURL)
	if err != nil {
		panic(err)
	}

	var msg = make([]byte, 4096)
	if _, err = ws.Read(msg); err != nil {
		panic(err)
	}

	testMatch(contestRunning, string(msg))

	ws.Close()

	time.Sleep(3 * time.Second)

	scoreboardURL := "ws://" + addr + "/scoreboard"

	ws, err = websocket.Dial(scoreboardURL, "", originURL)
	if err != nil {
		panic(err)
	}

	if _, err = ws.Read(msg); err != nil {
		panic(err)
	}

	testMatch("Team", string(msg))

	for i := 0; i < nteams; i++ {
		testMatch(fmt.Sprintf("team%d", i), string(msg))
	}

	solved, err := game.Solve(1, 1, validFlag)
	if err != nil {
		panic(err)
	}
	if !solved {
		panic("solve task failed")
	}

	time.Sleep(time.Second)

	if _, err = ws.Read(msg); err != nil {
		panic(err)
	}

	testMatch("Team", string(msg))

	for i := 1; i < nteams; i++ {
		testMatch(fmt.Sprintf("team%d	0	", i),
			string(msg))
	}

	testMatch("1	team0	500	", string(msg))

	ws.Close()

	err = solveTasks(game, validFlag, 10, 15)
	if err != nil {
		panic(err)
	}

	// tasks page
	tasksInfoURL := "ws://" + addr + "/tasks"

	ws, err = websocket.Dial(tasksInfoURL, "", originURL)
	if err != nil {
		panic(err)
	}

	if _, err = ws.Read(msg); err != nil {
		panic(err)
	}

	testMatch("category", string(msg))

	ws.Close()

	tasksURL := "ws://" + addr + "/info"

	ws, err = websocket.Dial(tasksURL, "", originURL)
	if err != nil {
		panic(err)
	}

	if _, err = ws.Read(msg); err != nil {
		panic(err)
	}

	testMatch(contestRunning, string(msg))

	ws.Close()

	// Check availability after close database

	database.Close()

	time.Sleep(time.Second * 2)

	err = checkAvailability(database, scoreboardURL, originURL, infoURL)
	if err != nil {
		panic(err)
	}

	return
}

func TestScoreboard(*testing.T) {

	database, err := db.InitDatabase(dbPath)
	if err != nil {
		panic(err)
	}

	defer database.Close()

	validFlag := "testflag"

	nteams := 20
	ncategories := 5
	ntasks := 5

	err = addTestData(database, nteams, ncategories, ntasks, validFlag)
	if err != nil {
		panic(err)
	}

	start := time.Now()
	end := start.Add(time.Hour)

	game, err := game.NewGame(database, start, end)
	if err != nil {
		panic(err)
	}

	addr := "localhost:8080"

	err = game.Run()
	if err != nil {
		panic(err)
	}

	go func() {
		err = Scoreboard(database, &game, "", addr)
		if err != nil {
			panic(err)
		}
	}()

	time.Sleep(time.Second) // wait for start listening

	err = checkScoreboard(database, &game,
		addr, validFlag, nteams, ncategories, ntasks)
	if err != nil {
		panic(err)
	}
}
<commit_msg>Move check tasks page out of TestScoreboard<commit_after>/**
 * @file scoreboard_test.go
 * @author Mikhail Klementyev jollheef<AT>riseup.net
 * @license GNU AGPLv3
 * @date November, 2015
 * @brief test scoreboard
 */

package scoreboard

import (
	"database/sql"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"testing"
	"time"

	"github.com/jollheef/henhouse/db"
	"github.com/jollheef/henhouse/game"
	"golang.org/x/net/websocket"
)

const dbPath = "user=postgres dbname=henhouse_test sslmode=disable"

func TestDurationToHMS(*testing.T) {

	t := time.Hour + time.Minute + time.Second
	real := "01:01:01"
	result := durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = time.Hour + time.Minute + time.Second + 100*time.Nanosecond
	real = "01:01:01"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 23*time.Hour + 13*time.Minute + 100*time.Nanosecond
	real = "23:13:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 0
	real = "00:00:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 15*time.Second + 100*time.Nanosecond
	real = "00:00:15"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 15 * time.Hour
	real = "15:00:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}

	t = 15 * time.Minute
	real = "00:15:00"
	result = durationToHMS(t)
	if result != real {
		panic(fmt.Sprintf("Invalid result: %s instead %s", result, real))
	}
}

func testMatch(pattern, s string) {
	matched, err := regexp.MatchString(pattern, s)
	if err != nil {
		panic(err)
	}
	if !matched {
		panic(s)
	}
}

func testNotMatch(pattern, s string) {
	matched, err := regexp.MatchString(pattern, s)
	if err != nil {
		panic(err)
	}
	if matched {
		panic(s)
	}
}

func TestGetInfo(*testing.T) {

	database, err := db.InitDatabase(dbPath)
	if err != nil {
		panic(err)
	}

	defer database.Close()

	startTime := time.Now().Add(time.Second)
	endTime := startTime.Add(time.Second)

	game, err := game.NewGame(database, startTime, endTime)
	if err != nil {
		panic(err)
	}

	gameShim = &game

	info := getInfo()

	testMatch(contestNotStarted, info)

	time.Sleep(time.Second)

	info = getInfo()

	testMatch(contestRunning, info)

	time.Sleep(time.Second)

	info = getInfo()

	testMatch(contestCompleted, info)
}

func matchBody(url, pattern string) {

	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	testMatch(pattern, string(body))
}

func addTestData(database *sql.DB, nteams, ncategories, ntasks int,
	validFlag string) (err error) {

	for i := 0; i < nteams; i++ {

		team := db.Team{255, fmt.Sprintf("team%d", i),
			"e", "d", "l", false}

		err = db.AddTeam(database, &team)
		if err != nil {
			panic(err)
		}
	}

	for i := 0; i < ncategories; i++ {

		category := db.Category{Name: fmt.Sprintf("category%d", i)}

		err = db.AddCategory(database, &category)
		if err != nil {
			return
		}

		for i := 0; i < ntasks; i++ {

			task := db.Task{
				Name:          fmt.Sprintf("task%d", i),
				Flag:          validFlag,
				CategoryID:    category.ID,
				Price:         500,
				MaxSharePrice: 500,
				MinSharePrice: 100,
				Shared:        true,
				Opened:        false,
			}

			err = db.AddTask(database, &task)
			if err != nil {
				return
			}
		}
	}

	return
}

func checkAvailability(database *sql.DB, scoreboardURL, originURL,
	infoURL string) (err error) {

	var msg = make([]byte, 4096)

	ws, err := websocket.Dial(scoreboardURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch("Team", string(msg))

	ws.Close()

	ws, err = websocket.Dial(infoURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch(contestRunning, string(msg))

	ws.Close()

	return
}

func solveTasks(game *game.Game, validFlag string, start, end int) (err error) {
	var solved bool
	for i := start; i < end; i++ {
		solved, err = game.Solve(i, i, validFlag)
		if err != nil {
			return
		}
		if !solved {
			err = errors.New("solve task failed")
			return
		}
		time.Sleep(time.Second)
	}
	return
}

func checkTasksPage(addr, originURL string) (err error) {
	var msg = make([]byte, 4096)

	tasksInfoURL := "ws://" + addr + "/tasks"

	ws, err := websocket.Dial(tasksInfoURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch("category", string(msg))

	ws.Close()

	tasksURL := "ws://" + addr + "/info"

	ws, err = websocket.Dial(tasksURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch(contestRunning, string(msg))

	ws.Close()

	return
}

func checkScoreboard(database *sql.DB, game *game.Game, addr, validFlag string,
	nteams, ncategories, ntasks int) (err error) {

	originURL := "http://localhost/"

	authEnabled = false

	// Invalid id => get the fu^W scoreboard
	matchBody("http://"+addr+"/task?id=kekeke", "Scoreboard")

	matchBody("http://"+addr+"/", "DOCTYPE html")

	matchBody("http://"+addr+"/tasks.html", "DOCTYPE html")

	// Scoreboard wwwPath is "" => must be not found
	matchBody("http://"+addr+"/news.html", "not found")

	infoURL := "ws://" + addr + "/info"

	ws, err := websocket.Dial(infoURL, "", originURL)
	if err != nil {
		return
	}

	var msg = make([]byte, 4096)
	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch(contestRunning, string(msg))

	ws.Close()

	time.Sleep(3 * time.Second)

	scoreboardURL := "ws://" + addr + "/scoreboard"

	ws, err = websocket.Dial(scoreboardURL, "", originURL)
	if err != nil {
		return
	}

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch("Team", string(msg))

	for i := 0; i < nteams; i++ {
		testMatch(fmt.Sprintf("team%d", i), string(msg))
	}

	solved, err := game.Solve(1, 1, validFlag)
	if err != nil {
		return
	}
	if !solved {
		errors.New("solve task failed")
	}

	time.Sleep(time.Second)

	if _, err = ws.Read(msg); err != nil {
		return
	}

	testMatch("Team", string(msg))

	for i := 1; i < nteams; i++ {
		testMatch(fmt.Sprintf("team%d	0	", i),
			string(msg))
	}

	testMatch("1	team0	500	", string(msg))

	ws.Close()

	err = solveTasks(game, validFlag, 10, 15)
	if err != nil {
		return
	}

	// Chech tasks page
	err = checkTasksPage(addr, originURL)
	if err != nil {
		return
	}

	// Check availability after close database

	database.Close()

	time.Sleep(time.Second * 2)

	err = checkAvailability(database, scoreboardURL, originURL, infoURL)
	if err != nil {
		return
	}

	return
}

func TestScoreboard(*testing.T) {

	database, err := db.InitDatabase(dbPath)
	if err != nil {
		panic(err)
	}

	defer database.Close()

	validFlag := "testflag"

	nteams := 20
	ncategories := 5
	ntasks := 5

	err = addTestData(database, nteams, ncategories, ntasks, validFlag)
	if err != nil {
		panic(err)
	}

	start := time.Now()
	end := start.Add(time.Hour)

	game, err := game.NewGame(database, start, end)
	if err != nil {
		panic(err)
	}

	addr := "localhost:8080"

	err = game.Run()
	if err != nil {
		panic(err)
	}

	go func() {
		err = Scoreboard(database, &game, "", addr)
		if err != nil {
			panic(err)
		}
	}()

	time.Sleep(time.Second) // wait for start listening

	err = checkScoreboard(database, &game,
		addr, validFlag, nteams, ncategories, ntasks)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>