<commit_before>package main

import (
	"crypto/tls"
	"errors"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/go-discover/discover"
)

type HTTPFrontend struct {
	Addr      string
	TLSAddr   string
	TLSConfig *tls.Config

	mtx      sync.RWMutex
	domains  map[string]*httpServer
	services map[string]*httpServer

	etcdPrefix string

	etcd     *etcd.Client
	discover *discover.Client
}

func NewHTTPFrontend(addr string) (*HTTPFrontend, error) {
	f := &HTTPFrontend{
		Addr:       addr,
		etcd:       etcd.NewClient(nil),
		etcdPrefix: "/strowger/http/",
		domains:    make(map[string]*httpServer),
		services:   make(map[string]*httpServer),
	}
	var err error
	f.discover, err = discover.NewClient()
	return f, err
}

func (s *HTTPFrontend) AddHTTPDomain(domain string, service string, certs [][]byte, key []byte) error {
	return s.addDomain(domain, service, true)
}

func (s *HTTPFrontend) addDomain(domain string, service string, persist bool) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	if server, ok := s.domains[domain]; ok {
		if server.name != service {
			return errors.New("domain exists with different service")
		}
		return nil
	}

	server := s.services[service]
	if server == nil {
		services, err := s.discover.Services(service)
		if err != nil {
			return err
		}
		server = &httpServer{name: service, services: services}
	}
	if persist {
		if _, err := s.etcd.Create(s.etcdPrefix+domain+"/service", service, 0); err != nil {
			return err
		}
	}
	// TODO: set cert/key data if provided

	server.refs++
	s.domains[domain] = server
	s.services[service] = server
	// TODO: TLS config

	log.Println("Add service", service, "to domain", domain)

	return nil
}

func (s *HTTPFrontend) RemoveHTTPDomain(domain string) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	server := s.domains[domain]
	if server == nil {
		return
	}
	delete(s.domains, domain)
	server.refs++
	if server.refs <= 0 {
		// TODO: close service set stream
		delete(s.services, server.name)
	}
	// TODO: persist
}

func (s *HTTPFrontend) syncDatabase() {
	var since uint64
	data, err := s.etcd.GetAll(s.etcdPrefix, false)
	if e, ok := err.(etcd.EtcdError); ok && e.ErrorCode == 100 {
		// key not found, ignore
		goto watch
	}
	if err != nil {
		log.Fatal(err)
		return
	}
	since = data.ModifiedIndex
	for _, res := range data.Kvs {
		if !res.Dir {
			continue
		}
		domain := path.Base(res.Key)
		serviceRes, err := s.etcd.Get(res.Key+"/service", false)
		if err != nil {
			log.Fatal(err)
		}
		service := serviceRes.Value
		if err := s.addDomain(domain, service, false); err != nil {
			log.Fatal(err)
		}
	}

watch:
	stream := make(chan *etcd.Response)
	stop := make(chan bool)
	// TODO: store stop
	go s.etcd.Watch(s.etcdPrefix, since, stream, stop)
	for res := range stream {
		if !res.Dir && res.NewKey && path.Base(res.Key) == "service" {
			domain := path.Base(path.Dir(res.Key))
			if err := s.addDomain(domain, res.Value, false); err != nil {
				// TODO: log error
			}
		}
		// TODO: handle delete
	}
	log.Println("done watching etcd")
}

func (s *HTTPFrontend) serve() {
	l, err := net.Listen("tcp", s.Addr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) serveTLS() {
	l, err := net.Listen("tcp", s.TLSAddr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) handle(conn net.Conn) {
	defer conn.Close()
	sc := httputil.NewServerConn(conn, nil)
	req, err := sc.Read()
	if err != nil {
		if err != httputil.ErrPersistEOF {
			// TODO: log error
		}
		return
	}

	s.mtx.RLock()
	// TODO: handle wildcard domains
	backend := s.domains[req.Host]
	s.mtx.RUnlock()
	log.Println(req, backend)
	if backend == nil {
		// TODO: return 404
		return
	}
	_, tls := conn.(*tls.Conn)
	backend.handle(req, sc, tls)
}

type httpServer struct {
	name     string
	services *discover.ServiceSet
	refs     int
}

func (s *httpServer) getBackend() *httputil.ClientConn {
	for _, addr := range shuffle(s.services.OnlineAddrs()) {
		// TODO: set connection timeout
		backend, err := net.Dial("tcp", addr)
		if err != nil {
			// TODO: log error
			// TODO: limit number of backends tried
			// TODO: temporarily quarantine failing backends
			log.Println("backend error", err)
			continue
		}
		return httputil.NewClientConn(backend, nil)
	}
	// TODO: log no backends found error
	return nil
}

func (s *httpServer) handle(req *http.Request, sc *httputil.ServerConn, tls bool) {
	req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	backend := s.getBackend()
	if backend == nil {
		// TODO: Return 503
		log.Println("no backend found")
		return
	}
	defer backend.Close()

	for {
		if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" &&
			req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" {
			// TODO: return 405
		}

		req.Proto = "HTTP/1.1"
		req.ProtoMajor = 1
		req.ProtoMinor = 1
		delete(req.Header, "Te")
		delete(req.Header, "Transfer-Encoding")

		if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			if prior, ok := req.Header["X-Forwarded-For"]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
			req.Header.Set("X-Forwarded-For", clientIP)
		}
		if tls {
			req.Header.Set("X-Forwarded-Proto", "https")
		} else {
			req.Header.Set("X-Forwarded-Proto", "http")
		}
		// TODO: Set X-Forwarded-Port

		if err := backend.Write(req); err != nil {
			log.Println("server write err:", err)
			return
		}
		res, err := backend.Read(req)
		if res != nil {
			if err := sc.Write(req, res); err != nil {
				if err != io.EOF && err != httputil.ErrPersistEOF {
					log.Println("client write err:", err)
					// TODO: log error
				}
				return
			}
		}
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("server read err:", err)
				// TODO: log error
				// TODO: Return 502
			}
			return
		}

		// TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP)
		if res.StatusCode == http.StatusSwitchingProtocols {
			serverW, serverR := backend.Hijack()
			clientW, clientR := sc.Hijack()
			defer serverW.Close()
			done := make(chan struct{})
			go func() {
				serverR.WriteTo(clientW)
				close(done)
			}()
			clientR.WriteTo(serverW)
			<-done
			return
		}

		// TODO: http pipelining
		req, err = sc.Read()
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("client read err:", err)
			}
			return
		}
		req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	}
}

func shuffle(s []string) []string {
	for i := len(s) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<commit_msg>router: Update go-etcd api calls<commit_after>package main

import (
	"crypto/tls"
	"errors"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/go-discover/discover"
)

type HTTPFrontend struct {
	Addr      string
	TLSAddr   string
	TLSConfig *tls.Config

	mtx      sync.RWMutex
	domains  map[string]*httpServer
	services map[string]*httpServer

	etcdPrefix string

	etcd     *etcd.Client
	discover *discover.Client
}

func NewHTTPFrontend(addr string) (*HTTPFrontend, error) {
	f := &HTTPFrontend{
		Addr:       addr,
		etcd:       etcd.NewClient(nil),
		etcdPrefix: "/strowger/http/",
		domains:    make(map[string]*httpServer),
		services:   make(map[string]*httpServer),
	}
	var err error
	f.discover, err = discover.NewClient()
	return f, err
}

func (s *HTTPFrontend) AddHTTPDomain(domain string, service string, certs [][]byte, key []byte) error {
	return s.addDomain(domain, service, true)
}

func (s *HTTPFrontend) addDomain(domain string, service string, persist bool) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	if server, ok := s.domains[domain]; ok {
		if server.name != service {
			return errors.New("domain exists with different service")
		}
		return nil
	}

	server := s.services[service]
	if server == nil {
		services, err := s.discover.Services(service)
		if err != nil {
			return err
		}
		server = &httpServer{name: service, services: services}
	}
	if persist {
		if _, err := s.etcd.Create(s.etcdPrefix+domain+"/service", service, 0); err != nil {
			return err
		}
	}
	// TODO: set cert/key data if provided

	server.refs++
	s.domains[domain] = server
	s.services[service] = server
	// TODO: TLS config

	log.Println("Add service", service, "to domain", domain)

	return nil
}

func (s *HTTPFrontend) RemoveHTTPDomain(domain string) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	server := s.domains[domain]
	if server == nil {
		return
	}
	delete(s.domains, domain)
	server.refs++
	if server.refs <= 0 {
		// TODO: close service set stream
		delete(s.services, server.name)
	}
	// TODO: persist
}

func (s *HTTPFrontend) syncDatabase() {
	var since uint64
	data, err := s.etcd.Get(s.etcdPrefix, false, true)
	if e, ok := err.(etcd.EtcdError); ok && e.ErrorCode == 100 {
		// key not found, ignore
		goto watch
	}
	if err != nil {
		log.Fatal(err)
		return
	}
	since = data.ModifiedIndex
	for _, res := range data.Kvs {
		if !res.Dir {
			continue
		}
		domain := path.Base(res.Key)
		serviceRes, err := s.etcd.Get(res.Key+"/service", false, false)
		if err != nil {
			log.Fatal(err)
		}
		service := serviceRes.Value
		if err := s.addDomain(domain, service, false); err != nil {
			log.Fatal(err)
		}
	}

watch:
	stream := make(chan *etcd.Response)
	stop := make(chan bool)
	// TODO: store stop
	go s.etcd.Watch(s.etcdPrefix, since, false, stream, stop)
	for res := range stream {
		if !res.Dir && res.NewKey && path.Base(res.Key) == "service" {
			domain := path.Base(path.Dir(res.Key))
			if err := s.addDomain(domain, res.Value, false); err != nil {
				// TODO: log error
			}
		}
		// TODO: handle delete
	}
	log.Println("done watching etcd")
}

func (s *HTTPFrontend) serve() {
	l, err := net.Listen("tcp", s.Addr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) serveTLS() {
	l, err := net.Listen("tcp", s.TLSAddr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) handle(conn net.Conn) {
	defer conn.Close()
	sc := httputil.NewServerConn(conn, nil)
	req, err := sc.Read()
	if err != nil {
		if err != httputil.ErrPersistEOF {
			// TODO: log error
		}
		return
	}

	s.mtx.RLock()
	// TODO: handle wildcard domains
	backend := s.domains[req.Host]
	s.mtx.RUnlock()
	log.Println(req, backend)
	if backend == nil {
		// TODO: return 404
		return
	}
	_, tls := conn.(*tls.Conn)
	backend.handle(req, sc, tls)
}

type httpServer struct {
	name     string
	services *discover.ServiceSet
	refs     int
}

func (s *httpServer) getBackend() *httputil.ClientConn {
	for _, addr := range shuffle(s.services.OnlineAddrs()) {
		// TODO: set connection timeout
		backend, err := net.Dial("tcp", addr)
		if err != nil {
			// TODO: log error
			// TODO: limit number of backends tried
			// TODO: temporarily quarantine failing backends
			log.Println("backend error", err)
			continue
		}
		return httputil.NewClientConn(backend, nil)
	}
	// TODO: log no backends found error
	return nil
}

func (s *httpServer) handle(req *http.Request, sc *httputil.ServerConn, tls bool) {
	req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	backend := s.getBackend()
	if backend == nil {
		// TODO: Return 503
		log.Println("no backend found")
		return
	}
	defer backend.Close()

	for {
		if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" &&
			req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" {
			// TODO: return 405
		}

		req.Proto = "HTTP/1.1"
		req.ProtoMajor = 1
		req.ProtoMinor = 1
		delete(req.Header, "Te")
		delete(req.Header, "Transfer-Encoding")

		if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			if prior, ok := req.Header["X-Forwarded-For"]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
			req.Header.Set("X-Forwarded-For", clientIP)
		}
		if tls {
			req.Header.Set("X-Forwarded-Proto", "https")
		} else {
			req.Header.Set("X-Forwarded-Proto", "http")
		}
		// TODO: Set X-Forwarded-Port

		if err := backend.Write(req); err != nil {
			log.Println("server write err:", err)
			return
		}
		res, err := backend.Read(req)
		if res != nil {
			if err := sc.Write(req, res); err != nil {
				if err != io.EOF && err != httputil.ErrPersistEOF {
					log.Println("client write err:", err)
					// TODO: log error
				}
				return
			}
		}
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("server read err:", err)
				// TODO: log error
				// TODO: Return 502
			}
			return
		}

		// TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP)
		if res.StatusCode == http.StatusSwitchingProtocols {
			serverW, serverR := backend.Hijack()
			clientW, clientR := sc.Hijack()
			defer serverW.Close()
			done := make(chan struct{})
			go func() {
				serverR.WriteTo(clientW)
				close(done)
			}()
			clientR.WriteTo(serverW)
			<-done
			return
		}

		// TODO: http pipelining
		req, err = sc.Read()
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("client read err:", err)
			}
			return
		}
		req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	}
}

func shuffle(s []string) []string {
	for i := len(s) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<|endoftext|>


<commit_before>/*
	Package hwio implements a simple Arduino-like interface for controlling
	hardware I/O, with configurable backends depending on the device.
*/
package hwio

import (
	"errors"
	"fmt"
	"time"
)

type BitShiftOrder byte

const (
	LSBFIRST BitShiftOrder = iota
	MSBFIRST
)

// Reference to driver we're using
var driver HardwareDriver

// Retrieved from the driver, this is the map of the hardware pins supported by
// the driver and their capabilities
var definedPins HardwarePinMap

// A private type for associating a pin's definition with the current IO mode
// and any other dynamic properties of the pin.
type assignedPin struct {
	pinDef    *PinDef   // definition of pin
	pinIOMode PinIOMode // mode that was assigned to this pin
}

// A map of pin numbers to the assigned dynamic properties of the pin. This is
// set by PinMode when errorChecking is on, and can be used by other functions
// to determine if the request is valid given the assigned properties of the pin.
var assignedPins map[Pin]*assignedPin

// If set to true, functions should test that their constraints are met.
// e.g. test that the pin is capable of doing what is asked. This can be set
// with SetErrorChecking(). Setting to false bypasses checks for performance.
// By default turned on, which is a better default for beginners.
var errorChecking bool = true

// init() attempts to determine from the environment what the driver is. The
// intent is that the consumer of the library would not generally have to worry
// about it, it would just work. If it cannot determine the driver, it doesn't
// set the driver to anything.
func init() {
	determineDriver()
}

// Work out the driver from environment if we can.
func determineDriver() {
	SetDriver(new(BeagleBoneDriver))
}

// Check if the driver is assigned. If not, return an error to indicate that,
// otherwise return no error.
func assertDriver() error {
	if driver == nil {
		return errors.New("hwio has no configured driver")
	}
	return nil
}

// Set the driver. Also calls Init on the driver, and loads the capabilities
// of the device.
func SetDriver(d HardwareDriver) {
	driver = d
	driver.Init()
	definedPins = driver.PinMap()
	assignedPins = make(map[Pin]*assignedPin)
}

// Retrieve the current hardware driver.
func GetDriver() HardwareDriver {
	return driver
}

// Returns a map of the hardware pins. This will only work once the driver is
// set.
func GetDefinedPins() HardwarePinMap {
	return definedPins
}

// Set error checking. This should be called before pin assignments.
func SetErrorChecking(check bool) {
	errorChecking = check
}

// Set the mode of a pin. Analogous to Arduino pin mode.
func PinMode(pin Pin, mode PinIOMode) (e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		pd := definedPins[pin]
		if pd == nil {
			return errors.New(fmt.Sprintf("Pin %d is not defined by the current driver", pin))
		}

		if e = checkPinMode(mode, pd); e != nil {
			return
		}

		// assign this pin
		assignedPins[pin] = &assignedPin{pinDef: pd, pinIOMode: mode}
	}

	return driver.PinMode(pin, mode)
}

func checkPinMode(mode PinIOMode, pd *PinDef) (e error) {
	ok := false
	switch mode {
	case INPUT:
		ok = pd.HasCapability(CAP_INPUT)
	case OUTPUT:
		ok = pd.HasCapability(CAP_OUTPUT)
	case INPUT_PULLUP:
		ok = pd.HasCapability(CAP_INPUT_PULLUP)
	}
	if ok {
		return nil
	}
	return errors.New(fmt.Sprintf("Pin %d can't be set to mode %s because it does not support that capability", pd.pin, mode.String()))
}

// Write a value to a digital pin
func DigitalWrite(pin Pin, value int) (e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		a := assignedPins[pin]
		if a == nil {
			return errors.New(fmt.Sprintf("DigitalWrite: pin %d mode has not been set", pin))
		}
		if a.pinIOMode != OUTPUT {
			return errors.New(fmt.Sprintf("DigitalWrite: pin %d mode is not set for output", pin))
		}
	}

	return driver.DigitalWrite(pin, value)
}

// Read a value from a digital pin
func DigitalRead(pin Pin) (result int, e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return 0, e
		}

		a := assignedPins[pin]
		if a == nil {
			e = errors.New(fmt.Sprintf("DigitalRead: pin %d mode has not been set", pin))
			return
		}
		if a.pinIOMode != INPUT && a.pinIOMode != INPUT_PULLUP {
			e = errors.New(fmt.Sprintf("DigitalRead: pin %d mode is not set for input", pin))
			return
		}
	}

	return driver.DigitalRead(pin)
}

// Read an analog value from a pin. The range of values is hardware driver dependent.
func AnalogRead(pin Pin) (result int, e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		a := assignedPins[pin]
		if a == nil {
			e = errors.New(fmt.Sprintf("AnalogRead: pin %d mode has not been set", pin))
			return
		}
		if a.pinIOMode != INPUT && a.pinIOMode != INPUT_PULLUP {
			e = errors.New(fmt.Sprintf("AnalogRead: pin %d mode is not set for input", pin))
			return
		}
	}

	return driver.AnalogRead(pin)
}

// Write an analog value. The interpretation is hardware dependent, but is
// generally implemented using PWM.
func AnalogWrite(pin Pin, value int) (e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		a := assignedPins[pin]
		if a == nil {
			return errors.New(fmt.Sprintf("AnalogWrite: pin %d mode has not been set", pin))
		}
		if a.pinIOMode != OUTPUT {
			return errors.New(fmt.Sprintf("AnalogWrite: pin %d mode is not set for output", pin))
		}
	}

	return driver.AnalogWrite(pin, value)
}

// Delay execution by the specified number of milliseconds. This is a helper
// function for similarity with Arduino. It is implemented using standard go
// time package.
func Delay(duration int) {
	time.Sleep(time.Duration(duration) * time.Millisecond)
}

// Delay execution by the specified number of microseconds. This is a helper
// function for similarity with Arduino. It is implemented using standard go
// time package
func DelayMicroseconds(duration int) {
	time.Sleep(time.Duration(duration) * time.Microsecond)
}

func DebugPinMap() {
	fmt.Println("HardwarePinMap:")
	for key, val := range definedPins {
		fmt.Printf("Pin %d: %s\n", key, val.String())
	}
	fmt.Printf("\n")
}

// The approximate mapping of Arduino shiftOut, this shifts a byte out on the
// data pin, pulsing the clock pin high and then low.
func ShiftOut(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder) error {
	return ShiftOutSize(dataPin, clockPin, value, order, 8)
}

// More generic version of ShiftOut which shifts out n of data from value. The
// value shifted out is always the lowest n bits of the value, but 'order'
// determines whether the msb or lsb from that value are shifted first
func ShiftOutSize(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder, n uint) error {
	bit := uint(0)
	v := value
	mask := uint(1) << (n - 1)
	for i := uint(0); i < n; i++ {
		// get the next bit
		if order == LSBFIRST {
			bit = v & 1
			v = v >> 1
		} else {
			bit = v & mask
			if bit != 0 {
				bit = 1
			}
			v = v << 1
		}
		// write to data pin
		e := DigitalWrite(dataPin, int(bit))
		if e != nil {
			return e
		}
		// pulse clock high and then low
		e = DigitalWrite(clockPin, HIGH)
		if e != nil {
			return e
		}
		DigitalWrite(clockPin, LOW)
	}
	return nil
}

// def toggle(gpio_pin):
//   """ Toggles the state of the given digital pin. """
//   assert (gpio_pin in GPIO), "*Invalid GPIO pin: '%s'" % gpio_pin
//   _xorReg(GPIO[gpio_pin][0]+GPIO_DATAOUT, GPIO[gpio_pin][1])

// def pinState(gpio_pin):
//   """ Returns the state of a digital pin if it is configured as
//       an output. Returns None if it is configuredas an input. """
//   assert (gpio_pin in GPIO), "*Invalid GPIO pin: '%s'" % gpio_pin
//   if (_getReg(GPIO[gpio_pin][0]+GPIO_OE) & GPIO[gpio_pin][1]):
//     return None
//   if (_getReg(GPIO[gpio_pin][0]+GPIO_DATAOUT) & GPIO[gpio_pin][1]):
//     return HIGH
//   return LOW

// @todo Implement other core Arduino function equivalents:
//	AnalogReference
//	Tone
//	NoTone
//	ShiftOut
//	ShiftIn
//	PulseIn
//	Millis
//	Micros
//	RandomSeed
//	Random
//	AttachInterupt
//	DetachInterupt

// This is the interface that hardware drivers implement.
type HardwareDriver interface {
	// Initialise the driver after creation
	Init() (e error)

	// Set mode of a pin
	PinMode(pin Pin, mode PinIOMode) (e error)

	// Write digital output
	DigitalWrite(pin Pin, value int) error

	// Read digital input
	DigitalRead(Pin) (int, error)

	// PWM write
	AnalogWrite(pin Pin, value int) error

	// Analog input. Resolution is device dependent.
	AnalogRead(pin Pin) (int, error)

	// Return the pin map for the driver, listing all supported pins and their capabilities
	PinMap() (pinMap HardwarePinMap)

	// Close the driver before destruction
	Close()
}
<commit_msg>BUG: check for pulldown<commit_after>/*
	Package hwio implements a simple Arduino-like interface for controlling
	hardware I/O, with configurable backends depending on the device.
*/
package hwio

import (
	"errors"
	"fmt"
	"time"
)

type BitShiftOrder byte

const (
	LSBFIRST BitShiftOrder = iota
	MSBFIRST
)

// Reference to driver we're using
var driver HardwareDriver

// Retrieved from the driver, this is the map of the hardware pins supported by
// the driver and their capabilities
var definedPins HardwarePinMap

// A private type for associating a pin's definition with the current IO mode
// and any other dynamic properties of the pin.
type assignedPin struct {
	pinDef    *PinDef   // definition of pin
	pinIOMode PinIOMode // mode that was assigned to this pin
}

// A map of pin numbers to the assigned dynamic properties of the pin. This is
// set by PinMode when errorChecking is on, and can be used by other functions
// to determine if the request is valid given the assigned properties of the pin.
var assignedPins map[Pin]*assignedPin

// If set to true, functions should test that their constraints are met.
// e.g. test that the pin is capable of doing what is asked. This can be set
// with SetErrorChecking(). Setting to false bypasses checks for performance.
// By default turned on, which is a better default for beginners.
var errorChecking bool = true

// init() attempts to determine from the environment what the driver is. The
// intent is that the consumer of the library would not generally have to worry
// about it, it would just work. If it cannot determine the driver, it doesn't
// set the driver to anything.
func init() {
	determineDriver()
}

// Work out the driver from environment if we can.
func determineDriver() {
	SetDriver(new(BeagleBoneDriver))
}

// Check if the driver is assigned. If not, return an error to indicate that,
// otherwise return no error.
func assertDriver() error {
	if driver == nil {
		return errors.New("hwio has no configured driver")
	}
	return nil
}

// Set the driver. Also calls Init on the driver, and loads the capabilities
// of the device.
func SetDriver(d HardwareDriver) {
	driver = d
	driver.Init()
	definedPins = driver.PinMap()
	assignedPins = make(map[Pin]*assignedPin)
}

// Retrieve the current hardware driver.
func GetDriver() HardwareDriver {
	return driver
}

// Returns a map of the hardware pins. This will only work once the driver is
// set.
func GetDefinedPins() HardwarePinMap {
	return definedPins
}

// Set error checking. This should be called before pin assignments.
func SetErrorChecking(check bool) {
	errorChecking = check
}

// Set the mode of a pin. Analogous to Arduino pin mode.
func PinMode(pin Pin, mode PinIOMode) (e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		pd := definedPins[pin]
		if pd == nil {
			return errors.New(fmt.Sprintf("Pin %d is not defined by the current driver", pin))
		}

		if e = checkPinMode(mode, pd); e != nil {
			return
		}

		// assign this pin
		assignedPins[pin] = &assignedPin{pinDef: pd, pinIOMode: mode}
	}

	return driver.PinMode(pin, mode)
}

func checkPinMode(mode PinIOMode, pd *PinDef) (e error) {
	ok := false
	switch mode {
	case INPUT:
		ok = pd.HasCapability(CAP_INPUT)
	case OUTPUT:
		ok = pd.HasCapability(CAP_OUTPUT)
	case INPUT_PULLUP:
		ok = pd.HasCapability(CAP_INPUT_PULLUP)
	case INPUT_PULLDOWN:
		ok = pd.HasCapability(CAP_INPUT_PULLDOWN)
	}
	if ok {
		return nil
	}
	return errors.New(fmt.Sprintf("Pin %d can't be set to mode %s because it does not support that capability", pd.pin, mode.String()))
}

// Write a value to a digital pin
func DigitalWrite(pin Pin, value int) (e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		a := assignedPins[pin]
		if a == nil {
			return errors.New(fmt.Sprintf("DigitalWrite: pin %d mode has not been set", pin))
		}
		if a.pinIOMode != OUTPUT {
			return errors.New(fmt.Sprintf("DigitalWrite: pin %d mode is not set for output", pin))
		}
	}

	return driver.DigitalWrite(pin, value)
}

// Read a value from a digital pin
func DigitalRead(pin Pin) (result int, e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return 0, e
		}

		a := assignedPins[pin]
		if a == nil {
			e = errors.New(fmt.Sprintf("DigitalRead: pin %d mode has not been set", pin))
			return
		}
		if a.pinIOMode != INPUT && a.pinIOMode != INPUT_PULLUP {
			e = errors.New(fmt.Sprintf("DigitalRead: pin %d mode is not set for input", pin))
			return
		}
	}

	return driver.DigitalRead(pin)
}

// Read an analog value from a pin. The range of values is hardware driver dependent.
func AnalogRead(pin Pin) (result int, e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		a := assignedPins[pin]
		if a == nil {
			e = errors.New(fmt.Sprintf("AnalogRead: pin %d mode has not been set", pin))
			return
		}
		if a.pinIOMode != INPUT && a.pinIOMode != INPUT_PULLUP {
			e = errors.New(fmt.Sprintf("AnalogRead: pin %d mode is not set for input", pin))
			return
		}
	}

	return driver.AnalogRead(pin)
}

// Write an analog value. The interpretation is hardware dependent, but is
// generally implemented using PWM.
func AnalogWrite(pin Pin, value int) (e error) {
	if errorChecking {
		if e = assertDriver(); e != nil {
			return
		}

		a := assignedPins[pin]
		if a == nil {
			return errors.New(fmt.Sprintf("AnalogWrite: pin %d mode has not been set", pin))
		}
		if a.pinIOMode != OUTPUT {
			return errors.New(fmt.Sprintf("AnalogWrite: pin %d mode is not set for output", pin))
		}
	}

	return driver.AnalogWrite(pin, value)
}

// Delay execution by the specified number of milliseconds. This is a helper
// function for similarity with Arduino. It is implemented using standard go
// time package.
func Delay(duration int) {
	time.Sleep(time.Duration(duration) * time.Millisecond)
}

// Delay execution by the specified number of microseconds. This is a helper
// function for similarity with Arduino. It is implemented using standard go
// time package
func DelayMicroseconds(duration int) {
	time.Sleep(time.Duration(duration) * time.Microsecond)
}

func DebugPinMap() {
	fmt.Println("HardwarePinMap:")
	for key, val := range definedPins {
		fmt.Printf("Pin %d: %s\n", key, val.String())
	}
	fmt.Printf("\n")
}

// The approximate mapping of Arduino shiftOut, this shifts a byte out on the
// data pin, pulsing the clock pin high and then low.
func ShiftOut(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder) error {
	return ShiftOutSize(dataPin, clockPin, value, order, 8)
}

// More generic version of ShiftOut which shifts out n of data from value. The
// value shifted out is always the lowest n bits of the value, but 'order'
// determines whether the msb or lsb from that value are shifted first
func ShiftOutSize(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder, n uint) error {
	bit := uint(0)
	v := value
	mask := uint(1) << (n - 1)
	for i := uint(0); i < n; i++ {
		// get the next bit
		if order == LSBFIRST {
			bit = v & 1
			v = v >> 1
		} else {
			bit = v & mask
			if bit != 0 {
				bit = 1
			}
			v = v << 1
		}
		// write to data pin
		e := DigitalWrite(dataPin, int(bit))
		if e != nil {
			return e
		}
		// pulse clock high and then low
		e = DigitalWrite(clockPin, HIGH)
		if e != nil {
			return e
		}
		DigitalWrite(clockPin, LOW)
	}
	return nil
}

// def toggle(gpio_pin):
//   """ Toggles the state of the given digital pin. """
//   assert (gpio_pin in GPIO), "*Invalid GPIO pin: '%s'" % gpio_pin
//   _xorReg(GPIO[gpio_pin][0]+GPIO_DATAOUT, GPIO[gpio_pin][1])

// def pinState(gpio_pin):
//   """ Returns the state of a digital pin if it is configured as
//       an output. Returns None if it is configuredas an input. """
//   assert (gpio_pin in GPIO), "*Invalid GPIO pin: '%s'" % gpio_pin
//   if (_getReg(GPIO[gpio_pin][0]+GPIO_OE) & GPIO[gpio_pin][1]):
//     return None
//   if (_getReg(GPIO[gpio_pin][0]+GPIO_DATAOUT) & GPIO[gpio_pin][1]):
//     return HIGH
//   return LOW

// @todo Implement other core Arduino function equivalents:
//	AnalogReference
//	Tone
//	NoTone
//	ShiftOut
//	ShiftIn
//	PulseIn
//	Millis
//	Micros
//	RandomSeed
//	Random
//	AttachInterupt
//	DetachInterupt

// This is the interface that hardware drivers implement.
type HardwareDriver interface {
	// Initialise the driver after creation
	Init() (e error)

	// Set mode of a pin
	PinMode(pin Pin, mode PinIOMode) (e error)

	// Write digital output
	DigitalWrite(pin Pin, value int) error

	// Read digital input
	DigitalRead(Pin) (int, error)

	// PWM write
	AnalogWrite(pin Pin, value int) error

	// Analog input. Resolution is device dependent.
	AnalogRead(pin Pin) (int, error)

	// Return the pin map for the driver, listing all supported pins and their capabilities
	PinMap() (pinMap HardwarePinMap)

	// Close the driver before destruction
	Close()
}
<|endoftext|>


<commit_before>/*
Package lite allows you to securely validate headers without a full node.

This library pulls together all the crypto and algorithms, so given a
relatively recent (< unbonding period) known validator set, one can get
indisputable proof that data is in the chain (current state) or detect if the
node is lying to the client.

Tendermint RPC exposes a lot of info, but a malicious node could return any
data it wants to queries, or even to block headers, even making up fake
signatures from non-existent validators to justify it. This is a lot of logic
to get right, to be contained in a small, easy to use library, that does this
for you, so you can just build nice applications.

We design for clients who have no strong trust relationship with any Tendermint
node, just the blockchain and validator set as a whole.

# Data structures

## SignedHeader

SignedHeader is a block header along with a commit -- enough validator
precommit-vote signatures to prove its validity (> 2/3 of the voting power)
given the validator set responsible for signing that header. A FullCommit is a
SignedHeader along with the current and next validator sets.

The hash of the next validator set is included and signed in the SignedHeader.
This lets the lite client keep track of arbitrary changes to the validator set,
as every change to the validator set must be approved by inclusion in the
header and signed in the commit.

In the worst case, with every block changing the validators around completely,
a lite client can sync up with every block header to verify each validator set
change on the chain. In practice, most applications will not have frequent
drastic updates to the validator set, so the logic defined in this package for
lite client syncing is optimized to use intelligent bisection and
block-skipping for efficient sourcing and verification of these data structures
and updates to the validator set (see the DynamicVerifier for more
information).

The FullCommit is also declared in this package as a convenience structure,
which includes the SignedHeader along with the full current and next
ValidatorSets.

## Verifier

A Verifier validates a new SignedHeader given the currently known state. There
are two different types of Verifiers provided.

BaseVerifier - given a validator set and a height, this Verifier verifies
that > 2/3 of the voting power of the given validator set had signed the
SignedHeader, and that the SignedHeader was to be signed by the exact given
validator set, and that the height of the commit is at least height (or
greater).

DynamicVerifier - this Verifier implements an auto-update and persistence
strategy to verify any SignedHeader of the blockchain.

## Provider and PersistentProvider

A Provider allows us to store and retrieve the FullCommits.

```go
type Provider interface {
	// LatestFullCommit returns the latest commit with
	// minHeight <= height <= maxHeight.
	// If maxHeight is zero, returns the latest where
	// minHeight <= height.
	LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error)
}
```

* client.NewHTTPProvider - query Tendermint rpc.

A PersistentProvider is a Provider that also allows for saving state.  This is
used by the DynamicVerifier for persistence.

```go
type PersistentProvider interface {
	Provider

	// SaveFullCommit saves a FullCommit (without verification).
	SaveFullCommit(fc FullCommit) error
}
```

* DBProvider - persistence provider for use with any libs/DB.
* MultiProvider - combine multiple providers.

The suggested use for local light clients is client.NewHTTPProvider(...) for
getting new data (Source), and NewMultiProvider(NewDBProvider("label",
dbm.NewMemDB()), NewDBProvider("label", db.NewFileDB(...))) to store confirmed
full commits (Trusted)

# How We Track Validators

Unless you want to blindly trust the node you talk with, you need to trace
every response back to a hash in a block header and validate the commit
signatures of that block header match the proper validator set.  If there is a
static validator set, you store it locally upon initialization of the client,
and check against that every time.

If the validator set for the blockchain is dynamic, verifying block commits is
a bit more involved -- if there is a block at height H with a known (trusted)
validator set V, and another block at height H' (H' > H) with validator set V'
!= V, then we want a way to safely update it.

First, we get the new (unconfirmed) validator set V' and verify that H' is
internally consistent and properly signed by this V'. Assuming it is a valid
block, we check that at least 2/3 of the validators in V also signed it,
meaning it would also be valid under our old assumptions.  Then, we accept H'
and V' as valid and trusted and use that to validate for heights X > H' until a
more recent and updated validator set is found.

If we cannot update directly from H -> H' because there was too much change to
the validator set, then we can look for some Hm (H < Hm < H') with a validator
set Vm.  Then we try to update H -> Hm and then Hm -> H' in two steps.  If one
of these steps doesn't work, then we continue bisecting, until we eventually
have to externally validate the validator set changes at every block.

Since we never trust any server in this protocol, only the signatures
themselves, it doesn't matter if the seed comes from a (possibly malicious)
node or a (possibly malicious) user.  We can accept it or reject it based only
on our trusted validator set and cryptographic proofs. This makes it extremely
important to verify that you have the proper validator set when initializing
the client, as that is the root of all trust.

The software currently assumes that the unbonding period is infinite in
duration.  If the DynamicVerifier hasn't been updated in a while, you should
manually verify the block headers using other sources.

TODO: Update the software to handle cases around the unbonding period.

*/
package lite
<commit_msg>docs: fix lite client formatting (#3198)<commit_after>/*
Package lite allows you to securely validate headers without a full node.

This library pulls together all the crypto and algorithms, so given a
relatively recent (< unbonding period) known validator set, one can get
indisputable proof that data is in the chain (current state) or detect if the
node is lying to the client.

Tendermint RPC exposes a lot of info, but a malicious node could return any
data it wants to queries, or even to block headers, even making up fake
signatures from non-existent validators to justify it. This is a lot of logic
to get right, to be contained in a small, easy to use library, that does this
for you, so you can just build nice applications.

We design for clients who have no strong trust relationship with any Tendermint
node, just the blockchain and validator set as a whole.

SignedHeader

SignedHeader is a block header along with a commit -- enough validator
precommit-vote signatures to prove its validity (> 2/3 of the voting power)
given the validator set responsible for signing that header. A FullCommit is a
SignedHeader along with the current and next validator sets.

The hash of the next validator set is included and signed in the SignedHeader.
This lets the lite client keep track of arbitrary changes to the validator set,
as every change to the validator set must be approved by inclusion in the
header and signed in the commit.

In the worst case, with every block changing the validators around completely,
a lite client can sync up with every block header to verify each validator set
change on the chain. In practice, most applications will not have frequent
drastic updates to the validator set, so the logic defined in this package for
lite client syncing is optimized to use intelligent bisection and
block-skipping for efficient sourcing and verification of these data structures
and updates to the validator set (see the DynamicVerifier for more
information).

The FullCommit is also declared in this package as a convenience structure,
which includes the SignedHeader along with the full current and next
ValidatorSets.

Verifier

A Verifier validates a new SignedHeader given the currently known state. There
are two different types of Verifiers provided.

BaseVerifier - given a validator set and a height, this Verifier verifies
that > 2/3 of the voting power of the given validator set had signed the
SignedHeader, and that the SignedHeader was to be signed by the exact given
validator set, and that the height of the commit is at least height (or
greater).

DynamicVerifier - this Verifier implements an auto-update and persistence
strategy to verify any SignedHeader of the blockchain.

Provider and PersistentProvider

A Provider allows us to store and retrieve the FullCommits.

    type Provider interface {
        // LatestFullCommit returns the latest commit with
        // minHeight <= height <= maxHeight.
        // If maxHeight is zero, returns the latest where
        // minHeight <= height.
        LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error)
    }

* client.NewHTTPProvider - query Tendermint rpc.

A PersistentProvider is a Provider that also allows for saving state.  This is
used by the DynamicVerifier for persistence.

    type PersistentProvider interface {
        Provider

        // SaveFullCommit saves a FullCommit (without verification).
        SaveFullCommit(fc FullCommit) error
    }

* DBProvider - persistence provider for use with any libs/DB.

* MultiProvider - combine multiple providers.

The suggested use for local light clients is client.NewHTTPProvider(...) for
getting new data (Source), and NewMultiProvider(NewDBProvider("label",
dbm.NewMemDB()), NewDBProvider("label", db.NewFileDB(...))) to store confirmed
full commits (Trusted)

How We Track Validators

Unless you want to blindly trust the node you talk with, you need to trace
every response back to a hash in a block header and validate the commit
signatures of that block header match the proper validator set.  If there is a
static validator set, you store it locally upon initialization of the client,
and check against that every time.

If the validator set for the blockchain is dynamic, verifying block commits is
a bit more involved -- if there is a block at height H with a known (trusted)
validator set V, and another block at height H' (H' > H) with validator set V'
!= V, then we want a way to safely update it.

First, we get the new (unconfirmed) validator set V' and verify that H' is
internally consistent and properly signed by this V'. Assuming it is a valid
block, we check that at least 2/3 of the validators in V also signed it,
meaning it would also be valid under our old assumptions.  Then, we accept H'
and V' as valid and trusted and use that to validate for heights X > H' until a
more recent and updated validator set is found.

If we cannot update directly from H -> H' because there was too much change to
the validator set, then we can look for some Hm (H < Hm < H') with a validator
set Vm.  Then we try to update H -> Hm and then Hm -> H' in two steps.  If one
of these steps doesn't work, then we continue bisecting, until we eventually
have to externally validate the validator set changes at every block.

Since we never trust any server in this protocol, only the signatures
themselves, it doesn't matter if the seed comes from a (possibly malicious)
node or a (possibly malicious) user.  We can accept it or reject it based only
on our trusted validator set and cryptographic proofs. This makes it extremely
important to verify that you have the proper validator set when initializing
the client, as that is the root of all trust.

The software currently assumes that the unbonding period is infinite in
duration.  If the DynamicVerifier hasn't been updated in a while, you should
manually verify the block headers using other sources.

TODO: Update the software to handle cases around the unbonding period.

*/
package lite
<|endoftext|>


<commit_before>package i18n

import (
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/qor/admin"
	"github.com/qor/cache"
	"github.com/qor/cache/memory"
	"github.com/qor/qor"
	"github.com/qor/qor/resource"
	"github.com/qor/qor/utils"
	"github.com/theplant/cldr"
)

// Default default locale for i18n
var Default = "en-US"

// I18n struct that hold all translations
type I18n struct {
	Resource        *admin.Resource
	scope           string
	value           string
	Backends        []Backend
	FallbackLocales map[string][]string
	fallbackLocales []string
	cacheStore      cache.CacheStoreInterface
}

// ResourceName change display name in qor admin
func (I18n) ResourceName() string {
	return "Translation"
}

// Backend defined methods that needs for translation backend
type Backend interface {
	LoadTranslations() []*Translation
	SaveTranslation(*Translation) error
	DeleteTranslation(*Translation) error
}

// Translation is a struct for translations, including Translation Key, Locale, Value
type Translation struct {
	Key     string
	Locale  string
	Value   string
	Backend Backend `json:"-"`
}

// New initialize I18n with backends
func New(backends ...Backend) *I18n {
	i18n := &I18n{Backends: backends, cacheStore: memory.New()}
	i18n.loadToCacheStore()
	return i18n
}

// SetCacheStore set i18n's cache store
func (i18n *I18n) SetCacheStore(cacheStore cache.CacheStoreInterface) {
	i18n.cacheStore = cacheStore
	i18n.loadToCacheStore()
}

func (i18n *I18n) loadToCacheStore() {
	backends := i18n.Backends
	for i := len(backends) - 1; i >= 0; i-- {
		var backend = backends[i]
		for _, translation := range backend.LoadTranslations() {
			i18n.AddTranslation(translation)
		}
	}
}

// LoadTranslations load translations as map `map[locale]map[key]*Translation`
func (i18n *I18n) LoadTranslations() map[string]map[string]*Translation {
	var translations = map[string]map[string]*Translation{}

	for i := len(i18n.Backends); i > 0; i-- {
		backend := i18n.Backends[i-1]
		for _, translation := range backend.LoadTranslations() {
			if translations[translation.Locale] == nil {
				translations[translation.Locale] = map[string]*Translation{}
			}
			translations[translation.Locale][translation.Key] = translation
		}
	}
	return translations
}

// AddTranslation add translation
func (i18n *I18n) AddTranslation(translation *Translation) error {
	return i18n.cacheStore.Set(cacheKey(translation.Locale, translation.Key), translation)
}

// SaveTranslation save translation
func (i18n *I18n) SaveTranslation(translation *Translation) error {
	for _, backend := range i18n.Backends {
		if backend.SaveTranslation(translation) == nil {
			i18n.AddTranslation(translation)
			return nil
		}
	}

	return errors.New("failed to save translation")
}

// DeleteTranslation delete translation
func (i18n *I18n) DeleteTranslation(translation *Translation) (err error) {
	for _, backend := range i18n.Backends {
		backend.DeleteTranslation(translation)
	}

	return i18n.cacheStore.Delete(cacheKey(translation.Locale, translation.Key))
}

// Scope i18n scope
func (i18n *I18n) Scope(scope string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

// Default default value of translation if key is missing
func (i18n *I18n) Default(value string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

//  default value of translation if key is missing
func (i18n *I18n) Fallbacks(locale ...string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: locale}
}

// T translate with locale, key and arguments
func (i18n *I18n) T(locale, key string, args ...interface{}) template.HTML {
	var (
		value           = i18n.value
		translationKey  = key
		fallbackLocales = i18n.fallbackLocales
	)

	if locale == "" {
		locale = Default
	}

	if locales, ok := i18n.FallbackLocales[locale]; ok {
		fallbackLocales = append(fallbackLocales, locales...)
	}
	fallbackLocales = append(fallbackLocales, Default)

	if i18n.scope != "" {
		translationKey = strings.Join([]string{i18n.scope, key}, ".")
	}

	var translation Translation
	if err := i18n.cacheStore.Unmarshal(cacheKey(locale, key), &translation); err != nil || translation.Value == "" {
		for _, fallbackLocale := range fallbackLocales {
			if err := i18n.cacheStore.Unmarshal(cacheKey(fallbackLocale, key), &translation); err == nil && translation.Value != "" {
				break
			}
		}

		if translation.Value == "" {
			// Get default translation if not translated
			if err := i18n.cacheStore.Unmarshal(cacheKey(Default, key), &translation); err != nil || translation.Value == "" {
				// If not initialized
				translation = Translation{Key: translationKey, Value: value, Locale: locale, Backend: i18n.Backends[0]}

				// Save translation
				i18n.SaveTranslation(&translation)
			}
		}
	}

	if translation.Value != "" {
		value = translation.Value
	} else {
		value = key
	}

	if str, err := cldr.Parse(locale, value, args...); err == nil {
		value = str
	}

	return template.HTML(value)
}

// RenderInlineEditAssets render inline edit html, it is using: http://vitalets.github.io/x-editable/index.html
// You could use Bootstrap or JQuery UI by set isIncludeExtendAssetLib to false and load files by yourself
func RenderInlineEditAssets(isIncludeJQuery bool, isIncludeExtendAssetLib bool) (template.HTML, error) {
	for _, gopath := range strings.Split(os.Getenv("GOPATH"), ":") {
		var content string
		var hasError bool

		if isIncludeJQuery {
			content = `<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>`
		}

		if isIncludeExtendAssetLib {
			if extendLib, err := ioutil.ReadFile(path.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/inline-edit-libs.tmpl")); err == nil {
				content += string(extendLib)
			} else {
				hasError = true
			}

			if css, err := ioutil.ReadFile(path.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/stylesheets/i18n-inline.css")); err == nil {
				content += fmt.Sprintf("<style>%s</style>", string(css))
			} else {
				hasError = true
			}

		}

		if js, err := ioutil.ReadFile(path.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/javascripts/i18n-inline.js")); err == nil {
			content += fmt.Sprintf("<script type=\"text/javascript\">%s</script>", string(js))
		} else {
			hasError = true
		}

		if !hasError {
			return template.HTML(content), nil
		}
	}

	return template.HTML(""), errors.New("templates not found")
}

func getLocaleFromContext(context *qor.Context) string {
	if locale := utils.GetLocale(context); locale != "" {
		return locale
	}

	return Default
}

type availableLocalesInterface interface {
	AvailableLocales() []string
}

type viewableLocalesInterface interface {
	ViewableLocales() []string
}

type editableLocalesInterface interface {
	EditableLocales() []string
}

func getAvailableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(viewableLocalesInterface); ok {
		return user.ViewableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

func getEditableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(editableLocalesInterface); ok {
		return user.EditableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

// ConfigureQorResource configure qor resource for qor admin
func (i18n *I18n) ConfigureQorResource(res resource.Resourcer) {
	if res, ok := res.(*admin.Resource); ok {
		i18n.Resource = res
		res.UseTheme("i18n")
		res.GetAdmin().I18n = i18n
		res.SearchAttrs("value") // generate search handler for i18n

		var getPrimaryLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("primary_locale"); locale != "" {
				return locale
			}
			if availableLocales := getAvailableLocales(context.Request, context.CurrentUser); len(availableLocales) > 0 {
				return availableLocales[0]
			}
			return ""
		}

		var getEditingLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("to_locale"); locale != "" {
				return locale
			}
			return getLocaleFromContext(context.Context)
		}

		type matchedTranslation struct {
			Key           string
			PrimaryLocale string
			PrimaryValue  string
			EditingLocale string
			EditingValue  string
		}

		res.GetAdmin().RegisterFuncMap("i18n_available_translations", func(context *admin.Context) (results []matchedTranslation) {
			var (
				translationsMap     = i18n.LoadTranslations()
				matchedTranslations = map[string]matchedTranslation{}
				keys                = []string{}
				keyword             = strings.ToLower(context.Request.URL.Query().Get("keyword"))
				primaryLocale       = getPrimaryLocale(context)
				editingLocale       = getEditingLocale(context)
			)

			var filterTranslations = func(translations map[string]*Translation, isPrimary bool) {
				if translations != nil {
					for key, translation := range translations {
						if (keyword == "") || (strings.Index(strings.ToLower(translation.Key), keyword) != -1 ||
							strings.Index(strings.ToLower(translation.Value), keyword) != -1) {
							if _, ok := matchedTranslations[key]; !ok {
								var t = matchedTranslation{
									Key:           key,
									PrimaryLocale: primaryLocale,
									EditingLocale: editingLocale,
									EditingValue:  translation.Value,
								}

								if localeTranslations, ok := translationsMap[primaryLocale]; ok {
									if v, ok := localeTranslations[key]; ok {
										t.PrimaryValue = v.Value
									}
								}

								matchedTranslations[key] = t
								keys = append(keys, key)
							}
						}
					}
				}
			}

			filterTranslations(translationsMap[getEditingLocale(context)], false)
			if primaryLocale != editingLocale {
				filterTranslations(translationsMap[getPrimaryLocale(context)], true)
			}

			sort.Strings(keys)

			pagination := context.Searcher.Pagination
			pagination.Total = len(keys)
			pagination.PerPage, _ = strconv.Atoi(context.Request.URL.Query().Get("per_page"))
			pagination.CurrentPage, _ = strconv.Atoi(context.Request.URL.Query().Get("page"))

			if pagination.CurrentPage == 0 {
				pagination.CurrentPage = 1
			}

			if pagination.PerPage == 0 {
				pagination.PerPage = 25
			}

			if pagination.CurrentPage > 0 {
				pagination.Pages = pagination.Total / pagination.PerPage
			}

			context.Searcher.Pagination = pagination

			var paginationKeys []string
			if pagination.CurrentPage == -1 {
				paginationKeys = keys
			} else {
				lastIndex := pagination.CurrentPage * pagination.PerPage
				if pagination.Total < lastIndex {
					lastIndex = pagination.Total
				}

				startIndex := (pagination.CurrentPage - 1) * pagination.PerPage
				if lastIndex >= startIndex {
					paginationKeys = keys[startIndex:lastIndex]
				}
			}

			for _, key := range paginationKeys {
				results = append(results, matchedTranslations[key])
			}
			return results
		})

		res.GetAdmin().RegisterFuncMap("i18n_primary_locale", getPrimaryLocale)

		res.GetAdmin().RegisterFuncMap("i18n_editing_locale", getEditingLocale)

		res.GetAdmin().RegisterFuncMap("i18n_viewable_locales", func(context admin.Context) []string {
			return getAvailableLocales(context.Request, context.CurrentUser)
		})

		res.GetAdmin().RegisterFuncMap("i18n_editable_locales", func(context admin.Context) []string {
			return getEditableLocales(context.Request, context.CurrentUser)
		})

		controller := i18nController{i18n}
		router := res.GetAdmin().GetRouter()
		router.Get(res.ToParam(), controller.Index)
		router.Post(res.ToParam(), controller.Update)
		router.Put(res.ToParam(), controller.Update)

		res.GetAdmin().RegisterViewPath("github.com/qor/i18n/views")
	}
}

func cacheKey(strs ...string) string {
	return strings.Join(strs, "/")
}
<commit_msg>Use pointer of RouteConfig<commit_after>package i18n

import (
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/qor/admin"
	"github.com/qor/cache"
	"github.com/qor/cache/memory"
	"github.com/qor/qor"
	"github.com/qor/qor/resource"
	"github.com/qor/qor/utils"
	"github.com/theplant/cldr"
)

// Default default locale for i18n
var Default = "en-US"

// I18n struct that hold all translations
type I18n struct {
	Resource        *admin.Resource
	scope           string
	value           string
	Backends        []Backend
	FallbackLocales map[string][]string
	fallbackLocales []string
	cacheStore      cache.CacheStoreInterface
}

// ResourceName change display name in qor admin
func (I18n) ResourceName() string {
	return "Translation"
}

// Backend defined methods that needs for translation backend
type Backend interface {
	LoadTranslations() []*Translation
	SaveTranslation(*Translation) error
	DeleteTranslation(*Translation) error
}

// Translation is a struct for translations, including Translation Key, Locale, Value
type Translation struct {
	Key     string
	Locale  string
	Value   string
	Backend Backend `json:"-"`
}

// New initialize I18n with backends
func New(backends ...Backend) *I18n {
	i18n := &I18n{Backends: backends, cacheStore: memory.New()}
	i18n.loadToCacheStore()
	return i18n
}

// SetCacheStore set i18n's cache store
func (i18n *I18n) SetCacheStore(cacheStore cache.CacheStoreInterface) {
	i18n.cacheStore = cacheStore
	i18n.loadToCacheStore()
}

func (i18n *I18n) loadToCacheStore() {
	backends := i18n.Backends
	for i := len(backends) - 1; i >= 0; i-- {
		var backend = backends[i]
		for _, translation := range backend.LoadTranslations() {
			i18n.AddTranslation(translation)
		}
	}
}

// LoadTranslations load translations as map `map[locale]map[key]*Translation`
func (i18n *I18n) LoadTranslations() map[string]map[string]*Translation {
	var translations = map[string]map[string]*Translation{}

	for i := len(i18n.Backends); i > 0; i-- {
		backend := i18n.Backends[i-1]
		for _, translation := range backend.LoadTranslations() {
			if translations[translation.Locale] == nil {
				translations[translation.Locale] = map[string]*Translation{}
			}
			translations[translation.Locale][translation.Key] = translation
		}
	}
	return translations
}

// AddTranslation add translation
func (i18n *I18n) AddTranslation(translation *Translation) error {
	return i18n.cacheStore.Set(cacheKey(translation.Locale, translation.Key), translation)
}

// SaveTranslation save translation
func (i18n *I18n) SaveTranslation(translation *Translation) error {
	for _, backend := range i18n.Backends {
		if backend.SaveTranslation(translation) == nil {
			i18n.AddTranslation(translation)
			return nil
		}
	}

	return errors.New("failed to save translation")
}

// DeleteTranslation delete translation
func (i18n *I18n) DeleteTranslation(translation *Translation) (err error) {
	for _, backend := range i18n.Backends {
		backend.DeleteTranslation(translation)
	}

	return i18n.cacheStore.Delete(cacheKey(translation.Locale, translation.Key))
}

// Scope i18n scope
func (i18n *I18n) Scope(scope string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

// Default default value of translation if key is missing
func (i18n *I18n) Default(value string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

//  default value of translation if key is missing
func (i18n *I18n) Fallbacks(locale ...string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: locale}
}

// T translate with locale, key and arguments
func (i18n *I18n) T(locale, key string, args ...interface{}) template.HTML {
	var (
		value           = i18n.value
		translationKey  = key
		fallbackLocales = i18n.fallbackLocales
	)

	if locale == "" {
		locale = Default
	}

	if locales, ok := i18n.FallbackLocales[locale]; ok {
		fallbackLocales = append(fallbackLocales, locales...)
	}
	fallbackLocales = append(fallbackLocales, Default)

	if i18n.scope != "" {
		translationKey = strings.Join([]string{i18n.scope, key}, ".")
	}

	var translation Translation
	if err := i18n.cacheStore.Unmarshal(cacheKey(locale, key), &translation); err != nil || translation.Value == "" {
		for _, fallbackLocale := range fallbackLocales {
			if err := i18n.cacheStore.Unmarshal(cacheKey(fallbackLocale, key), &translation); err == nil && translation.Value != "" {
				break
			}
		}

		if translation.Value == "" {
			// Get default translation if not translated
			if err := i18n.cacheStore.Unmarshal(cacheKey(Default, key), &translation); err != nil || translation.Value == "" {
				// If not initialized
				translation = Translation{Key: translationKey, Value: value, Locale: locale, Backend: i18n.Backends[0]}

				// Save translation
				i18n.SaveTranslation(&translation)
			}
		}
	}

	if translation.Value != "" {
		value = translation.Value
	} else {
		value = key
	}

	if str, err := cldr.Parse(locale, value, args...); err == nil {
		value = str
	}

	return template.HTML(value)
}

// RenderInlineEditAssets render inline edit html, it is using: http://vitalets.github.io/x-editable/index.html
// You could use Bootstrap or JQuery UI by set isIncludeExtendAssetLib to false and load files by yourself
func RenderInlineEditAssets(isIncludeJQuery bool, isIncludeExtendAssetLib bool) (template.HTML, error) {
	for _, gopath := range strings.Split(os.Getenv("GOPATH"), ":") {
		var content string
		var hasError bool

		if isIncludeJQuery {
			content = `<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>`
		}

		if isIncludeExtendAssetLib {
			if extendLib, err := ioutil.ReadFile(path.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/inline-edit-libs.tmpl")); err == nil {
				content += string(extendLib)
			} else {
				hasError = true
			}

			if css, err := ioutil.ReadFile(path.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/stylesheets/i18n-inline.css")); err == nil {
				content += fmt.Sprintf("<style>%s</style>", string(css))
			} else {
				hasError = true
			}

		}

		if js, err := ioutil.ReadFile(path.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/javascripts/i18n-inline.js")); err == nil {
			content += fmt.Sprintf("<script type=\"text/javascript\">%s</script>", string(js))
		} else {
			hasError = true
		}

		if !hasError {
			return template.HTML(content), nil
		}
	}

	return template.HTML(""), errors.New("templates not found")
}

func getLocaleFromContext(context *qor.Context) string {
	if locale := utils.GetLocale(context); locale != "" {
		return locale
	}

	return Default
}

type availableLocalesInterface interface {
	AvailableLocales() []string
}

type viewableLocalesInterface interface {
	ViewableLocales() []string
}

type editableLocalesInterface interface {
	EditableLocales() []string
}

func getAvailableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(viewableLocalesInterface); ok {
		return user.ViewableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

func getEditableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(editableLocalesInterface); ok {
		return user.EditableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

// ConfigureQorResource configure qor resource for qor admin
func (i18n *I18n) ConfigureQorResource(res resource.Resourcer) {
	if res, ok := res.(*admin.Resource); ok {
		i18n.Resource = res
		res.UseTheme("i18n")
		res.GetAdmin().I18n = i18n
		res.SearchAttrs("value") // generate search handler for i18n

		var getPrimaryLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("primary_locale"); locale != "" {
				return locale
			}
			if availableLocales := getAvailableLocales(context.Request, context.CurrentUser); len(availableLocales) > 0 {
				return availableLocales[0]
			}
			return ""
		}

		var getEditingLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("to_locale"); locale != "" {
				return locale
			}
			return getLocaleFromContext(context.Context)
		}

		type matchedTranslation struct {
			Key           string
			PrimaryLocale string
			PrimaryValue  string
			EditingLocale string
			EditingValue  string
		}

		res.GetAdmin().RegisterFuncMap("i18n_available_translations", func(context *admin.Context) (results []matchedTranslation) {
			var (
				translationsMap     = i18n.LoadTranslations()
				matchedTranslations = map[string]matchedTranslation{}
				keys                = []string{}
				keyword             = strings.ToLower(context.Request.URL.Query().Get("keyword"))
				primaryLocale       = getPrimaryLocale(context)
				editingLocale       = getEditingLocale(context)
			)

			var filterTranslations = func(translations map[string]*Translation, isPrimary bool) {
				if translations != nil {
					for key, translation := range translations {
						if (keyword == "") || (strings.Index(strings.ToLower(translation.Key), keyword) != -1 ||
							strings.Index(strings.ToLower(translation.Value), keyword) != -1) {
							if _, ok := matchedTranslations[key]; !ok {
								var t = matchedTranslation{
									Key:           key,
									PrimaryLocale: primaryLocale,
									EditingLocale: editingLocale,
									EditingValue:  translation.Value,
								}

								if localeTranslations, ok := translationsMap[primaryLocale]; ok {
									if v, ok := localeTranslations[key]; ok {
										t.PrimaryValue = v.Value
									}
								}

								matchedTranslations[key] = t
								keys = append(keys, key)
							}
						}
					}
				}
			}

			filterTranslations(translationsMap[getEditingLocale(context)], false)
			if primaryLocale != editingLocale {
				filterTranslations(translationsMap[getPrimaryLocale(context)], true)
			}

			sort.Strings(keys)

			pagination := context.Searcher.Pagination
			pagination.Total = len(keys)
			pagination.PerPage, _ = strconv.Atoi(context.Request.URL.Query().Get("per_page"))
			pagination.CurrentPage, _ = strconv.Atoi(context.Request.URL.Query().Get("page"))

			if pagination.CurrentPage == 0 {
				pagination.CurrentPage = 1
			}

			if pagination.PerPage == 0 {
				pagination.PerPage = 25
			}

			if pagination.CurrentPage > 0 {
				pagination.Pages = pagination.Total / pagination.PerPage
			}

			context.Searcher.Pagination = pagination

			var paginationKeys []string
			if pagination.CurrentPage == -1 {
				paginationKeys = keys
			} else {
				lastIndex := pagination.CurrentPage * pagination.PerPage
				if pagination.Total < lastIndex {
					lastIndex = pagination.Total
				}

				startIndex := (pagination.CurrentPage - 1) * pagination.PerPage
				if lastIndex >= startIndex {
					paginationKeys = keys[startIndex:lastIndex]
				}
			}

			for _, key := range paginationKeys {
				results = append(results, matchedTranslations[key])
			}
			return results
		})

		res.GetAdmin().RegisterFuncMap("i18n_primary_locale", getPrimaryLocale)

		res.GetAdmin().RegisterFuncMap("i18n_editing_locale", getEditingLocale)

		res.GetAdmin().RegisterFuncMap("i18n_viewable_locales", func(context admin.Context) []string {
			return getAvailableLocales(context.Request, context.CurrentUser)
		})

		res.GetAdmin().RegisterFuncMap("i18n_editable_locales", func(context admin.Context) []string {
			return getEditableLocales(context.Request, context.CurrentUser)
		})

		controller := i18nController{i18n}
		router := res.GetAdmin().GetRouter()
		router.Get(res.ToParam(), controller.Index, &admin.RouteConfig{Resource: res})
		router.Post(res.ToParam(), controller.Update, &admin.RouteConfig{Resource: res})
		router.Put(res.ToParam(), controller.Update, &admin.RouteConfig{Resource: res})

		res.GetAdmin().RegisterViewPath("github.com/qor/i18n/views")
	}
}

func cacheKey(strs ...string) string {
	return strings.Join(strs, "/")
}
<|endoftext|>


<commit_before>package keepalive

import (
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/juju/errors"
	"github.com/service-exposer/exposer"
	"github.com/service-exposer/exposer/listener"
)

type command struct {
	isClient bool
	isServer bool
	cmd      string
}

func (cmd *command) String() string {
	return fmt.Sprintf("%#v", cmd)
}

func Test_keepalive(t *testing.T) {
	ms := func(n int) time.Duration {
		return time.Duration(n) * time.Millisecond
	}

	test_keepalive := func(t *testing.T,
		server_timeout, server_delay, client_timeout, client_interval time.Duration,
	) <-chan *command {
		cmds := make(chan *command)

		ln, dial := listener.Pipe()
		go exposer.Serve(ln, func(conn net.Conn) exposer.ProtocalHandler {
			proto := exposer.NewProtocal(conn)
			handlefn := ServerSide(server_timeout)

			proto.On = func(proto *exposer.Protocal, cmd string, details []byte) error {
				time.Sleep(server_delay)
				cmds <- &command{
					isServer: true,
					cmd:      cmd,
				}
				return errors.Trace(handlefn(proto, cmd, details))
			}

			return proto
		})

		conn, err := dial()
		if err != nil {
			t.Fatal(err)
		}

		proto := exposer.NewProtocal(conn)

		handlefn := ClientSide(client_timeout, client_interval)
		proto.On = func(proto *exposer.Protocal, cmd string, details []byte) error {
			cmds <- &command{
				isClient: true,
				cmd:      cmd,
			}
			return handlefn(proto, cmd, details)
		}
		go proto.Request(CMD_PING, nil)

		var cmd *command
		cmd = <-cmds
		if cmd.cmd != CMD_PING || !cmd.isServer {
			t.Fatal("expect", CMD_PING, "& isServer", "got", cmd)
		}
		cmd = <-cmds
		if cmd.cmd != CMD_PONG || !cmd.isClient {
			t.Fatal("expect", CMD_PONG, "& isClient", "got", cmd)
		}
		return cmds
	}

	var cmds <-chan *command
	var cmd *command

	func() {
		cmds = test_keepalive(t, ms(60), ms(0), ms(60), ms(30))
		cmd = <-cmds
		if cmd.cmd != CMD_PING || !cmd.isServer {
			t.Fatal("expect", CMD_PING, "& isServer", "got", cmd)
		}
		cmd = <-cmds
		if cmd.cmd != CMD_PONG || !cmd.isClient {
			t.Fatal("expect", CMD_PONG, "& isClient", "got", cmd)
		}
	}()
	func() {
		cmds = test_keepalive(t, ms(50), ms(0), ms(100), ms(90))
		cmd = <-cmds
		if cmd.cmd != EVENT_TIMEOUT || !cmd.isServer {
			t.Fatal("expect", EVENT_TIMEOUT, "& isServer", "got", cmd)
		}
	}()

	func() {
		defer func() {
			if r := recover(); r == nil {
				t.Fatal("expect panic")
			}
		}()
		cmds = test_keepalive(t, ms(30), ms(0), ms(30), ms(90))
	}()

	func() {
		defer func() {
			if r := recover(); r == nil {
				t.Fatal("expect panic")
			}
		}()
		cmds = test_keepalive(t, ms(30), ms(0), ms(90), ms(90))
	}()

	func() {
		cmds = test_keepalive(t, ms(150), ms(90), ms(150), ms(90))
		cmd = <-cmds
		if cmd.cmd != EVENT_TIMEOUT || !cmd.isClient {
			t.Fatal("expect", EVENT_TIMEOUT, "& isClient", "got", cmd)
		}
	}()
}
<commit_msg>fix: keepalive test cases<commit_after>package keepalive

import (
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/juju/errors"
	"github.com/service-exposer/exposer"
	"github.com/service-exposer/exposer/listener"
)

type command struct {
	isClient bool
	isServer bool
	cmd      string
}

func (cmd *command) String() string {
	return fmt.Sprintf("%#v", cmd)
}

func Test_keepalive(t *testing.T) {
	ms := func(n int) time.Duration {
		return time.Duration(n) * time.Millisecond
	}

	test_keepalive := func(t *testing.T,
		server_timeout, server_delay, client_timeout, client_interval time.Duration,
	) <-chan *command {
		cmds := make(chan *command)

		ln, dial := listener.Pipe()
		go exposer.Serve(ln, func(conn net.Conn) exposer.ProtocalHandler {
			proto := exposer.NewProtocal(conn)
			handlefn := ServerSide(server_timeout)

			proto.On = func(proto *exposer.Protocal, cmd string, details []byte) error {
				time.Sleep(server_delay)
				cmds <- &command{
					isServer: true,
					cmd:      cmd,
				}
				return errors.Trace(handlefn(proto, cmd, details))
			}

			return proto
		})

		conn, err := dial()
		if err != nil {
			t.Fatal(err)
		}

		proto := exposer.NewProtocal(conn)

		handlefn := ClientSide(client_timeout, client_interval)
		proto.On = func(proto *exposer.Protocal, cmd string, details []byte) error {
			cmds <- &command{
				isClient: true,
				cmd:      cmd,
			}
			return handlefn(proto, cmd, details)
		}
		go proto.Request(CMD_PING, nil)

		var cmd *command
		cmd = <-cmds
		if cmd.cmd != CMD_PING || !cmd.isServer {
			t.Fatal("expect", CMD_PING, "& isServer", "got", cmd)
		}
		cmd = <-cmds
		if cmd.cmd != CMD_PONG || !cmd.isClient {
			t.Fatal("expect", CMD_PONG, "& isClient", "got", cmd)
		}
		return cmds
	}

	var cmds <-chan *command
	var cmd *command

	func() {
		cmds = test_keepalive(t, ms(100), ms(0), ms(100), ms(1))
		cmd = <-cmds
		if cmd.cmd != CMD_PING || !cmd.isServer {
			t.Fatal("expect", CMD_PING, "& isServer", "got", cmd)
		}
		cmd = <-cmds
		if cmd.cmd != CMD_PONG || !cmd.isClient {
			t.Fatal("expect", CMD_PONG, "& isClient", "got", cmd)
		}
	}()
	func() {
		cmds = test_keepalive(t, ms(1), ms(0), ms(100), ms(50))
		cmd = <-cmds
		if cmd.cmd != EVENT_TIMEOUT || !cmd.isServer {
			t.Fatal("expect", EVENT_TIMEOUT, "& isServer", "got", cmd)
		}
	}()

	func() {
		defer func() {
			if r := recover(); r == nil {
				t.Fatal("expect panic")
			}
		}()
		cmds = test_keepalive(t, ms(30), ms(0), ms(30), ms(90))
	}()

	func() {
		defer func() {
			if r := recover(); r == nil {
				t.Fatal("expect panic")
			}
		}()
		cmds = test_keepalive(t, ms(30), ms(0), ms(90), ms(90))
	}()

	func() {
		cmds = test_keepalive(t, ms(100), ms(100), ms(50), ms(1))
		cmd = <-cmds
		if cmd.cmd != EVENT_TIMEOUT || !cmd.isClient {
			t.Fatal("expect", EVENT_TIMEOUT, "& isClient", "got", cmd)
		}
	}()
}
<|endoftext|>


<commit_before>// +build go1.10

package metadatad

import (
	"fmt"
	"net"
	"net/http"
	"os/exec"
	"runtime"
	"strconv"

	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/log/prefixlogger"
	"github.com/Symantec/Dominator/lib/wsyscall"
	proto "github.com/Symantec/Dominator/proto/hypervisor"
)

type statusType struct {
	namespaceFd int
	threadId    int
	err         error
}

func (s *server) startServer() error {
	cmd := exec.Command("ebtables", "-t", "nat", "-F")
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error running ebtables: %s: %s", err, string(output))
	}
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()
	for _, bridge := range s.bridges {
		if err := s.startServerOnBridge(bridge); err != nil {
			return err
		}
	}
	return nil
}

func (s *server) startServerOnBridge(bridge net.Interface) error {
	logger := prefixlogger.New(bridge.Name+": ", s.logger)
	startChannel := make(chan struct{})
	statusChannel := make(chan statusType, 1)
	go s.createNamespace(startChannel, statusChannel, logger)
	status := <-statusChannel
	if status.err != nil {
		return status.err
	}
	if err := createInterface(bridge, status.threadId, logger); err != nil {
		return err
	}
	startChannel <- struct{}{}
	status = <-statusChannel
	if status.err != nil {
		return status.err
	}
	subnetChannel := s.manager.MakeSubnetChannel()
	go s.addSubnets(bridge, status.namespaceFd, subnetChannel, logger)
	return nil
}

func (s *server) addSubnets(bridge net.Interface, namespaceFd int,
	subnetChannel <-chan proto.Subnet, logger log.DebugLogger) {
	logger.Debugf(0, "waiting for subnet updates in namespaceFD=%d\n",
		namespaceFd)
	if err := wsyscall.SetNetNamespace(namespaceFd); err != nil {
		logger.Println(err)
		return
	}
	for subnet := range subnetChannel {
		addRouteForBridge(bridge, subnet, logger)
	}
}

func addRouteForBridge(bridge net.Interface, subnet proto.Subnet,
	logger log.DebugLogger) {
	subnetMask := net.IPMask(subnet.IpMask)
	subnetAddr := subnet.IpGateway.Mask(subnetMask)
	addr := subnetAddr.String()
	mask := fmt.Sprintf("%d.%d.%d.%d",
		subnetMask[0], subnetMask[1], subnetMask[2], subnetMask[3])
	cmd := exec.Command("route", "add", "-net", addr, "netmask", mask, "eth0")
	if output, err := cmd.CombinedOutput(); err != nil {
		logger.Printf("error adding route: %s/%s: %s: %s",
			addr, mask, err, string(output))
	} else {
		logger.Debugf(0, "added route: %s/%s\n", addr, mask)
	}
}

func (s *server) createNamespace(startChannel <-chan struct{},
	statusChannel chan<- statusType, logger log.DebugLogger) {
	namespaceFd, threadId, err := wsyscall.UnshareNetNamespace()
	if err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	statusChannel <- statusType{namespaceFd: namespaceFd, threadId: threadId}
	<-startChannel
	cmd := exec.Command("ifconfig", "eth0", "169.254.169.254", "netmask",
		"255.255.255.255", "up")
	if err := cmd.Run(); err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	hypervisorListener, err := net.Listen("tcp",
		fmt.Sprintf("169.254.169.254:%d", s.hypervisorPortNum))
	if err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	metadataListener, err := net.Listen("tcp", "169.254.169.254:80")
	if err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	statusChannel <- statusType{namespaceFd: namespaceFd, threadId: threadId}
	logger.Printf("starting metadata server in thread: %d\n", threadId)
	go http.Serve(hypervisorListener, nil)
	http.Serve(metadataListener, s)
}

func createInterface(bridge net.Interface, threadId int,
	logger log.DebugLogger) error {
	localName := bridge.Name + "-ll"
	remoteName := bridge.Name + "-lr"
	if _, err := net.InterfaceByName(localName); err == nil {
		exec.Command("ip", "link", "delete", localName).Run()
	}
	cmd := exec.Command("ip", "link", "add", localName, "type", "veth",
		"peer", "name", remoteName)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error creating veth for bridge: %s: %s: %s",
			bridge.Name, err, output)
	}
	cmd = exec.Command("ifconfig", localName, "up")
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error bringing up local interface: %s: %s: %s",
			localName, err, output)
	}
	remoteInterface, err := net.InterfaceByName(remoteName)
	if err != nil {
		return err
	}
	cmd = exec.Command("ip", "link", "set", remoteName, "netns",
		strconv.FormatInt(int64(threadId), 10), "name", "eth0")
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error moving interface to namespace: %s: %s: %s",
			remoteName, err, output)
	}
	cmd = exec.Command("ip", "link", "set", localName, "master", bridge.Name)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error adding interface: %s to bridge: %s: %s: %s",
			localName, bridge.Name, err, output)
	}
	hwAddr := remoteInterface.HardwareAddr.String()
	cmd = exec.Command("ebtables", "-t", "nat", "-A", "PREROUTING",
		"--logical-in", bridge.Name, "-p", "ip",
		"--ip-dst", "169.254.0.0/16", "-j", "dnat", "--to-destination",
		hwAddr)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf(
			"error adding ebtables dnat to: %s to bridge: %s: %s: %s",
			hwAddr, bridge.Name, err, output)
	}
	logger.Printf("created veth, remote addr: %s\n", hwAddr)
	return nil
}
<commit_msg>Close idle connections in metadata server (resource leaks and DOS attacks).<commit_after>// +build go1.10

package metadatad

import (
	"fmt"
	"net"
	"net/http"
	"os/exec"
	"runtime"
	"strconv"
	"time"

	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/log/prefixlogger"
	"github.com/Symantec/Dominator/lib/wsyscall"
	proto "github.com/Symantec/Dominator/proto/hypervisor"
)

type statusType struct {
	namespaceFd int
	threadId    int
	err         error
}

func httpServe(listener net.Listener, handler http.Handler,
	idleTimeout time.Duration) error {
	httpServer := &http.Server{Handler: handler, IdleTimeout: idleTimeout}
	return httpServer.Serve(listener)
}

func (s *server) startServer() error {
	cmd := exec.Command("ebtables", "-t", "nat", "-F")
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error running ebtables: %s: %s", err, string(output))
	}
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()
	for _, bridge := range s.bridges {
		if err := s.startServerOnBridge(bridge); err != nil {
			return err
		}
	}
	return nil
}

func (s *server) startServerOnBridge(bridge net.Interface) error {
	logger := prefixlogger.New(bridge.Name+": ", s.logger)
	startChannel := make(chan struct{})
	statusChannel := make(chan statusType, 1)
	go s.createNamespace(startChannel, statusChannel, logger)
	status := <-statusChannel
	if status.err != nil {
		return status.err
	}
	if err := createInterface(bridge, status.threadId, logger); err != nil {
		return err
	}
	startChannel <- struct{}{}
	status = <-statusChannel
	if status.err != nil {
		return status.err
	}
	subnetChannel := s.manager.MakeSubnetChannel()
	go s.addSubnets(bridge, status.namespaceFd, subnetChannel, logger)
	return nil
}

func (s *server) addSubnets(bridge net.Interface, namespaceFd int,
	subnetChannel <-chan proto.Subnet, logger log.DebugLogger) {
	logger.Debugf(0, "waiting for subnet updates in namespaceFD=%d\n",
		namespaceFd)
	if err := wsyscall.SetNetNamespace(namespaceFd); err != nil {
		logger.Println(err)
		return
	}
	for subnet := range subnetChannel {
		addRouteForBridge(bridge, subnet, logger)
	}
}

func addRouteForBridge(bridge net.Interface, subnet proto.Subnet,
	logger log.DebugLogger) {
	subnetMask := net.IPMask(subnet.IpMask)
	subnetAddr := subnet.IpGateway.Mask(subnetMask)
	addr := subnetAddr.String()
	mask := fmt.Sprintf("%d.%d.%d.%d",
		subnetMask[0], subnetMask[1], subnetMask[2], subnetMask[3])
	cmd := exec.Command("route", "add", "-net", addr, "netmask", mask, "eth0")
	if output, err := cmd.CombinedOutput(); err != nil {
		logger.Printf("error adding route: %s/%s: %s: %s",
			addr, mask, err, string(output))
	} else {
		logger.Debugf(0, "added route: %s/%s\n", addr, mask)
	}
}

func (s *server) createNamespace(startChannel <-chan struct{},
	statusChannel chan<- statusType, logger log.DebugLogger) {
	namespaceFd, threadId, err := wsyscall.UnshareNetNamespace()
	if err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	statusChannel <- statusType{namespaceFd: namespaceFd, threadId: threadId}
	<-startChannel
	cmd := exec.Command("ifconfig", "eth0", "169.254.169.254", "netmask",
		"255.255.255.255", "up")
	if err := cmd.Run(); err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	hypervisorListener, err := net.Listen("tcp",
		fmt.Sprintf("169.254.169.254:%d", s.hypervisorPortNum))
	if err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	metadataListener, err := net.Listen("tcp", "169.254.169.254:80")
	if err != nil {
		statusChannel <- statusType{err: err}
		return
	}
	statusChannel <- statusType{namespaceFd: namespaceFd, threadId: threadId}
	logger.Printf("starting metadata server in thread: %d\n", threadId)
	go httpServe(hypervisorListener, nil, time.Second*5)
	httpServe(metadataListener, s, time.Second*5)
}

func createInterface(bridge net.Interface, threadId int,
	logger log.DebugLogger) error {
	localName := bridge.Name + "-ll"
	remoteName := bridge.Name + "-lr"
	if _, err := net.InterfaceByName(localName); err == nil {
		exec.Command("ip", "link", "delete", localName).Run()
	}
	cmd := exec.Command("ip", "link", "add", localName, "type", "veth",
		"peer", "name", remoteName)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error creating veth for bridge: %s: %s: %s",
			bridge.Name, err, output)
	}
	cmd = exec.Command("ifconfig", localName, "up")
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error bringing up local interface: %s: %s: %s",
			localName, err, output)
	}
	remoteInterface, err := net.InterfaceByName(remoteName)
	if err != nil {
		return err
	}
	cmd = exec.Command("ip", "link", "set", remoteName, "netns",
		strconv.FormatInt(int64(threadId), 10), "name", "eth0")
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error moving interface to namespace: %s: %s: %s",
			remoteName, err, output)
	}
	cmd = exec.Command("ip", "link", "set", localName, "master", bridge.Name)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error adding interface: %s to bridge: %s: %s: %s",
			localName, bridge.Name, err, output)
	}
	hwAddr := remoteInterface.HardwareAddr.String()
	cmd = exec.Command("ebtables", "-t", "nat", "-A", "PREROUTING",
		"--logical-in", bridge.Name, "-p", "ip",
		"--ip-dst", "169.254.0.0/16", "-j", "dnat", "--to-destination",
		hwAddr)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf(
			"error adding ebtables dnat to: %s to bridge: %s: %s: %s",
			hwAddr, bridge.Name, err, output)
	}
	logger.Printf("created veth, remote addr: %s\n", hwAddr)
	return nil
}
<|endoftext|>


<commit_before><commit_msg>flag to set address.<commit_after><|endoftext|>


<commit_before>package plugins

import (
	"errors"
	"github.com/kabukky/journey/filenames"
	"github.com/kabukky/journey/structure"
	"github.com/yuin/gopher-lua"
	"log"
	"os"
	"path/filepath"
)

func Load() error {
	// Make map
	nameMap := make(map[string]string, 0)
	err := filepath.Walk(filenames.PluginsFilepath, func(filePath string, info os.FileInfo, err error) error {
		if !info.IsDir() && filepath.Ext(filePath) == ".lua" {
			// Check if the lua file is a plugin entry point by executing it
			helperNames, err := getHelperNames(filePath)
			if err != nil {
				return err
			}
			// Add all file names of helpers to the name map
			for _, helperName := range helperNames {
				log.Println("Helper name:", helperName)
				absPath, err := filepath.Abs(filePath)
				if err != nil {
					log.Println("Error while determining absolute path to lua file:", err)
					return err
				}
				nameMap[helperName] = absPath
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if len(nameMap) == 0 {
		return errors.New("No plugins were loaded.")
	}
	// If plugins were loaded, create LuaPool and assign name map to LuaPool
	LuaPool = newLuaPool()
	LuaPool.m.Lock()
	defer LuaPool.m.Unlock()
	LuaPool.files = nameMap
	return nil
}

func getHelperNames(fileName string) ([]string, error) {
	// Make a slice to hold all helper names
	helperList := make([]string, 0)
	// Create a new lua state
	vm := lua.NewState()
	defer vm.Close()
	// Set up vm functions
	values := &structure.RequestData{}
	absDir, err := filepath.Abs(fileName)
	if err != nil {
		log.Println("Error while determining absolute path to lua file:", err)
		return helperList, err
	}
	setUpVm(vm, values, absDir)
	// Execute plugin
	// TODO: Is there a better way to just load the file? We only need to execute the register function (see below)
	err = vm.DoFile(absDir)
	if err != nil {
		// TODO: We are not returning upon error here. Keep it like this?
		log.Println("Error while loading plugin:", err)
	}
	err = vm.CallByParam(lua.P{Fn: vm.GetGlobal("register"), NRet: 1, Protect: true})
	if err != nil {
		// Fail silently since this is probably just a lua file without a register function
		return helperList, nil
	}
	// Get return value
	table := vm.ToTable(-1)
	// Check if return value is a table
	if table != nil {
		// Iterate the table for every helper name to be registered
		table.ForEach(func(key lua.LValue, value lua.LValue) {
			if str, ok := value.(lua.LString); ok {
				if string(str) != "" {
					helperList = append(helperList, string(str))
				}
			}
		})
	}
	return helperList, nil
}

// Creates all methods that can be used from Lua.
func setUpVm(vm *lua.LState, values *structure.RequestData, absPathToLuaFile string) {
	luaPath := filepath.Dir(absPathToLuaFile)
	// Function to get the directory of the current file (to add to LUA_PATH in Lua)
	vm.SetGlobal("getCurrentDir", vm.NewFunction(func(vm *lua.LState) int {
		vm.Push(lua.LString(luaPath))
		return 1 // Number of results
	}))
	// Function to print to the log
	vm.SetGlobal("print", vm.NewFunction(func(vm *lua.LState) int {
		log.Println(vm.Get(-1).String())
		return 0 // Number of results
	}))
	// Function to get number of posts in values
	vm.SetGlobal("getNumberOfPosts", vm.NewFunction(func(vm *lua.LState) int {
		vm.Push(lua.LNumber(len(values.Posts)))
		return 1 // Number of results
	}))
	// Function to get a post by its index
	vm.SetGlobal("getPost", vm.NewFunction(func(vm *lua.LState) int {
		postIndex := vm.ToInt(-1)
		vm.Push(convertPost(vm, &values.Posts[postIndex-1]))
		return 1 // Number of results
	}))
	// Function to get a user by post
	vm.SetGlobal("getAuthorForPost", vm.NewFunction(func(vm *lua.LState) int {
		postIndex := vm.ToInt(-1)
		vm.Push(convertUser(vm, values.Posts[postIndex-1].Author))
		return 1 // Number of results
	}))
	// Function to get tags by post
	vm.SetGlobal("getTagsForPost", vm.NewFunction(func(vm *lua.LState) int {
		postIndex := vm.ToInt(-1)
		vm.Push(convertTags(vm, values.Posts[postIndex-1].Tags))
		return 1 // Number of results
	}))
	// Function to get blog
	vm.SetGlobal("getBlog", vm.NewFunction(func(vm *lua.LState) int {
		vm.Push(convertBlog(vm, values.Blog))
		return 1 // Number of results
	}))
}
<commit_msg>Removed printing of every helper name loaded from plugins.<commit_after>package plugins

import (
	"errors"
	"github.com/kabukky/journey/filenames"
	"github.com/kabukky/journey/structure"
	"github.com/yuin/gopher-lua"
	"log"
	"os"
	"path/filepath"
)

func Load() error {
	// Make map
	nameMap := make(map[string]string, 0)
	err := filepath.Walk(filenames.PluginsFilepath, func(filePath string, info os.FileInfo, err error) error {
		if !info.IsDir() && filepath.Ext(filePath) == ".lua" {
			// Check if the lua file is a plugin entry point by executing it
			helperNames, err := getHelperNames(filePath)
			if err != nil {
				return err
			}
			// Add all file names of helpers to the name map
			for _, helperName := range helperNames {
				absPath, err := filepath.Abs(filePath)
				if err != nil {
					log.Println("Error while determining absolute path to lua file:", err)
					return err
				}
				nameMap[helperName] = absPath
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if len(nameMap) == 0 {
		return errors.New("No plugins were loaded.")
	}
	// If plugins were loaded, create LuaPool and assign name map to LuaPool
	LuaPool = newLuaPool()
	LuaPool.m.Lock()
	defer LuaPool.m.Unlock()
	LuaPool.files = nameMap
	return nil
}

func getHelperNames(fileName string) ([]string, error) {
	// Make a slice to hold all helper names
	helperList := make([]string, 0)
	// Create a new lua state
	vm := lua.NewState()
	defer vm.Close()
	// Set up vm functions
	values := &structure.RequestData{}
	absDir, err := filepath.Abs(fileName)
	if err != nil {
		log.Println("Error while determining absolute path to lua file:", err)
		return helperList, err
	}
	setUpVm(vm, values, absDir)
	// Execute plugin
	// TODO: Is there a better way to just load the file? We only need to execute the register function (see below)
	err = vm.DoFile(absDir)
	if err != nil {
		// TODO: We are not returning upon error here. Keep it like this?
		log.Println("Error while loading plugin:", err)
	}
	err = vm.CallByParam(lua.P{Fn: vm.GetGlobal("register"), NRet: 1, Protect: true})
	if err != nil {
		// Fail silently since this is probably just a lua file without a register function
		return helperList, nil
	}
	// Get return value
	table := vm.ToTable(-1)
	// Check if return value is a table
	if table != nil {
		// Iterate the table for every helper name to be registered
		table.ForEach(func(key lua.LValue, value lua.LValue) {
			if str, ok := value.(lua.LString); ok {
				if string(str) != "" {
					helperList = append(helperList, string(str))
				}
			}
		})
	}
	return helperList, nil
}

// Creates all methods that can be used from Lua.
func setUpVm(vm *lua.LState, values *structure.RequestData, absPathToLuaFile string) {
	luaPath := filepath.Dir(absPathToLuaFile)
	// Function to get the directory of the current file (to add to LUA_PATH in Lua)
	vm.SetGlobal("getCurrentDir", vm.NewFunction(func(vm *lua.LState) int {
		vm.Push(lua.LString(luaPath))
		return 1 // Number of results
	}))
	// Function to print to the log
	vm.SetGlobal("print", vm.NewFunction(func(vm *lua.LState) int {
		log.Println(vm.Get(-1).String())
		return 0 // Number of results
	}))
	// Function to get number of posts in values
	vm.SetGlobal("getNumberOfPosts", vm.NewFunction(func(vm *lua.LState) int {
		vm.Push(lua.LNumber(len(values.Posts)))
		return 1 // Number of results
	}))
	// Function to get a post by its index
	vm.SetGlobal("getPost", vm.NewFunction(func(vm *lua.LState) int {
		postIndex := vm.ToInt(-1)
		vm.Push(convertPost(vm, &values.Posts[postIndex-1]))
		return 1 // Number of results
	}))
	// Function to get a user by post
	vm.SetGlobal("getAuthorForPost", vm.NewFunction(func(vm *lua.LState) int {
		postIndex := vm.ToInt(-1)
		vm.Push(convertUser(vm, values.Posts[postIndex-1].Author))
		return 1 // Number of results
	}))
	// Function to get tags by post
	vm.SetGlobal("getTagsForPost", vm.NewFunction(func(vm *lua.LState) int {
		postIndex := vm.ToInt(-1)
		vm.Push(convertTags(vm, values.Posts[postIndex-1].Tags))
		return 1 // Number of results
	}))
	// Function to get blog
	vm.SetGlobal("getBlog", vm.NewFunction(func(vm *lua.LState) int {
		vm.Push(convertBlog(vm, values.Blog))
		return 1 // Number of results
	}))
}
<|endoftext|>