<commit_before>// Package flagenv populates flags from environment variables.
package flagenv

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
)

// If set to true, the corresponding environment variable name for a flag will be uppercased.
// ie: For a flag named "foobar", the corresponding environment variable will be "FOOBAR"
var UseUpperCaseFlagNames = false

func contains(list []*flag.Flag, f *flag.Flag) bool {
	for _, i := range list {
		if i == f {
			return true
		}
	}
	return false
}

func parse() (err error) {
	explicit := make([]*flag.Flag, 0)
	all := make([]*flag.Flag, 0)
	flag.Visit(func(f *flag.Flag) {
		explicit = append(explicit, f)
	})

	defer func() {
		if e := recover(); e != nil {
			err = e.(error)
		}
	}()

	flag.VisitAll(func(f *flag.Flag) {
		all = append(all, f)
		if !contains(explicit, f) {
			r := strings.NewReplacer(".", "_", "-", "_")
			name := r.Replace(f.Name)
			if UseUpperCaseFlagNames {
				name = strings.ToUpper(name)
			}
			val := os.Getenv(name)
			if val != "" {
				err := f.Value.Set(val)
				if err != nil {
					panic(fmt.Errorf("Failed to set flag %s with value %s", f.Name, val))
				}
			}
		}
	})

	return nil
}

// For each declared flag, Parse() will get the value of the corresponding
// environment variable and will set it. If dots or dash are presents in the
// flag name, they will be converted to underscores. If you want flag names to
// be converted to uppercase, you can set `UseUpperCaseFlagNames` to `true`.
//
// If Parse fails, a fatal error is issued.
func Parse() {
	if err := parse(); err != nil {
		log.Fatalln(err)
	}
}
<commit_msg>Use map instead of slice for explicit<commit_after>// Package flagenv populates flags from environment variables.
package flagenv

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
)

// If set to true, the corresponding environment variable name for a flag will be uppercased.
// ie: For a flag named "foobar", the corresponding environment variable will be "FOOBAR"
var UseUpperCaseFlagNames = false

func parse() (err error) {
	explicit := make(map[*flag.Flag]bool, 0)
	all := make([]*flag.Flag, 0)
	flag.Visit(func(f *flag.Flag) {
		explicit[f] = true
	})

	defer func() {
		if e := recover(); e != nil {
			err = e.(error)
		}
	}()

	flag.VisitAll(func(f *flag.Flag) {
		all = append(all, f)
		if !explicit[f] {
			r := strings.NewReplacer(".", "_", "-", "_")
			name := r.Replace(f.Name)
			if UseUpperCaseFlagNames {
				name = strings.ToUpper(name)
			}
			val := os.Getenv(name)
			if val != "" {
				err := f.Value.Set(val)
				if err != nil {
					panic(fmt.Errorf("Failed to set flag %s with value %s", f.Name, val))
				}
			}
		}
	})

	return nil
}

// For each declared flag, Parse() will get the value of the corresponding
// environment variable and will set it. If dots or dash are presents in the
// flag name, they will be converted to underscores. If you want flag names to
// be converted to uppercase, you can set `UseUpperCaseFlagNames` to `true`.
//
// If Parse fails, a fatal error is issued.
func Parse() {
	if err := parse(); err != nil {
		log.Fatalln(err)
	}
}
<|endoftext|>


<commit_before>package ros

import (
	"fmt"
	"sync"

	"github.com/ScriptRock/crypto/ssh"
)

type Ros struct {
	sync.Mutex

	Client   *ssh.Client
	Hostname string
	Major    int
	Minor    int
}

func New(client *ssh.Client, hostname string) (*Ros, error) {
	r := Ros{
		Client:   client,
		Hostname: hostname,
	}

	res, err := r.SystemResource()
	if err != nil {
		return nil, err
	}

	if _, ok := res["version"]; !ok {
		return nil, fmt.Errorf("no version found")
	}

	minor, major := RouterOSVersion(res["version"])

	r.Major = major
	r.Minor = minor

	return &r, nil
}
func (r Ros) Id() string {
	return r.Hostname
}

func FormatBool(b bool) string {
	if b {
		return "yes"
	}
	return "no"
}
func ParseBool(x string) bool {
	if x == "yes" {
		return true
	}
	return false
}

func (r Ros) Parse(c Command) (string, error) {
	return c.Parse()
}
func (r Ros) Run(c Command) ([]string, error) {
	return c.Run(r.Client)
}
func (r Ros) Exec(c Command) error {
	return c.Exec(r.Client)
}
func (r Ros) Values(c Command) (map[string]string, error) {
	return c.Values(r.Client)
}
func (r Ros) List(c Command) ([]map[string]string, error) {
	return c.List(r.Client)
}
func (r Ros) First(c Command) (map[string]string, error) {
	return c.First(r.Client)
}
<commit_msg>updated versions and added locking<commit_after>package ros

import (
	"fmt"
	"sync"

	"github.com/ScriptRock/crypto/ssh"
)

type Ros struct {
	Client   *ssh.Client
	Error    error
	Hostname string

	Major int
	Minor int

	mu sync.Mutex
}

func (r *Ros) Version() error {
	if r.Major == 0 {
		res, err := r.SystemResource()
		if err != nil {
			return err
		}
		if _, ok := res["version"]; !ok {
			return fmt.Errorf("no version found")
		}

		major, minor := RouterOSVersion(res["version"])

		r.Major, r.Minor = major, minor
	}
	return nil
}

func (r Ros) Id() string {
	return r.Hostname
}

func FormatBool(b bool) string {
	if b {
		return "yes"
	}
	return "no"
}
func ParseBool(x string) bool {
	if x == "yes" {
		return true
	}
	return false
}

func (r Ros) Parse(c Command) (string, error) {
	return c.Parse()
}
func (r Ros) Run(c Command) ([]string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return c.Run(r.Client)
}
func (r Ros) Exec(c Command) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	return c.Exec(r.Client)
}
func (r Ros) Values(c Command) (map[string]string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return c.Values(r.Client)
}
func (r Ros) List(c Command) ([]map[string]string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return c.List(r.Client)
}
func (r Ros) First(c Command) (map[string]string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return c.First(r.Client)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/jurgen-kluft/go-home/config"
	"github.com/jurgen-kluft/go-home/micro-service"
	"github.com/jurgen-kluft/go-icloud-calendar"
)

// Calendar ...
type Calendar struct {
	name         string
	config       *config.CalendarConfig
	sensors      map[string]config.Csensor
	sensorStates map[string]*config.SensorState
	cals         []*icalendar.Calendar
	update       time.Time
	service      *microservice.Service
}

func new() *Calendar {
	c := &Calendar{}
	c.name = "calendar"
	c.sensors = map[string]config.Csensor{}
	c.sensorStates = map[string]*config.SensorState{}
	return c
}

// initialize  ... create a new Calendar from the given JSON configuration
func (c *Calendar) initialize(jsondata []byte) (err error) {
	c.config, err = config.CalendarConfigFromJSON(jsondata)
	if err != nil {
		c.service.Logger.LogError(c.name, err.Error())
	}
	//c.ccal.print()
	for _, sn := range c.config.Sensors {
		ekey := strings.ToLower(sn.Name)
		c.sensors[ekey] = sn
		sensor := &config.SensorState{Name: ekey, Time: time.Now()}
		if sn.Type == "string" {
			sensor.AddStringAttr(ekey, sn.State)
		} else if sn.Type == "float" {
			value, _ := strconv.ParseFloat(sn.State, 64)
			sensor.AddFloatAttr(ekey, value)
		} else if sn.Type == "bool" {
			value, _ := strconv.ParseBool(sn.State)
			sensor.AddBoolAttr(ekey, value)
		}
		c.sensorStates[ekey] = sensor
	}

	for _, cal := range c.config.Calendars {
		if strings.HasPrefix(cal.URL.String, "http") {
			c.cals = append(c.cals, icalendar.NewURLCalendar(cal.URL.String))
		} else if strings.HasPrefix(cal.URL.String, "file") {
			c.cals = append(c.cals, icalendar.NewFileCalendar(cal.URL.String))
		} else {
			c.service.Logger.LogError(c.name, fmt.Sprintf("Unknown calendar source '%s'", cal.URL))
		}
		c.service.Logger.LogInfo(c.name, cal.URL.String)
	}

	c.update = time.Now()

	return err
}

func (c *Calendar) updateSensorStates(when time.Time) error {
	fmt.Printf("Update calendar events: '%d'\n", len(c.cals))

	for _, cal := range c.cals {
		fmt.Printf("Update calendar events: '%s'\n", cal.Name)

		eventsForDay := cal.GetEventsByDate(when)
		for _, e := range eventsForDay {
			var dname string
			var dstate string
			title := strings.Replace(e.Summary, ":", ".", 3)
			title = strings.Replace(title, "=", " = ", 1)
			n, err := fmt.Sscanf(title, "%s = %s", &dname, &dstate)
			if n == 2 && err == nil {
				fmt.Printf("Parsed: '%s' - '%s' - '%s' - '%s'\n", domain, dproduct, dname, dstate)
				dname = strings.ToLower(strings.Trim(dname, " "))
				dstate = strings.ToLower(strings.Trim(dstate, " "))
				ekey := dname

				sensor, exists := c.sensorStates[ekey]
				if exists {
					sensor.Time = time.Now()
					if sensor.StringAttrs != nil {
						sensor.StringAttrs[0].Value = dstate
					} else if sensor.IntAttrs != nil {
						value, _ := strconv.ParseInt(dstate, 10, 64)
						sensor.IntAttrs[0].Value = value
					} else if sensor.FloatAttrs != nil {
						value, _ := strconv.ParseFloat(dstate, 64)
						sensor.FloatAttrs[0].Value = value
					} else if sensor.BoolAttrs != nil {
						value, _ := strconv.ParseBool(dstate)
						sensor.BoolAttrs[0].Value = value
					}
				}
			}
		}
	}
	return nil
}

func (c *Calendar) load() (err error) {
	for _, cal := range c.cals {
		err = cal.Load()
	}
	return err
}

func timeInRange(when time.Time, rangeBegin time.Time, rangeEnd time.Time) bool {
	t := when.Unix()
	rb := rangeBegin.Unix()
	re := rangeEnd.Unix()
	return t >= rb && t < re
}

func weekOrWeekEndStartEnd(now time.Time) (weekend bool, westart, weend, wdstart, wdend time.Time) {
	day := now.Day()

	westart = now
	weend = now

	wdstart = now
	wdend = now

	if now.Weekday() == time.Friday || now.Weekday() == time.Saturday || now.Weekday() == time.Sunday {
		if now.Weekday() == time.Saturday {
			day--
		} else if now.Weekday() == time.Sunday {
			day -= 2
		}
		westart = time.Date(now.Year(), now.Month(), day, 18, 0, 0, 0, now.Location())
		weend = time.Date(now.Year(), now.Month(), day+2, 18, 0, 0, 0, now.Location())
		if timeInRange(now, westart, weend) {
			weekend = true
			wdstart = weend
			wdend = time.Date(wdstart.Year(), wdstart.Month(), wdstart.Day()+5, 18, 0, 0, 0, now.Location())
		} else {
			weekend = false
			if now.Weekday() == time.Friday {
				wdend = westart
				wdstart = time.Date(wdend.Year(), wdend.Month(), wdend.Day()-4, 18, 0, 0, 0, now.Location())
			} else {
				wdstart = weend
				wdend = time.Date(wdstart.Year(), wdstart.Month(), wdstart.Day()+5, 18, 0, 0, 0, now.Location())
			}
		}
	} else {
		if now.Weekday() == time.Monday {
			day--
		} else if now.Weekday() == time.Tuesday {
			day -= 2
		} else if now.Weekday() == time.Wednesday {
			day -= 3
		} else if now.Weekday() == time.Thursday {
			day -= 4
		} else if now.Weekday() == time.Friday {
			day -= 5
		}

		wdstart = time.Date(now.Year(), now.Month(), day, 18, 0, 0, 0, now.Location())
		wdend = time.Date(now.Year(), now.Month(), day+4, 18, 0, 0, 0, now.Location())

		weekend = false
		westart = wdend
		weend = time.Date(westart.Year(), westart.Month(), westart.Day()+2, 18, 0, 0, 0, now.Location())
	}

	return weekend, westart, weend, wdstart, wdend
}

func (c *Calendar) applyRulesToSensorStates() {
	for _, p := range c.config.Rules {
		var sensor *config.SensorState
		var ifsensor *config.SensorState
		var exists bool
		sensor, exists = c.sensorStates[p.Key]
		if exists && sensor.StringAttrs != nil {
			for _, ifthen := range p.IfThen {
				ifsensor, exists = c.sensorStates[ifthen.Key]
				if exists && ifsensor.StringAttrs != nil {
					ifthenValue := ifsensor.StringAttrs[0].Value
					if ifthenValue == ifthen.State {
						sensor.StringAttrs[0].Value = p.State
					}
				} else if exists && ifsensor.BoolAttrs != nil {
					ifthenValue := ifsensor.BoolAttrs[0].Value
					if ifthenValue && ("true" == ifthen.State) {
						sensor.StringAttrs[0].Value = p.State
					} else if !ifthenValue && ("false" == ifthen.State) {
						sensor.StringAttrs[0].Value = p.State
					}
				} else {
					c.service.Logger.LogError(c.name, fmt.Sprintf("Logical error when applying rules to sensor states (%s)", p.Key+", "+p.State))
				}
			}
		}
	}
}

func (c *Calendar) publishSensorState(name string, sensorjsonbytes []byte) {
	c.service.Pubsub.Publish(fmt.Sprintf("state/sensor/%s/", name), sensorjsonbytes)
}

// Process will update 'events' from the calendar
func (c *Calendar) Process() {
	var err error
	now := time.Now()

	if now.Unix() >= c.update.Unix() {
		// Download again after 15 minutes
		c.update = time.Unix(now.Unix()+1*60, 0)

		// Download calendars
		fmt.Println("CALENDAR: LOAD")
		err := c.load()
		if err != nil {
			c.service.Logger.LogError(c.name, err.Error())
			return
		}
	}

	// Other general states
	weekendsensor, exists := c.sensorStates["weekend"]
	if exists {
		if weekendsensor.BoolAttrs != nil {
			weekend, _, _, _, _ := weekOrWeekEndStartEnd(now)
			weekendsensor.BoolAttrs[0].Value = weekend
			sensorjsonbytes, err := weekendsensor.ToJSON()
			if err == nil {
				c.publishSensorState("weekend", sensorjsonbytes)
			} else {
				c.service.Logger.LogError(c.name, err.Error())
			}
		}
	}

	// Update sensors and apply configured rules to sensors
	err = c.updateSensorStates(now)
	if err == nil {
		c.applyRulesToSensorStates()

		// Publish sensor states
		for _, ss := range c.sensorStates {
			sensorjsonbytes, err := ss.ToJSON()
			if err == nil {
				c.publishSensorState(ss.Name, sensorjsonbytes)
			} else {
				c.service.Logger.LogError(c.name, err.Error())
			}
		}
	} else {
		c.service.Logger.LogError(c.name, err.Error())
	}
}

func main() {
	register := []string{"config/calendar/"}
	subscribe := []string{"config/calendar/", "config/request/"}

	m := microservice.New("calendar")
	m.RegisterAndSubscribe(register, subscribe)

	c := new()
	c.service = m

	m.RegisterHandler("config/calendar/", func(m *microservice.Service, topic string, msg []byte) bool {
		m.Logger.LogInfo(m.Name, "received configuration")
		err := c.initialize(msg)
		if err != nil {
			c = nil
			m.Logger.LogError(c.name, err.Error())
		} else {
			// Register emitter channel for every sensor
			for _, ss := range c.sensorStates {
				if err = m.Register(fmt.Sprintf("state/sensor/%s/", ss.Name)); err != nil {
					m.Logger.LogError("pubsub", err.Error())
				}
			}
		}
		return true
	})

	tickCount := 0
	m.RegisterHandler("tick/", func(m *microservice.Service, topic string, msg []byte) bool {
		if tickCount%5 == 0 {
			if c != nil && c.config == nil {
				m.Pubsub.PublishStr("config/request/", m.Name)
			}
		}
		if tickCount%30 == 0 {
			if c != nil && c.config != nil {
				c.Process()
			}
		}
		tickCount++
		return true
	})

	m.Loop()
}
<commit_msg>* fix printf<commit_after>package main

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/jurgen-kluft/go-home/config"
	"github.com/jurgen-kluft/go-home/micro-service"
	"github.com/jurgen-kluft/go-icloud-calendar"
)

// Calendar ...
type Calendar struct {
	name         string
	config       *config.CalendarConfig
	sensors      map[string]config.Csensor
	sensorStates map[string]*config.SensorState
	cals         []*icalendar.Calendar
	update       time.Time
	service      *microservice.Service
}

func new() *Calendar {
	c := &Calendar{}
	c.name = "calendar"
	c.sensors = map[string]config.Csensor{}
	c.sensorStates = map[string]*config.SensorState{}
	return c
}

// initialize  ... create a new Calendar from the given JSON configuration
func (c *Calendar) initialize(jsondata []byte) (err error) {
	c.config, err = config.CalendarConfigFromJSON(jsondata)
	if err != nil {
		c.service.Logger.LogError(c.name, err.Error())
	}
	//c.ccal.print()
	for _, sn := range c.config.Sensors {
		ekey := strings.ToLower(sn.Name)
		c.sensors[ekey] = sn
		sensor := &config.SensorState{Name: ekey, Time: time.Now()}
		if sn.Type == "string" {
			sensor.AddStringAttr(ekey, sn.State)
		} else if sn.Type == "float" {
			value, _ := strconv.ParseFloat(sn.State, 64)
			sensor.AddFloatAttr(ekey, value)
		} else if sn.Type == "bool" {
			value, _ := strconv.ParseBool(sn.State)
			sensor.AddBoolAttr(ekey, value)
		}
		c.sensorStates[ekey] = sensor
	}

	for _, cal := range c.config.Calendars {
		if strings.HasPrefix(cal.URL.String, "http") {
			c.cals = append(c.cals, icalendar.NewURLCalendar(cal.URL.String))
		} else if strings.HasPrefix(cal.URL.String, "file") {
			c.cals = append(c.cals, icalendar.NewFileCalendar(cal.URL.String))
		} else {
			c.service.Logger.LogError(c.name, fmt.Sprintf("Unknown calendar source '%s'", cal.URL))
		}
		c.service.Logger.LogInfo(c.name, cal.URL.String)
	}

	c.update = time.Now()

	return err
}

func (c *Calendar) updateSensorStates(when time.Time) error {
	fmt.Printf("Update calendar events: '%d'\n", len(c.cals))

	for _, cal := range c.cals {
		fmt.Printf("Update calendar events: '%s'\n", cal.Name)

		eventsForDay := cal.GetEventsByDate(when)
		for _, e := range eventsForDay {
			var dname string
			var dstate string
			title := strings.Replace(e.Summary, ":", ".", 3)
			title = strings.Replace(title, "=", " = ", 1)
			n, err := fmt.Sscanf(title, "%s = %s", &dname, &dstate)
			if n == 2 && err == nil {
				fmt.Printf("Parsed: '%s' - '%s' - '%s' - '%s'\n", e.Summary, title, dname, dstate)
				dname = strings.ToLower(strings.Trim(dname, " "))
				dstate = strings.ToLower(strings.Trim(dstate, " "))
				ekey := dname

				sensor, exists := c.sensorStates[ekey]
				if exists {
					sensor.Time = time.Now()
					if sensor.StringAttrs != nil {
						sensor.StringAttrs[0].Value = dstate
					} else if sensor.IntAttrs != nil {
						value, _ := strconv.ParseInt(dstate, 10, 64)
						sensor.IntAttrs[0].Value = value
					} else if sensor.FloatAttrs != nil {
						value, _ := strconv.ParseFloat(dstate, 64)
						sensor.FloatAttrs[0].Value = value
					} else if sensor.BoolAttrs != nil {
						value, _ := strconv.ParseBool(dstate)
						sensor.BoolAttrs[0].Value = value
					}
				}
			}
		}
	}
	return nil
}

func (c *Calendar) load() (err error) {
	for _, cal := range c.cals {
		err = cal.Load()
	}
	return err
}

func timeInRange(when time.Time, rangeBegin time.Time, rangeEnd time.Time) bool {
	t := when.Unix()
	rb := rangeBegin.Unix()
	re := rangeEnd.Unix()
	return t >= rb && t < re
}

func weekOrWeekEndStartEnd(now time.Time) (weekend bool, westart, weend, wdstart, wdend time.Time) {
	day := now.Day()

	westart = now
	weend = now

	wdstart = now
	wdend = now

	if now.Weekday() == time.Friday || now.Weekday() == time.Saturday || now.Weekday() == time.Sunday {
		if now.Weekday() == time.Saturday {
			day--
		} else if now.Weekday() == time.Sunday {
			day -= 2
		}
		westart = time.Date(now.Year(), now.Month(), day, 18, 0, 0, 0, now.Location())
		weend = time.Date(now.Year(), now.Month(), day+2, 18, 0, 0, 0, now.Location())
		if timeInRange(now, westart, weend) {
			weekend = true
			wdstart = weend
			wdend = time.Date(wdstart.Year(), wdstart.Month(), wdstart.Day()+5, 18, 0, 0, 0, now.Location())
		} else {
			weekend = false
			if now.Weekday() == time.Friday {
				wdend = westart
				wdstart = time.Date(wdend.Year(), wdend.Month(), wdend.Day()-4, 18, 0, 0, 0, now.Location())
			} else {
				wdstart = weend
				wdend = time.Date(wdstart.Year(), wdstart.Month(), wdstart.Day()+5, 18, 0, 0, 0, now.Location())
			}
		}
	} else {
		if now.Weekday() == time.Monday {
			day--
		} else if now.Weekday() == time.Tuesday {
			day -= 2
		} else if now.Weekday() == time.Wednesday {
			day -= 3
		} else if now.Weekday() == time.Thursday {
			day -= 4
		} else if now.Weekday() == time.Friday {
			day -= 5
		}

		wdstart = time.Date(now.Year(), now.Month(), day, 18, 0, 0, 0, now.Location())
		wdend = time.Date(now.Year(), now.Month(), day+4, 18, 0, 0, 0, now.Location())

		weekend = false
		westart = wdend
		weend = time.Date(westart.Year(), westart.Month(), westart.Day()+2, 18, 0, 0, 0, now.Location())
	}

	return weekend, westart, weend, wdstart, wdend
}

func (c *Calendar) applyRulesToSensorStates() {
	for _, p := range c.config.Rules {
		var sensor *config.SensorState
		var ifsensor *config.SensorState
		var exists bool
		sensor, exists = c.sensorStates[p.Key]
		if exists && sensor.StringAttrs != nil {
			for _, ifthen := range p.IfThen {
				ifsensor, exists = c.sensorStates[ifthen.Key]
				if exists && ifsensor.StringAttrs != nil {
					ifthenValue := ifsensor.StringAttrs[0].Value
					if ifthenValue == ifthen.State {
						sensor.StringAttrs[0].Value = p.State
					}
				} else if exists && ifsensor.BoolAttrs != nil {
					ifthenValue := ifsensor.BoolAttrs[0].Value
					if ifthenValue && ("true" == ifthen.State) {
						sensor.StringAttrs[0].Value = p.State
					} else if !ifthenValue && ("false" == ifthen.State) {
						sensor.StringAttrs[0].Value = p.State
					}
				} else {
					c.service.Logger.LogError(c.name, fmt.Sprintf("Logical error when applying rules to sensor states (%s)", p.Key+", "+p.State))
				}
			}
		}
	}
}

func (c *Calendar) publishSensorState(name string, sensorjsonbytes []byte) {
	c.service.Pubsub.Publish(fmt.Sprintf("state/sensor/%s/", name), sensorjsonbytes)
}

// Process will update 'events' from the calendar
func (c *Calendar) Process() {
	var err error
	now := time.Now()

	if now.Unix() >= c.update.Unix() {
		// Download again after 15 minutes
		c.update = time.Unix(now.Unix()+1*60, 0)

		// Download calendars
		fmt.Println("CALENDAR: LOAD")
		err := c.load()
		if err != nil {
			c.service.Logger.LogError(c.name, err.Error())
			return
		}
	}

	// Other general states
	weekendsensor, exists := c.sensorStates["weekend"]
	if exists {
		if weekendsensor.BoolAttrs != nil {
			weekend, _, _, _, _ := weekOrWeekEndStartEnd(now)
			weekendsensor.BoolAttrs[0].Value = weekend
			sensorjsonbytes, err := weekendsensor.ToJSON()
			if err == nil {
				c.publishSensorState("weekend", sensorjsonbytes)
			} else {
				c.service.Logger.LogError(c.name, err.Error())
			}
		}
	}

	// Update sensors and apply configured rules to sensors
	err = c.updateSensorStates(now)
	if err == nil {
		c.applyRulesToSensorStates()

		// Publish sensor states
		for _, ss := range c.sensorStates {
			sensorjsonbytes, err := ss.ToJSON()
			if err == nil {
				c.publishSensorState(ss.Name, sensorjsonbytes)
			} else {
				c.service.Logger.LogError(c.name, err.Error())
			}
		}
	} else {
		c.service.Logger.LogError(c.name, err.Error())
	}
}

func main() {
	register := []string{"config/calendar/"}
	subscribe := []string{"config/calendar/", "config/request/"}

	m := microservice.New("calendar")
	m.RegisterAndSubscribe(register, subscribe)

	c := new()
	c.service = m

	m.RegisterHandler("config/calendar/", func(m *microservice.Service, topic string, msg []byte) bool {
		m.Logger.LogInfo(m.Name, "received configuration")
		err := c.initialize(msg)
		if err != nil {
			c = nil
			m.Logger.LogError(c.name, err.Error())
		} else {
			// Register emitter channel for every sensor
			for _, ss := range c.sensorStates {
				if err = m.Register(fmt.Sprintf("state/sensor/%s/", ss.Name)); err != nil {
					m.Logger.LogError("pubsub", err.Error())
				}
			}
		}
		return true
	})

	tickCount := 0
	m.RegisterHandler("tick/", func(m *microservice.Service, topic string, msg []byte) bool {
		if tickCount%5 == 0 {
			if c != nil && c.config == nil {
				m.Pubsub.PublishStr("config/request/", m.Name)
			}
		}
		if tickCount%5 == 0 {
			if c != nil && c.config != nil {
				c.Process()
			}
		}
		tickCount++
		return true
	})

	m.Loop()
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Foursquare Labs Inc.

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"log"
	"math/rand"
	"strings"

	"github.com/apache/thrift/lib/go/thrift"
	"github.com/foursquare/fsgo/net/thriftrpc"
	"github.com/foursquare/fsgo/report"
	"github.com/foursquare/quiver/gen"
	"github.com/foursquare/quiver/hfile"
	"github.com/foursquare/quiver/util"
)

func WrapHttpRpcHandler(cs *hfile.CollectionSet, stats *report.Recorder) *thriftrpc.ThriftOverHTTPHandler {
	return thriftrpc.NewThriftOverHTTPHandler(WrapProcessor(cs, stats), stats)
}

func WrapProcessor(cs *hfile.CollectionSet, stats *report.Recorder) thrift.TProcessor {
	return thriftrpc.AddLogging(gen.NewHFileServiceProcessor(&ThriftRpcImpl{cs}), stats, Settings.debug)
}

type ThriftRpcImpl struct {
	*hfile.CollectionSet
}

func (cs *ThriftRpcImpl) GetValuesSingle(req *gen.SingleHFileKeyRequest) (r *gen.SingleHFileKeyResponse, err error) {
	if Settings.debug {
		log.Printf("[GetValuesSingle] %s (%d keys)\n", *req.HfileName, len(req.SortedKeys))
	}
	hfile, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	reader := hfile.GetScanner()
	// TODO: clients should request strict during dev/testing?
	reader.EnforceKeyOrder = false
	defer reader.Release()

	res := new(gen.SingleHFileKeyResponse)
	res.Values = make(map[int32][]byte)
	found := int32(0)

	for idx, key := range req.SortedKeys {
		if Settings.debug {
			log.Printf("[GetValuesSingle] key: %s\n", hex.EncodeToString(key))
		}
		if idx > 0 && bytes.Equal(req.SortedKeys[idx-1], key) {
			if prev, ok := res.Values[int32(idx-1)]; ok {
				res.Values[int32(idx)] = prev
				found++
			}
			continue
		}

		if !hfile.MightContain(key) {
			continue
		}

		value, err, ok := reader.GetFirst(key)
		if err != nil {
			return nil, err
		}
		if ok {
			found++
			if !req.GetCountOnly() {
				res.Values[int32(idx)] = value
			}
		}
	}

	if Settings.debug {
		log.Printf("[GetValuesSingle] %s found %d of %d.\n", *req.HfileName, found, len(req.SortedKeys))
	}
	res.KeyCount = &found
	return res, nil
}

func (cs *ThriftRpcImpl) GetValuesMulti(req *gen.SingleHFileKeyRequest) (r *gen.MultiHFileKeyResponse, err error) {
	if Settings.debug {
		log.Println("[GetValuesMulti]", len(req.SortedKeys))
	}

	hfile, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	reader := hfile.GetScanner()
	defer reader.Release()

	res := new(gen.MultiHFileKeyResponse)
	res.Values = make(map[int32][][]byte)
	found := int32(0)

	for idx, key := range req.SortedKeys {
		if !hfile.MightContain(key) {
			continue
		}
		values, err := reader.GetAll(key)
		if err != nil {
			return nil, err
		}
		if len(values) > 0 {
			found += int32(len(values))
			if req.PerKeyValueLimit != nil {
				res.Values[int32(idx)] = values[:req.GetPerKeyValueLimit()]
			} else {
				res.Values[int32(idx)] = values
			}
		}
	}

	res.KeyCount = &found
	return res, nil

}

func (cs *ThriftRpcImpl) GetValuesForPrefixes(req *gen.PrefixRequest) (r *gen.PrefixResponse, err error) {
	res := new(gen.PrefixResponse)
	if reader, err := cs.ReaderFor(*req.HfileName); err != nil {
		return nil, err
	} else {
		i := reader.GetIterator()
		defer i.Release()
		limit := int32(0)
		if req.ValueLimit != nil {
			limit = *req.ValueLimit
		}
		if res.Values, res.LastKey, err = i.AllForPrefixes(req.SortedKeys, limit, req.LastKey); err != nil {
			return nil, err
		} else {
			return res, nil
		}
	}
}

func (cs *ThriftRpcImpl) GetValuesMultiSplitKeys(req *gen.MultiHFileSplitKeyRequest) (r *gen.KeyToValuesResponse, err error) {
	res := make(map[string][][]byte)
	reader, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	scanner := reader.GetScanner()
	defer scanner.Release()

	for _, parts := range util.RevProduct(req.SplitKey) {
		// TODO(davidt): avoid allocing concated key by adding split-key search lower down.
		key := bytes.Join(parts, nil)

		if values, err := scanner.GetAll(key); err != nil {
			return nil, err
		} else if len(values) > 0 {
			res[string(key)] = values
		}
	}
	return &gen.KeyToValuesResponse{res}, nil
}

func (cs *ThriftRpcImpl) GetIterator(req *gen.IteratorRequest) (*gen.IteratorResponse, error) {
	// 	HfileName     *string `thrift:"hfileName,1" json:"hfileName"`
	// 	IncludeValues *bool   `thrift:"includeValues,2" json:"includeValues"`
	// 	LastKey       []byte  `thrift:"lastKey,3" json:"lastKey"`
	// 	SkipKeys      *int32  `thrift:"skipKeys,4" json:"skipKeys"`
	// 	ResponseLimit *int32  `thrift:"responseLimit,5" json:"responseLimit"`
	// 	EndKey        []byte  `thrift:"endKey,6" json:"endKey"`
	var err error

	if req.ResponseLimit == nil {
		return nil, fmt.Errorf("Missing limit.")
	}
	limit := int(*req.ResponseLimit)

	reader, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	it := reader.GetIterator()
	defer it.Release()

	remaining := false

	if req.LastKey != nil {
		remaining, err = it.Seek(req.LastKey)
	} else {
		remaining, err = it.Next()
	}

	if err != nil {
		return nil, err
	}

	res := new(gen.IteratorResponse)

	if !remaining {
		return res, nil
	}

	skipKeys := int32(0)
	lastKey := it.Key()

	if toSkip := req.GetSkipKeys(); toSkip > 0 {
		for i := int32(0); i < toSkip && remaining; i++ {
			if bytes.Equal(lastKey, it.Key()) {
				skipKeys = skipKeys + 1
			} else {
				skipKeys = 0
			}

			lastKey = it.Key()

			remaining, err = it.Next()
			if err != nil {
				return nil, err
			}
		}
		if !remaining {
			return res, nil
		}
	}

	if req.EndKey != nil {
		remaining = remaining && !hfile.After(it.Key(), req.EndKey)
	}

	r := make([]*gen.KeyValueItem, 0)
	for i := 0; i < limit && remaining; i++ {
		v := []byte{}
		if req.IncludeValues == nil || *req.IncludeValues {
			v = it.Value()
		}
		r = append(r, &gen.KeyValueItem{it.Key(), v})

		if bytes.Equal(lastKey, it.Key()) {
			skipKeys = skipKeys + 1
		} else {
			skipKeys = 1
		}
		lastKey = it.Key()

		remaining, err = it.Next()
		if err != nil {
			return nil, err
		}
		if req.EndKey != nil {
			remaining = remaining && !hfile.After(it.Key(), req.EndKey)
		}
	}
	return &gen.IteratorResponse{r, lastKey, &skipKeys}, nil
}

func GetCollectionInfo(r *hfile.Reader, keySampleSize int) (*gen.HFileInfo, error) {
	i := new(gen.HFileInfo)
	i.Name = &r.Name
	i.Path = &r.SourcePath
	c := int64(r.EntryCount)
	i.NumElements = &c
	i.FirstKey, _ = r.FirstKey()

	if keySampleSize > 0 {
		it := r.GetIterator()
		defer it.Release()

		pr := float64(keySampleSize) / float64(c)
		buf := make([][]byte, keySampleSize)
		found := 0
		next, err := it.Next()
		for next && found < keySampleSize {
			if rand.Float64() < pr {
				buf[found] = it.Key()
				found++
			}
			next, err = it.Next()
			if err != nil {
				return nil, err
			}
		}
		buf = buf[:found]
		i.RandomKeys = buf
	}
	if Settings.debug {
		log.Printf("[GetCollectionInfo] %v (%d keys)\n", i, len(i.RandomKeys))
	}
	return i, nil
}

func (cs *ThriftRpcImpl) getInfo(req *gen.InfoRequest, allowRandom bool) (r []*gen.HFileInfo, err error) {
	if req == nil {
		return nil, fmt.Errorf("null request!")
	}
	if Settings.debug {
		log.Println("[GetInfo]", req.GetHfileName())
	}
	require := ""
	if req.IsSetHfileName() {
		require := req.GetHfileName()
		if require != "" && !strings.ContainsRune(require, '/') {
			require = require + "/"
		}
	}

	sample := 0
	if req.IsSetNumRandomKeys() && allowRandom {
		sample = int(*req.NumRandomKeys)
	}

	for name, reader := range cs.Collections {
		if require == "" || strings.HasPrefix(name, require) {
			if i, err := GetCollectionInfo(reader, sample); err != nil {
				return nil, err
			} else {
				r = append(r, i)
			}
		}
	}

	return r, nil
}

func (cs *ThriftRpcImpl) GetInfo(req *gen.InfoRequest) (r []*gen.HFileInfo, err error) {
	return cs.getInfo(req, false)
}

func (cs *ThriftRpcImpl) ScanCollectionAndSampleKeys(req *gen.InfoRequest) (r []*gen.HFileInfo, err error) {
	return cs.getInfo(req, true)
}

func (cs *ThriftRpcImpl) TestTimeout(waitInMillis int32) (r int32, err error) {
	return 0, fmt.Errorf("Not implemented")
}
<commit_msg>presize result maps for GetValuesSingle/Multi<commit_after>// Copyright (C) 2015 Foursquare Labs Inc.

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"log"
	"math/rand"
	"strings"

	"github.com/apache/thrift/lib/go/thrift"
	"github.com/foursquare/fsgo/net/thriftrpc"
	"github.com/foursquare/fsgo/report"
	"github.com/foursquare/quiver/gen"
	"github.com/foursquare/quiver/hfile"
	"github.com/foursquare/quiver/util"
)

func WrapHttpRpcHandler(cs *hfile.CollectionSet, stats *report.Recorder) *thriftrpc.ThriftOverHTTPHandler {
	return thriftrpc.NewThriftOverHTTPHandler(WrapProcessor(cs, stats), stats)
}

func WrapProcessor(cs *hfile.CollectionSet, stats *report.Recorder) thrift.TProcessor {
	return thriftrpc.AddLogging(gen.NewHFileServiceProcessor(&ThriftRpcImpl{cs}), stats, Settings.debug)
}

type ThriftRpcImpl struct {
	*hfile.CollectionSet
}

func (cs *ThriftRpcImpl) GetValuesSingle(req *gen.SingleHFileKeyRequest) (r *gen.SingleHFileKeyResponse, err error) {
	if Settings.debug {
		log.Printf("[GetValuesSingle] %s (%d keys)\n", *req.HfileName, len(req.SortedKeys))
	}
	hfile, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	reader := hfile.GetScanner()
	// TODO: clients should request strict during dev/testing?
	reader.EnforceKeyOrder = false
	defer reader.Release()

	res := new(gen.SingleHFileKeyResponse)
	res.Values = make(map[int32][]byte, len(req.SortedKeys))
	found := int32(0)

	for idx, key := range req.SortedKeys {
		if Settings.debug {
			log.Printf("[GetValuesSingle] key: %s\n", hex.EncodeToString(key))
		}
		if idx > 0 && bytes.Equal(req.SortedKeys[idx-1], key) {
			if prev, ok := res.Values[int32(idx-1)]; ok {
				res.Values[int32(idx)] = prev
				found++
			}
			continue
		}

		if !hfile.MightContain(key) {
			continue
		}

		value, err, ok := reader.GetFirst(key)
		if err != nil {
			return nil, err
		}
		if ok {
			found++
			if !req.GetCountOnly() {
				res.Values[int32(idx)] = value
			}
		}
	}

	if Settings.debug {
		log.Printf("[GetValuesSingle] %s found %d of %d.\n", *req.HfileName, found, len(req.SortedKeys))
	}
	res.KeyCount = &found
	return res, nil
}

func (cs *ThriftRpcImpl) GetValuesMulti(req *gen.SingleHFileKeyRequest) (r *gen.MultiHFileKeyResponse, err error) {
	if Settings.debug {
		log.Println("[GetValuesMulti]", len(req.SortedKeys))
	}

	hfile, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	reader := hfile.GetScanner()
	defer reader.Release()

	res := new(gen.MultiHFileKeyResponse)
	res.Values = make(map[int32][][]byte, len(req.SortedKeys))
	found := int32(0)

	for idx, key := range req.SortedKeys {
		if !hfile.MightContain(key) {
			continue
		}
		values, err := reader.GetAll(key)
		if err != nil {
			return nil, err
		}
		if len(values) > 0 {
			found += int32(len(values))
			if req.PerKeyValueLimit != nil {
				res.Values[int32(idx)] = values[:req.GetPerKeyValueLimit()]
			} else {
				res.Values[int32(idx)] = values
			}
		}
	}

	res.KeyCount = &found
	return res, nil

}

func (cs *ThriftRpcImpl) GetValuesForPrefixes(req *gen.PrefixRequest) (r *gen.PrefixResponse, err error) {
	res := new(gen.PrefixResponse)
	if reader, err := cs.ReaderFor(*req.HfileName); err != nil {
		return nil, err
	} else {
		i := reader.GetIterator()
		defer i.Release()
		limit := int32(0)
		if req.ValueLimit != nil {
			limit = *req.ValueLimit
		}
		if res.Values, res.LastKey, err = i.AllForPrefixes(req.SortedKeys, limit, req.LastKey); err != nil {
			return nil, err
		} else {
			return res, nil
		}
	}
}

func (cs *ThriftRpcImpl) GetValuesMultiSplitKeys(req *gen.MultiHFileSplitKeyRequest) (r *gen.KeyToValuesResponse, err error) {
	res := make(map[string][][]byte)
	reader, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	scanner := reader.GetScanner()
	defer scanner.Release()

	for _, parts := range util.RevProduct(req.SplitKey) {
		// TODO(davidt): avoid allocing concated key by adding split-key search lower down.
		key := bytes.Join(parts, nil)

		if values, err := scanner.GetAll(key); err != nil {
			return nil, err
		} else if len(values) > 0 {
			res[string(key)] = values
		}
	}
	return &gen.KeyToValuesResponse{res}, nil
}

func (cs *ThriftRpcImpl) GetIterator(req *gen.IteratorRequest) (*gen.IteratorResponse, error) {
	// 	HfileName     *string `thrift:"hfileName,1" json:"hfileName"`
	// 	IncludeValues *bool   `thrift:"includeValues,2" json:"includeValues"`
	// 	LastKey       []byte  `thrift:"lastKey,3" json:"lastKey"`
	// 	SkipKeys      *int32  `thrift:"skipKeys,4" json:"skipKeys"`
	// 	ResponseLimit *int32  `thrift:"responseLimit,5" json:"responseLimit"`
	// 	EndKey        []byte  `thrift:"endKey,6" json:"endKey"`
	var err error

	if req.ResponseLimit == nil {
		return nil, fmt.Errorf("Missing limit.")
	}
	limit := int(*req.ResponseLimit)

	reader, err := cs.ReaderFor(*req.HfileName)
	if err != nil {
		return nil, err
	}
	it := reader.GetIterator()
	defer it.Release()

	remaining := false

	if req.LastKey != nil {
		remaining, err = it.Seek(req.LastKey)
	} else {
		remaining, err = it.Next()
	}

	if err != nil {
		return nil, err
	}

	res := new(gen.IteratorResponse)

	if !remaining {
		return res, nil
	}

	skipKeys := int32(0)
	lastKey := it.Key()

	if toSkip := req.GetSkipKeys(); toSkip > 0 {
		for i := int32(0); i < toSkip && remaining; i++ {
			if bytes.Equal(lastKey, it.Key()) {
				skipKeys = skipKeys + 1
			} else {
				skipKeys = 0
			}

			lastKey = it.Key()

			remaining, err = it.Next()
			if err != nil {
				return nil, err
			}
		}
		if !remaining {
			return res, nil
		}
	}

	if req.EndKey != nil {
		remaining = remaining && !hfile.After(it.Key(), req.EndKey)
	}

	r := make([]*gen.KeyValueItem, 0)
	for i := 0; i < limit && remaining; i++ {
		v := []byte{}
		if req.IncludeValues == nil || *req.IncludeValues {
			v = it.Value()
		}
		r = append(r, &gen.KeyValueItem{it.Key(), v})

		if bytes.Equal(lastKey, it.Key()) {
			skipKeys = skipKeys + 1
		} else {
			skipKeys = 1
		}
		lastKey = it.Key()

		remaining, err = it.Next()
		if err != nil {
			return nil, err
		}
		if req.EndKey != nil {
			remaining = remaining && !hfile.After(it.Key(), req.EndKey)
		}
	}
	return &gen.IteratorResponse{r, lastKey, &skipKeys}, nil
}

func GetCollectionInfo(r *hfile.Reader, keySampleSize int) (*gen.HFileInfo, error) {
	i := new(gen.HFileInfo)
	i.Name = &r.Name
	i.Path = &r.SourcePath
	c := int64(r.EntryCount)
	i.NumElements = &c
	i.FirstKey, _ = r.FirstKey()

	if keySampleSize > 0 {
		it := r.GetIterator()
		defer it.Release()

		pr := float64(keySampleSize) / float64(c)
		buf := make([][]byte, keySampleSize)
		found := 0
		next, err := it.Next()
		for next && found < keySampleSize {
			if rand.Float64() < pr {
				buf[found] = it.Key()
				found++
			}
			next, err = it.Next()
			if err != nil {
				return nil, err
			}
		}
		buf = buf[:found]
		i.RandomKeys = buf
	}
	if Settings.debug {
		log.Printf("[GetCollectionInfo] %v (%d keys)\n", i, len(i.RandomKeys))
	}
	return i, nil
}

func (cs *ThriftRpcImpl) getInfo(req *gen.InfoRequest, allowRandom bool) (r []*gen.HFileInfo, err error) {
	if req == nil {
		return nil, fmt.Errorf("null request!")
	}
	if Settings.debug {
		log.Println("[GetInfo]", req.GetHfileName())
	}
	require := ""
	if req.IsSetHfileName() {
		require := req.GetHfileName()
		if require != "" && !strings.ContainsRune(require, '/') {
			require = require + "/"
		}
	}

	sample := 0
	if req.IsSetNumRandomKeys() && allowRandom {
		sample = int(*req.NumRandomKeys)
	}

	for name, reader := range cs.Collections {
		if require == "" || strings.HasPrefix(name, require) {
			if i, err := GetCollectionInfo(reader, sample); err != nil {
				return nil, err
			} else {
				r = append(r, i)
			}
		}
	}

	return r, nil
}

func (cs *ThriftRpcImpl) GetInfo(req *gen.InfoRequest) (r []*gen.HFileInfo, err error) {
	return cs.getInfo(req, false)
}

func (cs *ThriftRpcImpl) ScanCollectionAndSampleKeys(req *gen.InfoRequest) (r []*gen.HFileInfo, err error) {
	return cs.getInfo(req, true)
}

func (cs *ThriftRpcImpl) TestTimeout(waitInMillis int32) (r int32, err error) {
	return 0, fmt.Errorf("Not implemented")
}
<|endoftext|>


<commit_before>package networkserver

import (
	"errors"
	"time"

	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	pb_handler "github.com/TheThingsNetwork/ttn/api/handler"
	pb "github.com/TheThingsNetwork/ttn/api/networkserver"
	pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol"
	"github.com/TheThingsNetwork/ttn/core/fcnt"
	"github.com/TheThingsNetwork/ttn/core/networkserver/device"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/TheThingsNetwork/ttn/utils/random"
	"github.com/brocaar/lorawan"
)

// NetworkServer implements LoRaWAN-specific functionality for TTN
type NetworkServer interface {
	HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error)
	HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error)
	HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error)
	HandleUplink(*pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error)
	HandleDownlink(*pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error)
}

type networkServer struct {
	devices device.Store
}

func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) {
	devices, err := n.devices.GetWithAddress(*req.DevAddr)
	if err != nil {
		return nil, err
	}

	// Return all devices with DevAddr with FCnt <= fCnt or Security off

	res := &pb.DevicesResponse{
		Results: make([]*pb.DevicesResponse_Device, 0, len(devices)),
	}

	for _, device := range devices {
		dev := &pb.DevicesResponse_Device{
			AppEui:           &device.AppEUI,
			DevEui:           &device.DevEUI,
			NwkSKey:          &device.NwkSKey,
			FCnt:             device.FCntUp,
			Uses32BitFCnt:    device.Options.Uses32BitFCnt,
			DisableFCntCheck: device.Options.DisableFCntCheck,
		}
		if device.Options.DisableFCntCheck {
			res.Results = append(res.Results, dev)
			continue
		}
		if device.FCntUp <= req.FCnt {
			res.Results = append(res.Results, dev)
			continue
		} else if device.Options.Uses32BitFCnt && device.FCntUp <= fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) {
			res.Results = append(res.Results, dev)
			continue
		}
	}

	return res, nil
}

var netID = [3]byte{0x14, 0x14, 0x14} // TODO: Change to actual NetID
var nwkID byte = 0x13

func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) {
	// Build activation metadata if not present
	if meta := activation.GetActivationMetadata(); meta == nil {
		activation.ActivationMetadata = &pb_protocol.ActivationMetadata{}
	}
	// Build lorawan metadata if not present
	if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil {
		return nil, errors.New("ttn/networkserver: Can only handle LoRaWAN activations")
	}

	// Build response template if not present
	if pld := activation.GetResponseTemplate(); pld == nil {
		return nil, errors.New("ttn/networkserver: Activation does not contain a response template")
	}
	lorawanMeta := activation.ActivationMetadata.GetLorawan()

	// Generate random DevAddr
	// TODO: Be smarter than just randomly generating addresses.
	var devAddr types.DevAddr
	copy(devAddr[:], random.Bytes(4))
	devAddr[0] = (nwkID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb

	// Set the DevAddr in the Activation Metadata
	lorawanMeta.DevAddr = &devAddr

	// Build JoinAccept Payload
	phy := lorawan.PHYPayload{
		MHDR: lorawan.MHDR{
			MType: lorawan.JoinAccept,
			Major: lorawan.LoRaWANR1,
		},
		MACPayload: &lorawan.JoinAcceptPayload{
			NetID:      netID,
			DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)},
			RXDelay:    uint8(lorawanMeta.RxDelay),
			DevAddr:    lorawan.DevAddr(devAddr),
		},
	}
	if len(lorawanMeta.CfList) == 5 {
		var cfList lorawan.CFList
		for i, cfListItem := range lorawanMeta.CfList {
			cfList[i] = uint32(cfListItem)
		}
		phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList
	}

	// Set the Payload
	phyBytes, err := phy.MarshalBinary()
	if err != nil {
		return nil, err
	}
	activation.ResponseTemplate.Payload = phyBytes

	return activation, nil
}

func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) {
	meta := activation.GetActivationMetadata()
	if meta == nil {
		return nil, errors.New("ttn/networkserver: invalid ActivationMetadata")
	}
	lorawan := meta.GetLorawan()
	if lorawan == nil {
		return nil, errors.New("ttn/networkserver: invalid LoRaWAN ActivationMetadata")
	}
	err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey)
	if err != nil {
		return nil, err
	}
	return activation, nil
}

func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) {
	// Get Device
	dev, err := n.devices.Get(*message.AppEui, *message.DevEui)
	if err != nil {
		return nil, err
	}

	// Unmarshal LoRaWAN Payload
	var phyPayload lorawan.PHYPayload
	err = phyPayload.UnmarshalBinary(message.Payload)
	if err != nil {
		return nil, err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload")
	}

	// Update FCntUp
	dev.FCntUp = macPayload.FHDR.FCnt
	dev.LastSeen = time.Now().UTC()
	err = n.devices.Set(dev, "f_cnt_up")
	if err != nil {
		return nil, err
	}

	// Prepare Downlink
	if message.ResponseTemplate == nil {
		message.ResponseTemplate = &pb_broker.DownlinkMessage{}
	}
	message.ResponseTemplate.AppEui = message.AppEui
	message.ResponseTemplate.DevEui = message.DevEui

	phy := lorawan.PHYPayload{
		MHDR: lorawan.MHDR{
			MType: lorawan.UnconfirmedDataDown,
			Major: lorawan.LoRaWANR1,
		},
		MACPayload: &lorawan.MACPayload{
			FHDR: lorawan.FHDR{
				DevAddr: macPayload.FHDR.DevAddr,
				FCtrl: lorawan.FCtrl{
					ACK: phyPayload.MHDR.MType == lorawan.ConfirmedDataUp,
				},
				FCnt: dev.FCntDown,
			},
			FPort: macPayload.FPort,
		},
	}
	phyBytes, err := phy.MarshalBinary()
	if err != nil {
		return nil, err
	}

	// TODO: Maybe we need to add MAC commands on downlink

	message.ResponseTemplate.Payload = phyBytes

	return message, nil
}

func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) {
	// Get Device
	dev, err := n.devices.Get(*message.AppEui, *message.DevEui)
	if err != nil {
		return nil, err
	}

	// Unmarshal LoRaWAN Payload
	var phyPayload lorawan.PHYPayload
	err = phyPayload.UnmarshalBinary(message.Payload)
	if err != nil {
		return nil, err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload")
	}

	// Set DevAddr
	macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr)

	// FIRST set and THEN increment FCntDown
	// TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK
	macPayload.FHDR.FCnt = dev.FCntDown
	dev.FCntDown++
	err = n.devices.Set(dev, "f_cnt_down")
	if err != nil {
		return nil, err
	}

	// TODO: Maybe we need to add MAC commands on downlink

	// Sign MIC
	phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey))

	// Update message
	bytes, err := phyPayload.MarshalBinary()
	if err != nil {
		return nil, err
	}
	message.Payload = bytes

	return message, nil
}
<commit_msg>Use TTN's actual NetID<commit_after>package networkserver

import (
	"errors"
	"time"

	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	pb_handler "github.com/TheThingsNetwork/ttn/api/handler"
	pb "github.com/TheThingsNetwork/ttn/api/networkserver"
	pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol"
	"github.com/TheThingsNetwork/ttn/core/fcnt"
	"github.com/TheThingsNetwork/ttn/core/networkserver/device"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/TheThingsNetwork/ttn/utils/random"
	"github.com/brocaar/lorawan"
)

// NetworkServer implements LoRaWAN-specific functionality for TTN
type NetworkServer interface {
	HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error)
	HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error)
	HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error)
	HandleUplink(*pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error)
	HandleDownlink(*pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error)
}

type networkServer struct {
	devices device.Store
}

func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) {
	devices, err := n.devices.GetWithAddress(*req.DevAddr)
	if err != nil {
		return nil, err
	}

	// Return all devices with DevAddr with FCnt <= fCnt or Security off

	res := &pb.DevicesResponse{
		Results: make([]*pb.DevicesResponse_Device, 0, len(devices)),
	}

	for _, device := range devices {
		dev := &pb.DevicesResponse_Device{
			AppEui:           &device.AppEUI,
			DevEui:           &device.DevEUI,
			NwkSKey:          &device.NwkSKey,
			FCnt:             device.FCntUp,
			Uses32BitFCnt:    device.Options.Uses32BitFCnt,
			DisableFCntCheck: device.Options.DisableFCntCheck,
		}
		if device.Options.DisableFCntCheck {
			res.Results = append(res.Results, dev)
			continue
		}
		if device.FCntUp <= req.FCnt {
			res.Results = append(res.Results, dev)
			continue
		} else if device.Options.Uses32BitFCnt && device.FCntUp <= fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) {
			res.Results = append(res.Results, dev)
			continue
		}
	}

	return res, nil
}

var netID = [3]byte{0x00, 0x00, 0x13}
var nwkID byte = 0x13

func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) {
	// Build activation metadata if not present
	if meta := activation.GetActivationMetadata(); meta == nil {
		activation.ActivationMetadata = &pb_protocol.ActivationMetadata{}
	}
	// Build lorawan metadata if not present
	if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil {
		return nil, errors.New("ttn/networkserver: Can only handle LoRaWAN activations")
	}

	// Build response template if not present
	if pld := activation.GetResponseTemplate(); pld == nil {
		return nil, errors.New("ttn/networkserver: Activation does not contain a response template")
	}
	lorawanMeta := activation.ActivationMetadata.GetLorawan()

	// Generate random DevAddr
	// TODO: Be smarter than just randomly generating addresses.
	var devAddr types.DevAddr
	copy(devAddr[:], random.Bytes(4))
	devAddr[0] = (nwkID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb

	// Set the DevAddr in the Activation Metadata
	lorawanMeta.DevAddr = &devAddr

	// Build JoinAccept Payload
	phy := lorawan.PHYPayload{
		MHDR: lorawan.MHDR{
			MType: lorawan.JoinAccept,
			Major: lorawan.LoRaWANR1,
		},
		MACPayload: &lorawan.JoinAcceptPayload{
			NetID:      netID,
			DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)},
			RXDelay:    uint8(lorawanMeta.RxDelay),
			DevAddr:    lorawan.DevAddr(devAddr),
		},
	}
	if len(lorawanMeta.CfList) == 5 {
		var cfList lorawan.CFList
		for i, cfListItem := range lorawanMeta.CfList {
			cfList[i] = uint32(cfListItem)
		}
		phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList
	}

	// Set the Payload
	phyBytes, err := phy.MarshalBinary()
	if err != nil {
		return nil, err
	}
	activation.ResponseTemplate.Payload = phyBytes

	return activation, nil
}

func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) {
	meta := activation.GetActivationMetadata()
	if meta == nil {
		return nil, errors.New("ttn/networkserver: invalid ActivationMetadata")
	}
	lorawan := meta.GetLorawan()
	if lorawan == nil {
		return nil, errors.New("ttn/networkserver: invalid LoRaWAN ActivationMetadata")
	}
	err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey)
	if err != nil {
		return nil, err
	}
	return activation, nil
}

func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) {
	// Get Device
	dev, err := n.devices.Get(*message.AppEui, *message.DevEui)
	if err != nil {
		return nil, err
	}

	// Unmarshal LoRaWAN Payload
	var phyPayload lorawan.PHYPayload
	err = phyPayload.UnmarshalBinary(message.Payload)
	if err != nil {
		return nil, err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload")
	}

	// Update FCntUp
	dev.FCntUp = macPayload.FHDR.FCnt
	dev.LastSeen = time.Now().UTC()
	err = n.devices.Set(dev, "f_cnt_up")
	if err != nil {
		return nil, err
	}

	// Prepare Downlink
	if message.ResponseTemplate == nil {
		message.ResponseTemplate = &pb_broker.DownlinkMessage{}
	}
	message.ResponseTemplate.AppEui = message.AppEui
	message.ResponseTemplate.DevEui = message.DevEui

	phy := lorawan.PHYPayload{
		MHDR: lorawan.MHDR{
			MType: lorawan.UnconfirmedDataDown,
			Major: lorawan.LoRaWANR1,
		},
		MACPayload: &lorawan.MACPayload{
			FHDR: lorawan.FHDR{
				DevAddr: macPayload.FHDR.DevAddr,
				FCtrl: lorawan.FCtrl{
					ACK: phyPayload.MHDR.MType == lorawan.ConfirmedDataUp,
				},
				FCnt: dev.FCntDown,
			},
			FPort: macPayload.FPort,
		},
	}
	phyBytes, err := phy.MarshalBinary()
	if err != nil {
		return nil, err
	}

	// TODO: Maybe we need to add MAC commands on downlink

	message.ResponseTemplate.Payload = phyBytes

	return message, nil
}

func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) {
	// Get Device
	dev, err := n.devices.Get(*message.AppEui, *message.DevEui)
	if err != nil {
		return nil, err
	}

	// Unmarshal LoRaWAN Payload
	var phyPayload lorawan.PHYPayload
	err = phyPayload.UnmarshalBinary(message.Payload)
	if err != nil {
		return nil, err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload")
	}

	// Set DevAddr
	macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr)

	// FIRST set and THEN increment FCntDown
	// TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK
	macPayload.FHDR.FCnt = dev.FCntDown
	dev.FCntDown++
	err = n.devices.Set(dev, "f_cnt_down")
	if err != nil {
		return nil, err
	}

	// TODO: Maybe we need to add MAC commands on downlink

	// Sign MIC
	phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey))

	// Update message
	bytes, err := phyPayload.MarshalBinary()
	if err != nil {
		return nil, err
	}
	message.Payload = bytes

	return message, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

// DefaultRules are the standard validation to perform on git commits
var DefaultRules = []ValidateRule{
	func(c CommitEntry) (vr ValidateResult) {
		vr.CommitEntry = c
		hasValid := false
		for _, line := range strings.Split(c.Body, "\n") {
			if validDCO.MatchString(line) {
				hasValid = true
			}
		}
		if !hasValid {
			vr.Pass = false
			vr.Msg = "does not have a valid DCO"
		} else {
			vr.Pass = true
			vr.Msg = "has a valid DCO"
		}

		return vr
	},
	// TODO add something for the cleanliness of the c.Subject
}

var (
	flVerbose     = flag.Bool("v", false, "verbose")
	flCommitRange = flag.String("range", "", "use this commit range instead")

	validDCO = regexp.MustCompile(`^Signed-off-by: ([^<]+) <([^<>@]+@[^<>]+)>$`)
)

func main() {
	flag.Parse()

	var commitrange string
	if *flCommitRange != "" {
		commitrange = *flCommitRange
	} else {
		var err error
		commitrange, err = GitFetchHeadCommit()
		if err != nil {
			log.Fatal(err)
		}
	}

	c, err := GitCommits(commitrange)
	if err != nil {
		log.Fatal(err)
	}

	results := ValidateResults{}
	for _, commit := range c {
		fmt.Printf(" * %s %s ... ", commit.AbbreviatedCommit, commit.Subject)
		vr := ValidateCommit(commit, DefaultRules)
		results = append(results, vr...)
		if _, fail := vr.PassFail(); fail == 0 {
			fmt.Println("PASS")
			if *flVerbose {
				for _, r := range vr {
					if !r.Pass {
						fmt.Printf("  - %s\n", r.Msg)
					}
				}
			}
		} else {
			fmt.Println("FAIL")
			// default, only print out failed validations
			for _, r := range vr {
				if !r.Pass {
					fmt.Printf("  - %s\n", r.Msg)
				}
			}
		}
	}
	_, fail := results.PassFail()
	if fail > 0 {
		fmt.Printf("%d issues to fix\n", fail)
		os.Exit(1)
	}
}

// ValidateRule will operate over a provided CommitEntry, and return a result.
type ValidateRule func(CommitEntry) ValidateResult

// ValidateCommit processes the given rules on the provided commit, and returns the result set.
func ValidateCommit(c CommitEntry, rules []ValidateRule) ValidateResults {
	results := ValidateResults{}
	for _, r := range rules {
		results = append(results, r(c))
	}
	return results
}

// ValidateResult is the result for a single validation of a commit.
type ValidateResult struct {
	CommitEntry CommitEntry
	Pass        bool
	Msg         string
}

// ValidateResults is a set of results. This is type makes it easy for the following function.
type ValidateResults []ValidateResult

// PassFail gives a quick over/under of passes and failures of the results in this set
func (vr ValidateResults) PassFail() (pass int, fail int) {
	for _, res := range vr {
		if res.Pass {
			pass++
		} else {
			fail++
		}
	}
	return pass, fail
}

// CommitEntry represents a single commit's information from `git`
type CommitEntry struct {
	Commit               string
	AbbreviatedCommit    string `json:"abbreviated_commit"`
	Tree                 string
	AbbreviatedTree      string `json:"abbreviated_tree"`
	Parent               string
	AbbreviatedParent    string `json:"abbreviated_parent"`
	Refs                 string
	Encoding             string
	Subject              string
	SanitizedSubjectLine string `json:"sanitized_subject_line"`
	Body                 string
	CommitNotes          string `json:"commit_notes"`
	VerificationFlag     string `json:"verification_flag"`
	ShortMsg             string
	Signer               string
	SignerKey            string       `json:"signer_key"`
	Author               PersonAction `json:"author,omitempty"`
	Commiter             PersonAction `json:"commiter,omitempty"`
}

// PersonAction is a time and identity of an action on a git commit
type PersonAction struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Date  string `json:"date"` // this could maybe be an actual time.Time
}

var (
	prettyLogSubject       = `--pretty=format:%s`
	prettyLogBody          = `--pretty=format:%b`
	prettyLogCommit        = `--pretty=format:%H`
	prettyLogAuthorName    = `--pretty=format:%aN`
	prettyLogAuthorEmail   = `--pretty=format:%aE`
	prettyLogCommiterName  = `--pretty=format:%cN`
	prettyLogCommiterEmail = `--pretty=format:%cE`
	prettyLogSigner        = `--pretty=format:%GS`
	prettyLogCommitNotes   = `--pretty=format:%N`
	prettyLogFormat        = `--pretty=format:{"commit": "%H", "abbreviated_commit": "%h", "tree": "%T", "abbreviated_tree": "%t", "parent": "%P", "abbreviated_parent": "%p", "refs": "%D", "encoding": "%e", "sanitized_subject_line": "%f", "verification_flag": "%G?", "signer_key": "%GK", "author": { "date": "%aD" }, "commiter": { "date": "%cD" }}`
)

// GitLogCommit assembles the full information on a commit from its commit hash
func GitLogCommit(commit string) (*CommitEntry, error) {
	buf := bytes.NewBuffer([]byte{})
	cmd := exec.Command("git", "log", "-1", prettyLogFormat, commit)
	cmd.Stdout = buf
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Println(strings.Join(cmd.Args, " "))
		return nil, err
	}
	c := CommitEntry{}
	output := buf.Bytes()
	if err := json.Unmarshal(output, &c); err != nil {
		fmt.Println(string(output))
		return nil, err
	}

	output, err := exec.Command("git", "log", "-1", prettyLogSubject, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Subject = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogBody, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Body = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogAuthorName, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Author.Name = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogAuthorEmail, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Author.Email = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogCommiterName, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Commiter.Name = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogCommiterEmail, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Commiter.Email = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogCommitNotes, commit).Output()
	if err != nil {
		return nil, err
	}
	c.CommitNotes = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogSigner, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Signer = strings.TrimSpace(string(output))

	return &c, nil
}

// GitCommits returns a set of commits.
// If commitrange is a git still range 12345...54321, then it will be isolated set of commits.
// If commitrange is a single commit, all ancestor commits up through the hash provided.
func GitCommits(commitrange string) ([]CommitEntry, error) {
	output, err := exec.Command("git", "log", prettyLogCommit, commitrange).Output()
	if err != nil {
		return nil, err
	}
	commitHashes := strings.Split(strings.TrimSpace(string(output)), "\n")
	commits := make([]CommitEntry, len(commitHashes))
	for i, commitHash := range commitHashes {
		c, err := GitLogCommit(commitHash)
		if err != nil {
			return commits, err
		}
		commits[i] = *c
	}
	return commits, nil
}

// GitFetchHeadCommit returns the hash of FETCH_HEAD
func GitFetchHeadCommit() (string, error) {
	output, err := exec.Command("git", "rev-parse", "--verify", "HEAD").Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}

// GitHeadCommit returns the hash of HEAD
func GitHeadCommit() (string, error) {
	output, err := exec.Command("git", "rev-parse", "--verify", "HEAD").Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}
<commit_msg>Change commiter to committer<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

// DefaultRules are the standard validation to perform on git commits
var DefaultRules = []ValidateRule{
	func(c CommitEntry) (vr ValidateResult) {
		vr.CommitEntry = c
		hasValid := false
		for _, line := range strings.Split(c.Body, "\n") {
			if validDCO.MatchString(line) {
				hasValid = true
			}
		}
		if !hasValid {
			vr.Pass = false
			vr.Msg = "does not have a valid DCO"
		} else {
			vr.Pass = true
			vr.Msg = "has a valid DCO"
		}

		return vr
	},
	// TODO add something for the cleanliness of the c.Subject
}

var (
	flVerbose     = flag.Bool("v", false, "verbose")
	flCommitRange = flag.String("range", "", "use this commit range instead")

	validDCO = regexp.MustCompile(`^Signed-off-by: ([^<]+) <([^<>@]+@[^<>]+)>$`)
)

func main() {
	flag.Parse()

	var commitrange string
	if *flCommitRange != "" {
		commitrange = *flCommitRange
	} else {
		var err error
		commitrange, err = GitFetchHeadCommit()
		if err != nil {
			log.Fatal(err)
		}
	}

	c, err := GitCommits(commitrange)
	if err != nil {
		log.Fatal(err)
	}

	results := ValidateResults{}
	for _, commit := range c {
		fmt.Printf(" * %s %s ... ", commit.AbbreviatedCommit, commit.Subject)
		vr := ValidateCommit(commit, DefaultRules)
		results = append(results, vr...)
		if _, fail := vr.PassFail(); fail == 0 {
			fmt.Println("PASS")
			if *flVerbose {
				for _, r := range vr {
					if !r.Pass {
						fmt.Printf("  - %s\n", r.Msg)
					}
				}
			}
		} else {
			fmt.Println("FAIL")
			// default, only print out failed validations
			for _, r := range vr {
				if !r.Pass {
					fmt.Printf("  - %s\n", r.Msg)
				}
			}
		}
	}
	_, fail := results.PassFail()
	if fail > 0 {
		fmt.Printf("%d issues to fix\n", fail)
		os.Exit(1)
	}
}

// ValidateRule will operate over a provided CommitEntry, and return a result.
type ValidateRule func(CommitEntry) ValidateResult

// ValidateCommit processes the given rules on the provided commit, and returns the result set.
func ValidateCommit(c CommitEntry, rules []ValidateRule) ValidateResults {
	results := ValidateResults{}
	for _, r := range rules {
		results = append(results, r(c))
	}
	return results
}

// ValidateResult is the result for a single validation of a commit.
type ValidateResult struct {
	CommitEntry CommitEntry
	Pass        bool
	Msg         string
}

// ValidateResults is a set of results. This is type makes it easy for the following function.
type ValidateResults []ValidateResult

// PassFail gives a quick over/under of passes and failures of the results in this set
func (vr ValidateResults) PassFail() (pass int, fail int) {
	for _, res := range vr {
		if res.Pass {
			pass++
		} else {
			fail++
		}
	}
	return pass, fail
}

// CommitEntry represents a single commit's information from `git`
type CommitEntry struct {
	Commit               string
	AbbreviatedCommit    string `json:"abbreviated_commit"`
	Tree                 string
	AbbreviatedTree      string `json:"abbreviated_tree"`
	Parent               string
	AbbreviatedParent    string `json:"abbreviated_parent"`
	Refs                 string
	Encoding             string
	Subject              string
	SanitizedSubjectLine string `json:"sanitized_subject_line"`
	Body                 string
	CommitNotes          string `json:"commit_notes"`
	VerificationFlag     string `json:"verification_flag"`
	ShortMsg             string
	Signer               string
	SignerKey            string       `json:"signer_key"`
	Author               PersonAction `json:"author,omitempty"`
	Committer            PersonAction `json:"committer,omitempty"`
}

// PersonAction is a time and identity of an action on a git commit
type PersonAction struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Date  string `json:"date"` // this could maybe be an actual time.Time
}

var (
	prettyLogSubject        = `--pretty=format:%s`
	prettyLogBody           = `--pretty=format:%b`
	prettyLogCommit         = `--pretty=format:%H`
	prettyLogAuthorName     = `--pretty=format:%aN`
	prettyLogAuthorEmail    = `--pretty=format:%aE`
	prettyLogCommitterName  = `--pretty=format:%cN`
	prettyLogCommitterEmail = `--pretty=format:%cE`
	prettyLogSigner         = `--pretty=format:%GS`
	prettyLogCommitNotes    = `--pretty=format:%N`
	prettyLogFormat         = `--pretty=format:{"commit": "%H", "abbreviated_commit": "%h", "tree": "%T", "abbreviated_tree": "%t", "parent": "%P", "abbreviated_parent": "%p", "refs": "%D", "encoding": "%e", "sanitized_subject_line": "%f", "verification_flag": "%G?", "signer_key": "%GK", "author": { "date": "%aD" }, "committer": { "date": "%cD" }}`
)

// GitLogCommit assembles the full information on a commit from its commit hash
func GitLogCommit(commit string) (*CommitEntry, error) {
	buf := bytes.NewBuffer([]byte{})
	cmd := exec.Command("git", "log", "-1", prettyLogFormat, commit)
	cmd.Stdout = buf
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Println(strings.Join(cmd.Args, " "))
		return nil, err
	}
	c := CommitEntry{}
	output := buf.Bytes()
	if err := json.Unmarshal(output, &c); err != nil {
		fmt.Println(string(output))
		return nil, err
	}

	output, err := exec.Command("git", "log", "-1", prettyLogSubject, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Subject = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogBody, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Body = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogAuthorName, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Author.Name = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogAuthorEmail, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Author.Email = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogCommitterName, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Committer.Name = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogCommitterEmail, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Committer.Email = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogCommitNotes, commit).Output()
	if err != nil {
		return nil, err
	}
	c.CommitNotes = strings.TrimSpace(string(output))

	output, err = exec.Command("git", "log", "-1", prettyLogSigner, commit).Output()
	if err != nil {
		return nil, err
	}
	c.Signer = strings.TrimSpace(string(output))

	return &c, nil
}

// GitCommits returns a set of commits.
// If commitrange is a git still range 12345...54321, then it will be isolated set of commits.
// If commitrange is a single commit, all ancestor commits up through the hash provided.
func GitCommits(commitrange string) ([]CommitEntry, error) {
	output, err := exec.Command("git", "log", prettyLogCommit, commitrange).Output()
	if err != nil {
		return nil, err
	}
	commitHashes := strings.Split(strings.TrimSpace(string(output)), "\n")
	commits := make([]CommitEntry, len(commitHashes))
	for i, commitHash := range commitHashes {
		c, err := GitLogCommit(commitHash)
		if err != nil {
			return commits, err
		}
		commits[i] = *c
	}
	return commits, nil
}

// GitFetchHeadCommit returns the hash of FETCH_HEAD
func GitFetchHeadCommit() (string, error) {
	output, err := exec.Command("git", "rev-parse", "--verify", "HEAD").Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}

// GitHeadCommit returns the hash of HEAD
func GitHeadCommit() (string, error) {
	output, err := exec.Command("git", "rev-parse", "--verify", "HEAD").Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}
<|endoftext|>


<commit_before>package garnish

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/karlseguin/garnish/caches/ccache"
	"github.com/karlseguin/garnish/core"
	"github.com/karlseguin/garnish/middleware/caching"
	"github.com/karlseguin/garnish/middleware/stats"
	"github.com/karlseguin/garnish/middleware/upstream"
	"github.com/karlseguin/garnish/router"
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"strings"
	"time"
)

type Configuration struct {
	maxProcs             int
	address              string
	maxHeaderBytes       int
	readTimeout          time.Duration
	middlewareFactories  []core.MiddlewareFactory
	internalErrorMessage string
	notFoundMessage      string
	unauthorizedMessage  string
	router               core.Router
	logger               core.Logger
}

func Configure() *Configuration {
	return &Configuration{
		maxHeaderBytes:       8192,
		internalErrorMessage: "internal error",
		notFoundMessage:      "not found",
		unauthorizedMessage:  "unauthorized",
		maxProcs:             runtime.NumCPU(),
		readTimeout:          time.Second * 10,
		address:              "tcp://127.0.0.1:6772",
		middlewareFactories:  make([]core.MiddlewareFactory, 0, 1),
		logger:               &logger{logger: log.New(os.Stdout, "[garnish] ", log.Ldate|log.Lmicroseconds)},
	}
}

// The address to listen on should be in the format [tcp://127.0.0.1:6772]
// With unix socket: unix:/tmp/garnish.sock
func (c *Configuration) Listen(address string) *Configuration {
	c.address = address
	return c
}

// Maximum size of request headers, [8192]
func (c *Configuration) MaxHeaderBytes(bytes int) *Configuration {
	c.maxHeaderBytes = bytes
	return c
}

// Maximum duration before timing out read of the request [10 seconds]
func (c *Configuration) ReadTimeout(timeout time.Duration) *Configuration {
	c.readTimeout = timeout
	return c
}

// Maximum number of OS Threads to use (GOMAXPROCS) [# of CPUs]
func (c *Configuration) MaxiumOSThreads(count int) *Configuration {
	c.maxProcs = count
	return c
}

// Enable logging info messages
func (c *Configuration) LogInfo() *Configuration {
	c.logger.(*logger).info = true
	return c
}

// Registers the middlewares to use. Middleware will be executed in the order
// which they are supplied.
func (c *Configuration) Middleware(factories ...core.MiddlewareFactory) *Configuration {
	for _, factory := range factories {
		c.middlewareFactories = append(c.middlewareFactories, factory)
	}
	return c
}

// The body to use when replying with a 404 ["not found"]
func (c *Configuration) NotFound(message string) *Configuration {
	c.notFoundMessage = message
	return c
}

// The body to use when replying with a 401 ["unauthorized"]
func (c *Configuration) Unauthorized(message string) *Configuration {
	c.unauthorizedMessage = message
	return c
}

// The body to use when replying with a 500 ["internal error"]
func (c *Configuration) InternalError(message string) *Configuration {
	c.internalErrorMessage = message
	return c
}

// Creates and returns a new router
// As this breaks the chainable configuration, it'll normally be the last
// step in configuration.
func (c *Configuration) NewRouter() core.Router {
	c.router = router.New(c.logger, c.middlewareFactories)
	return c.router
}

// Gets the logger
func (c *Configuration) Logger() core.Logger {
	return c.logger
}

// Gets the router
func (c *Configuration) Router() core.Router {
	return c.router
}

func ConfigureFromJson(bytes []byte) (config *Configuration, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = errors.New(fmt.Sprintf("%v", e))
		}
	}()
	var raw map[string]interface{}
	if err := json.Unmarshal(bytes, &raw); err != nil {
		return nil, err
	}
	config = Configure()
	mapCoreConfig(config, raw)
	configurators := make(map[string]core.MiddlewareFactory)
	if configData := raw["defaults"].(map[string]interface{}); configData != nil {
		mapMiddlewareConfig(config, configData, configurators)
	}
	router := config.NewRouter()
	if configData := raw["routes"].([]interface{}); configData != nil {
		for _, routeConfigData := range configData {
			rcd := routeConfigData.(map[string]interface{})
			r := router.Add(rcd["name"].(string), rcd["method"].(string), rcd["path"].(string))
			for _, middleware := range config.middlewareFactories {
				middleware.OverrideFor(r.Route())
			}
			mapMiddlewareConfig(config, rcd, configurators)
		}
	}
	return config, nil
}

func ConfigureFromFile(path string) (*Configuration, error) {
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return ConfigureFromJson(bytes)
}

var configuatorFactories = map[string]func() core.MiddlewareFactory{
	"stats":    func() core.MiddlewareFactory { return stats.Configure() },
	"caching":  func() core.MiddlewareFactory { return caching.Configure() },
	"upstream": func() core.MiddlewareFactory { return upstream.Configure() },
}

func mapMiddlewareConfig(config *Configuration, data map[string]interface{}, configurators map[string]core.MiddlewareFactory) {
	for name, raw := range data {
		name = strings.ToLower(name)
		factory, ok := configuatorFactories[name]
		if ok == false {
			continue
		}

		configurator := configurators[name]
		if configurator == nil {
			configurator = factory()
			config.Middleware(configurator)
			configurators[name] = configurator
		}

		configData := raw.(map[string]interface{})
		switch name {
		case "stats":
			mapStatsConfig(configurator.(*stats.Configuration), configData)
		case "caching":
			mapCachingConfig(configurator.(*caching.Configuration), configData)
		case "upstream":
			mapUpstreamConfig(configurator.(*upstream.Configuration), configData)
		}
	}
}

func mapCoreConfig(config *Configuration, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "loginfo":
			config.LogInfo()
		case "listen":
			config.Listen(value.(string))
		case "threads":
			config.MaxiumOSThreads(toInt(value))
		case "readtimeout":
			config.ReadTimeout(toDuration(value))
		case "maxheaderbytes":
			config.MaxHeaderBytes(toInt(value))
		}
	}
}

func mapStatsConfig(config *stats.Configuration, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "samplesize":
			config.SampleSize(toInt(value))
		case "window":
			config.Window(toDuration(value))
		case "output":
			config.Persister(&stats.FilePersister{value.(string)})
		case "treshhold":
			config.Treshhold(toDuration(value))
		case "percentiles":
			floats := value.([]float64)
			ints := make([]int, 0, len(floats))
			for index, flt := range floats {
				ints[index] = int(flt)
			}
			config.Percentiles(ints...)
		}
	}
}

func mapCachingConfig(config *caching.Configuration, configData map[string]interface{}) {
	if f, ok := configData["size"].(float64); ok {
		config.Cache(ccache.New(ccache.Configure().Size(uint64(f))))
	}
	for key, value := range configData {
		switch key {
		case "ttl":
			config.TTL(toDuration(value))
		case "grace":
			config.Grace(toDuration(value))
		case "saint":
			config.Saint(toDuration(value))
		}
	}
}

func mapUpstreamConfig(config *upstream.Configuration, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "forwardheaders":
			config.ForwardHeaders(value.([]string)...)
		case "dnsrefresh":
			config.DnsRefresh(toDuration(value))
		case "servers":
			for _, raw := range value.([]interface{}) {
				serverConfig := raw.(map[string]interface{})
				server := config.Add(serverConfig["name"].(string), serverConfig["scheme"].(string), serverConfig["address"].(string))
				mapServerConfig(server, serverConfig)
			}
		case "upstream":
			config.Is(value.(string))
		}
	}
}

func mapServerConfig(server *upstream.Server, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "keepalive":
			if value.(bool) == false {
				server.DisableKeepAlives()
			}
		case "maxidle":
			server.MaxIdleConnectionsPerHost(toInt(value))
		}
	}
}

func toInt(value interface{}) int {
	return int(value.(float64))
}

func toDuration(value interface{}) time.Duration {
	return time.Duration(int(value.(float64)))
}
<commit_msg>fixed percentile json parsing<commit_after>package garnish

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/karlseguin/garnish/caches/ccache"
	"github.com/karlseguin/garnish/core"
	"github.com/karlseguin/garnish/middleware/caching"
	"github.com/karlseguin/garnish/middleware/stats"
	"github.com/karlseguin/garnish/middleware/upstream"
	"github.com/karlseguin/garnish/router"
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"strings"
	"time"
)

type Configuration struct {
	maxProcs             int
	address              string
	maxHeaderBytes       int
	readTimeout          time.Duration
	middlewareFactories  []core.MiddlewareFactory
	internalErrorMessage string
	notFoundMessage      string
	unauthorizedMessage  string
	router               core.Router
	logger               core.Logger
}

func Configure() *Configuration {
	return &Configuration{
		maxHeaderBytes:       8192,
		internalErrorMessage: "internal error",
		notFoundMessage:      "not found",
		unauthorizedMessage:  "unauthorized",
		maxProcs:             runtime.NumCPU(),
		readTimeout:          time.Second * 10,
		address:              "tcp://127.0.0.1:6772",
		middlewareFactories:  make([]core.MiddlewareFactory, 0, 1),
		logger:               &logger{logger: log.New(os.Stdout, "[garnish] ", log.Ldate|log.Lmicroseconds)},
	}
}

// The address to listen on should be in the format [tcp://127.0.0.1:6772]
// With unix socket: unix:/tmp/garnish.sock
func (c *Configuration) Listen(address string) *Configuration {
	c.address = address
	return c
}

// Maximum size of request headers, [8192]
func (c *Configuration) MaxHeaderBytes(bytes int) *Configuration {
	c.maxHeaderBytes = bytes
	return c
}

// Maximum duration before timing out read of the request [10 seconds]
func (c *Configuration) ReadTimeout(timeout time.Duration) *Configuration {
	c.readTimeout = timeout
	return c
}

// Maximum number of OS Threads to use (GOMAXPROCS) [# of CPUs]
func (c *Configuration) MaxiumOSThreads(count int) *Configuration {
	c.maxProcs = count
	return c
}

// Enable logging info messages
func (c *Configuration) LogInfo() *Configuration {
	c.logger.(*logger).info = true
	return c
}

// Registers the middlewares to use. Middleware will be executed in the order
// which they are supplied.
func (c *Configuration) Middleware(factories ...core.MiddlewareFactory) *Configuration {
	for _, factory := range factories {
		c.middlewareFactories = append(c.middlewareFactories, factory)
	}
	return c
}

// The body to use when replying with a 404 ["not found"]
func (c *Configuration) NotFound(message string) *Configuration {
	c.notFoundMessage = message
	return c
}

// The body to use when replying with a 401 ["unauthorized"]
func (c *Configuration) Unauthorized(message string) *Configuration {
	c.unauthorizedMessage = message
	return c
}

// The body to use when replying with a 500 ["internal error"]
func (c *Configuration) InternalError(message string) *Configuration {
	c.internalErrorMessage = message
	return c
}

// Creates and returns a new router
// As this breaks the chainable configuration, it'll normally be the last
// step in configuration.
func (c *Configuration) NewRouter() core.Router {
	c.router = router.New(c.logger, c.middlewareFactories)
	return c.router
}

// Gets the logger
func (c *Configuration) Logger() core.Logger {
	return c.logger
}

// Gets the router
func (c *Configuration) Router() core.Router {
	return c.router
}

func ConfigureFromJson(bytes []byte) (config *Configuration, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = errors.New(fmt.Sprintf("%v", e))
		}
	}()
	var raw map[string]interface{}
	if err := json.Unmarshal(bytes, &raw); err != nil {
		return nil, err
	}
	config = Configure()
	mapCoreConfig(config, raw)
	configurators := make(map[string]core.MiddlewareFactory)
	if configData := raw["defaults"].(map[string]interface{}); configData != nil {
		mapMiddlewareConfig(config, configData, configurators)
	}
	router := config.NewRouter()
	if configData := raw["routes"].([]interface{}); configData != nil {
		for _, routeConfigData := range configData {
			rcd := routeConfigData.(map[string]interface{})
			r := router.Add(rcd["name"].(string), rcd["method"].(string), rcd["path"].(string))
			for _, middleware := range config.middlewareFactories {
				middleware.OverrideFor(r.Route())
			}
			mapMiddlewareConfig(config, rcd, configurators)
		}
	}
	return config, nil
}

func ConfigureFromFile(path string) (*Configuration, error) {
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return ConfigureFromJson(bytes)
}

var configuatorFactories = map[string]func() core.MiddlewareFactory{
	"stats":    func() core.MiddlewareFactory { return stats.Configure() },
	"caching":  func() core.MiddlewareFactory { return caching.Configure() },
	"upstream": func() core.MiddlewareFactory { return upstream.Configure() },
}

func mapMiddlewareConfig(config *Configuration, data map[string]interface{}, configurators map[string]core.MiddlewareFactory) {
	for name, raw := range data {
		name = strings.ToLower(name)
		factory, ok := configuatorFactories[name]
		if ok == false {
			continue
		}

		configurator := configurators[name]
		if configurator == nil {
			configurator = factory()
			config.Middleware(configurator)
			configurators[name] = configurator
		}

		configData := raw.(map[string]interface{})
		switch name {
		case "stats":
			mapStatsConfig(configurator.(*stats.Configuration), configData)
		case "caching":
			mapCachingConfig(configurator.(*caching.Configuration), configData)
		case "upstream":
			mapUpstreamConfig(configurator.(*upstream.Configuration), configData)
		}
	}
}

func mapCoreConfig(config *Configuration, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "loginfo":
			config.LogInfo()
		case "listen":
			config.Listen(value.(string))
		case "threads":
			config.MaxiumOSThreads(toInt(value))
		case "readtimeout":
			config.ReadTimeout(toDuration(value))
		case "maxheaderbytes":
			config.MaxHeaderBytes(toInt(value))
		}
	}
}

func mapStatsConfig(config *stats.Configuration, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "samplesize":
			config.SampleSize(toInt(value))
		case "window":
			config.Window(toDuration(value))
		case "output":
			config.Persister(&stats.FilePersister{value.(string)})
		case "treshhold":
			config.Treshhold(toDuration(value))
		case "percentiles":
			floats := value.([]interface{})
			ints := make([]int, len(floats))
			for index, flt := range floats {
				ints[index] = int(flt.(float64))
			}
			config.Percentiles(ints...)
		}
	}
}

func mapCachingConfig(config *caching.Configuration, configData map[string]interface{}) {
	if f, ok := configData["size"].(float64); ok {
		config.Cache(ccache.New(ccache.Configure().Size(uint64(f))))
	}
	for key, value := range configData {
		switch key {
		case "ttl":
			config.TTL(toDuration(value))
		case "grace":
			config.Grace(toDuration(value))
		case "saint":
			config.Saint(toDuration(value))
		}
	}
}

func mapUpstreamConfig(config *upstream.Configuration, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "forwardheaders":
			config.ForwardHeaders(value.([]string)...)
		case "dnsrefresh":
			config.DnsRefresh(toDuration(value))
		case "servers":
			for _, raw := range value.([]interface{}) {
				serverConfig := raw.(map[string]interface{})
				server := config.Add(serverConfig["name"].(string), serverConfig["scheme"].(string), serverConfig["address"].(string))
				mapServerConfig(server, serverConfig)
			}
		case "upstream":
			config.Is(value.(string))
		}
	}
}

func mapServerConfig(server *upstream.Server, configData map[string]interface{}) {
	for key, value := range configData {
		switch strings.ToLower(key) {
		case "keepalive":
			if value.(bool) == false {
				server.DisableKeepAlives()
			}
		case "maxidle":
			server.MaxIdleConnectionsPerHost(toInt(value))
		}
	}
}

func toInt(value interface{}) int {
	return int(value.(float64))
}

func toDuration(value interface{}) time.Duration {
	return time.Duration(int(value.(float64)))
}
<|endoftext|>


<commit_before>package bugsnag

import (
	"log"
	"net/http"
	"path/filepath"
	"strings"
)

// Configuration sets up and customizes communication with the Bugsnag API.
type Configuration struct {
	// Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
	// find this by clicking Settings on https://bugsnag.com/.
	APIKey string
	// The Endpoint to notify about crashes. This defaults to
	// "https://notify.bugsnag.com/", if you're using Bugsnag Enterprise then
	// set it to your internal Bugsnag endpoint.
	Endpoint string

	// The current release stage. This defaults to "production" and is used to
	// filter errors in the Bugsnag dashboard.
	ReleaseStage string
	// A specialized type of the application, such as the worker queue or web
	// framework used, like "rails", "mailman", or "celery"
	AppType string
	// The currently running version of the app. This is used to filter errors
	// in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
	// resolved errors if they happen in different app versions.
	AppVersion string
	// The hostname of the current server. This defaults to the return value of
	// os.Hostname() and is graphed in the Bugsnag dashboard.
	Hostname string

	// The Release stages to notify in. If you set this then bugsnag-go will
	// only send notifications to Bugsnag if the ReleaseStage is listed here.
	NotifyReleaseStages []string

	// packages that are part of your app. Bugsnag uses this to determine how
	// to group errors and how to display them on your dashboard. You should
	// include any packages that are part of your app, and exclude libraries
	// and helpers. You can list wildcards here, and they'll be expanded using
	// filepath.Glob. The default value is []string{"main*"}
	ProjectPackages []string

	// The SourceRoot is the directory where the application is built, and the
	// assumed prefix of lines on the stacktrace originating in the parent
	// application. When set, the prefix is trimmed from callstack file names
	// before ProjectPackages for better readability and to better group errors
	// on the Bugsnag dashboard. The default value is $GOPATH/src or $GOROOT/src
	// if $GOPATH is unset. At runtime, $GOROOT is the root used during the Go
	// build.
	SourceRoot string

	// Any meta-data that matches these filters will be marked as [REDACTED]
	// before sending a Notification to Bugsnag. It defaults to
	// []string{"password", "secret"} so that request parameters like password,
	// password_confirmation and auth_secret will not be sent to Bugsnag.
	ParamsFilters []string

	// The PanicHandler is used by Bugsnag to catch unhandled panics in your
	// application. The default panicHandler uses mitchellh's panicwrap library,
	// and you can disable this feature by passing an empty: func() {}
	PanicHandler func()

	// The logger that Bugsnag should log to. Uses the same defaults as go's
	// builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
	// of an error, and when any error occurs inside the library itself.
	Logger interface {
		Printf(format string, v ...interface{}) // limited to the functions used
	}
	// The http Transport to use, defaults to the default http Transport. This
	// can be configured if you are in an environment like Google App Engine
	// that has stringent conditions on making http requests.
	Transport http.RoundTripper
	// Whether bugsnag should notify synchronously. This defaults to false which
	// causes bugsnag-go to spawn a new goroutine for each notification.
	Synchronous bool
	// TODO: remember to update the update() function when modifying this struct
}

func (config *Configuration) update(other *Configuration) *Configuration {
	if other.APIKey != "" {
		config.APIKey = other.APIKey
	}
	if other.Endpoint != "" {
		config.Endpoint = other.Endpoint
	}
	if other.Hostname != "" {
		config.Hostname = other.Hostname
	}
	if other.AppType != "" {
		config.AppType = other.AppType
	}
	if other.AppVersion != "" {
		config.AppVersion = other.AppVersion
	}
	if other.SourceRoot != "" {
		config.SourceRoot = other.SourceRoot
	}
	if other.ReleaseStage != "" {
		config.ReleaseStage = other.ReleaseStage
	}
	if other.ParamsFilters != nil {
		config.ParamsFilters = other.ParamsFilters
	}
	if other.ProjectPackages != nil {
		config.ProjectPackages = other.ProjectPackages
	}
	if other.Logger != nil {
		config.Logger = other.Logger
	}
	if other.NotifyReleaseStages != nil {
		config.NotifyReleaseStages = other.NotifyReleaseStages
	}
	if other.PanicHandler != nil {
		config.PanicHandler = other.PanicHandler
	}
	if other.Transport != nil {
		config.Transport = other.Transport
	}
	if other.Synchronous {
		config.Synchronous = true
	}

	return config
}

func (config *Configuration) merge(other *Configuration) *Configuration {
	return config.clone().update(other)
}

func (config *Configuration) clone() *Configuration {
	clone := *config
	return &clone
}

func (config *Configuration) isProjectPackage(pkg string) bool {
	for _, p := range config.ProjectPackages {
		if d, f := filepath.Split(p); f == "**" {
			if strings.HasPrefix(pkg, d) {
				return true
			}
		}

		if match, _ := filepath.Match(p, pkg); match {
			return true
		}
	}
	return false
}

func (config *Configuration) stripProjectPackages(file string) string {
	trimmedFile := file
	if strings.HasPrefix(trimmedFile, config.SourceRoot) {
		trimmedFile = strings.TrimPrefix(trimmedFile, config.SourceRoot)
	}
	for _, p := range config.ProjectPackages {
		if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
			p = p[:len(p)-1]
		} else if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
			p = p[:len(p)-2]
		} else {
			p = p + "/"
		}
		if strings.HasPrefix(trimmedFile, p) {
			return strings.TrimPrefix(trimmedFile, p)
		}
	}

	return trimmedFile
}

func (config *Configuration) logf(fmt string, args ...interface{}) {
	if config != nil && config.Logger != nil {
		config.Logger.Printf(fmt, args...)
	} else {
		log.Printf(fmt, args...)
	}
}

func (config *Configuration) notifyInReleaseStage() bool {
	if config.NotifyReleaseStages == nil {
		return true
	}
	for _, r := range config.NotifyReleaseStages {
		if r == config.ReleaseStage {
			return true
		}
	}
	return false
}
<commit_msg>[feat] Update config struct with Endpoints<commit_after>package bugsnag

import (
	"log"
	"net/http"
	"path/filepath"
	"strings"
)

// Endpoints hold the HTTP endpoints of the notifier.
type Endpoints struct {
	Sessions string
	Notify   string
}

// Configuration sets up and customizes communication with the Bugsnag API.
type Configuration struct {
	// Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
	// find this by clicking Settings on https://bugsnag.com/.
	APIKey string

	// Deprecated: Use Endpoints (with an 's') instead.
	// The Endpoint to notify about crashes. This defaults to
	// "https://notify.bugsnag.com/", if you're using Bugsnag Enterprise then
	// set it to your internal Bugsnag endpoint.
	Endpoint string
	// Endpoints define the HTTP endpoints that the notifier should notify
	// about crashes and sessions. These default to notify.bugsnag.com for
	// error reports and sessions.bugsnag.com for sessions.
	// If you are using bugsnag on-premise you will have to set these to your
	// Event Server and Session Server endpoints. If the notify endpoint is set
	// but the sessions endpoint is not, session tracking will be disabled
	// automatically to avoid leaking session information outside of your
	// server configuration, and a warning will be logged.
	Endpoints Endpoints

	// The current release stage. This defaults to "production" and is used to
	// filter errors in the Bugsnag dashboard.
	ReleaseStage string
	// A specialized type of the application, such as the worker queue or web
	// framework used, like "rails", "mailman", or "celery"
	AppType string
	// The currently running version of the app. This is used to filter errors
	// in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
	// resolved errors if they happen in different app versions.
	AppVersion string
	// The hostname of the current server. This defaults to the return value of
	// os.Hostname() and is graphed in the Bugsnag dashboard.
	Hostname string

	// The Release stages to notify in. If you set this then bugsnag-go will
	// only send notifications to Bugsnag if the ReleaseStage is listed here.
	NotifyReleaseStages []string

	// packages that are part of your app. Bugsnag uses this to determine how
	// to group errors and how to display them on your dashboard. You should
	// include any packages that are part of your app, and exclude libraries
	// and helpers. You can list wildcards here, and they'll be expanded using
	// filepath.Glob. The default value is []string{"main*"}
	ProjectPackages []string

	// The SourceRoot is the directory where the application is built, and the
	// assumed prefix of lines on the stacktrace originating in the parent
	// application. When set, the prefix is trimmed from callstack file names
	// before ProjectPackages for better readability and to better group errors
	// on the Bugsnag dashboard. The default value is $GOPATH/src or $GOROOT/src
	// if $GOPATH is unset. At runtime, $GOROOT is the root used during the Go
	// build.
	SourceRoot string

	// Any meta-data that matches these filters will be marked as [REDACTED]
	// before sending a Notification to Bugsnag. It defaults to
	// []string{"password", "secret"} so that request parameters like password,
	// password_confirmation and auth_secret will not be sent to Bugsnag.
	ParamsFilters []string

	// The PanicHandler is used by Bugsnag to catch unhandled panics in your
	// application. The default panicHandler uses mitchellh's panicwrap library,
	// and you can disable this feature by passing an empty: func() {}
	PanicHandler func()

	// The logger that Bugsnag should log to. Uses the same defaults as go's
	// builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
	// of an error, and when any error occurs inside the library itself.
	Logger interface {
		Printf(format string, v ...interface{}) // limited to the functions used
	}
	// The http Transport to use, defaults to the default http Transport. This
	// can be configured if you are in an environment like Google App Engine
	// that has stringent conditions on making http requests.
	Transport http.RoundTripper
	// Whether bugsnag should notify synchronously. This defaults to false which
	// causes bugsnag-go to spawn a new goroutine for each notification.
	Synchronous bool
	// TODO: remember to update the update() function when modifying this struct
}

func (config *Configuration) update(other *Configuration) *Configuration {
	if other.APIKey != "" {
		config.APIKey = other.APIKey
	}
	if other.Endpoint != "" {
		config.Endpoint = other.Endpoint
	}
	if other.Hostname != "" {
		config.Hostname = other.Hostname
	}
	if other.AppType != "" {
		config.AppType = other.AppType
	}
	if other.AppVersion != "" {
		config.AppVersion = other.AppVersion
	}
	if other.SourceRoot != "" {
		config.SourceRoot = other.SourceRoot
	}
	if other.ReleaseStage != "" {
		config.ReleaseStage = other.ReleaseStage
	}
	if other.ParamsFilters != nil {
		config.ParamsFilters = other.ParamsFilters
	}
	if other.ProjectPackages != nil {
		config.ProjectPackages = other.ProjectPackages
	}
	if other.Logger != nil {
		config.Logger = other.Logger
	}
	if other.NotifyReleaseStages != nil {
		config.NotifyReleaseStages = other.NotifyReleaseStages
	}
	if other.PanicHandler != nil {
		config.PanicHandler = other.PanicHandler
	}
	if other.Transport != nil {
		config.Transport = other.Transport
	}
	if other.Synchronous {
		config.Synchronous = true
	}

	return config
}

func (config *Configuration) merge(other *Configuration) *Configuration {
	return config.clone().update(other)
}

func (config *Configuration) clone() *Configuration {
	clone := *config
	return &clone
}

func (config *Configuration) isProjectPackage(pkg string) bool {
	for _, p := range config.ProjectPackages {
		if d, f := filepath.Split(p); f == "**" {
			if strings.HasPrefix(pkg, d) {
				return true
			}
		}

		if match, _ := filepath.Match(p, pkg); match {
			return true
		}
	}
	return false
}

func (config *Configuration) stripProjectPackages(file string) string {
	trimmedFile := file
	if strings.HasPrefix(trimmedFile, config.SourceRoot) {
		trimmedFile = strings.TrimPrefix(trimmedFile, config.SourceRoot)
	}
	for _, p := range config.ProjectPackages {
		if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
			p = p[:len(p)-1]
		} else if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
			p = p[:len(p)-2]
		} else {
			p = p + "/"
		}
		if strings.HasPrefix(trimmedFile, p) {
			return strings.TrimPrefix(trimmedFile, p)
		}
	}

	return trimmedFile
}

func (config *Configuration) logf(fmt string, args ...interface{}) {
	if config != nil && config.Logger != nil {
		config.Logger.Printf(fmt, args...)
	} else {
		log.Printf(fmt, args...)
	}
}

func (config *Configuration) notifyInReleaseStage() bool {
	if config.NotifyReleaseStages == nil {
		return true
	}
	for _, r := range config.NotifyReleaseStages {
		if r == config.ReleaseStage {
			return true
		}
	}
	return false
}
<|endoftext|>