<commit_before>/*
 * Simple recommendation engine
 *     Copyright (c) 2014, Christian Muehlhaeuser <muesli@gmail.com>
 *
 *   For license see LICENSE.txt
 */

package regommend

import (
	"errors"
	"log"
	"fmt"
	"sort"
	"sync"
	_ "time"
)

// Structure of a table with items in the engine.
type RegommendTable struct {
	sync.RWMutex

	// The table's name.
	name string
	// All items in the table.
	items map[interface{}]*RegommendItem

	// The logger used for this table.
	logger *log.Logger

	// Callback method triggered when trying to load a non-existing key.
	loadData func(key interface{}) *RegommendItem
	// Callback method triggered when adding a new item to the engine.
	addedItem func(item *RegommendItem)
	// Callback method triggered before deleting an item from the engine.
	aboutToDeleteItem func(item *RegommendItem)
}

// Returns how many items are currently stored in the engine.
func (table *RegommendTable) Count() int {
	table.RLock()
	defer table.RUnlock()
	return len(table.items)
}

// Configures a data-loader callback, which will be called when trying
// to use access a non-existing key.
func (table *RegommendTable) SetDataLoader(f func(interface{}) *RegommendItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// Configures a callback, which will be called every time a new item
// is added to the engine.
func (table *RegommendTable) SetAddedItemCallback(f func(*RegommendItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// Configures a callback, which will be called every time an item
// is about to be removed from the engine.
func (table *RegommendTable) SetAboutToDeleteItemCallback(f func(*RegommendItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

// Sets the logger to be used by this engine table.
func (table *RegommendTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

// Adds a key/value pair to the engine.
// Parameter key is the item's engine-key.
// Parameter data is the item's value.
func (table *RegommendTable) Add(key interface{}, data map[interface{}]float64) *RegommendItem {
	item := CreateRegommendItem(key, data)

	// Add item to engine.
	table.Lock()
	table.items[key] = &item

	// engine values so we don't keep blocking the mutex.
	addedItem := table.addedItem
	table.Unlock()

	// Trigger callback after adding an item to engine.
	if addedItem != nil {
		addedItem(&item)
	}

	return &item
}

// Delete an item from the engine.
func (table *RegommendTable) Delete(key interface{}) (*RegommendItem, error) {
	table.RLock()
	r, ok := table.items[key]
	if !ok {
		table.RUnlock()
		return nil, errors.New("Key not found in engine")
	}

	// engine value so we don't keep blocking the mutex.
	aboutToDeleteItem := table.aboutToDeleteItem
	table.RUnlock()

	// Trigger callbacks before deleting an item from engine.
	if aboutToDeleteItem != nil {
		aboutToDeleteItem(r)
	}

	r.RLock()
	defer r.RUnlock()

	table.Lock()
	defer table.Unlock()
	delete(table.items, key)

	return r, nil
}

// Test whether an item exists in the engine. Unlike the Value method
// Exists neither tries to fetch data via the loadData callback nor
// does it keep the item alive in the engine.
func (table *RegommendTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// Get an item from the engine and mark it to be kept alive.
func (table *RegommendTable) Value(key interface{}) (*RegommendItem, error) {
	table.RLock()
	r, ok := table.items[key]
	loadData := table.loadData
	table.RUnlock()

	if ok {
		return r, nil
	}

	// Item doesn't exist in engine. Try and fetch it with a data-loader.
	if loadData != nil {
		item := loadData(key)
		if item != nil {
			table.Add(key, item.data)
			return item, nil
		}

		return nil, errors.New("Key not found and could not be loaded into engine")
	}

	return nil, errors.New("Key not found in engine")
}

// Delete all items from engine.
func (table *RegommendTable) Flush() {
	table.Lock()
	defer table.Unlock()

	table.log("Flushing table", table.name)

	table.items = make(map[interface{}]*RegommendItem)
}

type DistancePair struct {
	Key interface{}
	Distance float64
}
type DistancePairList []DistancePair

func (p DistancePairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p DistancePairList) Len() int { return len(p) }
func (p DistancePairList) Less(i, j int) bool { return p[i].Distance > p[j].Distance }

func (table *RegommendTable) Recommend(key interface{}) (DistancePairList, error) {
	dists, err := table.Neighbors(key)
	if err != nil {
		return dists, err
	}
	sitem, err := table.Value(key)
	if err != nil {
		return dists, err
	}
	smap := sitem.Data()

	totalDistance := 0.0
	for _, v := range dists {
//		fmt.Println("Comparing to", v.Key, "-", v.Distance)
		totalDistance += v.Distance
	}

	recs := make(map[interface{}]float64)
	for _, v := range dists {
		weight := v.Distance / totalDistance
		if weight <= 0 {
			break
		}
		if weight > 1 {
			weight = 1
		}

		ditem, _ := table.Value(v.Key)
		recMap := ditem.Data()
		for key, x := range recMap {
			_, ok := smap[key]
			if ok {
				// key already knows this item, don't recommend it
				continue
			}

			score, ok := recs[key]
			if ok {
				recs[key] = score + x * weight
			} else {
				recs[key] = x * weight
			}
		}
	}

	recsList := make(DistancePairList, len(recs))
	i := 0
	for key, score := range recs {
		recsList[i] = DistancePair{
			Key: key,
			Distance: score,
		}
		i++
	}
	sort.Sort(recsList)

	return recsList, nil
}

func (table *RegommendTable) Neighbors(key interface{}) (DistancePairList, error) {
	dists := DistancePairList{}

	sitem, err := table.Value(key)
	if err != nil {
		return dists, err
	}
	smap := sitem.Data()

	table.RLock()
	defer table.RUnlock()
	for k, ditem := range table.items {
		if err != nil {
			continue
		}
		if k == key {
			continue
		}

		fmt.Println("Analyzing:", k)
		distance := DistancePair{
			Key: k,
			Distance: cosineSim(smap, ditem.Data()),
		}
		dists = append(dists, distance)
	}
	sort.Sort(dists)

	return dists, nil
}

// Internal logging method for convenience.
func (table *RegommendTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v)
}
<commit_msg>* Use pearson similarity for now.<commit_after>/*
 * Simple recommendation engine
 *     Copyright (c) 2014, Christian Muehlhaeuser <muesli@gmail.com>
 *
 *   For license see LICENSE.txt
 */

package regommend

import (
	"errors"
	"log"
	"fmt"
	"sort"
	"sync"
	_ "time"
)

// Structure of a table with items in the engine.
type RegommendTable struct {
	sync.RWMutex

	// The table's name.
	name string
	// All items in the table.
	items map[interface{}]*RegommendItem

	// The logger used for this table.
	logger *log.Logger

	// Callback method triggered when trying to load a non-existing key.
	loadData func(key interface{}) *RegommendItem
	// Callback method triggered when adding a new item to the engine.
	addedItem func(item *RegommendItem)
	// Callback method triggered before deleting an item from the engine.
	aboutToDeleteItem func(item *RegommendItem)
}

// Returns how many items are currently stored in the engine.
func (table *RegommendTable) Count() int {
	table.RLock()
	defer table.RUnlock()
	return len(table.items)
}

// Configures a data-loader callback, which will be called when trying
// to use access a non-existing key.
func (table *RegommendTable) SetDataLoader(f func(interface{}) *RegommendItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// Configures a callback, which will be called every time a new item
// is added to the engine.
func (table *RegommendTable) SetAddedItemCallback(f func(*RegommendItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// Configures a callback, which will be called every time an item
// is about to be removed from the engine.
func (table *RegommendTable) SetAboutToDeleteItemCallback(f func(*RegommendItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

// Sets the logger to be used by this engine table.
func (table *RegommendTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

// Adds a key/value pair to the engine.
// Parameter key is the item's engine-key.
// Parameter data is the item's value.
func (table *RegommendTable) Add(key interface{}, data map[interface{}]float64) *RegommendItem {
	item := CreateRegommendItem(key, data)

	// Add item to engine.
	table.Lock()
	table.items[key] = &item

	// engine values so we don't keep blocking the mutex.
	addedItem := table.addedItem
	table.Unlock()

	// Trigger callback after adding an item to engine.
	if addedItem != nil {
		addedItem(&item)
	}

	return &item
}

// Delete an item from the engine.
func (table *RegommendTable) Delete(key interface{}) (*RegommendItem, error) {
	table.RLock()
	r, ok := table.items[key]
	if !ok {
		table.RUnlock()
		return nil, errors.New("Key not found in engine")
	}

	// engine value so we don't keep blocking the mutex.
	aboutToDeleteItem := table.aboutToDeleteItem
	table.RUnlock()

	// Trigger callbacks before deleting an item from engine.
	if aboutToDeleteItem != nil {
		aboutToDeleteItem(r)
	}

	r.RLock()
	defer r.RUnlock()

	table.Lock()
	defer table.Unlock()
	delete(table.items, key)

	return r, nil
}

// Test whether an item exists in the engine. Unlike the Value method
// Exists neither tries to fetch data via the loadData callback nor
// does it keep the item alive in the engine.
func (table *RegommendTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// Get an item from the engine and mark it to be kept alive.
func (table *RegommendTable) Value(key interface{}) (*RegommendItem, error) {
	table.RLock()
	r, ok := table.items[key]
	loadData := table.loadData
	table.RUnlock()

	if ok {
		return r, nil
	}

	// Item doesn't exist in engine. Try and fetch it with a data-loader.
	if loadData != nil {
		item := loadData(key)
		if item != nil {
			table.Add(key, item.data)
			return item, nil
		}

		return nil, errors.New("Key not found and could not be loaded into engine")
	}

	return nil, errors.New("Key not found in engine")
}

// Delete all items from engine.
func (table *RegommendTable) Flush() {
	table.Lock()
	defer table.Unlock()

	table.log("Flushing table", table.name)

	table.items = make(map[interface{}]*RegommendItem)
}

type DistancePair struct {
	Key interface{}
	Distance float64
}
type DistancePairList []DistancePair

func (p DistancePairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p DistancePairList) Len() int { return len(p) }
func (p DistancePairList) Less(i, j int) bool { return p[i].Distance > p[j].Distance }

func (table *RegommendTable) Recommend(key interface{}) (DistancePairList, error) {
	dists, err := table.Neighbors(key)
	if err != nil {
		return dists, err
	}
	sitem, err := table.Value(key)
	if err != nil {
		return dists, err
	}
	smap := sitem.Data()

	totalDistance := 0.0
	for _, v := range dists {
//		fmt.Println("Comparing to", v.Key, "-", v.Distance)
		totalDistance += v.Distance
	}

	recs := make(map[interface{}]float64)
	for _, v := range dists {
		weight := v.Distance / totalDistance
		if weight <= 0 {
			break
		}
		if weight > 1 {
			weight = 1
		}

		ditem, _ := table.Value(v.Key)
		recMap := ditem.Data()
		for key, x := range recMap {
			_, ok := smap[key]
			if ok {
				// key already knows this item, don't recommend it
				continue
			}

			score, ok := recs[key]
			if ok {
				recs[key] = score + x * weight
			} else {
				recs[key] = x * weight
			}
		}
	}

	recsList := make(DistancePairList, len(recs))
	i := 0
	for key, score := range recs {
		recsList[i] = DistancePair{
			Key: key,
			Distance: score,
		}
		i++
	}
	sort.Sort(recsList)

	return recsList, nil
}

func (table *RegommendTable) Neighbors(key interface{}) (DistancePairList, error) {
	dists := DistancePairList{}

	sitem, err := table.Value(key)
	if err != nil {
		return dists, err
	}
	smap := sitem.Data()

	table.RLock()
	defer table.RUnlock()
	for k, ditem := range table.items {
		if err != nil {
			continue
		}
		if k == key {
			continue
		}

		fmt.Println("Analyzing:", k)
		distance := DistancePair{
			Key: k,
			Distance: pearsonSim(smap, ditem.Data()),
		}
		dists = append(dists, distance)
	}
	sort.Sort(dists)

	return dists, nil
}

// Internal logging method for convenience.
func (table *RegommendTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v)
}
<|endoftext|>


<commit_before>package docstore

import (
	"reflect"
	"strconv"
)

// flattenList takes a `[]interface{}` and flatten/explode each items a map key.
// e.g.: ["s1", "s2"] => {"0": "s1", "1": "s2"}
func flattenList(l []interface{}, parent, delimiter string) map[string]interface{} {
	out := map[string]interface{}{}
	var key string
	for i, ival := range l {
		if len(parent) > 0 {
			key = parent + delimiter + strconv.Itoa(i)
		} else {
			key = strconv.Itoa(i)
		}
		switch val := ival.(type) {
		case nil, int, float64, string, bool:
			out[key] = val
		case []interface{}:
			tmpout := flattenList(val, key, delimiter)
			for tmpkey, tmpval := range tmpout {
				out[tmpkey] = tmpval
			}
		case map[string]interface{}:
			tmpout := flattenMap(val, key, delimiter)
			for tmpkey, tmpval := range tmpout {
				out[tmpkey] = tmpval
			}

		default:
		}
	}
	return out
}

// flattenMap takes a `map[string]interface{}` and flatten/explode it
// e.g.: {"k1": {"k2": 1}} => {"k1.k2": 1}
func flattenMap(m map[string]interface{}, parent, delimiter string) map[string]interface{} {
	out := map[string]interface{}{}
	for key, ival := range m {
		if len(parent) > 0 {
			key = parent + delimiter + key
		}
		switch val := ival.(type) {
		case nil, int, float64, string, bool:
			out[key] = val
		case []interface{}:
			tmpout := flattenList(val, key, delimiter)
			for tmpk, tmpv := range tmpout {
				out[tmpk] = tmpv
			}
		case map[string]interface{}:
			tmpout := flattenMap(val, key, delimiter)
			for tmpk, tmpv := range tmpout {
				out[tmpk] = tmpv
			}
		default:
		}
	}
	return out
}

// matchQuery takes a MongoDB-like query object and returns wether or not
// the given document match the query.
// The document will be flattened before the checks, to handle the dot-notation.
func matchQuery(query, odoc map[string]interface{}) bool {
	ok := true
	// Flatten the map to handle dot-notation handling
	doc := flattenMap(odoc, "", ".")
	for key, eval := range query {
		switch key {
		case "$or":
			res := false
			for _, iexpr := range eval.([]interface{}) {
				expr := iexpr.(map[string]interface{})
				res = res || matchQuery(expr, doc)
			}
			if !res {
				ok = false
			}
		case "$and":
			res := true
			for _, iexpr := range eval.([]interface{}) {
				expr := iexpr.(map[string]interface{})
				res = res && matchQuery(expr, doc)
			}
			ok = res
		default:
			val, check := doc[key]
			if !check {
				return false
			}
			switch eeval := eval.(type) {
			// basic `{ <field>: <value> }` query
			case nil, int, float64, string, bool, []interface{}:
				ok = ok && reflect.DeepEqual(eval, val)
			// query like `{ <field>: { <$operator>: <value> } }`
			case map[string]interface{}:
				for k, v := range eeval {
					switch k {
					case "$eq":
						ok = ok && reflect.DeepEqual(v, val)
					case "$gt":
						switch vv := v.(type) {
						case float64:
							ok = ok && val.(float64) > vv
						case int:
							ok = ok && val.(float64) > float64(vv)
						default:
							// FIXME(ts) should log a warning or a custom error
							return false
						}
					case "$gte":
						switch vv := v.(type) {
						case float64:
							ok = ok && val.(float64) >= vv
						case int:
							ok = ok && val.(float64) >= float64(vv)
						default:
							// FIXME(ts) should log a warning or a custom error
							return false
						}
					case "$lt":
						switch vv := v.(type) {
						case float64:
							ok = ok && val.(float64) < vv
						case int:
							ok = ok && val.(float64) < float64(vv)
						default:
							// FIXME(ts) should log a warning or a custom error
							return false
						}
					case "$lte":
						switch vv := v.(type) {
						case float64:
							ok = ok && val.(float64) <= vv
						case int:
							ok = ok && val.(float64) <= float64(vv)
						default:
							// FIXME(ts) should log a warning or a custom error
							return false
						}
					default:
						// Unsupported operators
						// FIXME(ts) should log a warning here or a custom error
						return false
					}
				}
			default:
				panic("shouldn't happen")
			}
		}
	}
	return ok
}
<commit_msg>ext/docstore: starting support on array fields<commit_after>package docstore

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

// flattenList takes a `[]interface{}` and flatten/explode each items a map key.
// e.g.: ["s1", "s2"] => {"0": "s1", "1": "s2"}
func flattenList(l []interface{}, parent, delimiter string) map[string]interface{} {
	out := map[string]interface{}{}
	var key string
	for i, ival := range l {
		if len(parent) > 0 {
			key = parent + delimiter + strconv.Itoa(i)
		} else {
			key = strconv.Itoa(i)
		}
		switch val := ival.(type) {
		case nil, int, float64, string, bool:
			out[key] = val
		case []interface{}:
			tmpout := flattenList(val, key, delimiter)
			for tmpkey, tmpval := range tmpout {
				out[tmpkey] = tmpval
			}
		case map[string]interface{}:
			tmpout := flattenMap(val, key, delimiter)
			for tmpkey, tmpval := range tmpout {
				out[tmpkey] = tmpval
			}

		default:
		}
	}
	return out
}

// flattenMap takes a `map[string]interface{}` and flatten/explode it
// e.g.: {"k1": {"k2": 1}} => {"k1.k2": 1}
func flattenMap(m map[string]interface{}, parent, delimiter string) map[string]interface{} {
	out := map[string]interface{}{}
	for key, ival := range m {
		if len(parent) > 0 {
			key = parent + delimiter + key
		}
		switch val := ival.(type) {
		case nil, int, float64, string, bool:
			out[key] = val
		case []interface{}:
			tmpout := flattenList(val, key, delimiter)
			for tmpk, tmpv := range tmpout {
				out[tmpk] = tmpv
			}
		case map[string]interface{}:
			tmpout := flattenMap(val, key, delimiter)
			for tmpk, tmpv := range tmpout {
				out[tmpk] = tmpv
			}
		default:
		}
	}
	return out
}

func getPath(path string, doc map[string]interface{}) interface{} {
	keys := strings.Split(path, ".")
	for i, key := range keys {
		if val, ok := doc[key]; ok {
			if i == len(keys)-1 {
				return val
			}
			if mval, ok := val.(map[string]interface{}); ok {
				doc = mval
			} else {
				return nil
			}
		} else {
			return nil
			// TODO(ts) may be better to return an error to help
			// make the difference between nil value and non existent key
		}
	}
	return doc
}

// matchQuery takes a MongoDB-like query object and returns wether or not
// the given document match the query.
// The document will be flattened before the checks, to handle the dot-notation.
func matchQuery(query, odoc map[string]interface{}) bool {
	ok := true
	// Flatten the map to handle dot-notation handling
	doc := flattenMap(odoc, "", ".")
	for key, eval := range query {
		switch key {
		case "$or":
			res := false
			for _, iexpr := range eval.([]interface{}) {
				expr := iexpr.(map[string]interface{})
				res = res || matchQuery(expr, doc)
			}
			if !res {
				ok = false
			}
		case "$and":
			res := true
			for _, iexpr := range eval.([]interface{}) {
				expr := iexpr.(map[string]interface{})
				res = res && matchQuery(expr, doc)
			}
			ok = res
		default:
			// TODO(ts) make this part cleaner
			// (check orignal doc VS flattend doc)
			val, check := doc[key]
			oval := getPath(key, odoc)
			if !check && oval == nil {
				return false
			}
			// `val` (the value of the queried doc) must be:
			// - a standard type: nil, int, float64, string, bool
			// - a []interface[}
			// It can't ba `map[string]interface{}` since maps are flattened
			switch vval := oval.(type) {
			// If it's an array, the doc is returned if a lest one of the doc match the query
			case []interface{}:
				res := false
				for _, li := range vval {
					res = res || matchQueryValue(eval, li)
				}
				if res {
					return true
				}
			default:
				return matchQueryValue(eval, val)
			}
		}
	}
	return ok
}

// matchQueryValue check the query value againt the doc (doc[queried_key]) and compare.
// /!\ doc must be flattened
func matchQueryValue(eval, val interface{}) bool {
	ok := true
	switch eeval := eval.(type) {
	// basic `{ <field>: <value> }` query
	case nil, int, float64, string, bool, []interface{}:
		ok = ok && reflect.DeepEqual(eval, val)
	// query like `{ <field>: { <$operator>: <value> } }`
	case map[string]interface{}:
		for k, v := range eeval {
			switch k {
			case "$eq":
				ok = ok && reflect.DeepEqual(v, val)
			case "$gt":
				switch vv := v.(type) {
				case float64:
					ok = ok && val.(float64) > vv
				case int:
					ok = ok && val.(float64) > float64(vv)
				default:
					// FIXME(ts) should log a warning or a custom error
					return false
				}
			case "$gte":
				switch vv := v.(type) {
				case float64:
					ok = ok && val.(float64) >= vv
				case int:
					ok = ok && val.(float64) >= float64(vv)
				default:
					// FIXME(ts) should log a warning or a custom error
					return false
				}
			case "$lt":
				switch vv := v.(type) {
				case float64:
					ok = ok && val.(float64) < vv
				case int:
					ok = ok && val.(float64) < float64(vv)
				default:
					// FIXME(ts) should log a warning or a custom error
					return false
				}
			case "$lte":
				switch vv := v.(type) {
				case float64:
					ok = ok && val.(float64) <= vv
				case int:
					ok = ok && val.(float64) <= float64(vv)
				default:
					// FIXME(ts) should log a warning or a custom error
					return false
				}
			default:
				// Unsupported operators
				// FIXME(ts) should log a warning here or a custom error
				return false
			}
		}
	default:
		panic("shouldn't happen")
	}
	return ok
}
<|endoftext|>


<commit_before>// +build e2e

/*
Copyright 2020 The Knative Authors

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

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

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

package ingress

import (
	"net/http"
	"testing"

	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/apimachinery/pkg/util/sets"
	"knative.dev/serving/pkg/apis/networking"
	"knative.dev/serving/pkg/apis/networking/v1alpha1"
	"knative.dev/serving/test"
)

func TestVisibility(t *testing.T) {
	t.Parallel()
	clients := test.Setup(t)

	// Create the private backend
	name, port, cancel := CreateRuntimeService(t, clients, networking.ServicePortNameHTTP1)
	defer cancel()

	privateServiceName := test.ObjectNameForTest(t)
	privateHostName := privateServiceName + "." + test.ServingNamespace + ".svc.cluster.local"
	ingress, client, cancel := CreateIngressReady(t, clients, v1alpha1.IngressSpec{
		Rules: []v1alpha1.IngressRule{{
			Hosts:      []string{privateHostName},
			Visibility: v1alpha1.IngressVisibilityClusterLocal,
			HTTP: &v1alpha1.HTTPIngressRuleValue{
				Paths: []v1alpha1.HTTPIngressPath{{
					Splits: []v1alpha1.IngressBackendSplit{{
						IngressBackend: v1alpha1.IngressBackend{
							ServiceName:      name,
							ServiceNamespace: test.ServingNamespace,
							ServicePort:      intstr.FromInt(port),
						},
					}},
				}},
			},
		}},
	})
	defer cancel()

	// Ensure the service is not publicly accessible
	RuntimeRequestWithStatus(t, client, "http://"+privateHostName, sets.NewInt(http.StatusNotFound))

	loadbalancerAddress := ingress.Status.PrivateLoadBalancer.Ingress[0].DomainInternal
	proxyName, proxyPort, cancel := CreateProxyService(t, clients, privateHostName, loadbalancerAddress)
	defer cancel()

	publicHostName := "publicproxy.example.com"
	_, client, cancel = CreateIngressReady(t, clients, v1alpha1.IngressSpec{
		Rules: []v1alpha1.IngressRule{{
			Hosts:      []string{publicHostName},
			Visibility: v1alpha1.IngressVisibilityExternalIP,
			HTTP: &v1alpha1.HTTPIngressRuleValue{
				Paths: []v1alpha1.HTTPIngressPath{{
					Splits: []v1alpha1.IngressBackendSplit{{
						IngressBackend: v1alpha1.IngressBackend{
							ServiceName:      proxyName,
							ServiceNamespace: test.ServingNamespace,
							ServicePort:      intstr.FromInt(proxyPort),
						},
					}},
				}},
			},
		}},
	})
	defer cancel()

	// Ensure the service is accessible from within the cluster.
	RuntimeRequest(t, client, "http://"+publicHostName)
}
<commit_msg>Add ingress conformance test (private traffic split) (#6638)<commit_after>// +build e2e

/*
Copyright 2020 The Knative Authors

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

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

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

package ingress

import (
	"fmt"
	"math"
	"net/http"
	"testing"

	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/apimachinery/pkg/util/sets"
	"knative.dev/serving/pkg/apis/networking"
	"knative.dev/serving/pkg/apis/networking/v1alpha1"
	"knative.dev/serving/test"
)

func TestVisibility(t *testing.T) {
	t.Parallel()
	clients := test.Setup(t)

	// Create the private backend
	name, port, cancel := CreateRuntimeService(t, clients, networking.ServicePortNameHTTP1)
	defer cancel()

	privateServiceName := test.ObjectNameForTest(t)
	privateHostName := privateServiceName + "." + test.ServingNamespace + ".svc.cluster.local"
	ingress, client, cancel := CreateIngressReady(t, clients, v1alpha1.IngressSpec{
		Rules: []v1alpha1.IngressRule{{
			Hosts:      []string{privateHostName},
			Visibility: v1alpha1.IngressVisibilityClusterLocal,
			HTTP: &v1alpha1.HTTPIngressRuleValue{
				Paths: []v1alpha1.HTTPIngressPath{{
					Splits: []v1alpha1.IngressBackendSplit{{
						IngressBackend: v1alpha1.IngressBackend{
							ServiceName:      name,
							ServiceNamespace: test.ServingNamespace,
							ServicePort:      intstr.FromInt(port),
						},
					}},
				}},
			},
		}},
	})
	defer cancel()

	// Ensure the service is not publicly accessible
	RuntimeRequestWithStatus(t, client, "http://"+privateHostName, sets.NewInt(http.StatusNotFound))

	loadbalancerAddress := ingress.Status.PrivateLoadBalancer.Ingress[0].DomainInternal
	proxyName, proxyPort, cancel := CreateProxyService(t, clients, privateHostName, loadbalancerAddress)
	defer cancel()

	publicHostName := "publicproxy.example.com"
	_, client, cancel = CreateIngressReady(t, clients, v1alpha1.IngressSpec{
		Rules: []v1alpha1.IngressRule{{
			Hosts:      []string{publicHostName},
			Visibility: v1alpha1.IngressVisibilityExternalIP,
			HTTP: &v1alpha1.HTTPIngressRuleValue{
				Paths: []v1alpha1.HTTPIngressPath{{
					Splits: []v1alpha1.IngressBackendSplit{{
						IngressBackend: v1alpha1.IngressBackend{
							ServiceName:      proxyName,
							ServiceNamespace: test.ServingNamespace,
							ServicePort:      intstr.FromInt(proxyPort),
						},
					}},
				}},
			},
		}},
	})
	defer cancel()

	// Ensure the service is accessible from within the cluster.
	RuntimeRequest(t, client, "http://"+publicHostName)
}

func TestVisibilitySplit(t *testing.T) {
	t.Parallel()
	clients := test.Setup(t)

	// Use a post-split injected header to establish which split we are sending traffic to.
	const headerName = "Foo-Bar-Baz"

	backends := make([]v1alpha1.IngressBackendSplit, 0, 10)
	weights := make(map[string]float64, len(backends))

	// Double the percentage of the split each iteration until it would overflow, and then
	// give the last route the remainder.
	percent, total := 1, 0
	for i := 0; i < 10; i++ {
		name, port, cancel := CreateRuntimeService(t, clients, networking.ServicePortNameHTTP1)
		defer cancel()
		backends = append(backends, v1alpha1.IngressBackendSplit{
			IngressBackend: v1alpha1.IngressBackend{
				ServiceName:      name,
				ServiceNamespace: test.ServingNamespace,
				ServicePort:      intstr.FromInt(port),
			},
			// Append different headers to each split, which lets us identify
			// which backend we hit.
			AppendHeaders: map[string]string{
				headerName: name,
			},
			Percent: percent,
		})
		weights[name] = float64(percent)

		total += percent
		percent *= 2
		// Cap the final non-zero bucket so that we total 100%
		// After that, this will zero out remaining buckets.
		if total+percent > 100 {
			percent = 100 - total
		}
	}

	name := test.ObjectNameForTest(t)

	// Create a simple Ingress over the 10 Services.
	privateHostName := fmt.Sprintf("%s.%s.%s", name, test.ServingNamespace, "svc.cluster.local")
	localIngress, client, cancel := CreateIngressReady(t, clients, v1alpha1.IngressSpec{
		Rules: []v1alpha1.IngressRule{{
			Hosts:      []string{privateHostName},
			Visibility: v1alpha1.IngressVisibilityClusterLocal,
			HTTP: &v1alpha1.HTTPIngressRuleValue{
				Paths: []v1alpha1.HTTPIngressPath{{
					Splits: backends,
				}},
			},
		}},
	})
	defer cancel()

	// Ensure we can't connect to the private resources
	RuntimeRequestWithStatus(t, client, "http://"+privateHostName, sets.NewInt(http.StatusNotFound))

	loadbalancerAddress := localIngress.Status.PrivateLoadBalancer.Ingress[0].DomainInternal
	proxyName, proxyPort, cancel := CreateProxyService(t, clients, privateHostName, loadbalancerAddress)
	defer cancel()

	publicHostName := fmt.Sprintf("%s.%s", name, "example.com")
	_, client, cancel = CreateIngressReady(t, clients, v1alpha1.IngressSpec{
		Rules: []v1alpha1.IngressRule{{
			Hosts:      []string{publicHostName},
			Visibility: v1alpha1.IngressVisibilityExternalIP,
			HTTP: &v1alpha1.HTTPIngressRuleValue{
				Paths: []v1alpha1.HTTPIngressPath{{
					Splits: []v1alpha1.IngressBackendSplit{{
						IngressBackend: v1alpha1.IngressBackend{
							ServiceName:      proxyName,
							ServiceNamespace: test.ServingNamespace,
							ServicePort:      intstr.FromInt(proxyPort),
						},
					}},
				}},
			},
		}},
	})
	defer cancel()

	// Create a large enough population of requests that we can reasonably assess how
	// well the Ingress respected the percentage split.
	seen := make(map[string]float64, len(backends))

	const (
		// The total number of requests to make (as a float to avoid conversions in later computations).
		totalRequests = 1000.0
		// The increment to make for each request, so that the values of seen reflect the
		// percentage of the total number of requests we are making.
		increment = 100.0 / totalRequests
		// Allow the Ingress to be within 5% of the configured value.
		margin = 5.0
	)
	for i := 0.0; i < totalRequests; i++ {
		ri := RuntimeRequest(t, client, "http://"+publicHostName)
		if ri == nil {
			continue
		}
		seen[ri.Request.Headers.Get(headerName)] += increment
	}

	for name, want := range weights {
		got := seen[name]
		switch {
		case want == 0.0 && got > 0.0:
			// For 0% targets, we have tighter requirements.
			t.Errorf("Target %q received traffic, wanted none (0%% target).", name)
		case math.Abs(got-want) > margin:
			t.Errorf("Target %q received %f%%, wanted %f +/- %f", name, got, want, margin)
		}
	}
}
<|endoftext|>


<commit_before>/*
Tests for resource abstractions.

Author:		Alastair Hughes
Contact:	<hobbitalastair at yandex dot com>
*/

package main

import (
    "fmt"
    "testing"
    "gopkg.in/DATA-DOG/go-sqlmock.v1"
)

type MockEncoder struct {
    contents []string
}

func (e *MockEncoder) Encode(item interface{}) error {
    e.contents = append(e.contents, fmt.Sprintf("%v", item))
    return nil
}

func TestProjectListPermissions(t *testing.T) {
    l := projectList{"", nil}
    if l.Permissions() != Get | Set | Create {
        t.Errorf("Project list should have all permissions!")
    }
}

func TestProjectListGet(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("opening database: %s", err)
    }

    mock.ExpectQuery(`SELECT .* FROM views WHERE name=?`).WillReturnRows(sqlmock.NewRows([]string{"pid"}).AddRow("0").AddRow("1"))
    mock.ExpectQuery(`SELECT .* FROM owns WHERE name=?`).WillReturnRows(sqlmock.NewRows([]string{"pid"}).AddRow("2"))

    l := projectList{"test", db}
    e := MockEncoder{[]string{}}
    err = l.Get(&e)
    if err != nil {
        t.Errorf("Unexpected error %q", err)
    }
    if len(e.contents) != 3 || e.contents[0] != "0" || e.contents[1] != "1" || e.contents[2] != "2" {
        t.Errorf("Expected '0 1 2', got %q", e.contents)
    }
    err = mock.ExpectationsWereMet()
    if err != nil {
        t.Errorf("Expectations were not met: %q", err)
    }
}
<commit_msg>Added test cases for project.Permissions()<commit_after>/*
Tests for resource abstractions.

Author:		Alastair Hughes
Contact:	<hobbitalastair at yandex dot com>
*/

package main

import (
    "database/sql"
    "fmt"
    "testing"
    "gopkg.in/DATA-DOG/go-sqlmock.v1"
)

type MockEncoder struct {
    contents []string
}

func (e *MockEncoder) Encode(item interface{}) error {
    e.contents = append(e.contents, fmt.Sprintf("%v", item))
    return nil
}

func TestProjectListPermissions(t *testing.T) {
    l := projectList{"", nil}
    if l.Permissions() != Get | Set | Create {
        t.Errorf("Project list should have all permissions!")
    }
}

func TestProjectListGet(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("opening database: %s", err)
    }

    mock.ExpectQuery("SELECT .* FROM views WHERE name=?").WillReturnRows(sqlmock.NewRows([]string{"pid"}).AddRow("0").AddRow("1"))
    mock.ExpectQuery("SELECT .* FROM owns WHERE name=?").WillReturnRows(sqlmock.NewRows([]string{"pid"}).AddRow("2"))

    l := projectList{"test", db}
    e := MockEncoder{[]string{}}
    err = l.Get(&e)
    if err != nil {
        t.Errorf("Unexpected error %q", err)
    }
    if len(e.contents) != 3 || e.contents[0] != "0" || e.contents[1] != "1" || e.contents[2] != "2" {
        t.Errorf("Expected '0 1 2', got %q", e.contents)
    }
    err = mock.ExpectationsWereMet()
    if err != nil {
        t.Errorf("Expectations were not met: %q", err)
    }
}

func TestProjectListSet(t *testing.T) {
    t.Skip("projectList Set is not yet implemented!")
}

func TestProjectListCreate(t *testing.T) {
    t.Skip("projectList Create is implemented elsewhere!")
}

func TestProjectPermissions(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("opening database: %s", err)
    }

    initDB := func(t *testing.T, views, owns, is_manager bool) {
        q := mock.ExpectQuery("SELECT pid FROM views WHERE .*").WillReturnRows(sqlmock.NewRows([]string{"pid"}).AddRow(0))
        if !views {
            q.WillReturnError(sql.ErrNoRows)
        }
        q = mock.ExpectQuery("SELECT pid FROM owns WHERE .*").WillReturnRows(sqlmock.NewRows([]string{"pid"}).AddRow(0))
        if !owns {
            q.WillReturnError(sql.ErrNoRows)
        }
        q = mock.ExpectQuery("SELECT is_manager FROM users WHERE name=?").WillReturnRows(sqlmock.NewRows([]string{"is_manager"}).AddRow(is_manager))
    }

    check := func(t *testing.T, expected int) {
        p, err := NewProject("test", 0, db)
        if err != nil {
            t.Fatalf("Unexpected error %q", err)
        }
        if p == nil {
            t.Fatalf("Returned project is unexpectedly nil!")
        }
        if p.Permissions() != expected {
            t.Errorf("Expected permissions %b, got %b", expected, p.Permissions())
        }
        err = mock.ExpectationsWereMet()
        if err != nil {
            t.Errorf("Expectations were not met: %q", err)
        }
    }

    t.Run("No project", func(t *testing.T) {
        initDB(t, false, false, false)
        check(t, 0)
    })
    t.Run("Manager", func(t *testing.T) {
        initDB(t, false, false, true)
        check(t, Create)
    })
    t.Run("Views", func(t *testing.T) {
        initDB(t, true, false, false)
        check(t, Get)
    })
    t.Run("Owns", func(t *testing.T) {
        initDB(t, false, true, false)
        check(t, Get | Set)
    })
    t.Run("Owns and is a manager", func(t *testing.T) {
        initDB(t, false, true, true)
        check(t, Get | Set | Create)
    })
}
<|endoftext|>


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

package oneview

import (
	"fmt"
	"github.com/HewlettPackard/oneview-golang/ov"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"testing"
)

func TestAccLogicalEnclosure_1(t *testing.T) {
	var logicalEnclosure ov.LogicalEnclosure

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLogicalEnclosureDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccLogicalEnclosure,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLogicalEnclosureExists(
						"oneview_logical_enclosure.test", &logicalEnclosure),
					resource.TestCheckResourceAttr(
						"oneview_logical_enclosure.test", "name", "Terraform le 1",
					),
				),
			},
			{
				ResourceName:      testAccLogicalEnclosure,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccCheckLogicalEnclosureExists(n string, logicalEnclosure *ov.LogicalEnclosure) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found :%v", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		config, err := testProviderConfig()
		if err != nil {
			return err
		}

		testLogicalEnclosure, err := config.ovClient.GetLogicalEnclosureByName(rs.Primary.ID)
		if err != nil {
			return err
		}
		if testLogicalEnclosure.Name != rs.Primary.ID {
			return fmt.Errorf("Instance not found")
		}
		*logicalEnclosure = testLogicalEnclosure
		return nil
	}
}

func testAccLogicalEnclosureDestroy(s *terraform.State) error {
	config := testAccProvider.Meta().(*Config)
	for _, rs := range s.RootModule().Resources {
		if rs.Type != "oneview_logical_enclosure" {
			continue
		}

		testNet, _ := config.ovClient.GetLogicalEnclosureByName(rs.Primary.ID)

		if testNet.Name != "" {
			return fmt.Errorf("NetworkSet still exists")
		}
	}

	return nil
}

var testAccLogicalEnclosure = `
  resource "oneview_logical_enclosure" "test" {
    name = "Terraform le 1"
  }`
<commit_msg>corrected the method name<commit_after>// (C) Copyright 2019 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

package oneview

import (
	"fmt"
	"github.com/HewlettPackard/oneview-golang/ov"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"testing"
)

func TestAccLogicalEnclosure_1(t *testing.T) {
	var logicalEnclosure ov.LogicalEnclosure

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckLogicalEnclosureDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccLogicalEnclosure,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckLogicalEnclosureExists(
						"oneview_logical_enclosure.test", &logicalEnclosure),
					resource.TestCheckResourceAttr(
						"oneview_logical_enclosure.test", "name", "Terraform le 1",
					),
				),
			},
			{
				ResourceName:      testAccLogicalEnclosure,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccCheckLogicalEnclosureExists(n string, logicalEnclosure *ov.LogicalEnclosure) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found :%v", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		config, err := testProviderConfig()
		if err != nil {
			return err
		}

		testLogicalEnclosure, err := config.ovClient.GetLogicalEnclosureByName(rs.Primary.ID)
		if err != nil {
			return err
		}
		if testLogicalEnclosure.Name != rs.Primary.ID {
			return fmt.Errorf("Instance not found")
		}
		*logicalEnclosure = testLogicalEnclosure
		return nil
	}
}

func testAccCheckLogicalEnclosureDestroy(s *terraform.State) error {
	config := testAccProvider.Meta().(*Config)
	for _, rs := range s.RootModule().Resources {
		if rs.Type != "oneview_logical_enclosure" {
			continue
		}

		testNet, _ := config.ovClient.GetLogicalEnclosureByName(rs.Primary.ID)

		if testNet.Name != "" {
			return fmt.Errorf("NetworkSet still exists")
		}
	}

	return nil
}

var testAccLogicalEnclosure = `
  resource "oneview_logical_enclosure" "test" {
    name = "Terraform le 1"
  }`
<|endoftext|>


<commit_before>package fetch

import (
	"io"

	"github.com/slotix/dataflowkit/errs"
)

//RobotsTxtMiddleware checks if scraping of specified resource is allowed by robots.txt
func RobotsTxtMiddleware() ServiceMiddleware {
	return func(next Service) Service {
		return robotstxtMiddleware{next}
	}
}

type robotstxtMiddleware struct {
	Service
}

//Fetch gets response from req.URL, then passes response.URL to Robots.txt validator.
//issue #1 https://github.com/slotix/dataflowkit/issues/1
func (mw robotstxtMiddleware) Fetch(req Request) (io.ReadCloser, error) {
	url := req.getURL()
	//to avoid recursion while retrieving robots.txt
	if !isRobotsTxt(url) {
		robotsData, _ := RobotstxtData(url)
		//robots.txt may be empty but we have to continue processing the page
		if !AllowedByRobots(url, robotsData) {
			//no need a body retrieve to get information about redirects
			r := Request{URL: url, Method: "HEAD"}
			resp, err := fetchRobots(r)
			if err != nil {
				return nil, err
			}
			//if initial URL is not equal to final URL (Redirected) f.e. domains are different
			//then try to fetch robots following by final URL
			//finalURL := resp.URL
			finalURL := resp.Request.URL.String()
			//	finalURL := resp.Request.URL.String()
			if url != finalURL {
				robotsData, err = RobotstxtData(finalURL)
				if err != nil {
					return nil, err
				}
				if !AllowedByRobots(finalURL, robotsData) {
					return nil, &errs.ForbiddenByRobots{finalURL}
				}
			} else {
				return nil, &errs.ForbiddenByRobots{url}
			}
		}

	}

	return mw.Service.Fetch(req)
}
<commit_msg>remove robotstxt mw from fetcher service<commit_after><|endoftext|>


<commit_before>package filters

import (
	"fmt"
	"time"

	"github.com/stripe/unilog/json"
	flag "launchpad.net/gnuflag"
)

const defaultFormat = "2006-01-02 15:04:05.000000"

// TimePrefixFilter prepends a timestamp onto each event line using the specified
// format string, plus an optional newline.
type TimePrefixFilter struct {
	Omit   bool
	Format string
}

// FilterLine prepends the current time, in square brackets with a separating
// space, to the provided log line.
func (f *TimePrefixFilter) FilterLine(line string) string {
	if f.Omit {
		return line
	}
	return fmt.Sprintf("[%s] %s", time.Now().Format(f.getTimeFormat()), line)
}

// FilterJSON is a no-op - TimePrefixFilter does nothing on JSON logs (for now!).
func (f *TimePrefixFilter) FilterJSON(line *json.LogLine) {}

func (f *TimePrefixFilter) getTimeFormat() string {
	if f.Format != "" {
		return f.Format
	}

	return defaultFormat
}

// AddFlags adds time-prefix related flags to the CLI options
func (f *TimePrefixFilter) AddFlags() {
	flag.BoolVar(&f.Omit, "omit-timestamps", false, "Do not prepend timestamps to each line before flushing.")
}
<commit_msg>Time prefix filter didn't even work/match the interface??<commit_after>package filters

import (
	"fmt"
	"time"

	"github.com/stripe/unilog/json"
	flag "launchpad.net/gnuflag"
)

const defaultFormat = "2006-01-02 15:04:05.000000"

// TimePrefixFilter prepends a timestamp onto each event line using the specified
// format string, plus an optional newline.
type TimePrefixFilter struct {
	Omit   bool
	Format string
}

// FilterLine prepends the current time, in square brackets with a separating
// space, to the provided log line.
func (f TimePrefixFilter) FilterLine(line string) string {
	if f.Omit {
		return line
	}
	return fmt.Sprintf("[%s] %s", time.Now().Format(f.getTimeFormat()), line)
}

// FilterJSON is a no-op - TimePrefixFilter does nothing on JSON logs (for now!).
func (f TimePrefixFilter) FilterJSON(line *json.LogLine) {}

func (f *TimePrefixFilter) getTimeFormat() string {
	if f.Format != "" {
		return f.Format
	}

	return defaultFormat
}

// AddFlags adds time-prefix related flags to the CLI options
func (f *TimePrefixFilter) AddFlags() {
	flag.BoolVar(&f.Omit, "omit-timestamps", false, "Do not prepend timestamps to each line before flushing.")
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go 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 flate

import (
	"bytes"
	"crypto/rand"
	"io"
	"io/ioutil"
	"strconv"
	"strings"
	"testing"
)

func TestReset(t *testing.T) {
	ss := []string{
		"lorem ipsum izzle fo rizzle",
		"the quick brown fox jumped over",
	}

	deflated := make([]bytes.Buffer, 2)
	for i, s := range ss {
		w, _ := NewWriter(&deflated[i], 1)
		w.Write([]byte(s))
		w.Close()
	}

	inflated := make([]bytes.Buffer, 2)

	f := NewReader(&deflated[0])
	io.Copy(&inflated[0], f)
	f.(Resetter).Reset(&deflated[1], nil)
	io.Copy(&inflated[1], f)
	f.Close()

	for i, s := range ss {
		if s != inflated[i].String() {
			t.Errorf("inflated[%d]:\ngot  %q\nwant %q", i, inflated[i], s)
		}
	}
}

// Tests ported from zlib/test/infcover.c
type infTest struct {
	hex string
	id  string
	n   int
}

var infTests = []infTest{
	infTest{"0 0 0 0 0", "invalid stored block lengths", 1},
	infTest{"3 0", "fixed", 0},
	infTest{"6", "invalid block type", 1},
	infTest{"1 1 0 fe ff 0", "stored", 0},
	infTest{"fc 0 0", "too many length or distance symbols", 1},
	infTest{"4 0 fe ff", "invalid code lengths set", 1},
	infTest{"4 0 24 49 0", "invalid bit length repeat", 1},
	infTest{"4 0 24 e9 ff ff", "invalid bit length repeat", 1},
	infTest{"4 0 24 e9 ff 6d", "invalid code -- missing end-of-block", 1},
	infTest{"4 80 49 92 24 49 92 24 71 ff ff 93 11 0", "invalid literal/lengths set", 1},
	infTest{"4 80 49 92 24 49 92 24 f b4 ff ff c3 84", "invalid distances set", 1},
	infTest{"4 c0 81 8 0 0 0 0 20 7f eb b 0 0", "invalid literal/length code", 1},
	infTest{"2 7e ff ff", "invalid distance code", 1},
	infTest{"c c0 81 0 0 0 0 0 90 ff 6b 4 0", "invalid distance too far back", 1},

	// also trailer mismatch just in inflate()
	infTest{"1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 1", "incorrect data check", -1},
	infTest{"1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 1", "incorrect length check", -1},
	infTest{"5 c0 21 d 0 0 0 80 b0 fe 6d 2f 91 6c", "pull 17", 0},
	infTest{"5 e0 81 91 24 cb b2 2c 49 e2 f 2e 8b 9a 47 56 9f fb fe ec d2 ff 1f", "long code", 0},
	infTest{"ed c0 1 1 0 0 0 40 20 ff 57 1b 42 2c 4f", "length extra", 0},
	infTest{"ed cf c1 b1 2c 47 10 c4 30 fa 6f 35 1d 1 82 59 3d fb be 2e 2a fc f c", "long distance and extra", 0},
	infTest{"ed c0 81 0 0 0 0 80 a0 fd a9 17 a9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6", "window end", 0},
}

func TestInflate(t *testing.T) {
	for _, test := range infTests {
		hex := strings.Split(test.hex, " ")
		data := make([]byte, len(hex))
		for i, h := range hex {
			b, _ := strconv.ParseInt(h, 16, 32)
			data[i] = byte(b)
		}
		buf := bytes.NewReader(data)
		r := NewReader(buf)

		_, err := io.Copy(ioutil.Discard, r)
		if (test.n == 0 && err == nil) || (test.n != 0 && err != nil) {
			t.Logf("%q: OK:", test.id)
			t.Logf(" - got %v", err)
			continue
		}

		if test.n == 0 && err != nil {
			t.Errorf("%q: Expected no error, but got %v", test.id, err)
			continue
		}

		if test.n != 0 && err == nil {
			t.Errorf("%q:Expected an error, but got none", test.id, err)
			continue
		}
		t.Fatal(test.n, err)
	}

	for _, test := range infOutTests {
		hex := strings.Split(test.hex, " ")
		data := make([]byte, len(hex))
		for i, h := range hex {
			b, _ := strconv.ParseInt(h, 16, 32)
			data[i] = byte(b)
		}
		buf := bytes.NewReader(data)
		r := NewReader(buf)

		_, err := io.Copy(ioutil.Discard, r)
		if test.err == (err != nil) {
			t.Logf("%q: OK:", test.id)
			t.Logf(" - got %v", err)
			continue
		}

		if test.err == false && err != nil {
			t.Errorf("%q: Expected no error, but got %v", test.id, err)
			continue
		}

		if test.err && err == nil {
			t.Errorf("%q: Expected an error, but got none", test.id)
			continue
		}
		t.Fatal(test.err, err)
	}

}

// Tests ported from zlib/test/infcover.c
// Since zlib inflate is push (writer) instead of pull (reader)
// some of the window size tests have been removed, since they
// are irrelevant.
type infOutTest struct {
	hex    string
	id     string
	step   int
	win    int
	length int
	err    bool
}

var infOutTests = []infOutTest{
	infOutTest{"2 8 20 80 0 3 0", "inflate_fast TYPE return", 0, -15, 258, false},
	infOutTest{"63 18 5 40 c 0", "window wrap", 3, -8, 300, false},
	infOutTest{"e5 e0 81 ad 6d cb b2 2c c9 01 1e 59 63 ae 7d ee fb 4d fd b5 35 41 68 ff 7f 0f 0 0 0", "fast length extra bits", 0, -8, 258, true},
	infOutTest{"25 fd 81 b5 6d 59 b6 6a 49 ea af 35 6 34 eb 8c b9 f6 b9 1e ef 67 49 50 fe ff ff 3f 0 0", "fast distance extra bits", 0, -8, 258, true},
	infOutTest{"3 7e 0 0 0 0 0", "fast invalid distance code", 0, -8, 258, true},
	infOutTest{"1b 7 0 0 0 0 0", "fast invalid literal/length code", 0, -8, 258, true},
	infOutTest{"d c7 1 ae eb 38 c 4 41 a0 87 72 de df fb 1f b8 36 b1 38 5d ff ff 0", "fast 2nd level codes and too far back", 0, -8, 258, true},
	infOutTest{"63 18 5 8c 10 8 0 0 0 0", "very common case", 0, -8, 259, false},
	infOutTest{"63 60 60 18 c9 0 8 18 18 18 26 c0 28 0 29 0 0 0", "contiguous and wrap around window", 6, -8, 259, false},
	infOutTest{"63 0 3 0 0 0 0 0", "copy direct from output", 0, -8, 259, false},
	infOutTest{"1f 8b 0 0", "bad gzip method", 0, 31, 0, true},
	infOutTest{"1f 8b 8 80", "bad gzip flags", 0, 31, 0, true},
	infOutTest{"77 85", "bad zlib method", 0, 15, 0, true},
	infOutTest{"78 9c", "bad zlib window size", 0, 8, 0, true},
	infOutTest{"1f 8b 8 1e 0 0 0 0 0 0 1 0 0 0 0 0 0", "bad header crc", 0, 47, 1, true},
	infOutTest{"1f 8b 8 2 0 0 0 0 0 0 1d 26 3 0 0 0 0 0 0 0 0 0", "check gzip length", 0, 47, 0, true},
	infOutTest{"78 90", "bad zlib header check", 0, 47, 0, true},
	infOutTest{"8 b8 0 0 0 1", "need dictionary", 0, 8, 0, true},
	infOutTest{"63 18 68 30 d0 0 0", "force split window update", 4, -8, 259, false},
	infOutTest{"3 0", "use fixed blocks", 0, -15, 1, false},
	infOutTest{"", "bad window size", 0, 1, 0, true},
}

func TestWriteTo(t *testing.T) {
	input := make([]byte, 100000)
	n, err := rand.Read(input)
	if err != nil {
		t.Fatal(err)
	}
	if n != len(input) {
		t.Fatal("did not fill buffer")
	}
	compressed := &bytes.Buffer{}
	w, err := NewWriter(compressed, -2)
	if err != nil {
		t.Fatal(err)
	}
	n, err = w.Write(input)
	if err != nil {
		t.Fatal(err)
	}
	if n != len(input) {
		t.Fatal("did not fill buffer")
	}
	w.Close()
	buf := compressed.Bytes()

	dec := NewReader(bytes.NewBuffer(buf))
	readall, err := ioutil.ReadAll(dec)
	if err != nil {
		t.Fatal(err)
	}
	if len(readall) != len(input) {
		t.Fatal("did not decompress everything")
	}

	dec = NewReader(bytes.NewBuffer(buf))
	wtbuf := &bytes.Buffer{}
	written, err := dec.(io.WriterTo).WriteTo(wtbuf)
	if err != nil {
		t.Fatal(err)
	}
	if written != int64(len(input)) {
		t.Error("Returned length did not match, expected", len(input), "got", written)
	}
	if wtbuf.Len() != len(input) {
		t.Error("Actual Length did not match, expected", len(input), "got", wtbuf.Len())
	}
	if bytes.Compare(wtbuf.Bytes(), input) != 0 {
		t.Fatal("output did not match input")
	}
}
<commit_msg>Ensure that ReadAll functionality doesn't change in the future.<commit_after>// Copyright 2014 The Go 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 flate

import (
	"bytes"
	"crypto/rand"
	"io"
	"io/ioutil"
	"strconv"
	"strings"
	"testing"
)

func TestReset(t *testing.T) {
	ss := []string{
		"lorem ipsum izzle fo rizzle",
		"the quick brown fox jumped over",
	}

	deflated := make([]bytes.Buffer, 2)
	for i, s := range ss {
		w, _ := NewWriter(&deflated[i], 1)
		w.Write([]byte(s))
		w.Close()
	}

	inflated := make([]bytes.Buffer, 2)

	f := NewReader(&deflated[0])
	io.Copy(&inflated[0], f)
	f.(Resetter).Reset(&deflated[1], nil)
	io.Copy(&inflated[1], f)
	f.Close()

	for i, s := range ss {
		if s != inflated[i].String() {
			t.Errorf("inflated[%d]:\ngot  %q\nwant %q", i, inflated[i], s)
		}
	}
}

// Tests ported from zlib/test/infcover.c
type infTest struct {
	hex string
	id  string
	n   int
}

var infTests = []infTest{
	infTest{"0 0 0 0 0", "invalid stored block lengths", 1},
	infTest{"3 0", "fixed", 0},
	infTest{"6", "invalid block type", 1},
	infTest{"1 1 0 fe ff 0", "stored", 0},
	infTest{"fc 0 0", "too many length or distance symbols", 1},
	infTest{"4 0 fe ff", "invalid code lengths set", 1},
	infTest{"4 0 24 49 0", "invalid bit length repeat", 1},
	infTest{"4 0 24 e9 ff ff", "invalid bit length repeat", 1},
	infTest{"4 0 24 e9 ff 6d", "invalid code -- missing end-of-block", 1},
	infTest{"4 80 49 92 24 49 92 24 71 ff ff 93 11 0", "invalid literal/lengths set", 1},
	infTest{"4 80 49 92 24 49 92 24 f b4 ff ff c3 84", "invalid distances set", 1},
	infTest{"4 c0 81 8 0 0 0 0 20 7f eb b 0 0", "invalid literal/length code", 1},
	infTest{"2 7e ff ff", "invalid distance code", 1},
	infTest{"c c0 81 0 0 0 0 0 90 ff 6b 4 0", "invalid distance too far back", 1},

	// also trailer mismatch just in inflate()
	infTest{"1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 1", "incorrect data check", -1},
	infTest{"1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 1", "incorrect length check", -1},
	infTest{"5 c0 21 d 0 0 0 80 b0 fe 6d 2f 91 6c", "pull 17", 0},
	infTest{"5 e0 81 91 24 cb b2 2c 49 e2 f 2e 8b 9a 47 56 9f fb fe ec d2 ff 1f", "long code", 0},
	infTest{"ed c0 1 1 0 0 0 40 20 ff 57 1b 42 2c 4f", "length extra", 0},
	infTest{"ed cf c1 b1 2c 47 10 c4 30 fa 6f 35 1d 1 82 59 3d fb be 2e 2a fc f c", "long distance and extra", 0},
	infTest{"ed c0 81 0 0 0 0 80 a0 fd a9 17 a9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6", "window end", 0},
}

func TestInflate(t *testing.T) {
	for _, test := range infTests {
		hex := strings.Split(test.hex, " ")
		data := make([]byte, len(hex))
		for i, h := range hex {
			b, _ := strconv.ParseInt(h, 16, 32)
			data[i] = byte(b)
		}
		buf := bytes.NewReader(data)
		r := NewReader(buf)

		_, err := io.Copy(ioutil.Discard, r)
		if (test.n == 0 && err == nil) || (test.n != 0 && err != nil) {
			t.Logf("%q: OK:", test.id)
			t.Logf(" - got %v", err)
			continue
		}

		if test.n == 0 && err != nil {
			t.Errorf("%q: Expected no error, but got %v", test.id, err)
			continue
		}

		if test.n != 0 && err == nil {
			t.Errorf("%q:Expected an error, but got none", test.id, err)
			continue
		}
		t.Fatal(test.n, err)
	}

	for _, test := range infOutTests {
		hex := strings.Split(test.hex, " ")
		data := make([]byte, len(hex))
		for i, h := range hex {
			b, _ := strconv.ParseInt(h, 16, 32)
			data[i] = byte(b)
		}
		buf := bytes.NewReader(data)
		r := NewReader(buf)

		_, err := io.Copy(ioutil.Discard, r)
		if test.err == (err != nil) {
			t.Logf("%q: OK:", test.id)
			t.Logf(" - got %v", err)
			continue
		}

		if test.err == false && err != nil {
			t.Errorf("%q: Expected no error, but got %v", test.id, err)
			continue
		}

		if test.err && err == nil {
			t.Errorf("%q: Expected an error, but got none", test.id)
			continue
		}
		t.Fatal(test.err, err)
	}

}

// Tests ported from zlib/test/infcover.c
// Since zlib inflate is push (writer) instead of pull (reader)
// some of the window size tests have been removed, since they
// are irrelevant.
type infOutTest struct {
	hex    string
	id     string
	step   int
	win    int
	length int
	err    bool
}

var infOutTests = []infOutTest{
	infOutTest{"2 8 20 80 0 3 0", "inflate_fast TYPE return", 0, -15, 258, false},
	infOutTest{"63 18 5 40 c 0", "window wrap", 3, -8, 300, false},
	infOutTest{"e5 e0 81 ad 6d cb b2 2c c9 01 1e 59 63 ae 7d ee fb 4d fd b5 35 41 68 ff 7f 0f 0 0 0", "fast length extra bits", 0, -8, 258, true},
	infOutTest{"25 fd 81 b5 6d 59 b6 6a 49 ea af 35 6 34 eb 8c b9 f6 b9 1e ef 67 49 50 fe ff ff 3f 0 0", "fast distance extra bits", 0, -8, 258, true},
	infOutTest{"3 7e 0 0 0 0 0", "fast invalid distance code", 0, -8, 258, true},
	infOutTest{"1b 7 0 0 0 0 0", "fast invalid literal/length code", 0, -8, 258, true},
	infOutTest{"d c7 1 ae eb 38 c 4 41 a0 87 72 de df fb 1f b8 36 b1 38 5d ff ff 0", "fast 2nd level codes and too far back", 0, -8, 258, true},
	infOutTest{"63 18 5 8c 10 8 0 0 0 0", "very common case", 0, -8, 259, false},
	infOutTest{"63 60 60 18 c9 0 8 18 18 18 26 c0 28 0 29 0 0 0", "contiguous and wrap around window", 6, -8, 259, false},
	infOutTest{"63 0 3 0 0 0 0 0", "copy direct from output", 0, -8, 259, false},
	infOutTest{"1f 8b 0 0", "bad gzip method", 0, 31, 0, true},
	infOutTest{"1f 8b 8 80", "bad gzip flags", 0, 31, 0, true},
	infOutTest{"77 85", "bad zlib method", 0, 15, 0, true},
	infOutTest{"78 9c", "bad zlib window size", 0, 8, 0, true},
	infOutTest{"1f 8b 8 1e 0 0 0 0 0 0 1 0 0 0 0 0 0", "bad header crc", 0, 47, 1, true},
	infOutTest{"1f 8b 8 2 0 0 0 0 0 0 1d 26 3 0 0 0 0 0 0 0 0 0", "check gzip length", 0, 47, 0, true},
	infOutTest{"78 90", "bad zlib header check", 0, 47, 0, true},
	infOutTest{"8 b8 0 0 0 1", "need dictionary", 0, 8, 0, true},
	infOutTest{"63 18 68 30 d0 0 0", "force split window update", 4, -8, 259, false},
	infOutTest{"3 0", "use fixed blocks", 0, -15, 1, false},
	infOutTest{"", "bad window size", 0, 1, 0, true},
}

func TestWriteTo(t *testing.T) {
	input := make([]byte, 100000)
	n, err := rand.Read(input)
	if err != nil {
		t.Fatal(err)
	}
	if n != len(input) {
		t.Fatal("did not fill buffer")
	}
	compressed := &bytes.Buffer{}
	w, err := NewWriter(compressed, -2)
	if err != nil {
		t.Fatal(err)
	}
	n, err = w.Write(input)
	if err != nil {
		t.Fatal(err)
	}
	if n != len(input) {
		t.Fatal("did not fill buffer")
	}
	w.Close()
	buf := compressed.Bytes()

	dec := NewReader(bytes.NewBuffer(buf))
	// ReadAll does not use WriteTo, but we wrap it in a NopCloser to be sure.
	readall, err := ioutil.ReadAll(ioutil.NopCloser(dec))
	if err != nil {
		t.Fatal(err)
	}
	if len(readall) != len(input) {
		t.Fatal("did not decompress everything")
	}

	dec = NewReader(bytes.NewBuffer(buf))
	wtbuf := &bytes.Buffer{}
	written, err := dec.(io.WriterTo).WriteTo(wtbuf)
	if err != nil {
		t.Fatal(err)
	}
	if written != int64(len(input)) {
		t.Error("Returned length did not match, expected", len(input), "got", written)
	}
	if wtbuf.Len() != len(input) {
		t.Error("Actual Length did not match, expected", len(input), "got", wtbuf.Len())
	}
	if bytes.Compare(wtbuf.Bytes(), input) != 0 {
		t.Fatal("output did not match input")
	}
}
<|endoftext|>


<commit_before>package libp2pwebtransport

import (
	"bytes"
	"context"
	"crypto/sha256"
	"crypto/tls"
	"fmt"
	"sync"
	"time"

	"github.com/benbjohnson/clock"
	ma "github.com/multiformats/go-multiaddr"
	"github.com/multiformats/go-multibase"
	"github.com/multiformats/go-multihash"
)

// Allow for a bit of clock skew.
// When we generate a certificate, the NotBefore time is set to clockSkewAllowance before the current time.
// Similarly, we stop using a certificate one clockSkewAllowance before its expiry time.
const clockSkewAllowance = time.Hour

type certConfig struct {
	tlsConf *tls.Config
	sha256  [32]byte // cached from the tlsConf
}

func (c *certConfig) Start() time.Time { return c.tlsConf.Certificates[0].Leaf.NotBefore }
func (c *certConfig) End() time.Time   { return c.tlsConf.Certificates[0].Leaf.NotAfter }

func newCertConfig(start, end time.Time) (*certConfig, error) {
	conf, err := getTLSConf(start, end)
	if err != nil {
		return nil, err
	}
	return &certConfig{
		tlsConf: conf,
		sha256:  sha256.Sum256(conf.Certificates[0].Leaf.Raw),
	}, nil
}

// Certificate renewal logic:
// 1. On startup, we generate one cert that is valid from now (-1h, to allow for clock skew), and another
//    cert that is valid from the expiry date of the first certificate (again, with allowance for clock skew).
// 2. Once we reach 1h before expiry of the first certificate, we switch over to the second certificate.
//    At the same time, we stop advertising the certhash of the first cert and generate the next cert.
type certManager struct {
	clock     clock.Clock
	ctx       context.Context
	ctxCancel context.CancelFunc
	refCount  sync.WaitGroup

	mx            sync.RWMutex
	lastConfig    *certConfig // initially nil
	currentConfig *certConfig
	nextConfig    *certConfig // nil until we have passed half the certValidity of the current config
	addrComp      ma.Multiaddr
}

func newCertManager(clock clock.Clock) (*certManager, error) {
	m := &certManager{clock: clock}
	m.ctx, m.ctxCancel = context.WithCancel(context.Background())
	if err := m.init(); err != nil {
		return nil, err
	}

	m.background()
	return m, nil
}

func (m *certManager) init() error {
	start := m.clock.Now().Add(-clockSkewAllowance)
	var err error
	m.nextConfig, err = newCertConfig(start, start.Add(certValidity))
	if err != nil {
		return err
	}
	return m.rollConfig()
}

func (m *certManager) rollConfig() error {
	// We stop using the current certificate clockSkewAllowance before its expiry time.
	// At this point, the next certificate needs to be valid for one clockSkewAllowance.
	nextStart := m.nextConfig.End().Add(-2 * clockSkewAllowance)
	c, err := newCertConfig(nextStart, nextStart.Add(certValidity))
	if err != nil {
		return err
	}
	m.lastConfig = m.currentConfig
	m.currentConfig = m.nextConfig
	m.nextConfig = c
	return m.cacheAddrComponent()
}

func (m *certManager) background() {
	d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(m.clock.Now())
	log.Debugw("setting timer", "duration", d.String())
	t := m.clock.Timer(d)
	m.refCount.Add(1)

	go func() {
		defer m.refCount.Done()
		defer t.Stop()

		for {
			select {
			case <-m.ctx.Done():
				return
			case now := <-t.C:
				m.mx.Lock()
				if err := m.rollConfig(); err != nil {
					log.Errorw("rolling config failed", "error", err)
				}
				d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(now)
				log.Debugw("rolling certificates", "next", d.String())
				t.Reset(d)
				m.mx.Unlock()
			}
		}
	}()
}

func (m *certManager) GetConfig() *tls.Config {
	m.mx.RLock()
	defer m.mx.RUnlock()
	return m.currentConfig.tlsConf
}

func (m *certManager) AddrComponent() ma.Multiaddr {
	m.mx.RLock()
	defer m.mx.RUnlock()
	return m.addrComp
}

func (m *certManager) Verify(hashes []multihash.DecodedMultihash) error {
	for _, h := range hashes {
		if h.Code != multihash.SHA2_256 {
			return fmt.Errorf("expected SHA256 hash, got %d", h.Code)
		}
		if !bytes.Equal(h.Digest, m.currentConfig.sha256[:]) &&
			(m.nextConfig == nil || !bytes.Equal(h.Digest, m.nextConfig.sha256[:])) &&
			(m.lastConfig == nil || !bytes.Equal(h.Digest, m.lastConfig.sha256[:])) {
			return fmt.Errorf("found unexpected hash: %+x", h.Digest)
		}
	}
	return nil
}

func (m *certManager) cacheAddrComponent() error {
	addr, err := m.addrComponentForCert(m.currentConfig.sha256[:])
	if err != nil {
		return err
	}
	if m.nextConfig != nil {
		comp, err := m.addrComponentForCert(m.nextConfig.sha256[:])
		if err != nil {
			return err
		}
		addr = addr.Encapsulate(comp)
	}
	m.addrComp = addr
	return nil
}

func (m *certManager) addrComponentForCert(hash []byte) (ma.Multiaddr, error) {
	mh, err := multihash.Encode(hash, multihash.SHA2_256)
	if err != nil {
		return nil, err
	}
	certStr, err := multibase.Encode(multibase.Base58BTC, mh)
	if err != nil {
		return nil, err
	}
	return ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, certStr)
}

func (m *certManager) Close() error {
	m.ctxCancel()
	m.refCount.Wait()
	return nil
}
<commit_msg>chore: update CI to Go 1.18 / 1.19, update webtransport-go to v0.1.0<commit_after>package libp2pwebtransport

import (
	"bytes"
	"context"
	"crypto/sha256"
	"crypto/tls"
	"fmt"
	"sync"
	"time"

	"github.com/benbjohnson/clock"
	ma "github.com/multiformats/go-multiaddr"
	"github.com/multiformats/go-multibase"
	"github.com/multiformats/go-multihash"
)

// Allow for a bit of clock skew.
// When we generate a certificate, the NotBefore time is set to clockSkewAllowance before the current time.
// Similarly, we stop using a certificate one clockSkewAllowance before its expiry time.
const clockSkewAllowance = time.Hour

type certConfig struct {
	tlsConf *tls.Config
	sha256  [32]byte // cached from the tlsConf
}

func (c *certConfig) Start() time.Time { return c.tlsConf.Certificates[0].Leaf.NotBefore }
func (c *certConfig) End() time.Time   { return c.tlsConf.Certificates[0].Leaf.NotAfter }

func newCertConfig(start, end time.Time) (*certConfig, error) {
	conf, err := getTLSConf(start, end)
	if err != nil {
		return nil, err
	}
	return &certConfig{
		tlsConf: conf,
		sha256:  sha256.Sum256(conf.Certificates[0].Leaf.Raw),
	}, nil
}

// Certificate renewal logic:
//  1. On startup, we generate one cert that is valid from now (-1h, to allow for clock skew), and another
//     cert that is valid from the expiry date of the first certificate (again, with allowance for clock skew).
//  2. Once we reach 1h before expiry of the first certificate, we switch over to the second certificate.
//     At the same time, we stop advertising the certhash of the first cert and generate the next cert.
type certManager struct {
	clock     clock.Clock
	ctx       context.Context
	ctxCancel context.CancelFunc
	refCount  sync.WaitGroup

	mx            sync.RWMutex
	lastConfig    *certConfig // initially nil
	currentConfig *certConfig
	nextConfig    *certConfig // nil until we have passed half the certValidity of the current config
	addrComp      ma.Multiaddr
}

func newCertManager(clock clock.Clock) (*certManager, error) {
	m := &certManager{clock: clock}
	m.ctx, m.ctxCancel = context.WithCancel(context.Background())
	if err := m.init(); err != nil {
		return nil, err
	}

	m.background()
	return m, nil
}

func (m *certManager) init() error {
	start := m.clock.Now().Add(-clockSkewAllowance)
	var err error
	m.nextConfig, err = newCertConfig(start, start.Add(certValidity))
	if err != nil {
		return err
	}
	return m.rollConfig()
}

func (m *certManager) rollConfig() error {
	// We stop using the current certificate clockSkewAllowance before its expiry time.
	// At this point, the next certificate needs to be valid for one clockSkewAllowance.
	nextStart := m.nextConfig.End().Add(-2 * clockSkewAllowance)
	c, err := newCertConfig(nextStart, nextStart.Add(certValidity))
	if err != nil {
		return err
	}
	m.lastConfig = m.currentConfig
	m.currentConfig = m.nextConfig
	m.nextConfig = c
	return m.cacheAddrComponent()
}

func (m *certManager) background() {
	d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(m.clock.Now())
	log.Debugw("setting timer", "duration", d.String())
	t := m.clock.Timer(d)
	m.refCount.Add(1)

	go func() {
		defer m.refCount.Done()
		defer t.Stop()

		for {
			select {
			case <-m.ctx.Done():
				return
			case now := <-t.C:
				m.mx.Lock()
				if err := m.rollConfig(); err != nil {
					log.Errorw("rolling config failed", "error", err)
				}
				d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(now)
				log.Debugw("rolling certificates", "next", d.String())
				t.Reset(d)
				m.mx.Unlock()
			}
		}
	}()
}

func (m *certManager) GetConfig() *tls.Config {
	m.mx.RLock()
	defer m.mx.RUnlock()
	return m.currentConfig.tlsConf
}

func (m *certManager) AddrComponent() ma.Multiaddr {
	m.mx.RLock()
	defer m.mx.RUnlock()
	return m.addrComp
}

func (m *certManager) Verify(hashes []multihash.DecodedMultihash) error {
	for _, h := range hashes {
		if h.Code != multihash.SHA2_256 {
			return fmt.Errorf("expected SHA256 hash, got %d", h.Code)
		}
		if !bytes.Equal(h.Digest, m.currentConfig.sha256[:]) &&
			(m.nextConfig == nil || !bytes.Equal(h.Digest, m.nextConfig.sha256[:])) &&
			(m.lastConfig == nil || !bytes.Equal(h.Digest, m.lastConfig.sha256[:])) {
			return fmt.Errorf("found unexpected hash: %+x", h.Digest)
		}
	}
	return nil
}

func (m *certManager) cacheAddrComponent() error {
	addr, err := m.addrComponentForCert(m.currentConfig.sha256[:])
	if err != nil {
		return err
	}
	if m.nextConfig != nil {
		comp, err := m.addrComponentForCert(m.nextConfig.sha256[:])
		if err != nil {
			return err
		}
		addr = addr.Encapsulate(comp)
	}
	m.addrComp = addr
	return nil
}

func (m *certManager) addrComponentForCert(hash []byte) (ma.Multiaddr, error) {
	mh, err := multihash.Encode(hash, multihash.SHA2_256)
	if err != nil {
		return nil, err
	}
	certStr, err := multibase.Encode(multibase.Base58BTC, mh)
	if err != nil {
		return nil, err
	}
	return ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, certStr)
}

func (m *certManager) Close() error {
	m.ctxCancel()
	m.refCount.Wait()
	return nil
}
<|endoftext|>


<commit_before>package slackRealtime

import (
	"errors"
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"

	"github.com/FogCreek/slack"
	"github.com/FogCreek/victor/pkg/chat"
	"github.com/FogCreek/victor/pkg/events"
)

// TokenLength is the expected length of a Slack API auth token.
const TokenLength = 40

// The Slack Websocket's registered adapter name for the victor framework.
const AdapterName = "slackRealtime"

// Prefix for the user's ID which is used when reading/writing from the bot's store
const userInfoPrefix = AdapterName + "."

const userIDRegexpString = "\\b<?@?(U[[:alnum:]]+)(?:(?:|\\S+)?>?)"

// Match "<@Userid>" and "<@UserID|fullname>"
var userIDRegexp = regexp.MustCompile(userIDRegexpString)

// Match "johndoe", "@johndoe",
// not needed?
// var userIDAndNameRegexp = regexp.MustCompile("\\A@?(\\w+)|" + userIDRegexpString)

// channelGroupInfo is used instead of the slack library's Channel struct since we
// are trying to consider channels and groups to be roughly the same while it
// considers them seperate and provides no way to consolidate them on its own.
//
// This also allows us to throw our information that we don't care about (members, etc.).
type channelGroupInfo struct {
	Name      string
	ID        string
	IsDM      bool
	UserID    string
	IsChannel bool
	// UserID is only stored for IM/DM's so we can then send a user a DM as a
	// response if needed
}

// init registers SlackAdapter to the victor chat framework.
func init() {
	chat.Register(AdapterName, func(r chat.Robot) chat.Adapter {
		config, configSet := r.AdapterConfig()
		if !configSet {
			log.Println("A configuration struct implementing the SlackConfig interface must be set.")
			os.Exit(1)
		}
		sConfig, ok := config.(Config)
		if !ok {
			log.Println("The bot's config must implement the SlackConfig interface.")
			os.Exit(1)
		}
		return &SlackAdapter{
			robot:           r,
			chReceiver:      make(chan slack.SlackEvent),
			token:           sConfig.Token(),
			channelInfo:     make(map[string]channelGroupInfo),
			directMessageID: make(map[string]string),
			userInfo:        make(map[string]slack.User),
		}
	})
}

// Config provides the slack adapter with the necessary
// information to open a websocket connection with the slack Real time API.
type Config interface {
	Token() string
}

// Config implements the SlackRealtimeConfig interface to provide a slack
// adapter with the information it needs to authenticate with slack.
type configImpl struct {
	token string
}

// NewConfig returns a new slack configuration instance using the given token.
func NewConfig(token string) configImpl {
	return configImpl{token: token}
}

// Token returns the slack token.
func (c configImpl) Token() string {
	return c.token
}

// SlackAdapter holds all information needed by the adapter to send/receive messages.
type SlackAdapter struct {
	robot           chat.Robot
	token           string
	instance        *slack.Client
	rtm             *slack.RTM
	chReceiver      chan slack.SlackEvent
	channelInfo     map[string]channelGroupInfo
	directMessageID map[string]string
	userInfo        map[string]slack.User
	domain          string
	botID           string
}

// GetUser will parse the given user ID string and then return the user's
// information as provided by the slack API. This will first try to get the
// user's information from a local cache and then will perform a slack API
// call if the user's information is not cached. Returns nil if the user does
// not exist or if an error occurrs during the slack API call.
func (adapter *SlackAdapter) GetUser(userIDStr string) chat.User {
	if !adapter.IsPotentialUser(userIDStr) {
		log.Printf("%s is not a potential user", userIDStr)
		return nil
	}
	userID := adapter.NormalizeUserID(userIDStr)
	userObj, err := adapter.getUserFromSlack(userID)
	if err != nil {
		log.Println("Error getting user: " + err.Error())
		return nil
	}
	return &chat.BaseUser{
		UserID:    userObj.Id,
		UserName:  userObj.Name,
		UserEmail: userObj.Profile.Email,
		UserIsBot: userObj.IsBot,
	}
}

// IsPotentialUser checks if a given string is potentially referring to a slack
// user. Strings given to this function should be trimmed of leading whitespace
// as it does not account for that (it is meant to be used with the fields
// method on the frameworks calls to handlers which are trimmed).
func (adapter *SlackAdapter) IsPotentialUser(userString string) bool {
	return userIDRegexp.MatchString(userString)
}

// normalizeUserID returns a user's ID without the extra formatting that slack
// might add. This will return "U01234567" for inputs: "U01234567",
// "@U01234567", "<@U01234567>", and "<@U01234567|name>"
func (adapter *SlackAdapter) NormalizeUserID(userID string) string {
	userIDArr := userIDRegexp.FindAllStringSubmatch(userID, 1)
	if len(userIDArr) == 0 {
		return userID
	}
	return userIDArr[0][1]
}

// Run starts the adapter and begins to listen for new messages to send/receive.
// At the moment this will crash the program and print the error messages to a
// log if the connection fails.
func (adapter *SlackAdapter) Run() {
	adapter.instance = slack.New(adapter.token)
	adapter.instance.SetDebug(false)
	adapter.rtm = adapter.instance.NewRTM()
	go adapter.monitorEvents()
	go adapter.rtm.ManageConnection()
}

func (adapter *SlackAdapter) initAdapterInfo(info *slack.Info) {
	// info := adapter.rtm.GetInfo()
	adapter.botID = info.User.Id
	adapter.domain = info.Team.Domain
	for _, channel := range info.Channels {
		if !channel.IsMember {
			continue
		}
		adapter.channelInfo[channel.Id] = channelGroupInfo{
			ID:        channel.Id,
			Name:      channel.Name,
			IsChannel: true,
		}
	}
	for _, group := range info.Groups {
		adapter.channelInfo[group.Id] = channelGroupInfo{
			ID:   group.Id,
			Name: group.Name,
		}
	}
	for _, im := range info.IMs {
		adapter.channelInfo[im.Id] = channelGroupInfo{
			ID:     im.Id,
			Name:   fmt.Sprintf("DM %s", im.Id),
			IsDM:   true,
			UserID: im.UserId,
		}
		adapter.directMessageID[im.UserId] = im.Id
	}
	for _, user := range info.Users {
		adapter.userInfo[user.Id] = user
	}
}

// Stop stops the adapter.
// TODO implement
func (adapter *SlackAdapter) Stop() {
}

// ID returns a unique ID for this adapter. At the moment this just returns
// the slack instance token but could be modified to return a uuid using a
// package such as https://godoc.org/code.google.com/p/go-uuid/uuid
func (adapter *SlackAdapter) ID() string {
	return adapter.token
}

func (adapter *SlackAdapter) getUserFromSlack(userID string) (*slack.User, error) {
	// try to get the stored user info
	user, exists := adapter.userInfo[userID]
	// if it hasn't been stored then perform a slack API call to get it and
	// store it
	if !exists {
		user, err := adapter.instance.GetUserInfo(userID)
		if err != nil {
			log.Println(err.Error())
			return nil, err
		}
		// try to encode it as a json string for storage
		adapter.userInfo[user.Id] = *user
		return user, nil
	}

	return &user, nil
}

func (adapter *SlackAdapter) handleMessage(event *slack.MessageEvent) {
	if len(event.SubType) > 0 {
		return
	}
	user, _ := adapter.getUserFromSlack(event.UserId)
	channel, exists := adapter.channelInfo[event.ChannelId]
	if !exists {
		log.Printf("Unrecognized channel with ID %s", event.Id)
		channel = channelGroupInfo{
			Name: "Unrecognized",
			ID:   event.ChannelId,
		}
	}
	// TODO use error
	if user != nil {
		// ignore any messages that are sent by any bot
		if user.IsBot {
			return
		}
		messageText := adapter.unescapeMessage(event.Text)
		var archiveLink string
		if !channel.IsDM {
			archiveLink = adapter.getArchiveLink(channel.Name, event.Timestamp)
		} else {
			archiveLink = "No archive link for Direct Messages"
		}
		msg := chat.BaseMessage{
			MsgUser: &chat.BaseUser{
				UserID:    user.Id,
				UserName:  user.Name,
				UserEmail: user.Profile.Email,
			},
			MsgText:        messageText,
			MsgChannelID:   channel.ID,
			MsgChannelName: channel.Name,
			MsgIsDirect:    channel.IsDM,
			MsgTimestamp:   strings.SplitN(event.Timestamp, ".", 2)[0],
			MsgArchiveLink: archiveLink,
		}
		adapter.robot.Receive(&msg)
	}
}

const archiveURLFormat = "http://%s.slack.com/archives/%s/p%s"

func (adapter *SlackAdapter) getArchiveLink(channelName, timestamp string) string {
	return fmt.Sprintf(archiveURLFormat, adapter.domain, channelName, strings.Replace(timestamp, ".", "", 1))
}

// Replace all instances of the bot's encoded name with it's actual name.
//
// TODO might want to handle unescaping emails and urls here
func (adapter *SlackAdapter) unescapeMessage(msg string) string {
	userID := getEncodedUserID(adapter.botID)
	if strings.HasPrefix(msg, userID) {
		return strings.Replace(msg, userID, "@"+adapter.robot.Name(), 1)
	}
	return msg
}

// Returns the encoded string version of a user's slack ID.
func getEncodedUserID(userID string) string {
	return fmt.Sprintf("<@%s>", userID)
}

// monitorEvents handles incoming events and filters them to only worry about
// incoming messages.
func (adapter *SlackAdapter) monitorEvents() {
	errorChannel := adapter.robot.ChatErrors()
	for {
		event := <-adapter.rtm.IncomingEvents
		switch e := event.Data.(type) {
		case *slack.InvalidAuthEvent:
			errorChannel <- &events.InvalidAuth{}
		case *slack.ConnectingEvent:
			log.Println(adapter.token + " connecting")
		case *slack.ConnectedEvent:
			log.Println(adapter.token + " connected")
			adapter.initAdapterInfo(e.Info)
		case *slack.SlackWSError:
			errorChannel <- &events.BaseError{
				ErrorObj: e,
			}
		case *slack.DisconnectedEvent:
			errorChannel <- &events.BaseError{
				ErrorObj: errors.New("disconnect"),
			}
		case *slack.MessageEvent:
			go adapter.handleMessage(e)
		case *slack.ChannelJoinedEvent:
			go adapter.joinedChannel(e.Channel, true)
		case *slack.GroupJoinedEvent:
			go adapter.joinedChannel(e.Channel, false)
		case *slack.IMCreatedEvent:
			go adapter.joinedIM(e)
		case *slack.ChannelLeftEvent:
			go adapter.leftChannel(e.ChannelId)
		case *slack.GroupLeftEvent:
			go adapter.leftChannel(e.ChannelId)
		case *slack.IMCloseEvent:
			go adapter.leftIM(e)
		case *slack.TeamDomainChangeEvent:
			go adapter.domainChanged(e)
		case *slack.UserChangeEvent:
			go adapter.userChanged(e.User)
		case *slack.TeamJoinEvent:
			go adapter.userChanged(*e.User)
		}
	}
}

func (adapter *SlackAdapter) userChanged(user slack.User) {
	if user.IsBot {
		return
	}
	adapter.userInfo[user.Id] = user
}

func (adapter *SlackAdapter) domainChanged(event *slack.TeamDomainChangeEvent) {
	adapter.domain = event.Domain
}

func (adapter *SlackAdapter) joinedChannel(channel slack.Channel, isChannel bool) {
	adapter.channelInfo[channel.Id] = channelGroupInfo{
		Name:      channel.Name,
		ID:        channel.Id,
		IsChannel: isChannel,
	}
}

func (adapter *SlackAdapter) joinedIM(event *slack.IMCreatedEvent) {
	adapter.channelInfo[event.Channel.Id] = channelGroupInfo{
		Name:   event.Channel.Name,
		ID:     event.Channel.Id,
		IsDM:   true,
		UserID: event.UserId,
	}
	adapter.directMessageID[event.UserId] = event.Channel.Id
}

func (adapter *SlackAdapter) leftIM(event *slack.IMCloseEvent) {
	adapter.leftChannel(event.ChannelId)
	delete(adapter.directMessageID, event.UserId)
}

func (adapter *SlackAdapter) leftChannel(channelID string) {
	delete(adapter.channelInfo, channelID)
}

// Send sends a message to the given slack channel.
func (adapter *SlackAdapter) Send(channelID, msg string) {
	msgObj := adapter.rtm.NewOutgoingMessage(msg, channelID)
	adapter.rtm.SendMessage(msgObj)
}

// SendDirectMessage sends the given message to the given user in a direct
// (private) message.
func (adapter *SlackAdapter) SendDirectMessage(userID, msg string) {
	channelID, err := adapter.getDirectMessageID(userID)
	if err != nil {
		log.Printf("Error getting direct message channel ID for user \"%s\": %s", userID, err.Error())
		return
	}
	adapter.Send(channelID, msg)
}

func (adapter *SlackAdapter) SendTyping(channelID string) {
	adapter.rtm.SendMessage(&slack.OutgoingMessage{Type: "typing", ChannelId: channelID})
}

func (adapter *SlackAdapter) getDirectMessageID(userID string) (string, error) {
	// need to figure out if the first two bool return values are important
	// https://github.com/nlopes/slack/blob/master/dm.go#L58
	channel, exists := adapter.channelInfo[userID]
	if !exists {
		_, _, channelID, err := adapter.instance.OpenIMChannel(userID)
		return channelID, err
	}
	return channel.ID, nil
}
<commit_msg>Fixed potential user regex<commit_after>package slackRealtime

import (
	"errors"
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"

	"github.com/FogCreek/slack"
	"github.com/FogCreek/victor/pkg/chat"
	"github.com/FogCreek/victor/pkg/events"
)

// TokenLength is the expected length of a Slack API auth token.
const TokenLength = 40

// The Slack Websocket's registered adapter name for the victor framework.
const AdapterName = "slackRealtime"

// Prefix for the user's ID which is used when reading/writing from the bot's store
const userInfoPrefix = AdapterName + "."

const userIDRegexpString = `^<?@?(U[[:alnum:]]+)(?:(?:|\S+)?>?)`

// Match "<@Userid>" and "<@UserID|fullname>"
var userIDRegexp = regexp.MustCompile(userIDRegexpString)

// Match "johndoe", "@johndoe",
// not needed?
// var userIDAndNameRegexp = regexp.MustCompile("\\A@?(\\w+)|" + userIDRegexpString)

// channelGroupInfo is used instead of the slack library's Channel struct since we
// are trying to consider channels and groups to be roughly the same while it
// considers them seperate and provides no way to consolidate them on its own.
//
// This also allows us to throw our information that we don't care about (members, etc.).
type channelGroupInfo struct {
	Name      string
	ID        string
	IsDM      bool
	UserID    string
	IsChannel bool
	// UserID is only stored for IM/DM's so we can then send a user a DM as a
	// response if needed
}

// init registers SlackAdapter to the victor chat framework.
func init() {
	chat.Register(AdapterName, func(r chat.Robot) chat.Adapter {
		config, configSet := r.AdapterConfig()
		if !configSet {
			log.Println("A configuration struct implementing the SlackConfig interface must be set.")
			os.Exit(1)
		}
		sConfig, ok := config.(Config)
		if !ok {
			log.Println("The bot's config must implement the SlackConfig interface.")
			os.Exit(1)
		}
		return &SlackAdapter{
			robot:           r,
			chReceiver:      make(chan slack.SlackEvent),
			token:           sConfig.Token(),
			channelInfo:     make(map[string]channelGroupInfo),
			directMessageID: make(map[string]string),
			userInfo:        make(map[string]slack.User),
		}
	})
}

// Config provides the slack adapter with the necessary
// information to open a websocket connection with the slack Real time API.
type Config interface {
	Token() string
}

// Config implements the SlackRealtimeConfig interface to provide a slack
// adapter with the information it needs to authenticate with slack.
type configImpl struct {
	token string
}

// NewConfig returns a new slack configuration instance using the given token.
func NewConfig(token string) configImpl {
	return configImpl{token: token}
}

// Token returns the slack token.
func (c configImpl) Token() string {
	return c.token
}

// SlackAdapter holds all information needed by the adapter to send/receive messages.
type SlackAdapter struct {
	robot           chat.Robot
	token           string
	instance        *slack.Client
	rtm             *slack.RTM
	chReceiver      chan slack.SlackEvent
	channelInfo     map[string]channelGroupInfo
	directMessageID map[string]string
	userInfo        map[string]slack.User
	domain          string
	botID           string
}

// GetUser will parse the given user ID string and then return the user's
// information as provided by the slack API. This will first try to get the
// user's information from a local cache and then will perform a slack API
// call if the user's information is not cached. Returns nil if the user does
// not exist or if an error occurrs during the slack API call.
func (adapter *SlackAdapter) GetUser(userIDStr string) chat.User {
	if !adapter.IsPotentialUser(userIDStr) {
		log.Printf("%s is not a potential user", userIDStr)
		return nil
	}
	userID := adapter.NormalizeUserID(userIDStr)
	userObj, err := adapter.getUserFromSlack(userID)
	if err != nil {
		log.Println("Error getting user: " + err.Error())
		return nil
	}
	return &chat.BaseUser{
		UserID:    userObj.Id,
		UserName:  userObj.Name,
		UserEmail: userObj.Profile.Email,
		UserIsBot: userObj.IsBot,
	}
}

// IsPotentialUser checks if a given string is potentially referring to a slack
// user. Strings given to this function should be trimmed of leading whitespace
// as it does not account for that (it is meant to be used with the fields
// method on the frameworks calls to handlers which are trimmed).
func (adapter *SlackAdapter) IsPotentialUser(userString string) bool {
	return userIDRegexp.MatchString(userString)
}

// normalizeUserID returns a user's ID without the extra formatting that slack
// might add. This will return "U01234567" for inputs: "U01234567",
// "@U01234567", "<@U01234567>", and "<@U01234567|name>"
func (adapter *SlackAdapter) NormalizeUserID(userID string) string {
	userIDArr := userIDRegexp.FindAllStringSubmatch(userID, 1)
	if len(userIDArr) == 0 {
		return userID
	}
	return userIDArr[0][1]
}

// Run starts the adapter and begins to listen for new messages to send/receive.
// At the moment this will crash the program and print the error messages to a
// log if the connection fails.
func (adapter *SlackAdapter) Run() {
	adapter.instance = slack.New(adapter.token)
	adapter.instance.SetDebug(false)
	adapter.rtm = adapter.instance.NewRTM()
	go adapter.monitorEvents()
	go adapter.rtm.ManageConnection()
}

func (adapter *SlackAdapter) initAdapterInfo(info *slack.Info) {
	// info := adapter.rtm.GetInfo()
	adapter.botID = info.User.Id
	adapter.domain = info.Team.Domain
	for _, channel := range info.Channels {
		if !channel.IsMember {
			continue
		}
		adapter.channelInfo[channel.Id] = channelGroupInfo{
			ID:        channel.Id,
			Name:      channel.Name,
			IsChannel: true,
		}
	}
	for _, group := range info.Groups {
		adapter.channelInfo[group.Id] = channelGroupInfo{
			ID:   group.Id,
			Name: group.Name,
		}
	}
	for _, im := range info.IMs {
		adapter.channelInfo[im.Id] = channelGroupInfo{
			ID:     im.Id,
			Name:   fmt.Sprintf("DM %s", im.Id),
			IsDM:   true,
			UserID: im.UserId,
		}
		adapter.directMessageID[im.UserId] = im.Id
	}
	for _, user := range info.Users {
		adapter.userInfo[user.Id] = user
	}
}

// Stop stops the adapter.
// TODO implement
func (adapter *SlackAdapter) Stop() {
}

// ID returns a unique ID for this adapter. At the moment this just returns
// the slack instance token but could be modified to return a uuid using a
// package such as https://godoc.org/code.google.com/p/go-uuid/uuid
func (adapter *SlackAdapter) ID() string {
	return adapter.token
}

func (adapter *SlackAdapter) getUserFromSlack(userID string) (*slack.User, error) {
	// try to get the stored user info
	user, exists := adapter.userInfo[userID]
	// if it hasn't been stored then perform a slack API call to get it and
	// store it
	if !exists {
		user, err := adapter.instance.GetUserInfo(userID)
		if err != nil {
			log.Println(err.Error())
			return nil, err
		}
		// try to encode it as a json string for storage
		adapter.userInfo[user.Id] = *user
		return user, nil
	}

	return &user, nil
}

func (adapter *SlackAdapter) handleMessage(event *slack.MessageEvent) {
	if len(event.SubType) > 0 {
		return
	}
	user, _ := adapter.getUserFromSlack(event.UserId)
	channel, exists := adapter.channelInfo[event.ChannelId]
	if !exists {
		log.Printf("Unrecognized channel with ID %s", event.Id)
		channel = channelGroupInfo{
			Name: "Unrecognized",
			ID:   event.ChannelId,
		}
	}
	// TODO use error
	if user != nil {
		// ignore any messages that are sent by any bot
		if user.IsBot {
			return
		}
		messageText := adapter.unescapeMessage(event.Text)
		var archiveLink string
		if !channel.IsDM {
			archiveLink = adapter.getArchiveLink(channel.Name, event.Timestamp)
		} else {
			archiveLink = "No archive link for Direct Messages"
		}
		msg := chat.BaseMessage{
			MsgUser: &chat.BaseUser{
				UserID:    user.Id,
				UserName:  user.Name,
				UserEmail: user.Profile.Email,
			},
			MsgText:        messageText,
			MsgChannelID:   channel.ID,
			MsgChannelName: channel.Name,
			MsgIsDirect:    channel.IsDM,
			MsgTimestamp:   strings.SplitN(event.Timestamp, ".", 2)[0],
			MsgArchiveLink: archiveLink,
		}
		adapter.robot.Receive(&msg)
	}
}

const archiveURLFormat = "http://%s.slack.com/archives/%s/p%s"

func (adapter *SlackAdapter) getArchiveLink(channelName, timestamp string) string {
	return fmt.Sprintf(archiveURLFormat, adapter.domain, channelName, strings.Replace(timestamp, ".", "", 1))
}

// Replace all instances of the bot's encoded name with it's actual name.
//
// TODO might want to handle unescaping emails and urls here
func (adapter *SlackAdapter) unescapeMessage(msg string) string {
	userID := getEncodedUserID(adapter.botID)
	if strings.HasPrefix(msg, userID) {
		return strings.Replace(msg, userID, "@"+adapter.robot.Name(), 1)
	}
	return msg
}

// Returns the encoded string version of a user's slack ID.
func getEncodedUserID(userID string) string {
	return fmt.Sprintf("<@%s>", userID)
}

// monitorEvents handles incoming events and filters them to only worry about
// incoming messages.
func (adapter *SlackAdapter) monitorEvents() {
	errorChannel := adapter.robot.ChatErrors()
	for {
		event := <-adapter.rtm.IncomingEvents
		switch e := event.Data.(type) {
		case *slack.InvalidAuthEvent:
			errorChannel <- &events.InvalidAuth{}
		case *slack.ConnectingEvent:
			log.Println(adapter.token + " connecting")
		case *slack.ConnectedEvent:
			log.Println(adapter.token + " connected")
			adapter.initAdapterInfo(e.Info)
		case *slack.SlackWSError:
			errorChannel <- &events.BaseError{
				ErrorObj: e,
			}
		case *slack.DisconnectedEvent:
			errorChannel <- &events.BaseError{
				ErrorObj: errors.New("disconnect"),
			}
		case *slack.MessageEvent:
			go adapter.handleMessage(e)
		case *slack.ChannelJoinedEvent:
			go adapter.joinedChannel(e.Channel, true)
		case *slack.GroupJoinedEvent:
			go adapter.joinedChannel(e.Channel, false)
		case *slack.IMCreatedEvent:
			go adapter.joinedIM(e)
		case *slack.ChannelLeftEvent:
			go adapter.leftChannel(e.ChannelId)
		case *slack.GroupLeftEvent:
			go adapter.leftChannel(e.ChannelId)
		case *slack.IMCloseEvent:
			go adapter.leftIM(e)
		case *slack.TeamDomainChangeEvent:
			go adapter.domainChanged(e)
		case *slack.UserChangeEvent:
			go adapter.userChanged(e.User)
		case *slack.TeamJoinEvent:
			go adapter.userChanged(*e.User)
		}
	}
}

func (adapter *SlackAdapter) userChanged(user slack.User) {
	if user.IsBot {
		return
	}
	adapter.userInfo[user.Id] = user
}

func (adapter *SlackAdapter) domainChanged(event *slack.TeamDomainChangeEvent) {
	adapter.domain = event.Domain
}

func (adapter *SlackAdapter) joinedChannel(channel slack.Channel, isChannel bool) {
	adapter.channelInfo[channel.Id] = channelGroupInfo{
		Name:      channel.Name,
		ID:        channel.Id,
		IsChannel: isChannel,
	}
}

func (adapter *SlackAdapter) joinedIM(event *slack.IMCreatedEvent) {
	adapter.channelInfo[event.Channel.Id] = channelGroupInfo{
		Name:   event.Channel.Name,
		ID:     event.Channel.Id,
		IsDM:   true,
		UserID: event.UserId,
	}
	adapter.directMessageID[event.UserId] = event.Channel.Id
}

func (adapter *SlackAdapter) leftIM(event *slack.IMCloseEvent) {
	adapter.leftChannel(event.ChannelId)
	delete(adapter.directMessageID, event.UserId)
}

func (adapter *SlackAdapter) leftChannel(channelID string) {
	delete(adapter.channelInfo, channelID)
}

// Send sends a message to the given slack channel.
func (adapter *SlackAdapter) Send(channelID, msg string) {
	msgObj := adapter.rtm.NewOutgoingMessage(msg, channelID)
	adapter.rtm.SendMessage(msgObj)
}

// SendDirectMessage sends the given message to the given user in a direct
// (private) message.
func (adapter *SlackAdapter) SendDirectMessage(userID, msg string) {
	channelID, err := adapter.getDirectMessageID(userID)
	if err != nil {
		log.Printf("Error getting direct message channel ID for user \"%s\": %s", userID, err.Error())
		return
	}
	adapter.Send(channelID, msg)
}

func (adapter *SlackAdapter) SendTyping(channelID string) {
	adapter.rtm.SendMessage(&slack.OutgoingMessage{Type: "typing", ChannelId: channelID})
}

func (adapter *SlackAdapter) getDirectMessageID(userID string) (string, error) {
	// need to figure out if the first two bool return values are important
	// https://github.com/nlopes/slack/blob/master/dm.go#L58
	channel, exists := adapter.channelInfo[userID]
	if !exists {
		_, _, channelID, err := adapter.instance.OpenIMChannel(userID)
		return channelID, err
	}
	return channel.ID, nil
}
<|endoftext|>


<commit_before>package rootlessports

import (
	"context"
	coreClients "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
)

func Register(ctx context.Context, serviceController coreClients.ServiceController, httpsPort int) error {
	panic("Rootless is not supported on windows")
}
<commit_msg>Mock out rootlessports on windows<commit_after>package rootlessports

import (
	"context"

	coreClients "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
)

func Register(ctx context.Context, serviceController coreClients.ServiceController, enabled bool, httpsPort int) error {
	panic("Rootless is not supported on windows")
}
<|endoftext|>