<commit_before>package when

import (
	"reflect"
	"testing"

	"github.com/google/go-cmp/cmp"
	yaml "gopkg.in/yaml.v2"
)

var unmarshalTests = []struct {
	desc     string
	input    string
	expected When
}{
	{
		"short notation",
		`foo`,
		Create(WithEqual("foo", "true")),
	},
	{
		"list short notation",
		`[foo, bar, baz]`,
		Create(WithEqual("foo", "true"), WithEqual("bar", "true"), WithEqual("baz", "true")),
	},
	{
		"not-equal",
		`not-equal: {foo: bar}`,
		Create(WithNotEqual("foo", "bar")),
	},
	{
		"null environment",
		`environment: {foo: null}`,
		Create(WithoutEnv("foo")),
	},
	{
		"environment list with null",
		`environment: {foo: ["a", null, "b"]}`,
		Create(
			WithEnv("foo", "a"),
			WithoutEnv("foo"),
			WithEnv("foo", "b"),
		),
	},
}

func TestWhen_UnmarshalYAML(t *testing.T) {
	for _, tt := range unmarshalTests {
		w := When{}
		if err := yaml.Unmarshal([]byte(tt.input), &w); err != nil {
			t.Errorf(
				`Unmarshalling %s: unexpected error: %s`,
				tt.desc, err,
			)
			continue
		}

		if !cmp.Equal(w, tt.expected) {
			t.Errorf("mismatch:\n%s", cmp.Diff(tt.expected, w))
		}
	}
}

var whenDepTests = []struct {
	when     When
	expected []string
}{
	{When{}, []string{}},

	// Equal
	{
		Create(WithEqual("foo", "true")),
		[]string{"foo"},
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "true")),
		[]string{"foo", "bar"},
	},

	// NotEqual
	{
		Create(WithNotEqual("foo", "true")),
		[]string{"foo"},
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "true")),
		[]string{"foo", "bar"},
	},

	// Both
	{
		Create(WithEqual("foo", "true"), WithNotEqual("foo", "true")),
		[]string{"foo"},
	},
	{
		Create(WithEqual("foo", "true"), WithNotEqual("bar", "true")),
		[]string{"foo", "bar"},
	},
}

func TestWhen_Dependencies(t *testing.T) {
	for _, tt := range whenDepTests {
		actual := tt.when.Dependencies()
		if !equalUnordered(tt.expected, actual) {
			t.Errorf(
				"%+v.Dependencies(): expected %s, actual %s",
				tt.when, tt.expected, actual,
			)
		}
	}
}

func equalUnordered(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	// Since this list is unordered, convert to maps
	aMap := make(map[string]interface{})
	for _, val := range a {
		aMap[val] = struct{}{}
	}

	bMap := make(map[string]interface{})
	for _, val := range b {
		bMap[val] = struct{}{}
	}

	return reflect.DeepEqual(aMap, bMap)
}

var whenValidateTests = []struct {
	when      When
	options   map[string]string
	shouldErr bool
}{
	// Empty
	{When{}, nil, false},
	{When{}, map[string]string{"foo": "bar"}, false},

	// Command Clauses
	{Create(WithCommandSuccess), nil, false},
	{Create(WithCommandFailure), nil, true},
	{Create(WithCommandSuccess, WithCommandSuccess), nil, false},
	{Create(WithCommandSuccess, WithCommandFailure), nil, false},
	{Create(WithCommandFailure, WithCommandFailure), nil, true},

	// Exist Clauses
	{Create(WithExists("when_test.go")), nil, false},
	{Create(WithExists("fakefile")), nil, true},
	{Create(WithExists("fakefile"), WithExists("when_test.go")), nil, false},
	{Create(WithExists("when_test.go"), WithExists("fakefile")), nil, false},
	{Create(WithExists("fakefile"), WithExists("fakefile2")), nil, true},

	// Not Exist Clauses
	{Create(WithNotExists("when_test.go")), nil, true},
	{Create(WithNotExists("fakefile")), nil, false},
	{Create(WithNotExists("fakefile"), WithNotExists("when_test.go")), nil, true},
	{Create(WithNotExists("when_test.go"), WithNotExists("fakefile")), nil, true},
	{Create(WithNotExists("fakefile"), WithNotExists("fakefile2")), nil, false},

	// OS Clauses
	{Create(WithOSSuccess), nil, false},
	{Create(WithOSFailure), nil, true},
	{Create(WithOSSuccess, WithOSFailure), nil, false},
	{Create(WithOSFailure, WithOSSuccess), nil, false},
	{Create(WithOSFailure, WithOSFailure), nil, true},

	// Environment Clauses
	{Create(WithEnvSuccess), nil, false},
	{Create(WithoutEnvSuccess), nil, false},
	{Create(WithEnvFailure), nil, true},
	{Create(WithoutEnvFailure), nil, true},
	{Create(WithEnvSuccess, WithoutEnvFailure), nil, false},
	{Create(WithEnvFailure, WithoutEnvSuccess), nil, false},
	{Create(WithEnvFailure, WithoutEnvFailure), nil, true},

	// Equal Clauses
	{
		Create(WithEqual("foo", "true")),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"foo": "true", "bar": "false"},
		false,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"foo": "true", "bar": "true"},
		false,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(WithEqual("foo", "true")),
		map[string]string{"foo": "false"},
		true,
	},
	{
		Create(WithEqual("foo", "true")),
		map[string]string{},
		true,
	},
	{
		Create(WithEqual("foo", "true")),
		map[string]string{"bar": "true"},
		true,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"bar": "true"},
		true,
	},

	// NotEqual Clauses
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{"foo": "true"},
		true,
	},
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{"foo": "false"},
		false,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "false")),
		map[string]string{"foo": "true", "bar": "true"},
		false,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "false")),
		map[string]string{"foo": "false"},
		false,
	},
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{},
		true,
	},
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{"bar": "true"},
		true,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "true")),
		map[string]string{"bar": "true"},
		true,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "true")),
		map[string]string{"foo": "false", "bar": "false"},
		false,
	},

	// Combinations
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		true,
	},
	{
		Create(
			WithCommandSuccess,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("when_test.go"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSSuccess,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvSuccess,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "true"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "fake"),
		),
		map[string]string{"foo": "true"},
		false,
	},
}

func TestWhen_Validate(t *testing.T) {
	for _, tt := range whenValidateTests {
		err := tt.when.Validate(tt.options)
		didErr := err != nil
		if tt.shouldErr != didErr {
			t.Errorf(
				"%+v.Validate():\nexpected error: %t, got error: '%s'",
				tt.when, tt.shouldErr, err,
			)
		}
	}
}

var normalizetests = []struct {
	input    string
	expected string
}{
	{"nonsense", "nonsense"},
	{"darwin", "darwin"},
	{"Darwin", "darwin"},
	{"OSX", "darwin"},
	{"macOS", "darwin"},
	{"win", "windows"},
}

func TestNormalizeOS(t *testing.T) {
	for _, tt := range normalizetests {
		actual := normalizeOS(tt.input)
		if tt.expected != actual {
			t.Errorf(
				"normalizeOS(%s): expected: %s, actual: %s",
				tt.input, tt.expected, actual,
			)
		}
	}
}
<commit_msg>Fixed tests<commit_after>package when

import (
	"reflect"
	"testing"

	"github.com/google/go-cmp/cmp"
	yaml "gopkg.in/yaml.v2"
)

var unmarshalTests = []struct {
	desc     string
	input    string
	expected When
}{
	{
		"short notation",
		`foo`,
		Create(WithEqual("foo", "true")),
	},
	{
		"list short notation",
		`[foo, bar, baz]`,
		Create(WithEqual("foo", "true"), WithEqual("bar", "true"), WithEqual("baz", "true")),
	},
	{
		"not-equal",
		`not-equal: {foo: bar}`,
		Create(WithNotEqual("foo", "bar")),
	},
	{
		"null environment",
		`environment: {foo: null}`,
		Create(WithoutEnv("foo")),
	},
	{
		"environment list with null",
		`environment: {foo: ["a", null, "b"]}`,
		Create(
			WithEnv("foo", "a"),
			WithoutEnv("foo"),
			WithEnv("foo", "b"),
		),
	},
}

func TestWhen_UnmarshalYAML(t *testing.T) {
	for _, tt := range unmarshalTests {
		w := When{}
		if err := yaml.Unmarshal([]byte(tt.input), &w); err != nil {
			t.Errorf(
				`Unmarshalling %s: unexpected error: %s`,
				tt.desc, err,
			)
			continue
		}

		if !cmp.Equal(w, tt.expected) {
			t.Errorf("mismatch:\n%s", cmp.Diff(tt.expected, w))
		}
	}
}

var whenDepTests = []struct {
	when     When
	expected []string
}{
	{When{}, []string{}},

	// Equal
	{
		Create(WithEqual("foo", "true")),
		[]string{"foo"},
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "true")),
		[]string{"foo", "bar"},
	},

	// NotEqual
	{
		Create(WithNotEqual("foo", "true")),
		[]string{"foo"},
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "true")),
		[]string{"foo", "bar"},
	},

	// Both
	{
		Create(WithEqual("foo", "true"), WithNotEqual("foo", "true")),
		[]string{"foo"},
	},
	{
		Create(WithEqual("foo", "true"), WithNotEqual("bar", "true")),
		[]string{"foo", "bar"},
	},
}

func TestWhen_Dependencies(t *testing.T) {
	for _, tt := range whenDepTests {
		actual := tt.when.Dependencies()
		if !equalUnordered(tt.expected, actual) {
			t.Errorf(
				"%+v.Dependencies(): expected %s, actual %s",
				tt.when, tt.expected, actual,
			)
		}
	}
}

func equalUnordered(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	// Since this list is unordered, convert to maps
	aMap := make(map[string]interface{})
	for _, val := range a {
		aMap[val] = struct{}{}
	}

	bMap := make(map[string]interface{})
	for _, val := range b {
		bMap[val] = struct{}{}
	}

	return reflect.DeepEqual(aMap, bMap)
}

var whenValidateTests = []struct {
	when      When
	options   map[string]string
	shouldErr bool
}{
	// Empty
	{When{}, nil, false},
	{When{}, map[string]string{"foo": "bar"}, false},

	// Command Clauses
	{Create(WithCommandSuccess), nil, false},
	{Create(WithCommandFailure), nil, true},
	{Create(WithCommandSuccess, WithCommandSuccess), nil, false},
	{Create(WithCommandSuccess, WithCommandFailure), nil, false},
	{Create(WithCommandFailure, WithCommandFailure), nil, true},

	// Exist Clauses
	{Create(WithExists("when_test.go")), nil, false},
	{Create(WithExists("fakefile")), nil, true},
	{Create(WithExists("fakefile"), WithExists("when_test.go")), nil, false},
	{Create(WithExists("when_test.go"), WithExists("fakefile")), nil, false},
	{Create(WithExists("fakefile"), WithExists("fakefile2")), nil, true},

	// Not Exist Clauses
	{Create(WithNotExists("when_test.go")), nil, true},
	{Create(WithNotExists("fakefile")), nil, false},
	{Create(WithNotExists("fakefile"), WithNotExists("when_test.go")), nil, false},
	{Create(WithNotExists("when_test.go"), WithNotExists("fakefile")), nil, false},
	{Create(WithNotExists("fakefile"), WithNotExists("fakefile2")), nil, false},

	// OS Clauses
	{Create(WithOSSuccess), nil, false},
	{Create(WithOSFailure), nil, true},
	{Create(WithOSSuccess, WithOSFailure), nil, false},
	{Create(WithOSFailure, WithOSSuccess), nil, false},
	{Create(WithOSFailure, WithOSFailure), nil, true},

	// Environment Clauses
	{Create(WithEnvSuccess), nil, false},
	{Create(WithoutEnvSuccess), nil, false},
	{Create(WithEnvFailure), nil, true},
	{Create(WithoutEnvFailure), nil, true},
	{Create(WithEnvSuccess, WithoutEnvFailure), nil, false},
	{Create(WithEnvFailure, WithoutEnvSuccess), nil, false},
	{Create(WithEnvFailure, WithoutEnvFailure), nil, true},

	// Equal Clauses
	{
		Create(WithEqual("foo", "true")),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"foo": "true", "bar": "false"},
		false,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"foo": "true", "bar": "true"},
		false,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(WithEqual("foo", "true")),
		map[string]string{"foo": "false"},
		true,
	},
	{
		Create(WithEqual("foo", "true")),
		map[string]string{},
		true,
	},
	{
		Create(WithEqual("foo", "true")),
		map[string]string{"bar": "true"},
		true,
	},
	{
		Create(WithEqual("foo", "true"), WithEqual("bar", "false")),
		map[string]string{"bar": "true"},
		true,
	},

	// NotEqual Clauses
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{"foo": "true"},
		true,
	},
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{"foo": "false"},
		false,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "false")),
		map[string]string{"foo": "true", "bar": "true"},
		false,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "false")),
		map[string]string{"foo": "false"},
		false,
	},
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{},
		true,
	},
	{
		Create(WithNotEqual("foo", "true")),
		map[string]string{"bar": "true"},
		true,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "true")),
		map[string]string{"bar": "true"},
		true,
	},
	{
		Create(WithNotEqual("foo", "true"), WithNotEqual("bar", "true")),
		map[string]string{"foo": "false", "bar": "false"},
		false,
	},

	// Combinations
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		true,
	},
	{
		Create(
			WithCommandSuccess,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("when_test.go"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSSuccess,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvSuccess,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "true"),
			WithNotEqual("foo", "true"),
		),
		map[string]string{"foo": "true"},
		false,
	},
	{
		Create(
			WithCommandFailure,
			WithExists("fakefile"),
			WithOSFailure,
			WithEnvFailure,
			WithEqual("foo", "wrong"),
			WithNotEqual("foo", "fake"),
		),
		map[string]string{"foo": "true"},
		false,
	},
}

func TestWhen_Validate(t *testing.T) {
	for _, tt := range whenValidateTests {
		err := tt.when.Validate(tt.options)
		didErr := err != nil
		if tt.shouldErr != didErr {
			t.Errorf(
				"%+v.Validate():\nexpected error: %t, got error: '%s'",
				tt.when, tt.shouldErr, err,
			)
		}
	}
}

var normalizetests = []struct {
	input    string
	expected string
}{
	{"nonsense", "nonsense"},
	{"darwin", "darwin"},
	{"Darwin", "darwin"},
	{"OSX", "darwin"},
	{"macOS", "darwin"},
	{"win", "windows"},
}

func TestNormalizeOS(t *testing.T) {
	for _, tt := range normalizetests {
		actual := normalizeOS(tt.input)
		if tt.expected != actual {
			t.Errorf(
				"normalizeOS(%s): expected: %s, actual: %s",
				tt.input, tt.expected, actual,
			)
		}
	}
}
<|endoftext|>


<commit_before>package connection

import (
	"fmt"
	"reflect"
	"strconv"
	"time"
)

//following two structs represent cipherscan output

type CipherscanOutput struct {
	Target         string                  `json:"target"`
	IP             string                  `json:"ip"`
	Timestamp      string                  `json:"utctimestamp"`
	ServerSide     string                  `json:"serverside"`
	CurvesFallback string                  `json:"curves_fallback"`
	CipherSuites   []CipherscanCiphersuite `json:"ciphersuite"`
}

type CipherscanCiphersuite struct {
	Cipher       string   `json:"cipher"`
	Protocols    []string `json:"protocols"`
	PubKey       []string `json:"pubkey"`
	SigAlg       []string `json:"sigalg"`
	Trusted      string   `json:"trusted"`
	TicketHint   string   `json:"ticket_hint"`
	OCSPStapling string   `json:"ocsp_stapling"`
	PFS          string   `json:"pfs"`
	Curves       []string `json:"curves,omitempty"`
}

//the following structs represent the output we want to provide to DB.

type Stored struct {
	ScanIP         string        `json:"scanIP"`
	ServerSide     bool          `json:"serverside,omitempty"`
	CipherSuite    []Ciphersuite `json:"ciphersuite,omitempty"`
	CurvesFallback bool          `json:"curvesFallback,omitempty"`
}

type Ciphersuite struct {
	Cipher       string   `json:"cipher,omitempty"`
	Protocols    []string `json:"protocols,omitempty"`
	PubKey       float64  `json:"pubkey,omitempty"`
	SigAlg       string   `json:"sigalg,omitempty"`
	TicketHint   string   `json:"ticket_hint,omitempty"`
	OCSPStapling bool     `json:"ocsp_stapling,omitempty"`
	PFS          string   `json:"pfs,omitempty"`
	Curves       []string `json:"curves,omitempty"`
}

func stringtoBool(s string) bool {
	if s == "True" {
		return true
	} else {
		return false
	}
}

func (c Stored) Equal(ci Stored) bool {

	if c.CurvesFallback != ci.CurvesFallback {
		return false
	}

	if c.ServerSide != ci.ServerSide {
		return false
	}

	for i, suite := range c.CipherSuite {

		if !suite.equal(ci.CipherSuite[i]) {
			return false
		}
	}

	return true
}

func (s Ciphersuite) equal(cs Ciphersuite) bool {

	if s.Cipher != cs.Cipher {
		return false
	}

	if s.OCSPStapling != cs.OCSPStapling {
		return false
	}

	if s.PFS != cs.PFS {
		return false
	}

	if s.PubKey != cs.PubKey {
		return false
	}

	if s.SigAlg != cs.SigAlg {
		return false
	}

	if !reflect.DeepEqual(s.Curves, cs.Curves) {
		return false
	}

	if !reflect.DeepEqual(s.Protocols, cs.Protocols) {
		return false
	}

	return true

}

func (s CipherscanOutput) convertTimestamp(t string) (time.Time, error) {

	layout := "2006-01-02T15:04:05.0Z"
	return time.Parse(layout, t)
}

// Stored creates a Stored struct from the CipherscanOutput struct
func (s CipherscanOutput) Stored() (Stored, error) {

	c := Stored{}

	var err error

	c.ServerSide = stringtoBool(s.ServerSide)
	c.CurvesFallback = stringtoBool(s.CurvesFallback)
	c.ScanIP = s.IP

	for _, cipher := range s.CipherSuites {

		newcipher := Ciphersuite{}

		newcipher.Cipher = cipher.Cipher
		newcipher.OCSPStapling = stringtoBool(cipher.OCSPStapling)
		newcipher.PFS = cipher.PFS

		newcipher.Protocols = cipher.Protocols

		if len(cipher.PubKey) > 1 {
			return c, fmt.Errorf("Multiple PubKeys for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.PubKey) > 0 {
			newcipher.PubKey, err = strconv.ParseFloat(cipher.PubKey[0], 64)
		} else {
			return c, fmt.Errorf("No Public Keys found")
		}

		if len(cipher.SigAlg) > 1 {

			return c, fmt.Errorf("Multiple SigAlgs for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.SigAlg) > 0 {
			newcipher.SigAlg = cipher.SigAlg[0]
		} else {
			return c, fmt.Errorf("No Signature Algorithms found")
		}

		newcipher.TicketHint = cipher.TicketHint
		if err != nil {
			return c, err
		}

		newcipher.Curves = append(newcipher.Curves, cipher.Curves...)
		c.CipherSuite = append(c.CipherSuite, newcipher)
	}

	return c, nil
}
<commit_msg>Store and return ciphersuite code, fixes #138<commit_after>package connection

import (
	"fmt"
	"reflect"
	"strconv"
	"time"

	"github.com/mozilla/tls-observatory/constants"
)

//following two structs represent cipherscan output

type CipherscanOutput struct {
	Target         string                  `json:"target"`
	IP             string                  `json:"ip"`
	Timestamp      string                  `json:"utctimestamp"`
	ServerSide     string                  `json:"serverside"`
	CurvesFallback string                  `json:"curves_fallback"`
	CipherSuites   []CipherscanCiphersuite `json:"ciphersuite"`
}

type CipherscanCiphersuite struct {
	Cipher       string   `json:"cipher"`
	Protocols    []string `json:"protocols"`
	PubKey       []string `json:"pubkey"`
	SigAlg       []string `json:"sigalg"`
	Trusted      string   `json:"trusted"`
	TicketHint   string   `json:"ticket_hint"`
	OCSPStapling string   `json:"ocsp_stapling"`
	PFS          string   `json:"pfs"`
	Curves       []string `json:"curves,omitempty"`
}

//the following structs represent the output we want to provide to DB.

type Stored struct {
	ScanIP         string        `json:"scanIP"`
	ServerSide     bool          `json:"serverside,omitempty"`
	CipherSuite    []Ciphersuite `json:"ciphersuite,omitempty"`
	CurvesFallback bool          `json:"curvesFallback,omitempty"`
}

type Ciphersuite struct {
	Cipher       string   `json:"cipher,omitempty"`
	Code         uint64   `json:"code"`
	Protocols    []string `json:"protocols,omitempty"`
	PubKey       float64  `json:"pubkey,omitempty"`
	SigAlg       string   `json:"sigalg,omitempty"`
	TicketHint   string   `json:"ticket_hint,omitempty"`
	OCSPStapling bool     `json:"ocsp_stapling,omitempty"`
	PFS          string   `json:"pfs,omitempty"`
	Curves       []string `json:"curves,omitempty"`
}

func stringtoBool(s string) bool {
	if s == "True" {
		return true
	} else {
		return false
	}
}

func (c Stored) Equal(ci Stored) bool {

	if c.CurvesFallback != ci.CurvesFallback {
		return false
	}

	if c.ServerSide != ci.ServerSide {
		return false
	}

	for i, suite := range c.CipherSuite {

		if !suite.equal(ci.CipherSuite[i]) {
			return false
		}
	}

	return true
}

func (s Ciphersuite) equal(cs Ciphersuite) bool {

	if s.Cipher != cs.Cipher {
		return false
	}

	if s.OCSPStapling != cs.OCSPStapling {
		return false
	}

	if s.PFS != cs.PFS {
		return false
	}

	if s.PubKey != cs.PubKey {
		return false
	}

	if s.SigAlg != cs.SigAlg {
		return false
	}

	if !reflect.DeepEqual(s.Curves, cs.Curves) {
		return false
	}

	if !reflect.DeepEqual(s.Protocols, cs.Protocols) {
		return false
	}

	return true

}

func (s CipherscanOutput) convertTimestamp(t string) (time.Time, error) {

	layout := "2006-01-02T15:04:05.0Z"
	return time.Parse(layout, t)
}

// Stored creates a Stored struct from the CipherscanOutput struct
func (s CipherscanOutput) Stored() (Stored, error) {

	c := Stored{}

	var err error

	c.ServerSide = stringtoBool(s.ServerSide)
	c.CurvesFallback = stringtoBool(s.CurvesFallback)
	c.ScanIP = s.IP

	for _, cipher := range s.CipherSuites {

		newcipher := Ciphersuite{}

		newcipher.Cipher = cipher.Cipher
		newcipher.Code = constants.CipherSuites[cipher.Cipher].Code
		newcipher.OCSPStapling = stringtoBool(cipher.OCSPStapling)
		newcipher.PFS = cipher.PFS

		newcipher.Protocols = cipher.Protocols

		if len(cipher.PubKey) > 1 {
			return c, fmt.Errorf("Multiple PubKeys for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.PubKey) > 0 {
			newcipher.PubKey, err = strconv.ParseFloat(cipher.PubKey[0], 64)
		} else {
			return c, fmt.Errorf("No Public Keys found")
		}

		if len(cipher.SigAlg) > 1 {

			return c, fmt.Errorf("Multiple SigAlgs for ", s.Target, " at cipher :", cipher.Cipher)
		}

		if len(cipher.SigAlg) > 0 {
			newcipher.SigAlg = cipher.SigAlg[0]
		} else {
			return c, fmt.Errorf("No Signature Algorithms found")
		}

		newcipher.TicketHint = cipher.TicketHint
		if err != nil {
			return c, err
		}

		newcipher.Curves = append(newcipher.Curves, cipher.Curves...)
		c.CipherSuite = append(c.CipherSuite, newcipher)
	}

	return c, nil
}
<|endoftext|>


<commit_before>package discordgo

/*
TODO: How to name these? If we make a variable to store
channels from READY packet, etc.  We can't have a Channel
func?  And which is better.  Channels func gets live up
to date data on each call.. so, there is some benefit there.

Maybe it should have both, but make the Channels check and
pull new data based on a cache time?

func (s *Server) Channels() (c []Channel, err error) {
	c, err = Channels(s.Session, s.Id)
	return
}
*/
/*

func (s *Server) Members() (m []Users, err error) {
}
*/
<commit_msg>No longer used.<commit_after><|endoftext|>


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

package migrations

import (
	"fmt"
	"strings"
	"time"

	"github.com/Unknwon/com"
	"github.com/go-xorm/xorm"

	"github.com/gogits/gogs/modules/log"
	"github.com/gogits/gogs/modules/setting"
)

const _MIN_DB_VER = 0

type Migration interface {
	Description() string
	Migrate(*xorm.Engine) error
}

type migration struct {
	description string
	migrate     func(*xorm.Engine) error
}

func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
	return &migration{desc, fn}
}

func (m *migration) Description() string {
	return m.description
}

func (m *migration) Migrate(x *xorm.Engine) error {
	return m.migrate(x)
}

// The version table. Should have only one row with id==1
type Version struct {
	Id      int64
	Version int64
}

// This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, remove it from the top of the list and
// update _MIN_VER_DB accordingly
var migrations = []Migration{
	NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
	NewMigration("refactor access table to use id's", accessRefactor),         // V1 -> V2
}

// Migrate database to current version
func Migrate(x *xorm.Engine) error {
	if err := x.Sync(new(Version)); err != nil {
		return fmt.Errorf("sync: %v", err)
	}

	currentVersion := &Version{Id: 1}
	has, err := x.Get(currentVersion)
	if err != nil {
		return fmt.Errorf("get: %v", err)
	} else if !has {
		// If the user table does not exist it is a fresh installation and we
		// can skip all migrations.
		needsMigration, err := x.IsTableExist("user")
		if err != nil {
			return err
		}
		if needsMigration {
			isEmpty, err := x.IsTableEmpty("user")
			if err != nil {
				return err
			}
			// If the user table is empty it is a fresh installation and we can
			// skip all migrations.
			needsMigration = !isEmpty
		}
		if !needsMigration {
			currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
		}

		if _, err = x.InsertOne(currentVersion); err != nil {
			return fmt.Errorf("insert: %v", err)
		}
	}

	v := currentVersion.Version
	for i, m := range migrations[v-_MIN_DB_VER:] {
		log.Info("Migration: %s", m.Description())
		if err = m.Migrate(x); err != nil {
			return fmt.Errorf("do migrate: %v", err)
		}
		currentVersion.Version = v + int64(i) + 1
		if _, err = x.Id(1).Update(currentVersion); err != nil {
			return err
		}
	}
	return nil
}

func sessionRelease(sess *xorm.Session) {
	if !sess.IsCommitedOrRollbacked {
		sess.Rollback()
	}
	sess.Close()
}

func accessToCollaboration(x *xorm.Engine) (err error) {
	type Collaboration struct {
		ID      int64 `xorm:"pk autoincr"`
		RepoID  int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
		UserID  int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
		Created time.Time
	}

	if err = x.Sync(new(Collaboration)); err != nil {
		return fmt.Errorf("sync: %v", err)
	}

	results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
	if err != nil {
		return err
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	offset := strings.Split(time.Now().String(), " ")[2]
	for _, result := range results {
		mode := com.StrTo(result["mode"]).MustInt64()
		// Collaborators must have write access.
		if mode < 2 {
			continue
		}

		userID := com.StrTo(result["uid"]).MustInt64()
		repoRefName := string(result["repo"])

		var created time.Time
		switch {
		case setting.UseSQLite3:
			created, _ = time.Parse(time.RFC3339, string(result["created"]))
		case setting.UseMySQL:
			created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
		case setting.UsePostgreSQL:
			created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
		}

		// find owner of repository
		parts := strings.SplitN(repoRefName, "/", 2)
		ownerName := parts[0]
		repoName := parts[1]

		results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
		if err != nil {
			return err
		}
		if len(results) < 1 {
			continue
		}

		ownerID := com.StrTo(results[0]["uid"]).MustInt64()
		if ownerID == userID {
			continue
		}

		// test if user is member of owning organization
		isMember := false
		for _, member := range results {
			memberID := com.StrTo(member["memberid"]).MustInt64()
			// We can skip all cases that a user is member of the owning organization
			if memberID == userID {
				isMember = true
			}
		}
		if isMember {
			continue
		}

		results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
		if err != nil {
			return err
		} else if len(results) < 1 {
			continue
		}

		collaboration := &Collaboration{
			UserID: userID,
			RepoID: com.StrTo(results[0]["id"]).MustInt64(),
		}
		has, err := sess.Get(collaboration)
		if err != nil {
			return err
		} else if has {
			continue
		}

		collaboration.Created = created
		if _, err = sess.InsertOne(collaboration); err != nil {
			return err
		}
	}

	return sess.Commit()
}

func accessRefactor(x *xorm.Engine) (err error) {
	type (
		AccessMode int
		Access     struct {
			ID       int64 `xorm:"pk autoincr"`
			UserName string
			RepoName string
			UserID   int64 `xorm:"UNIQUE(s)"`
			RepoID   int64 `xorm:"UNIQUE(s)"`
			Mode     AccessMode
		}
	)

	var rawSQL string
	switch {
	case setting.UseSQLite3, setting.UsePostgreSQL:
		rawSQL = "DROP INDEX IF EXISTS `UQE_access_S`"
	case setting.UseMySQL:
		rawSQL = "DROP INDEX `UQE_access_S` ON `access`"
	}
	if _, err = x.Exec(rawSQL); err != nil &&
		!strings.Contains(err.Error(), "check that column/key exists") {
		return fmt.Errorf("drop index: %v", err)
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = sess.Sync2(new(Access)); err != nil {
		return fmt.Errorf("sync: %v", err)
	}

	accesses := make([]*Access, 0, 50)
	if err = sess.Iterate(new(Access), func(idx int, bean interface{}) error {
		a := bean.(*Access)

		// Update username to user ID.
		users, err := sess.Query("SELECT `id` FROM `user` WHERE lower_name=?", a.UserName)
		if err != nil {
			return fmt.Errorf("query user: %v", err)
		} else if len(users) < 1 {
			return nil
		}
		a.UserID = com.StrTo(users[0]["id"]).MustInt64()

		// Update repository name(username/reponame) to repository ID.
		names := strings.Split(a.RepoName, "/")
		ownerName := names[0]
		repoName := names[1]

		// Check if user is the owner of the repository.
		ownerID := a.UserID
		if ownerName != a.UserName {
			users, err := sess.Query("SELECT `id` FROM `user` WHERE lower_name=?", ownerName)
			if err != nil {
				return fmt.Errorf("query owner: %v", err)
			} else if len(users) < 1 {
				return nil
			}
			ownerID = com.StrTo(users[0]["id"]).MustInt64()
		}

		repos, err := sess.Query("SELECT `id` FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
		if err != nil {
			return fmt.Errorf("query repository: %v", err)
		} else if len(repos) < 1 {
			return nil
		}
		a.RepoID = com.StrTo(repos[0]["id"]).MustInt64()

		accesses = append(accesses, a)
		return nil
	}); err != nil {
		return fmt.Errorf("iterate: %v", err)
	}

	for i := range accesses {
		if _, err = sess.Id(accesses[i].ID).Update(accesses[i]); err != nil {
			return fmt.Errorf("update: %v", err)
		}
	}

	return sess.Commit()
}
<commit_msg>Rewrite of access migration<commit_after>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
	"fmt"
	"strings"
	"time"

	"github.com/Unknwon/com"
	"github.com/go-xorm/xorm"

	"github.com/gogits/gogs/modules/log"
	"github.com/gogits/gogs/modules/setting"
)

const _MIN_DB_VER = 0

type Migration interface {
	Description() string
	Migrate(*xorm.Engine) error
}

type migration struct {
	description string
	migrate     func(*xorm.Engine) error
}

func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
	return &migration{desc, fn}
}

func (m *migration) Description() string {
	return m.description
}

func (m *migration) Migrate(x *xorm.Engine) error {
	return m.migrate(x)
}

// The version table. Should have only one row with id==1
type Version struct {
	Id      int64
	Version int64
}

// This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, remove it from the top of the list and
// update _MIN_VER_DB accordingly
var migrations = []Migration{
	NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
	NewMigration("make authorize 4 if team is owners", ownerTeamUpdate),       // V1 -> V2
	NewMigration("refactor access table to use id's", accessRefactor),         // V2 -> V3
}

// Migrate database to current version
func Migrate(x *xorm.Engine) error {
	if err := x.Sync(new(Version)); err != nil {
		return fmt.Errorf("sync: %v", err)
	}

	currentVersion := &Version{Id: 1}
	has, err := x.Get(currentVersion)
	if err != nil {
		return fmt.Errorf("get: %v", err)
	} else if !has {
		// If the user table does not exist it is a fresh installation and we
		// can skip all migrations.
		needsMigration, err := x.IsTableExist("user")
		if err != nil {
			return err
		}
		if needsMigration {
			isEmpty, err := x.IsTableEmpty("user")
			if err != nil {
				return err
			}
			// If the user table is empty it is a fresh installation and we can
			// skip all migrations.
			needsMigration = !isEmpty
		}
		if !needsMigration {
			currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
		}

		if _, err = x.InsertOne(currentVersion); err != nil {
			return fmt.Errorf("insert: %v", err)
		}
	}

	v := currentVersion.Version
	for i, m := range migrations[v-_MIN_DB_VER:] {
		log.Info("Migration: %s", m.Description())
		if err = m.Migrate(x); err != nil {
			return fmt.Errorf("do migrate: %v", err)
		}
		currentVersion.Version = v + int64(i) + 1
		if _, err = x.Id(1).Update(currentVersion); err != nil {
			return err
		}
	}
	return nil
}

func sessionRelease(sess *xorm.Session) {
	if !sess.IsCommitedOrRollbacked {
		sess.Rollback()
	}
	sess.Close()
}

func accessToCollaboration(x *xorm.Engine) (err error) {
	type Collaboration struct {
		ID      int64 `xorm:"pk autoincr"`
		RepoID  int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
		UserID  int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
		Created time.Time
	}

	if err = x.Sync(new(Collaboration)); err != nil {
		return fmt.Errorf("sync: %v", err)
	}

	results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
	if err != nil {
		return err
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	offset := strings.Split(time.Now().String(), " ")[2]
	for _, result := range results {
		mode := com.StrTo(result["mode"]).MustInt64()
		// Collaborators must have write access.
		if mode < 2 {
			continue
		}

		userID := com.StrTo(result["uid"]).MustInt64()
		repoRefName := string(result["repo"])

		var created time.Time
		switch {
		case setting.UseSQLite3:
			created, _ = time.Parse(time.RFC3339, string(result["created"]))
		case setting.UseMySQL:
			created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
		case setting.UsePostgreSQL:
			created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
		}

		// find owner of repository
		parts := strings.SplitN(repoRefName, "/", 2)
		ownerName := parts[0]
		repoName := parts[1]

		results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
		if err != nil {
			return err
		}
		if len(results) < 1 {
			continue
		}

		ownerID := com.StrTo(results[0]["uid"]).MustInt64()
		if ownerID == userID {
			continue
		}

		// test if user is member of owning organization
		isMember := false
		for _, member := range results {
			memberID := com.StrTo(member["memberid"]).MustInt64()
			// We can skip all cases that a user is member of the owning organization
			if memberID == userID {
				isMember = true
			}
		}
		if isMember {
			continue
		}

		results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
		if err != nil {
			return err
		} else if len(results) < 1 {
			continue
		}

		collaboration := &Collaboration{
			UserID: userID,
			RepoID: com.StrTo(results[0]["id"]).MustInt64(),
		}
		has, err := sess.Get(collaboration)
		if err != nil {
			return err
		} else if has {
			continue
		}

		collaboration.Created = created
		if _, err = sess.InsertOne(collaboration); err != nil {
			return err
		}
	}

	return sess.Commit()
}

func ownerTeamUpdate(x *xorm.Engine) (err error) {
	if _, err := x.Exec("UPDATE team SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
		return fmt.Errorf("drop table: %v", err)
	}
	return nil
}

func accessRefactor(x *xorm.Engine) (err error) {
	type (
		AccessMode int
		Access     struct {
			ID     int64 `xorm:"pk autoincr"`
			UserID int64 `xorm:"UNIQUE(s)"`
			RepoID int64 `xorm:"UNIQUE(s)"`
			Mode   AccessMode
		}
		UserRepo struct {
			UserID int64
			RepoID int64
		}
	)

	// We consiously don't start a session yet as we make only reads for now, no writes

	accessMap := make(map[UserRepo]AccessMode, 50)

	results, err := x.Query("SELECT r.id as `repo_id`, r.is_private as `is_private`, r.owner_id as `owner_id`, u.type as `owner_type` FROM `repository` r LEFT JOIN user u ON r.owner_id=u.id")
	if err != nil {
		return err
	}
	for _, repo := range results {
		repoID := com.StrTo(repo["repo_id"]).MustInt64()
		isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0
		ownerID := com.StrTo(repo["owner_id"]).MustInt64()
		ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0

		results, err := x.Query("SELECT user_id FROM collaboration WHERE repo_id=?", repoID)
		if err != nil {
			return fmt.Errorf("select repos: %v", err)
		}
		for _, user := range results {
			userID := com.StrTo(user["user_id"]).MustInt64()
			accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
		}

		if !ownerIsOrganization {
			continue
		}

		minAccessLevel := AccessMode(0)
		if !isPrivate {
			minAccessLevel = 1
		}

		repoString := "$" + string(repo["repo_id"]) + "|"

		results, err = x.Query("SELECT id, authorize, repo_ids FROM team WHERE org_id=? AND authorize > ? ORDER BY authorize ASC", ownerID, int(minAccessLevel))
		if err != nil {
			return fmt.Errorf("select teams from org: %v", err)
		}

		for _, team := range results {
			if !strings.Contains(string(team["repo_ids"]), repoString) {
				continue
			}
			teamID := com.StrTo(team["id"]).MustInt64()
			mode := AccessMode(com.StrTo(team["authorize"]).MustInt())

			results, err := x.Query("SELECT uid FROM team_user WHERE team_id=?", teamID)
			if err != nil {
				return fmt.Errorf("select users from team: %v", err)
			}
			for _, user := range results {
				userID := com.StrTo(user["uid"]).MustInt64()
				accessMap[UserRepo{userID, repoID}] = mode
			}
		}
	}

	// Drop table can't be in a session (at least not in sqlite)
	if _, err = x.Exec("DROP TABLE access"); err != nil {
		return fmt.Errorf("drop table: %v", err)
	}

	// Now we start writing so we make a session
	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = sess.Sync2(new(Access)); err != nil {
		return fmt.Errorf("sync: %v", err)
	}

	accesses := make([]*Access, 0, len(accessMap))
	for ur, mode := range accessMap {
		accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
	}

	_, err = sess.Insert(accesses)

	return sess.Commit()
}
<|endoftext|>


<commit_before>package eventsocketclient

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/dronemill/eventsocket-client-go/Godeps/_workspace/src/github.com/gorilla/websocket"
)

var d websocket.Dialer

func init() {
	d = websocket.Dialer{
		NetDial:          nil,
		TLSClientConfig:  nil,
		HandshakeTimeout: time.Second * 5,
		ReadBufferSize:   4096,
		WriteBufferSize:  4096,
	}
}

type Client struct {
	// The client Id, provided by the server
	Id string `json:"Id"`

	// channels for receiving messages
	RecvBroadcast chan *Received `json:-`
	RecvRequest   chan *Received `json:-`
	RecvError     chan error     `json:-`

	// the URL to the server
	url string `json:-`

	// the actual websocket connection, after we've connected
	ws *websocket.Conn `json:-`

	// a mapping of all requests that are currently inflight
	liveRequests map[string]chan *Received `json:-`

	// a mapping of the subscription return channels
	subscriptions map[string]chan *Received `json:-`
}

// register a new client with the server
func NewClient(url string) (*Client, error) {
	resp, err := http.Post(fmt.Sprintf("http://%s/v1/clients", url), "application/json", strings.NewReader(""))
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	client := Client{
		url:           url,
		liveRequests:  make(map[string]chan *Received),
		subscriptions: make(map[string]chan *Received),
		RecvBroadcast: make(chan *Received),
		RecvRequest:   make(chan *Received),
		RecvError:     make(chan error),
	}

	// unmarshal the response body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	json.Unmarshal(body, &client)

	return &client, nil
}

// dial the websocket server, and get a websocket connection in return
func (client *Client) DialWs() error {
	headers := http.Header{}
	ws, _, err := d.Dial(fmt.Sprintf("ws://%s/v1/clients/%s/ws", client.url, client.Id), headers)
	if err != nil {
		return err
	}

	client.ws = ws

	return nil
}

// Receive from the socket
func (client *Client) Recv() error {
	for {
		m := &Message{}
		if err := client.ws.ReadJSON(m); err != nil {
			// TODO: something other than panic
			client.RecvError <- err
			continue
		}

		r := &Received{
			Message: m,
			Err:     nil,
		}

		switch m.MessageType {
		case MESSAGE_TYPE_BROADCAST:
			client.RecvBroadcast <- r
		case MESSAGE_TYPE_STANDARD:
			client.subscriptions[r.Message.Event] <- r
		case MESSAGE_TYPE_REQUEST:
			client.RecvRequest <- r
		case MESSAGE_TYPE_REPLY:
			client.liveRequests[m.RequestId] <- r
			close(client.liveRequests[m.RequestId])
			delete(client.liveRequests, m.RequestId)
		}
	}

	return nil
}

// broadcast a payload
func (client *Client) Broadcast(p *Payload) error {
	m := &Message{
		MessageType: MESSAGE_TYPE_BROADCAST,
		Payload:     p,
	}

	return client.write(m)
}

// emit an event
func (client *Client) Emit(event string, p *Payload) error {
	m := &Message{
		MessageType: MESSAGE_TYPE_STANDARD,
		Event:       event,
		Payload:     p,
	}

	return client.write(m)
}

// suscribe to an event(s)
func (client *Client) Suscribe(events ...string) (<-chan *Received, error) {
	p := NewPayload()
	p["Events"] = events

	m := &Message{
		MessageType: MESSAGE_TYPE_SUSCRIBE,
		Payload:     &p,
	}

	if err := client.write(m); err != nil {
		return nil, err
	}

	// create the return channel
	c := make(chan *Received)
	for _, event := range events {
		client.subscriptions[event] = c
	}

	return c, nil
}

// Execute a request against a client
func (client *Client) Request(id string, p *Payload) (<-chan *Received, error) {
	requestId := makeUuid()
	m := &Message{
		MessageType:     MESSAGE_TYPE_REQUEST,
		RequestId:       requestId,
		RequestClientId: id,
		Payload:         p,
	}

	if err := client.write(m); err != nil {
		return nil, err
	}

	c := make(chan *Received)
	client.liveRequests[requestId] = c
	return c, nil
}

// Send a reply to a request
func (client *Client) Reply(requestId, cid string, p *Payload) error {
	m := &Message{
		MessageType:   MESSAGE_TYPE_REPLY,
		ReplyClientId: cid,
		RequestId:     requestId,
		Payload:       p,
	}

	return client.write(m)

}

// write a message to the socket
func (client *Client) write(m *Message) error {
	return client.ws.WriteJSON(m)
}
<commit_msg>Added SetReadLimit<commit_after>package eventsocketclient

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/dronemill/eventsocket-client-go/Godeps/_workspace/src/github.com/gorilla/websocket"
)

var d websocket.Dialer

func init() {
	d = websocket.Dialer{
		NetDial:          nil,
		TLSClientConfig:  nil,
		HandshakeTimeout: time.Second * 5,
		ReadBufferSize:   4096,
		WriteBufferSize:  4096,
	}
}

type Client struct {
	// The client Id, provided by the server
	Id string `json:"Id"`

	// channels for receiving messages
	RecvBroadcast chan *Received `json:-`
	RecvRequest   chan *Received `json:-`
	RecvError     chan error     `json:-`

	// the URL to the server
	url string `json:-`

	// the actual websocket connection, after we've connected
	ws *websocket.Conn `json:-`

	// a mapping of all requests that are currently inflight
	liveRequests map[string]chan *Received `json:-`

	// a mapping of the subscription return channels
	subscriptions map[string]chan *Received `json:-`
}

// register a new client with the server
func NewClient(url string) (*Client, error) {
	resp, err := http.Post(fmt.Sprintf("http://%s/v1/clients", url), "application/json", strings.NewReader(""))
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	client := Client{
		url:           url,
		liveRequests:  make(map[string]chan *Received),
		subscriptions: make(map[string]chan *Received),
		RecvBroadcast: make(chan *Received),
		RecvRequest:   make(chan *Received),
		RecvError:     make(chan error),
	}

	// unmarshal the response body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	json.Unmarshal(body, &client)

	return &client, nil
}

// dial the websocket server, and get a websocket connection in return
func (client *Client) DialWs() error {
	headers := http.Header{}
	ws, _, err := d.Dial(fmt.Sprintf("ws://%s/v1/clients/%s/ws", client.url, client.Id), headers)
	if err != nil {
		return err
	}

	client.ws = ws

	return nil
}

// Receive from the socket
func (client *Client) Recv() error {
	for {
		m := &Message{}
		if err := client.ws.ReadJSON(m); err != nil {
			// TODO: something other than panic
			client.RecvError <- err
			continue
		}

		r := &Received{
			Message: m,
			Err:     nil,
		}

		switch m.MessageType {
		case MESSAGE_TYPE_BROADCAST:
			client.RecvBroadcast <- r
		case MESSAGE_TYPE_STANDARD:
			client.subscriptions[r.Message.Event] <- r
		case MESSAGE_TYPE_REQUEST:
			client.RecvRequest <- r
		case MESSAGE_TYPE_REPLY:
			client.liveRequests[m.RequestId] <- r
			close(client.liveRequests[m.RequestId])
			delete(client.liveRequests, m.RequestId)
		}
	}

	return nil
}

// broadcast a payload
func (client *Client) Broadcast(p *Payload) error {
	m := &Message{
		MessageType: MESSAGE_TYPE_BROADCAST,
		Payload:     p,
	}

	return client.write(m)
}

// emit an event
func (client *Client) Emit(event string, p *Payload) error {
	m := &Message{
		MessageType: MESSAGE_TYPE_STANDARD,
		Event:       event,
		Payload:     p,
	}

	return client.write(m)
}

// suscribe to an event(s)
func (client *Client) Suscribe(events ...string) (<-chan *Received, error) {
	p := NewPayload()
	p["Events"] = events

	m := &Message{
		MessageType: MESSAGE_TYPE_SUSCRIBE,
		Payload:     &p,
	}

	if err := client.write(m); err != nil {
		return nil, err
	}

	// create the return channel
	c := make(chan *Received)
	for _, event := range events {
		client.subscriptions[event] = c
	}

	return c, nil
}

// Execute a request against a client
func (client *Client) Request(id string, p *Payload) (<-chan *Received, error) {
	requestId := makeUuid()
	m := &Message{
		MessageType:     MESSAGE_TYPE_REQUEST,
		RequestId:       requestId,
		RequestClientId: id,
		Payload:         p,
	}

	if err := client.write(m); err != nil {
		return nil, err
	}

	c := make(chan *Received)
	client.liveRequests[requestId] = c
	return c, nil
}

// Send a reply to a request
func (client *Client) Reply(requestId, cid string, p *Payload) error {
	m := &Message{
		MessageType:   MESSAGE_TYPE_REPLY,
		ReplyClientId: cid,
		RequestId:     requestId,
		Payload:       p,
	}

	return client.write(m)

}

// write a message to the socket
func (client *Client) write(m *Message) error {
	return client.ws.WriteJSON(m)
}

// allow read limits to be set
func (client *Client) SetReadLimit(limit int64) {
	client.ws.SetReadLimit(limit)
}
<|endoftext|>


<commit_before>package dns

import (
	"io"
	"hash"
	"strings"
	"crypto/sha1"
)

type saltWireFmt struct {
	Salt string "size-hex"
}

// Hash a string/label according to RFC5155
func Nsec3Hash(label string, ha int, iterations int, salt string) string {

	saltwire := new(saltWireFmt)
	saltwire.Salt = salt
	wire := make([]byte, DefaultMsgSize)
	n, ok := packStruct(saltwire, wire, 0)
	if !ok {
		return ""
	}
	wire = wire[:n]
	name := make([]byte, 255)
	off, ok1 := packDomainName(strings.ToLower(label), name, 0)
	if !ok1 {
		return ""
	}
	name = name[:off]

	var s hash.Hash
	switch ha {
	case HashSHA1:
		s = sha1.New()
        default:
                return ""
	}

	// k = 0
	name = append(name, wire...)
	io.WriteString(s, string(name))
	nsec3 := s.Sum()
        // k > 0
        for k := 0; k < iterations; k++ {
                s.Reset()
                nsec3 = append(nsec3, wire...)
                io.WriteString(s, string(nsec3))
                nsec3 = s.Sum()
        }
	return unpackBase32(nsec3)
}
<commit_msg>cleanups<commit_after>package dns

import (
	"io"
	"hash"
	"strings"
	"crypto/sha1"
)

type saltWireFmt struct {
	Salt string "size-hex"
}

// Hash a string/label according to RFC5155
func Nsec3Hash(label string, ha int, iterations int, salt string) string {
	saltwire := new(saltWireFmt)
	saltwire.Salt = salt
	wire := make([]byte, DefaultMsgSize)
	n, ok := packStruct(saltwire, wire, 0)
	if !ok {
		return ""
	}
	wire = wire[:n]
	name := make([]byte, 255)
	off, ok1 := packDomainName(strings.ToLower(label), name, 0)
	if !ok1 {
		return ""
	}
	name = name[:off]
	var s hash.Hash
	switch ha {
	case HashSHA1:
		s = sha1.New()
        default:
                return ""
	}

	// k = 0
	name = append(name, wire...)
	io.WriteString(s, string(name))
	nsec3 := s.Sum()
        // k > 0
        for k := 0; k < iterations; k++ {
                s.Reset()
                nsec3 = append(nsec3, wire...)
                io.WriteString(s, string(nsec3))
                nsec3 = s.Sum()
        }
	return unpackBase32(nsec3)
}
<|endoftext|>


<commit_before>package api

// Resources represents the system resources avaible for LXD
// API extension: resources
type Resources struct {
	CPU    ResourcesCPU    `json:"cpu" yaml:"cpu"`
	Memory ResourcesMemory `json:"memory" yaml:"memory"`

	// API extension: resources_gpu
	GPU ResourcesGPU `json:"gpu" yaml:"gpu"`

	// API extension: resources_v2
	Network ResourcesNetwork `json:"network" yaml:"network"`
	Storage ResourcesStorage `json:"storage" yaml:"storage"`
}

// ResourcesCPU represents the cpu resources available on the system
// API extension: resources
type ResourcesCPU struct {
	// API extension: resources_v2
	Architecture string `json:"architecture" yaml:"architecture"`

	Sockets []ResourcesCPUSocket `json:"sockets" yaml:"sockets"`
	Total   uint64               `json:"total" yaml:"total"`
}

// ResourcesCPUSocket represents a CPU socket on the system
// API extension: resources_v2
type ResourcesCPUSocket struct {
	Name   string `json:"name,omitempty" yaml:"name,omitempty"`
	Vendor string `json:"vendor,omitempty" yaml:"vendor,omitempty"`

	Socket uint64              `json:"socket" yaml:"socket"`
	Cache  []ResourcesCPUCache `json:"cache,omitempty" yaml:"cache,omitempty"`
	Cores  []ResourcesCPUCore  `json:"cores" yaml:"cores"`

	Frequency        uint64 `json:"frequency,omitempty" yaml:"frequency,omitempty"`
	FrequencyMinimum uint64 `json:"frequency_minimum,omitempty" yaml:"frequency_minimum,omitempty"`
	FrequencyTurbo   uint64 `json:"frequency_turbo,omitempty" yaml:"frequency_turbo,omitempty"`
}

// ResourcesCPUCache represents a CPU cache
// API extension: resources_v2
type ResourcesCPUCache struct {
	Level uint64 `json:"level" yaml:"level"`
	Type  string `json:"type" yaml:"type"`
	Size  uint64 `json:"size" yaml:"size"`
}

// ResourcesCPUCore represents a CPU core on the system
// API extension: resources_v2
type ResourcesCPUCore struct {
	Core     uint64 `json:"core" yaml:"core"`
	NUMANode uint64 `json:"numa_node" yaml:"numa_node"`

	Threads []ResourcesCPUThread `json:"threads" yaml:"threads"`

	Frequency uint64 `json:"frequency,omitempty" yaml:"frequency,omitempty"`
}

// ResourcesCPUThread represents a CPU thread on the system
// API extension: resources_v2
type ResourcesCPUThread struct {
	ID     int64  `json:"id" yaml:"id"`
	Thread uint64 `json:"thread" yaml:"thread"`
	Online bool   `json:"online" yaml:"online"`
}

// ResourcesGPU represents the GPU resources available on the system
// API extension: resources_gpu
type ResourcesGPU struct {
	Cards []ResourcesGPUCard `json:"cards" yaml:"cards"`
	Total uint64             `json:"total" yaml:"total"`
}

// ResourcesGPUCard represents a GPU card on the system
// API extension: resources_v2
type ResourcesGPUCard struct {
	Driver        string `json:"driver,omitempty" yaml:"driver,omitempty"`
	DriverVersion string `json:"driver_version,omitempty" yaml:"driver_version,omitempty"`

	DRM    *ResourcesGPUCardDRM    `json:"drm,omitempty" yaml:"drm,omitempty"`
	SRIOV  *ResourcesGPUCardSRIOV  `json:"sriov,omitempty" yaml:"sriov,omitempty"`
	Nvidia *ResourcesGPUCardNvidia `json:"nvidia,omitempty" yaml:"nvidia,omitempty"`

	NUMANode   uint64 `json:"numa_node" yaml:"numa_node"`
	PCIAddress string `json:"pci_address,omitempty" yaml:"pci_address,omitempty"`

	Vendor    string `json:"vendor,omitempty" yaml:"vendor,omitempty"`
	VendorID  string `json:"vendor_id,omitempty" yaml:"vendor_id,omitempty"`
	Product   string `json:"product,omitempty" yaml:"product,omitempty"`
	ProductID string `json:"product_id,omitempty" yaml:"product_id,omitempty"`
}

// ResourcesGPUCardDRM represents the Linux DRM configuration of the GPU
// API extension: resources_v2
type ResourcesGPUCardDRM struct {
	ID uint64 `json:"id" yaml:"id"`

	CardName   string `json:"card_name" yaml:"card_name"`
	CardDevice string `json:"card_device" yaml:"card_device"`

	ControlName   string `json:"control_name,omitempty" yaml:"control_name,omitempty"`
	ControlDevice string `json:"control_device,omitempty" yaml:"control_device,omitempty"`

	RenderName   string `json:"render_name,omitempty" yaml:"render_name,omitempty"`
	RenderDevice string `json:"render_device,omitempty" yaml:"render_device,omitempty"`
}

// ResourcesGPUCardSRIOV represents the SRIOV configuration of the GPU
// API extension: resources_v2
type ResourcesGPUCardSRIOV struct {
	CurrentVFs uint64 `json:"current_vfs" yaml:"current_vfs"`
	MaximumVFs uint64 `json:"maximum_vfs" yaml:"maximum_vfs"`

	VFs []ResourcesGPUCard `json:"vfs" yaml:"vfs"`
}

// ResourcesGPUCardNvidia represents additional information for NVIDIA GPUs
// API extension: resources_gpu
type ResourcesGPUCardNvidia struct {
	CUDAVersion string `json:"cuda_version,omitempty" yaml:"cuda_version,omitempty"`
	NVRMVersion string `json:"nvrm_version,omitempty" yaml:"nvrm_version,omitempty"`

	Brand        string `json:"brand" yaml:"brand"`
	Model        string `json:"model" yaml:"model"`
	UUID         string `json:"uuid,omitempty" yaml:"uuid,omitempty"`
	Architecture string `json:"architecture,omitempty" yaml:"architecture,omitempty"`

	// API extension: resources_v2
	CardName   string `json:"card_name" yaml:"card_name"`
	CardDevice string `json:"card_device" yaml:"card_device"`
}

// ResourcesNetwork represents the network cards available on the system
// API extension: resources_v2
type ResourcesNetwork struct {
	Cards []ResourcesNetworkCard `json:"cards" yaml:"cards"`
	Total uint64                 `json:"total" yaml:"total"`
}

// ResourcesNetworkCard represents a network card on the system
// API extension: resources_v2
type ResourcesNetworkCard struct {
	Driver        string `json:"driver,omitempty" yaml:"driver,omitempty"`
	DriverVersion string `json:"driver_version,omitempty" yaml:"driver_version,omitempty"`

	Ports []ResourcesNetworkCardPort `json:"ports,omitempty" yaml:"ports,omitempty"`
	SRIOV *ResourcesNetworkCardSRIOV `json:"sriov,omitempty" yaml:"sriov,omitempty"`

	NUMANode   uint64 `json:"numa_node" yaml:"numa_node"`
	PCIAddress string `json:"pci_address,omitempty" yaml:"pci_address,omitempty"`

	Vendor    string `json:"vendor,omitempty" yaml:"vendor,omitempty"`
	VendorID  string `json:"vendor_id,omitempty" yaml:"vendor_id,omitempty"`
	Product   string `json:"product,omitempty" yaml:"product,omitempty"`
	ProductID string `json:"product_id,omitempty" yaml:"product_id,omitempty"`
}

// ResourcesNetworkCardPort represents a network port on the system
// API extension: resources_v2
type ResourcesNetworkCardPort struct {
	ID       string `json:"id" yaml:"id"`
	Address  string `json:"address,omitempty" yaml:"address,omitempty"`
	Port     uint64 `json:"port" yaml:"port"`
	Protocol string `json:"protocol" yaml:"protocol"`

	SupportedModes []string `json:"supported_modes,omitempty" yaml:"supported_modes,omitempty"`
	SupportedPorts []string `json:"supported_ports,omitempty" yaml:"supported_ports,omitempty"`

	PortType        string `json:"port_type,omitempty" yaml:"port_type,omitempty"`
	TransceiverType string `json:"transceiver_type,omitempty" yaml:"transceiver_type,omitempty"`

	AutoNegotiation bool   `json:"auto_negotiation" yaml:"auto_negotiation"`
	LinkDetected    bool   `json:"link_detected" yaml:"link_detected"`
	LinkSpeed       uint64 `json:"link_speed,omitempty" yaml:"link_speed,omitempty"`
	LinkDuplex      string `json:"link_duplex,omitempty" yaml:"link_duplex,omitempty"`

	// API extension: resources_infiniband
	Infiniband *ResourcesNetworkCardPortInfiniband `json:"infiniband,omitempty" yaml:"infiniband,omitempty"`
}

// ResourcesNetworkCardPortInfiniband represents the Linux Infiniband configuration for the port
// API extension: resources_infiniband
type ResourcesNetworkCardPortInfiniband struct {
	IsSMName   string `json:"issm_name,omitempty" yaml:"issm_name,omitempty"`
	IsSMDevice string `json:"issm_device,omitempty" yaml:"issm_device,omitempty"`

	MADName   string `json:"mad_name,omitempty" yaml:"mad_name,omitempty"`
	MADDevice string `json:"mad_device,omitempty" yaml:"mad_device,omitempty"`

	VerbName   string `json:"verb_name,omitempty" yaml:"verb_name,omitempty"`
	VerbDevice string `json:"verb_device,omitempty" yaml:"verb_device,omitempty"`
}

// ResourcesNetworkCardSRIOV represents the SRIOV configuration of the network card
// API extension: resources_v2
type ResourcesNetworkCardSRIOV struct {
	CurrentVFs uint64 `json:"current_vfs" yaml:"current_vfs"`
	MaximumVFs uint64 `json:"maximum_vfs" yaml:"maximum_vfs"`

	VFs []ResourcesNetworkCard `json:"vfs" yaml:"vfs"`
}

// ResourcesStorage represents the local storage
// API extension: resources_v2
type ResourcesStorage struct {
	Disks []ResourcesStorageDisk `json:"disks" yaml:"disks"`
	Total uint64                 `json:"total" yaml:"total"`
}

// ResourcesStorageDisk represents a disk
// API extension: resources_v2
type ResourcesStorageDisk struct {
	ID       string `json:"id" yaml:"id"`
	Device   string `json:"device" yaml:"device"`
	Model    string `json:"model,omitempty" yaml:"model,omitempty"`
	Type     string `json:"type,omitempty" yaml:"type,omitempty"`
	ReadOnly bool   `json:"read_only" yaml:"read_only"`
	Size     uint64 `json:"size" yaml:"size"`

	Removable bool   `json:"removable" yaml:"removable"`
	WWN       string `json:"wwn,omitempty" yaml:"wwn,omitempty"`
	NUMANode  uint64 `json:"numa_node" yaml:"numa_node"`

	// API extension: resources_disk_sata
	DevicePath      string `json:"device_path" yaml:"device_path"`
	BlockSize       uint64 `json:"block_size" yaml:"block_size"`
	FirmwareVersion string `json:"firmware_version,omitempty" yaml:"firmware_version,omitempty"`
	RPM             uint64 `json:"rpm" yaml:"rpm"`
	Serial          string `json:"serial,omitempty" yaml:"serial,omitempty"`

	Partitions []ResourcesStorageDiskPartition `json:"partitions" yaml:"partitions"`
}

// ResourcesStorageDiskPartition represents a partition on a disk
// API extension: resources_v2
type ResourcesStorageDiskPartition struct {
	ID       string `json:"id" yaml:"id"`
	Device   string `json:"device" yaml:"device"`
	ReadOnly bool   `json:"read_only" yaml:"read_only"`
	Size     uint64 `json:"size" yaml:"size"`

	Partition uint64 `json:"partition" yaml:"partition"`
}

// ResourcesMemory represents the memory resources available on the system
// API extension: resources
type ResourcesMemory struct {
	// API extension: resources_v2
	Nodes          []ResourcesMemoryNode `json:"nodes,omitempty" yaml:"nodes,omitempty"`
	HugepagesTotal uint64                `json:"hugepages_total" yaml:"hugepages_total"`
	HugepagesUsed  uint64                `json:"hugepages_used" yaml:"hugepages_used"`
	HugepagesSize  uint64                `json:"hugepages_size" yaml:"hugepages_size"`

	Used  uint64 `json:"used" yaml:"used"`
	Total uint64 `json:"total" yaml:"total"`
}

// ResourcesMemoryNode represents the node-specific memory resources available on the system
// API extension: resources_v2
type ResourcesMemoryNode struct {
	NUMANode       uint64 `json:"numa_node" yaml:"numa_node"`
	HugepagesUsed  uint64 `json:"hugepages_used" yaml:"hugepages_used"`
	HugepagesTotal uint64 `json:"hugepages_total" yaml:"hugepages_total"`

	Used  uint64 `json:"used" yaml:"used"`
	Total uint64 `json:"total" yaml:"total"`
}

// ResourcesStoragePool represents the resources available to a given storage pool
// API extension: resources
type ResourcesStoragePool struct {
	Space  ResourcesStoragePoolSpace  `json:"space,omitempty" yaml:"space,omitempty"`
	Inodes ResourcesStoragePoolInodes `json:"inodes,omitempty" yaml:"inodes,omitempty"`
}

// ResourcesStoragePoolSpace represents the space available to a given storage pool
// API extension: resources
type ResourcesStoragePoolSpace struct {
	Used  uint64 `json:"used,omitempty" yaml:"used,omitempty"`
	Total uint64 `json:"total" yaml:"total"`
}

// ResourcesStoragePoolInodes represents the inodes available to a given storage pool
// API extension: resources
type ResourcesStoragePoolInodes struct {
	Used  uint64 `json:"used" yaml:"used"`
	Total uint64 `json:"total" yaml:"total"`
}
<commit_msg>shared/api: Add FirmwareVersion to ResourcesNetworkCard<commit_after>package api

// Resources represents the system resources avaible for LXD
// API extension: resources
type Resources struct {
	CPU    ResourcesCPU    `json:"cpu" yaml:"cpu"`
	Memory ResourcesMemory `json:"memory" yaml:"memory"`

	// API extension: resources_gpu
	GPU ResourcesGPU `json:"gpu" yaml:"gpu"`

	// API extension: resources_v2
	Network ResourcesNetwork `json:"network" yaml:"network"`
	Storage ResourcesStorage `json:"storage" yaml:"storage"`
}

// ResourcesCPU represents the cpu resources available on the system
// API extension: resources
type ResourcesCPU struct {
	// API extension: resources_v2
	Architecture string `json:"architecture" yaml:"architecture"`

	Sockets []ResourcesCPUSocket `json:"sockets" yaml:"sockets"`
	Total   uint64               `json:"total" yaml:"total"`
}

// ResourcesCPUSocket represents a CPU socket on the system
// API extension: resources_v2
type ResourcesCPUSocket struct {
	Name   string `json:"name,omitempty" yaml:"name,omitempty"`
	Vendor string `json:"vendor,omitempty" yaml:"vendor,omitempty"`

	Socket uint64              `json:"socket" yaml:"socket"`
	Cache  []ResourcesCPUCache `json:"cache,omitempty" yaml:"cache,omitempty"`
	Cores  []ResourcesCPUCore  `json:"cores" yaml:"cores"`

	Frequency        uint64 `json:"frequency,omitempty" yaml:"frequency,omitempty"`
	FrequencyMinimum uint64 `json:"frequency_minimum,omitempty" yaml:"frequency_minimum,omitempty"`
	FrequencyTurbo   uint64 `json:"frequency_turbo,omitempty" yaml:"frequency_turbo,omitempty"`
}

// ResourcesCPUCache represents a CPU cache
// API extension: resources_v2
type ResourcesCPUCache struct {
	Level uint64 `json:"level" yaml:"level"`
	Type  string `json:"type" yaml:"type"`
	Size  uint64 `json:"size" yaml:"size"`
}

// ResourcesCPUCore represents a CPU core on the system
// API extension: resources_v2
type ResourcesCPUCore struct {
	Core     uint64 `json:"core" yaml:"core"`
	NUMANode uint64 `json:"numa_node" yaml:"numa_node"`

	Threads []ResourcesCPUThread `json:"threads" yaml:"threads"`

	Frequency uint64 `json:"frequency,omitempty" yaml:"frequency,omitempty"`
}

// ResourcesCPUThread represents a CPU thread on the system
// API extension: resources_v2
type ResourcesCPUThread struct {
	ID     int64  `json:"id" yaml:"id"`
	Thread uint64 `json:"thread" yaml:"thread"`
	Online bool   `json:"online" yaml:"online"`
}

// ResourcesGPU represents the GPU resources available on the system
// API extension: resources_gpu
type ResourcesGPU struct {
	Cards []ResourcesGPUCard `json:"cards" yaml:"cards"`
	Total uint64             `json:"total" yaml:"total"`
}

// ResourcesGPUCard represents a GPU card on the system
// API extension: resources_v2
type ResourcesGPUCard struct {
	Driver        string `json:"driver,omitempty" yaml:"driver,omitempty"`
	DriverVersion string `json:"driver_version,omitempty" yaml:"driver_version,omitempty"`

	DRM    *ResourcesGPUCardDRM    `json:"drm,omitempty" yaml:"drm,omitempty"`
	SRIOV  *ResourcesGPUCardSRIOV  `json:"sriov,omitempty" yaml:"sriov,omitempty"`
	Nvidia *ResourcesGPUCardNvidia `json:"nvidia,omitempty" yaml:"nvidia,omitempty"`

	NUMANode   uint64 `json:"numa_node" yaml:"numa_node"`
	PCIAddress string `json:"pci_address,omitempty" yaml:"pci_address,omitempty"`

	Vendor    string `json:"vendor,omitempty" yaml:"vendor,omitempty"`
	VendorID  string `json:"vendor_id,omitempty" yaml:"vendor_id,omitempty"`
	Product   string `json:"product,omitempty" yaml:"product,omitempty"`
	ProductID string `json:"product_id,omitempty" yaml:"product_id,omitempty"`
}

// ResourcesGPUCardDRM represents the Linux DRM configuration of the GPU
// API extension: resources_v2
type ResourcesGPUCardDRM struct {
	ID uint64 `json:"id" yaml:"id"`

	CardName   string `json:"card_name" yaml:"card_name"`
	CardDevice string `json:"card_device" yaml:"card_device"`

	ControlName   string `json:"control_name,omitempty" yaml:"control_name,omitempty"`
	ControlDevice string `json:"control_device,omitempty" yaml:"control_device,omitempty"`

	RenderName   string `json:"render_name,omitempty" yaml:"render_name,omitempty"`
	RenderDevice string `json:"render_device,omitempty" yaml:"render_device,omitempty"`
}

// ResourcesGPUCardSRIOV represents the SRIOV configuration of the GPU
// API extension: resources_v2
type ResourcesGPUCardSRIOV struct {
	CurrentVFs uint64 `json:"current_vfs" yaml:"current_vfs"`
	MaximumVFs uint64 `json:"maximum_vfs" yaml:"maximum_vfs"`

	VFs []ResourcesGPUCard `json:"vfs" yaml:"vfs"`
}

// ResourcesGPUCardNvidia represents additional information for NVIDIA GPUs
// API extension: resources_gpu
type ResourcesGPUCardNvidia struct {
	CUDAVersion string `json:"cuda_version,omitempty" yaml:"cuda_version,omitempty"`
	NVRMVersion string `json:"nvrm_version,omitempty" yaml:"nvrm_version,omitempty"`

	Brand        string `json:"brand" yaml:"brand"`
	Model        string `json:"model" yaml:"model"`
	UUID         string `json:"uuid,omitempty" yaml:"uuid,omitempty"`
	Architecture string `json:"architecture,omitempty" yaml:"architecture,omitempty"`

	// API extension: resources_v2
	CardName   string `json:"card_name" yaml:"card_name"`
	CardDevice string `json:"card_device" yaml:"card_device"`
}

// ResourcesNetwork represents the network cards available on the system
// API extension: resources_v2
type ResourcesNetwork struct {
	Cards []ResourcesNetworkCard `json:"cards" yaml:"cards"`
	Total uint64                 `json:"total" yaml:"total"`
}

// ResourcesNetworkCard represents a network card on the system
// API extension: resources_v2
type ResourcesNetworkCard struct {
	Driver        string `json:"driver,omitempty" yaml:"driver,omitempty"`
	DriverVersion string `json:"driver_version,omitempty" yaml:"driver_version,omitempty"`

	Ports []ResourcesNetworkCardPort `json:"ports,omitempty" yaml:"ports,omitempty"`
	SRIOV *ResourcesNetworkCardSRIOV `json:"sriov,omitempty" yaml:"sriov,omitempty"`

	NUMANode   uint64 `json:"numa_node" yaml:"numa_node"`
	PCIAddress string `json:"pci_address,omitempty" yaml:"pci_address,omitempty"`

	Vendor    string `json:"vendor,omitempty" yaml:"vendor,omitempty"`
	VendorID  string `json:"vendor_id,omitempty" yaml:"vendor_id,omitempty"`
	Product   string `json:"product,omitempty" yaml:"product,omitempty"`
	ProductID string `json:"product_id,omitempty" yaml:"product_id,omitempty"`

	// API extension: resources_network_firmware
	FirmwareVersion string `json:"firmware_version,omitempty" yaml:"firmware_version,omitempty"`
}

// ResourcesNetworkCardPort represents a network port on the system
// API extension: resources_v2
type ResourcesNetworkCardPort struct {
	ID       string `json:"id" yaml:"id"`
	Address  string `json:"address,omitempty" yaml:"address,omitempty"`
	Port     uint64 `json:"port" yaml:"port"`
	Protocol string `json:"protocol" yaml:"protocol"`

	SupportedModes []string `json:"supported_modes,omitempty" yaml:"supported_modes,omitempty"`
	SupportedPorts []string `json:"supported_ports,omitempty" yaml:"supported_ports,omitempty"`

	PortType        string `json:"port_type,omitempty" yaml:"port_type,omitempty"`
	TransceiverType string `json:"transceiver_type,omitempty" yaml:"transceiver_type,omitempty"`

	AutoNegotiation bool   `json:"auto_negotiation" yaml:"auto_negotiation"`
	LinkDetected    bool   `json:"link_detected" yaml:"link_detected"`
	LinkSpeed       uint64 `json:"link_speed,omitempty" yaml:"link_speed,omitempty"`
	LinkDuplex      string `json:"link_duplex,omitempty" yaml:"link_duplex,omitempty"`

	// API extension: resources_infiniband
	Infiniband *ResourcesNetworkCardPortInfiniband `json:"infiniband,omitempty" yaml:"infiniband,omitempty"`
}

// ResourcesNetworkCardPortInfiniband represents the Linux Infiniband configuration for the port
// API extension: resources_infiniband
type ResourcesNetworkCardPortInfiniband struct {
	IsSMName   string `json:"issm_name,omitempty" yaml:"issm_name,omitempty"`
	IsSMDevice string `json:"issm_device,omitempty" yaml:"issm_device,omitempty"`

	MADName   string `json:"mad_name,omitempty" yaml:"mad_name,omitempty"`
	MADDevice string `json:"mad_device,omitempty" yaml:"mad_device,omitempty"`

	VerbName   string `json:"verb_name,omitempty" yaml:"verb_name,omitempty"`
	VerbDevice string `json:"verb_device,omitempty" yaml:"verb_device,omitempty"`
}

// ResourcesNetworkCardSRIOV represents the SRIOV configuration of the network card
// API extension: resources_v2
type ResourcesNetworkCardSRIOV struct {
	CurrentVFs uint64 `json:"current_vfs" yaml:"current_vfs"`
	MaximumVFs uint64 `json:"maximum_vfs" yaml:"maximum_vfs"`

	VFs []ResourcesNetworkCard `json:"vfs" yaml:"vfs"`
}

// ResourcesStorage represents the local storage
// API extension: resources_v2
type ResourcesStorage struct {
	Disks []ResourcesStorageDisk `json:"disks" yaml:"disks"`
	Total uint64                 `json:"total" yaml:"total"`
}

// ResourcesStorageDisk represents a disk
// API extension: resources_v2
type ResourcesStorageDisk struct {
	ID       string `json:"id" yaml:"id"`
	Device   string `json:"device" yaml:"device"`
	Model    string `json:"model,omitempty" yaml:"model,omitempty"`
	Type     string `json:"type,omitempty" yaml:"type,omitempty"`
	ReadOnly bool   `json:"read_only" yaml:"read_only"`
	Size     uint64 `json:"size" yaml:"size"`

	Removable bool   `json:"removable" yaml:"removable"`
	WWN       string `json:"wwn,omitempty" yaml:"wwn,omitempty"`
	NUMANode  uint64 `json:"numa_node" yaml:"numa_node"`

	// API extension: resources_disk_sata
	DevicePath      string `json:"device_path" yaml:"device_path"`
	BlockSize       uint64 `json:"block_size" yaml:"block_size"`
	FirmwareVersion string `json:"firmware_version,omitempty" yaml:"firmware_version,omitempty"`
	RPM             uint64 `json:"rpm" yaml:"rpm"`
	Serial          string `json:"serial,omitempty" yaml:"serial,omitempty"`

	Partitions []ResourcesStorageDiskPartition `json:"partitions" yaml:"partitions"`
}

// ResourcesStorageDiskPartition represents a partition on a disk
// API extension: resources_v2
type ResourcesStorageDiskPartition struct {
	ID       string `json:"id" yaml:"id"`
	Device   string `json:"device" yaml:"device"`
	ReadOnly bool   `json:"read_only" yaml:"read_only"`
	Size     uint64 `json:"size" yaml:"size"`

	Partition uint64 `json:"partition" yaml:"partition"`
}

// ResourcesMemory represents the memory resources available on the system
// API extension: resources
type ResourcesMemory struct {
	// API extension: resources_v2
	Nodes          []ResourcesMemoryNode `json:"nodes,omitempty" yaml:"nodes,omitempty"`
	HugepagesTotal uint64                `json:"hugepages_total" yaml:"hugepages_total"`
	HugepagesUsed  uint64                `json:"hugepages_used" yaml:"hugepages_used"`
	HugepagesSize  uint64                `json:"hugepages_size" yaml:"hugepages_size"`

	Used  uint64 `json:"used" yaml:"used"`
	Total uint64 `json:"total" yaml:"total"`
}

// ResourcesMemoryNode represents the node-specific memory resources available on the system
// API extension: resources_v2
type ResourcesMemoryNode struct {
	NUMANode       uint64 `json:"numa_node" yaml:"numa_node"`
	HugepagesUsed  uint64 `json:"hugepages_used" yaml:"hugepages_used"`
	HugepagesTotal uint64 `json:"hugepages_total" yaml:"hugepages_total"`

	Used  uint64 `json:"used" yaml:"used"`
	Total uint64 `json:"total" yaml:"total"`
}

// ResourcesStoragePool represents the resources available to a given storage pool
// API extension: resources
type ResourcesStoragePool struct {
	Space  ResourcesStoragePoolSpace  `json:"space,omitempty" yaml:"space,omitempty"`
	Inodes ResourcesStoragePoolInodes `json:"inodes,omitempty" yaml:"inodes,omitempty"`
}

// ResourcesStoragePoolSpace represents the space available to a given storage pool
// API extension: resources
type ResourcesStoragePoolSpace struct {
	Used  uint64 `json:"used,omitempty" yaml:"used,omitempty"`
	Total uint64 `json:"total" yaml:"total"`
}

// ResourcesStoragePoolInodes represents the inodes available to a given storage pool
// API extension: resources
type ResourcesStoragePoolInodes struct {
	Used  uint64 `json:"used" yaml:"used"`
	Total uint64 `json:"total" yaml:"total"`
}
<|endoftext|>


<commit_before>// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"net"
	"net/http"
	"net/http/httputil"
	"sync"
	"sync/atomic"
	"time"
)

// APIEvents represents events coming from the Docker API
// The fields in the Docker API changed in API version 1.22, and
// events for more than images and containers are now fired off.
// To maintain forward and backward compatibility, go-dockerclient
// replicates the event in both the new and old format as faithfully as possible.
//
// For events that only exist in 1.22 in later, `Status` is filled in as
// `"Type:Action"` instead of just `Action` to allow for older clients to
// differentiate and not break if they rely on the pre-1.22 Status types.
//
// The transformEvent method can be consulted for more information about how
// events are translated from new/old API formats
type APIEvents struct {
	// New API Fields in 1.22
	Action string   `json:"action,omitempty"`
	Type   string   `json:"type,omitempty"`
	Actor  APIActor `json:"actor,omitempty"`

	// Old API fields for < 1.22
	Status string `json:"status,omitempty"`
	ID     string `json:"id,omitempty"`
	From   string `json:"from,omitempty"`

	// Fields in both
	Time     int64 `json:"time,omitempty"`
	TimeNano int64 `json:"timeNano,omitempty"`
}

// APIActor represents an actor that accomplishes something for an event
type APIActor struct {
	ID         string            `json:"id,omitempty"`
	Attributes map[string]string `json:"attributes,omitempty"`
}

type eventMonitoringState struct {
	// `sync/atomic` expects the first word in an allocated struct to be 64-bit
	// aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
	lastSeen int64
	sync.RWMutex
	sync.WaitGroup
	enabled   bool
	C         chan *APIEvents
	errC      chan error
	listeners []chan<- *APIEvents
}

const (
	maxMonitorConnRetries = 5
	retryInitialWaitTime  = 10.
)

var (
	// ErrNoListeners is the error returned when no listeners are available
	// to receive an event.
	ErrNoListeners = errors.New("no listeners present to receive event")

	// ErrListenerAlreadyExists is the error returned when the listerner already
	// exists.
	ErrListenerAlreadyExists = errors.New("listener already exists for docker events")

	// ErrTLSNotSupported is the error returned when the client does not support
	// TLS (this applies to the Windows named pipe client).
	ErrTLSNotSupported = errors.New("tls not supported by this client")

	// EOFEvent is sent when the event listener receives an EOF error.
	EOFEvent = &APIEvents{
		Type:   "EOF",
		Status: "EOF",
	}
)

// AddEventListener adds a new listener to container events in the Docker API.
//
// The parameter is a channel through which events will be sent.
func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
	var err error
	if !c.eventMonitor.isEnabled() {
		err = c.eventMonitor.enableEventMonitoring(c)
		if err != nil {
			return err
		}
	}
	return c.eventMonitor.addListener(listener)
}

// RemoveEventListener removes a listener from the monitor.
func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
	err := c.eventMonitor.removeListener(listener)
	if err != nil {
		return err
	}
	if c.eventMonitor.listernersCount() == 0 {
		c.eventMonitor.disableEventMonitoring()
	}
	return nil
}

func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
	eventState.Lock()
	defer eventState.Unlock()
	if listenerExists(listener, &eventState.listeners) {
		return ErrListenerAlreadyExists
	}
	eventState.Add(1)
	eventState.listeners = append(eventState.listeners, listener)
	return nil
}

func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
	eventState.Lock()
	defer eventState.Unlock()
	if listenerExists(listener, &eventState.listeners) {
		var newListeners []chan<- *APIEvents
		for _, l := range eventState.listeners {
			if l != listener {
				newListeners = append(newListeners, l)
			}
		}
		eventState.listeners = newListeners
		eventState.Add(-1)
	}
	return nil
}

func (eventState *eventMonitoringState) closeListeners() {
	for _, l := range eventState.listeners {
		close(l)
		eventState.Add(-1)
	}
	eventState.listeners = nil
}

func (eventState *eventMonitoringState) listernersCount() int {
	eventState.RLock()
	defer eventState.RUnlock()
	return len(eventState.listeners)
}

func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
	for _, b := range *list {
		if b == a {
			return true
		}
	}
	return false
}

func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
	eventState.Lock()
	defer eventState.Unlock()
	if !eventState.enabled {
		eventState.enabled = true
		atomic.StoreInt64(&eventState.lastSeen, 0)
		eventState.C = make(chan *APIEvents, 100)
		eventState.errC = make(chan error, 1)
		go eventState.monitorEvents(c)
	}
	return nil
}

func (eventState *eventMonitoringState) disableEventMonitoring() error {
	eventState.Lock()
	defer eventState.Unlock()

	eventState.closeListeners()

	eventState.Wait()

	if eventState.enabled {
		eventState.enabled = false
		close(eventState.C)
		close(eventState.errC)
	}
	return nil
}

func (eventState *eventMonitoringState) monitorEvents(c *Client) {
	var err error
	for eventState.noListeners() {
		time.Sleep(10 * time.Millisecond)
	}
	if err = eventState.connectWithRetry(c); err != nil {
		// terminate if connect failed
		eventState.disableEventMonitoring()
		return
	}
	for eventState.isEnabled() {
		timeout := time.After(100 * time.Millisecond)
		select {
		case ev, ok := <-eventState.C:
			if !ok {
				return
			}
			if ev == EOFEvent {
				eventState.disableEventMonitoring()
				return
			}
			eventState.updateLastSeen(ev)
			eventState.sendEvent(ev)
		case err = <-eventState.errC:
			if err == ErrNoListeners {
				eventState.disableEventMonitoring()
				return
			} else if err != nil {
				defer func() { go eventState.monitorEvents(c) }()
				return
			}
		case <-timeout:
			continue
		}
	}
}

func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
	var retries int
	eventState.RLock()
	eventChan := eventState.C
	errChan := eventState.errC
	eventState.RUnlock()
	err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
	for ; err != nil && retries < maxMonitorConnRetries; retries++ {
		waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
		time.Sleep(time.Duration(waitTime) * time.Millisecond)
		eventState.RLock()
		eventChan = eventState.C
		errChan = eventState.errC
		eventState.RUnlock()
		err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
	}
	return err
}

func (eventState *eventMonitoringState) noListeners() bool {
	eventState.RLock()
	defer eventState.RUnlock()
	return len(eventState.listeners) == 0
}

func (eventState *eventMonitoringState) isEnabled() bool {
	eventState.RLock()
	defer eventState.RUnlock()
	return eventState.enabled
}

func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
	eventState.RLock()
	defer eventState.RUnlock()
	eventState.Add(1)
	defer eventState.Done()
	if eventState.enabled {
		if len(eventState.listeners) == 0 {
			eventState.errC <- ErrNoListeners
			return
		}

		for _, listener := range eventState.listeners {
			select {
			case listener <- event:
			default:
			}
		}
	}
}

func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
	eventState.Lock()
	defer eventState.Unlock()
	if atomic.LoadInt64(&eventState.lastSeen) < e.Time {
		atomic.StoreInt64(&eventState.lastSeen, e.Time)
	}
}

func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
	uri := "/events"
	if startTime != 0 {
		uri += fmt.Sprintf("?since=%d", startTime)
	}
	protocol := c.endpointURL.Scheme
	address := c.endpointURL.Path
	if protocol != "unix" && protocol != "npipe" {
		protocol = "tcp"
		address = c.endpointURL.Host
	}
	var dial net.Conn
	var err error
	if c.TLSConfig == nil {
		dial, err = c.Dialer.Dial(protocol, address)
	} else {
		netDialer, ok := c.Dialer.(*net.Dialer)
		if !ok {
			return ErrTLSNotSupported
		}
		dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig)
	}
	if err != nil {
		return err
	}
	conn := httputil.NewClientConn(dial, nil)
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}
	res, err := conn.Do(req)
	if err != nil {
		return err
	}
	go func(res *http.Response, conn *httputil.ClientConn) {
		defer conn.Close()
		defer res.Body.Close()
		decoder := json.NewDecoder(res.Body)
		for {
			var event APIEvents
			if err = decoder.Decode(&event); err != nil {
				if err == io.EOF || err == io.ErrUnexpectedEOF {
					c.eventMonitor.RLock()
					if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
						// Signal that we're exiting.
						eventChan <- EOFEvent
					}
					c.eventMonitor.RUnlock()
					break
				}
				errChan <- err
			}
			if event.Time == 0 {
				continue
			}
			transformEvent(&event)
			c.eventMonitor.RLock()
			if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
				eventChan <- &event
			}
			c.eventMonitor.RUnlock()
		}
	}(res, conn)
	return nil
}

// transformEvent takes an event and determines what version it is from
// then populates both versions of the event
func transformEvent(event *APIEvents) {
	// if event version is <= 1.21 there will be no Action and no Type
	if event.Action == "" && event.Type == "" {
		event.Action = event.Status
		event.Actor.ID = event.ID
		event.Actor.Attributes = map[string]string{}
		switch event.Status {
		case "delete", "import", "pull", "push", "tag", "untag":
			event.Type = "image"
		default:
			event.Type = "container"
			if event.From != "" {
				event.Actor.Attributes["image"] = event.From
			}
		}
	} else {
		if event.Status == "" {
			if event.Type == "image" || event.Type == "container" {
				event.Status = event.Action
			} else {
				// Because just the Status has been overloaded with different Types
				// if an event is not for an image or a container, we prepend the type
				// to avoid problems for people relying on actions being only for
				// images and containers
				event.Status = event.Type + ":" + event.Action
			}
		}
		if event.ID == "" {
			event.ID = event.Actor.ID
		}
		if event.From == "" {
			event.From = event.Actor.Attributes["image"]
		}
	}
}
<commit_msg>event: introduce a timeout on monitorEvents<commit_after>// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"net"
	"net/http"
	"net/http/httputil"
	"sync"
	"sync/atomic"
	"time"
)

// APIEvents represents events coming from the Docker API
// The fields in the Docker API changed in API version 1.22, and
// events for more than images and containers are now fired off.
// To maintain forward and backward compatibility, go-dockerclient
// replicates the event in both the new and old format as faithfully as possible.
//
// For events that only exist in 1.22 in later, `Status` is filled in as
// `"Type:Action"` instead of just `Action` to allow for older clients to
// differentiate and not break if they rely on the pre-1.22 Status types.
//
// The transformEvent method can be consulted for more information about how
// events are translated from new/old API formats
type APIEvents struct {
	// New API Fields in 1.22
	Action string   `json:"action,omitempty"`
	Type   string   `json:"type,omitempty"`
	Actor  APIActor `json:"actor,omitempty"`

	// Old API fields for < 1.22
	Status string `json:"status,omitempty"`
	ID     string `json:"id,omitempty"`
	From   string `json:"from,omitempty"`

	// Fields in both
	Time     int64 `json:"time,omitempty"`
	TimeNano int64 `json:"timeNano,omitempty"`
}

// APIActor represents an actor that accomplishes something for an event
type APIActor struct {
	ID         string            `json:"id,omitempty"`
	Attributes map[string]string `json:"attributes,omitempty"`
}

type eventMonitoringState struct {
	// `sync/atomic` expects the first word in an allocated struct to be 64-bit
	// aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
	lastSeen int64
	sync.RWMutex
	sync.WaitGroup
	enabled   bool
	C         chan *APIEvents
	errC      chan error
	listeners []chan<- *APIEvents
}

const (
	maxMonitorConnRetries = 5
	retryInitialWaitTime  = 10.
)

var (
	// ErrNoListeners is the error returned when no listeners are available
	// to receive an event.
	ErrNoListeners = errors.New("no listeners present to receive event")

	// ErrListenerAlreadyExists is the error returned when the listerner already
	// exists.
	ErrListenerAlreadyExists = errors.New("listener already exists for docker events")

	// ErrTLSNotSupported is the error returned when the client does not support
	// TLS (this applies to the Windows named pipe client).
	ErrTLSNotSupported = errors.New("tls not supported by this client")

	// EOFEvent is sent when the event listener receives an EOF error.
	EOFEvent = &APIEvents{
		Type:   "EOF",
		Status: "EOF",
	}
)

// AddEventListener adds a new listener to container events in the Docker API.
//
// The parameter is a channel through which events will be sent.
func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
	var err error
	if !c.eventMonitor.isEnabled() {
		err = c.eventMonitor.enableEventMonitoring(c)
		if err != nil {
			return err
		}
	}
	return c.eventMonitor.addListener(listener)
}

// RemoveEventListener removes a listener from the monitor.
func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
	err := c.eventMonitor.removeListener(listener)
	if err != nil {
		return err
	}
	if c.eventMonitor.listernersCount() == 0 {
		c.eventMonitor.disableEventMonitoring()
	}
	return nil
}

func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
	eventState.Lock()
	defer eventState.Unlock()
	if listenerExists(listener, &eventState.listeners) {
		return ErrListenerAlreadyExists
	}
	eventState.Add(1)
	eventState.listeners = append(eventState.listeners, listener)
	return nil
}

func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
	eventState.Lock()
	defer eventState.Unlock()
	if listenerExists(listener, &eventState.listeners) {
		var newListeners []chan<- *APIEvents
		for _, l := range eventState.listeners {
			if l != listener {
				newListeners = append(newListeners, l)
			}
		}
		eventState.listeners = newListeners
		eventState.Add(-1)
	}
	return nil
}

func (eventState *eventMonitoringState) closeListeners() {
	for _, l := range eventState.listeners {
		close(l)
		eventState.Add(-1)
	}
	eventState.listeners = nil
}

func (eventState *eventMonitoringState) listernersCount() int {
	eventState.RLock()
	defer eventState.RUnlock()
	return len(eventState.listeners)
}

func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
	for _, b := range *list {
		if b == a {
			return true
		}
	}
	return false
}

func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
	eventState.Lock()
	defer eventState.Unlock()
	if !eventState.enabled {
		eventState.enabled = true
		atomic.StoreInt64(&eventState.lastSeen, 0)
		eventState.C = make(chan *APIEvents, 100)
		eventState.errC = make(chan error, 1)
		go eventState.monitorEvents(c)
	}
	return nil
}

func (eventState *eventMonitoringState) disableEventMonitoring() error {
	eventState.Lock()
	defer eventState.Unlock()

	eventState.closeListeners()

	eventState.Wait()

	if eventState.enabled {
		eventState.enabled = false
		close(eventState.C)
		close(eventState.errC)
	}
	return nil
}

func (eventState *eventMonitoringState) monitorEvents(c *Client) {
	const (
		noListenersTimeout  = 5 * time.Second
		noListenersInterval = 10 * time.Millisecond
		noListenersMaxTries = noListenersTimeout / noListenersInterval
	)

	var err error
	for i := time.Duration(0); i < noListenersMaxTries && eventState.noListeners(); i++ {
		time.Sleep(10 * time.Millisecond)
	}

	if eventState.noListeners() {
		// terminate if no listener is available after 5 seconds.
		// Prevents goroutine leak when RemoveEventListener is called
		// right after AddEventListener.
		eventState.disableEventMonitoring()
		return
	}

	if err = eventState.connectWithRetry(c); err != nil {
		// terminate if connect failed
		eventState.disableEventMonitoring()
		return
	}
	for eventState.isEnabled() {
		timeout := time.After(100 * time.Millisecond)
		select {
		case ev, ok := <-eventState.C:
			if !ok {
				return
			}
			if ev == EOFEvent {
				eventState.disableEventMonitoring()
				return
			}
			eventState.updateLastSeen(ev)
			eventState.sendEvent(ev)
		case err = <-eventState.errC:
			if err == ErrNoListeners {
				eventState.disableEventMonitoring()
				return
			} else if err != nil {
				defer func() { go eventState.monitorEvents(c) }()
				return
			}
		case <-timeout:
			continue
		}
	}
}

func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
	var retries int
	eventState.RLock()
	eventChan := eventState.C
	errChan := eventState.errC
	eventState.RUnlock()
	err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
	for ; err != nil && retries < maxMonitorConnRetries; retries++ {
		waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
		time.Sleep(time.Duration(waitTime) * time.Millisecond)
		eventState.RLock()
		eventChan = eventState.C
		errChan = eventState.errC
		eventState.RUnlock()
		err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
	}
	return err
}

func (eventState *eventMonitoringState) noListeners() bool {
	eventState.RLock()
	defer eventState.RUnlock()
	return len(eventState.listeners) == 0
}

func (eventState *eventMonitoringState) isEnabled() bool {
	eventState.RLock()
	defer eventState.RUnlock()
	return eventState.enabled
}

func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
	eventState.RLock()
	defer eventState.RUnlock()
	eventState.Add(1)
	defer eventState.Done()
	if eventState.enabled {
		if len(eventState.listeners) == 0 {
			eventState.errC <- ErrNoListeners
			return
		}

		for _, listener := range eventState.listeners {
			select {
			case listener <- event:
			default:
			}
		}
	}
}

func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
	eventState.Lock()
	defer eventState.Unlock()
	if atomic.LoadInt64(&eventState.lastSeen) < e.Time {
		atomic.StoreInt64(&eventState.lastSeen, e.Time)
	}
}

func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
	uri := "/events"
	if startTime != 0 {
		uri += fmt.Sprintf("?since=%d", startTime)
	}
	protocol := c.endpointURL.Scheme
	address := c.endpointURL.Path
	if protocol != "unix" && protocol != "npipe" {
		protocol = "tcp"
		address = c.endpointURL.Host
	}
	var dial net.Conn
	var err error
	if c.TLSConfig == nil {
		dial, err = c.Dialer.Dial(protocol, address)
	} else {
		netDialer, ok := c.Dialer.(*net.Dialer)
		if !ok {
			return ErrTLSNotSupported
		}
		dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig)
	}
	if err != nil {
		return err
	}
	conn := httputil.NewClientConn(dial, nil)
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}
	res, err := conn.Do(req)
	if err != nil {
		return err
	}
	go func(res *http.Response, conn *httputil.ClientConn) {
		defer conn.Close()
		defer res.Body.Close()
		decoder := json.NewDecoder(res.Body)
		for {
			var event APIEvents
			if err = decoder.Decode(&event); err != nil {
				if err == io.EOF || err == io.ErrUnexpectedEOF {
					c.eventMonitor.RLock()
					if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
						// Signal that we're exiting.
						eventChan <- EOFEvent
					}
					c.eventMonitor.RUnlock()
					break
				}
				errChan <- err
			}
			if event.Time == 0 {
				continue
			}
			transformEvent(&event)
			c.eventMonitor.RLock()
			if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
				eventChan <- &event
			}
			c.eventMonitor.RUnlock()
		}
	}(res, conn)
	return nil
}

// transformEvent takes an event and determines what version it is from
// then populates both versions of the event
func transformEvent(event *APIEvents) {
	// if event version is <= 1.21 there will be no Action and no Type
	if event.Action == "" && event.Type == "" {
		event.Action = event.Status
		event.Actor.ID = event.ID
		event.Actor.Attributes = map[string]string{}
		switch event.Status {
		case "delete", "import", "pull", "push", "tag", "untag":
			event.Type = "image"
		default:
			event.Type = "container"
			if event.From != "" {
				event.Actor.Attributes["image"] = event.From
			}
		}
	} else {
		if event.Status == "" {
			if event.Type == "image" || event.Type == "container" {
				event.Status = event.Action
			} else {
				// Because just the Status has been overloaded with different Types
				// if an event is not for an image or a container, we prepend the type
				// to avoid problems for people relying on actions being only for
				// images and containers
				event.Status = event.Type + ":" + event.Action
			}
		}
		if event.ID == "" {
			event.ID = event.Actor.ID
		}
		if event.From == "" {
			event.From = event.Actor.Attributes["image"]
		}
	}
}
<|endoftext|>