<commit_before>package main

import (
	"database/sql"
	"fmt"

	"time"

	"github.com/pkg/errors"
)

func check3321(db *sql.DB) error {
	// 3.3.2.1 Entries in the WAREHOUSE and DISTRICT tables must satisfy the relationship:
	// W_YTD = sum (D_YTD)

	row := db.QueryRow(`
SELECT COUNT(*)
FROM warehouse
FULL OUTER JOIN
(SELECT d_w_id, SUM(d_ytd) as sum_d_ytd FROM district GROUP BY d_w_id)
ON (w_id = d_w_id)
WHERE w_ytd != sum_d_ytd
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func check3322(db *sql.DB) error {
	// Entries in the DISTRICT, ORDER, and NEW-ORDER tables must satisfy the relationship:
	// D_NEXT_O_ID - 1 = max(O_ID) = max(NO_O_ID)

	districtRows, err := db.Query("SELECT d_next_o_id FROM district ORDER BY d_w_id, d_id")
	if err != nil {
		return err
	}
	newOrderRows, err := db.Query(`
SELECT MAX(no_o_id) FROM tpcc.new_order GROUP BY no_d_id, no_w_id ORDER BY no_w_id, no_d_id
`)
	if err != nil {
		return err
	}
	orderRows, err := db.Query(`
SELECT MAX(o_id) FROM tpcc.order GROUP BY o_d_id, o_w_id ORDER BY o_w_id, o_d_id
`)
	if err != nil {
		return err
	}

	var district, newOrder, order float64
	var i int
	for ; districtRows.Next() && newOrderRows.Next() && orderRows.Next(); i++ {
		if err := districtRows.Scan(&district); err != nil {
			return err
		}
		if err := newOrderRows.Scan(&newOrder); err != nil {
			return err
		}
		if err := orderRows.Scan(&order); err != nil {
			return err
		}

		if (order != newOrder) || (order != (district - 1)) {
			return errors.Errorf("inequality at idx %d: order: %d, newOrder: %d, district-1: %d",
				i, order, newOrder, district-1)
		}
	}
	if districtRows.Next() || newOrderRows.Next() || orderRows.Next() {
		return errors.New("length mismatch between rows")
	}
	if err := districtRows.Close(); err != nil {
		return err
	}
	if err := newOrderRows.Close(); err != nil {
		return err
	}
	if err := orderRows.Close(); err != nil {
		return err
	}

	if i == 0 {
		return errors.Errorf("zero rows")
	}

	return nil
}

func check3323(db *sql.DB) error {
	// max(NO_O_ID) - min(NO_O_ID) + 1 = # of rows in new_order for each warehouse/district
	row := db.QueryRow(`
SELECT COUNT(*) FROM
 (SELECT MAX(no_o_id) - MIN(no_o_id) - COUNT(*) AS nod FROM new_order
  GROUP BY no_w_id, no_d_id)
 WHERE nod != -1
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func check3324(db *sql.DB) error {
	// SUM(O_OL_CNT) = [number of rows in the ORDER-LINE table for this district]

	leftRows, err := db.Query(`
SELECT SUM(o_ol_cnt) FROM tpcc.order GROUP BY o_w_id, o_d_id ORDER BY o_w_id, o_d_id
`)
	if err != nil {
		return err
	}
	rightRows, err := db.Query(`
SELECT COUNT(*) FROM tpcc.order_line GROUP BY ol_w_id, ol_d_id ORDER BY ol_w_id, ol_d_id
`)
	if err != nil {
		return err
	}
	var i int
	var left, right int64
	for ; leftRows.Next() && rightRows.Next(); i++ {
		if err := leftRows.Scan(&left); err != nil {
			return err
		}
		if err := rightRows.Scan(&right); err != nil {
			return err
		}
		if left != right {
			return errors.Errorf("order.SUM(o_ol_cnt): %d != order_line.count(*): %d", left, right)
		}
	}
	if i == 0 {
		return errors.Errorf("0 rows returned")
	}
	if leftRows.Next() || rightRows.Next() {
		return errors.Errorf("length of order.SUM(o_ol_cnt) != order_line.count(*)")
	}

	if err := leftRows.Close(); err != nil {
		return err
	}
	return rightRows.Close()
}

func check3325(db *sql.DB) error {
	// We want the symmetric difference between the sets:
	// (SELECT no_w_id, no_d_id, no_o_id FROM tpcc.new_order)
	// (SELECT o_w_id, o_d_id, o_id FROM tpcc.order@primary WHERE o_carrier_id IS NULL)
	// We achieve this by two EXCEPT ALL queries.

	firstQuery, err := db.Query(`
(SELECT no_w_id, no_d_id, no_o_id FROM tpcc.new_order)
EXCEPT ALL
(SELECT o_w_id, o_d_id, o_id FROM tpcc.order@primary WHERE o_carrier_id IS NULL)`)
	if err != nil {
		return err
	}
	secondQuery, err := db.Query(`
(SELECT o_w_id, o_d_id, o_id FROM tpcc.order@primary WHERE o_carrier_id IS NULL)
EXCEPT ALL
(SELECT no_w_id, no_d_id, no_o_id FROM tpcc.new_order)`)
	if err != nil {
		return err
	}

	if firstQuery.Next() {
		return errors.Errorf("left EXCEPT right returned nonzero results.")
	}

	if secondQuery.Next() {
		return errors.Errorf("right EXCEPT left returned nonzero results.")
	}

	if err := firstQuery.Close(); err != nil {
		return err
	}
	return secondQuery.Close()
}

func check3326(db *sql.DB) error {
	firstQuery, err := db.Query(`
(SELECT o_w_id, o_d_id, o_id, o_ol_cnt FROM tpcc.order
  ORDER BY o_w_id, o_d_id, o_id DESC)
EXCEPT ALL
(SELECT ol_w_id, ol_d_id, ol_o_id, COUNT(*) FROM tpcc.order_line
  GROUP BY (ol_w_id, ol_d_id, ol_o_id)
  ORDER BY ol_w_id, ol_d_id, ol_o_id DESC)`)
	if err != nil {
		return err
	}
	secondQuery, err := db.Query(`
(SELECT ol_w_id, ol_d_id, ol_o_id, COUNT(*) FROM tpcc.order_line
  GROUP BY (ol_w_id, ol_d_id, ol_o_id) ORDER BY ol_w_id, ol_d_id, ol_o_id DESC)
EXCEPT ALL
(SELECT o_w_id, o_d_id, o_id, o_ol_cnt FROM tpcc.order
  ORDER BY o_w_id, o_d_id, o_id DESC)`)
	if err != nil {
		return err
	}

	if firstQuery.Next() {
		return errors.Errorf("left EXCEPT right returned nonzero results")
	}

	if secondQuery.Next() {
		return errors.Errorf("right EXCEPT left returned nonzero results")
	}

	if err := firstQuery.Close(); err != nil {
		return err
	}
	return secondQuery.Close()
}

func checkConsistency(db *sql.DB) bool {
	type check struct {
		name      string
		f         func(db *sql.DB) error
		expensive bool
	}

	checks := []check{
		{"3.3.2.1", check3321, false},
		{"3.3.2.2", check3322, false},
		{"3.3.2.3", check3323, false},
		{"3.3.2.4", check3324, false},
		{"3.3.2.5", check3325, false},
		{"3.3.2.6", check3326, true},
	}
	var errorEncountered bool

	// TODO(arjun): We should run each test in a single transaction as currently
	// we have to shut down load before running the checks.

	for _, check := range checks {
		if check.expensive && !*expensive {
			continue
		}
		start := time.Now()
		fmt.Printf(check.name)
		if err := check.f(db); err != nil {
			fmt.Printf(": error encountered: '%+v'", err)
			errorEncountered = true
		} else {
			fmt.Printf(" passed")
		}

		fmt.Printf(" after %f seconds.\n", time.Since(start).Seconds())
	}
	return errorEncountered
}
<commit_msg>tpcc: add more checks<commit_after>package main

import (
	"database/sql"
	"fmt"

	"time"

	"github.com/pkg/errors"
)

func check3321(db *sql.DB) error {
	// 3.3.2.1 Entries in the WAREHOUSE and DISTRICT tables must satisfy the relationship:
	// W_YTD = sum (D_YTD)

	row := db.QueryRow(`
SELECT COUNT(*)
FROM warehouse
FULL OUTER JOIN
(SELECT d_w_id, SUM(d_ytd) as sum_d_ytd FROM district GROUP BY d_w_id)
ON (w_id = d_w_id)
WHERE w_ytd != sum_d_ytd
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func check3322(db *sql.DB) error {
	// Entries in the DISTRICT, ORDER, and NEW-ORDER tables must satisfy the relationship:
	// D_NEXT_O_ID - 1 = max(O_ID) = max(NO_O_ID)

	districtRows, err := db.Query("SELECT d_next_o_id FROM district ORDER BY d_w_id, d_id")
	if err != nil {
		return err
	}
	newOrderRows, err := db.Query(`
SELECT MAX(no_o_id) FROM tpcc.new_order GROUP BY no_d_id, no_w_id ORDER BY no_w_id, no_d_id
`)
	if err != nil {
		return err
	}
	orderRows, err := db.Query(`
SELECT MAX(o_id) FROM tpcc.order GROUP BY o_d_id, o_w_id ORDER BY o_w_id, o_d_id
`)
	if err != nil {
		return err
	}

	var district, newOrder, order float64
	var i int
	for ; districtRows.Next() && newOrderRows.Next() && orderRows.Next(); i++ {
		if err := districtRows.Scan(&district); err != nil {
			return err
		}
		if err := newOrderRows.Scan(&newOrder); err != nil {
			return err
		}
		if err := orderRows.Scan(&order); err != nil {
			return err
		}

		if (order != newOrder) || (order != (district - 1)) {
			return errors.Errorf("inequality at idx %d: order: %d, newOrder: %d, district-1: %d",
				i, order, newOrder, district-1)
		}
	}
	if districtRows.Next() || newOrderRows.Next() || orderRows.Next() {
		return errors.New("length mismatch between rows")
	}
	if err := districtRows.Close(); err != nil {
		return err
	}
	if err := newOrderRows.Close(); err != nil {
		return err
	}
	if err := orderRows.Close(); err != nil {
		return err
	}

	if i == 0 {
		return errors.Errorf("zero rows")
	}

	return nil
}

func check3323(db *sql.DB) error {
	// max(NO_O_ID) - min(NO_O_ID) + 1 = # of rows in new_order for each warehouse/district
	row := db.QueryRow(`
SELECT COUNT(*) FROM
 (SELECT MAX(no_o_id) - MIN(no_o_id) - COUNT(*) AS nod FROM new_order
  GROUP BY no_w_id, no_d_id)
 WHERE nod != -1
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func check3324(db *sql.DB) error {
	// SUM(O_OL_CNT) = [number of rows in the ORDER-LINE table for this district]

	leftRows, err := db.Query(`
SELECT SUM(o_ol_cnt) FROM tpcc.order GROUP BY o_w_id, o_d_id ORDER BY o_w_id, o_d_id
`)
	if err != nil {
		return err
	}
	rightRows, err := db.Query(`
SELECT COUNT(*) FROM tpcc.order_line GROUP BY ol_w_id, ol_d_id ORDER BY ol_w_id, ol_d_id
`)
	if err != nil {
		return err
	}
	var i int
	var left, right int64
	for ; leftRows.Next() && rightRows.Next(); i++ {
		if err := leftRows.Scan(&left); err != nil {
			return err
		}
		if err := rightRows.Scan(&right); err != nil {
			return err
		}
		if left != right {
			return errors.Errorf("order.SUM(o_ol_cnt): %d != order_line.count(*): %d", left, right)
		}
	}
	if i == 0 {
		return errors.Errorf("0 rows returned")
	}
	if leftRows.Next() || rightRows.Next() {
		return errors.Errorf("length of order.SUM(o_ol_cnt) != order_line.count(*)")
	}

	if err := leftRows.Close(); err != nil {
		return err
	}
	return rightRows.Close()
}

func check3325(db *sql.DB) error {
	// We want the symmetric difference between the sets:
	// (SELECT no_w_id, no_d_id, no_o_id FROM tpcc.new_order)
	// (SELECT o_w_id, o_d_id, o_id FROM tpcc.order@primary WHERE o_carrier_id IS NULL)
	// We achieve this by two EXCEPT ALL queries.

	firstQuery, err := db.Query(`
(SELECT no_w_id, no_d_id, no_o_id FROM tpcc.new_order)
EXCEPT ALL
(SELECT o_w_id, o_d_id, o_id FROM tpcc.order@primary WHERE o_carrier_id IS NULL)`)
	if err != nil {
		return err
	}
	secondQuery, err := db.Query(`
(SELECT o_w_id, o_d_id, o_id FROM tpcc.order@primary WHERE o_carrier_id IS NULL)
EXCEPT ALL
(SELECT no_w_id, no_d_id, no_o_id FROM tpcc.new_order)`)
	if err != nil {
		return err
	}

	if firstQuery.Next() {
		return errors.Errorf("left EXCEPT right returned nonzero results.")
	}

	if secondQuery.Next() {
		return errors.Errorf("right EXCEPT left returned nonzero results.")
	}

	if err := firstQuery.Close(); err != nil {
		return err
	}
	return secondQuery.Close()
}

func check3326(db *sql.DB) error {
	// For any row in the ORDER table, O_OL_CNT must equal the number of rows
	// in the ORDER-LINE table for the corresponding order defined by
	// (O_W_ID, O_D_ID, O_ID) = (OL_W_ID, OL_D_ID, OL_O_ID).

	firstQuery, err := db.Query(`
(SELECT o_w_id, o_d_id, o_id, o_ol_cnt FROM tpcc.order
  ORDER BY o_w_id, o_d_id, o_id DESC)
EXCEPT ALL
(SELECT ol_w_id, ol_d_id, ol_o_id, COUNT(*) FROM tpcc.order_line
  GROUP BY (ol_w_id, ol_d_id, ol_o_id)
  ORDER BY ol_w_id, ol_d_id, ol_o_id DESC)`)
	if err != nil {
		return err
	}
	secondQuery, err := db.Query(`
(SELECT ol_w_id, ol_d_id, ol_o_id, COUNT(*) FROM tpcc.order_line
  GROUP BY (ol_w_id, ol_d_id, ol_o_id) ORDER BY ol_w_id, ol_d_id, ol_o_id DESC)
EXCEPT ALL
(SELECT o_w_id, o_d_id, o_id, o_ol_cnt FROM tpcc.order
  ORDER BY o_w_id, o_d_id, o_id DESC)`)
	if err != nil {
		return err
	}

	if firstQuery.Next() {
		return errors.Errorf("left EXCEPT right returned nonzero results")
	}

	if secondQuery.Next() {
		return errors.Errorf("right EXCEPT left returned nonzero results")
	}

	if err := firstQuery.Close(); err != nil {
		return err
	}
	return secondQuery.Close()
}

func check3327(db *sql.DB) error {
	// For any row in the ORDER-LINE table, OL_DELIVERY_D is set to a null
	// date/time if and only if the corresponding row in the ORDER table defined
	// by (O_W_ID, O_D_ID, O_ID) = (OL_W_ID, OL_D_ID, OL_O_ID) has
	// O_CARRIER_ID set to a null value.

	row := db.QueryRow(`
SELECT COUNT(*) FROM
  (SELECT o_w_id, o_d_id, o_id FROM "order" WHERE o_carrier_id IS NULL)
FULL OUTER JOIN
  (SELECT ol_w_id, ol_d_id, ol_o_id FROM order_line WHERE ol_delivery_d IS NULL)
ON (ol_w_id = o_w_id AND ol_d_id = o_d_id AND ol_o_id = o_id)
WHERE ol_o_id IS NULL OR o_id IS NULL
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func check3328(db *sql.DB) error {
	// Entries in the WAREHOUSE and HISTORY tables must satisfy the relationship:
	// W_YTD = SUM(H_AMOUNT) for each warehouse defined by (W_ID = H _W_ID).

	row := db.QueryRow(`
SELECT COUNT(*) FROM
  (SELECT w_id, w_ytd, sum FROM warehouse
  JOIN
  (SELECT h_w_id, SUM(h_amount) FROM history GROUP BY h_w_id)
  ON w_id = h_w_id
  WHERE w_ytd != sum
  )
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func check3329(db *sql.DB) error {
	// Entries in the DISTRICT and HISTORY tables must satisfy the relationship:
	// D_YTD=SUM(H_AMOUNT) for each district defined by (D_W_ID,D_ID)=(H_W_ID,H_D_ID)

	row := db.QueryRow(`
SELECT COUNT(*) FROM
  (SELECT d_id, d_ytd, sum FROM district
  JOIN
  (SELECT h_w_id, h_d_id, SUM(h_amount) FROM history GROUP BY (h_w_id, h_d_id))
  ON d_id = h_d_id AND d_w_id = h_w_id
  WHERE d_ytd != sum
  )
`)

	var i int
	if err := row.Scan(&i); err != nil {
		return err
	}

	if i != 0 {
		return errors.Errorf("%d rows returned, expected zero", i)
	}

	return nil
}

func checkConsistency(db *sql.DB) bool {
	type check struct {
		name      string
		f         func(db *sql.DB) error
		expensive bool
	}

	checks := []check{
		{"3.3.2.1 ", check3321, false},
		{"3.3.2.2 ", check3322, false},
		{"3.3.2.3 ", check3323, false},
		{"3.3.2.4 ", check3324, false},
		{"3.3.2.5 ", check3325, false},
		{"3.3.2.6 ", check3326, true},
		{"3.3.2.7 ", check3327, false},
		{"3.3.2.8 ", check3327, false},
		{"3.3.2.9 ", check3327, false},
	}
	var errorEncountered bool

	// TODO(arjun): We should run each test in a single transaction as currently
	// we have to shut down load before running the checks.

	for _, check := range checks {
		if check.expensive && !*expensive {
			continue
		}
		start := time.Now()
		fmt.Printf(check.name)
		if err := check.f(db); err != nil {
			fmt.Printf(": error encountered: '%+v'", err)
			errorEncountered = true
		} else {
			fmt.Printf(" passed")
		}

		fmt.Printf(" after %f seconds.\n", time.Since(start).Seconds())
	}
	return errorEncountered
}
<|endoftext|>


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

package bloom

import (
	"testing"
)

func (f Filter) String() string {
	s := make([]byte, 8*len(f))
	for i, x := range f {
		for j := 0; j < 8; j++ {
			if x&(1<<uint(j)) != 0 {
				s[8*i+j] = '1'
			} else {
				s[8*i+j] = '.'
			}
		}
	}
	return string(s)
}

func TestSmallBloomFilter(t *testing.T) {
	f := NewFilter(nil, [][]byte{
		[]byte("hello"),
		[]byte("world"),
	}, 10)
	got := f.String()
	// The magic want string comes from running the C++ leveldb code's bloom_test.cc.
	want := "1...1.........1.........1.....1...1...1.....1.........1.....1....11....."
	if got != want {
		t.Fatalf("bits:\ngot  %q\nwant %q", got, want)
	}

	m := map[string]bool{
		"hello": true,
		"world": true,
		"x":     false,
		"foo":   false,
	}
	for k, want := range m {
		got := f.MayContain([]byte(k))
		if got != want {
			t.Errorf("MayContain: k=%q: got %v, want %v", k, got, want)
		}
	}
}

func TestBloomFilter(t *testing.T) {
	nextLength := func(x int) int {
		if x < 10 {
			return x + 1
		}
		if x < 100 {
			return x + 10
		}
		if x < 1000 {
			return x + 100
		}
		return x + 1000
	}
	le32 := func(i int) []byte {
		b := make([]byte, 4)
		b[0] = uint8(uint32(i) >> 0)
		b[1] = uint8(uint32(i) >> 8)
		b[2] = uint8(uint32(i) >> 16)
		b[3] = uint8(uint32(i) >> 24)
		return b
	}

	nMediocreFilters, nGoodFilters := 0, 0
loop:
	for length := 1; length <= 10000; length = nextLength(length) {
		keys := make([][]byte, 0, length)
		for i := 0; i < length; i++ {
			keys = append(keys, le32(i))
		}
		f := NewFilter(nil, keys, 10)

		if len(f) > (length*10/8)+40 {
			t.Errorf("length=%d: len(f)=%d is too large", length, len(f))
			continue
		}

		// All added keys must match.
		for _, key := range keys {
			if !f.MayContain(key) {
				t.Errorf("length=%d: did not contain key %q", length, key)
				continue loop
			}
		}

		// Check false positive rate.
		nFalsePositive := 0
		for i := 0; i < 10000; i++ {
			if f.MayContain(le32(1e9 + i)) {
				nFalsePositive++
			}
		}
		if nFalsePositive > 0.02*10000 {
			t.Errorf("length=%d: %d false positives in 10000", length, nFalsePositive)
			continue
		}
		if nFalsePositive > 0.0125*10000 {
			nMediocreFilters++
		} else {
			nGoodFilters++
		}
	}

	if nMediocreFilters > nGoodFilters/5 {
		t.Errorf("%d mediocre filters but only %d good filters", nMediocreFilters, nGoodFilters)
	}
}

func TestHash(t *testing.T) {
	// The magic want numbers come from running the C++ leveldb code in hash.cc.
	testCases := []struct {
		s    string
		want uint32
	}{
		{"", 0xbc9f1d34},
		{"g", 0xd04a8bda},
		{"go", 0x3e0b0745},
		{"gop", 0x0c326610},
		{"goph", 0x8c9d6390},
		{"gophe", 0x9bfd4b0a},
		{"gopher", 0xa78edc7c},
		{"I always dreamed it would end this way.", 0xef6dc642},
	}
	for _, tc := range testCases {
		if got := hash([]byte(tc.s)); got != tc.want {
			t.Errorf("s=%q: got 0x%08x, want 0x%08x", tc.s, got, tc.want)
		}
	}
}
<commit_msg>leveldb: get the gosh-darned quote right.<commit_after>// Copyright 2013 The LevelDB-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bloom

import (
	"testing"
)

func (f Filter) String() string {
	s := make([]byte, 8*len(f))
	for i, x := range f {
		for j := 0; j < 8; j++ {
			if x&(1<<uint(j)) != 0 {
				s[8*i+j] = '1'
			} else {
				s[8*i+j] = '.'
			}
		}
	}
	return string(s)
}

func TestSmallBloomFilter(t *testing.T) {
	f := NewFilter(nil, [][]byte{
		[]byte("hello"),
		[]byte("world"),
	}, 10)
	got := f.String()
	// The magic want string comes from running the C++ leveldb code's bloom_test.cc.
	want := "1...1.........1.........1.....1...1...1.....1.........1.....1....11....."
	if got != want {
		t.Fatalf("bits:\ngot  %q\nwant %q", got, want)
	}

	m := map[string]bool{
		"hello": true,
		"world": true,
		"x":     false,
		"foo":   false,
	}
	for k, want := range m {
		got := f.MayContain([]byte(k))
		if got != want {
			t.Errorf("MayContain: k=%q: got %v, want %v", k, got, want)
		}
	}
}

func TestBloomFilter(t *testing.T) {
	nextLength := func(x int) int {
		if x < 10 {
			return x + 1
		}
		if x < 100 {
			return x + 10
		}
		if x < 1000 {
			return x + 100
		}
		return x + 1000
	}
	le32 := func(i int) []byte {
		b := make([]byte, 4)
		b[0] = uint8(uint32(i) >> 0)
		b[1] = uint8(uint32(i) >> 8)
		b[2] = uint8(uint32(i) >> 16)
		b[3] = uint8(uint32(i) >> 24)
		return b
	}

	nMediocreFilters, nGoodFilters := 0, 0
loop:
	for length := 1; length <= 10000; length = nextLength(length) {
		keys := make([][]byte, 0, length)
		for i := 0; i < length; i++ {
			keys = append(keys, le32(i))
		}
		f := NewFilter(nil, keys, 10)

		if len(f) > (length*10/8)+40 {
			t.Errorf("length=%d: len(f)=%d is too large", length, len(f))
			continue
		}

		// All added keys must match.
		for _, key := range keys {
			if !f.MayContain(key) {
				t.Errorf("length=%d: did not contain key %q", length, key)
				continue loop
			}
		}

		// Check false positive rate.
		nFalsePositive := 0
		for i := 0; i < 10000; i++ {
			if f.MayContain(le32(1e9 + i)) {
				nFalsePositive++
			}
		}
		if nFalsePositive > 0.02*10000 {
			t.Errorf("length=%d: %d false positives in 10000", length, nFalsePositive)
			continue
		}
		if nFalsePositive > 0.0125*10000 {
			nMediocreFilters++
		} else {
			nGoodFilters++
		}
	}

	if nMediocreFilters > nGoodFilters/5 {
		t.Errorf("%d mediocre filters but only %d good filters", nMediocreFilters, nGoodFilters)
	}
}

func TestHash(t *testing.T) {
	// The magic want numbers come from running the C++ leveldb code in hash.cc.
	testCases := []struct {
		s    string
		want uint32
	}{
		{"", 0xbc9f1d34},
		{"g", 0xd04a8bda},
		{"go", 0x3e0b0745},
		{"gop", 0x0c326610},
		{"goph", 0x8c9d6390},
		{"gophe", 0x9bfd4b0a},
		{"gopher", 0xa78edc7c},
		{"I had a dream it would end this way.", 0xe14a9db9},
	}
	for _, tc := range testCases {
		if got := hash([]byte(tc.s)); got != tc.want {
			t.Errorf("s=%q: got 0x%08x, want 0x%08x", tc.s, got, tc.want)
		}
	}
}
<|endoftext|>


<commit_before>package data

import (
	"github.com/UHERO/rest-api/models"
)

func (r *SeriesRepository) GetSeriesBySearchText(searchText string) (seriesList []models.DataPortalSeries, err error) {
	rows, err := r.DB.Query(`SELECT series.id, name, description, frequency, seasonally_adjusted,
	measurements.units_label, measurements.units_label_short, measurements.data_portal_name, measurements.percent, measurements.real,
	fips, SUBSTRING_INDEX(SUBSTR(series.name, LOCATE('@', series.name) + 1), '.', 1) as shandle, display_name_short
	FROM series LEFT JOIN geographies ON name LIKE CONCAT('%@', handle, '.%')
	LEFT JOIN measurements ON measurements.id = series.measurement_id
	WHERE NOT restricted AND
	((MATCH(name, description, dataPortalName)
	AGAINST(? IN NATURAL LANGUAGE MODE))
	OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%')) LIMIT 50;`, searchText, searchText)
	if err != nil {
		return
	}
	for rows.Next() {
		dataPortalSeries, scanErr := getNextSeriesFromRows(rows)
		if scanErr != nil {
			return seriesList, scanErr
		}
		seriesList = append(seriesList, dataPortalSeries)
	}
	return
}

func (r *SeriesRepository) GetSearchSummary(searchText string) (searchSummary models.SearchSummary, err error) {
	searchSummary.SearchText = searchText
	rows, err := r.DB.Query(`SELECT geographies.fips, geographies.display_name_short, geofreq.geo, geofreq.freq
FROM (SELECT MAX(SUBSTRING_INDEX(SUBSTR(name, LOCATE('@', name) + 1), '.', 1)) as geo,
       MAX(RIGHT(name, 1)) as freq
FROM (SELECT name FROM series WHERE NOT restricted AND
                                    (MATCH(name, description, dataPortalName)
                                    AGAINST(? IN NATURAL LANGUAGE MODE))
                                    OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%')) AS s
GROUP BY SUBSTR(name, LOCATE('@', name) + 1) ORDER BY COUNT(*) DESC) as geofreq
LEFT JOIN geographies ON geographies.handle = geofreq.geo;`, searchText, searchText)
	if err != nil {
		return
	}
	legacyGeoFreqs := map[string][]string{}
	legacyFreqGeos := map[string][]string{}
	geoFreqs := map[string][]models.FrequencyResult{}
	geoByHandle := map[string]models.DataPortalGeography{}
	freqGeos := map[string][]models.DataPortalGeography{}
	freqByHandle := map[string]models.FrequencyResult{}

	for rows.Next() {
		scangeo := models.Geography{}
		frequency := models.FrequencyResult{}
		err = rows.Scan(
			&scangeo.FIPS,
			&scangeo.Name,
			&scangeo.Handle,
			&frequency.Freq,
		)
		geography := models.DataPortalGeography{Handle: scangeo.Handle}
		if scangeo.FIPS.Valid {
			geography.FIPS = scangeo.FIPS.String
		}
		if scangeo.Name.Valid {
			geography.Name = scangeo.Name.String
		}
		frequency.Label = freqLabel[frequency.Freq]
		// set the default as the first in the sorted list
		if searchSummary.DefaultGeoFreq == nil {
			searchSummary.DefaultGeoFreq = &models.GeographyFrequency{
				Geography: geography,
				Frequency: frequency,
			}
		}
		// update the freq and geo maps
		geoByHandle[geography.Handle] = geography
		freqByHandle[frequency.Freq] = frequency
		// add to the geoFreqs and freqGeos maps
		geoFreqs[geography.Handle] = append(geoFreqs[geography.Handle], frequency)
		freqGeos[frequency.Freq] = append(freqGeos[frequency.Freq], geography)

		legacyGeoFreqs[geography.Handle] = append(legacyGeoFreqs[geography.Handle], frequency.Freq)
		legacyFreqGeos[frequency.Freq] = append(legacyFreqGeos[frequency.Freq], geography.Handle)
	}

	geoFreqsResult := []models.GeographyFrequencies{}
	for geo, freqs := range geoFreqs {
		geoFreqsResult = append(geoFreqsResult, models.GeographyFrequencies{
			DataPortalGeography: geoByHandle[geo],
			Frequencies: freqs,
		})
	}

	freqGeosResult := []models.FrequencyGeographies{}
	for freq, geos := range freqGeos {
		freqGeosResult = append(freqGeosResult, models.FrequencyGeographies{
			FrequencyResult: freqByHandle[freq],
			Geographies: geos,
		})
	}

	searchSummary.GeoFreqs = legacyGeoFreqs
	searchSummary.FreqGeos = legacyFreqGeos
	searchSummary.GeographyFrequencies = &geoFreqsResult
	searchSummary.FrequencyGeographies = &freqGeosResult
	return
}

func (r *SeriesRepository) GetSearchResultsByGeoAndFreq(searchText string, geo string, freq string) (seriesList []models.DataPortalSeries, err error) {
	rows, err := r.DB.Query(`SELECT series.id, name, description, frequency, seasonally_adjusted,
	measurements.units_label, measurements.units_label_short, measurements.data_portal_name, measurements.percent, measurements.real,
	fips, ?, display_name_short
	FROM series
	LEFT JOIN geographies ON handle LIKE ?
	LEFT JOIN measurements ON measurements.id = series.measurement_id
	WHERE NOT restricted AND
	((MATCH(name, description, dataPortalName)
	AGAINST(? IN NATURAL LANGUAGE MODE))
	OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%'))
	AND LOWER(name) LIKE CONCAT('%@', LOWER(?), '.', LOWER(?)) LIMIT 50;`,
		geo,
		geo,
		searchText,
		searchText,
		geo,
		freq,
	)
	if err != nil {
		return
	}
	for rows.Next() {
		dataPortalSeries, scanErr := getNextSeriesFromRows(rows)
		if scanErr != nil {
			return seriesList, scanErr
		}
		seriesList = append(seriesList, dataPortalSeries)
	}
	return
}

func (r *SeriesRepository) GetInflatedSearchResultsByGeoAndFreq(
	searchText string,
	geo string,
	freq string,
) (seriesList []models.InflatedSeries, err error) {
	rows, err := r.DB.Query(`SELECT series.id, name, description, frequency, seasonally_adjusted,
	measurements.units_label, measurements.units_label_short, measurements.data_portal_name, measurements.percent, measurements.real,
	fips, ?, display_name_short
	FROM series
	LEFT JOIN geographies ON handle LIKE ?
	LEFT JOIN measurements ON measurements.id = series.measurement_id
	WHERE NOT series.restricted AND
	((MATCH(name, description, dataPortalName)
	AGAINST(? IN NATURAL LANGUAGE MODE))
	OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%'))
	AND LOWER(name) LIKE CONCAT('%@', LOWER(?), '.', LOWER(?)) LIMIT 50;`,
		geo,
		geo,
		searchText,
		searchText,
		geo,
		freq,
	)
	if err != nil {
		return
	}
	for rows.Next() {
		dataPortalSeries, scanErr := getNextSeriesFromRows(rows)
		if scanErr != nil {
			return seriesList, scanErr
		}
		seriesObservations, scanErr := r.GetSeriesObservations(dataPortalSeries.Id)
		if scanErr != nil {
			return seriesList, scanErr
		}
		inflatedSeries := models.InflatedSeries{dataPortalSeries, seriesObservations}
		seriesList = append(seriesList, inflatedSeries)
	}
	return
}
<commit_msg>added freq sorting to search endpoint<commit_after>package data

import (
	"github.com/UHERO/rest-api/models"
	"sort"
)

func (r *SeriesRepository) GetSeriesBySearchText(searchText string) (seriesList []models.DataPortalSeries, err error) {
	rows, err := r.DB.Query(`SELECT series.id, name, description, frequency, seasonally_adjusted,
	measurements.units_label, measurements.units_label_short, measurements.data_portal_name, measurements.percent, measurements.real,
	fips, SUBSTRING_INDEX(SUBSTR(series.name, LOCATE('@', series.name) + 1), '.', 1) as shandle, display_name_short
	FROM series LEFT JOIN geographies ON name LIKE CONCAT('%@', handle, '.%')
	LEFT JOIN measurements ON measurements.id = series.measurement_id
	WHERE NOT restricted AND
	((MATCH(name, description, dataPortalName)
	AGAINST(? IN NATURAL LANGUAGE MODE))
	OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%')) LIMIT 50;`, searchText, searchText)
	if err != nil {
		return
	}
	for rows.Next() {
		dataPortalSeries, scanErr := getNextSeriesFromRows(rows)
		if scanErr != nil {
			return seriesList, scanErr
		}
		seriesList = append(seriesList, dataPortalSeries)
	}
	return
}

func (r *SeriesRepository) GetSearchSummary(searchText string) (searchSummary models.SearchSummary, err error) {
	searchSummary.SearchText = searchText
	rows, err := r.DB.Query(`SELECT geographies.fips, geographies.display_name_short, geofreq.geo, geofreq.freq
FROM (SELECT MAX(SUBSTRING_INDEX(SUBSTR(name, LOCATE('@', name) + 1), '.', 1)) as geo,
       MAX(RIGHT(name, 1)) as freq
FROM (SELECT name FROM series WHERE NOT restricted AND
                                    (MATCH(name, description, dataPortalName)
                                    AGAINST(? IN NATURAL LANGUAGE MODE))
                                    OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%')) AS s
GROUP BY SUBSTR(name, LOCATE('@', name) + 1) ORDER BY COUNT(*) DESC) as geofreq
LEFT JOIN geographies ON geographies.handle = geofreq.geo;`, searchText, searchText)
	if err != nil {
		return
	}
	legacyGeoFreqs := map[string][]string{}
	legacyFreqGeos := map[string][]string{}
	geoFreqs := map[string][]models.FrequencyResult{}
	geoByHandle := map[string]models.DataPortalGeography{}
	freqGeos := map[string][]models.DataPortalGeography{}
	freqByHandle := map[string]models.FrequencyResult{}

	for rows.Next() {
		scangeo := models.Geography{}
		frequency := models.FrequencyResult{}
		err = rows.Scan(
			&scangeo.FIPS,
			&scangeo.Name,
			&scangeo.Handle,
			&frequency.Freq,
		)
		geography := models.DataPortalGeography{Handle: scangeo.Handle}
		if scangeo.FIPS.Valid {
			geography.FIPS = scangeo.FIPS.String
		}
		if scangeo.Name.Valid {
			geography.Name = scangeo.Name.String
		}
		frequency.Label = freqLabel[frequency.Freq]
		// set the default as the first in the sorted list
		if searchSummary.DefaultGeoFreq == nil {
			searchSummary.DefaultGeoFreq = &models.GeographyFrequency{
				Geography: geography,
				Frequency: frequency,
			}
		}
		// update the freq and geo maps
		geoByHandle[geography.Handle] = geography
		freqByHandle[frequency.Freq] = frequency
		// add to the geoFreqs and freqGeos maps
		geoFreqs[geography.Handle] = append(geoFreqs[geography.Handle], frequency)
		freqGeos[frequency.Freq] = append(freqGeos[frequency.Freq], geography)

		legacyGeoFreqs[geography.Handle] = append(legacyGeoFreqs[geography.Handle], frequency.Freq)
		legacyFreqGeos[frequency.Freq] = append(legacyFreqGeos[frequency.Freq], geography.Handle)
	}

	geoFreqsResult := []models.GeographyFrequencies{}
	for geo, freqs := range geoFreqs {
		sort.Sort(models.ByFrequency(freqs))
		geoFreqsResult = append(geoFreqsResult, models.GeographyFrequencies{
			DataPortalGeography: geoByHandle[geo],
			Frequencies: freqs,
		})
	}

	freqGeosResult := []models.FrequencyGeographies{}
	for _, freq := range models.FreqOrder {
		if val, ok := freqByHandle[freq]; ok {
			freqGeosResult = append(freqGeosResult, models.FrequencyGeographies{
				FrequencyResult: val,
				Geographies: freqGeos[freq],
			})
		}
	}

	searchSummary.GeoFreqs = legacyGeoFreqs
	searchSummary.FreqGeos = legacyFreqGeos
	searchSummary.GeographyFrequencies = &geoFreqsResult
	searchSummary.FrequencyGeographies = &freqGeosResult
	return
}

func (r *SeriesRepository) GetSearchResultsByGeoAndFreq(searchText string, geo string, freq string) (seriesList []models.DataPortalSeries, err error) {
	rows, err := r.DB.Query(`SELECT series.id, name, description, frequency, seasonally_adjusted,
	measurements.units_label, measurements.units_label_short, measurements.data_portal_name, measurements.percent, measurements.real,
	fips, ?, display_name_short
	FROM series
	LEFT JOIN geographies ON handle LIKE ?
	LEFT JOIN measurements ON measurements.id = series.measurement_id
	WHERE NOT restricted AND
	((MATCH(name, description, dataPortalName)
	AGAINST(? IN NATURAL LANGUAGE MODE))
	OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%'))
	AND LOWER(name) LIKE CONCAT('%@', LOWER(?), '.', LOWER(?)) LIMIT 50;`,
		geo,
		geo,
		searchText,
		searchText,
		geo,
		freq,
	)
	if err != nil {
		return
	}
	for rows.Next() {
		dataPortalSeries, scanErr := getNextSeriesFromRows(rows)
		if scanErr != nil {
			return seriesList, scanErr
		}
		seriesList = append(seriesList, dataPortalSeries)
	}
	return
}

func (r *SeriesRepository) GetInflatedSearchResultsByGeoAndFreq(
	searchText string,
	geo string,
	freq string,
) (seriesList []models.InflatedSeries, err error) {
	rows, err := r.DB.Query(`SELECT series.id, name, description, frequency, seasonally_adjusted,
	measurements.units_label, measurements.units_label_short, measurements.data_portal_name, measurements.percent, measurements.real,
	fips, ?, display_name_short
	FROM series
	LEFT JOIN geographies ON handle LIKE ?
	LEFT JOIN measurements ON measurements.id = series.measurement_id
	WHERE NOT series.restricted AND
	((MATCH(name, description, dataPortalName)
	AGAINST(? IN NATURAL LANGUAGE MODE))
	OR LOWER(CONCAT(name, description, dataPortalName)) LIKE CONCAT('%', LOWER(?), '%'))
	AND LOWER(name) LIKE CONCAT('%@', LOWER(?), '.', LOWER(?)) LIMIT 50;`,
		geo,
		geo,
		searchText,
		searchText,
		geo,
		freq,
	)
	if err != nil {
		return
	}
	for rows.Next() {
		dataPortalSeries, scanErr := getNextSeriesFromRows(rows)
		if scanErr != nil {
			return seriesList, scanErr
		}
		seriesObservations, scanErr := r.GetSeriesObservations(dataPortalSeries.Id)
		if scanErr != nil {
			return seriesList, scanErr
		}
		inflatedSeries := models.InflatedSeries{dataPortalSeries, seriesObservations}
		seriesList = append(seriesList, inflatedSeries)
	}
	return
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Thomas de Zeeuw.
//
// Licensed onder the MIT license that can be found in the LICENSE file.

package ini

import (
	"reflect"
	"strings"
	"testing"
	"time"
)

// todo: test int/uint/float overflows.

type completeTestData struct {
	Str      string
	Duration time.Duration
	Time     time.Time
	Bools    Bools
	Ints     Ints
	Uints    Uints
	Floats   Floats
	Slices   Slices
}

type Bools struct {
	T1 bool
	T2 bool
	T3 bool
	F1 bool
	F2 bool
	F3 bool
	F4 bool
}

type Ints struct {
	I   int
	I8  int8
	I16 int16
	I32 int32
	I64 int64
}

type Uints struct {
	Ui   uint
	Ui8  uint8
	Ui16 uint16
	Ui32 uint32
	Ui64 uint64
}

type Floats struct {
	F32 float32
	F64 float64
}

type Slices struct {
	Str      []string
	Duration []time.Duration
	Time     []time.Time
	T        []bool
	F        []bool
	I        []int
	I8       []int8
	I16      []int16
	I32      []int32
	I64      []int64
	Ui       []uint
	Ui8      []uint8
	Ui16     []uint16
	Ui32     []uint32
	Ui64     []uint64
	F32      []float32
	F64      []float64
}

func TestConfigDecode(t *testing.T) {
	t.Parallel()
	c := Config{
		Global: {
			"str":      "string",
			"duration": "5s",
			"time":     "2015-05-08 11:07:30",
		},
		"bools": {
			"t1": "true",
			"t2": "TRUE",
			"t3": "1",
			"f1": "false",
			"f2": "FALSE",
			"f3": "0",
			"f4": "anything else",
		},
		"ints": {
			"i":   "0",
			"i8":  "127",
			"i16": "32767",
			"i32": "2147483647",
			"i64": "9223372036854775807",
		},
		"uints": {
			"ui":   "0",
			"ui8":  "255",
			"ui16": "65535",
			"ui32": "4294967295",
			"ui64": "18446744073709551615",
		},
		"floats": {
			"f32": "12.21",
			"f64": "123.321",
		},
		"slices": {
			"str":      "string1, string2",
			"duration": "10s, 20m",
			"time":     "2015-05-10, 2015-05-09 11:07, 2015-05-08 11:07:30",
			"t":        "true, TRUE, 1",
			"f":        "false, FALSE, 0, anything else",
			"i":        "1, 2, 3",
			"i8":       "124, -125, 126",
			"i16":      "2343, 123, -23423",
			"i32":      "3534534, -234234, 86767",
			"i64":      "53534534530, 65756756, 4365456",
			"ui":       "1, 23425",
			"ui8":      "154, 126",
			"ui16":     "4645, 4353",
			"ui32":     "46424535, 3453",
			"ui64":     "3234464645, 453",
			"f32":      "12.21, 123.21",
			"f64":      "123.321, 4234.123",
		},
	}

	var got completeTestData

	if err := c.Decode(&got); err != nil {
		t.Fatalf("Unexpected error decoding config into variable: %q", err.Error())
	}

	strTimes := []string{"2015-05-10", "2015-05-09 11:07", "2015-05-08 11:07:30"}
	var times []time.Time
	for i, tValue := range strTimes {
		t1, err := time.Parse(timeFormats[i], tValue)
		if err != nil {
			t.Fatalf("Unexpected error parsing time: %s", err.Error())
		}
		times = append(times, t1)
	}

	expected := completeTestData{
		Str:      "string",
		Duration: 5 * time.Second,
		Time:     times[2],
		Bools: Bools{
			T1: true,
			T2: true,
			T3: true,
			F1: false,
			F2: false,
			F3: false,
			F4: false,
		},
		Ints: Ints{
			I:   0,
			I8:  127,
			I16: 32767,
			I32: 2147483647,
			I64: 9223372036854775807,
		},
		Uints: Uints{
			Ui:   0,
			Ui8:  255,
			Ui16: 65535,
			Ui32: 4294967295,
			Ui64: 18446744073709551615,
		},
		Floats: Floats{
			F32: 12.21,
			F64: 123.321,
		},
		Slices: Slices{
			Str:      []string{"string1", "string2"},
			Duration: []time.Duration{10 * time.Second, 20 * time.Minute},
			Time:     times,
			T:        []bool{true, true, true},
			F:        []bool{false, false, false, false},
			I:        []int{1, 2, 3},
			I8:       []int8{124, -125, 126},
			I16:      []int16{2343, 123, -23423},
			I32:      []int32{3534534, -234234, 86767},
			I64:      []int64{53534534530, 65756756, 4365456},
			Ui:       []uint{1, 23425},
			Ui8:      []uint8{154, 126},
			Ui16:     []uint16{4645, 4353},
			Ui32:     []uint32{46424535, 3453},
			Ui64:     []uint64{3234464645, 453},
			F32:      []float32{12.21, 123.21},
			F64:      []float64{123.321, 4234.123},
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Expected Config.Decode() to return %v, but got %v", expected, got)
	}
}

type smallTestData struct {
	Key     string
	Section smallSection
}

type smallSection struct {
	Key2 string
}

func TestDecode(t *testing.T) {
	t.Parallel()
	content := "key = value\n[section]\nkey2=value2"

	var got smallTestData

	if err := Decode(strings.NewReader(content), &got); err != nil {
		t.Fatalf("Unexpected error decoding: %q", err.Error())
	}

	expected := smallTestData{
		Key: "value",
		Section: smallSection{
			Key2: "value2",
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Expected %v, but got %v", got, expected)
	}
}

func TestDecodeValue(t *testing.T) {
	t.Parallel()

	var host string
	var port int
	conf := Config{
		Global: {
			"host": "localhost",
			"port": "8080",
		},
	}

	if err := DecodeValue(conf[Global]["host"], &host); err != nil {
		t.Fatalf("Unexpected error decoding variable: %s", err.Error())
	} else if err := DecodeValue(conf[Global]["port"], &port); err != nil {
		t.Fatalf("Unexpected error decoding variable: %s", err.Error())
	}

	if expected := "localhost"; host != expected {
		t.Fatalf("Expected host to be %q, but got %q", expected, host)
	}
	if expected := 8080; port != expected {
		t.Fatalf("Expected port to be %d, but got %d", expected, port)
	}
}

func TestDecodeValueNotPointerError(t *testing.T) {
	t.Parallel()

	var ui8 uint8
	conf := Config{
		Global: {
			"ui8": "500",
		},
	}

	err := DecodeValue(conf[Global]["ui8"], ui8)
	expected := "ini: DecodeValue requires a pointer to a destination value"
	if err == nil {
		t.Fatal("Expected an error, but didn't get one")
	} else if err.Error() != expected {
		t.Fatalf("Expected error message to be %q, but got %q",
			expected, err.Error())
	}
}

func TestDecodeValueOverflowError(t *testing.T) {
	t.Parallel()

	var ui8 uint8
	conf := Config{
		Global: {
			"ui8": "500",
		},
	}

	err := DecodeValue(conf[Global]["ui8"], &ui8)
	expected := "ini: can't convert '500' to type uint8, it overflows the type"
	if err == nil {
		t.Fatal("Expected an error, but didn't get one")
	} else if err.Error() != expected {
		t.Fatalf("Expected error message to be %q, but got %q",
			expected, err.Error())
	} else if !IsOverflowError(err) {
		t.Fatal("Expected error to be an overflow error, but it isn't")
	}
}
<commit_msg>Small test code cleanup<commit_after>// Copyright (C) 2015 Thomas de Zeeuw.
//
// Licensed onder the MIT license that can be found in the LICENSE file.

package ini

import (
	"reflect"
	"strings"
	"testing"
	"time"
)

// todo: test int/uint/float overflows.

type completeTestData struct {
	Str      string
	Duration time.Duration
	Time     time.Time
	Bools    Bools
	Ints     Ints
	Uints    Uints
	Floats   Floats
	Slices   Slices
}

type Bools struct {
	T1 bool
	T2 bool
	T3 bool
	F1 bool
	F2 bool
	F3 bool
	F4 bool
}

type Ints struct {
	I   int
	I8  int8
	I16 int16
	I32 int32
	I64 int64
}

type Uints struct {
	Ui   uint
	Ui8  uint8
	Ui16 uint16
	Ui32 uint32
	Ui64 uint64
}

type Floats struct {
	F32 float32
	F64 float64
}

type Slices struct {
	Str      []string
	Duration []time.Duration
	Time     []time.Time
	T        []bool
	F        []bool
	I        []int
	I8       []int8
	I16      []int16
	I32      []int32
	I64      []int64
	Ui       []uint
	Ui8      []uint8
	Ui16     []uint16
	Ui32     []uint32
	Ui64     []uint64
	F32      []float32
	F64      []float64
}

func TestConfigDecode(t *testing.T) {
	t.Parallel()
	c := Config{
		Global: {
			"str":      "string",
			"duration": "5s",
			"time":     "2015-05-08 11:07:30",
		},
		"bools": {
			"t1": "true",
			"t2": "TRUE",
			"t3": "1",
			"f1": "false",
			"f2": "FALSE",
			"f3": "0",
			"f4": "anything else",
		},
		"ints": {
			"i":   "0",
			"i8":  "127",
			"i16": "32767",
			"i32": "2147483647",
			"i64": "9223372036854775807",
		},
		"uints": {
			"ui":   "0",
			"ui8":  "255",
			"ui16": "65535",
			"ui32": "4294967295",
			"ui64": "18446744073709551615",
		},
		"floats": {
			"f32": "12.21",
			"f64": "123.321",
		},
		"slices": {
			"str":      "string1, string2",
			"duration": "10s, 20m",
			"time":     "2015-05-10, 2015-05-09 11:07, 2015-05-08 11:07:30",
			"t":        "true, TRUE, 1",
			"f":        "false, FALSE, 0, anything else",
			"i":        "1, 2, 3",
			"i8":       "124, -125, 126",
			"i16":      "2343, 123, -23423",
			"i32":      "3534534, -234234, 86767",
			"i64":      "53534534530, 65756756, 4365456",
			"ui":       "1, 23425",
			"ui8":      "154, 126",
			"ui16":     "4645, 4353",
			"ui32":     "46424535, 3453",
			"ui64":     "3234464645, 453",
			"f32":      "12.21, 123.21",
			"f64":      "123.321, 4234.123",
		},
	}

	var got completeTestData

	if err := c.Decode(&got); err != nil {
		t.Fatalf("Unexpected error decoding config into variable: %q", err.Error())
	}

	strTimes := []string{"2015-05-10", "2015-05-09 11:07", "2015-05-08 11:07:30"}
	var times []time.Time
	for i, tValue := range strTimes {
		t1, err := time.Parse(timeFormats[i], tValue)
		if err != nil {
			t.Fatalf("Unexpected error parsing time: %s", err.Error())
		}
		times = append(times, t1)
	}

	expected := completeTestData{
		Str:      "string",
		Duration: 5 * time.Second,
		Time:     times[2],
		Bools: Bools{
			T1: true,
			T2: true,
			T3: true,
			F1: false,
			F2: false,
			F3: false,
			F4: false,
		},
		Ints: Ints{
			I:   0,
			I8:  127,
			I16: 32767,
			I32: 2147483647,
			I64: 9223372036854775807,
		},
		Uints: Uints{
			Ui:   0,
			Ui8:  255,
			Ui16: 65535,
			Ui32: 4294967295,
			Ui64: 18446744073709551615,
		},
		Floats: Floats{
			F32: 12.21,
			F64: 123.321,
		},
		Slices: Slices{
			Str:      []string{"string1", "string2"},
			Duration: []time.Duration{10 * time.Second, 20 * time.Minute},
			Time:     times,
			T:        []bool{true, true, true},
			F:        []bool{false, false, false, false},
			I:        []int{1, 2, 3},
			I8:       []int8{124, -125, 126},
			I16:      []int16{2343, 123, -23423},
			I32:      []int32{3534534, -234234, 86767},
			I64:      []int64{53534534530, 65756756, 4365456},
			Ui:       []uint{1, 23425},
			Ui8:      []uint8{154, 126},
			Ui16:     []uint16{4645, 4353},
			Ui32:     []uint32{46424535, 3453},
			Ui64:     []uint64{3234464645, 453},
			F32:      []float32{12.21, 123.21},
			F64:      []float64{123.321, 4234.123},
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Expected Config.Decode() to return %v, but got %v", expected, got)
	}
}

type smallTestData struct {
	Key     string
	Section smallSection
}

type smallSection struct {
	Key2 string
}

func TestDecode(t *testing.T) {
	t.Parallel()
	content := "key = value\n[section]\nkey2=value2"

	var got smallTestData
	if err := Decode(strings.NewReader(content), &got); err != nil {
		t.Fatalf("Unexpected error decoding: %q", err.Error())
	}

	expected := smallTestData{
		Key: "value",
		Section: smallSection{
			Key2: "value2",
		},
	}

	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Expected %v, but got %v", expected, got)
	}
}

func TestDecodeValue(t *testing.T) {
	t.Parallel()

	var host string
	var port int
	conf := Config{
		Global: {
			"host": "localhost",
			"port": "8080",
		},
	}

	if err := DecodeValue(conf[Global]["host"], &host); err != nil {
		t.Fatalf("Unexpected error decoding variable: %s", err.Error())
	} else if err := DecodeValue(conf[Global]["port"], &port); err != nil {
		t.Fatalf("Unexpected error decoding variable: %s", err.Error())
	}

	if expected := "localhost"; host != expected {
		t.Fatalf("Expected host to be %q, but got %q", expected, host)
	}
	if expected := 8080; port != expected {
		t.Fatalf("Expected port to be %d, but got %d", expected, port)
	}
}

func TestDecodeValueNotPointerError(t *testing.T) {
	t.Parallel()

	var ui8 uint8
	conf := Config{
		Global: {
			"ui8": "500",
		},
	}

	err := DecodeValue(conf[Global]["ui8"], ui8)
	expected := "ini: DecodeValue requires a pointer to a destination value"
	if err == nil {
		t.Fatal("Expected an error, but didn't get one")
	} else if err.Error() != expected {
		t.Fatalf("Expected error message to be %q, but got %q",
			expected, err.Error())
	}
}

func TestDecodeValueOverflowError(t *testing.T) {
	t.Parallel()

	var ui8 uint8
	conf := Config{
		Global: {
			"ui8": "500",
		},
	}

	err := DecodeValue(conf[Global]["ui8"], &ui8)
	expected := "ini: can't convert '500' to type uint8, it overflows the type"
	if err == nil {
		t.Fatal("Expected an error, but didn't get one")
	} else if err.Error() != expected {
		t.Fatalf("Expected error message to be %q, but got %q",
			expected, err.Error())
	} else if !IsOverflowError(err) {
		t.Fatal("Expected error to be an overflow error, but it isn't")
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

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

package tchannel

import (
	"errors"
	"fmt"
	"time"

	"golang.org/x/net/context"
)

var errInboundRequestAlreadyActive = errors.New("inbound request is already active; possible duplicate client id")

// handleCallReq handles an incoming call request, registering a message
// exchange to receive further fragments for that call, and dispatching it in
// another goroutine
func (c *Connection) handleCallReq(frame *Frame) bool {
	now := c.timeNow()
	switch state := c.readState(); state {
	case connectionActive:
		break
	case connectionStartClose, connectionInboundClosed, connectionClosed:
		c.SendSystemError(frame.Header.ID, nil, ErrChannelClosed)
		return true
	case connectionWaitingToRecvInitReq, connectionWaitingToSendInitReq, connectionWaitingToRecvInitRes:
		c.SendSystemError(frame.Header.ID, nil, NewSystemError(ErrCodeDeclined, "connection not ready"))
		return true
	default:
		panic(fmt.Errorf("unknown connection state for call req: %v", state))
	}

	callReq := new(callReq)
	initialFragment, err := parseInboundFragment(c.framePool, frame, callReq)
	if err != nil {
		// TODO(mmihic): Probably want to treat this as a protocol error
		c.log.WithFields(
			LogField{"header", frame.Header},
			ErrField(err),
		).Error("Couldn't decode initial fragment.")
		return true
	}

	call := new(InboundCall)
	call.conn = c
	ctx, cancel := newIncomingContext(call, callReq.TimeToLive, &callReq.Tracing)

	mex, err := c.inbound.newExchange(ctx, c.framePool, callReq.messageType(), frame.Header.ID, mexChannelBufferSize)
	if err != nil {
		if err == errDuplicateMex {
			err = errInboundRequestAlreadyActive
		}
		c.log.WithFields(LogField{"header", frame.Header}).Error("Couldn't register exchange.")
		c.protocolError(frame.Header.ID, errInboundRequestAlreadyActive)
		return true
	}

	// Close may have been called between the time we checked the state and us creating the exchange.
	if c.readState() != connectionActive {
		mex.shutdown()
		return true
	}

	response := new(InboundCallResponse)
	response.calledAt = now
	response.Annotations = Annotations{
		reporter: c.traceReporter,
		timeNow:  c.timeNow,
		data: TraceData{
			Span: callReq.Tracing,
			Source: TraceEndpoint{
				HostPort:    c.localPeerInfo.HostPort,
				ServiceName: callReq.Service,
			},
		},
		binaryAnnotationsBacking: [2]BinaryAnnotation{
			{Key: "cn", Value: callReq.Headers[CallerName]},
			{Key: "as", Value: callReq.Headers[ArgScheme]},
		},
	}
	response.data.Target = response.data.Source
	response.data.Annotations = response.annotationsBacking[:0]
	response.data.BinaryAnnotations = response.binaryAnnotationsBacking[:]
	response.AddAnnotationAt(AnnotationKeyServerReceive, now)
	response.mex = mex
	response.conn = c
	response.cancel = cancel
	response.span = callReq.Tracing
	response.log = c.log.WithFields(LogField{"In-Response", callReq.ID()})
	response.contents = newFragmentingWriter(response.log, response, initialFragment.checksumType.New())
	response.headers = transportHeaders{}
	response.messageForFragment = func(initial bool) message {
		if initial {
			callRes := new(callRes)
			callRes.Headers = response.headers
			callRes.ResponseCode = responseOK
			if response.applicationError {
				callRes.ResponseCode = responseApplicationError
			}
			return callRes
		}

		return new(callResContinue)
	}

	call.mex = mex
	call.initialFragment = initialFragment
	call.serviceName = string(callReq.Service)
	call.headers = callReq.Headers
	call.span = callReq.Tracing
	call.response = response
	call.log = c.log.WithFields(LogField{"In-Call", callReq.ID()})
	call.messageForFragment = func(initial bool) message { return new(callReqContinue) }
	call.contents = newFragmentingReader(call.log, call)
	call.statsReporter = c.statsReporter
	call.createStatsTags(c.commonStatsTags)

	response.statsReporter = c.statsReporter
	response.commonStatsTags = call.commonStatsTags

	setResponseHeaders(call.headers, response.headers)
	go c.dispatchInbound(c.connID, callReq.ID(), call, frame)
	return false
}

// handleCallReqContinue handles the continuation of a call request, forwarding
// it to the request channel for that request, where it can be pulled during
// defragmentation
func (c *Connection) handleCallReqContinue(frame *Frame) bool {
	if err := c.inbound.forwardPeerFrame(frame); err != nil {
		// If forward fails, it's due to a timeout.
		c.inbound.timeoutExchange(frame.Header.ID)
		return true
	}
	return false
}

// createStatsTags creates the common stats tags, if they are not already created.
func (call *InboundCall) createStatsTags(connectionTags map[string]string) {
	call.commonStatsTags = map[string]string{
		"calling-service": call.CallerName(),
	}
	for k, v := range connectionTags {
		call.commonStatsTags[k] = v
	}
}

// dispatchInbound ispatches an inbound call to the appropriate handler
func (c *Connection) dispatchInbound(_ uint32, _ uint32, call *InboundCall, frame *Frame) {
	releaseFrame := func() {
		c.framePool.Release(frame)
	}

	if c.log.Enabled(LogLevelDebug) {
		c.log.Debugf("Received incoming call for %s from %s", call.ServiceName(), c.remotePeerInfo)
	}

	if err := call.readMethod(); err != nil {
		c.log.WithFields(
			LogField{"remotePeer", c.remotePeerInfo},
			ErrField(err),
		).Error("Couldn't read method.")
		releaseFrame()
		return
	}

	call.commonStatsTags["endpoint"] = string(call.method)
	call.statsReporter.IncCounter("inbound.calls.recvd", call.commonStatsTags, 1)
	call.response.SetMethod(string(call.method))

	// NB(mmihic): Don't cast method name to string here - this will
	// create a copy of the byte array, where as aliasing to string in the
	// map look up can be optimized by the compiler to avoid the copy.  See
	// https://github.com/golang/go/issues/3512
	h := c.handlers.find(call.ServiceName(), call.Method())
	if h == nil {
		// Check the subchannel map to see if we find one there
		if c.log.Enabled(LogLevelDebug) {
			c.log.Debugf("Checking the subchannel's handlers for %s:%s", call.ServiceName(), call.Method())
		}

		h = c.subChannels.find(call.ServiceName(), call.Method())
	}
	if h == nil {
		c.log.WithFields(
			LogField{"serviceName", call.ServiceName()},
			LogField{"method", call.MethodString()},
		).Error("Couldn't find handler.")
		call.Response().SendSystemError(
			NewSystemError(ErrCodeBadRequest, "no handler for service %q and method %q", call.ServiceName(), call.Method()))
		releaseFrame()
		return
	}

	// TODO(prashant): This is an expensive way to check for cancellation. Use a heap for timeouts.
	go func() {
		if <-call.mex.ctx.Done(); call.mex.ctx.Err() == context.DeadlineExceeded {
			call.mex.inboundTimeout()
		}
	}()

	if c.log.Enabled(LogLevelDebug) {
		c.log.Debugf("Dispatching %s:%s from %s", call.ServiceName(), call.Method(), c.remotePeerInfo)
	}
	h.Handle(call.mex.ctx, call)
}

// An InboundCall is an incoming call from a peer
type InboundCall struct {
	reqResReader

	conn            *Connection
	response        *InboundCallResponse
	serviceName     string
	method          []byte
	methodString    string
	headers         transportHeaders
	span            Span
	statsReporter   StatsReporter
	commonStatsTags map[string]string
}

// ServiceName returns the name of the service being called
func (call *InboundCall) ServiceName() string {
	return call.serviceName
}

// Method returns the method being called
func (call *InboundCall) Method() []byte {
	return call.method
}

// MethodString returns the method being called as a string.
func (call *InboundCall) MethodString() string {
	return call.methodString
}

// Format the format of the request from the ArgScheme transport header.
func (call *InboundCall) Format() Format {
	return Format(call.headers[ArgScheme])
}

// CallerName returns the caller name from the CallerName transport header.
func (call *InboundCall) CallerName() string {
	return call.headers[CallerName]
}

// ShardKey returns the shard key from the ShardKey transport header.
func (call *InboundCall) ShardKey() string {
	return call.headers[ShardKey]
}

// RoutingDelegate returns the routing delegate from the RoutingDelegate transport header.
func (call *InboundCall) RoutingDelegate() string {
	return call.headers[RoutingDelegate]
}

// RemotePeer returns the caller's peer info.
func (call *InboundCall) RemotePeer() PeerInfo {
	return call.conn.RemotePeerInfo()
}

// Reads the entire method name (arg1) from the request stream.
func (call *InboundCall) readMethod() error {
	var arg1 []byte
	if err := NewArgReader(call.arg1Reader()).Read(&arg1); err != nil {
		return call.failed(err)
	}

	call.method = arg1
	call.methodString = string(arg1)
	return nil
}

// Arg2Reader returns an ArgReader to read the second argument.
// The ReadCloser must be closed once the argument has been read.
func (call *InboundCall) Arg2Reader() (ArgReader, error) {
	return call.arg2Reader()
}

// Arg3Reader returns an ArgReader to read the last argument.
// The ReadCloser must be closed once the argument has been read.
func (call *InboundCall) Arg3Reader() (ArgReader, error) {
	return call.arg3Reader()
}

// Response provides access to the InboundCallResponse object which can be used
// to write back to the calling peer
func (call *InboundCall) Response() *InboundCallResponse {
	if call.err != nil {
		// While reading Thrift, we cannot distinguish between malformed Thrift and other errors,
		// and so we may try to respond with a bad request. We should ensure that the response
		// is marked as failed if the request has failed so that we don't try to shutdown the exchange
		// a second time.
		call.response.err = call.err
	}
	return call.response
}

func (call *InboundCall) doneReading(unexpected error) {}

// An InboundCallResponse is used to send the response back to the calling peer
type InboundCallResponse struct {
	reqResWriter
	Annotations

	cancel context.CancelFunc
	// calledAt is the time the inbound call was routed to the application.
	calledAt         time.Time
	applicationError bool
	systemError      bool
	headers          transportHeaders
	span             Span
	statsReporter    StatsReporter
	commonStatsTags  map[string]string
}

// SendSystemError returns a system error response to the peer.  The call is considered
// complete after this method is called, and no further data can be written.
func (response *InboundCallResponse) SendSystemError(err error) error {
	if response.err != nil {
		return response.err
	}
	// Fail all future attempts to read fragments
	response.state = reqResWriterComplete
	response.systemError = true
	response.doneSending()
	return response.conn.SendSystemError(response.mex.msgID, CurrentSpan(response.mex.ctx), err)
}

// SetApplicationError marks the response as being an application error.  This method can
// only be called before any arguments have been sent to the calling peer.
func (response *InboundCallResponse) SetApplicationError() error {
	if response.state > reqResWriterPreArg2 {
		return response.failed(errReqResWriterStateMismatch{
			state:         response.state,
			expectedState: reqResWriterPreArg2,
		})
	}
	response.applicationError = true
	return nil
}

// Arg2Writer returns a WriteCloser that can be used to write the second argument.
// The returned writer must be closed once the write is complete.
func (response *InboundCallResponse) Arg2Writer() (ArgWriter, error) {
	if err := NewArgWriter(response.arg1Writer()).Write(nil); err != nil {
		return nil, err
	}
	return response.arg2Writer()
}

// Arg3Writer returns a WriteCloser that can be used to write the last argument.
// The returned writer must be closed once the write is complete.
func (response *InboundCallResponse) Arg3Writer() (ArgWriter, error) {
	return response.arg3Writer()
}

// doneSending shuts down the message exchange for this call.
// For incoming calls, the last message is sending the call response.
func (response *InboundCallResponse) doneSending() {
	// TODO(prashant): Move this to when the message is actually being sent.
	now := response.GetTime()
	response.AddAnnotationAt(AnnotationKeyServerSend, now)
	response.Report()

	latency := now.Sub(response.calledAt)
	response.statsReporter.RecordTimer("inbound.calls.latency", response.commonStatsTags, latency)

	if response.systemError {
		// TODO(prashant): Report the error code type as per metrics doc and enable.
		// response.statsReporter.IncCounter("inbound.calls.system-errors", response.commonStatsTags, 1)
	} else if response.applicationError {
		response.statsReporter.IncCounter("inbound.calls.app-errors", response.commonStatsTags, 1)
	} else {
		response.statsReporter.IncCounter("inbound.calls.success", response.commonStatsTags, 1)
	}

	// Cancel the context since the response is complete.
	response.cancel()

	// The message exchange is still open if there are no errors, call shutdown.
	if response.err == nil {
		response.mex.shutdown()
	}
}
<commit_msg>Don't timeoutExchange on forwardPeerFrame failures<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

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

package tchannel

import (
	"errors"
	"fmt"
	"time"

	"golang.org/x/net/context"
)

var errInboundRequestAlreadyActive = errors.New("inbound request is already active; possible duplicate client id")

// handleCallReq handles an incoming call request, registering a message
// exchange to receive further fragments for that call, and dispatching it in
// another goroutine
func (c *Connection) handleCallReq(frame *Frame) bool {
	now := c.timeNow()
	switch state := c.readState(); state {
	case connectionActive:
		break
	case connectionStartClose, connectionInboundClosed, connectionClosed:
		c.SendSystemError(frame.Header.ID, nil, ErrChannelClosed)
		return true
	case connectionWaitingToRecvInitReq, connectionWaitingToSendInitReq, connectionWaitingToRecvInitRes:
		c.SendSystemError(frame.Header.ID, nil, NewSystemError(ErrCodeDeclined, "connection not ready"))
		return true
	default:
		panic(fmt.Errorf("unknown connection state for call req: %v", state))
	}

	callReq := new(callReq)
	initialFragment, err := parseInboundFragment(c.framePool, frame, callReq)
	if err != nil {
		// TODO(mmihic): Probably want to treat this as a protocol error
		c.log.WithFields(
			LogField{"header", frame.Header},
			ErrField(err),
		).Error("Couldn't decode initial fragment.")
		return true
	}

	call := new(InboundCall)
	call.conn = c
	ctx, cancel := newIncomingContext(call, callReq.TimeToLive, &callReq.Tracing)

	mex, err := c.inbound.newExchange(ctx, c.framePool, callReq.messageType(), frame.Header.ID, mexChannelBufferSize)
	if err != nil {
		if err == errDuplicateMex {
			err = errInboundRequestAlreadyActive
		}
		c.log.WithFields(LogField{"header", frame.Header}).Error("Couldn't register exchange.")
		c.protocolError(frame.Header.ID, errInboundRequestAlreadyActive)
		return true
	}

	// Close may have been called between the time we checked the state and us creating the exchange.
	if c.readState() != connectionActive {
		mex.shutdown()
		return true
	}

	response := new(InboundCallResponse)
	response.calledAt = now
	response.Annotations = Annotations{
		reporter: c.traceReporter,
		timeNow:  c.timeNow,
		data: TraceData{
			Span: callReq.Tracing,
			Source: TraceEndpoint{
				HostPort:    c.localPeerInfo.HostPort,
				ServiceName: callReq.Service,
			},
		},
		binaryAnnotationsBacking: [2]BinaryAnnotation{
			{Key: "cn", Value: callReq.Headers[CallerName]},
			{Key: "as", Value: callReq.Headers[ArgScheme]},
		},
	}
	response.data.Target = response.data.Source
	response.data.Annotations = response.annotationsBacking[:0]
	response.data.BinaryAnnotations = response.binaryAnnotationsBacking[:]
	response.AddAnnotationAt(AnnotationKeyServerReceive, now)
	response.mex = mex
	response.conn = c
	response.cancel = cancel
	response.span = callReq.Tracing
	response.log = c.log.WithFields(LogField{"In-Response", callReq.ID()})
	response.contents = newFragmentingWriter(response.log, response, initialFragment.checksumType.New())
	response.headers = transportHeaders{}
	response.messageForFragment = func(initial bool) message {
		if initial {
			callRes := new(callRes)
			callRes.Headers = response.headers
			callRes.ResponseCode = responseOK
			if response.applicationError {
				callRes.ResponseCode = responseApplicationError
			}
			return callRes
		}

		return new(callResContinue)
	}

	call.mex = mex
	call.initialFragment = initialFragment
	call.serviceName = string(callReq.Service)
	call.headers = callReq.Headers
	call.span = callReq.Tracing
	call.response = response
	call.log = c.log.WithFields(LogField{"In-Call", callReq.ID()})
	call.messageForFragment = func(initial bool) message { return new(callReqContinue) }
	call.contents = newFragmentingReader(call.log, call)
	call.statsReporter = c.statsReporter
	call.createStatsTags(c.commonStatsTags)

	response.statsReporter = c.statsReporter
	response.commonStatsTags = call.commonStatsTags

	setResponseHeaders(call.headers, response.headers)
	go c.dispatchInbound(c.connID, callReq.ID(), call, frame)
	return false
}

// handleCallReqContinue handles the continuation of a call request, forwarding
// it to the request channel for that request, where it can be pulled during
// defragmentation
func (c *Connection) handleCallReqContinue(frame *Frame) bool {
	if err := c.inbound.forwardPeerFrame(frame); err != nil {
		// If forward fails, it's due to a timeout. We can free this frame.
		return true
	}
	return false
}

// createStatsTags creates the common stats tags, if they are not already created.
func (call *InboundCall) createStatsTags(connectionTags map[string]string) {
	call.commonStatsTags = map[string]string{
		"calling-service": call.CallerName(),
	}
	for k, v := range connectionTags {
		call.commonStatsTags[k] = v
	}
}

// dispatchInbound ispatches an inbound call to the appropriate handler
func (c *Connection) dispatchInbound(_ uint32, _ uint32, call *InboundCall, frame *Frame) {
	releaseFrame := func() {
		c.framePool.Release(frame)
	}

	if c.log.Enabled(LogLevelDebug) {
		c.log.Debugf("Received incoming call for %s from %s", call.ServiceName(), c.remotePeerInfo)
	}

	if err := call.readMethod(); err != nil {
		c.log.WithFields(
			LogField{"remotePeer", c.remotePeerInfo},
			ErrField(err),
		).Error("Couldn't read method.")
		releaseFrame()
		return
	}

	call.commonStatsTags["endpoint"] = string(call.method)
	call.statsReporter.IncCounter("inbound.calls.recvd", call.commonStatsTags, 1)
	call.response.SetMethod(string(call.method))

	// NB(mmihic): Don't cast method name to string here - this will
	// create a copy of the byte array, where as aliasing to string in the
	// map look up can be optimized by the compiler to avoid the copy.  See
	// https://github.com/golang/go/issues/3512
	h := c.handlers.find(call.ServiceName(), call.Method())
	if h == nil {
		// Check the subchannel map to see if we find one there
		if c.log.Enabled(LogLevelDebug) {
			c.log.Debugf("Checking the subchannel's handlers for %s:%s", call.ServiceName(), call.Method())
		}

		h = c.subChannels.find(call.ServiceName(), call.Method())
	}
	if h == nil {
		c.log.WithFields(
			LogField{"serviceName", call.ServiceName()},
			LogField{"method", call.MethodString()},
		).Error("Couldn't find handler.")
		call.Response().SendSystemError(
			NewSystemError(ErrCodeBadRequest, "no handler for service %q and method %q", call.ServiceName(), call.Method()))
		releaseFrame()
		return
	}

	// TODO(prashant): This is an expensive way to check for cancellation. Use a heap for timeouts.
	go func() {
		if <-call.mex.ctx.Done(); call.mex.ctx.Err() == context.DeadlineExceeded {
			call.mex.inboundTimeout()
		}
	}()

	if c.log.Enabled(LogLevelDebug) {
		c.log.Debugf("Dispatching %s:%s from %s", call.ServiceName(), call.Method(), c.remotePeerInfo)
	}
	h.Handle(call.mex.ctx, call)
}

// An InboundCall is an incoming call from a peer
type InboundCall struct {
	reqResReader

	conn            *Connection
	response        *InboundCallResponse
	serviceName     string
	method          []byte
	methodString    string
	headers         transportHeaders
	span            Span
	statsReporter   StatsReporter
	commonStatsTags map[string]string
}

// ServiceName returns the name of the service being called
func (call *InboundCall) ServiceName() string {
	return call.serviceName
}

// Method returns the method being called
func (call *InboundCall) Method() []byte {
	return call.method
}

// MethodString returns the method being called as a string.
func (call *InboundCall) MethodString() string {
	return call.methodString
}

// Format the format of the request from the ArgScheme transport header.
func (call *InboundCall) Format() Format {
	return Format(call.headers[ArgScheme])
}

// CallerName returns the caller name from the CallerName transport header.
func (call *InboundCall) CallerName() string {
	return call.headers[CallerName]
}

// ShardKey returns the shard key from the ShardKey transport header.
func (call *InboundCall) ShardKey() string {
	return call.headers[ShardKey]
}

// RoutingDelegate returns the routing delegate from the RoutingDelegate transport header.
func (call *InboundCall) RoutingDelegate() string {
	return call.headers[RoutingDelegate]
}

// RemotePeer returns the caller's peer info.
func (call *InboundCall) RemotePeer() PeerInfo {
	return call.conn.RemotePeerInfo()
}

// Reads the entire method name (arg1) from the request stream.
func (call *InboundCall) readMethod() error {
	var arg1 []byte
	if err := NewArgReader(call.arg1Reader()).Read(&arg1); err != nil {
		return call.failed(err)
	}

	call.method = arg1
	call.methodString = string(arg1)
	return nil
}

// Arg2Reader returns an ArgReader to read the second argument.
// The ReadCloser must be closed once the argument has been read.
func (call *InboundCall) Arg2Reader() (ArgReader, error) {
	return call.arg2Reader()
}

// Arg3Reader returns an ArgReader to read the last argument.
// The ReadCloser must be closed once the argument has been read.
func (call *InboundCall) Arg3Reader() (ArgReader, error) {
	return call.arg3Reader()
}

// Response provides access to the InboundCallResponse object which can be used
// to write back to the calling peer
func (call *InboundCall) Response() *InboundCallResponse {
	if call.err != nil {
		// While reading Thrift, we cannot distinguish between malformed Thrift and other errors,
		// and so we may try to respond with a bad request. We should ensure that the response
		// is marked as failed if the request has failed so that we don't try to shutdown the exchange
		// a second time.
		call.response.err = call.err
	}
	return call.response
}

func (call *InboundCall) doneReading(unexpected error) {}

// An InboundCallResponse is used to send the response back to the calling peer
type InboundCallResponse struct {
	reqResWriter
	Annotations

	cancel context.CancelFunc
	// calledAt is the time the inbound call was routed to the application.
	calledAt         time.Time
	applicationError bool
	systemError      bool
	headers          transportHeaders
	span             Span
	statsReporter    StatsReporter
	commonStatsTags  map[string]string
}

// SendSystemError returns a system error response to the peer.  The call is considered
// complete after this method is called, and no further data can be written.
func (response *InboundCallResponse) SendSystemError(err error) error {
	if response.err != nil {
		return response.err
	}
	// Fail all future attempts to read fragments
	response.state = reqResWriterComplete
	response.systemError = true
	response.doneSending()
	return response.conn.SendSystemError(response.mex.msgID, CurrentSpan(response.mex.ctx), err)
}

// SetApplicationError marks the response as being an application error.  This method can
// only be called before any arguments have been sent to the calling peer.
func (response *InboundCallResponse) SetApplicationError() error {
	if response.state > reqResWriterPreArg2 {
		return response.failed(errReqResWriterStateMismatch{
			state:         response.state,
			expectedState: reqResWriterPreArg2,
		})
	}
	response.applicationError = true
	return nil
}

// Arg2Writer returns a WriteCloser that can be used to write the second argument.
// The returned writer must be closed once the write is complete.
func (response *InboundCallResponse) Arg2Writer() (ArgWriter, error) {
	if err := NewArgWriter(response.arg1Writer()).Write(nil); err != nil {
		return nil, err
	}
	return response.arg2Writer()
}

// Arg3Writer returns a WriteCloser that can be used to write the last argument.
// The returned writer must be closed once the write is complete.
func (response *InboundCallResponse) Arg3Writer() (ArgWriter, error) {
	return response.arg3Writer()
}

// doneSending shuts down the message exchange for this call.
// For incoming calls, the last message is sending the call response.
func (response *InboundCallResponse) doneSending() {
	// TODO(prashant): Move this to when the message is actually being sent.
	now := response.GetTime()
	response.AddAnnotationAt(AnnotationKeyServerSend, now)
	response.Report()

	latency := now.Sub(response.calledAt)
	response.statsReporter.RecordTimer("inbound.calls.latency", response.commonStatsTags, latency)

	if response.systemError {
		// TODO(prashant): Report the error code type as per metrics doc and enable.
		// response.statsReporter.IncCounter("inbound.calls.system-errors", response.commonStatsTags, 1)
	} else if response.applicationError {
		response.statsReporter.IncCounter("inbound.calls.app-errors", response.commonStatsTags, 1)
	} else {
		response.statsReporter.IncCounter("inbound.calls.success", response.commonStatsTags, 1)
	}

	// Cancel the context since the response is complete.
	response.cancel()

	// The message exchange is still open if there are no errors, call shutdown.
	if response.err == nil {
		response.mex.shutdown()
	}
}
<|endoftext|>


<commit_before>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp    = regexp.MustCompile("oItem =\\s(.+?});")
	myEscrowExp   = regexp.MustCompile("var g_daysMyEscrow = (\\d+);")
	themEscrowExp = regexp.MustCompile("var g_daysTheirEscrow = (\\d+);")
	offerInfoExp  = regexp.MustCompile("token=([a-zA-Z0-9-_]+)")
	apiCallURL    = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch        = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive  = errors.New("unable to accept a non-active trade")
	ErrCannotFindOfferInfo = errors.New("unable to match data from trade offer url")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint64 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            int64       `json:"time_created"`
	Updated            int64       `json:"time_updated"`
	Expires            int64       `json:"expiration_time"`
	EscrowEndDate      int64       `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (session *Session) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := session.client.Get(apiCallURL + "/GetTradeOffer/v1/?" + url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (session *Session) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	params := url.Values{
		"key": {session.apiKey},
	}
	if testBit(filter, TradeFilterSentOffers) {
		params.Set("get_sent_offers", "1")
	}

	if testBit(filter, TradeFilterRecvOffers) {
		params.Set("get_received_offers", "1")
	}

	if testBit(filter, TradeFilterActiveOnly) {
		params.Set("active_only", "1")
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		params.Set("historical_only", "1")
		params.Set("time_historical_cutoff", strconv.FormatInt(timeCutOff.Unix(), 10))
	}

	resp, err := session.client.Get(apiCallURL + "/GetTradeOffers/v1/?" + params.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (session *Session) GetMyTradeToken() (string, error) {
	resp, err := session.client.Get("https://steamcommunity.com/my/tradeoffers/privacy")
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return "", err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	m := offerInfoExp.FindStringSubmatch(string(body))
	if m == nil || len(m) < 2 {
		return "", ErrCannotFindOfferInfo
	}

	return string(m[1]), nil
}

func (session *Session) GetEscrowDuration(sid SteamID, token string) (int64, int64, error) {
	resp, err := session.client.Get("https://steamcommunity.com/tradeoffer/new/?" + url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return 0, 0, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, 0, err
	}

	my := int64(0)
	m := myEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		my, _ = strconv.ParseInt(m[1], 10, 32)
	}

	them := int64(0)
	m = themEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		them, _ = strconv.ParseInt(m[1], 10, 32)
	}

	return my, them, nil
}

func (session *Session) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	req, err := http.NewRequest(
		http.MethodPost,
		"https://steamcommunity.com/tradeoffer/new/send",
		strings.NewReader(url.Values{
			"sessionid":                 {session.sessionID},
			"serverid":                  {"1"},
			"partner":                   {sid.ToString()},
			"tradeoffermessage":         {offer.Message},
			"json_tradeoffer":           {string(contentJSON)},
			"trade_offer_create_params": {string(params)},
		}.Encode()),
	)
	if err != nil {
		return err
	}
	req.Header.Add("Referer", "https://steamcommunity.com/tradeoffer/new/?"+url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID
	offer.Created = time.Now().Unix()
	offer.Updated = time.Now().Unix()

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (session *Session) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := session.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

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

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (session *Session) DeclineTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (session *Session) CancelTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (session *Session) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	postURL := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)

	req, err := http.NewRequest(
		http.MethodPost,
		postURL+"/accept",
		strings.NewReader(url.Values{
			"sessionid":    {session.sessionID},
			"serverid":     {"1"},
			"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
		}.Encode()),
	)
	if err != nil {
		return err
	}

	req.Header.Add("Referer", postURL)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Send(session *Session, sid SteamID, token string) error {
	return session.SendTradeOffer(offer, sid, token)
}

func (offer *TradeOffer) Accept(session *Session) error {
	return session.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(session *Session) error {
	if offer.IsOurOffer {
		return session.CancelTradeOffer(offer.ID)
	}

	return session.DeclineTradeOffer(offer.ID)
}
<commit_msg>tradeoffer: check for http status<commit_after>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp    = regexp.MustCompile("oItem =\\s(.+?});")
	myEscrowExp   = regexp.MustCompile("var g_daysMyEscrow = (\\d+);")
	themEscrowExp = regexp.MustCompile("var g_daysTheirEscrow = (\\d+);")
	offerInfoExp  = regexp.MustCompile("token=([a-zA-Z0-9-_]+)")
	apiCallURL    = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch        = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive  = errors.New("unable to accept a non-active trade")
	ErrCannotFindOfferInfo = errors.New("unable to match data from trade offer url")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint64 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            int64       `json:"time_created"`
	Updated            int64       `json:"time_updated"`
	Expires            int64       `json:"expiration_time"`
	EscrowEndDate      int64       `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (session *Session) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := session.client.Get(apiCallURL + "/GetTradeOffer/v1/?" + url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (session *Session) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	params := url.Values{
		"key": {session.apiKey},
	}
	if testBit(filter, TradeFilterSentOffers) {
		params.Set("get_sent_offers", "1")
	}

	if testBit(filter, TradeFilterRecvOffers) {
		params.Set("get_received_offers", "1")
	}

	if testBit(filter, TradeFilterActiveOnly) {
		params.Set("active_only", "1")
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		params.Set("historical_only", "1")
		params.Set("time_historical_cutoff", strconv.FormatInt(timeCutOff.Unix(), 10))
	}

	resp, err := session.client.Get(apiCallURL + "/GetTradeOffers/v1/?" + params.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (session *Session) GetMyTradeToken() (string, error) {
	resp, err := session.client.Get("https://steamcommunity.com/my/tradeoffers/privacy")
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return "", err
	}

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("http error: %d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	m := offerInfoExp.FindStringSubmatch(string(body))
	if m == nil || len(m) < 2 {
		return "", ErrCannotFindOfferInfo
	}

	return string(m[1]), nil
}

func (session *Session) GetEscrowDuration(sid SteamID, token string) (int64, int64, error) {
	resp, err := session.client.Get("https://steamcommunity.com/tradeoffer/new/?" + url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return 0, 0, err
	}

	if resp.StatusCode != http.StatusOK {
		return 0, 0, fmt.Errorf("http error: %d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, 0, err
	}

	my := int64(0)
	m := myEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		my, _ = strconv.ParseInt(m[1], 10, 32)
	}

	them := int64(0)
	m = themEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		them, _ = strconv.ParseInt(m[1], 10, 32)
	}

	return my, them, nil
}

func (session *Session) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	req, err := http.NewRequest(
		http.MethodPost,
		"https://steamcommunity.com/tradeoffer/new/send",
		strings.NewReader(url.Values{
			"sessionid":                 {session.sessionID},
			"serverid":                  {"1"},
			"partner":                   {sid.ToString()},
			"tradeoffermessage":         {offer.Message},
			"json_tradeoffer":           {string(contentJSON)},
			"trade_offer_create_params": {string(params)},
		}.Encode()),
	)
	if err != nil {
		return err
	}
	req.Header.Add("Referer", "https://steamcommunity.com/tradeoffer/new/?"+url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("http error: %d", resp.StatusCode)
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID
	offer.Created = time.Now().Unix()
	offer.Updated = time.Now().Unix()

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (session *Session) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := session.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http error: %d", resp.StatusCode)
	}

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

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (session *Session) DeclineTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (session *Session) CancelTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (session *Session) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	postURL := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)

	req, err := http.NewRequest(
		http.MethodPost,
		postURL+"/accept",
		strings.NewReader(url.Values{
			"sessionid":    {session.sessionID},
			"serverid":     {"1"},
			"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
		}.Encode()),
	)
	if err != nil {
		return err
	}

	req.Header.Add("Referer", postURL)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("http error: %d", resp.StatusCode)
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Send(session *Session, sid SteamID, token string) error {
	return session.SendTradeOffer(offer, sid, token)
}

func (offer *TradeOffer) Accept(session *Session) error {
	return session.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(session *Session) error {
	if offer.IsOurOffer {
		return session.CancelTradeOffer(offer.ID)
	}

	return session.DeclineTradeOffer(offer.ID)
}
<|endoftext|>