<commit_before>package router

import (
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"strconv"
)

type FieldValidator struct {
	fields map[string]string
	err    error
}

func NewFieldValidator(fields map[string]string) FieldValidator {
	return FieldValidator{fields, nil}
}

func (fv FieldValidator) Value(fieldName string) (string, error) {
	if fv.err != nil {
		return "", fv.err
	}
	val, found := fv.fields[fieldName]
	if !found {
		fv.err = fmt.Errorf("Field %s is missing", fieldName)
		return "", fv.err
	}
	return val, nil
}

func (fv FieldValidator) CheckEqual(fieldName, expectedValue string) error {
	val, err := fv.Value(fieldName)
	if err != nil {
		return err
	}
	if val != expectedValue {
		fv.err = fmt.Errorf("Field %s has wrong value; expected '%s', received '%s'", fieldName, expectedValue, val)
		return fv.err
	}
	return nil
}

func (fv FieldValidator) Err() error {
	return fv.err
}

func (conn *LocalConnection) handshake(enc *gob.Encoder, dec *gob.Decoder, acceptNewPeer bool) error {
	// We do not need to worry about locking in here as at this point
	// the connection is not reachable by any go-routine other than
	// ourself. Only when we add this connection to the conn.local
	// peer will it be visible from multiple go-routines.

	conn.extendReadDeadline()

	localConnID := randUint64()
	versionStr := fmt.Sprint(ProtocolVersion)
	handshakeSend := map[string]string{
		"Protocol":        Protocol,
		"ProtocolVersion": versionStr,
		"PeerNameFlavour": PeerNameFlavour,
		"Name":            conn.local.Name.String(),
		"NickName":        conn.local.NickName,
		"UID":             fmt.Sprint(conn.local.UID),
		"ConnID":          fmt.Sprint(localConnID)}
	handshakeRecv := map[string]string{}

	usingPassword := conn.Router.UsingPassword()
	var public, private *[32]byte
	var err error
	if usingPassword {
		public, private, err = GenerateKeyPair()
		if err != nil {
			return err
		}
		handshakeSend["PublicKey"] = hex.EncodeToString(public[:])
	}
	enc.Encode(handshakeSend)

	err = dec.Decode(&handshakeRecv)
	if err != nil {
		return err
	}
	fv := NewFieldValidator(handshakeRecv)
	fv.CheckEqual("Protocol", Protocol)
	fv.CheckEqual("ProtocolVersion", versionStr)
	fv.CheckEqual("PeerNameFlavour", PeerNameFlavour)
	nameStr, _ := fv.Value("Name")
	nickNameStr, _ := fv.Value("NickName")
	uidStr, _ := fv.Value("UID")
	remoteConnIDStr, _ := fv.Value("ConnID")
	if err := fv.Err(); err != nil {
		return err
	}

	name, err := PeerNameFromString(nameStr)
	if err != nil {
		return err
	}
	if !acceptNewPeer {
		if _, found := conn.Router.Peers.Fetch(name); !found {
			return fmt.Errorf("Found unknown remote name: %s at %s", name, conn.remoteTCPAddr)
		}
	}
	if existingConn, found := conn.local.ConnectionTo(name); found && existingConn.Established() {
		return fmt.Errorf("Already have connection to %s at %s", existingConn.Remote().FullName(), existingConn.RemoteTCPAddr())
	}
	uid, err := strconv.ParseUint(uidStr, 10, 64)
	if err != nil {
		return err
	}
	remoteConnID, err := strconv.ParseUint(remoteConnIDStr, 10, 64)
	if err != nil {
		return err
	}
	conn.uid = localConnID ^ remoteConnID

	remotePublicStr, rpErr := fv.Value("PublicKey")
	if usingPassword {
		if rpErr != nil {
			return rpErr
		}
		remotePublicSlice, rpErr := hex.DecodeString(remotePublicStr)
		if rpErr != nil {
			return rpErr
		}
		remotePublic := [32]byte{}
		for idx, elem := range remotePublicSlice {
			remotePublic[idx] = elem
		}
		conn.SessionKey = FormSessionKey(&remotePublic, private, conn.Router.Password)
		conn.tcpSender = NewEncryptedTCPSender(enc, conn)
		conn.Decryptor = NewNaClDecryptor(conn)
	} else {
		if rpErr == nil {
			return fmt.Errorf("Remote network is encrypted. Password required.")
		}
		conn.tcpSender = NewSimpleTCPSender(enc)
		conn.Decryptor = NewNonDecryptor(conn)
	}

	toPeer := NewPeer(name, nickNameStr, uid, 0)
	toPeer = conn.Router.Peers.FetchWithDefault(toPeer)
	switch toPeer {
	case nil:
		return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
	case conn.local:
		conn.remote = toPeer // have to do assigment here to ensure Shutdown releases ref count
		return fmt.Errorf("Cannot connect to ourself")
	default:
		conn.remote = toPeer
		return nil
	}
}
<commit_msg>refactor: extract helper function<commit_after>package router

import (
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"strconv"
)

type FieldValidator struct {
	fields map[string]string
	err    error
}

func NewFieldValidator(fields map[string]string) FieldValidator {
	return FieldValidator{fields, nil}
}

func (fv FieldValidator) Value(fieldName string) (string, error) {
	if fv.err != nil {
		return "", fv.err
	}
	val, found := fv.fields[fieldName]
	if !found {
		fv.err = fmt.Errorf("Field %s is missing", fieldName)
		return "", fv.err
	}
	return val, nil
}

func (fv FieldValidator) CheckEqual(fieldName, expectedValue string) error {
	val, err := fv.Value(fieldName)
	if err != nil {
		return err
	}
	if val != expectedValue {
		fv.err = fmt.Errorf("Field %s has wrong value; expected '%s', received '%s'", fieldName, expectedValue, val)
		return fv.err
	}
	return nil
}

func (fv FieldValidator) Err() error {
	return fv.err
}

func (conn *LocalConnection) handshake(enc *gob.Encoder, dec *gob.Decoder, acceptNewPeer bool) error {
	// We do not need to worry about locking in here as at this point
	// the connection is not reachable by any go-routine other than
	// ourself. Only when we add this connection to the conn.local
	// peer will it be visible from multiple go-routines.

	conn.extendReadDeadline()

	localConnID := randUint64()
	usingPassword := conn.Router.UsingPassword()
	fv, private, err := conn.handshakeSendRecv(localConnID, usingPassword, enc, dec)
	if err != nil {
		return err
	}
	nameStr, _ := fv.Value("Name")
	nickNameStr, _ := fv.Value("NickName")
	uidStr, _ := fv.Value("UID")
	remoteConnIDStr, _ := fv.Value("ConnID")
	if err := fv.Err(); err != nil {
		return err
	}

	name, err := PeerNameFromString(nameStr)
	if err != nil {
		return err
	}
	if !acceptNewPeer {
		if _, found := conn.Router.Peers.Fetch(name); !found {
			return fmt.Errorf("Found unknown remote name: %s at %s", name, conn.remoteTCPAddr)
		}
	}
	if existingConn, found := conn.local.ConnectionTo(name); found && existingConn.Established() {
		return fmt.Errorf("Already have connection to %s at %s", existingConn.Remote().FullName(), existingConn.RemoteTCPAddr())
	}
	uid, err := strconv.ParseUint(uidStr, 10, 64)
	if err != nil {
		return err
	}
	remoteConnID, err := strconv.ParseUint(remoteConnIDStr, 10, 64)
	if err != nil {
		return err
	}
	conn.uid = localConnID ^ remoteConnID

	remotePublicStr, rpErr := fv.Value("PublicKey")
	if usingPassword {
		if rpErr != nil {
			return rpErr
		}
		remotePublicSlice, rpErr := hex.DecodeString(remotePublicStr)
		if rpErr != nil {
			return rpErr
		}
		remotePublic := [32]byte{}
		for idx, elem := range remotePublicSlice {
			remotePublic[idx] = elem
		}
		conn.SessionKey = FormSessionKey(&remotePublic, private, conn.Router.Password)
		conn.tcpSender = NewEncryptedTCPSender(enc, conn)
		conn.Decryptor = NewNaClDecryptor(conn)
	} else {
		if rpErr == nil {
			return fmt.Errorf("Remote network is encrypted. Password required.")
		}
		conn.tcpSender = NewSimpleTCPSender(enc)
		conn.Decryptor = NewNonDecryptor(conn)
	}

	toPeer := NewPeer(name, nickNameStr, uid, 0)
	toPeer = conn.Router.Peers.FetchWithDefault(toPeer)
	switch toPeer {
	case nil:
		return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
	case conn.local:
		conn.remote = toPeer // have to do assigment here to ensure Shutdown releases ref count
		return fmt.Errorf("Cannot connect to ourself")
	default:
		conn.remote = toPeer
		return nil
	}
}

func (conn *LocalConnection) handshakeSendRecv(localConnID uint64, usingPassword bool, enc *gob.Encoder, dec *gob.Decoder) (*FieldValidator, *[32]byte, error) {
	versionStr := fmt.Sprint(ProtocolVersion)
	handshakeSend := map[string]string{
		"Protocol":        Protocol,
		"ProtocolVersion": versionStr,
		"PeerNameFlavour": PeerNameFlavour,
		"Name":            conn.local.Name.String(),
		"NickName":        conn.local.NickName,
		"UID":             fmt.Sprint(conn.local.UID),
		"ConnID":          fmt.Sprint(localConnID)}
	handshakeRecv := map[string]string{}

	var public, private *[32]byte
	var err error
	if usingPassword {
		public, private, err = GenerateKeyPair()
		if err != nil {
			return nil, nil, err
		}
		handshakeSend["PublicKey"] = hex.EncodeToString(public[:])
	}
	enc.Encode(handshakeSend)

	err = dec.Decode(&handshakeRecv)
	if err != nil {
		return nil, nil, err
	}
	fv := NewFieldValidator(handshakeRecv)
	fv.CheckEqual("Protocol", Protocol)
	fv.CheckEqual("ProtocolVersion", versionStr)
	fv.CheckEqual("PeerNameFlavour", PeerNameFlavour)
	return &fv, private, nil
}
<|endoftext|>


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

package config

import (
	"encoding/json"
	"errors"
	"reflect"
	"testing"

	"github.com/coreos/ignition/third_party/github.com/go-yaml/yaml"
)

func TestDevicePathUnmarshalJSON(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		device DevicePath
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"/path"`},
			out: out{device: DevicePath("/path")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{device: DevicePath("bad"), err: errors.New("device path not absolute")},
		},
	}

	for i, test := range tests {
		var device DevicePath
		err := json.Unmarshal([]byte(test.in.data), &device)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.device, device) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.device, device)
		}
	}
}

func TestDevicePathUnmarshalYAML(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		device DevicePath
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"/path"`},
			out: out{device: DevicePath("/path")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{device: DevicePath("bad"), err: errors.New("device path not absolute")},
		},
	}

	for i, test := range tests {
		var device DevicePath
		err := yaml.Unmarshal([]byte(test.in.data), &device)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.device, device) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.device, device)
		}
	}
}

func TestDevicePathAssertValid(t *testing.T) {
	type in struct {
		device DevicePath
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{device: DevicePath("/good/path")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("/name")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("/this/is/a/fairly/long/path/to/a/device.")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("/this one has spaces")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("relative/path")},
			out: out{err: errors.New("device path not absolute")},
		},
	}

	for i, test := range tests {
		err := test.in.device.assertValid()
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}

func TestFilesystemFormatUnmarshalJSON(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		format FilesystemFormat
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"ext4"`},
			out: out{format: FilesystemFormat("ext4")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{format: FilesystemFormat("bad"), err: errors.New("invalid filesystem format")},
		},
	}

	for i, test := range tests {
		var format FilesystemFormat
		err := json.Unmarshal([]byte(test.in.data), &format)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.format, format) {
			t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.format, format)
		}
	}
}

func TestFilesystemFormatUnmarshalYAML(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		format FilesystemFormat
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"ext4"`},
			out: out{format: FilesystemFormat("ext4")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{format: FilesystemFormat("bad"), err: errors.New("invalid filesystem format")},
		},
	}

	for i, test := range tests {
		var format FilesystemFormat
		err := yaml.Unmarshal([]byte(test.in.data), &format)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.format, format) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.format, format)
		}
	}
}

func TestFilesystemFormatAssertValid(t *testing.T) {
	type in struct {
		format FilesystemFormat
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{format: FilesystemFormat("ext4")},
			out: out{},
		},
		{
			in:  in{format: FilesystemFormat("btrfs")},
			out: out{},
		},
		{
			in:  in{format: FilesystemFormat("")},
			out: out{err: errors.New("invalid filesystem format")},
		},
	}

	for i, test := range tests {
		err := test.in.format.assertValid()
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}

func TestMkfsOptionsUnmarshalJSON(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		options MkfsOptions
		err     error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `["--label=ROOT"]`},
			out: out{options: MkfsOptions([]string{"--label=ROOT"})},
		},
	}

	for i, test := range tests {
		var options MkfsOptions
		err := json.Unmarshal([]byte(test.in.data), &options)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.options, options) {
			t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.options, options)
		}
	}
}

func TestMkfsOptionsUnmarshalYAML(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		options MkfsOptions
		err     error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `["--label=ROOT"]`},
			out: out{options: MkfsOptions([]string{"--label=ROOT"})},
		},
	}

	for i, test := range tests {
		var options MkfsOptions
		err := yaml.Unmarshal([]byte(test.in.data), &options)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.options, options) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.options, options)
		}
	}
}
<commit_msg>config: clean up filesystem tests<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/coreos/ignition/third_party/github.com/go-yaml/yaml"
)

func TestDevicePathUnmarshalJSON(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		device DevicePath
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"/path"`},
			out: out{device: DevicePath("/path")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{device: DevicePath("bad"), err: ErrFilesystemRelativePath},
		},
	}

	for i, test := range tests {
		var device DevicePath
		err := json.Unmarshal([]byte(test.in.data), &device)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.device, device) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.device, device)
		}
	}
}

func TestDevicePathUnmarshalYAML(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		device DevicePath
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"/path"`},
			out: out{device: DevicePath("/path")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{device: DevicePath("bad"), err: ErrFilesystemRelativePath},
		},
	}

	for i, test := range tests {
		var device DevicePath
		err := yaml.Unmarshal([]byte(test.in.data), &device)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.device, device) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.device, device)
		}
	}
}

func TestDevicePathAssertValid(t *testing.T) {
	type in struct {
		device DevicePath
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{device: DevicePath("/good/path")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("/name")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("/this/is/a/fairly/long/path/to/a/device.")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("/this one has spaces")},
			out: out{},
		},
		{
			in:  in{device: DevicePath("relative/path")},
			out: out{err: ErrFilesystemRelativePath},
		},
	}

	for i, test := range tests {
		err := test.in.device.assertValid()
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}

func TestFilesystemFormatUnmarshalJSON(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		format FilesystemFormat
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"ext4"`},
			out: out{format: FilesystemFormat("ext4")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{format: FilesystemFormat("bad"), err: ErrFilesystemInvalidFormat},
		},
	}

	for i, test := range tests {
		var format FilesystemFormat
		err := json.Unmarshal([]byte(test.in.data), &format)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.format, format) {
			t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.format, format)
		}
	}
}

func TestFilesystemFormatUnmarshalYAML(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		format FilesystemFormat
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `"ext4"`},
			out: out{format: FilesystemFormat("ext4")},
		},
		{
			in:  in{data: `"bad"`},
			out: out{format: FilesystemFormat("bad"), err: ErrFilesystemInvalidFormat},
		},
	}

	for i, test := range tests {
		var format FilesystemFormat
		err := yaml.Unmarshal([]byte(test.in.data), &format)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.format, format) {
			t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.format, format)
		}
	}
}

func TestFilesystemFormatAssertValid(t *testing.T) {
	type in struct {
		format FilesystemFormat
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{format: FilesystemFormat("ext4")},
			out: out{},
		},
		{
			in:  in{format: FilesystemFormat("btrfs")},
			out: out{},
		},
		{
			in:  in{format: FilesystemFormat("")},
			out: out{err: ErrFilesystemInvalidFormat},
		},
	}

	for i, test := range tests {
		err := test.in.format.assertValid()
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}

func TestMkfsOptionsUnmarshalJSON(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		options MkfsOptions
		err     error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `["--label=ROOT"]`},
			out: out{options: MkfsOptions([]string{"--label=ROOT"})},
		},
	}

	for i, test := range tests {
		var options MkfsOptions
		err := json.Unmarshal([]byte(test.in.data), &options)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.options, options) {
			t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.options, options)
		}
	}
}

func TestMkfsOptionsUnmarshalYAML(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		options MkfsOptions
		err     error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: `["--label=ROOT"]`},
			out: out{options: MkfsOptions([]string{"--label=ROOT"})},
		},
	}

	for i, test := range tests {
		var options MkfsOptions
		err := yaml.Unmarshal([]byte(test.in.data), &options)
		if !reflect.DeepEqual(test.out.err, err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.options, options) {
			t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.options, options)
		}
	}
}
<|endoftext|>


<commit_before>package mirror

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"

	"github.com/cybozu-go/aptutil/apt"
	"github.com/pkg/errors"
)

const (
	infoJSON = "info.json"
)

// Storage manages a directory tree that mirrors a Debian repository.
//
// Storage also keeps checksum information for stored files.
type Storage struct {
	dir    string
	prefix string

	mu   sync.RWMutex
	info map[string]*apt.FileInfo
}

// NewStorage constructs Storage.
//
// dir must be an absolute path to an existing directory.
// prefix should be a directory name.
func NewStorage(dir, prefix string) (*Storage, error) {
	if !filepath.IsAbs(dir) {
		return nil, errors.New("none absolute: " + dir)
	}

	dir = filepath.Clean(dir)
	st, err := os.Stat(dir)
	if err != nil {
		return nil, err
	}
	if !st.Mode().IsDir() {
		return nil, errors.New("not a directory: " + dir)
	}

	return &Storage{
		dir:    dir,
		prefix: prefix,
		info:   make(map[string]*apt.FileInfo),
	}, nil
}

// Dir returns the directory of the Storage.
func (s *Storage) Dir() string {
	return s.dir
}

// Load loads existing directory contents.
func (s *Storage) Load() error {
	infoPath := filepath.Join(s.dir, infoJSON)

	f, err := os.Open(infoPath)
	switch {
	case os.IsNotExist(err):
		return nil
	case err != nil:
		return err
	}
	defer f.Close()

	jd := json.NewDecoder(f)
	err = jd.Decode(&s.info)
	if err != nil {
		return errors.Wrap(err, "Storage.Load: "+infoPath)
	}
	return nil
}

// Save saves storage contents persistently.
func (s *Storage) Save() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	infoPath := filepath.Join(s.dir, infoJSON)
	f, err := os.OpenFile(infoPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	enc := json.NewEncoder(f)
	err = enc.Encode(s.info)
	if err != nil {
		return err
	}

	f.Sync()
	DirSyncTree(s.dir)

	return nil
}

// Store stores a file into this storage.
func (s *Storage) Store(fi *apt.FileInfo, data []byte) error {
	p := fi.Path()

	s.mu.Lock()
	_, ok := s.info[p]
	if ok {
		s.mu.Unlock()
		return errors.New("already stored: " + p)
	}
	s.info[p] = fi
	s.mu.Unlock()

	fp := filepath.Join(s.dir, s.prefix, filepath.Clean(p))
	d := filepath.Dir(fp)

	err := os.MkdirAll(d, 0755)
	if err != nil {
		return err
	}

	f, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(data)
	if err != nil {
		return err
	}
	return f.Sync()
}

// StoreWithHash stores a file into this storage with additional
// hard links for by-hash retrieval.
func (s *Storage) StoreWithHash(fi *apt.FileInfo, data []byte) error {
	fp := filepath.Join(s.dir, s.prefix, filepath.Clean(fi.Path()))
	d := filepath.Dir(fp)

	err := os.MkdirAll(d, 0755)
	if err != nil {
		return err
	}

	tmpf, err := ioutil.TempFile(d, "tmp")
	if err != nil {
		return errors.Wrap(err, "StoreWithHash")
	}
	defer func() {
		tmpf.Close()
		os.Remove(tmpf.Name())
	}()

	_, err = tmpf.Write(data)
	if err != nil {
		return errors.Wrap(err, "StoreWithHash")
	}
	err = tmpf.Sync()
	if err != nil {
		return errors.Wrap(err, "StoreWithHash")
	}

	return s.StoreLinkWithHash(fi, tmpf.Name())
}

// StoreLink stores a hard link to a file into this storage.
func (s *Storage) StoreLink(fi *apt.FileInfo, fullpath string) error {
	p := fi.Path()

	s.mu.Lock()
	_, ok := s.info[p]
	if ok {
		s.mu.Unlock()
		return errors.New("already stored: " + p)
	}
	s.info[p] = fi
	s.mu.Unlock()

	fp := filepath.Join(s.dir, s.prefix, filepath.Clean(p))
	d := filepath.Dir(fp)

	err := os.MkdirAll(d, 0755)
	if err != nil {
		return err
	}

	return os.Link(fullpath, fp)
}

// StoreLinkWithHash stores a hard link to a file into this storage
// with additional hard links for by-hash retrieval.
func (s *Storage) StoreLinkWithHash(fi *apt.FileInfo, fullpath string) error {
	p := fi.Path()
	md5p := fi.MD5SumPath()
	sha1p := fi.SHA1Path()
	sha256p := fi.SHA256Path()
	fpl := []string{
		filepath.Join(s.dir, s.prefix, filepath.Clean(p)),
		filepath.Join(s.dir, s.prefix, filepath.Clean(md5p)),
		filepath.Join(s.dir, s.prefix, filepath.Clean(sha1p)),
		filepath.Join(s.dir, s.prefix, filepath.Clean(sha256p)),
	}

	s.mu.Lock()
	_, ok := s.info[p]
	if ok {
		// ignore the canonical path because another file was already stored.
		fpl = fpl[1:]
	} else {
		s.info[p] = fi
	}
	s.info[md5p] = fi
	s.info[sha1p] = fi
	s.info[sha256p] = fi
	s.mu.Unlock()

	for _, fp := range fpl {
		d := filepath.Dir(fp)
		err := os.MkdirAll(d, 0755)
		if err != nil {
			return errors.Wrap(err, "StoreLinkWithHash: "+fp)
		}
		err = os.Link(fullpath, fp)
		if err != nil {
			return errors.Wrap(err, "StoreLinkWithHash: "+fp)
		}
	}
	return nil
}

// Lookup looks up a file in this storage.
//
// If a file matching fi exists, its info and full path is returned.
// Otherwise, nil and empty string is returned.
func (s *Storage) Lookup(fi *apt.FileInfo, byhash bool) (*apt.FileInfo, string) {
	f := func(p string) (*apt.FileInfo, string) {
		s.mu.RLock()
		defer s.mu.RUnlock()

		fi2, ok := s.info[p]
		if !ok || !fi.Same(fi2) {
			return nil, ""
		}
		return fi2, filepath.Join(s.dir, s.prefix, filepath.Clean(p))
	}

	if byhash {
		fi2, fullpath := f(fi.SHA256Path())
		if fi2 != nil {
			return fi2, fullpath
		}
	}

	return f(fi.Path())
}

// Open opens the named file and returns it.
func (s *Storage) Open(p string) (*os.File, error) {
	return os.Open(filepath.Join(s.dir, s.prefix, filepath.Clean(p)))
}
<commit_msg>[mirror] allow duplicates in by-hash directory<commit_after>package mirror

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"

	"github.com/cybozu-go/aptutil/apt"
	"github.com/pkg/errors"
)

const (
	infoJSON = "info.json"
)

// Storage manages a directory tree that mirrors a Debian repository.
//
// Storage also keeps checksum information for stored files.
type Storage struct {
	dir    string
	prefix string

	mu   sync.RWMutex
	info map[string]*apt.FileInfo
}

// NewStorage constructs Storage.
//
// dir must be an absolute path to an existing directory.
// prefix should be a directory name.
func NewStorage(dir, prefix string) (*Storage, error) {
	if !filepath.IsAbs(dir) {
		return nil, errors.New("none absolute: " + dir)
	}

	dir = filepath.Clean(dir)
	st, err := os.Stat(dir)
	if err != nil {
		return nil, err
	}
	if !st.Mode().IsDir() {
		return nil, errors.New("not a directory: " + dir)
	}

	return &Storage{
		dir:    dir,
		prefix: prefix,
		info:   make(map[string]*apt.FileInfo),
	}, nil
}

// Dir returns the directory of the Storage.
func (s *Storage) Dir() string {
	return s.dir
}

// Load loads existing directory contents.
func (s *Storage) Load() error {
	infoPath := filepath.Join(s.dir, infoJSON)

	f, err := os.Open(infoPath)
	switch {
	case os.IsNotExist(err):
		return nil
	case err != nil:
		return err
	}
	defer f.Close()

	jd := json.NewDecoder(f)
	err = jd.Decode(&s.info)
	if err != nil {
		return errors.Wrap(err, "Storage.Load: "+infoPath)
	}
	return nil
}

// Save saves storage contents persistently.
func (s *Storage) Save() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	infoPath := filepath.Join(s.dir, infoJSON)
	f, err := os.OpenFile(infoPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	enc := json.NewEncoder(f)
	err = enc.Encode(s.info)
	if err != nil {
		return err
	}

	f.Sync()
	DirSyncTree(s.dir)

	return nil
}

// Store stores a file into this storage.
func (s *Storage) Store(fi *apt.FileInfo, data []byte) error {
	p := fi.Path()

	s.mu.Lock()
	_, ok := s.info[p]
	if ok {
		s.mu.Unlock()
		return errors.New("already stored: " + p)
	}
	s.info[p] = fi
	s.mu.Unlock()

	fp := filepath.Join(s.dir, s.prefix, filepath.Clean(p))
	d := filepath.Dir(fp)

	err := os.MkdirAll(d, 0755)
	if err != nil {
		return err
	}

	f, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(data)
	if err != nil {
		return err
	}
	return f.Sync()
}

// StoreWithHash stores a file into this storage with additional
// hard links for by-hash retrieval.
func (s *Storage) StoreWithHash(fi *apt.FileInfo, data []byte) error {
	fp := filepath.Join(s.dir, s.prefix, filepath.Clean(fi.Path()))
	d := filepath.Dir(fp)

	err := os.MkdirAll(d, 0755)
	if err != nil {
		return err
	}

	tmpf, err := ioutil.TempFile(d, "tmp")
	if err != nil {
		return errors.Wrap(err, "StoreWithHash")
	}
	defer func() {
		tmpf.Close()
		os.Remove(tmpf.Name())
	}()

	_, err = tmpf.Write(data)
	if err != nil {
		return errors.Wrap(err, "StoreWithHash")
	}
	err = tmpf.Sync()
	if err != nil {
		return errors.Wrap(err, "StoreWithHash")
	}

	return s.StoreLinkWithHash(fi, tmpf.Name())
}

// StoreLink stores a hard link to a file into this storage.
func (s *Storage) StoreLink(fi *apt.FileInfo, fullpath string) error {
	p := fi.Path()

	s.mu.Lock()
	_, ok := s.info[p]
	if ok {
		s.mu.Unlock()
		return errors.New("already stored: " + p)
	}
	s.info[p] = fi
	s.mu.Unlock()

	fp := filepath.Join(s.dir, s.prefix, filepath.Clean(p))
	d := filepath.Dir(fp)

	err := os.MkdirAll(d, 0755)
	if err != nil {
		return err
	}

	return os.Link(fullpath, fp)
}

// StoreLinkWithHash stores a hard link to a file into this storage
// with additional hard links for by-hash retrieval.
func (s *Storage) StoreLinkWithHash(fi *apt.FileInfo, fullpath string) error {
	p := fi.Path()
	md5p := fi.MD5SumPath()
	sha1p := fi.SHA1Path()
	sha256p := fi.SHA256Path()
	fpl := []string{
		filepath.Join(s.dir, s.prefix, filepath.Clean(p)),
		filepath.Join(s.dir, s.prefix, filepath.Clean(md5p)),
		filepath.Join(s.dir, s.prefix, filepath.Clean(sha1p)),
		filepath.Join(s.dir, s.prefix, filepath.Clean(sha256p)),
	}

	s.mu.Lock()
	_, ok := s.info[p]
	if ok {
		// ignore the canonical path because another file was already stored.
		fpl = fpl[1:]
	} else {
		s.info[p] = fi
	}
	s.info[md5p] = fi
	s.info[sha1p] = fi
	s.info[sha256p] = fi
	s.mu.Unlock()

	for _, fp := range fpl {
		d := filepath.Dir(fp)
		err := os.MkdirAll(d, 0755)
		if err != nil {
			return errors.Wrap(err, "StoreLinkWithHash: "+fp)
		}
		err = os.Link(fullpath, fp)
		if err != nil && !os.IsExist(err) {
			return errors.Wrap(err, "StoreLinkWithHash: "+fp)
		}
	}
	return nil
}

// Lookup looks up a file in this storage.
//
// If a file matching fi exists, its info and full path is returned.
// Otherwise, nil and empty string is returned.
func (s *Storage) Lookup(fi *apt.FileInfo, byhash bool) (*apt.FileInfo, string) {
	f := func(p string) (*apt.FileInfo, string) {
		s.mu.RLock()
		defer s.mu.RUnlock()

		fi2, ok := s.info[p]
		if !ok || !fi.Same(fi2) {
			return nil, ""
		}
		return fi2, filepath.Join(s.dir, s.prefix, filepath.Clean(p))
	}

	if byhash {
		fi2, fullpath := f(fi.SHA256Path())
		if fi2 != nil {
			return fi2, fullpath
		}
	}

	return f(fi.Path())
}

// Open opens the named file and returns it.
func (s *Storage) Open(p string) (*os.File, error) {
	return os.Open(filepath.Join(s.dir, s.prefix, filepath.Clean(p)))
}
<|endoftext|>


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

// Native Client system calls.

package syscall

const OS = "nacl"

// Auto-generated

//sys	Chmod(path string, mode int) (errno int)
//sys	Clock() (clock int)
//sys	Close(fd int) (errno int)
//sys	Exit(code int)
//sys	Fstat(fd int, stat *Stat_t) (errno int)
//sys	Getdents(fd int, buf []byte) (n int, errno int)
//sys	Getpid() (pid int)
//sys	Gettimeofday(tv *Timeval) (errno int)
//sys	Open(path string, mode int, perm int) (fd int, errno int)
//sys	Read(fd int, p []byte) (n int, errno int)
//sys	read(fd int, buf *byte, nbuf int) (n int, errno int)
//sys	Stat(path string, stat *Stat_t) (errno int)
//sys	Write(fd int, p []byte) (n int, errno int)

//sys	MultimediaInit(subsys int) (errno int)
//sys	MultimediaShutdown() (errno int)

//sys	CondCreate() (cv int, errno int)
//sys	CondWait(cv int, mutex int) (errno int)
//sys	CondSignal(cv int) (errno int)
//sys	CondBroadcast(cv int) (errno int)
//sys	CondTimedWaitAbs(cv int, mutex int, abstime *Timespec) (errno int)
//sys	MutexCreate() (mutex int, errno int)
//sys	MutexLock(mutex int) (errno int)
//sys	MutexUnlock(mutex int) (errno int)
//sys	MutexTryLock(mutex int) (errno int) = SYS_MUTEX_TRYLOCK
//sys	SemCreate() (sema int, errno int)
//sys	SemWait(sema int) (errno int)
//sys	SemPost(sema int) (errno int)
//sys	VideoInit(dx int, dy int) (errno int)
//sys	VideoUpdate(data *uint32) (errno int)
//sys	VideoPollEvent(ev *byte) (errno int)
//sys	VideoShutdown() (errno int)
//sys	AudioInit(fmt int, nreq int, data *int) (errno int)
//sys	AudioShutdown() (errno int)
//sys	AudioStream(data *uint16, size *uintptr) (errno int)

// Hand-written

func Seek(fd int, offset int64, whence int) (newoffset int64, errno int) {
	// Offset passed to system call is 32 bits.  Failure of vision by NaCl.
	if int64(int32(offset)) != offset {
		return 0, ERANGE
	}
	o, _, e := Syscall(SYS_LSEEK, uintptr(fd), uintptr(offset), uintptr(whence));
	return int64(o), int(e);
}

// Sleep by waiting on a condition variable that will never be signaled.
// TODO(rsc): Replace when NaCl adds a proper sleep system call.
var tcv, tmu int

func init() {
	tmu, _ = MutexCreate();
	tcv, _ = CondCreate();
}

func Sleep(ns int64) (errno int) {
	ts := NsecToTimespec(ns);
	var tv Timeval;
	if errno = Gettimeofday(&tv); errno != 0 {
		return
	}
	ts.Sec += tv.Sec;
	ts.Nsec += tv.Usec * 1000;
	switch {
	case ts.Nsec >= 1e9:
		ts.Nsec -= 1e9;
		ts.Sec++;
	case ts.Nsec <= -1e9:
		ts.Nsec += 1e9;
		ts.Sec--;
	}
	if errno = MutexLock(tmu); errno != 0 {
		return
	}
	errno = CondTimedWaitAbs(tcv, tmu, &ts);
	if e := MutexUnlock(tmu); e != 0 && errno == 0 {
		errno = e
	}
	return;
}

// Implemented in NaCl but not here; maybe later:
//	SYS_IOCTL
//	SYS_IMC_*
//	SYS_MMAP ???
//	SYS_SRPC_*
//	SYS_SYSCONF

// Implemented in NaCl but not here; used by runtime instead:
//	SYS_SYSBRK
//	SYS_MMAP
//	SYS_MUNMAP
//	SYS_THREAD_*
//	SYS_TLS_*
//	SYS_SCHED_YIELD

// Not implemented in NaCl but needed to compile other packages.

const (
	SIGTRAP = 5;
)

func Pipe(p []int) (errno int)	{ return ENACL }

func fcntl(fd, cmd, arg int) (val int, errno int) {
	return 0, ENACL
}

func Pread(fd int, p []byte, offset int64) (n int, errno int) {
	return 0, ENACL
}

func Pwrite(fd int, p []byte, offset int64) (n int, errno int) {
	return 0, ENACL
}

func Mkdir(path string, mode int) (errno int)	{ return ENACL }

func Lstat(path string, stat *Stat_t) (errno int) {
	return ENACL
}

func Chdir(path string) (errno int)	{ return ENACL }

func Fchdir(fd int) (errno int)	{ return ENACL }

func Unlink(path string) (errno int)	{ return ENACL }

func Rmdir(path string) (errno int)	{ return ENACL }

func Link(oldpath, newpath string) (errno int) {
	return ENACL
}

func Symlink(path, link string) (errno int)	{ return ENACL }

func Readlink(path string, buf []byte) (n int, errno int) {
	return 0, ENACL
}

func Fchmod(fd int, mode int) (errno int)	{ return ENACL }

func Chown(path string, uid int, gid int) (errno int) {
	return ENACL
}

func Lchown(path string, uid int, gid int) (errno int) {
	return ENACL
}

func Fchown(fd int, uid int, gid int) (errno int) {
	return ENACL
}

func Truncate(name string, size int64) (errno int) {
	return ENACL
}

func Ftruncate(fd int, length int64) (errno int) {
	return ENACL
}

// NaCL doesn't actually implement Getwd, but it also
// don't implement Chdir, so the fallback algorithm
// fails worse than calling Getwd does.

const ImplementsGetwd = true

func Getwd() (wd string, errno int)	{ return "", ENACL }

func Getuid() (uid int)	{ return -1 }

func Geteuid() (euid int)	{ return -1 }

func Getgid() (gid int)	{ return -1 }

func Getegid() (egid int)	{ return -1 }

func Getppid() (ppid int)	{ return -1 }

func Getgroups() (gids []int, errno int)	{ return nil, ENACL }

type Sockaddr interface {
	sockaddr();
}

type SockaddrInet4 struct {
	Port	int;
	Addr	[4]byte;
}

func (*SockaddrInet4) sockaddr()	{}

type SockaddrInet6 struct {
	Port	int;
	Addr	[16]byte;
}

func (*SockaddrInet6) sockaddr()	{}

type SockaddrUnix struct {
	Name string;
}

func (*SockaddrUnix) sockaddr()	{}

const (
	AF_INET	= 1 + iota;
	AF_INET6;
	AF_UNIX;
	IPPROTO_TCP;
	SOCK_DGRAM;
	SOCK_STREAM;
	SOL_SOCKET;
	SOMAXCONN;
	SO_DONTROUTE;
	SO_KEEPALIVE;
	SO_LINGER;
	SO_RCVBUF;
	SO_REUSEADDR;
	SO_SNDBUF;
	TCP_NODELAY;
	WNOHANG;
	WSTOPPED;
	PTRACE_TRACEME;
	SO_BROADCAST	= 0;
	SHUT_RDWR	= 0;
)

func Accept(fd int) (nfd int, sa Sockaddr, errno int) {
	return 0, nil, ENACL
}

func Getsockname(fd int) (sa Sockaddr, errno int) {
	return nil, ENACL
}

func Getpeername(fd int) (sa Sockaddr, errno int) {
	return nil, ENACL
}

func Bind(fd int, sa Sockaddr) (errno int)	{ return ENACL }

func Connect(fd int, sa Sockaddr) (errno int)	{ return ENACL }

func Socket(domain, typ, proto int) (fd, errno int) {
	return 0, ENACL
}

func SetsockoptInt(fd, level, opt int, value int) (errno int) {
	return ENACL
}

func Shutdown(fd, how int) (errno int)	{ return ENACL }

func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, errno int) {
	return 0, nil, ENACL
}

func Sendto(fd int, p []byte, flags int, to Sockaddr) (errno int) {
	return ENACL
}

func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (errno int) {
	return ENACL
}

type Linger struct {
	Onoff	int32;
	Linger	int32;
}

func SetsockoptLinger(fd, level, opt int, l *Linger) (errno int) {
	return ENACL
}

func Listen(s int, n int) (errno int)	{ return ENACL }

type Rusage struct {
	Utime		Timeval;
	Stime		Timeval;
	Maxrss		int32;
	Ixrss		int32;
	Idrss		int32;
	Isrss		int32;
	Minflt		int32;
	Majflt		int32;
	Nswap		int32;
	Inblock		int32;
	Oublock		int32;
	Msgsnd		int32;
	Msgrcv		int32;
	Nsignals	int32;
	Nvcsw		int32;
	Nivcsw		int32;
}

func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, errno int) {
	return 0, ENACL
}

type WaitStatus uint32

func (WaitStatus) Exited() bool	{ return false }

func (WaitStatus) ExitStatus() int	{ return -1 }

func (WaitStatus) Signal() int	{ return -1 }

func (WaitStatus) CoreDump() bool	{ return false }

func (WaitStatus) Stopped() bool	{ return false }

func (WaitStatus) Continued() bool	{ return false }

func (WaitStatus) StopSignal() int	{ return -1 }

func (WaitStatus) Signaled() bool	{ return false }

func (WaitStatus) TrapCause() int	{ return -1 }
<commit_msg>Add syscall.Rename for NaCl.  Fixes NaCl build.<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Native Client system calls.

package syscall

const OS = "nacl"

// Auto-generated

//sys	Chmod(path string, mode int) (errno int)
//sys	Clock() (clock int)
//sys	Close(fd int) (errno int)
//sys	Exit(code int)
//sys	Fstat(fd int, stat *Stat_t) (errno int)
//sys	Getdents(fd int, buf []byte) (n int, errno int)
//sys	Getpid() (pid int)
//sys	Gettimeofday(tv *Timeval) (errno int)
//sys	Open(path string, mode int, perm int) (fd int, errno int)
//sys	Read(fd int, p []byte) (n int, errno int)
//sys	read(fd int, buf *byte, nbuf int) (n int, errno int)
//sys	Stat(path string, stat *Stat_t) (errno int)
//sys	Write(fd int, p []byte) (n int, errno int)

//sys	MultimediaInit(subsys int) (errno int)
//sys	MultimediaShutdown() (errno int)

//sys	CondCreate() (cv int, errno int)
//sys	CondWait(cv int, mutex int) (errno int)
//sys	CondSignal(cv int) (errno int)
//sys	CondBroadcast(cv int) (errno int)
//sys	CondTimedWaitAbs(cv int, mutex int, abstime *Timespec) (errno int)
//sys	MutexCreate() (mutex int, errno int)
//sys	MutexLock(mutex int) (errno int)
//sys	MutexUnlock(mutex int) (errno int)
//sys	MutexTryLock(mutex int) (errno int) = SYS_MUTEX_TRYLOCK
//sys	SemCreate() (sema int, errno int)
//sys	SemWait(sema int) (errno int)
//sys	SemPost(sema int) (errno int)
//sys	VideoInit(dx int, dy int) (errno int)
//sys	VideoUpdate(data *uint32) (errno int)
//sys	VideoPollEvent(ev *byte) (errno int)
//sys	VideoShutdown() (errno int)
//sys	AudioInit(fmt int, nreq int, data *int) (errno int)
//sys	AudioShutdown() (errno int)
//sys	AudioStream(data *uint16, size *uintptr) (errno int)

// Hand-written

func Seek(fd int, offset int64, whence int) (newoffset int64, errno int) {
	// Offset passed to system call is 32 bits.  Failure of vision by NaCl.
	if int64(int32(offset)) != offset {
		return 0, ERANGE
	}
	o, _, e := Syscall(SYS_LSEEK, uintptr(fd), uintptr(offset), uintptr(whence));
	return int64(o), int(e);
}

// Sleep by waiting on a condition variable that will never be signaled.
// TODO(rsc): Replace when NaCl adds a proper sleep system call.
var tcv, tmu int

func init() {
	tmu, _ = MutexCreate();
	tcv, _ = CondCreate();
}

func Sleep(ns int64) (errno int) {
	ts := NsecToTimespec(ns);
	var tv Timeval;
	if errno = Gettimeofday(&tv); errno != 0 {
		return
	}
	ts.Sec += tv.Sec;
	ts.Nsec += tv.Usec * 1000;
	switch {
	case ts.Nsec >= 1e9:
		ts.Nsec -= 1e9;
		ts.Sec++;
	case ts.Nsec <= -1e9:
		ts.Nsec += 1e9;
		ts.Sec--;
	}
	if errno = MutexLock(tmu); errno != 0 {
		return
	}
	errno = CondTimedWaitAbs(tcv, tmu, &ts);
	if e := MutexUnlock(tmu); e != 0 && errno == 0 {
		errno = e
	}
	return;
}

// Implemented in NaCl but not here; maybe later:
//	SYS_IOCTL
//	SYS_IMC_*
//	SYS_MMAP ???
//	SYS_SRPC_*
//	SYS_SYSCONF

// Implemented in NaCl but not here; used by runtime instead:
//	SYS_SYSBRK
//	SYS_MMAP
//	SYS_MUNMAP
//	SYS_THREAD_*
//	SYS_TLS_*
//	SYS_SCHED_YIELD

// Not implemented in NaCl but needed to compile other packages.

const (
	SIGTRAP = 5;
)

func Pipe(p []int) (errno int)	{ return ENACL }

func fcntl(fd, cmd, arg int) (val int, errno int) {
	return 0, ENACL
}

func Pread(fd int, p []byte, offset int64) (n int, errno int) {
	return 0, ENACL
}

func Pwrite(fd int, p []byte, offset int64) (n int, errno int) {
	return 0, ENACL
}

func Mkdir(path string, mode int) (errno int)	{ return ENACL }

func Lstat(path string, stat *Stat_t) (errno int) {
	return ENACL
}

func Chdir(path string) (errno int)	{ return ENACL }

func Fchdir(fd int) (errno int)	{ return ENACL }

func Unlink(path string) (errno int)	{ return ENACL }

func Rmdir(path string) (errno int)	{ return ENACL }

func Link(oldpath, newpath string) (errno int) {
	return ENACL
}

func Symlink(path, link string) (errno int)	{ return ENACL }

func Readlink(path string, buf []byte) (n int, errno int) {
	return 0, ENACL
}

func Rename(oldpath, newpath string) (errno int) {
	return ENACL
}

func Fchmod(fd int, mode int) (errno int)	{ return ENACL }

func Chown(path string, uid int, gid int) (errno int) {
	return ENACL
}

func Lchown(path string, uid int, gid int) (errno int) {
	return ENACL
}

func Fchown(fd int, uid int, gid int) (errno int) {
	return ENACL
}

func Truncate(name string, size int64) (errno int) {
	return ENACL
}

func Ftruncate(fd int, length int64) (errno int) {
	return ENACL
}

// NaCL doesn't actually implement Getwd, but it also
// don't implement Chdir, so the fallback algorithm
// fails worse than calling Getwd does.

const ImplementsGetwd = true

func Getwd() (wd string, errno int)	{ return "", ENACL }

func Getuid() (uid int)	{ return -1 }

func Geteuid() (euid int)	{ return -1 }

func Getgid() (gid int)	{ return -1 }

func Getegid() (egid int)	{ return -1 }

func Getppid() (ppid int)	{ return -1 }

func Getgroups() (gids []int, errno int)	{ return nil, ENACL }

type Sockaddr interface {
	sockaddr();
}

type SockaddrInet4 struct {
	Port	int;
	Addr	[4]byte;
}

func (*SockaddrInet4) sockaddr()	{}

type SockaddrInet6 struct {
	Port	int;
	Addr	[16]byte;
}

func (*SockaddrInet6) sockaddr()	{}

type SockaddrUnix struct {
	Name string;
}

func (*SockaddrUnix) sockaddr()	{}

const (
	AF_INET	= 1 + iota;
	AF_INET6;
	AF_UNIX;
	IPPROTO_TCP;
	SOCK_DGRAM;
	SOCK_STREAM;
	SOL_SOCKET;
	SOMAXCONN;
	SO_DONTROUTE;
	SO_KEEPALIVE;
	SO_LINGER;
	SO_RCVBUF;
	SO_REUSEADDR;
	SO_SNDBUF;
	TCP_NODELAY;
	WNOHANG;
	WSTOPPED;
	PTRACE_TRACEME;
	SO_BROADCAST	= 0;
	SHUT_RDWR	= 0;
)

func Accept(fd int) (nfd int, sa Sockaddr, errno int) {
	return 0, nil, ENACL
}

func Getsockname(fd int) (sa Sockaddr, errno int) {
	return nil, ENACL
}

func Getpeername(fd int) (sa Sockaddr, errno int) {
	return nil, ENACL
}

func Bind(fd int, sa Sockaddr) (errno int)	{ return ENACL }

func Connect(fd int, sa Sockaddr) (errno int)	{ return ENACL }

func Socket(domain, typ, proto int) (fd, errno int) {
	return 0, ENACL
}

func SetsockoptInt(fd, level, opt int, value int) (errno int) {
	return ENACL
}

func Shutdown(fd, how int) (errno int)	{ return ENACL }

func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, errno int) {
	return 0, nil, ENACL
}

func Sendto(fd int, p []byte, flags int, to Sockaddr) (errno int) {
	return ENACL
}

func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (errno int) {
	return ENACL
}

type Linger struct {
	Onoff	int32;
	Linger	int32;
}

func SetsockoptLinger(fd, level, opt int, l *Linger) (errno int) {
	return ENACL
}

func Listen(s int, n int) (errno int)	{ return ENACL }

type Rusage struct {
	Utime		Timeval;
	Stime		Timeval;
	Maxrss		int32;
	Ixrss		int32;
	Idrss		int32;
	Isrss		int32;
	Minflt		int32;
	Majflt		int32;
	Nswap		int32;
	Inblock		int32;
	Oublock		int32;
	Msgsnd		int32;
	Msgrcv		int32;
	Nsignals	int32;
	Nvcsw		int32;
	Nivcsw		int32;
}

func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, errno int) {
	return 0, ENACL
}

type WaitStatus uint32

func (WaitStatus) Exited() bool	{ return false }

func (WaitStatus) ExitStatus() int	{ return -1 }

func (WaitStatus) Signal() int	{ return -1 }

func (WaitStatus) CoreDump() bool	{ return false }

func (WaitStatus) Stopped() bool	{ return false }

func (WaitStatus) Continued() bool	{ return false }

func (WaitStatus) StopSignal() int	{ return -1 }

func (WaitStatus) Signaled() bool	{ return false }

func (WaitStatus) TrapCause() int	{ return -1 }
<|endoftext|>


<commit_before>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package module

import (
	"log"
	"net/http"
	"sort"

	"github.com/issue9/middleware"
	"github.com/issue9/mux/v2"

	"github.com/issue9/web/internal/webconfig"
)

const (
	// CoreModuleName 框架自带的模块名称
	//
	// 该模块会在所有模块初始化之后，进行最后的初始化操作，包括了以下内容：
	// - 配置文件中指定的静态文件服务内容 static；
	// - 所有模块注册的服务，也有此模块负责启动。
	CoreModuleName        = "web-core"
	coreModuleDescription = "web 框架自带的模块，包括启动服务等，最后加载运行"
)

// Modules 模块管理
//
// 负责模块的初始化工作，包括路由的加载等。
type Modules struct {
	middleware.Manager
	modules  []*Module
	router   *mux.Prefix
	services []*Service

	// 框架自身用到的模块，除了配置文件中的静态文件服务之外，
	// 还有所有服务的注册启动等，其初始化包含了启动服务等。
	coreModule *Module
}

// NewModules 声明 Modules 变量
func NewModules(conf *webconfig.WebConfig) (*Modules, error) {
	mux := mux.New(conf.DisableOptions, conf.DisableHead, false, nil, nil)
	ms := &Modules{
		Manager:  *middleware.NewManager(mux),
		modules:  make([]*Module, 0, 100),
		router:   mux.Prefix(conf.Root),
		services: make([]*Service, 0, 100),
	}

	ms.buildCoreModule(conf)

	if conf.Plugins != "" {
		if err := ms.loadPlugins(conf.Plugins); err != nil {
			return nil, err
		}
	}

	return ms, nil
}

// 收录模块到当前实例，并更 coreModule 的依赖项。
func (ms *Modules) appendModules(modules ...*Module) {
	for _, m := range modules {
		ms.modules = append(ms.modules, m)

		// 让 coreModule 依赖所有模块，保证其在最后进行初始化
		ms.coreModule.Deps = append(ms.coreModule.Deps, m.Name)
	}
}

func (ms *Modules) buildCoreModule(conf *webconfig.WebConfig) {
	ms.coreModule = newModule(ms, CoreModuleName, coreModuleDescription)
	ms.modules = append(ms.modules, ms.coreModule)

	// 初始化静态文件处理
	for url, dir := range conf.Static {
		h := http.StripPrefix(url, http.FileServer(http.Dir(dir)))
		ms.coreModule.Get(url+"{path}", h)
	}
}

// Mux 返回相关的 mux.Mux 实例
func (ms *Modules) Mux() *mux.Mux {
	return ms.router.Mux()
}

// Init 初始化插件
//
// 若指定了 tag 参数，则只初始化该名称的子模块内容。
//
// 指定 log 参数，可以输出详细的初始化步骤。
func (ms *Modules) Init(tag string, log *log.Logger) error {
	if err := newDepencency(ms, log).init(tag); err != nil {
		ms.reset()
		return err
	}

	return nil
}

// 重置状态为未初始化时的状态
func (ms *Modules) reset() {
	ms.router.Clean()

	ms.Stop() // 先停止停止服务
	ms.services = ms.services[:0]

	for _, m := range ms.modules {
		m.inited = false
	}
}

// Modules 获取所有的模块信息
func (ms *Modules) Modules() []*Module {
	return ms.modules
}

// Services 返回所有的服务列表
func (ms *Modules) Services() []*Service {
	return ms.services
}

// Stop 停止服务
func (ms *Modules) Stop() {
	for _, srv := range ms.services {
		srv.Stop()
	}
}

// Tags 返回所有的子模块名称
func (ms *Modules) Tags() []string {
	tags := make([]string, 0, len(ms.modules)*2)

	for _, m := range ms.modules {
	LOOP:
		for k := range m.tags {
			for _, v := range tags {
				if v == k {
					continue LOOP
				}
			}
			tags = append(tags, k)
		}
	}

	sort.Strings(tags)

	return tags
}
<commit_msg>更新文字错误<commit_after>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package module

import (
	"log"
	"net/http"
	"sort"

	"github.com/issue9/middleware"
	"github.com/issue9/mux/v2"

	"github.com/issue9/web/internal/webconfig"
)

const (
	// CoreModuleName 框架自带的模块名称
	//
	// 该模块会在所有模块初始化之后，进行最后的初始化操作，包括了以下内容：
	// - 配置文件中指定的静态文件服务内容 static；
	// - 所有模块注册的服务，也由此模块负责启动。
	CoreModuleName        = "web-core"
	coreModuleDescription = "web 框架自带的模块，包括启动服务等，最后加载运行"
)

// Modules 模块管理
//
// 负责模块的初始化工作，包括路由的加载等。
type Modules struct {
	middleware.Manager
	modules  []*Module
	router   *mux.Prefix
	services []*Service

	// 框架自身用到的模块，除了配置文件中的静态文件服务之外，
	// 还有所有服务的注册启动等，其初始化包含了启动服务等。
	coreModule *Module
}

// NewModules 声明 Modules 变量
func NewModules(conf *webconfig.WebConfig) (*Modules, error) {
	mux := mux.New(conf.DisableOptions, conf.DisableHead, false, nil, nil)
	ms := &Modules{
		Manager:  *middleware.NewManager(mux),
		modules:  make([]*Module, 0, 100),
		router:   mux.Prefix(conf.Root),
		services: make([]*Service, 0, 100),
	}

	ms.buildCoreModule(conf)

	if conf.Plugins != "" {
		if err := ms.loadPlugins(conf.Plugins); err != nil {
			return nil, err
		}
	}

	return ms, nil
}

// 收录模块到当前实例，并更 coreModule 的依赖项。
func (ms *Modules) appendModules(modules ...*Module) {
	for _, m := range modules {
		ms.modules = append(ms.modules, m)

		// 让 coreModule 依赖所有模块，保证其在最后进行初始化
		ms.coreModule.Deps = append(ms.coreModule.Deps, m.Name)
	}
}

func (ms *Modules) buildCoreModule(conf *webconfig.WebConfig) {
	ms.coreModule = newModule(ms, CoreModuleName, coreModuleDescription)
	ms.modules = append(ms.modules, ms.coreModule)

	// 初始化静态文件处理
	for url, dir := range conf.Static {
		h := http.StripPrefix(url, http.FileServer(http.Dir(dir)))
		ms.coreModule.Get(url+"{path}", h)
	}
}

// Mux 返回相关的 mux.Mux 实例
func (ms *Modules) Mux() *mux.Mux {
	return ms.router.Mux()
}

// Init 初始化插件
//
// 若指定了 tag 参数，则只初始化该名称的子模块内容。
//
// 指定 log 参数，可以输出详细的初始化步骤。
func (ms *Modules) Init(tag string, log *log.Logger) error {
	if err := newDepencency(ms, log).init(tag); err != nil {
		ms.reset()
		return err
	}

	return nil
}

// 重置状态为未初始化时的状态
func (ms *Modules) reset() {
	ms.router.Clean()

	ms.Stop() // 先停止停止服务
	ms.services = ms.services[:0]

	for _, m := range ms.modules {
		m.inited = false
	}
}

// Modules 获取所有的模块信息
func (ms *Modules) Modules() []*Module {
	return ms.modules
}

// Services 返回所有的服务列表
func (ms *Modules) Services() []*Service {
	return ms.services
}

// Stop 停止服务
func (ms *Modules) Stop() {
	for _, srv := range ms.services {
		srv.Stop()
	}
}

// Tags 返回所有的子模块名称
func (ms *Modules) Tags() []string {
	tags := make([]string, 0, len(ms.modules)*2)

	for _, m := range ms.modules {
	LOOP:
		for k := range m.tags {
			for _, v := range tags {
				if v == k {
					continue LOOP
				}
			}
			tags = append(tags, k)
		}
	}

	sort.Strings(tags)

	return tags
}
<|endoftext|>


<commit_before>package stun

import (
	"encoding/base64"
	"testing"
)

func BenchmarkErrorCode_AddTo(b *testing.B) {
	m := New()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		CodeStaleNonce.AddTo(m)
		m.Reset()
	}
}

func BenchmarkErrorCodeAttribute_AddTo(b *testing.B) {
	m := New()
	b.ReportAllocs()
	a := &ErrorCodeAttribute{
		Code:   404,
		Reason: []byte("not found!"),
	}
	for i := 0; i < b.N; i++ {
		a.AddTo(m)
		m.Reset()
	}
}

func BenchmarkErrorCodeAttribute_GetFrom(b *testing.B) {
	m := New()
	b.ReportAllocs()
	a := &ErrorCodeAttribute{
		Code:   404,
		Reason: []byte("not found!"),
	}
	a.AddTo(m)
	for i := 0; i < b.N; i++ {
		a.GetFrom(m)
	}
}

func TestMessage_AddErrorCode(t *testing.T) {
	m := New()
	transactionID, err := base64.StdEncoding.DecodeString("jxhBARZwX+rsC6er")
	if err != nil {
		t.Error(err)
	}
	copy(m.TransactionID[:], transactionID)
	expectedCode := ErrorCode(428)
	expectedReason := "Stale Nonce"
	CodeStaleNonce.AddTo(m)
	m.WriteHeader()

	mRes := New()
	if _, err = mRes.ReadFrom(m.reader()); err != nil {
		t.Fatal(err)
	}
	errCodeAttr := new(ErrorCodeAttribute)
	if err = errCodeAttr.GetFrom(mRes); err != nil {
		t.Error(err)
	}
	code := errCodeAttr.Code
	if err != nil {
		t.Error(err)
	}
	if code != expectedCode {
		t.Error("bad code", code)
	}
	if string(errCodeAttr.Reason) != expectedReason {
		t.Error("bad reason", string(errCodeAttr.Reason))
	}
}

func TestErrorCode(t *testing.T) {
	a := &ErrorCodeAttribute{
		Code:   404,
		Reason: []byte("not found!"),
	}
	if a.String() != "404: not found!" {
		t.Error("bad string", a)
	}
	m := New()
	cod := ErrorCode(666)
	if err := cod.AddTo(m); err != ErrNoDefaultReason {
		t.Error("should be ErrNoDefaultReason", err)
	}
	if err := a.GetFrom(m); err == nil {
		t.Error("attr should not be in message")
	}
	a.Reason = make([]byte, 2048)
	if err := a.AddTo(m); err == nil {
		t.Error("should error")
	}
}
<commit_msg>a:errorcode: add test for ErrUnexpectedEOF<commit_after>package stun

import (
	"encoding/base64"
	"io"
	"testing"
)

func BenchmarkErrorCode_AddTo(b *testing.B) {
	m := New()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		CodeStaleNonce.AddTo(m)
		m.Reset()
	}
}

func BenchmarkErrorCodeAttribute_AddTo(b *testing.B) {
	m := New()
	b.ReportAllocs()
	a := &ErrorCodeAttribute{
		Code:   404,
		Reason: []byte("not found!"),
	}
	for i := 0; i < b.N; i++ {
		a.AddTo(m)
		m.Reset()
	}
}

func BenchmarkErrorCodeAttribute_GetFrom(b *testing.B) {
	m := New()
	b.ReportAllocs()
	a := &ErrorCodeAttribute{
		Code:   404,
		Reason: []byte("not found!"),
	}
	a.AddTo(m)
	for i := 0; i < b.N; i++ {
		a.GetFrom(m)
	}
}

func TestErrorCodeAttribute_GetFrom(t *testing.T) {
	m := New()
	m.Add(AttrErrorCode, []byte{1})
	c := new(ErrorCodeAttribute)
	if err := c.GetFrom(m); err != io.ErrUnexpectedEOF {
		t.Errorf("GetFrom should return <%s>, but got <%s>",
			io.ErrUnexpectedEOF, err,
		)
	}
}

func TestMessage_AddErrorCode(t *testing.T) {
	m := New()
	transactionID, err := base64.StdEncoding.DecodeString("jxhBARZwX+rsC6er")
	if err != nil {
		t.Error(err)
	}
	copy(m.TransactionID[:], transactionID)
	expectedCode := ErrorCode(428)
	expectedReason := "Stale Nonce"
	CodeStaleNonce.AddTo(m)
	m.WriteHeader()

	mRes := New()
	if _, err = mRes.ReadFrom(m.reader()); err != nil {
		t.Fatal(err)
	}
	errCodeAttr := new(ErrorCodeAttribute)
	if err = errCodeAttr.GetFrom(mRes); err != nil {
		t.Error(err)
	}
	code := errCodeAttr.Code
	if err != nil {
		t.Error(err)
	}
	if code != expectedCode {
		t.Error("bad code", code)
	}
	if string(errCodeAttr.Reason) != expectedReason {
		t.Error("bad reason", string(errCodeAttr.Reason))
	}
}

func TestErrorCode(t *testing.T) {
	a := &ErrorCodeAttribute{
		Code:   404,
		Reason: []byte("not found!"),
	}
	if a.String() != "404: not found!" {
		t.Error("bad string", a)
	}
	m := New()
	cod := ErrorCode(666)
	if err := cod.AddTo(m); err != ErrNoDefaultReason {
		t.Error("should be ErrNoDefaultReason", err)
	}
	if err := a.GetFrom(m); err == nil {
		t.Error("attr should not be in message")
	}
	a.Reason = make([]byte, 2048)
	if err := a.AddTo(m); err == nil {
		t.Error("should error")
	}
}
<|endoftext|>


<commit_before>package triggers

import (
	"regexp"
)

type MergeableTriggers struct {
	triggers map[string]*mergeableTrigger // Key: service name.
}

type mergeableTrigger struct {
	matchLines map[string]struct{}
	doReboot   bool
	highImpact bool
}

type Trigger struct {
	MatchLines   []string
	matchRegexes []*regexp.Regexp
	Service      string
	DoReboot     bool
	HighImpact   bool
}

func (trigger *Trigger) ReplaceStrings(replaceFunc func(string) string) {
	trigger.replaceStrings(replaceFunc)
}

type Triggers struct {
	Triggers          []*Trigger
	compiled          bool
	matchedTriggers   map[*Trigger]bool
	unmatchedTriggers map[*Trigger]bool
}

func Decode(jsonData []byte) (*Triggers, error) {
	return decode(jsonData)
}

func Load(filename string) (*Triggers, error) {
	return load(filename)
}

func New() *Triggers {
	return newTriggers()
}

func (triggers *Triggers) ReplaceStrings(replaceFunc func(string) string) {
	triggers.replaceStrings(replaceFunc)
}

func (mt *MergeableTriggers) ExportTriggers() *Triggers {
	return mt.exportTriggers()
}

func (mt *MergeableTriggers) Merge(triggers *Triggers) {
	mt.merge(triggers)
}

func (triggers *Triggers) Match(line string) {
	triggers.match(line)
}

func (triggers *Triggers) GetMatchedTriggers() []*Trigger {
	return triggers.getMatchedTriggers()
}
<commit_msg>Trim JSON output for lib/triggers.Trigger type.<commit_after>package triggers

import (
	"regexp"
)

type MergeableTriggers struct {
	triggers map[string]*mergeableTrigger // Key: service name.
}

type mergeableTrigger struct {
	matchLines map[string]struct{}
	doReboot   bool
	highImpact bool
}

type Trigger struct {
	MatchLines   []string
	matchRegexes []*regexp.Regexp
	Service      string
	DoReboot     bool `json:",omitempty"`
	HighImpact   bool `json:",omitempty"`
}

func (trigger *Trigger) ReplaceStrings(replaceFunc func(string) string) {
	trigger.replaceStrings(replaceFunc)
}

type Triggers struct {
	Triggers          []*Trigger
	compiled          bool
	matchedTriggers   map[*Trigger]bool
	unmatchedTriggers map[*Trigger]bool
}

func Decode(jsonData []byte) (*Triggers, error) {
	return decode(jsonData)
}

func Load(filename string) (*Triggers, error) {
	return load(filename)
}

func New() *Triggers {
	return newTriggers()
}

func (triggers *Triggers) ReplaceStrings(replaceFunc func(string) string) {
	triggers.replaceStrings(replaceFunc)
}

func (mt *MergeableTriggers) ExportTriggers() *Triggers {
	return mt.exportTriggers()
}

func (mt *MergeableTriggers) Merge(triggers *Triggers) {
	mt.merge(triggers)
}

func (triggers *Triggers) Match(line string) {
	triggers.match(line)
}

func (triggers *Triggers) GetMatchedTriggers() []*Trigger {
	return triggers.getMatchedTriggers()
}
<|endoftext|>


<commit_before>package stns

import (
	"reflect"
	"regexp"

	"github.com/STNS/STNS/settings"
	"github.com/ant0ine/go-json-rest/rest"
)

type Handler struct {
	config *Config
}

type MetaData struct {
	ApiVersion float64 `json:"api_version"`
	Salt       bool    `json:"salt_enable"`
	Stretching int     `json:"stretching_number"`
	HashType   string  `json:"hash_type"`
	Result     string  `json:"result""`
	MinId      int     `json:"min_id""`
}

type ResponseFormat struct {
	MetaData *MetaData   `json:"metadata"`
	Items    *Attributes `json:"items"`
}

func (h *Handler) getQuery(r *rest.Request) *Query {
	value := r.PathParam("value")
	column := r.PathParam("column")
	resource_name := r.PathParam("resource_name")
	return &Query{h.config, resource_name, column, value}
}

func (h *Handler) getListQuery(r *rest.Request) *Query {
	resource_name := r.PathParam("resource_name")
	return &Query{h.config, resource_name, "list", ""}
}

func (h *Handler) Get(w rest.ResponseWriter, r *rest.Request) {
	query := h.getQuery(r)
	h.Response(query, w, r)
}

func (h *Handler) GetList(w rest.ResponseWriter, r *rest.Request) {
	query := h.getListQuery(r)
	h.Response(query, w, r)
}

func (h *Handler) Response(q *Query, w rest.ResponseWriter, r *rest.Request) {
	attr := q.Get()
	v2 := regexp.MustCompile(`^/v2`)
	if v2.MatchString(r.URL.Path) {
		response := ResponseFormat{
			MetaData: &MetaData{
				ApiVersion: settings.API_VERSION,
				Salt:       h.config.Salt,
				Stretching: h.config.Stretching,
				Result:     settings.SUCCESS,
				HashType:   h.config.HashType,
				MinId:      q.GetMinId(),
			},
			Items: &attr,
		}
		w.WriteJson(response)
		if attr == nil || reflect.ValueOf(attr).IsNil() {
			w.WriteHeader(404)
			return
		}
		return
	} else {
		if attr == nil || reflect.ValueOf(attr).IsNil() {
			rest.NotFound(w, r)
			return
		}
		w.WriteJson(attr)
		return
	}
}
<commit_msg>to const<commit_after>package stns

import (
	"net/http"
	"reflect"
	"regexp"

	"github.com/STNS/STNS/settings"
	"github.com/ant0ine/go-json-rest/rest"
)

type Handler struct {
	config *Config
}

type MetaData struct {
	ApiVersion float64 `json:"api_version"`
	Salt       bool    `json:"salt_enable"`
	Stretching int     `json:"stretching_number"`
	HashType   string  `json:"hash_type"`
	Result     string  `json:"result""`
	MinId      int     `json:"min_id""`
}

type ResponseFormat struct {
	MetaData *MetaData   `json:"metadata"`
	Items    *Attributes `json:"items"`
}

func (h *Handler) getQuery(r *rest.Request) *Query {
	value := r.PathParam("value")
	column := r.PathParam("column")
	resource_name := r.PathParam("resource_name")
	return &Query{h.config, resource_name, column, value}
}

func (h *Handler) getListQuery(r *rest.Request) *Query {
	resource_name := r.PathParam("resource_name")
	return &Query{h.config, resource_name, "list", ""}
}

func (h *Handler) Get(w rest.ResponseWriter, r *rest.Request) {
	query := h.getQuery(r)
	h.Response(query, w, r)
}

func (h *Handler) GetList(w rest.ResponseWriter, r *rest.Request) {
	query := h.getListQuery(r)
	h.Response(query, w, r)
}

func (h *Handler) Response(q *Query, w rest.ResponseWriter, r *rest.Request) {
	attr := q.Get()
	v2 := regexp.MustCompile(`^/v2`)
	if v2.MatchString(r.URL.Path) {
		response := ResponseFormat{
			MetaData: &MetaData{
				ApiVersion: settings.API_VERSION,
				Salt:       h.config.Salt,
				Stretching: h.config.Stretching,
				Result:     settings.SUCCESS,
				HashType:   h.config.HashType,
				MinId:      q.GetMinId(),
			},
			Items: &attr,
		}
		w.WriteJson(response)
		if attr == nil || reflect.ValueOf(attr).IsNil() {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		return
	} else {
		if attr == nil || reflect.ValueOf(attr).IsNil() {
			rest.NotFound(w, r)
			return
		}
		w.WriteJson(attr)
		return
	}
}
<|endoftext|>


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

package storage

import (
	"errors"
	"fmt"

	"golang.org/x/net/context"
	raw "google.golang.org/api/storage/v1"
)

// CopierFrom creates a Copier that can copy src to dst.
// You can immediately call Run on the returned Copier, or
// you can configure it first.
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
	return &Copier{dst: dst, src: src}
}

// A Copier copies a source object to a destination.
type Copier struct {
	// ObjectAttrs are optional attributes to set on the destination object.
	// Any attributes must be initialized before any calls on the Copier. Nil
	// or zero-valued attributes are ignored.
	ObjectAttrs

	// RewriteToken can be set before calling Run to resume a copy
	// operation. After Run returns a non-nil error, RewriteToken will
	// have been updated to contain the value needed to resume the copy.
	RewriteToken string

	// ProgressFunc can be used to monitor the progress of a multi-RPC copy
	// operation. If ProgressFunc is not nil and CopyFrom requires multiple
	// calls to the underlying service (see
	// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
	// ProgressFunc will be invoked after each call with the number of bytes of
	// content copied so far and the total size in bytes of the source object.
	//
	// ProgressFunc is intended to make upload progress available to the
	// application. For example, the implementation of ProgressFunc may update
	// a progress bar in the application's UI, or log the result of
	// float64(copiedBytes)/float64(totalBytes).
	//
	// ProgressFunc should return quickly without blocking.
	ProgressFunc func(copiedBytes, totalBytes uint64)

	dst, src *ObjectHandle
}

// Run performs the copy.
func (c *Copier) Run(ctx context.Context) (*ObjectAttrs, error) {
	if err := c.src.validate(); err != nil {
		return nil, err
	}
	if err := c.dst.validate(); err != nil {
		return nil, err
	}
	// Convert destination attributes to raw form, omitting the bucket.
	// If the bucket is included but name or content-type aren't, the service
	// returns a 400 with "Required" as the only message. Omitting the bucket
	// does not cause any problems.
	rawObject := c.ObjectAttrs.toRawObject("")
	for {
		res, err := c.callRewrite(ctx, c.src, rawObject)
		if err != nil {
			return nil, err
		}
		if c.ProgressFunc != nil {
			c.ProgressFunc(res.TotalBytesRewritten, res.ObjectSize)
		}
		if res.Done { // Finished successfully.
			return newObject(res.Resource), nil
		}
	}
}

func (c *Copier) callRewrite(ctx context.Context, src *ObjectHandle, rawObj *raw.Object) (*raw.RewriteResponse, error) {
	call := c.dst.c.raw.Objects.Rewrite(src.bucket, src.object, c.dst.bucket, c.dst.object, rawObj)

	call.Context(ctx).Projection("full")
	if c.RewriteToken != "" {
		call.RewriteToken(c.RewriteToken)
	}
	if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
		return nil, err
	}
	if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
		return nil, err
	}
	if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
		return nil, err
	}
	if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
		return nil, err
	}
	var res *raw.RewriteResponse
	var err error
	setClientHeader(call.Header())
	err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
	if err != nil {
		return nil, err
	}
	c.RewriteToken = res.RewriteToken
	return res, nil
}

// ComposerFrom creates a Composer that can compose srcs into dst.
// You can immediately call Run on the returned Composer, or you can
// configure it first.
//
// The encryption key for the destination object will be used to decrypt all
// source objects and encrypt the destination object. It is an error
// to specify an encryption key for any of the source objects.
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
	return &Composer{dst: dst, srcs: srcs}
}

// A Composer composes source objects into a destination object.
type Composer struct {
	// ObjectAttrs are optional attributes to set on the destination object.
	// Any attributes must be initialized before any calls on the Composer. Nil
	// or zero-valued attributes are ignored.
	ObjectAttrs

	dst  *ObjectHandle
	srcs []*ObjectHandle
}

// Run performs the compose operation.
func (c *Composer) Run(ctx context.Context) (*ObjectAttrs, error) {
	if err := c.dst.validate(); err != nil {
		return nil, err
	}
	if len(c.srcs) == 0 {
		return nil, errors.New("storage: at least one source object must be specified")
	}

	req := &raw.ComposeRequest{}
	// Compose requires a non-empty Destination, so we always set it,
	// even if the caller-provided ObjectAttrs is the zero value.
	req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
	for _, src := range c.srcs {
		if err := src.validate(); err != nil {
			return nil, err
		}
		if src.bucket != c.dst.bucket {
			return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
		}
		if src.encryptionKey != nil {
			return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
		}
		srcObj := &raw.ComposeRequestSourceObjects{
			Name: src.object,
		}
		if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
			return nil, err
		}
		req.SourceObjects = append(req.SourceObjects, srcObj)
	}

	call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
	if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
		return nil, err
	}
	if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
		return nil, err
	}
	var obj *raw.Object
	var err error
	setClientHeader(call.Header())
	err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
	if err != nil {
		return nil, err
	}
	return newObject(obj), nil
}
<commit_msg>storage: fix changed type in underlying library<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package storage

import (
	"errors"
	"fmt"

	"golang.org/x/net/context"
	raw "google.golang.org/api/storage/v1"
)

// CopierFrom creates a Copier that can copy src to dst.
// You can immediately call Run on the returned Copier, or
// you can configure it first.
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
	return &Copier{dst: dst, src: src}
}

// A Copier copies a source object to a destination.
type Copier struct {
	// ObjectAttrs are optional attributes to set on the destination object.
	// Any attributes must be initialized before any calls on the Copier. Nil
	// or zero-valued attributes are ignored.
	ObjectAttrs

	// RewriteToken can be set before calling Run to resume a copy
	// operation. After Run returns a non-nil error, RewriteToken will
	// have been updated to contain the value needed to resume the copy.
	RewriteToken string

	// ProgressFunc can be used to monitor the progress of a multi-RPC copy
	// operation. If ProgressFunc is not nil and CopyFrom requires multiple
	// calls to the underlying service (see
	// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
	// ProgressFunc will be invoked after each call with the number of bytes of
	// content copied so far and the total size in bytes of the source object.
	//
	// ProgressFunc is intended to make upload progress available to the
	// application. For example, the implementation of ProgressFunc may update
	// a progress bar in the application's UI, or log the result of
	// float64(copiedBytes)/float64(totalBytes).
	//
	// ProgressFunc should return quickly without blocking.
	ProgressFunc func(copiedBytes, totalBytes uint64)

	dst, src *ObjectHandle
}

// Run performs the copy.
func (c *Copier) Run(ctx context.Context) (*ObjectAttrs, error) {
	if err := c.src.validate(); err != nil {
		return nil, err
	}
	if err := c.dst.validate(); err != nil {
		return nil, err
	}
	// Convert destination attributes to raw form, omitting the bucket.
	// If the bucket is included but name or content-type aren't, the service
	// returns a 400 with "Required" as the only message. Omitting the bucket
	// does not cause any problems.
	rawObject := c.ObjectAttrs.toRawObject("")
	for {
		res, err := c.callRewrite(ctx, c.src, rawObject)
		if err != nil {
			return nil, err
		}
		if c.ProgressFunc != nil {
			c.ProgressFunc(uint64(res.TotalBytesRewritten), uint64(res.ObjectSize))
		}
		if res.Done { // Finished successfully.
			return newObject(res.Resource), nil
		}
	}
}

func (c *Copier) callRewrite(ctx context.Context, src *ObjectHandle, rawObj *raw.Object) (*raw.RewriteResponse, error) {
	call := c.dst.c.raw.Objects.Rewrite(src.bucket, src.object, c.dst.bucket, c.dst.object, rawObj)

	call.Context(ctx).Projection("full")
	if c.RewriteToken != "" {
		call.RewriteToken(c.RewriteToken)
	}
	if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
		return nil, err
	}
	if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
		return nil, err
	}
	if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
		return nil, err
	}
	if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
		return nil, err
	}
	var res *raw.RewriteResponse
	var err error
	setClientHeader(call.Header())
	err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
	if err != nil {
		return nil, err
	}
	c.RewriteToken = res.RewriteToken
	return res, nil
}

// ComposerFrom creates a Composer that can compose srcs into dst.
// You can immediately call Run on the returned Composer, or you can
// configure it first.
//
// The encryption key for the destination object will be used to decrypt all
// source objects and encrypt the destination object. It is an error
// to specify an encryption key for any of the source objects.
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
	return &Composer{dst: dst, srcs: srcs}
}

// A Composer composes source objects into a destination object.
type Composer struct {
	// ObjectAttrs are optional attributes to set on the destination object.
	// Any attributes must be initialized before any calls on the Composer. Nil
	// or zero-valued attributes are ignored.
	ObjectAttrs

	dst  *ObjectHandle
	srcs []*ObjectHandle
}

// Run performs the compose operation.
func (c *Composer) Run(ctx context.Context) (*ObjectAttrs, error) {
	if err := c.dst.validate(); err != nil {
		return nil, err
	}
	if len(c.srcs) == 0 {
		return nil, errors.New("storage: at least one source object must be specified")
	}

	req := &raw.ComposeRequest{}
	// Compose requires a non-empty Destination, so we always set it,
	// even if the caller-provided ObjectAttrs is the zero value.
	req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
	for _, src := range c.srcs {
		if err := src.validate(); err != nil {
			return nil, err
		}
		if src.bucket != c.dst.bucket {
			return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
		}
		if src.encryptionKey != nil {
			return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
		}
		srcObj := &raw.ComposeRequestSourceObjects{
			Name: src.object,
		}
		if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
			return nil, err
		}
		req.SourceObjects = append(req.SourceObjects, srcObj)
	}

	call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
	if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
		return nil, err
	}
	if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
		return nil, err
	}
	var obj *raw.Object
	var err error
	setClientHeader(call.Header())
	err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
	if err != nil {
		return nil, err
	}
	return newObject(obj), nil
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/stefanwichmann/go.hue"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

// HueBridge represents the Philips Hue bridge in
// your system.
// It is used to communicate with all devices.
type HueBridge struct {
	bridge   hue.Bridge
	BridgeIP string
	Username string
	Version  int
}

const hueBridgeAppName = "kelvin"

// InitializeBridge creates and returns an initialized HueBridge.
// If you have a valid configuration this will be used. Otherwise a local
// discovery will be started, followed by a user registration on your bridge.
func (bridge *HueBridge) InitializeBridge(configuration *Configuration) error {
	err := bridge.discover(configuration.Bridge.IP)
	if err != nil {
		return err
	}
	configuration.Bridge.IP = bridge.BridgeIP

	if configuration.Bridge.Username != "" {
		log.Debugf("Found bridge username in configuration: %s", configuration.Bridge.Username)
		bridge.Username = configuration.Bridge.Username
	} else {
		log.Debugf("No username found in bridge configuration. Starting registration...")
		err := bridge.register()
		if err != nil {
			return err
		}
		log.Debugf("Saving new username in bridge configuration: %s", bridge.Username)
		configuration.Bridge.Username = bridge.Username
	}

	log.Debugf("Connecting to bridge %s with username %s", bridge.BridgeIP, bridge.Username)
	err = bridge.connect()
	if err != nil {
		return err
	}
	log.Println("⌘ Connection to bridge established")
	go bridge.validateSofwareVersion()
	err = bridge.printDevices()
	if err != nil {
		return err
	}

	err = bridge.populateSchedule(configuration)
	return err
}

// Lights return all known lights on your bridge.
func (bridge *HueBridge) Lights() ([]*Light, error) {
	var lights []*Light
	hueLights, err := bridge.bridge.GetAllLights()
	if err != nil {
		return lights, err
	}

	for _, hueLight := range hueLights {
		var light Light
		light.ID, err = strconv.Atoi(hueLight.Id)
		if err != nil {
			return lights, err
		}

		light.HueLight = *hueLight
		light.initialize()

		lights = append(lights, &light)
	}

	sort.Slice(lights, func(i, j int) bool { return lights[i].ID < lights[j].ID })
	return lights, nil
}

func (bridge *HueBridge) printDevices() error {
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}

	log.Printf("💡 Devices found on current bridge:")
	log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %-9v | %-8v |", "Name", "ID", "Reachable", "On", "Dimmable", "Temperature", "Color", "Cur. Temp", "Cur. Bri")
	for _, light := range lights {
		var temp string
		if !light.SupportsColorTemperature && !light.SupportsXYColor {
			temp = "-"
		} else {
			temp = strings.Join([]string{strconv.Itoa(light.CurrentLightState.ColorTemperature), "K"}, "")
		}
		log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %9v | %8v |", light.Name, light.ID, light.Reachable, light.On, light.Dimmable, light.SupportsColorTemperature, light.SupportsXYColor, temp, light.CurrentLightState.Brightness)
	}
	return nil
}

func (bridge *HueBridge) discover(ip string) error {
	if ip != "" {
		// we have a known IP adress. Validate if it points to a reachable bridge
		bridge.BridgeIP = ip
		err := bridge.validateBridge()
		if err == nil {
			return nil
		}
	}
	log.Debugf("Starting bridge discovery")
	bridges, err := hue.DiscoverBridges(false)
	if err != nil {
		bridge.BridgeIP = ""
		return err
	}
	if len(bridges) == 0 {
		bridge.BridgeIP = ""
		return errors.New("Bridge discovery failed. Please configure manually in config.json.")
	}
	for _, candidate := range bridges {
		bridge.BridgeIP = candidate.IpAddr
		err := bridge.validateBridge()
		if err == nil {
			log.Printf("⌘ Found bridge at %s", bridge.BridgeIP)
			return nil
		}
	}
	bridge.BridgeIP = ""
	return errors.New("Bridge discovery failed. Please configure manually in config.json.")
}

func (bridge *HueBridge) register() error {
	if bridge.BridgeIP == "" {
		return errors.New("Registration at bridge not possible because no IP is configured. Start discovery first or enter manually.")
	}

	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, "")
	log.Printf("⌘ Starting user registration.")
	log.Warningf("PLEASE PUSH THE BLUE BUTTON ON YOUR HUE BRIDGE")
	for {
		time.Sleep(5 * time.Second)

		// try user creation, will fail if the button wasn't pressed.
		err := bridge.bridge.CreateUser(hueBridgeAppName)
		if err != nil {
			return err
		}

		if bridge.bridge.Username != "" {
			// registration successful
			bridge.Username = bridge.bridge.Username
			log.Printf("⌘ User registration successful.")
			return nil
		}
	}
}

func (bridge *HueBridge) connect() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge IP configured")
	}

	if bridge.Username == "" {
		return errors.New("No username on bridge configured")
	}
	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, bridge.Username)

	// Test bridge
	_, err := bridge.bridge.Search()
	return err
}

func (bridge *HueBridge) populateSchedule(configuration *Configuration) error {
	if len(configuration.Schedules) == 0 {
		return errors.New("Configuration does not contain any schedules to populate")
	}

	// Do we have associated lights?
	for _, schedule := range configuration.Schedules {
		if len(schedule.AssociatedDeviceIDs) > 0 {
			log.Debugf("Configuration contains at least one schedule with associated lights.")
			return nil // At least one schedule is configured
		}
	}

	// No schedule has associated lights
	log.Debugf("Configuration contains no schedule with associated lights. Initializing first schedule with all lights.")
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}
	var lightIDs []int
	for _, light := range lights {
		lightIDs = append(lightIDs, light.ID)
	}
	configuration.Schedules[0].AssociatedDeviceIDs = lightIDs
	return nil
}

func (bridge *HueBridge) validateSofwareVersion() {
	configuration, err := bridge.bridge.Configuration()
	if err != nil {
		log.Warningf("Could not validate bridge software version: %v", err)
		return
	}

	swversion, err := strconv.Atoi(configuration.SoftwareVersion)
	if err != nil {
		log.Warningf("Could not validate bridge software version: %v", err)
		return
	}
	log.Debugf("Bridge is running software version %s", configuration.SoftwareVersion)

	if (bridge.Version == 1 && swversion < 1038802) || (bridge.Version == 2 && swversion < 1705121051) {
		log.Warningf("Your hue bridge is running an old software version. Please update using the hue app to ensure Kelvin will run smoothly.")
	} else {
		log.Debugf("Bridge software is up to date")
	}
}

func (bridge *HueBridge) validateBridge() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge configured. Could not validate")
	}
	resp, err := http.Get("http://" + bridge.BridgeIP + "/description.xml")
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	if strings.Contains(string(data), "<modelNumber>929000226503</modelNumber>") {
		bridge.Version = 1
		return nil
	}
	if strings.Contains(string(data), "<modelNumber>BSB002</modelNumber>") {
		bridge.Version = 2
		return nil
	}
	return fmt.Errorf("Bridge validation failed.")
}
<commit_msg>Fix typo<commit_after>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/stefanwichmann/go.hue"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

// HueBridge represents the Philips Hue bridge in
// your system.
// It is used to communicate with all devices.
type HueBridge struct {
	bridge   hue.Bridge
	BridgeIP string
	Username string
	Version  int
}

const hueBridgeAppName = "kelvin"

// InitializeBridge creates and returns an initialized HueBridge.
// If you have a valid configuration this will be used. Otherwise a local
// discovery will be started, followed by a user registration on your bridge.
func (bridge *HueBridge) InitializeBridge(configuration *Configuration) error {
	err := bridge.discover(configuration.Bridge.IP)
	if err != nil {
		return err
	}
	configuration.Bridge.IP = bridge.BridgeIP

	if configuration.Bridge.Username != "" {
		log.Debugf("Found bridge username in configuration: %s", configuration.Bridge.Username)
		bridge.Username = configuration.Bridge.Username
	} else {
		log.Debugf("No username found in bridge configuration. Starting registration...")
		err := bridge.register()
		if err != nil {
			return err
		}
		log.Debugf("Saving new username in bridge configuration: %s", bridge.Username)
		configuration.Bridge.Username = bridge.Username
	}

	log.Debugf("Connecting to bridge %s with username %s", bridge.BridgeIP, bridge.Username)
	err = bridge.connect()
	if err != nil {
		return err
	}
	log.Println("⌘ Connection to bridge established")
	go bridge.validateSofwareVersion()
	err = bridge.printDevices()
	if err != nil {
		return err
	}

	err = bridge.populateSchedule(configuration)
	return err
}

// Lights return all known lights on your bridge.
func (bridge *HueBridge) Lights() ([]*Light, error) {
	var lights []*Light
	hueLights, err := bridge.bridge.GetAllLights()
	if err != nil {
		return lights, err
	}

	for _, hueLight := range hueLights {
		var light Light
		light.ID, err = strconv.Atoi(hueLight.Id)
		if err != nil {
			return lights, err
		}

		light.HueLight = *hueLight
		light.initialize()

		lights = append(lights, &light)
	}

	sort.Slice(lights, func(i, j int) bool { return lights[i].ID < lights[j].ID })
	return lights, nil
}

func (bridge *HueBridge) printDevices() error {
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}

	log.Printf("💡 Devices found on current bridge:")
	log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %-9v | %-8v |", "Name", "ID", "Reachable", "On", "Dimmable", "Temperature", "Color", "Cur. Temp", "Cur. Bri")
	for _, light := range lights {
		var temp string
		if !light.SupportsColorTemperature && !light.SupportsXYColor {
			temp = "-"
		} else {
			temp = strings.Join([]string{strconv.Itoa(light.CurrentLightState.ColorTemperature), "K"}, "")
		}
		log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %9v | %8v |", light.Name, light.ID, light.Reachable, light.On, light.Dimmable, light.SupportsColorTemperature, light.SupportsXYColor, temp, light.CurrentLightState.Brightness)
	}
	return nil
}

func (bridge *HueBridge) discover(ip string) error {
	if ip != "" {
		// we have a known IP address. Validate if it points to a reachable bridge
		bridge.BridgeIP = ip
		err := bridge.validateBridge()
		if err == nil {
			return nil
		}
	}
	log.Debugf("Starting bridge discovery")
	bridges, err := hue.DiscoverBridges(false)
	if err != nil {
		bridge.BridgeIP = ""
		return err
	}
	if len(bridges) == 0 {
		bridge.BridgeIP = ""
		return errors.New("Bridge discovery failed. Please configure manually in config.json.")
	}
	for _, candidate := range bridges {
		bridge.BridgeIP = candidate.IpAddr
		err := bridge.validateBridge()
		if err == nil {
			log.Printf("⌘ Found bridge at %s", bridge.BridgeIP)
			return nil
		}
	}
	bridge.BridgeIP = ""
	return errors.New("Bridge discovery failed. Please configure manually in config.json.")
}

func (bridge *HueBridge) register() error {
	if bridge.BridgeIP == "" {
		return errors.New("Registration at bridge not possible because no IP is configured. Start discovery first or enter manually.")
	}

	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, "")
	log.Printf("⌘ Starting user registration.")
	log.Warningf("PLEASE PUSH THE BLUE BUTTON ON YOUR HUE BRIDGE")
	for {
		time.Sleep(5 * time.Second)

		// try user creation, will fail if the button wasn't pressed.
		err := bridge.bridge.CreateUser(hueBridgeAppName)
		if err != nil {
			return err
		}

		if bridge.bridge.Username != "" {
			// registration successful
			bridge.Username = bridge.bridge.Username
			log.Printf("⌘ User registration successful.")
			return nil
		}
	}
}

func (bridge *HueBridge) connect() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge IP configured")
	}

	if bridge.Username == "" {
		return errors.New("No username on bridge configured")
	}
	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, bridge.Username)

	// Test bridge
	_, err := bridge.bridge.Search()
	return err
}

func (bridge *HueBridge) populateSchedule(configuration *Configuration) error {
	if len(configuration.Schedules) == 0 {
		return errors.New("Configuration does not contain any schedules to populate")
	}

	// Do we have associated lights?
	for _, schedule := range configuration.Schedules {
		if len(schedule.AssociatedDeviceIDs) > 0 {
			log.Debugf("Configuration contains at least one schedule with associated lights.")
			return nil // At least one schedule is configured
		}
	}

	// No schedule has associated lights
	log.Debugf("Configuration contains no schedule with associated lights. Initializing first schedule with all lights.")
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}
	var lightIDs []int
	for _, light := range lights {
		lightIDs = append(lightIDs, light.ID)
	}
	configuration.Schedules[0].AssociatedDeviceIDs = lightIDs
	return nil
}

func (bridge *HueBridge) validateSofwareVersion() {
	configuration, err := bridge.bridge.Configuration()
	if err != nil {
		log.Warningf("Could not validate bridge software version: %v", err)
		return
	}

	swversion, err := strconv.Atoi(configuration.SoftwareVersion)
	if err != nil {
		log.Warningf("Could not validate bridge software version: %v", err)
		return
	}
	log.Debugf("Bridge is running software version %s", configuration.SoftwareVersion)

	if (bridge.Version == 1 && swversion < 1038802) || (bridge.Version == 2 && swversion < 1705121051) {
		log.Warningf("Your hue bridge is running an old software version. Please update using the hue app to ensure Kelvin will run smoothly.")
	} else {
		log.Debugf("Bridge software is up to date")
	}
}

func (bridge *HueBridge) validateBridge() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge configured. Could not validate")
	}
	resp, err := http.Get("http://" + bridge.BridgeIP + "/description.xml")
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	if strings.Contains(string(data), "<modelNumber>929000226503</modelNumber>") {
		bridge.Version = 1
		return nil
	}
	if strings.Contains(string(data), "<modelNumber>BSB002</modelNumber>") {
		bridge.Version = 2
		return nil
	}
	return fmt.Errorf("Bridge validation failed.")
}
<|endoftext|>