<commit_before>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package filters

import (
	"regexp"
	"time"

	"github.com/pmylund/go-cache"

	"github.com/skydive-project/skydive/common"
)

var regexpCache *cache.Cache

// Eval evaluates a filter
func (f *Filter) Eval(g common.Getter) bool {
	if f.BoolFilter != nil {
		return f.BoolFilter.Eval(g)
	}
	if f.TermStringFilter != nil {
		return f.TermStringFilter.Eval(g)
	}
	if f.TermInt64Filter != nil {
		return f.TermInt64Filter.Eval(g)
	}
	if f.TermBoolFilter != nil {
		return f.TermBoolFilter.Eval(g)
	}
	if f.GtInt64Filter != nil {
		return f.GtInt64Filter.Eval(g)
	}
	if f.LtInt64Filter != nil {
		return f.LtInt64Filter.Eval(g)
	}
	if f.GteInt64Filter != nil {
		return f.GteInt64Filter.Eval(g)
	}
	if f.LteInt64Filter != nil {
		return f.LteInt64Filter.Eval(g)
	}
	if f.RegexFilter != nil {
		return f.RegexFilter.Eval(g)
	}
	if f.NullFilter != nil {
		return f.NullFilter.Eval(g)
	}
	if f.IPV4RangeFilter != nil {
		return f.IPV4RangeFilter.Eval(g)
	}

	return true
}

// Eval evaluates a boolean (not, and, or) filter
func (b *BoolFilter) Eval(g common.Getter) bool {
	for _, filter := range b.Filters {
		result := filter.Eval(g)
		if b.Op == BoolFilterOp_NOT && !result {
			return true
		}
		if b.Op == BoolFilterOp_AND && !result {
			return false
		} else if b.Op == BoolFilterOp_OR && result {
			return true
		}
	}
	return b.Op == BoolFilterOp_AND || len(b.Filters) == 0
}

// Eval evaluates an int64 > filter
func (r *GtInt64Filter) Eval(g common.Getter) bool {
	field, err := g.GetFieldInt64(r.Key)
	if err != nil {
		return false
	}

	if field > r.Value {
		return true
	}
	return false
}

// Eval evaluates an int64 < filter
func (r *LtInt64Filter) Eval(g common.Getter) bool {
	field, err := g.GetFieldInt64(r.Key)
	if err != nil {
		return false
	}

	if field < r.Value {
		return true
	}
	return false
}

// Eval evaluates an int64 >= filter
func (r *GteInt64Filter) Eval(g common.Getter) bool {
	field, err := g.GetFieldInt64(r.Key)
	if err != nil {
		return false
	}

	if field >= r.Value {
		return true
	}
	return false
}

// Eval evaluates an int64 <= filter
func (r *LteInt64Filter) Eval(g common.Getter) bool {
	field, err := g.GetFieldInt64(r.Key)
	if err != nil {
		return false
	}

	if field <= r.Value {
		return true
	}
	return false
}

// Eval evaluates an string type filter
func (t *TermStringFilter) Eval(g common.Getter) bool {
	field, err := g.GetField(t.Key)
	if err != nil {
		return false
	}
	switch field := field.(type) {
	case []interface{}:
		for _, intf := range field {
			if s, ok := intf.(string); ok && s == t.Value {
				return true
			}
		}
	case []string:
		for _, s := range field {
			if s == t.Value {
				return true
			}
		}
	case string:
		if field == t.Value {
			return true
		}
	}
	return false
}

// Eval evaluates an int64 type filter
func (t *TermInt64Filter) Eval(g common.Getter) bool {
	field, err := g.GetField(t.Key)
	if err != nil {
		return false
	}
	switch field := field.(type) {
	case []interface{}:
		for _, intf := range field {
			if v, err := common.ToInt64(intf); err == nil && v == t.Value {
				return true
			}
		}
	case []int64:
		for _, v := range field {
			if v == t.Value {
				return true
			}
		}
	case int64:
		if field == t.Value {
			return true
		}
	default:
		if v, err := common.ToInt64(field); err == nil && v == t.Value {
			return true
		}
	}
	return false
}

// Eval evaluates a bool type filter
func (t *TermBoolFilter) Eval(g common.Getter) bool {
	field, err := g.GetField(t.Key)
	if err != nil {
		return false
	}
	switch field := field.(type) {
	case []interface{}:
		for _, intf := range field {
			if b, ok := intf.(bool); ok && b == t.Value {
				return true
			}
		}
	case []bool:
		for _, v := range field {
			if v == t.Value {
				return true
			}
		}
	case bool:
		if field == t.Value {
			return true
		}
	}
	return false
}

// Eval evaluates an regex filter
func (r *RegexFilter) Eval(g common.Getter) bool {
	field, err := g.GetField(r.Key)
	if err != nil {
		return false
	}

	re, found := regexpCache.Get(r.Value)
	if !found {
		re = regexp.MustCompile(r.Value)
		regexpCache.Set(r.Value, re, cache.DefaultExpiration)
	}

	switch field := field.(type) {
	case []interface{}:
		for _, intf := range field {
			if s, ok := intf.(string); ok && re.(*regexp.Regexp).MatchString(s) {
				return true
			}
		}
	case []string:
		for _, s := range field {
			if re.(*regexp.Regexp).MatchString(s) {
				return true
			}
		}
	case string:
		return re.(*regexp.Regexp).MatchString(field)
	}

	return false
}

// NewRegexFilter returns a new regular expression based filter
func NewRegexFilter(key string, pattern string) (*RegexFilter, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return nil, err
	}
	regexpCache.Set(pattern, re, cache.DefaultExpiration)

	return &RegexFilter{Key: key, Value: pattern}, nil
}

// Eval evaluates an null filter (not string and not int64 types)
func (n *NullFilter) Eval(g common.Getter) bool {
	if _, err := g.GetField(n.Key); err == nil {
		return false
	}
	return true
}

// Eval evaluates an ipv4 range filter
func (r *IPV4RangeFilter) Eval(g common.Getter) bool {
	field, err := g.GetField(r.Key)
	if err != nil {
		return false
	}

	re, found := regexpCache.Get(r.Value)
	if !found {
		// ignore error at this point should have been check in the contructor
		regex, _ := common.IPV4CIDRToRegex(r.Value)
		re = regexp.MustCompile(regex)
		regexpCache.Set(r.Value, re, cache.DefaultExpiration)
	}

	switch field := field.(type) {
	case []interface{}:
		for _, intf := range field {
			if s, ok := intf.(string); ok && re.(*regexp.Regexp).MatchString(s) {
				return true
			}
		}
	case []string:
		for _, s := range field {
			if re.(*regexp.Regexp).MatchString(s) {
				return true
			}
		}
	case string:
		return re.(*regexp.Regexp).MatchString(field)
	}

	return false
}

// NewIPV4RangeFilter creates a regex based filter corresponding to the ip range
func NewIPV4RangeFilter(key, cidr string) (*IPV4RangeFilter, error) {
	regex, err := common.IPV4CIDRToRegex(cidr)
	if err != nil {
		return nil, err
	}
	re, err := regexp.Compile(regex)
	if err != nil {
		return nil, err
	}
	regexpCache.Set(cidr, re, cache.DefaultExpiration)

	return &IPV4RangeFilter{Key: key, Value: cidr}, nil
}

// NewBoolFilter creates a new boolean filter
func NewBoolFilter(op BoolFilterOp, filters ...*Filter) *Filter {
	boolFilter := &BoolFilter{
		Op:      op,
		Filters: []*Filter{},
	}

	for _, filter := range filters {
		if filter != nil {
			boolFilter.Filters = append(boolFilter.Filters, filter)
		}
	}

	return &Filter{BoolFilter: boolFilter}
}

// NewAndFilter creates a new boolean And filter
func NewAndFilter(filters ...*Filter) *Filter {
	return NewBoolFilter(BoolFilterOp_AND, filters...)
}

// NewOrFilter creates a new boolean Or filter
func NewOrFilter(filters ...*Filter) *Filter {
	return NewBoolFilter(BoolFilterOp_OR, filters...)
}

// NewNotFilter creates a new boolean Not filter
func NewNotFilter(filter *Filter) *Filter {
	return NewBoolFilter(BoolFilterOp_NOT, filter)
}

// NewGtInt64Filter creates a new > filter
func NewGtInt64Filter(key string, value int64) *Filter {
	return &Filter{GtInt64Filter: &GtInt64Filter{Key: key, Value: value}}
}

// NewGteInt64Filter creates a new >= filter
func NewGteInt64Filter(key string, value int64) *Filter {
	return &Filter{GteInt64Filter: &GteInt64Filter{Key: key, Value: value}}
}

// NewLtInt64Filter creates a new < filter
func NewLtInt64Filter(key string, value int64) *Filter {
	return &Filter{LtInt64Filter: &LtInt64Filter{Key: key, Value: value}}
}

// NewLteInt64Filter creates a new <= filter
func NewLteInt64Filter(key string, value int64) *Filter {
	return &Filter{LteInt64Filter: &LteInt64Filter{Key: key, Value: value}}
}

// NewTermInt64Filter creates a new string iny64 filter
func NewTermInt64Filter(key string, value int64) *Filter {
	return &Filter{TermInt64Filter: &TermInt64Filter{Key: key, Value: value}}
}

// NewTermStringFilter creates a new string filter
func NewTermStringFilter(key string, value string) *Filter {
	return &Filter{TermStringFilter: &TermStringFilter{Key: key, Value: value}}
}

// NewTermBoolFilter creates a new bool filter
func NewTermBoolFilter(key string, value bool) *Filter {
	return &Filter{TermBoolFilter: &TermBoolFilter{Key: key, Value: value}}
}

// NewNullFilter creates a new null filter
func NewNullFilter(key string) *Filter {
	return &Filter{NullFilter: &NullFilter{Key: key}}
}

// NewOrTermStringFilter creates a new "or" filter based on values and attributes
func NewOrTermStringFilter(values []string, attrs ...string) *Filter {
	terms := make([]*Filter, len(values)*len(attrs))
	for i, value := range values {
		for j, attr := range attrs {
			terms[i*len(attrs)+j] = NewTermStringFilter(attr, value)
		}
	}
	return NewOrFilter(terms...)
}

// NewFilterActiveIn returns a filter that returns elements that were active
// in the given time range.
func NewFilterActiveIn(fr Range, prefix string) *Filter {
	return NewAndFilter(
		NewLteInt64Filter(prefix+"Start", fr.To),
		NewGteInt64Filter(prefix+"Last", fr.From),
	)
}

// NewFilterIncludedIn returns a filter that returns elements that include in
// the time range.
func NewFilterIncludedIn(fr Range, prefix string) *Filter {
	return NewAndFilter(
		NewGteInt64Filter(prefix+"Start", fr.From),
		NewLteInt64Filter(prefix+"Last", fr.To),
	)
}

// NewNotNullFilter returns a filter that returns elements with a field set.
func NewNotNullFilter(key string) *Filter {
	return NewNotFilter(NewNullFilter(key))
}

func init() {
	regexpCache = cache.New(5*time.Minute, 10*time.Minute)
}
<commit_msg>filters: support comparing in lists and refactoring<commit_after>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package filters

import (
	"regexp"
	"time"

	"github.com/pmylund/go-cache"

	"github.com/skydive-project/skydive/common"
)

var regexpCache *cache.Cache

func evalStringField(g common.Getter, key string, predicate func(string) bool) bool {
	if s, err := common.GetFieldString(g, key); err == nil {
		return predicate(s)
	}

	if strings, err := common.GetFieldStringList(g, key); err == nil {
		for _, s := range strings {
			if predicate(s) {
				return true
			}
		}
	}

	return false
}

func evalInt64Field(g common.Getter, key string, predicate func(int64) bool) bool {
	if s, err := common.GetFieldInt64(g, key); err == nil {
		return predicate(s)
	}

	if integers, err := common.GetFieldInt64List(g, key); err == nil {
		for _, i := range integers {
			if predicate(i) {
				return true
			}
		}
	}

	return false
}

// Eval evaluates a filter
func (f *Filter) Eval(g common.Getter) bool {
	if f.BoolFilter != nil {
		return f.BoolFilter.Eval(g)
	}
	if f.TermStringFilter != nil {
		return f.TermStringFilter.Eval(g)
	}
	if f.TermInt64Filter != nil {
		return f.TermInt64Filter.Eval(g)
	}
	if f.TermBoolFilter != nil {
		return f.TermBoolFilter.Eval(g)
	}
	if f.GtInt64Filter != nil {
		return f.GtInt64Filter.Eval(g)
	}
	if f.LtInt64Filter != nil {
		return f.LtInt64Filter.Eval(g)
	}
	if f.GteInt64Filter != nil {
		return f.GteInt64Filter.Eval(g)
	}
	if f.LteInt64Filter != nil {
		return f.LteInt64Filter.Eval(g)
	}
	if f.RegexFilter != nil {
		return f.RegexFilter.Eval(g)
	}
	if f.NullFilter != nil {
		return f.NullFilter.Eval(g)
	}
	if f.IPV4RangeFilter != nil {
		return f.IPV4RangeFilter.Eval(g)
	}

	return true
}

// Eval evaluates a boolean (not, and, or) filter
func (b *BoolFilter) Eval(g common.Getter) bool {
	for _, filter := range b.Filters {
		result := filter.Eval(g)
		if b.Op == BoolFilterOp_NOT && !result {
			return true
		}
		if b.Op == BoolFilterOp_AND && !result {
			return false
		} else if b.Op == BoolFilterOp_OR && result {
			return true
		}
	}
	return b.Op == BoolFilterOp_AND || len(b.Filters) == 0
}

// Eval evaluates an int64 > filter
func (r *GtInt64Filter) Eval(g common.Getter) bool {
	return evalInt64Field(g, r.Key, func(i int64) bool {
		return i > r.Value
	})
}

// Eval evaluates an int64 < filter
func (r *LtInt64Filter) Eval(g common.Getter) bool {
	return evalInt64Field(g, r.Key, func(i int64) bool {
		return i < r.Value
	})
}

// Eval evaluates an int64 >= filter
func (r *GteInt64Filter) Eval(g common.Getter) bool {
	return evalInt64Field(g, r.Key, func(i int64) bool {
		return i >= r.Value
	})
}

// Eval evaluates an int64 <= filter
func (r *LteInt64Filter) Eval(g common.Getter) bool {
	return evalInt64Field(g, r.Key, func(i int64) bool {
		return i <= r.Value
	})
}

// Eval evaluates an string type filter
func (t *TermStringFilter) Eval(g common.Getter) bool {
	return evalStringField(g, t.Key, func(s string) bool {
		return s == t.Value
	})
}

// Eval evaluates an int64 type filter
func (t *TermInt64Filter) Eval(g common.Getter) bool {
	return evalInt64Field(g, t.Key, func(i int64) bool {
		return i == t.Value
	})
}

// Eval evaluates a bool type filter
func (t *TermBoolFilter) Eval(g common.Getter) bool {
	field, err := common.GetField(g, t.Key)
	if err != nil {
		return false
	}
	switch field := field.(type) {
	case []interface{}:
		for _, intf := range field {
			if b, ok := intf.(bool); ok && b == t.Value {
				return true
			}
		}
	case []bool:
		for _, v := range field {
			if v == t.Value {
				return true
			}
		}
	case bool:
		if field == t.Value {
			return true
		}
	}
	return false
}

// Eval evaluates an regex filter
func (r *RegexFilter) Eval(g common.Getter) bool {
	re, found := regexpCache.Get(r.Value)
	if !found {
		re = regexp.MustCompile(r.Value)
		regexpCache.Set(r.Value, re, cache.DefaultExpiration)
	}

	return evalStringField(g, r.Key, func(s string) bool {
		return re.(*regexp.Regexp).MatchString(s)
	})
}

// NewRegexFilter returns a new regular expression based filter
func NewRegexFilter(key string, pattern string) (*RegexFilter, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return nil, err
	}
	regexpCache.Set(pattern, re, cache.DefaultExpiration)

	return &RegexFilter{Key: key, Value: pattern}, nil
}

// Eval evaluates an null filter (not string and not int64 types)
func (n *NullFilter) Eval(g common.Getter) bool {
	if _, err := common.GetField(g, n.Key); err == nil {
		return false
	}
	return true
}

// Eval evaluates an ipv4 range filter
func (r *IPV4RangeFilter) Eval(g common.Getter) bool {
	re, found := regexpCache.Get(r.Value)
	if !found {
		// ignore error at this point should have been check in the contructor
		regex, _ := common.IPV4CIDRToRegex(r.Value)
		re = regexp.MustCompile(regex)
		regexpCache.Set(r.Value, re, cache.DefaultExpiration)
	}

	return evalStringField(g, r.Key, func(s string) bool {
		return re.(*regexp.Regexp).MatchString(s)
	})
}

// NewIPV4RangeFilter creates a regex based filter corresponding to the ip range
func NewIPV4RangeFilter(key, cidr string) (*IPV4RangeFilter, error) {
	regex, err := common.IPV4CIDRToRegex(cidr)
	if err != nil {
		return nil, err
	}
	re, err := regexp.Compile(regex)
	if err != nil {
		return nil, err
	}
	regexpCache.Set(cidr, re, cache.DefaultExpiration)

	return &IPV4RangeFilter{Key: key, Value: cidr}, nil
}

// NewBoolFilter creates a new boolean filter
func NewBoolFilter(op BoolFilterOp, filters ...*Filter) *Filter {
	boolFilter := &BoolFilter{
		Op:      op,
		Filters: []*Filter{},
	}

	for _, filter := range filters {
		if filter != nil {
			boolFilter.Filters = append(boolFilter.Filters, filter)
		}
	}

	return &Filter{BoolFilter: boolFilter}
}

// NewAndFilter creates a new boolean And filter
func NewAndFilter(filters ...*Filter) *Filter {
	return NewBoolFilter(BoolFilterOp_AND, filters...)
}

// NewOrFilter creates a new boolean Or filter
func NewOrFilter(filters ...*Filter) *Filter {
	return NewBoolFilter(BoolFilterOp_OR, filters...)
}

// NewNotFilter creates a new boolean Not filter
func NewNotFilter(filter *Filter) *Filter {
	return NewBoolFilter(BoolFilterOp_NOT, filter)
}

// NewGtInt64Filter creates a new > filter
func NewGtInt64Filter(key string, value int64) *Filter {
	return &Filter{GtInt64Filter: &GtInt64Filter{Key: key, Value: value}}
}

// NewGteInt64Filter creates a new >= filter
func NewGteInt64Filter(key string, value int64) *Filter {
	return &Filter{GteInt64Filter: &GteInt64Filter{Key: key, Value: value}}
}

// NewLtInt64Filter creates a new < filter
func NewLtInt64Filter(key string, value int64) *Filter {
	return &Filter{LtInt64Filter: &LtInt64Filter{Key: key, Value: value}}
}

// NewLteInt64Filter creates a new <= filter
func NewLteInt64Filter(key string, value int64) *Filter {
	return &Filter{LteInt64Filter: &LteInt64Filter{Key: key, Value: value}}
}

// NewTermInt64Filter creates a new string iny64 filter
func NewTermInt64Filter(key string, value int64) *Filter {
	return &Filter{TermInt64Filter: &TermInt64Filter{Key: key, Value: value}}
}

// NewTermStringFilter creates a new string filter
func NewTermStringFilter(key string, value string) *Filter {
	return &Filter{TermStringFilter: &TermStringFilter{Key: key, Value: value}}
}

// NewTermBoolFilter creates a new bool filter
func NewTermBoolFilter(key string, value bool) *Filter {
	return &Filter{TermBoolFilter: &TermBoolFilter{Key: key, Value: value}}
}

// NewNullFilter creates a new null filter
func NewNullFilter(key string) *Filter {
	return &Filter{NullFilter: &NullFilter{Key: key}}
}

// NewOrTermStringFilter creates a new "or" filter based on values and attributes
func NewOrTermStringFilter(values []string, attrs ...string) *Filter {
	terms := make([]*Filter, len(values)*len(attrs))
	for i, value := range values {
		for j, attr := range attrs {
			terms[i*len(attrs)+j] = NewTermStringFilter(attr, value)
		}
	}
	return NewOrFilter(terms...)
}

// NewFilterActiveIn returns a filter that returns elements that were active
// in the given time range.
func NewFilterActiveIn(fr Range, prefix string) *Filter {
	return NewAndFilter(
		NewLteInt64Filter(prefix+"Start", fr.To),
		NewGteInt64Filter(prefix+"Last", fr.From),
	)
}

// NewFilterIncludedIn returns a filter that returns elements that include in
// the time range.
func NewFilterIncludedIn(fr Range, prefix string) *Filter {
	return NewAndFilter(
		NewGteInt64Filter(prefix+"Start", fr.From),
		NewLteInt64Filter(prefix+"Last", fr.To),
	)
}

// NewNotNullFilter returns a filter that returns elements with a field set.
func NewNotNullFilter(key string) *Filter {
	return NewNotFilter(NewNullFilter(key))
}

func init() {
	regexpCache = cache.New(5*time.Minute, 10*time.Minute)
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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
//
// 	https://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 csp provides a safehttp.Interceptor which applies Content-Security Policies
// to responses.
//
// Three default policies are provided:
//  - A strict nonce based CSP
//  - A framing policy which sets frame-ancestors to 'self'
//  - A Trusted Types policy which makes usage of dangerous web API functions secure by default
package csp

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"strings"

	"github.com/google/go-safeweb/safehttp/plugins/htmlinject"

	"github.com/google/go-safeweb/safehttp"
)

var randReader = rand.Reader

// nonceSize is the size of the nonces in bytes. According to the CSP3 spec it should
// be larger than 16 bytes. 20 bytes was picked to be future proof.
// https://www.w3.org/TR/CSP3/#security-nonces
const nonceSize = 20

func generateNonce() string {
	b := make([]byte, nonceSize)
	_, err := randReader.Read(b)
	if err != nil {
		panic(fmt.Errorf("failed to generate entropy using crypto/rand/RandReader: %v", err))
	}
	return base64.StdEncoding.EncodeToString(b)
}

// Policy defines a CSP policy.
type Policy interface {
	// Serialize serializes this policy for use in a Content-Security-Policy header
	// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
	// to serialize which can be used in 'nonce-{random-nonce}' values in directives.
	Serialize(nonce string) string
}

type ctxKey struct{}

// Nonce retrieves the nonce from the given context. If there is no nonce stored
// in the context, an error will be returned.
func Nonce(ctx context.Context) (string, error) {
	v := ctx.Value(ctxKey{})
	if v == nil {
		return "", errors.New("no nonce in context")
	}
	return v.(string), nil
}

// StrictPolicy can be used to build a strict, nonce-based CSP.
//
// See https://csp.withgoogle.com/docs/strict-csp.html for more info.
type StrictPolicy struct {
	// NoStrictDynamic controls whether script-src should contain the 'strict-dynamic'
	// value.
	//
	// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic
	// for more info.
	NoStrictDynamic bool
	// UnsafeEval controls whether script-src should contain the 'unsafe-eval' value.
	// If enabled, the eval() JavaScript function is allowed.
	UnsafeEval bool
	// BaseURI controls the base-uri directive. If BaseURI is an empty string the
	// directive will be set to 'none'. The base-uri directive restricts the URLs
	// which can be used in a document's <base> element.
	//
	// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/base-uri
	// for more info.
	BaseURI string
	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
	// directive will be set.
	ReportURI string
	// Hashes adds a set of hashes to script-src. An example of a hash would be:
	//  sha256-CihokcEcBW4atb/CW/XWsvWwbTjqwQlE9nj9ii5ww5M=
	// which is the SHA256 hash for the script "console.log(1)".
	//
	// For more info, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
	Hashes []string
}

// Serialize serializes this policy for use in a Content-Security-Policy header
// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
func (s StrictPolicy) Serialize(nonce string) string {
	var b strings.Builder

	// object-src 'none'; script-src 'unsafe-inline' 'nonce-{random}'
	b.WriteString("object-src 'none'; script-src 'unsafe-inline' 'nonce-")
	b.WriteString(nonce)
	b.WriteByte('\'')

	if !s.NoStrictDynamic {
		b.WriteString(" 'strict-dynamic' https: http:")
	}

	if s.UnsafeEval {
		b.WriteString(" 'unsafe-eval'")
	}

	for _, h := range s.Hashes {
		b.WriteString(" '")
		b.WriteString(h)
		b.WriteByte('\'')
	}

	b.WriteString("; base-uri ")
	if s.BaseURI == "" {
		b.WriteString("'none'")
	} else {
		b.WriteString(s.BaseURI)
	}

	if s.ReportURI != "" {
		b.WriteString("; report-uri ")
		b.WriteString(s.ReportURI)
	}

	return b.String()

}

// FramingPolicy can be used to create a new CSP policy with frame-ancestors
// set to 'self'.
//
// TODO: allow relaxation on specific endpoints according to #77.
type FramingPolicy struct {
	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
	// directive will be set.
	ReportURI string
}

// Serialize serializes this policy for use in a Content-Security-Policy header
// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
func (f FramingPolicy) Serialize(nonce string) string {
	var b strings.Builder
	b.WriteString("frame-ancestors 'self'")

	if f.ReportURI != "" {
		b.WriteString("; report-uri ")
		b.WriteString(f.ReportURI)
	}

	return b.String()
}

// TrustedTypesPolicy policy can be used to create a new CSP policy which makes
// dangerous web API functions secure by default.
//
// See https://web.dev/trusted-types for more info.
type TrustedTypesPolicy struct {
	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
	// directive will be set.
	ReportURI string
	// PolicyNames controls allowed names of policies that can be created.
	PolicyNames []string
	// AllowDuplicates allows creating policies with a name that was already used.
	AllowDuplicates bool
}

// Serialize serializes this policy for use in a Content-Security-Policy header
// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
func (t TrustedTypesPolicy) Serialize(nonce string) string {
	var b strings.Builder
	b.WriteString("require-trusted-types-for 'script'")

	if len(t.PolicyNames) > 0 {
		b.WriteString("; trusted-types")
		for _, name := range t.PolicyNames {
			b.WriteString(" ")
			b.WriteString(name)
		}

		if t.AllowDuplicates {
			b.WriteString(" ")
			b.WriteString("'allow-duplicates'")
		}
	}

	if t.ReportURI != "" {
		b.WriteString("; report-uri ")
		b.WriteString(t.ReportURI)
	}

	return b.String()
}

// Interceptor intercepts requests and applies CSP policies.
type Interceptor struct {
	// TODO: move it to a safehttp.InterceptorConfig implementation to enable
	// per-handler configuration.

	// Enforce specifies which policies will be set as the Content-Security-Policy
	// header.
	Enforce []Policy
	// ReportOnly specifies which policies will be set as the Content-Security-Policy-Report-Only
	// header.
	ReportOnly []Policy
}

var _ safehttp.Interceptor = Interceptor{}

// Default creates a new CSP interceptor with a strict nonce-based policy, a framing policy
// and a TrustedTypes policy. All in enforcement mode.
func Default(reportURI string) Interceptor {
	return Interceptor{
		Enforce: []Policy{
			StrictPolicy{ReportURI: reportURI},
			FramingPolicy{ReportURI: reportURI},
			TrustedTypesPolicy{ReportURI: reportURI},
		},
	}
}

// Before claims and sets the Content-Security-Policy header and the
// Content-Security-Policy-Report-Only header.
func (it Interceptor) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {
	nonce := generateNonce()
	r.SetContext(context.WithValue(r.Context(), ctxKey{}, nonce))

	var CSPs []string
	for _, p := range it.Enforce {
		CSPs = append(CSPs, p.Serialize(nonce))
	}
	var reportCSPs []string
	for _, p := range it.ReportOnly {
		reportCSPs = append(reportCSPs, p.Serialize(nonce))
	}

	h := w.Header()
	setCSP := h.Claim("Content-Security-Policy")
	setCSPReportOnly := h.Claim("Content-Security-Policy-Report-Only")

	setCSP(CSPs)
	setCSPReportOnly(reportCSPs)

	return safehttp.NotWritten()
}

// Commit adds the nonce to the safehttp.TemplateResponse which is going to be
// injected as the value of the nonce attribute in <script> and <link> tags. The
// nonce is going to be unique for each safehttp.IncomingRequest.
func (it Interceptor) Commit(w safehttp.ResponseHeadersWriter, r *safehttp.IncomingRequest, resp safehttp.Response, cfg safehttp.InterceptorConfig) {
	tmplResp, ok := resp.(*safehttp.TemplateResponse)
	if !ok {
		return
	}

	nonce, err := Nonce(r.Context())
	if err != nil {
		// The nonce should have been added in the Before stage and, if that is
		// not the case, a server misconfiguration occured.
		panic("no CSP nonce")
	}

	if tmplResp.FuncMap == nil {
		tmplResp.FuncMap = map[string]interface{}{}
	}
	tmplResp.FuncMap[htmlinject.CSPNoncesDefaultFuncName] = func() string { return nonce }
}
<commit_msg>Update safehttp/plugins/csp/csp.go<commit_after>// Copyright 2020 Google LLC
//
// 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
//
// 	https://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 csp provides a safehttp.Interceptor which applies Content-Security Policies
// to responses.
//
// Three default policies are provided:
//  - A strict nonce based CSP
//  - A framing policy which sets frame-ancestors to 'self'
//  - A Trusted Types policy which makes usage of dangerous web API functions secure by default
package csp

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"strings"

	"github.com/google/go-safeweb/safehttp/plugins/htmlinject"

	"github.com/google/go-safeweb/safehttp"
)

var randReader = rand.Reader

// nonceSize is the size of the nonces in bytes. According to the CSP3 spec it should
// be larger than 16 bytes. 20 bytes was picked to be future proof.
// https://www.w3.org/TR/CSP3/#security-nonces
const nonceSize = 20

func generateNonce() string {
	b := make([]byte, nonceSize)
	_, err := randReader.Read(b)
	if err != nil {
		panic(fmt.Errorf("failed to generate entropy using crypto/rand/RandReader: %v", err))
	}
	return base64.StdEncoding.EncodeToString(b)
}

// Policy defines a CSP policy.
type Policy interface {
	// Serialize serializes this policy for use in a Content-Security-Policy header
	// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
	// to serialize which can be used in 'nonce-{random-nonce}' values in directives.
	Serialize(nonce string) string
}

type ctxKey struct{}

// Nonce retrieves the nonce from the given context. If there is no nonce stored
// in the context, an error will be returned.
func Nonce(ctx context.Context) (string, error) {
	v := ctx.Value(ctxKey{})
	if v == nil {
		return "", errors.New("no nonce in context")
	}
	return v.(string), nil
}

// StrictPolicy can be used to build a strict, nonce-based CSP.
//
// See https://csp.withgoogle.com/docs/strict-csp.html for more info.
type StrictPolicy struct {
	// NoStrictDynamic controls whether script-src should contain the 'strict-dynamic'
	// value.
	//
	// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic
	// for more info.
	NoStrictDynamic bool
	// UnsafeEval controls whether script-src should contain the 'unsafe-eval' value.
	// If enabled, the eval() JavaScript function is allowed.
	UnsafeEval bool
	// BaseURI controls the base-uri directive. If BaseURI is an empty string the
	// directive will be set to 'none'. The base-uri directive restricts the URLs
	// which can be used in a document's <base> element.
	//
	// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/base-uri
	// for more info.
	BaseURI string
	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
	// directive will be set.
	ReportURI string
	// Hashes adds a set of hashes to script-src. An example of a hash would be:
	//  sha256-CihokcEcBW4atb/CW/XWsvWwbTjqwQlE9nj9ii5ww5M=
	// which is the SHA256 hash for the script "console.log(1)".
	//
	// For more info, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
	Hashes []string
}

// Serialize serializes this policy for use in a Content-Security-Policy header
// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
func (s StrictPolicy) Serialize(nonce string) string {
	var b strings.Builder

	// object-src 'none'; script-src 'unsafe-inline' 'nonce-{random}'
	b.WriteString("object-src 'none'; script-src 'unsafe-inline' 'nonce-")
	b.WriteString(nonce)
	b.WriteByte('\'')

	if !s.NoStrictDynamic {
		b.WriteString(" 'strict-dynamic' https: http:")
	}

	if s.UnsafeEval {
		b.WriteString(" 'unsafe-eval'")
	}

	for _, h := range s.Hashes {
		b.WriteString(" '")
		b.WriteString(h)
		b.WriteByte('\'')
	}

	b.WriteString("; base-uri ")
	if s.BaseURI == "" {
		b.WriteString("'none'")
	} else {
		b.WriteString(s.BaseURI)
	}

	if s.ReportURI != "" {
		b.WriteString("; report-uri ")
		b.WriteString(s.ReportURI)
	}

	return b.String()

}

// FramingPolicy can be used to create a new CSP policy with frame-ancestors
// set to 'self'.
//
// TODO: allow relaxation on specific endpoints according to #77.
type FramingPolicy struct {
	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
	// directive will be set.
	ReportURI string
}

// Serialize serializes this policy for use in a Content-Security-Policy header
// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
func (f FramingPolicy) Serialize(nonce string) string {
	var b strings.Builder
	b.WriteString("frame-ancestors 'self'")

	if f.ReportURI != "" {
		b.WriteString("; report-uri ")
		b.WriteString(f.ReportURI)
	}

	return b.String()
}

// TrustedTypesPolicy policy can be used to create a new CSP which makes
// dangerous web API functions secure by default.
//
// See https://web.dev/trusted-types for more info.
type TrustedTypesPolicy struct {
	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
	// directive will be set.
	ReportURI string
	// PolicyNames controls allowed names of policies that can be created.
	PolicyNames []string
	// AllowDuplicates allows creating policies with a name that was already used.
	AllowDuplicates bool
}

// Serialize serializes this policy for use in a Content-Security-Policy header
// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
func (t TrustedTypesPolicy) Serialize(nonce string) string {
	var b strings.Builder
	b.WriteString("require-trusted-types-for 'script'")

	if len(t.PolicyNames) > 0 {
		b.WriteString("; trusted-types")
		for _, name := range t.PolicyNames {
			b.WriteString(" ")
			b.WriteString(name)
		}

		if t.AllowDuplicates {
			b.WriteString(" ")
			b.WriteString("'allow-duplicates'")
		}
	}

	if t.ReportURI != "" {
		b.WriteString("; report-uri ")
		b.WriteString(t.ReportURI)
	}

	return b.String()
}

// Interceptor intercepts requests and applies CSP policies.
type Interceptor struct {
	// TODO: move it to a safehttp.InterceptorConfig implementation to enable
	// per-handler configuration.

	// Enforce specifies which policies will be set as the Content-Security-Policy
	// header.
	Enforce []Policy
	// ReportOnly specifies which policies will be set as the Content-Security-Policy-Report-Only
	// header.
	ReportOnly []Policy
}

var _ safehttp.Interceptor = Interceptor{}

// Default creates a new CSP interceptor with a strict nonce-based policy, a framing policy
// and a TrustedTypes policy. All in enforcement mode.
func Default(reportURI string) Interceptor {
	return Interceptor{
		Enforce: []Policy{
			StrictPolicy{ReportURI: reportURI},
			FramingPolicy{ReportURI: reportURI},
			TrustedTypesPolicy{ReportURI: reportURI},
		},
	}
}

// Before claims and sets the Content-Security-Policy header and the
// Content-Security-Policy-Report-Only header.
func (it Interceptor) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {
	nonce := generateNonce()
	r.SetContext(context.WithValue(r.Context(), ctxKey{}, nonce))

	var CSPs []string
	for _, p := range it.Enforce {
		CSPs = append(CSPs, p.Serialize(nonce))
	}
	var reportCSPs []string
	for _, p := range it.ReportOnly {
		reportCSPs = append(reportCSPs, p.Serialize(nonce))
	}

	h := w.Header()
	setCSP := h.Claim("Content-Security-Policy")
	setCSPReportOnly := h.Claim("Content-Security-Policy-Report-Only")

	setCSP(CSPs)
	setCSPReportOnly(reportCSPs)

	return safehttp.NotWritten()
}

// Commit adds the nonce to the safehttp.TemplateResponse which is going to be
// injected as the value of the nonce attribute in <script> and <link> tags. The
// nonce is going to be unique for each safehttp.IncomingRequest.
func (it Interceptor) Commit(w safehttp.ResponseHeadersWriter, r *safehttp.IncomingRequest, resp safehttp.Response, cfg safehttp.InterceptorConfig) {
	tmplResp, ok := resp.(*safehttp.TemplateResponse)
	if !ok {
		return
	}

	nonce, err := Nonce(r.Context())
	if err != nil {
		// The nonce should have been added in the Before stage and, if that is
		// not the case, a server misconfiguration occured.
		panic("no CSP nonce")
	}

	if tmplResp.FuncMap == nil {
		tmplResp.FuncMap = map[string]interface{}{}
	}
	tmplResp.FuncMap[htmlinject.CSPNoncesDefaultFuncName] = func() string { return nonce }
}
<|endoftext|>


<commit_before>package main

import (
	"strconv"
	"time"

	"github.com/MJKWoolnough/gopherjs/overlay"
	"github.com/MJKWoolnough/gopherjs/tabs"
	"github.com/MJKWoolnough/gopherjs/xdom"
	"github.com/MJKWoolnough/gopherjs/xform"
	"github.com/MJKWoolnough/gopherjs/xjs"
	"github.com/MJKWoolnough/minewebgen/internal/data"
	"honnef.co/go/js/dom"
)

type Server struct {
	data.Server
	row    dom.Node
	name   *dom.HTMLTableCellElement
	status *dom.HTMLTableCellElement
	button *dom.HTMLButtonElement
}

func ServersTab() func(dom.Element) {
	forceUpdate := make(chan struct{})
	ns := xdom.Button()
	ns.AddEventListener("click", false, func(dom.Event) {
		d := xdom.Div()
		o := overlay.New(d)
		d.AppendChild(transferFile("Server", "Upload/Download", 0, o))
		o.OnClose(func() {
			go func() {
				forceUpdate <- struct{}{}
			}()
		})
		xjs.Body().AppendChild(o)
	})
	noneTd := xdom.Td()
	noneTd.ColSpan = 3
	none := xjs.AppendChildren(xdom.Tr(), xjs.SetInnerText(noneTd, "No Servers Found"))
	serverList := xjs.AppendChildren(xdom.Table(),
		xjs.AppendChildren(xdom.Thead(), xjs.AppendChildren(xdom.Tr(),
			xjs.SetInnerText(xdom.Th(), "Server Name"),
			xjs.SetInnerText(xdom.Th(), "Status"),
			xjs.SetInnerText(xdom.Th(), "Controls"),
		)),
		none,
	)
	nodes := xjs.AppendChildren(xdom.Div(),
		xjs.SetInnerText(xdom.H2(), "Servers"),
		xjs.SetInnerText(ns, "New Server"),
		serverList,
	)
	servers := make(map[int]*Server)

	return func(c dom.Element) {
		c.AppendChild(nodes)
		updateStop := make(chan struct{})
		registerUpdateStopper(c, updateStop)
		for {
			servs, err := RPC.ServerList()
			if err != nil {
				xjs.Alert("Error getting server list: %s", err)
				return
			}

			if none.ParentNode() != nil {
				serverList.RemoveChild(none)
			}

			for _, s := range servers {
				s.ID = -1
			}

			for _, s := range servs {
				os, ok := servers[s.ID]
				if ok {
					os.Server = s
				} else {
					name := xdom.Td()
					status := xdom.Td()
					startStop := xdom.Button()
					os = &Server{
						Server: s,
						row: xjs.AppendChildren(xdom.Tr(),
							name,
							status,
							xjs.AppendChildren(xdom.Td(), startStop),
						),
						name:   name,
						status: status,
						button: startStop,
					}
					servers[s.ID] = os
					serverList.AppendChild(os.row)
					name.AddEventListener("click", false, func() func(dom.Event) {
						s := os
						return func(dom.Event) {
							go func() {
								d, err := RPC.ServerEULA(s.ID)
								if err != nil {
									d = ""
								}
								t := []tabs.Tab{
									{"General", serverGeneral(s.Server)},
									{"Properties", serverProperties(s.Server)},
									{"Console", serverConsole(s.Server)},
								}
								if d != "" {
									t = append(t, tabs.Tab{"EULA", serverEULA(s.Server, d)})
								}
								t = append(t, tabs.Tab{"Misc.", serverMisc(s.Server)})
								o := overlay.New(xjs.AppendChildren(xdom.Div(), tabs.New(t)))
								o.OnClose(func() {
									go func() {
										forceUpdate <- struct{}{}
									}()
								})
								xjs.Body().AppendChild(o)
							}()

						}
					}())
					startStop.AddEventListener("click", false, func() func(dom.Event) {
						b := startStop
						s := os
						return func(dom.Event) {
							go func() {
								b.Disabled = true
								switch s.State {
								case data.StateStopped:
									err := RPC.StartServer(s.ID)
									if err != nil {
										xjs.Alert("Error starting server: %s", err)
										return
									}
								case data.StateRunning:
									err := RPC.StopServer(s.ID)
									if err != nil {
										xjs.Alert("Error stopping server: %s", err)
										return
									}
								default:
									return
								}
								go func() {
									forceUpdate <- struct{}{}
								}()
							}()
						}
					}())
				}
				xjs.SetInnerText(os.status, os.State.String())
				xjs.SetInnerText(os.name, os.Name)
				switch os.State {
				case data.StateStopped:
					xjs.SetInnerText(os.button, "Start")
					os.button.Disabled = false
				case data.StateRunning:
					xjs.SetInnerText(os.button, "Stop")
					os.button.Disabled = false
				default:
					xjs.SetInnerText(os.button, "N/A")
					os.button.Disabled = true
				}
			}

			for id, s := range servers {
				if s.ID == -1 {
					delete(servers, id)
					serverList.RemoveChild(s.row)
				}
			}

			if len(servers) == 0 {
				serverList.AppendChild(none)
			}

			// Sleep until update
			if !updateSleep(forceUpdate, updateStop) {
				return
			}
		}
	}
}

func serverGeneral(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go func() {
			maps, err := RPC.MapList()
			if err != nil {
				c.AppendChild(xjs.SetInnerText(xdom.Div(), "Error getting map list: "+err.Error()))
				return
			}
			name := xform.InputText("name", s.Name)
			name.Required = true
			opts := make([]xform.Option, 1, len(maps)+1)
			opts[0] = xform.Option{
				Label:    "-- None -- ",
				Value:    "-1",
				Selected: s.Map == -1,
			}
			for i, m := range maps {
				n := m.Name
				if m.Server != -1 {
					if m.ID == s.Map {
						n = "* - " + n
					} else {
						n = "! - " + n
					}
				} else {
					n = "    " + n
				}
				opts = append(opts, xform.Option{
					Label:    n,
					Value:    strconv.Itoa(i),
					Selected: m.ID == s.Map,
				})
			}
			args := xform.InputSizeableList(s.Args...)
			sel := xform.SelectBox("map", opts...)
			submit := xform.InputSubmit("Set")
			submit.AddEventListener("click", false, func(e dom.Event) {
				if s.State != data.StateStopped {
					xjs.Alert("Cannot modify these settings while the server is running")
					return
				}
				if name.Value == "" {
					return
				}
				sID, err := strconv.Atoi(sel.Value)
				if err != nil || sID < -1 || sID >= len(maps) {
					return
				}
				submit.Disabled = true
				e.PreventDefault()
				if sID >= 0 {
					m := maps[sID]
					sID = m.ID
				}
				go func() {
					err = RPC.SetServerMap(s.ID, sID)
					if err != nil {
						xjs.Alert("Error setting server map: %s", err)
						return
					}
					s.Name = name.Value
					s.Args = args.Values()
					err = RPC.SetServer(s)
					if err != nil {
						xjs.Alert("Error setting server data: %s", err)
						return
					}
					span := xdom.Span()
					span.Style().Set("color", "#f00")
					c.AppendChild(xjs.SetInnerText(span, "Saved!"))
					time.Sleep(5 * time.Second)
					c.RemoveChild(span)
					submit.Disabled = false
				}()
			})
			xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(),
				xform.Label("Server Name", "name"),
				name,
				xdom.Br(),
				xform.Label("Arguments", "args"),
				args,
				xdom.Br(),
				xform.Label("Map Name", "map"),
				sel,
				xdom.Br(),
				submit,
			))
		}()
	}
}

type PropertyList [][2]string

func (p PropertyList) Len() int {
	return len(p)
}

func (p PropertyList) Less(i, j int) bool {
	return p[i][0] < p[j][0]
}

func (p PropertyList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func serverProperties(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go editProperties(c, "Server", s.ID, RPC.ServerProperties, RPC.SetServerProperties)
	}
}

func serverConsole(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		c.AppendChild(xjs.SetInnerText(xdom.Div(), "Console"))
	}
}

func serverEULA(s data.Server, d string) func(dom.Element) {
	return func(c dom.Element) {
		t := xform.TextArea("eula", d)
		submit := xform.InputSubmit("Save")
		c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
			xjs.SetInnerText(xdom.Label(), "End User License Agreement"),
			xform.Label("EULA", "eula"), t, xdom.Br(),
			submit,
		)))
		submit.AddEventListener("click", false, func(e dom.Event) {
			e.PreventDefault()
			submit.Disabled = true
			go func() {
				err := RPC.SetServerEULA(s.ID, t.Value)
				if err != nil {
					xjs.Alert("Error setting server EULA: %s", err)
					return
				}
				span := xdom.Span()
				span.Style().Set("color", "#f00")
				c.AppendChild(xjs.SetInnerText(span, "Saved!"))
				time.Sleep(5 * time.Second)
				c.RemoveChild(span)
				submit.Disabled = false
			}()
		})
	}
}

func serverMisc(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		// Delete Server
		// Download Server
	}
}
<commit_msg>server list now shows status as unassigned if true<commit_after>package main

import (
	"strconv"
	"time"

	"github.com/MJKWoolnough/gopherjs/overlay"
	"github.com/MJKWoolnough/gopherjs/tabs"
	"github.com/MJKWoolnough/gopherjs/xdom"
	"github.com/MJKWoolnough/gopherjs/xform"
	"github.com/MJKWoolnough/gopherjs/xjs"
	"github.com/MJKWoolnough/minewebgen/internal/data"
	"honnef.co/go/js/dom"
)

type Server struct {
	data.Server
	row    dom.Node
	name   *dom.HTMLTableCellElement
	status *dom.HTMLTableCellElement
	button *dom.HTMLButtonElement
}

func ServersTab() func(dom.Element) {
	forceUpdate := make(chan struct{})
	ns := xdom.Button()
	ns.AddEventListener("click", false, func(dom.Event) {
		d := xdom.Div()
		o := overlay.New(d)
		d.AppendChild(transferFile("Server", "Upload/Download", 0, o))
		o.OnClose(func() {
			go func() {
				forceUpdate <- struct{}{}
			}()
		})
		xjs.Body().AppendChild(o)
	})
	noneTd := xdom.Td()
	noneTd.ColSpan = 3
	none := xjs.AppendChildren(xdom.Tr(), xjs.SetInnerText(noneTd, "No Servers Found"))
	serverList := xjs.AppendChildren(xdom.Table(),
		xjs.AppendChildren(xdom.Thead(), xjs.AppendChildren(xdom.Tr(),
			xjs.SetInnerText(xdom.Th(), "Server Name"),
			xjs.SetInnerText(xdom.Th(), "Status"),
			xjs.SetInnerText(xdom.Th(), "Controls"),
		)),
		none,
	)
	nodes := xjs.AppendChildren(xdom.Div(),
		xjs.SetInnerText(xdom.H2(), "Servers"),
		xjs.SetInnerText(ns, "New Server"),
		serverList,
	)
	servers := make(map[int]*Server)

	return func(c dom.Element) {
		c.AppendChild(nodes)
		updateStop := make(chan struct{})
		registerUpdateStopper(c, updateStop)
		for {
			servs, err := RPC.ServerList()
			if err != nil {
				xjs.Alert("Error getting server list: %s", err)
				return
			}

			if none.ParentNode() != nil {
				serverList.RemoveChild(none)
			}

			for _, s := range servers {
				s.ID = -1
			}

			for _, s := range servs {
				os, ok := servers[s.ID]
				if ok {
					os.Server = s
				} else {
					name := xdom.Td()
					status := xdom.Td()
					startStop := xdom.Button()
					os = &Server{
						Server: s,
						row: xjs.AppendChildren(xdom.Tr(),
							name,
							status,
							xjs.AppendChildren(xdom.Td(), startStop),
						),
						name:   name,
						status: status,
						button: startStop,
					}
					servers[s.ID] = os
					serverList.AppendChild(os.row)
					name.AddEventListener("click", false, func() func(dom.Event) {
						s := os
						return func(dom.Event) {
							go func() {
								d, err := RPC.ServerEULA(s.ID)
								if err != nil {
									d = ""
								}
								t := []tabs.Tab{
									{"General", serverGeneral(s.Server)},
									{"Properties", serverProperties(s.Server)},
									{"Console", serverConsole(s.Server)},
								}
								if d != "" {
									t = append(t, tabs.Tab{"EULA", serverEULA(s.Server, d)})
								}
								t = append(t, tabs.Tab{"Misc.", serverMisc(s.Server)})
								o := overlay.New(xjs.AppendChildren(xdom.Div(), tabs.New(t)))
								o.OnClose(func() {
									go func() {
										forceUpdate <- struct{}{}
									}()
								})
								xjs.Body().AppendChild(o)
							}()

						}
					}())
					startStop.AddEventListener("click", false, func() func(dom.Event) {
						b := startStop
						s := os
						return func(dom.Event) {
							go func() {
								b.Disabled = true
								switch s.State {
								case data.StateStopped:
									err := RPC.StartServer(s.ID)
									if err != nil {
										xjs.Alert("Error starting server: %s", err)
										return
									}
								case data.StateRunning:
									err := RPC.StopServer(s.ID)
									if err != nil {
										xjs.Alert("Error stopping server: %s", err)
										return
									}
								default:
									return
								}
								go func() {
									forceUpdate <- struct{}{}
								}()
							}()
						}
					}())
				}
				if os.Map >= 0 {
					xjs.SetInnerText(os.status, os.State.String())
					switch os.State {
					case data.StateStopped:
						xjs.SetInnerText(os.button, "Start")
						os.button.Disabled = false
					case data.StateRunning:
						xjs.SetInnerText(os.button, "Stop")
						os.button.Disabled = false
					default:
						xjs.SetInnerText(os.button, "N/A")
						os.button.Disabled = true
					}
				} else {
					xjs.SetInnerText(os.status, "No Map")
					os.button.Disabled = true
					xjs.SetInnerText(os.button, "N/A")
				}
				xjs.SetInnerText(os.name, os.Name)
			}

			for id, s := range servers {
				if s.ID == -1 {
					delete(servers, id)
					serverList.RemoveChild(s.row)
				}
			}

			if len(servers) == 0 {
				serverList.AppendChild(none)
			}

			// Sleep until update
			if !updateSleep(forceUpdate, updateStop) {
				return
			}
		}
	}
}

func serverGeneral(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go func() {
			maps, err := RPC.MapList()
			if err != nil {
				c.AppendChild(xjs.SetInnerText(xdom.Div(), "Error getting map list: "+err.Error()))
				return
			}
			name := xform.InputText("name", s.Name)
			name.Required = true
			opts := make([]xform.Option, 1, len(maps)+1)
			opts[0] = xform.Option{
				Label:    "-- None -- ",
				Value:    "-1",
				Selected: s.Map == -1,
			}
			for i, m := range maps {
				n := m.Name
				if m.Server != -1 {
					if m.ID == s.Map {
						n = "* - " + n
					} else {
						n = "! - " + n
					}
				} else {
					n = "    " + n
				}
				opts = append(opts, xform.Option{
					Label:    n,
					Value:    strconv.Itoa(i),
					Selected: m.ID == s.Map,
				})
			}
			args := xform.InputSizeableList(s.Args...)
			sel := xform.SelectBox("map", opts...)
			submit := xform.InputSubmit("Set")
			submit.AddEventListener("click", false, func(e dom.Event) {
				if s.State != data.StateStopped {
					xjs.Alert("Cannot modify these settings while the server is running")
					return
				}
				if name.Value == "" {
					return
				}
				sID, err := strconv.Atoi(sel.Value)
				if err != nil || sID < -1 || sID >= len(maps) {
					return
				}
				submit.Disabled = true
				e.PreventDefault()
				if sID >= 0 {
					m := maps[sID]
					sID = m.ID
				}
				go func() {
					err = RPC.SetServerMap(s.ID, sID)
					if err != nil {
						xjs.Alert("Error setting server map: %s", err)
						return
					}
					s.Name = name.Value
					s.Args = args.Values()
					err = RPC.SetServer(s)
					if err != nil {
						xjs.Alert("Error setting server data: %s", err)
						return
					}
					span := xdom.Span()
					span.Style().Set("color", "#f00")
					c.AppendChild(xjs.SetInnerText(span, "Saved!"))
					time.Sleep(5 * time.Second)
					c.RemoveChild(span)
					submit.Disabled = false
				}()
			})
			xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(),
				xform.Label("Server Name", "name"),
				name,
				xdom.Br(),
				xform.Label("Arguments", "args"),
				args,
				xdom.Br(),
				xform.Label("Map Name", "map"),
				sel,
				xdom.Br(),
				submit,
			))
		}()
	}
}

type PropertyList [][2]string

func (p PropertyList) Len() int {
	return len(p)
}

func (p PropertyList) Less(i, j int) bool {
	return p[i][0] < p[j][0]
}

func (p PropertyList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func serverProperties(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		go editProperties(c, "Server", s.ID, RPC.ServerProperties, RPC.SetServerProperties)
	}
}

func serverConsole(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		c.AppendChild(xjs.SetInnerText(xdom.Div(), "Console"))
	}
}

func serverEULA(s data.Server, d string) func(dom.Element) {
	return func(c dom.Element) {
		t := xform.TextArea("eula", d)
		submit := xform.InputSubmit("Save")
		c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
			xjs.SetInnerText(xdom.Label(), "End User License Agreement"),
			xform.Label("EULA", "eula"), t, xdom.Br(),
			submit,
		)))
		submit.AddEventListener("click", false, func(e dom.Event) {
			e.PreventDefault()
			submit.Disabled = true
			go func() {
				err := RPC.SetServerEULA(s.ID, t.Value)
				if err != nil {
					xjs.Alert("Error setting server EULA: %s", err)
					return
				}
				span := xdom.Span()
				span.Style().Set("color", "#f00")
				c.AppendChild(xjs.SetInnerText(span, "Saved!"))
				time.Sleep(5 * time.Second)
				c.RemoveChild(span)
				submit.Disabled = false
			}()
		})
	}
}

func serverMisc(s data.Server) func(dom.Element) {
	return func(c dom.Element) {
		// Delete Server
		// Download Server
	}
}
<|endoftext|>


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

// Package tabix implements tabix coordinate sorted indexing.
package tabix

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/biogo/hts/bgzf"
	"github.com/biogo/hts/bgzf/index"
	"github.com/biogo/hts/internal"
)

// Index is a tabix index.
type Index struct {
	Format    byte
	ZeroBased bool

	NameColumn  int32
	BeginColumn int32
	EndColumn   int32

	MetaChar rune
	Skip     int32

	refNames []string
	nameMap  map[string]int

	idx internal.Index
}

// New returns a new tabix index.
func New() *Index {
	return &Index{nameMap: make(map[string]int)}
}

// NumRefs returns the number of references in the index.
func (i *Index) NumRefs() int {
	return len(i.idx.Refs)
}

// Names returns the reference names in the index. The returned
// slice should not be altered.
func (i *Index) Names() []string {
	return i.refNames
}

// IDs returns a map of strings to integer IDs. The returned
// map should not be altered.
func (i *Index) IDs() map[string]int {
	return i.nameMap
}

// ReferenceStats returns the index statistics for the given reference and true
// if the statistics are valid.
func (i *Index) ReferenceStats(id int) (stats index.ReferenceStats, ok bool) {
	s := i.idx.Refs[id].Stats
	if s == nil {
		return index.ReferenceStats{}, false
	}
	return index.ReferenceStats(*s), true
}

// Unmapped returns the number of unmapped reads and true if the count is valid.
func (i *Index) Unmapped() (n uint64, ok bool) {
	if i.idx.Unmapped == nil {
		return 0, false
	}
	return *i.idx.Unmapped, true
}

// Record wraps types that may be indexed by an Index.
type Record interface {
	RefName() string
	Start() int
	End() int
}

type tabixShim struct {
	id, start, end int
}

func (r tabixShim) RefID() int { return r.id }
func (r tabixShim) Start() int { return r.start }
func (r tabixShim) End() int   { return r.end }

// Add records the SAM record as having being located at the given chunk.
func (i *Index) Add(r Record, c bgzf.Chunk, placed, mapped bool) error {
	refName := r.RefName()
	rid, ok := i.nameMap[refName]
	if !ok {
		rid = len(i.refNames)
		i.refNames = append(i.refNames, refName)
	}
	shim := tabixShim{id: rid, start: r.Start(), end: r.End()}
	return i.idx.Add(shim, internal.BinFor(r.Start(), r.End()), c, placed, mapped)
}

// Chunks returns a []bgzf.Chunk that corresponds to the given genomic interval.
func (i *Index) Chunks(ref string, beg, end int) ([]bgzf.Chunk, error) {
	id, ok := i.nameMap[ref]
	if !ok {
		return nil, index.ErrNoReference
	}
	chunks, err := i.idx.Chunks(id, beg, end)
	if err != nil {
		return nil, err
	}
	return adjacent(chunks), nil
}

var adjacent = index.Adjacent

// MergeChunks applies the given MergeStrategy to all bins in the Index.
func (i *Index) MergeChunks(s index.MergeStrategy) {
	i.idx.MergeChunks(s)
}

var tbiMagic = [4]byte{'T', 'B', 'I', 0x1}

// ReadFrom reads the tabix index from the given io.Reader. Note that
// the tabix specification states that the index is stored as BGZF, but
// ReadFrom does not perform decompression.
func ReadFrom(r io.Reader) (*Index, error) {
	var (
		idx   Index
		magic [4]byte
		err   error
	)
	err = binary.Read(r, binary.LittleEndian, &magic)
	if err != nil {
		return nil, err
	}
	if magic != tbiMagic {
		return nil, errors.New("tabix: magic number mismatch")
	}

	var n int32
	err = binary.Read(r, binary.LittleEndian, &n)
	if err != nil {
		return nil, err
	}
	if n == 0 {
		return nil, nil
	}

	err = readTabixHeader(r, &idx)
	if err != nil {
		return nil, err
	}
	if len(idx.refNames) != int(n) {
		return nil, fmt.Errorf("tabix: name count mismatch: %d != %d", len(idx.refNames), n)
	}
	idx.nameMap = make(map[string]int)
	for i, n := range idx.refNames {
		idx.nameMap[n] = i
	}

	idx.idx, err = internal.ReadIndex(r, n, "tabix")
	if err != nil {
		return nil, err
	}
	return &idx, nil
}

func readTabixHeader(r io.Reader, idx *Index) error {
	var (
		format int32
		err    error
	)
	err = binary.Read(r, binary.LittleEndian, &format)
	if err != nil {
		return fmt.Errorf("tabix: failed to read format: %v", err)
	}
	idx.Format = byte(format)
	idx.ZeroBased = format&0x10000 != 0

	err = binary.Read(r, binary.LittleEndian, &idx.NameColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to read name column index: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.BeginColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to read begin column index: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.EndColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to read end column index: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.MetaChar)
	if err != nil {
		return fmt.Errorf("tabix: failed to read metacharacter: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.Skip)
	if err != nil {
		return fmt.Errorf("tabix: failed to read skip count: %v", err)
	}
	var n int32
	err = binary.Read(r, binary.LittleEndian, &n)
	if err != nil {
		return fmt.Errorf("tabix: failed to read name lengths: %v", err)
	}
	nameBytes := make([]byte, n)
	_, err = r.Read(nameBytes)
	if err != nil {
		return fmt.Errorf("tabix: failed to read names: %v", err)
	}
	names := string(nameBytes)
	if names[len(names)-1] != 0 {
		return errors.New("tabix: last name not zero-terminated")
	}
	idx.refNames = strings.Split(names[:len(names)-1], string(0))

	return nil
}

// WriteTo writes the index to the given io.Writer. Note that
// the tabix specification states that the index is stored as BGZF, but
// WriteTo does not perform compression.
func WriteTo(w io.Writer, idx *Index) error {
	err := binary.Write(w, binary.LittleEndian, tbiMagic)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.LittleEndian, int32(len(idx.idx.Refs)))
	if err != nil {
		return err
	}
	err = writeTabixHeader(w, idx)
	if err != nil {
		return err
	}

	return internal.WriteIndex(w, &idx.idx, "tabix")
}

func writeTabixHeader(w io.Writer, idx *Index) error {
	var err error
	format := int32(idx.Format)
	if idx.ZeroBased {
		format |= 0x10000
	}
	err = binary.Write(w, binary.LittleEndian, format)
	if err != nil {
		return fmt.Errorf("tabix: failed to write format: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.NameColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to write name column index: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.BeginColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to write begin column index: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.EndColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to write end column index: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.MetaChar)
	if err != nil {
		return fmt.Errorf("tabix: failed to write metacharacter: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.Skip)
	if err != nil {
		return fmt.Errorf("tabix: failed to write skip count: %v", err)
	}
	var n int32
	for _, name := range idx.refNames {
		n += int32(len(name) + 1)
	}
	err = binary.Write(w, binary.LittleEndian, n)
	if err != nil {
		return fmt.Errorf("tabix: failed to write name lengths: %v", err)
	}
	for _, name := range idx.refNames {
		_, err = w.Write([]byte(name))
		if err != nil {
			return fmt.Errorf("tabix: failed to write name: %v", err)
		}
		_, err = w.Write([]byte{0})
		if err != nil {
			return fmt.Errorf("tabix: failed to write name: %v", err)
		}
	}
	return nil
}
<commit_msg>tabix: replace Read with io.ReadFull<commit_after>// Copyright ©2014 The bíogo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package tabix implements tabix coordinate sorted indexing.
package tabix

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/biogo/hts/bgzf"
	"github.com/biogo/hts/bgzf/index"
	"github.com/biogo/hts/internal"
)

// Index is a tabix index.
type Index struct {
	Format    byte
	ZeroBased bool

	NameColumn  int32
	BeginColumn int32
	EndColumn   int32

	MetaChar rune
	Skip     int32

	refNames []string
	nameMap  map[string]int

	idx internal.Index
}

// New returns a new tabix index.
func New() *Index {
	return &Index{nameMap: make(map[string]int)}
}

// NumRefs returns the number of references in the index.
func (i *Index) NumRefs() int {
	return len(i.idx.Refs)
}

// Names returns the reference names in the index. The returned
// slice should not be altered.
func (i *Index) Names() []string {
	return i.refNames
}

// IDs returns a map of strings to integer IDs. The returned
// map should not be altered.
func (i *Index) IDs() map[string]int {
	return i.nameMap
}

// ReferenceStats returns the index statistics for the given reference and true
// if the statistics are valid.
func (i *Index) ReferenceStats(id int) (stats index.ReferenceStats, ok bool) {
	s := i.idx.Refs[id].Stats
	if s == nil {
		return index.ReferenceStats{}, false
	}
	return index.ReferenceStats(*s), true
}

// Unmapped returns the number of unmapped reads and true if the count is valid.
func (i *Index) Unmapped() (n uint64, ok bool) {
	if i.idx.Unmapped == nil {
		return 0, false
	}
	return *i.idx.Unmapped, true
}

// Record wraps types that may be indexed by an Index.
type Record interface {
	RefName() string
	Start() int
	End() int
}

type tabixShim struct {
	id, start, end int
}

func (r tabixShim) RefID() int { return r.id }
func (r tabixShim) Start() int { return r.start }
func (r tabixShim) End() int   { return r.end }

// Add records the SAM record as having being located at the given chunk.
func (i *Index) Add(r Record, c bgzf.Chunk, placed, mapped bool) error {
	refName := r.RefName()
	rid, ok := i.nameMap[refName]
	if !ok {
		rid = len(i.refNames)
		i.refNames = append(i.refNames, refName)
	}
	shim := tabixShim{id: rid, start: r.Start(), end: r.End()}
	return i.idx.Add(shim, internal.BinFor(r.Start(), r.End()), c, placed, mapped)
}

// Chunks returns a []bgzf.Chunk that corresponds to the given genomic interval.
func (i *Index) Chunks(ref string, beg, end int) ([]bgzf.Chunk, error) {
	id, ok := i.nameMap[ref]
	if !ok {
		return nil, index.ErrNoReference
	}
	chunks, err := i.idx.Chunks(id, beg, end)
	if err != nil {
		return nil, err
	}
	return adjacent(chunks), nil
}

var adjacent = index.Adjacent

// MergeChunks applies the given MergeStrategy to all bins in the Index.
func (i *Index) MergeChunks(s index.MergeStrategy) {
	i.idx.MergeChunks(s)
}

var tbiMagic = [4]byte{'T', 'B', 'I', 0x1}

// ReadFrom reads the tabix index from the given io.Reader. Note that
// the tabix specification states that the index is stored as BGZF, but
// ReadFrom does not perform decompression.
func ReadFrom(r io.Reader) (*Index, error) {
	var (
		idx   Index
		magic [4]byte
		err   error
	)
	err = binary.Read(r, binary.LittleEndian, &magic)
	if err != nil {
		return nil, err
	}
	if magic != tbiMagic {
		return nil, errors.New("tabix: magic number mismatch")
	}

	var n int32
	err = binary.Read(r, binary.LittleEndian, &n)
	if err != nil {
		return nil, err
	}
	if n == 0 {
		return nil, nil
	}

	err = readTabixHeader(r, &idx)
	if err != nil {
		return nil, err
	}
	if len(idx.refNames) != int(n) {
		return nil, fmt.Errorf("tabix: name count mismatch: %d != %d", len(idx.refNames), n)
	}
	idx.nameMap = make(map[string]int)
	for i, n := range idx.refNames {
		idx.nameMap[n] = i
	}

	idx.idx, err = internal.ReadIndex(r, n, "tabix")
	if err != nil {
		return nil, err
	}
	return &idx, nil
}

func readTabixHeader(r io.Reader, idx *Index) error {
	var (
		format int32
		err    error
	)
	err = binary.Read(r, binary.LittleEndian, &format)
	if err != nil {
		return fmt.Errorf("tabix: failed to read format: %v", err)
	}
	idx.Format = byte(format)
	idx.ZeroBased = format&0x10000 != 0

	err = binary.Read(r, binary.LittleEndian, &idx.NameColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to read name column index: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.BeginColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to read begin column index: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.EndColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to read end column index: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.MetaChar)
	if err != nil {
		return fmt.Errorf("tabix: failed to read metacharacter: %v", err)
	}
	err = binary.Read(r, binary.LittleEndian, &idx.Skip)
	if err != nil {
		return fmt.Errorf("tabix: failed to read skip count: %v", err)
	}
	var n int32
	err = binary.Read(r, binary.LittleEndian, &n)
	if err != nil {
		return fmt.Errorf("tabix: failed to read name lengths: %v", err)
	}
	nameBytes := make([]byte, n)
	_, err = io.ReadFull(r, nameBytes)
	if err != nil {
		return fmt.Errorf("tabix: failed to read names: %v", err)
	}
	names := string(nameBytes)
	if names[len(names)-1] != 0 {
		return errors.New("tabix: last name not zero-terminated")
	}
	idx.refNames = strings.Split(names[:len(names)-1], string(0))

	return nil
}

// WriteTo writes the index to the given io.Writer. Note that
// the tabix specification states that the index is stored as BGZF, but
// WriteTo does not perform compression.
func WriteTo(w io.Writer, idx *Index) error {
	err := binary.Write(w, binary.LittleEndian, tbiMagic)
	if err != nil {
		return err
	}

	err = binary.Write(w, binary.LittleEndian, int32(len(idx.idx.Refs)))
	if err != nil {
		return err
	}
	err = writeTabixHeader(w, idx)
	if err != nil {
		return err
	}

	return internal.WriteIndex(w, &idx.idx, "tabix")
}

func writeTabixHeader(w io.Writer, idx *Index) error {
	var err error
	format := int32(idx.Format)
	if idx.ZeroBased {
		format |= 0x10000
	}
	err = binary.Write(w, binary.LittleEndian, format)
	if err != nil {
		return fmt.Errorf("tabix: failed to write format: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.NameColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to write name column index: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.BeginColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to write begin column index: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.EndColumn)
	if err != nil {
		return fmt.Errorf("tabix: failed to write end column index: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.MetaChar)
	if err != nil {
		return fmt.Errorf("tabix: failed to write metacharacter: %v", err)
	}
	err = binary.Write(w, binary.LittleEndian, idx.Skip)
	if err != nil {
		return fmt.Errorf("tabix: failed to write skip count: %v", err)
	}
	var n int32
	for _, name := range idx.refNames {
		n += int32(len(name) + 1)
	}
	err = binary.Write(w, binary.LittleEndian, n)
	if err != nil {
		return fmt.Errorf("tabix: failed to write name lengths: %v", err)
	}
	for _, name := range idx.refNames {
		_, err = w.Write([]byte(name))
		if err != nil {
			return fmt.Errorf("tabix: failed to write name: %v", err)
		}
		_, err = w.Write([]byte{0})
		if err != nil {
			return fmt.Errorf("tabix: failed to write name: %v", err)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package memory

import (
	"strings"
	"sync"
	"time"

	"github.com/grafana/metrictank/stats"
	lru "github.com/hashicorp/golang-lru"
	"github.com/raintank/schema"
	log "github.com/sirupsen/logrus"
)

var (
	// metric idx.memory.find-cache.hit is a counter of findCache hits
	findCacheHit = stats.NewCounterRate32("idx.memory.find-cache.hit")
	// metric idx.memory.find-cache.miss is a counter of findCache misses
	findCacheMiss = stats.NewCounterRate32("idx.memory.find-cache.miss")

	findCacheSize            = 1000
	findCacheInvalidateQueue = 100
	findCacheBackoff         = time.Minute
)

type FindCache struct {
	cache map[uint32]*lru.Cache
	size  int
	sync.RWMutex
	newSeries map[uint32]chan struct{}
	backoff   map[uint32]time.Time
}

func NewFindCache(size int) *FindCache {
	fc := &FindCache{
		cache:     make(map[uint32]*lru.Cache),
		size:      size,
		newSeries: make(map[uint32]chan struct{}),
		backoff:   make(map[uint32]time.Time),
	}
	return fc
}

func (c *FindCache) Get(orgId uint32, pattern string) ([]*Node, bool) {
	c.RLock()
	cache, ok := c.cache[orgId]
	c.RUnlock()
	if !ok {
		findCacheMiss.Inc()
		return nil, false
	}
	nodes, ok := cache.Get(pattern)
	if !ok {
		findCacheMiss.Inc()
		return nil, ok
	}
	findCacheHit.Inc()
	return nodes.([]*Node), ok
}

func (c *FindCache) Add(orgId uint32, pattern string, nodes []*Node) {
	c.RLock()
	cache, ok := c.cache[orgId]
	t := c.backoff[orgId]
	c.RUnlock()
	var err error
	if !ok {
		// dont init the cache if we are in backoff mode.
		if time.Until(t) > 0 {
			return
		}
		cache, err = lru.New(c.size)
		if err != nil {
			log.Errorf("memory-idx: findCache failed to create lru. err=%s", err)
			return
		}
		c.Lock()
		c.cache[orgId] = cache
		c.newSeries[orgId] = make(chan struct{}, findCacheInvalidateQueue)
		c.Unlock()
	}
	cache.Add(pattern, nodes)
}

func (c *FindCache) Purge(orgId uint32) {
	c.RLock()
	cache, ok := c.cache[orgId]
	c.RUnlock()
	if !ok {
		return
	}
	cache.Purge()
}

func (c *FindCache) PurgeAll() {
	c.RLock()
	orgs := make([]uint32, len(c.cache))
	i := 0
	for k := range c.cache {
		orgs[i] = k
		i++
	}
	c.RUnlock()
	for _, org := range orgs {
		c.Purge(org)
	}
}

func (c *FindCache) InvalidateFor(orgId uint32, path string) {
	c.RLock()
	cache, ok := c.cache[orgId]
	c.RUnlock()
	if !ok || cache.Len() < 1 {
		return
	}

	select {
	case c.newSeries[orgId] <- struct{}{}:
	default:
		c.Lock()
		c.backoff[orgId] = time.Now().Add(findCacheBackoff)
		delete(c.cache, orgId)
		c.Unlock()
		for i := 0; i < len(c.newSeries[orgId]); i++ {
			select {
			case <-c.newSeries[orgId]:
			default:
			}
		}
		log.Infof("memory-idx: findCache invalidate-queue full. Disabling cache for %s. num-cached-entries=%d", findCacheBackoff.String(), cache.Len())
		return
	}

	tree := &Tree{
		Items: map[string]*Node{
			"": {
				Path:     "",
				Children: make([]string, 0),
				Defs:     make([]schema.MKey, 0),
			},
		},
	}
	pos := strings.Index(path, ".")
	prevPos := 0
	for {
		branch := path[:pos]
		// add as child of parent branch
		tree.Items[path[:prevPos]].Children = []string{branch[prevPos+1:]}

		// create this branch/leaf
		tree.Items[branch] = &Node{
			Path: branch,
		}
		if branch == path {
			tree.Items[branch].Defs = []schema.MKey{{}}
			break
		}
		prevPos = pos
		nextPos := strings.Index(path[pos+1:], ".")
		if nextPos < 0 {
			pos = len(path)
		} else {
			pos = pos + nextPos + 1
		}
	}

	for _, k := range cache.Keys() {
		matches, err := find(tree, k.(string))
		if err != nil {
			log.Errorf("memory-idx: checking if new series matches expressions in findCache. series=%s expr=%s err=%s", path, k, err)
			continue
		}
		if len(matches) > 0 {
			cache.Remove(k)
		}
	}
	select {
	case <-c.newSeries[orgId]:
	default:
	}
}

func (m *UnpartitionedMemoryIdx) PurgeFindCache() {
	m.findCache.PurgeAll()
}

func (p *PartitionedMemoryIdx) PurgeFindCache() {
	for _, m := range p.Partition {
		m.findCache.PurgeAll()
	}
}
<commit_msg>be consistent with return values in FindCache.Get<commit_after>package memory

import (
	"strings"
	"sync"
	"time"

	"github.com/grafana/metrictank/stats"
	lru "github.com/hashicorp/golang-lru"
	"github.com/raintank/schema"
	log "github.com/sirupsen/logrus"
)

var (
	// metric idx.memory.find-cache.hit is a counter of findCache hits
	findCacheHit = stats.NewCounterRate32("idx.memory.find-cache.hit")
	// metric idx.memory.find-cache.miss is a counter of findCache misses
	findCacheMiss = stats.NewCounterRate32("idx.memory.find-cache.miss")

	findCacheSize            = 1000
	findCacheInvalidateQueue = 100
	findCacheBackoff         = time.Minute
)

type FindCache struct {
	cache map[uint32]*lru.Cache
	size  int
	sync.RWMutex
	newSeries map[uint32]chan struct{}
	backoff   map[uint32]time.Time
}

func NewFindCache(size int) *FindCache {
	fc := &FindCache{
		cache:     make(map[uint32]*lru.Cache),
		size:      size,
		newSeries: make(map[uint32]chan struct{}),
		backoff:   make(map[uint32]time.Time),
	}
	return fc
}

func (c *FindCache) Get(orgId uint32, pattern string) ([]*Node, bool) {
	c.RLock()
	cache, ok := c.cache[orgId]
	c.RUnlock()
	if !ok {
		findCacheMiss.Inc()
		return nil, ok
	}
	nodes, ok := cache.Get(pattern)
	if !ok {
		findCacheMiss.Inc()
		return nil, ok
	}
	findCacheHit.Inc()
	return nodes.([]*Node), ok
}

func (c *FindCache) Add(orgId uint32, pattern string, nodes []*Node) {
	c.RLock()
	cache, ok := c.cache[orgId]
	t := c.backoff[orgId]
	c.RUnlock()
	var err error
	if !ok {
		// dont init the cache if we are in backoff mode.
		if time.Until(t) > 0 {
			return
		}
		cache, err = lru.New(c.size)
		if err != nil {
			log.Errorf("memory-idx: findCache failed to create lru. err=%s", err)
			return
		}
		c.Lock()
		c.cache[orgId] = cache
		c.newSeries[orgId] = make(chan struct{}, findCacheInvalidateQueue)
		c.Unlock()
	}
	cache.Add(pattern, nodes)
}

func (c *FindCache) Purge(orgId uint32) {
	c.RLock()
	cache, ok := c.cache[orgId]
	c.RUnlock()
	if !ok {
		return
	}
	cache.Purge()
}

func (c *FindCache) PurgeAll() {
	c.RLock()
	orgs := make([]uint32, len(c.cache))
	i := 0
	for k := range c.cache {
		orgs[i] = k
		i++
	}
	c.RUnlock()
	for _, org := range orgs {
		c.Purge(org)
	}
}

func (c *FindCache) InvalidateFor(orgId uint32, path string) {
	c.RLock()
	cache, ok := c.cache[orgId]
	c.RUnlock()
	if !ok || cache.Len() < 1 {
		return
	}

	select {
	case c.newSeries[orgId] <- struct{}{}:
	default:
		c.Lock()
		c.backoff[orgId] = time.Now().Add(findCacheBackoff)
		delete(c.cache, orgId)
		c.Unlock()
		for i := 0; i < len(c.newSeries[orgId]); i++ {
			select {
			case <-c.newSeries[orgId]:
			default:
			}
		}
		log.Infof("memory-idx: findCache invalidate-queue full. Disabling cache for %s. num-cached-entries=%d", findCacheBackoff.String(), cache.Len())
		return
	}

	tree := &Tree{
		Items: map[string]*Node{
			"": {
				Path:     "",
				Children: make([]string, 0),
				Defs:     make([]schema.MKey, 0),
			},
		},
	}
	pos := strings.Index(path, ".")
	prevPos := 0
	for {
		branch := path[:pos]
		// add as child of parent branch
		tree.Items[path[:prevPos]].Children = []string{branch[prevPos+1:]}

		// create this branch/leaf
		tree.Items[branch] = &Node{
			Path: branch,
		}
		if branch == path {
			tree.Items[branch].Defs = []schema.MKey{{}}
			break
		}
		prevPos = pos
		nextPos := strings.Index(path[pos+1:], ".")
		if nextPos < 0 {
			pos = len(path)
		} else {
			pos = pos + nextPos + 1
		}
	}

	for _, k := range cache.Keys() {
		matches, err := find(tree, k.(string))
		if err != nil {
			log.Errorf("memory-idx: checking if new series matches expressions in findCache. series=%s expr=%s err=%s", path, k, err)
			continue
		}
		if len(matches) > 0 {
			cache.Remove(k)
		}
	}
	select {
	case <-c.newSeries[orgId]:
	default:
	}
}

func (m *UnpartitionedMemoryIdx) PurgeFindCache() {
	m.findCache.PurgeAll()
}

func (p *PartitionedMemoryIdx) PurgeFindCache() {
	for _, m := range p.Partition {
		m.findCache.PurgeAll()
	}
}
<|endoftext|>


<commit_before>package htlcswitch

import (
	"encoding/binary"
	"io"

	"github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/lnwire"
)

// NetworkHop indicates the blockchain network that is intended to be the next
// hop for a forwarded HTLC. The existnce of this field within the
// ForwardingInfo struct enables the ability for HTLC to cross chain-boundaries
// at will.
type NetworkHop uint8

const (
	// BitcoinHop denotes that an HTLC is to be forwarded along the Bitcoin
	// link with the specified short channel ID.
	BitcoinHop NetworkHop = iota

	// LitecoinHop denotes that an HTLC is to be forwarded along the
	// Litecoin link with the specified short channel ID.
	LitecoinHop
)

// String returns the string representation of the target NetworkHop.
func (c NetworkHop) String() string {
	switch c {
	case BitcoinHop:
		return "Bitcoin"
	case LitecoinHop:
		return "Litecoin"
	default:
		return "Kekcoin"
	}
}

var (
	// exitHop is a special "hop" which denotes that an incoming HTLC is
	// meant to pay finally to the receiving node.
	exitHop lnwire.ShortChannelID
)

// ForwardingInfo contains all the information that is necessary to forward and
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
// Forwarding links are to use this information to authenticate the information
// received within the incoming HTLC, to ensure that the prior hop didn't
// tamper with the end-to-end routing information at all.
type ForwardingInfo struct {
	// Network is the target blockchain network that the HTLC will travel
	// over next.
	Network NetworkHop

	// NextHop is the channel ID of the next hop. The received HTLC should
	// be forwarded to this particular channel in order to continue the
	// end-to-end route.
	NextHop lnwire.ShortChannelID

	// AmountToForward is the amount of milli-satoshis that the receiving
	// node should forward to the next hop.
	AmountToForward lnwire.MilliSatoshi

	// OutgoingCTLV is the specified value of the CTLV timelock to be used
	// in the outgoing HTLC.
	OutgoingCTLV uint32

	// TODO(roasbeef): modify sphinx logic to not just discard the
	// remaining bytes, instead should include the rest as excess
}

// HopIterator is an interface that abstracts away the routing information
// included in HTLC's which includes the entirety of the payment path of an
// HTLC. This interface provides two basic method which carry out: how to
// interpret the forwarding information encoded within the HTLC packet, and hop
// to encode the forwarding information for the _next_ hop.
type HopIterator interface {
	// ForwardingInstructions returns the set of fields that detail exactly
	// _how_ this hop should forward the HTLC to the next hop.
	// Additionally, the information encoded within the returned
	// ForwardingInfo is to be used by each hop to authenticate the
	// information given to it by the prior hop.
	ForwardingInstructions() ForwardingInfo

	// EncodeNextHop encodes the onion packet destined for the next hop
	// into the passed io.Writer.
	EncodeNextHop(w io.Writer) error
}

// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
// onion routing to encode the payment route  in such a way so that node might
// see only the next hop in the route..
type sphinxHopIterator struct {
	// nextPacket is the decoded onion packet for the _next_ hop.
	nextPacket *sphinx.OnionPacket

	// processedPacket is the outcome of processing an onion packet. It
	// includes the information required to properly forward the packet to
	// the next hop.
	processedPacket *sphinx.ProcessedPacket
}

// A compile time check to ensure sphinxHopIterator implements the HopIterator
// interface.
var _ HopIterator = (*sphinxHopIterator)(nil)

// Encode encodes iterator and writes it to the writer.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
	return r.nextPacket.Encode(w)
}

// ForwardingInstructions returns the set of fields that detail exactly _how_
// this hop should forward the HTLC to the next hop.  Additionally, the
// information encoded within the returned ForwardingInfo is to be used by each
// hop to authenticate the information given to it by the prior hop.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ForwardingInstructions() ForwardingInfo {
	fwdInst := r.processedPacket.ForwardingInstructions

	var nextHop lnwire.ShortChannelID
	switch r.processedPacket.Action {
	case sphinx.ExitNode:
		nextHop = exitHop
	case sphinx.MoreHops:
		s := binary.BigEndian.Uint64(fwdInst.NextAddress[:])
		nextHop = lnwire.NewShortChanIDFromInt(s)
	}

	return ForwardingInfo{
		Network:         BitcoinHop,
		NextHop:         nextHop,
		AmountToForward: lnwire.MilliSatoshi(fwdInst.ForwardAmount),
		OutgoingCTLV:    fwdInst.OutgoingCltv,
	}
}

// OnionProcessor is responsible for keeping all sphinx dependent parts inside
// and expose only decoding function. With such approach we give freedom for
// subsystems which wants to decode sphinx path to not be dependable from
// sphinx at all.
//
// NOTE: The reason for keeping decoder separated from hop iterator is too
// maintain the hop iterator abstraction. Without it the structures which using
// the hop iterator should contain sphinx router which makes their creations in
// tests dependent from the sphinx internal parts.
type OnionProcessor struct {
	router *sphinx.Router
}

// NewOnionProcessor creates new instance of decoder.
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
	return &OnionProcessor{router}
}

// DecodeHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
// instance using the rHash as the associated data when checking the relevant
// MACs during the decoding process.
func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte) (HopIterator,
	lnwire.FailCode) {
	onionPkt := &sphinx.OnionPacket{}
	if err := onionPkt.Decode(r); err != nil {
		return nil, lnwire.CodeTemporaryChannelFailure
	}

	// Attempt to process the Sphinx packet. We include the payment hash of
	// the HTLC as it's authenticated within the Sphinx packet itself as
	// associated data in order to thwart attempts a replay attacks. In the
	// case of a replay, an attacker is *forced* to use the same payment
	// hash twice, thereby losing their money entirely.
	sphinxPacket, err := p.router.ProcessOnionPacket(onionPkt, rHash)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			return nil, lnwire.CodeTemporaryChannelFailure
		}
	}

	return &sphinxHopIterator{
		nextPacket:      sphinxPacket.NextPacket,
		processedPacket: sphinxPacket,
	}, lnwire.CodeNone
}

// DecodeOnionObfuscator takes an io.Reader which should contain the onion
// packet as original received by a forwarding node and creates an Obfuscator
// instance using the derived shared secret. In the case that en error occurs,
// a lnwire failure code detailing the parsing failure will be returned.
func (p *OnionProcessor) DecodeOnionObfuscator(r io.Reader) (Obfuscator, lnwire.FailCode) {
	onionPkt := &sphinx.OnionPacket{}
	if err := onionPkt.Decode(r); err != nil {
		return nil, lnwire.CodeTemporaryChannelFailure
	}

	onionObfuscator, err := sphinx.NewOnionObfuscator(p.router,
		onionPkt.EphemeralKey)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			return nil, lnwire.CodeTemporaryChannelFailure
		}
	}

	return &FailureObfuscator{
		OnionObfuscator: onionObfuscator,
	}, lnwire.CodeNone
}
<commit_msg>htlcswitch: if we fail to parse and onion error, return the proper lnwire.FailCode<commit_after>package htlcswitch

import (
	"encoding/binary"
	"io"

	"github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/lnwire"
)

// NetworkHop indicates the blockchain network that is intended to be the next
// hop for a forwarded HTLC. The existnce of this field within the
// ForwardingInfo struct enables the ability for HTLC to cross chain-boundaries
// at will.
type NetworkHop uint8

const (
	// BitcoinHop denotes that an HTLC is to be forwarded along the Bitcoin
	// link with the specified short channel ID.
	BitcoinHop NetworkHop = iota

	// LitecoinHop denotes that an HTLC is to be forwarded along the
	// Litecoin link with the specified short channel ID.
	LitecoinHop
)

// String returns the string representation of the target NetworkHop.
func (c NetworkHop) String() string {
	switch c {
	case BitcoinHop:
		return "Bitcoin"
	case LitecoinHop:
		return "Litecoin"
	default:
		return "Kekcoin"
	}
}

var (
	// exitHop is a special "hop" which denotes that an incoming HTLC is
	// meant to pay finally to the receiving node.
	exitHop lnwire.ShortChannelID
)

// ForwardingInfo contains all the information that is necessary to forward and
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
// Forwarding links are to use this information to authenticate the information
// received within the incoming HTLC, to ensure that the prior hop didn't
// tamper with the end-to-end routing information at all.
type ForwardingInfo struct {
	// Network is the target blockchain network that the HTLC will travel
	// over next.
	Network NetworkHop

	// NextHop is the channel ID of the next hop. The received HTLC should
	// be forwarded to this particular channel in order to continue the
	// end-to-end route.
	NextHop lnwire.ShortChannelID

	// AmountToForward is the amount of milli-satoshis that the receiving
	// node should forward to the next hop.
	AmountToForward lnwire.MilliSatoshi

	// OutgoingCTLV is the specified value of the CTLV timelock to be used
	// in the outgoing HTLC.
	OutgoingCTLV uint32

	// TODO(roasbeef): modify sphinx logic to not just discard the
	// remaining bytes, instead should include the rest as excess
}

// HopIterator is an interface that abstracts away the routing information
// included in HTLC's which includes the entirety of the payment path of an
// HTLC. This interface provides two basic method which carry out: how to
// interpret the forwarding information encoded within the HTLC packet, and hop
// to encode the forwarding information for the _next_ hop.
type HopIterator interface {
	// ForwardingInstructions returns the set of fields that detail exactly
	// _how_ this hop should forward the HTLC to the next hop.
	// Additionally, the information encoded within the returned
	// ForwardingInfo is to be used by each hop to authenticate the
	// information given to it by the prior hop.
	ForwardingInstructions() ForwardingInfo

	// EncodeNextHop encodes the onion packet destined for the next hop
	// into the passed io.Writer.
	EncodeNextHop(w io.Writer) error
}

// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
// onion routing to encode the payment route  in such a way so that node might
// see only the next hop in the route..
type sphinxHopIterator struct {
	// nextPacket is the decoded onion packet for the _next_ hop.
	nextPacket *sphinx.OnionPacket

	// processedPacket is the outcome of processing an onion packet. It
	// includes the information required to properly forward the packet to
	// the next hop.
	processedPacket *sphinx.ProcessedPacket
}

// A compile time check to ensure sphinxHopIterator implements the HopIterator
// interface.
var _ HopIterator = (*sphinxHopIterator)(nil)

// Encode encodes iterator and writes it to the writer.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
	return r.nextPacket.Encode(w)
}

// ForwardingInstructions returns the set of fields that detail exactly _how_
// this hop should forward the HTLC to the next hop.  Additionally, the
// information encoded within the returned ForwardingInfo is to be used by each
// hop to authenticate the information given to it by the prior hop.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ForwardingInstructions() ForwardingInfo {
	fwdInst := r.processedPacket.ForwardingInstructions

	var nextHop lnwire.ShortChannelID
	switch r.processedPacket.Action {
	case sphinx.ExitNode:
		nextHop = exitHop
	case sphinx.MoreHops:
		s := binary.BigEndian.Uint64(fwdInst.NextAddress[:])
		nextHop = lnwire.NewShortChanIDFromInt(s)
	}

	return ForwardingInfo{
		Network:         BitcoinHop,
		NextHop:         nextHop,
		AmountToForward: lnwire.MilliSatoshi(fwdInst.ForwardAmount),
		OutgoingCTLV:    fwdInst.OutgoingCltv,
	}
}

// OnionProcessor is responsible for keeping all sphinx dependent parts inside
// and expose only decoding function. With such approach we give freedom for
// subsystems which wants to decode sphinx path to not be dependable from
// sphinx at all.
//
// NOTE: The reason for keeping decoder separated from hop iterator is too
// maintain the hop iterator abstraction. Without it the structures which using
// the hop iterator should contain sphinx router which makes their creations in
// tests dependent from the sphinx internal parts.
type OnionProcessor struct {
	router *sphinx.Router
}

// NewOnionProcessor creates new instance of decoder.
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
	return &OnionProcessor{router}
}

// DecodeHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
// instance using the rHash as the associated data when checking the relevant
// MACs during the decoding process.
func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte) (HopIterator,
	lnwire.FailCode) {

	onionPkt := &sphinx.OnionPacket{}
	if err := onionPkt.Decode(r); err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			return nil, lnwire.CodeTemporaryChannelFailure
		}
	}

	// Attempt to process the Sphinx packet. We include the payment hash of
	// the HTLC as it's authenticated within the Sphinx packet itself as
	// associated data in order to thwart attempts a replay attacks. In the
	// case of a replay, an attacker is *forced* to use the same payment
	// hash twice, thereby losing their money entirely.
	sphinxPacket, err := p.router.ProcessOnionPacket(onionPkt, rHash)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			return nil, lnwire.CodeTemporaryChannelFailure
		}
	}

	return &sphinxHopIterator{
		nextPacket:      sphinxPacket.NextPacket,
		processedPacket: sphinxPacket,
	}, lnwire.CodeNone
}

// DecodeOnionObfuscator takes an io.Reader which should contain the onion
// packet as original received by a forwarding node and creates an Obfuscator
// instance using the derived shared secret. In the case that en error occurs,
// a lnwire failure code detailing the parsing failure will be returned.
func (p *OnionProcessor) DecodeOnionObfuscator(r io.Reader) (Obfuscator, lnwire.FailCode) {
	onionPkt := &sphinx.OnionPacket{}
	if err := onionPkt.Decode(r); err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			return nil, lnwire.CodeTemporaryChannelFailure
		}
	}

	onionObfuscator, err := sphinx.NewOnionObfuscator(p.router,
		onionPkt.EphemeralKey)
	if err != nil {
		switch err {
		case sphinx.ErrInvalidOnionVersion:
			return nil, lnwire.CodeInvalidOnionVersion
		case sphinx.ErrInvalidOnionHMAC:
			return nil, lnwire.CodeInvalidOnionHmac
		case sphinx.ErrInvalidOnionKey:
			return nil, lnwire.CodeInvalidOnionKey
		default:
			return nil, lnwire.CodeTemporaryChannelFailure
		}
	}

	return &FailureObfuscator{
		OnionObfuscator: onionObfuscator,
	}, lnwire.CodeNone
}
<|endoftext|>


<commit_before>// Package tasks provides functions for scheduling
// periodic tasks (e.g. background jobs).
package tasks

import (
	"bytes"
	"fmt"
	"gnd.la/log"
	"gnd.la/mux"
	"gnd.la/runtimeutil"
	"runtime"
	"time"
)

// Task represent a scheduled task.
type Task time.Ticker

// Stop stops the task. After stopping the task, it won't be started
// again but if it's currently running, it will be completed.
func (t *Task) Stop() {
	(*time.Ticker)(t).Stop()
}

func afterTask(name string, started time.Time) {
	if err := recover(); err != nil {
		skip := 2
		if _, ok := err.(runtime.Error); ok {
			// When err is a runtime.Error, there are two
			// additional stack frames inside the runtime
			// which are the ones finally calling panic()
			skip += 2
		}
		var buf bytes.Buffer
		buf.WriteString(fmt.Sprintf("Panic executing task %s: ", name))
		buf.WriteString(fmt.Sprintf("%v", err))
		buf.WriteByte('\n')
		stack := runtimeutil.FormatStack(2)
		location, code := runtimeutil.FormatCaller(skip, 5, true, true)
		if location != "" {
			buf.WriteString("\n At ")
			buf.WriteString(location)
			if code != "" {
				buf.WriteByte('\n')
				buf.WriteString(code)
				buf.WriteByte('\n')
			}
		}
		if stack != "" {
			buf.WriteString("\nStack:\n")
			buf.WriteString(stack)
		}
		log.Error(buf.String())
	}
	end := time.Now()
	log.Debugf("Finished task %s at %v (took %v)", name, end, end.Sub(started))
}

func executeTask(m *mux.Mux, task mux.Handler) {
	name := runtimeutil.FuncName(task)
	ctx := m.NewContext(contextProvider(0))
	defer m.CloseContext(ctx)
	now := time.Now()
	log.Debugf("Starting task %s at %v", name, now)
	defer afterTask(name, now)
	task(ctx)
}

// Schedule schedules a task to be run at the given interval. If the now parameter
// is true, the task is also executed right now (in a goroutine), rather than waiting
// until interval for the first run.
func Schedule(m *mux.Mux, task mux.Handler, interval time.Duration, now bool) *Task {
	ticker := time.NewTicker(interval)
	go func() {
		if now {
			executeTask(m, task)
		}
		for _ = range ticker.C {
			executeTask(m, task)
		}
	}()
	return (*Task)(ticker)
}
<commit_msg>Make the handler the last argument to Schedule<commit_after>// Package tasks provides functions for scheduling
// periodic tasks (e.g. background jobs).
package tasks

import (
	"bytes"
	"fmt"
	"gnd.la/log"
	"gnd.la/mux"
	"gnd.la/runtimeutil"
	"runtime"
	"time"
)

// Task represent a scheduled task.
type Task time.Ticker

// Stop stops the task. After stopping the task, it won't be started
// again but if it's currently running, it will be completed.
func (t *Task) Stop() {
	(*time.Ticker)(t).Stop()
}

func afterTask(name string, started time.Time) {
	if err := recover(); err != nil {
		skip := 2
		if _, ok := err.(runtime.Error); ok {
			// When err is a runtime.Error, there are two
			// additional stack frames inside the runtime
			// which are the ones finally calling panic()
			skip += 2
		}
		var buf bytes.Buffer
		buf.WriteString(fmt.Sprintf("Panic executing task %s: ", name))
		buf.WriteString(fmt.Sprintf("%v", err))
		buf.WriteByte('\n')
		stack := runtimeutil.FormatStack(2)
		location, code := runtimeutil.FormatCaller(skip, 5, true, true)
		if location != "" {
			buf.WriteString("\n At ")
			buf.WriteString(location)
			if code != "" {
				buf.WriteByte('\n')
				buf.WriteString(code)
				buf.WriteByte('\n')
			}
		}
		if stack != "" {
			buf.WriteString("\nStack:\n")
			buf.WriteString(stack)
		}
		log.Error(buf.String())
	}
	end := time.Now()
	log.Debugf("Finished task %s at %v (took %v)", name, end, end.Sub(started))
}

func executeTask(m *mux.Mux, task mux.Handler) {
	name := runtimeutil.FuncName(task)
	ctx := m.NewContext(contextProvider(0))
	defer m.CloseContext(ctx)
	now := time.Now()
	log.Debugf("Starting task %s at %v", name, now)
	defer afterTask(name, now)
	task(ctx)
}

// Schedule schedules a task to be run at the given interval. If the now parameter
// is true, the task is also executed right now (in a goroutine), rather than waiting
// until interval for the first run.
func Schedule(m *mux.Mux, interval time.Duration, now bool, task mux.Handler) *Task {
	ticker := time.NewTicker(interval)
	go func() {
		if now {
			executeTask(m, task)
		}
		for _ = range ticker.C {
			executeTask(m, task)
		}
	}()
	return (*Task)(ticker)
}
<|endoftext|>