瀏覽代碼

first commit

lijian 6 年之前
當前提交
bc677c9466
共有 100 個文件被更改,包括 7097 次插入0 次删除
  1. 19 0
      pkg/cache/cache.go
  2. 117 0
      pkg/cache/memcache.go
  3. 144 0
      pkg/cache/memcache_test.go
  4. 110 0
      pkg/generator/key_gen.go
  5. 40 0
      pkg/generator/key_gen_test.go
  6. 21 0
      pkg/generator/password_gen.go
  7. 13 0
      pkg/generator/password_gen_test.go
  8. 20 0
      pkg/generator/token_gen.go
  9. 13 0
      pkg/generator/token_gen_test.go
  10. 22 0
      pkg/models/application.go
  11. 23 0
      pkg/models/device.go
  12. 12 0
      pkg/models/privilege.go
  13. 23 0
      pkg/models/product.go
  14. 12 0
      pkg/models/roles.go
  15. 23 0
      pkg/models/rule.go
  16. 35 0
      pkg/models/user.go
  17. 18 0
      pkg/models/vendor.go
  18. 58 0
      pkg/mongo/recorder.go
  19. 63 0
      pkg/mongo/recorder_test.go
  20. 35 0
      pkg/mqtt/broker.go
  21. 309 0
      pkg/mqtt/connection.go
  22. 102 0
      pkg/mqtt/manager.go
  23. 696 0
      pkg/mqtt/message.go
  24. 43 0
      pkg/mqtt/payload.go
  25. 13 0
      pkg/mqtt/provider.go
  26. 151 0
      pkg/mqtt/utils.go
  27. 43 0
      pkg/mysql/client.go
  28. 50 0
      pkg/mysql/migrate.go
  29. 12 0
      pkg/mysql/migrate_test.go
  30. 114 0
      pkg/online/online.go
  31. 77 0
      pkg/online/online_test.go
  32. 212 0
      pkg/productconfig/productconfig.go
  33. 167 0
      pkg/productconfig/productconfig_test.go
  34. 133 0
      pkg/protocol/protocol.go
  35. 150 0
      pkg/protocol/protocol_test.go
  36. 47 0
      pkg/protocol/structure.go
  37. 53 0
      pkg/queue/queque_test.go
  38. 140 0
      pkg/queue/queue.go
  39. 36 0
      pkg/redispool/redispool.go
  40. 18 0
      pkg/redispool/redispool_test.go
  41. 27 0
      pkg/rpcs/access.go
  42. 7 0
      pkg/rpcs/common.go
  43. 23 0
      pkg/rpcs/controller.go
  44. 35 0
      pkg/rpcs/devicemanager.go
  45. 15 0
      pkg/rpcs/registry.go
  46. 67 0
      pkg/rule/ifttt.go
  47. 95 0
      pkg/rule/rule_action.go
  48. 62 0
      pkg/rule/timer.go
  49. 22 0
      pkg/serializer/serializer.go
  50. 29 0
      pkg/serializer/serializer_test.go
  51. 7 0
      pkg/server/README.md
  52. 36 0
      pkg/server/config.go
  53. 15 0
      pkg/server/errors.go
  54. 45 0
      pkg/server/http_server.go
  55. 83 0
      pkg/server/http_server_test.go
  56. 35 0
      pkg/server/log.go
  57. 20 0
      pkg/server/log_test.go
  58. 93 0
      pkg/server/netif.go
  59. 33 0
      pkg/server/netif_test.go
  60. 87 0
      pkg/server/rpc_client.go
  61. 44 0
      pkg/server/rpc_client_test.go
  62. 17 0
      pkg/server/rpc_server.go
  63. 72 0
      pkg/server/rpc_server_test.go
  64. 257 0
      pkg/server/server.go
  65. 161 0
      pkg/server/server_manager.go
  66. 141 0
      pkg/server/server_test.go
  67. 77 0
      pkg/server/tcp_server.go
  68. 95 0
      pkg/server/tcp_server_test.go
  69. 18 0
      pkg/server/testdata/cert.pem
  70. 27 0
      pkg/server/testdata/key.pem
  71. 7 0
      pkg/server/timer_task.go
  72. 12 0
      pkg/server/utils.go
  73. 13 0
      pkg/server/utils_test.go
  74. 346 0
      pkg/tlv/tlv.go
  75. 118 0
      pkg/tlv/tlv_test.go
  76. 92 0
      pkg/token/token.go
  77. 27 0
      pkg/token/token_test.go
  78. 41 0
      pkg/utils/http.go
  79. 18 0
      pkg/utils/http_test.go
  80. 8 0
      services/README.md
  81. 233 0
      services/apiprovider/actions.go
  82. 40 0
      services/apiprovider/actions_test.go
  83. 14 0
      services/apiprovider/flags.go
  84. 42 0
      services/apiprovider/main.go
  85. 147 0
      services/apiprovider/middleware.go
  86. 30 0
      services/apiprovider/middleware_test.go
  87. 173 0
      services/apiprovider/notifier.go
  88. 8 0
      services/apiprovider/request.go
  89. 26 0
      services/apiprovider/response.go
  90. 46 0
      services/apiprovider/router.go
  91. 15 0
      services/apiprovider/user.go
  92. 140 0
      services/controller/controller.go
  93. 18 0
      services/controller/flags.go
  94. 33 0
      services/controller/main.go
  95. 15 0
      services/devicemanager/flags.go
  96. 28 0
      services/devicemanager/main.go
  97. 66 0
      services/devicemanager/manager.go
  98. 74 0
      services/devicemanager/manager_test.go
  99. 120 0
      services/httpaccess/actions.go
  100. 15 0
      services/httpaccess/flags.go

+ 19 - 0
pkg/cache/cache.go

@@ -0,0 +1,19 @@
+package cache
+
+
+
+//return status of chache
+type CacheStatus struct {
+	Gets        int64
+	Hits        int64
+	MaxItemSize int
+	CurrentSize int
+}
+
+//this is a interface which defines some common functions
+type Cache interface{
+	Set(key string, value interface{})
+	Get(key string) (interface{}, bool)
+	Delete(key string)
+	Status()(*CacheStatus)
+}

+ 117 - 0
pkg/cache/memcache.go

@@ -0,0 +1,117 @@
+package cache
+
+import (
+	"container/list"
+	"sync"
+	"sync/atomic"
+	//"fmt"
+)
+
+// An AtomicInt is an int64 to be accessed atomically.
+type AtomicInt int64
+
+// MemCache is an LRU cache. It is safe for concurrent access.
+type MemCache struct {
+	mutex       sync.RWMutex
+	maxItemSize int
+	cacheList   *list.List
+	cache       map[interface{}]*list.Element
+	hits, gets  AtomicInt
+}
+
+type entry struct {
+	key   interface{}
+	value interface{}
+}
+
+// If maxItemSize is zero, the cache has no limit.
+//if maxItemSize is not zero, when cache's size beyond maxItemSize,start to swap
+func NewMemCache(maxItemSize int) *MemCache {
+	return &MemCache{
+		maxItemSize: maxItemSize,
+		cacheList:   list.New(),
+		cache:       make(map[interface{}]*list.Element),
+	}
+}
+
+//return the status of cache
+func (c *MemCache) Status() *CacheStatus{
+	c.mutex.RLock()
+	defer c.mutex.RUnlock()
+	return &CacheStatus{
+		MaxItemSize: c.maxItemSize,
+		CurrentSize: c.cacheList.Len(),
+		Gets:        c.gets.Get(),
+		Hits:        c.hits.Get(),
+	}
+}
+
+//get value with key
+func (c *MemCache) Get(key string) (interface{}, bool) {
+	c.mutex.RLock()
+	defer c.mutex.RUnlock()
+	c.gets.Add(1)
+	if ele, hit := c.cache[key]; hit {
+		c.hits.Add(1)
+		return ele.Value.(*entry).value, true
+	}
+	return nil, false
+}
+
+//set a value with key
+func (c *MemCache) Set(key string, value interface{}) {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
+	if c.cache == nil {
+		c.cache = make(map[interface{}]*list.Element)
+		c.cacheList = list.New()
+	}
+
+	if ele, ok := c.cache[key]; ok {
+		c.cacheList.MoveToFront(ele)
+		ele.Value.(*entry).value = value
+		return
+	}
+
+	ele := c.cacheList.PushFront(&entry{key: key, value: value})
+	c.cache[key] = ele
+	if c.maxItemSize != 0 && c.cacheList.Len() > c.maxItemSize {
+		c.RemoveOldest()
+	}
+}
+
+func (c *MemCache) Delete(key string) {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
+	if c.cache == nil {
+		return
+	}
+	if ele, ok := c.cache[key]; ok {
+		c.cacheList.Remove(ele)
+		key := ele.Value.(*entry).key
+		delete(c.cache, key)
+		return
+	}
+}
+
+func (c *MemCache) RemoveOldest() {
+	if c.cache == nil {
+		return
+	}
+	ele := c.cacheList.Back()
+	if ele != nil {
+		c.cacheList.Remove(ele)
+		key := ele.Value.(*entry).key
+		delete(c.cache, key)
+	}
+}
+
+// Add atomically adds n to i.
+func (i *AtomicInt) Add(n int64) {
+	atomic.AddInt64((*int64)(i), n)
+}
+
+// Get atomically gets the value of i.
+func (i *AtomicInt) Get() int64 {
+	return atomic.LoadInt64((*int64)(i))
+}

+ 144 - 0
pkg/cache/memcache_test.go

@@ -0,0 +1,144 @@
+package cache
+
+import (
+	"testing"
+)
+
+type simpleStruct struct {
+	int
+	string
+}
+
+type complexStruct struct {
+	int
+	simpleStruct
+}
+
+var getTests = []struct {
+	name       string
+	keyToAdd   string
+	keyToGet   string
+	expectedOk bool
+}{
+	{"string_hit", "myKey", "myKey", true},
+	{"string_miss", "myKey", "nonsense", false},
+}
+
+func TestSet(t *testing.T) {
+	var cache Cache
+	cache = NewMemCache(0)
+	values := []string{"test1", "test2", "test3"}
+	key := "key1"
+	for _, v := range values {
+		cache.Set(key, v)
+		val, ok := cache.Get(key)
+		if !ok{
+			t.Fatalf("expect key:%v ,value:%v", key, v)
+		} else if ok && val != v {
+			t.Fatalf("expect value:%v, get value:%v", key, v, val)
+		}
+		t.Logf("value:%v ", val)
+	}
+}
+
+func TestGet(t *testing.T) {
+	var cache Cache
+	cache = NewMemCache(0)
+	for _, tt := range getTests {
+		cache.Set(tt.keyToAdd, 1234)
+		val, ok := cache.Get(tt.keyToGet)
+
+		if ok != tt.expectedOk {
+			t.Fatalf("%s: val:%v cache hit = %v; want %v", tt.name, val, ok, !ok)
+		} else if ok && val != 1234 {
+			t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val)
+		}
+
+	}
+}
+
+func TestDelete(t *testing.T) {
+	var cache Cache
+	cache = NewMemCache(0)
+	cache.Set("myKey", 1234)
+	if val, ok := cache.Get("myKey"); !ok {
+		t.Fatal("TestRemove returned no match")
+	} else if val != 1234 {
+		t.Fatalf("TestRemove failed.  Expected %d, got %v", 1234, val)
+	}
+
+	cache.Delete("myKey")
+	if _, ok := cache.Get("myKey"); ok {
+		t.Fatal("TestRemove returned a removed item")
+	}
+}
+
+func TestStatus(t *testing.T) {
+
+	keys := []string{"1", "2", "3", "4", "5"}
+
+	var gets int64
+	var hits int64
+	var maxSize int
+	var currentSize int
+	maxSize = 20
+	var cache Cache
+	cache = NewMemCache(maxSize)
+	//keys := []string{"1", "2", "3", "4", "5"}
+	for _, key := range keys {
+		cache.Set(key, 1234)
+		currentSize++
+	}
+
+	newKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
+
+	for _, newKey := range newKeys {
+		_, ok := cache.Get(newKey)
+		if ok == true {
+			hits++
+		}
+		gets++
+	}
+	t.Logf("gets:%v, hits:%v, maxSize:%v, currentSize:%v", gets, hits, maxSize, currentSize)
+	status := cache.Status()
+	if status.CurrentSize != currentSize || status.MaxItemSize != maxSize ||
+		status.Gets != gets || status.Hits != hits {
+		t.Fatalf("get status maxSize:%v, currentSize:%v, nget:%v, nhit:%v",
+			status.MaxItemSize, status.CurrentSize, status.Gets, status.Hits)
+	}
+
+}
+
+func TestLRU(t *testing.T) {
+	keys := []string{"1", "2", "3", "4", "2", "1", "3", "5", "6", "5", "6"}
+	maxSize := 3
+	var cache Cache
+	cache = NewMemCache(maxSize)
+	for i, key := range keys {
+		cache.Set(key, 1234)
+		if i == 3 {
+			status := cache.Status()
+			if status.CurrentSize != maxSize {
+				t.Fatalf("expected maxSize %v,currentSize:%v", maxSize, status.CurrentSize)
+			}
+			_, ok1 := cache.Get("2")
+			_, ok2 := cache.Get("3")
+			_, ok3 := cache.Get("4")
+			if !(ok1 && ok2 && ok3) {
+				t.Fatalf("expected remains key 2:%v,3:%v, 4:%v", ok1, ok2, ok3)
+			}
+		}
+	}
+
+	status := cache.Status()
+	if status.CurrentSize != maxSize {
+		t.Fatalf("expected maxSize %v,currentSize:%v", maxSize, status.CurrentSize)
+	}
+
+	_, ok1 := cache.Get("3")
+	_, ok2 := cache.Get("5")
+	_, ok3 := cache.Get("6")
+	if !(ok1 && ok2 && ok3) {
+		t.Fatalf("expected remains key 3:%v,5:%v, 6:%v", ok1, ok2, ok3)
+	}
+}

+ 110 - 0
pkg/generator/key_gen.go

@@ -0,0 +1,110 @@
+package generator
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"io"
+)
+
+const (
+	maxEncodeLen = 32
+)
+
+type KeyGenerator struct {
+	AESKey string
+}
+
+func encryptAESCFB(msg, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertext := make([]byte, aes.BlockSize+len(msg))
+	iv := ciphertext[:aes.BlockSize]
+	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+		return nil, err
+	}
+
+	stream := cipher.NewCFBEncrypter(block, iv)
+	stream.XORKeyStream(ciphertext[aes.BlockSize:], msg)
+
+	return ciphertext, nil
+}
+
+func decryptAESCFB(msg, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(msg) < aes.BlockSize {
+		return nil, errors.New("decrypt message too short")
+	}
+	iv := msg[:aes.BlockSize]
+	msg = msg[aes.BlockSize:]
+
+	stream := cipher.NewCFBDecrypter(block, iv)
+
+	stream.XORKeyStream(msg, msg)
+	return msg, nil
+}
+
+func NewKeyGenerator(key string) (*KeyGenerator, error) {
+	l := len(key)
+	if l != 16 && l != 24 && l != 32 {
+		return nil, errors.New("invalid aes key length, should be 16, 24 or 32 bytes.")
+	}
+	return &KeyGenerator{
+		AESKey: key,
+	}, nil
+}
+
+func (g *KeyGenerator) GenRandomKey(id uint) (string, error) {
+	buf := make([]byte, maxEncodeLen-binary.Size(id)-aes.BlockSize)
+	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
+		return "", nil
+	}
+
+	binid := bytes.NewBuffer([]byte{})
+	binary.Write(binid, binary.BigEndian, id)
+
+	buf = append(buf, binid.Bytes()...)
+
+	binkey, err := encryptAESCFB(buf, []byte(g.AESKey))
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(binkey), nil
+}
+
+// get id from encrypt strings
+func (g *KeyGenerator) DecodeIdFromRandomKey(encrypted string) (int64, error) {
+	buf, err := hex.DecodeString(encrypted)
+	if err != nil {
+		return 0, err
+	}
+
+	raw, err := decryptAESCFB(buf, []byte(g.AESKey))
+	if err != nil {
+		return 0, err
+	}
+
+	var id int64
+
+	if len(raw) > maxEncodeLen || len(raw) < maxEncodeLen-aes.BlockSize-binary.Size(id) {
+		return 0, errors.New("invalid key format.")
+	}
+
+	binbuf := bytes.NewBuffer(raw[maxEncodeLen-aes.BlockSize-binary.Size(id):])
+	binary.Read(binbuf, binary.BigEndian, &id)
+
+	return id, nil
+
+}

+ 40 - 0
pkg/generator/key_gen_test.go

@@ -0,0 +1,40 @@
+package generator
+
+import (
+	"testing"
+)
+
+func TestKeyGen(t *testing.T) {
+	generator, err := NewKeyGenerator("INVALIDKEY")
+	if err == nil {
+		t.Error("should return error when key length is invalid")
+	}
+	testid := int64(10000)
+	generator, err = NewKeyGenerator("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP")
+	if err != nil {
+		t.Fatal(err)
+	}
+	key, err := generator.GenRandomKey(testid)
+	if err != nil {
+		t.Error(err)
+	}
+	t.Log(key)
+	id, err := generator.DecodeIdFromRandomKey(key)
+	if err != nil {
+		t.Error(err)
+	}
+	if id != testid {
+		t.Errorf("wrong id %d, want %d", id, testid)
+	}
+
+	id, err = generator.DecodeIdFromRandomKey("")
+	if err == nil {
+		t.Error("decode id from random key should return error for empty key.")
+	}
+
+	id, err = generator.DecodeIdFromRandomKey("1111111111111111111111111111111111111111")
+	if err == nil {
+		t.Errorf("decode id from random key should return error for bad key : %s", "1111111111111111111111111111111111111111")
+	}
+
+}

+ 21 - 0
pkg/generator/password_gen.go

@@ -0,0 +1,21 @@
+package generator
+
+import (
+	"crypto/rand"
+	"encoding/base64"
+)
+
+const (
+	ranPasswordByteLength = 24
+)
+
+// gen random base64 encoded password
+func GenRandomPassword() (string, error) {
+	ranbuf := make([]byte, ranPasswordByteLength)
+	_, err := rand.Read(ranbuf)
+	if err != nil {
+		return "", err
+	}
+
+	return base64.StdEncoding.EncodeToString(ranbuf), nil
+}

+ 13 - 0
pkg/generator/password_gen_test.go

@@ -0,0 +1,13 @@
+package generator
+
+import (
+	"testing"
+)
+
+func TestPasswordGen(t *testing.T) {
+	pass, err := GenRandomPassword()
+	if err != nil {
+		t.Error(err)
+	}
+	t.Log(pass)
+}

+ 20 - 0
pkg/generator/token_gen.go

@@ -0,0 +1,20 @@
+package generator
+
+import (
+	"crypto/rand"
+)
+
+const (
+	ranTokendByteLength = 16
+)
+
+// gen random token bytes
+func GenRandomToken() ([]byte, error) {
+	ranbuf := make([]byte, ranTokendByteLength)
+	_, err := rand.Read(ranbuf)
+	if err != nil {
+		return nil, err
+	}
+
+	return ranbuf, nil
+}

+ 13 - 0
pkg/generator/token_gen_test.go

@@ -0,0 +1,13 @@
+package generator
+
+import (
+	"testing"
+)
+
+func TestTokenGen(t *testing.T) {
+	pass, err := GenRandomToken()
+	if err != nil {
+		t.Error(err)
+	}
+	t.Log(pass)
+}

+ 22 - 0
pkg/models/application.go

@@ -0,0 +1,22 @@
+// application is app who will use the cloud api
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+type Application struct {
+	gorm.Model
+	// App-Key for api
+	AppKey string `sql:"type:varchar(200);not null;"`
+	// App-Token for web hook
+	AppToken string `sql:"type:varchar(200);not null;"`
+	// Report Url for web hook
+	ReportUrl string `sql:"type:varchar(200);not null;"`
+	// name
+	AppName string `sql:"type:varchar(200);"`
+	// desc
+	AppDescription string `sql:"type:text;"`
+	// app domain which allows wildcard string like "*", "vendor/12", "product/10"
+	AppDomain string `sql:"type:varchar(200);not null;"`
+}

+ 23 - 0
pkg/models/device.go

@@ -0,0 +1,23 @@
+// device is a product instance, which is managed by our platform
+package models
+
+import "github.com/jinzhu/gorm"
+
+// Device device model
+type Device struct {
+	gorm.Model
+	// which product the device belongs to
+	ProductID int32
+	// universal device identifier, generated from vendorid-productid-deviceserial
+	DeviceIdentifier string `sql:"type:varchar(200);not null;unique;key"`
+	// device secret which is auto generated by the platform
+	DeviceSecret string `sql:"type:varchar(200);not null;"`
+	// device key is used to auth a device
+	DeviceKey string `sql:"type:varchar(200);not null;key;"`
+	// device name
+	DeviceName string `sql:"type:varchar(200);not null;"`
+	// device desc
+	DeviceDescription string `sql:"type:text;not null;"`
+	// device version(the agent version)
+	DeviceVersion string `sql:"type:text;not null;"`
+}

+ 12 - 0
pkg/models/privilege.go

@@ -0,0 +1,12 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Privilege 权限列表
+type Privilege struct {
+	gorm.Model
+	PrivilegeName string `gorm:"size:50;not null;"`
+	PrivilegeID   int32
+}

+ 23 - 0
pkg/models/product.go

@@ -0,0 +1,23 @@
+// product is a abstract define of same devices made by some vendor
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Product product
+type Product struct {
+	gorm.Model
+	// which vendor
+	VendorID int32
+	// name
+	ProductName string `sql:"type:varchar(200);not null;"`
+	// desc
+	ProductDescription string `sql:"type:text;not null;"`
+	// product key to auth a product
+	ProductKey string `sql:"type:varchar(200);not null;unique;key;"`
+	// product config string (JSON)
+	ProductConfig string `sql:"type:text; not null;"`
+
+	Devices []Device
+}

+ 12 - 0
pkg/models/roles.go

@@ -0,0 +1,12 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Role 角色表
+type Role struct {
+	gorm.Model
+	RoleName string `gorm:"size:50;not null"`
+	RoleCode int32
+}

+ 23 - 0
pkg/models/rule.go

@@ -0,0 +1,23 @@
+// rule is used for automated works such as timers, ifttts.
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Rule rule
+type Rule struct {
+	gorm.Model
+	// which device the rule belongs to
+	DeviceID int64
+	// rule type, timmer | ifttt
+	RuleType string `sql:"type:varchar(20);not null;"`
+	// which action triggers the rule
+	Trigger string `sql:"type:varchar(200);not null;"`
+	// where to send
+	Target string `sql:"type:varchar(200);not null;"`
+	// what action to take.
+	Action string `sql:"type:varchar(200);not null;"`
+	// if trigger once
+	Once bool `sql:"default(false)"`
+}

+ 35 - 0
pkg/models/user.go

@@ -0,0 +1,35 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// User user
+type User struct {
+	gorm.Model
+	UserKey    string
+	UserRoleID int
+	UserName   string `sql:"type:varchar(20);not null;"`
+	UserPass   string `sql:"type:varchar(50);not null;"`
+	Phone      string `sql:"type:varchar(20);not null;"`
+	Email      string `sql:"type:varchar(200);not null;"`
+	UserType   int    `sql:"default:1;not null;"`
+	VendorID   int
+	Status     int `sql:"default:1;not null;"`
+	Vendor     Vendor
+}
+
+// LoginRequest 登录请求
+type LoginRequest struct {
+	UserName string `json:"login_name"`
+	Password string `json:"login_pass"`
+}
+
+// Reqrequest 注册请求
+type Reqrequest struct {
+	UserName   string `json:"username"`
+	PassWord   string `json:"password"`
+	Phone      string `json:"phone"`
+	Email      string `json:"email"`
+	VendorName string `json:"company"`
+}

+ 18 - 0
pkg/models/vendor.go

@@ -0,0 +1,18 @@
+package models
+
+import (
+	"github.com/jinzhu/gorm"
+)
+
+// Vendor vendor
+// vendor is those who make products
+type Vendor struct {
+	gorm.Model
+	// vendor name
+	VendorName string `sql:"type:varchar(200);not null;"`
+	// vendor key
+	VendorKey string `sql:"type:varchar(200);not null;key;"`
+	// vendor description
+	VendorDescription string `sql:"type:text;not null;"`
+	Products          []Product
+}

+ 58 - 0
pkg/mongo/recorder.go

@@ -0,0 +1,58 @@
+package mongo
+
+import (
+	"labix.org/v2/mgo"
+	"labix.org/v2/mgo/bson"
+)
+
+type Recorder struct {
+	session    *mgo.Session
+	set        string
+	collection string
+}
+
+func NewRecorder(host string, set string, collection string) (*Recorder, error) {
+	sess, err := mgo.Dial(host)
+	if err != nil {
+		return nil, err
+	}
+
+	sess.DB(set).C(collection).EnsureIndexKey("deviceid", "timestamp")
+
+	return &Recorder{
+		session:    sess,
+		set:        set,
+		collection: collection,
+	}, nil
+}
+
+func (r *Recorder) Insert(args interface{}) error {
+	dbHandler := r.session.DB(r.set).C(r.collection)
+
+	err := dbHandler.Insert(args)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (r *Recorder) FindLatest(deviceid uint64, record interface{}) error {
+	dbHandler := r.session.DB(r.set).C(r.collection)
+	err := dbHandler.Find(bson.M{
+		"$query":   bson.M{"deviceid": deviceid},
+		"$orderby": bson.M{"timestamp": -1},
+	}).Limit(1).One(record)
+
+	return err
+}
+
+func (r *Recorder) FindByTimestamp(deviceid uint64, start uint64, end uint64, records interface{}) error {
+	dbHandler := r.session.DB(r.set).C(r.collection)
+	err := dbHandler.Find(bson.M{
+		"deviceid":  deviceid,
+		"timestamp": bson.M{"$gte": start, "$lte": end},
+	}).All(records)
+
+	return err
+}

+ 63 - 0
pkg/mongo/recorder_test.go

@@ -0,0 +1,63 @@
+package mongo
+
+import (
+	"sparrow/pkg/protocol"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/tlv"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestRecorder(t *testing.T) {
+	r, err := NewRecorder("localhost", "pandocloud", "commands")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	tlvs, err := tlv.MakeTLVs([]interface{}{float64(0.1), int64(100), uint64(200)})
+	if err != nil {
+		t.Error(err)
+	}
+
+	deviceid := uint64(12345)
+	timestamp := uint64(time.Now().Unix() * 1000)
+
+	subdata := protocol.SubData{
+		Head:   protocol.SubDataHead{1, 2, 3},
+		Params: tlvs,
+	}
+
+	subdatas := []protocol.SubData{}
+
+	subdatas = append(subdatas, subdata)
+
+	data := rpcs.ArgsOnStatus{
+		DeviceId:  deviceid,
+		Timestamp: timestamp,
+		Subdata:   subdatas,
+	}
+
+	err = r.Insert(data)
+	if err != nil {
+		t.Error(err)
+	}
+
+	readData := rpcs.ArgsOnStatus{}
+	err = r.FindLatest(deviceid, &readData)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	if !reflect.DeepEqual(data, readData) {
+		t.Errorf("read data want %v, but got %v", data, readData)
+	}
+
+	readDatas := []rpcs.ArgsOnStatus{}
+	err = r.FindByTimestamp(deviceid, timestamp, timestamp, &readDatas)
+	t.Log(readDatas)
+	if !reflect.DeepEqual(data, readDatas[0]) {
+		t.Errorf("read data by timestamp want %v, but got %v", data, readDatas[0])
+	}
+}

+ 35 - 0
pkg/mqtt/broker.go

@@ -0,0 +1,35 @@
+package mqtt
+
+import (
+	"net"
+	"time"
+)
+
+type Broker struct {
+	mgr *Manager
+}
+
+func NewBroker(p Provider) *Broker {
+	// manager
+	mgr := NewManager(p)
+
+	handler := &Broker{mgr: mgr}
+
+	return handler
+}
+
+func (b *Broker) Handle(conn net.Conn) {
+	b.mgr.NewConn(conn)
+}
+
+func (b *Broker) SendMessageToDevice(deviceid uint64, msgtype string, message []byte, timeout time.Duration) error {
+	msg := &Publish{}
+	msg.Header.QosLevel = QosAtLeastOnce
+	msg.TopicName = msgtype
+	msg.Payload = BytesPayload(message)
+	return b.mgr.PublishMessage2Device(deviceid, msg, timeout)
+}
+
+func (b *Broker) GetToken(deviceid uint64) ([]byte, error) {
+	return b.mgr.GetToken(deviceid)
+}

+ 309 - 0
pkg/mqtt/connection.go

@@ -0,0 +1,309 @@
+package mqtt
+
+import (
+	"encoding/hex"
+	"errors"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"net"
+	"time"
+)
+
+const (
+	SendChanLen      = 16
+	defaultKeepAlive = 30
+)
+
+type ResponseType struct {
+	SendTime    uint8
+	PublishType uint8
+	DataType    string
+}
+
+type Connection struct {
+	Mgr             *Manager
+	DeviceId        uint64
+	Conn            net.Conn
+	SendChan        chan Message
+	MessageId       uint16
+	MessageWaitChan map[uint16]chan error
+	KeepAlive       uint16
+	LastHbTime      int64
+	Token           []byte
+}
+
+func NewConnection(conn net.Conn, mgr *Manager) *Connection {
+	sendchan := make(chan Message, SendChanLen)
+	c := &Connection{
+		Conn:            conn,
+		SendChan:        sendchan,
+		Mgr:             mgr,
+		KeepAlive:       defaultKeepAlive,
+		MessageWaitChan: make(map[uint16]chan error),
+	}
+
+	go c.SendMsgToClient()
+	go c.RcvMsgFromClient()
+
+	return c
+}
+
+func (c *Connection) Submit(msg Message) {
+	if c.Conn != nil {
+		c.SendChan <- msg
+	}
+}
+
+// Publish will publish a message , and return a chan to wait for completion.
+func (c *Connection) Publish(msg Message, timeout time.Duration) error {
+	server.Log.Debugf("publishing message : %v, timeout %v", msg, timeout)
+
+	message := msg.(*Publish)
+	message.MessageId = c.MessageId
+	c.MessageId++
+	c.Submit(message)
+
+	ch := make(chan error)
+
+	// we don't wait for confirm.
+	if timeout == 0 {
+		return nil
+	}
+
+	c.MessageWaitChan[message.MessageId] = ch
+	// wait for timeout and
+	go func() {
+		timer := time.NewTimer(timeout)
+		<-timer.C
+		waitCh, exist := c.MessageWaitChan[message.MessageId]
+		if exist {
+			waitCh <- errors.New("timeout pushlishing message.")
+			delete(c.MessageWaitChan, message.MessageId)
+			close(waitCh)
+		}
+	}()
+
+	err := <-ch
+	return err
+}
+
+func (c *Connection) confirmPublish(messageid uint16) {
+	waitCh, exist := c.MessageWaitChan[messageid]
+	if exist {
+		waitCh <- nil
+		delete(c.MessageWaitChan, messageid)
+		close(waitCh)
+	}
+}
+
+func (c *Connection) ValidateToken(token []byte) error {
+
+	err := c.Mgr.Provider.ValidateDeviceToken(c.DeviceId, token)
+	if err != nil {
+		return err
+	}
+
+	c.Token = token
+
+	return nil
+}
+
+func (c *Connection) Close() {
+	deviceid := c.DeviceId
+	server.Log.Infof("closing connection of device %v", deviceid)
+	if c.Conn != nil {
+		c.Conn.Close()
+		c.Conn = nil
+		c.Mgr.Provider.OnDeviceOffline(deviceid)
+	}
+	if c.SendChan != nil {
+		close(c.SendChan)
+		c.SendChan = nil
+	}
+}
+
+func (c *Connection) RcvMsgFromClient() {
+	conn := c.Conn
+	host := conn.RemoteAddr().String()
+	server.Log.Infof("recieve new connection from %s", host)
+	for {
+		msg, err := DecodeOneMessage(conn)
+		if err != nil {
+
+			server.Log.Errorf("read error: %s", err)
+			c.Close()
+			return
+		}
+
+		server.Log.Infof("%s, come msg===\n%v\n=====", host, msg)
+		c.LastHbTime = time.Now().Unix()
+		switch msg := msg.(type) {
+		case *Connect:
+			ret := RetCodeAccepted
+			if msg.ProtocolVersion == 3 && msg.ProtocolName != "MQIsdp" {
+				ret = RetCodeUnacceptableProtocolVersion
+			} else if msg.ProtocolVersion == 4 && msg.ProtocolName != "MQTT" {
+				ret = RetCodeUnacceptableProtocolVersion
+			} else if msg.ProtocolVersion > 4 {
+				ret = RetCodeUnacceptableProtocolVersion
+			}
+
+			if len(msg.ClientId) < 1 || len(msg.ClientId) > 23 {
+				server.Log.Warn("invalid clientid length: %d", len(msg.ClientId))
+				ret = RetCodeIdentifierRejected
+				c.Close()
+				return
+			}
+
+			deviceid, err := ClientIdToDeviceId(msg.ClientId)
+			if err != nil {
+				server.Log.Warn("invalid Identify: %d", ret)
+				c.Close()
+				return
+			}
+			c.DeviceId = deviceid
+
+			token, err := hex.DecodeString(msg.Password)
+			if err != nil {
+				server.Log.Warn("token format error : %v", err)
+				ret = RetCodeNotAuthorized
+				c.Close()
+				return
+			}
+			err = c.ValidateToken(token)
+			if err != nil {
+				server.Log.Warn("validate token error : %v", err)
+				ret = RetCodeNotAuthorized
+				c.Close()
+				return
+			}
+
+			if ret != RetCodeAccepted {
+				server.Log.Warn("invalid CON: %d", ret)
+				c.Close()
+				return
+			}
+
+			args := rpcs.ArgsGetOnline{
+				Id:                c.DeviceId,
+				ClientIP:          host,
+				AccessRPCHost:     server.GetRPCHost(),
+				HeartbeatInterval: uint32(c.KeepAlive),
+			}
+
+			c.Mgr.AddConn(c.DeviceId, c)
+			connack := &ConnAck{
+				ReturnCode: ret,
+			}
+
+			c.Submit(connack)
+			c.KeepAlive = msg.KeepAliveTimer
+
+			err = c.Mgr.Provider.OnDeviceOnline(args)
+			if err != nil {
+				server.Log.Warn("device online error : %v", err)
+				c.Close()
+				return
+			}
+
+			server.Log.Infof("device %d, connected to server now, host: %s", c.DeviceId, host)
+
+		case *Publish:
+			server.Log.Infof("%s, publish topic: %s", host, msg.TopicName)
+
+			c.Mgr.PublishMessage2Server(c.DeviceId, msg)
+			if msg.QosLevel.IsAtLeastOnce() {
+				server.Log.Infof("publish ack send now")
+				publishack := &PubAck{MessageId: msg.MessageId}
+				c.Submit(publishack)
+			} else if msg.QosLevel.IsExactlyOnce() {
+				server.Log.Infof("publish Rec send now")
+				publishRec := &PubRec{MessageId: msg.MessageId}
+				c.Submit(publishRec)
+			}
+
+			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
+			if err != nil {
+				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
+				c.Close()
+				return
+			}
+
+		case *PubAck:
+			server.Log.Infof("%s, comes publish ack", host)
+			c.confirmPublish(msg.MessageId)
+			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
+			if err != nil {
+				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
+				c.Close()
+				return
+			}
+
+		case *PubRec:
+			server.Log.Infof("%s, comes publish rec", host)
+			publishRel := &PubRel{MessageId: msg.MessageId}
+			c.Submit(publishRel)
+
+		case *PubRel:
+			server.Log.Infof("%s, comes publish rel", host)
+			publishCom := &PubComp{MessageId: msg.MessageId}
+			c.Submit(publishCom)
+
+		case *PubComp:
+			server.Log.Infof("%s, comes publish comp", host)
+			c.confirmPublish(msg.MessageId)
+			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
+			if err != nil {
+				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
+				c.Close()
+				return
+			}
+
+		case *PingReq:
+			server.Log.Infof("%s, ping req comes", host)
+			pingrsp := &PingResp{}
+			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
+			if err != nil {
+				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
+				c.Close()
+				return
+			}
+			c.Submit(pingrsp)
+
+		case *Subscribe:
+			server.Log.Infof("%s, subscribe topic: %v", host, msg.Topics)
+
+		case *Unsubscribe:
+			server.Log.Infof("%s, unsubscribe topic: %v", host, msg.Topics)
+
+		case *Disconnect:
+			server.Log.Infof("%s, disconnect now, exit...", host)
+			c.Close()
+			return
+
+		default:
+			server.Log.Errorf("unknown msg type %T", msg)
+			c.Close()
+			return
+		}
+	}
+
+}
+
+func (c *Connection) SendMsgToClient() {
+	host := c.Conn.RemoteAddr()
+	for {
+		msg, ok := <-c.SendChan
+		if !ok {
+			server.Log.Errorf("%s is end now", host)
+			return
+		}
+
+		server.Log.Debugf("send msg to %s=======\n%v\n=========", host, msg)
+		err := msg.Encode(c.Conn)
+		if err != nil {
+			server.Log.Errorf("send msg err: %s=====\n%v\n=====", err, msg)
+			continue
+		}
+	}
+}

+ 102 - 0
pkg/mqtt/manager.go

@@ -0,0 +1,102 @@
+package mqtt
+
+import (
+	"sparrow/pkg/server"
+	"net"
+	"sync"
+	"time"
+)
+
+type Manager struct {
+	Provider Provider
+	CxtMutex sync.RWMutex
+	IdToConn map[uint64]*Connection
+}
+
+func NewManager(p Provider) *Manager {
+	m := &Manager{
+		Provider: p,
+		IdToConn: make(map[uint64]*Connection),
+	}
+
+	go m.CleanWorker()
+
+	return m
+}
+
+func (m *Manager) NewConn(conn net.Conn) {
+	NewConnection(conn, m)
+}
+
+func (m *Manager) AddConn(id uint64, c *Connection) {
+	m.CxtMutex.Lock()
+	oldSub, exist := m.IdToConn[id]
+	if exist {
+		oldSub.Close()
+	}
+
+	m.IdToConn[id] = c
+	m.CxtMutex.Unlock()
+}
+
+func (m *Manager) DelConn(id uint64) {
+	m.CxtMutex.Lock()
+	_, exist := m.IdToConn[id]
+
+	if exist {
+		delete(m.IdToConn, id)
+	}
+	m.CxtMutex.Unlock()
+}
+
+func (m *Manager) GetToken(deviceid uint64) ([]byte, error) {
+	m.CxtMutex.RLock()
+	con, exist := m.IdToConn[deviceid]
+	m.CxtMutex.RUnlock()
+	if !exist {
+		return nil, errorf("device not exist: %v[%v]", deviceid, deviceid)
+	}
+
+	return con.Token, nil
+}
+
+func (m *Manager) PublishMessage2Device(deviceid uint64, msg *Publish, timeout time.Duration) error {
+	m.CxtMutex.RLock()
+	con, exist := m.IdToConn[deviceid]
+	m.CxtMutex.RUnlock()
+	if !exist {
+		return errorf("device not exist: %v", deviceid)
+	}
+
+	return con.Publish(msg, timeout)
+}
+
+func (m *Manager) PublishMessage2Server(deviceid uint64, msg *Publish) error {
+	topic := msg.TopicName
+
+	payload := msg.Payload.(BytesPayload)
+
+	m.Provider.OnDeviceMessage(deviceid, topic, payload)
+	return nil
+}
+
+func (m *Manager) CleanWorker() {
+	for {
+		server.Log.Infoln("scanning and removing inactive connections...")
+		curTime := time.Now().Unix()
+
+		for _, con := range m.IdToConn {
+			if con.KeepAlive == 0 {
+				continue
+			}
+
+			if uint16(curTime-con.LastHbTime) > uint16(3*con.KeepAlive/2) {
+				server.Log.Infof("connection %v inactive , removing", con)
+				con.Close()
+				delete(m.IdToConn, con.DeviceId)
+			}
+		}
+
+		time.Sleep(60 * time.Second)
+	}
+}

+ 696 - 0
pkg/mqtt/message.go

@@ -0,0 +1,696 @@
+package mqtt
+
+import (
+	"bytes"
+	"errors"
+	"io"
+)
+
+// OoS only support QoS 0
+const (
+	QosAtMostOnce = TagQosLevel(iota)
+	QosAtLeastOnce
+	QosExactlyOnce
+	QosInvalid
+)
+
+// Max Payload size
+const (
+	MaxPayloadSize = (1 << (4 * 7)) - 1
+)
+
+type TagQosLevel uint8
+
+func (qos TagQosLevel) IsValid() bool {
+	return qos < QosInvalid && qos >= QosAtMostOnce
+}
+
+func (qos TagQosLevel) HasId() bool {
+	return qos == QosAtLeastOnce || qos == QosExactlyOnce
+}
+
+func (qos TagQosLevel) IsAtLeastOnce() bool {
+	return qos == QosAtLeastOnce
+}
+
+func (qos TagQosLevel) IsExactlyOnce() bool {
+	return qos == QosExactlyOnce
+}
+
+// Message Type
+const (
+	MsgConnect = TagMessageType(iota + 1)
+	MsgConnAck
+	MsgPublish
+	MsgPubAck
+	MsgPubRec
+	MsgPubRel
+	MsgPubComp
+	MsgSubscribe
+	MsgSubAck
+	MsgUnsubscribe
+	MsgUnsubAck
+	MsgPingReq
+	MsgPingResp
+	MsgDisconnect
+	MsgInvalid
+)
+
+//  retcode
+const (
+	RetCodeAccepted = TagRetCode(iota)
+	RetCodeUnacceptableProtocolVersion
+	RetCodeIdentifierRejected
+	RetCodeServerUnavailable
+	RetCodeBadUsernameOrPassword
+	RetCodeNotAuthorized
+	RetCodeInvalid
+)
+
+type TagRetCode uint8
+
+func (rc TagRetCode) IsValid() bool {
+	return rc >= RetCodeAccepted && rc < RetCodeInvalid
+}
+
+type TagMessageType uint8
+
+func (msg TagMessageType) IsValid() bool {
+	return msg >= MsgConnect && msg < MsgInvalid
+}
+
+// message interface
+type Message interface {
+	Encode(w io.Writer) error
+	Decode(r io.Reader, hdr Header, packetRemaining int32) error
+}
+
+// message fix header
+type Header struct {
+	DupFlag  bool
+	QosLevel TagQosLevel
+	Retain   bool
+}
+
+func (hdr *Header) Encode(w io.Writer, msgType TagMessageType, remainingLength int32) error {
+	buf := new(bytes.Buffer)
+	err := hdr.EncodeInto(buf, msgType, remainingLength)
+	if err != nil {
+		return err
+	}
+
+	_, err = w.Write(buf.Bytes())
+
+	return err
+}
+
+func (hdr *Header) EncodeInto(buf *bytes.Buffer, msgType TagMessageType, remainingLength int32) error {
+	if !hdr.QosLevel.IsValid() {
+		return errors.New("Invalid Qos level")
+	}
+
+	if !msgType.IsValid() {
+		return errors.New("Invalid MsgType")
+	}
+
+	val := byte(msgType) << 4
+	val |= (boolToByte(hdr.DupFlag) << 3)
+	val |= byte(hdr.QosLevel) << 1
+	val |= boolToByte(hdr.Retain)
+	buf.WriteByte(val)
+	encodeLength(remainingLength, buf)
+
+	return nil
+}
+
+func (hdr *Header) Decode(r io.Reader) (msgType TagMessageType, remainingLength int32, err error) {
+	var buf [1]byte
+
+	if _, err := io.ReadFull(r, buf[:]); err != nil {
+		return 0, 0, err
+	}
+
+	byte1 := buf[0]
+	msgType = TagMessageType(byte1 & 0xf0 >> 4)
+
+	*hdr = Header{
+		DupFlag:  byte1&0x08 > 0,
+		QosLevel: TagQosLevel(byte1 & 0x06 >> 1),
+		Retain:   byte1&0x01 > 0,
+	}
+
+	remainingLength, err = decodeLength(r)
+
+	return msgType, remainingLength, err
+}
+
+func writeMessage(w io.Writer, msgType TagMessageType, hdr *Header, payloadBuf *bytes.Buffer, extraLength int32) error {
+	totalPayloadLength := int64(len(payloadBuf.Bytes())) + int64(extraLength)
+	if totalPayloadLength > MaxPayloadSize {
+		return errors.New("message too long")
+	}
+
+	buf := new(bytes.Buffer)
+	err := hdr.EncodeInto(buf, msgType, int32(totalPayloadLength))
+	if err != nil {
+		return err
+	}
+
+	buf.Write(payloadBuf.Bytes())
+	_, err = w.Write(buf.Bytes())
+
+	return err
+}
+
+// Connect represents an MQTT CONNECT message.
+type Connect struct {
+	Header
+	ProtocolName               string
+	ProtocolVersion            uint8
+	WillRetain                 bool
+	WillFlag                   bool
+	CleanSession               bool
+	WillQos                    TagQosLevel
+	KeepAliveTimer             uint16
+	ClientId                   string
+	WillTopic, WillMessage     string
+	UsernameFlag, PasswordFlag bool
+	Username, Password         string
+}
+
+func (msg *Connect) Encode(w io.Writer) (err error) {
+	if msg.WillQos > QosInvalid {
+		return errors.New("invalid Qos")
+	}
+
+	buf := new(bytes.Buffer)
+
+	flags := boolToByte(msg.UsernameFlag) << 7
+	flags |= boolToByte(msg.PasswordFlag) << 6
+	flags |= boolToByte(msg.WillRetain) << 5
+	flags |= byte(msg.WillQos) << 3
+	flags |= boolToByte(msg.WillFlag) << 2
+	flags |= boolToByte(msg.CleanSession) << 1
+
+	setString(msg.ProtocolName, buf)
+	setUint8(msg.ProtocolVersion, buf)
+	buf.WriteByte(flags)
+	setUint16(msg.KeepAliveTimer, buf)
+	setString(msg.ClientId, buf)
+	if msg.WillFlag {
+		setString(msg.WillTopic, buf)
+		setString(msg.WillMessage, buf)
+	}
+	if msg.UsernameFlag {
+		setString(msg.Username, buf)
+	}
+	if msg.PasswordFlag {
+		setString(msg.Password, buf)
+	}
+
+	return writeMessage(w, MsgConnect, &msg.Header, buf, 0)
+}
+
+func (msg *Connect) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	protocolName, err := getString(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	protocolVersion, err := getUint8(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	flags, err := getUint8(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	keepAliveTimer, err := getUint16(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	clientId, err := getString(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+
+	*msg = Connect{
+		Header:          hdr,
+		ProtocolName:    protocolName,
+		ProtocolVersion: protocolVersion,
+		UsernameFlag:    flags&0x80 > 0,
+		PasswordFlag:    flags&0x40 > 0,
+		WillRetain:      flags&0x20 > 0,
+		WillQos:         TagQosLevel(flags & 0x18 >> 3),
+		WillFlag:        flags&0x04 > 0,
+		CleanSession:    flags&0x02 > 0,
+		KeepAliveTimer:  keepAliveTimer,
+		ClientId:        clientId,
+	}
+
+	if msg.WillFlag {
+		msg.WillTopic, err = getString(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+		msg.WillMessage, err = getString(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+	}
+	if msg.UsernameFlag {
+		msg.Username, err = getString(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+	}
+	if msg.PasswordFlag {
+		msg.Password, err = getString(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+	}
+
+	if packetRemaining != 0 {
+		return errors.New("message too long")
+	}
+
+	return nil
+}
+
+// ConnAck represents an MQTT CONNACK message.
+type ConnAck struct {
+	Header
+	ReturnCode TagRetCode
+}
+
+func (msg *ConnAck) Encode(w io.Writer) (err error) {
+	buf := new(bytes.Buffer)
+
+	buf.WriteByte(byte(0)) // Reserved byte.
+	setUint8(uint8(msg.ReturnCode), buf)
+
+	return writeMessage(w, MsgConnAck, &msg.Header, buf, 0)
+}
+
+func (msg *ConnAck) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+
+	_, err = getUint8(r, &packetRemaining) // Skip reserved byte.
+	if err != nil {
+		return err
+	}
+
+	code, err := getUint8(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	msg.ReturnCode = TagRetCode(code)
+	if !msg.ReturnCode.IsValid() {
+		return errors.New("invliad retcode")
+	}
+
+	if packetRemaining != 0 {
+		return errors.New("message too long")
+	}
+
+	return nil
+}
+
+// Publish represents an MQTT PUBLISH message.
+type Publish struct {
+	Header
+	TopicName string
+	MessageId uint16
+	Payload   Payload
+}
+
+func (msg *Publish) Encode(w io.Writer) (err error) {
+	buf := new(bytes.Buffer)
+
+	setString(msg.TopicName, buf)
+	if msg.Header.QosLevel.HasId() {
+		setUint16(msg.MessageId, buf)
+	}
+
+	if err = msg.Payload.WritePayload(buf); err != nil {
+		return err
+	}
+
+	if err = writeMessage(w, MsgPublish, &msg.Header, buf, int32(0)); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (msg *Publish) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+
+	msg.TopicName, err = getString(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	if msg.Header.QosLevel.HasId() {
+		msg.MessageId, err = getUint16(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+	}
+
+	payloadReader := &io.LimitedReader{r, int64(packetRemaining)}
+	msg.Payload = make(BytesPayload, int(packetRemaining))
+
+	return msg.Payload.ReadPayload(payloadReader, int(packetRemaining))
+}
+
+// PubAck represents an MQTT PUBACK message.
+type PubAck struct {
+	Header
+	MessageId uint16
+}
+
+func (msg *PubAck) Encode(w io.Writer) error {
+	return encodeAckCommon(w, &msg.Header, msg.MessageId, MsgPubAck)
+}
+
+func (msg *PubAck) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+	return decodeAckCommon(r, packetRemaining, &msg.MessageId)
+}
+
+// PubRec represents an MQTT PUBREC message.
+type PubRec struct {
+	Header
+	MessageId uint16
+}
+
+func (msg *PubRec) Encode(w io.Writer) error {
+	return encodeAckCommon(w, &msg.Header, msg.MessageId, MsgPubRec)
+}
+
+func (msg *PubRec) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+	return decodeAckCommon(r, packetRemaining, &msg.MessageId)
+}
+
+// PubRel represents an MQTT PUBREL message.
+type PubRel struct {
+	Header
+	MessageId uint16
+}
+
+func (msg *PubRel) Encode(w io.Writer) error {
+	return encodeAckCommon(w, &msg.Header, msg.MessageId, MsgPubRel)
+}
+
+func (msg *PubRel) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+	return decodeAckCommon(r, packetRemaining, &msg.MessageId)
+}
+
+// PubComp represents an MQTT PUBCOMP message.
+type PubComp struct {
+	Header
+	MessageId uint16
+}
+
+func (msg *PubComp) Encode(w io.Writer) error {
+	return encodeAckCommon(w, &msg.Header, msg.MessageId, MsgPubComp)
+}
+
+func (msg *PubComp) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+	return decodeAckCommon(r, packetRemaining, &msg.MessageId)
+}
+
+// Subscribe represents an MQTT SUBSCRIBE message.
+type Subscribe struct {
+	Header
+	MessageId uint16
+	Topics    []TopicQos
+}
+
+type TopicQos struct {
+	Topic string
+	Qos   TagQosLevel
+}
+
+func (msg *Subscribe) Encode(w io.Writer) (err error) {
+	buf := new(bytes.Buffer)
+	if msg.Header.QosLevel.HasId() {
+		setUint16(msg.MessageId, buf)
+	}
+	for _, topicSub := range msg.Topics {
+		setString(topicSub.Topic, buf)
+		setUint8(uint8(topicSub.Qos), buf)
+	}
+
+	return writeMessage(w, MsgSubscribe, &msg.Header, buf, 0)
+}
+
+func (msg *Subscribe) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+
+	if msg.Header.QosLevel.HasId() {
+		msg.MessageId, err = getUint16(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+	}
+	var topics []TopicQos
+	for packetRemaining > 0 {
+		topic, err := getString(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+		qos, err := getUint8(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+		topics = append(topics, TopicQos{
+			Topic: topic,
+			Qos:   TagQosLevel(qos),
+		})
+	}
+	msg.Topics = topics
+
+	return nil
+}
+
+// SubAck represents an MQTT SUBACK message.
+type SubAck struct {
+	Header
+	MessageId uint16
+	TopicsQos []TagQosLevel
+}
+
+func (msg *SubAck) Encode(w io.Writer) (err error) {
+	buf := new(bytes.Buffer)
+	setUint16(msg.MessageId, buf)
+	for i := 0; i < len(msg.TopicsQos); i += 1 {
+		setUint8(uint8(msg.TopicsQos[i]), buf)
+	}
+
+	return writeMessage(w, MsgSubAck, &msg.Header, buf, 0)
+}
+
+func (msg *SubAck) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+
+	msg.MessageId, err = getUint16(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+	topicsQos := make([]TagQosLevel, 0)
+	for packetRemaining > 0 {
+		qos, err := getUint8(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+		grantedQos := TagQosLevel(qos & 0x03)
+		topicsQos = append(topicsQos, grantedQos)
+	}
+	msg.TopicsQos = topicsQos
+
+	return nil
+}
+
+// Unsubscribe represents an MQTT UNSUBSCRIBE message.
+type Unsubscribe struct {
+	Header
+	MessageId uint16
+	Topics    []string
+}
+
+func (msg *Unsubscribe) Encode(w io.Writer) (err error) {
+	buf := new(bytes.Buffer)
+	if msg.Header.QosLevel.HasId() {
+		setUint16(msg.MessageId, buf)
+	}
+	for _, topic := range msg.Topics {
+		setString(topic, buf)
+	}
+
+	return writeMessage(w, MsgUnsubscribe, &msg.Header, buf, 0)
+}
+
+func (msg *Unsubscribe) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+
+	if msg.Header.QosLevel.HasId() {
+		msg.MessageId, err = getUint16(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+	}
+	topics := make([]string, 0)
+	for packetRemaining > 0 {
+		topic, err := getString(r, &packetRemaining)
+		if err != nil {
+			return err
+		}
+		topics = append(topics, topic)
+	}
+	msg.Topics = topics
+
+	return nil
+}
+
+// UnsubAck represents an MQTT UNSUBACK message.
+type UnsubAck struct {
+	Header
+	MessageId uint16
+}
+
+func (msg *UnsubAck) Encode(w io.Writer) error {
+	return encodeAckCommon(w, &msg.Header, msg.MessageId, MsgUnsubAck)
+}
+
+func (msg *UnsubAck) Decode(r io.Reader, hdr Header, packetRemaining int32) (err error) {
+	msg.Header = hdr
+	return decodeAckCommon(r, packetRemaining, &msg.MessageId)
+}
+
+// PingReq represents an MQTT PINGREQ message.
+type PingReq struct {
+	Header
+}
+
+func (msg *PingReq) Encode(w io.Writer) error {
+	return msg.Header.Encode(w, MsgPingReq, 0)
+}
+
+func (msg *PingReq) Decode(r io.Reader, hdr Header, packetRemaining int32) error {
+	if packetRemaining != 0 {
+		return errors.New("msg too long")
+	}
+	return nil
+}
+
+// PingResp represents an MQTT PINGRESP message.
+type PingResp struct {
+	Header
+}
+
+func (msg *PingResp) Encode(w io.Writer) error {
+	return msg.Header.Encode(w, MsgPingResp, 0)
+}
+
+func (msg *PingResp) Decode(r io.Reader, hdr Header, packetRemaining int32) error {
+	if packetRemaining != 0 {
+		return errors.New("msg too long")
+	}
+	return nil
+}
+
+// Disconnect represents an MQTT DISCONNECT message.
+type Disconnect struct {
+	Header
+}
+
+func (msg *Disconnect) Encode(w io.Writer) error {
+	return msg.Header.Encode(w, MsgDisconnect, 0)
+}
+
+func (msg *Disconnect) Decode(r io.Reader, hdr Header, packetRemaining int32) error {
+	if packetRemaining != 0 {
+		return errors.New("msg too long")
+	}
+	return nil
+}
+
+func encodeAckCommon(w io.Writer, hdr *Header, messageId uint16, msgType TagMessageType) error {
+	buf := new(bytes.Buffer)
+	setUint16(messageId, buf)
+	return writeMessage(w, msgType, hdr, buf, 0)
+}
+
+func decodeAckCommon(r io.Reader, packetRemaining int32, messageId *uint16) (err error) {
+	*messageId, err = getUint16(r, &packetRemaining)
+	if err != nil {
+		return err
+	}
+
+	if packetRemaining != 0 {
+		return errors.New("msg too long")
+	}
+
+	return nil
+}
+
+// DecodeOneMessage decodes one message from r. config provides specifics on
+// how to decode messages, nil indicates that the DefaultDecoderConfig should
+// be used.
+func DecodeOneMessage(r io.Reader) (msg Message, err error) {
+	var hdr Header
+	var msgType TagMessageType
+	var packetRemaining int32
+	msgType, packetRemaining, err = hdr.Decode(r)
+	if err != nil {
+		return nil, err
+	}
+
+	msg, err = NewMessage(msgType)
+	if err != nil {
+		return nil, err
+	}
+
+	return msg, msg.Decode(r, hdr, packetRemaining)
+}
+
+func NewMessage(msgType TagMessageType) (msg Message, err error) {
+	switch msgType {
+	case MsgConnect:
+		msg = new(Connect)
+	case MsgConnAck:
+		msg = new(ConnAck)
+	case MsgPublish:
+		msg = new(Publish)
+	case MsgPubAck:
+		msg = new(PubAck)
+	case MsgPubRec:
+		msg = new(PubRec)
+	case MsgPubRel:
+		msg = new(PubRel)
+	case MsgPubComp:
+		msg = new(PubComp)
+	case MsgSubscribe:
+		msg = new(Subscribe)
+	case MsgUnsubAck:
+		msg = new(UnsubAck)
+	case MsgSubAck:
+		msg = new(SubAck)
+	case MsgUnsubscribe:
+		msg = new(Unsubscribe)
+	case MsgPingReq:
+		msg = new(PingReq)
+	case MsgPingResp:
+		msg = new(PingResp)
+	case MsgDisconnect:
+		msg = new(Disconnect)
+	default:
+		return nil, errors.New("msgType error")
+	}
+
+	return msg, nil
+}

+ 43 - 0
pkg/mqtt/payload.go

@@ -0,0 +1,43 @@
+package mqtt
+
+import (
+	"bytes"
+	"io"
+)
+
+// Payload is the interface for Publish payloads. Typically the BytesPayload
+// implementation will be sufficient for small payloads whose full contents
+// will exist in memory. However, other implementations can read or write
+// payloads requiring them holding their complete contents in memory.
+type Payload interface {
+	// Size returns the number of bytes that WritePayload will write.
+	Size() int
+
+	// WritePayload writes the payload data to w. Implementations must write
+	// Size() bytes of data, but it is *not* required to do so prior to
+	// returning. Size() bytes must have been written to w prior to another
+	// message being encoded to the underlying connection.
+	WritePayload(b *bytes.Buffer) error
+
+	// ReadPayload reads the payload data from r (r will EOF at the end of the
+	// payload). It is *not* required for r to have been consumed prior to this
+	// returning. r must have been consumed completely prior to another message
+	// being decoded from the underlying connection.
+	ReadPayload(r io.Reader, n int) error
+}
+
+type BytesPayload []byte
+
+func (p BytesPayload) Size() int {
+	return len(p)
+}
+
+func (p BytesPayload) WritePayload(b *bytes.Buffer) error {
+	_, err := b.Write(p)
+	return err
+}
+
+func (p BytesPayload) ReadPayload(r io.Reader, n int) error {
+	_, err := io.ReadFull(r, p)
+	return err
+}

+ 13 - 0
pkg/mqtt/provider.go

@@ -0,0 +1,13 @@
+package mqtt
+
+import (
+	"sparrow/pkg/rpcs"
+)
+
+type Provider interface {
+	ValidateDeviceToken(deviceid uint64, token []byte) error
+	OnDeviceOnline(args rpcs.ArgsGetOnline) error
+	OnDeviceOffline(deviceid uint64) error
+	OnDeviceHeartBeat(deviceid uint64) error
+	OnDeviceMessage(deviceid uint64, msgtype string, message []byte)
+}

+ 151 - 0
pkg/mqtt/utils.go

@@ -0,0 +1,151 @@
+package mqtt
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+)
+
+func Uint16ToByte(value uint16) []byte {
+	buf := bytes.NewBuffer([]byte{})
+	binary.Write(buf, binary.BigEndian, value)
+
+	return buf.Bytes()
+}
+
+func ByteToUint16(buf []byte) uint16 {
+	tmpBuf := bytes.NewBuffer(buf)
+	var value uint16
+	binary.Read(tmpBuf, binary.BigEndian, &value)
+
+	return value
+}
+
+func DeviceIdToClientId(deviceid uint64) string {
+	return strconv.FormatUint(deviceid, 16)
+}
+
+func ClientIdToDeviceId(identify string) (uint64, error) {
+	deviceId, err := strconv.ParseUint(identify, 16, 64)
+	if err != nil {
+		return uint64(0), err
+	}
+
+	return deviceId, nil
+}
+
+func boolToByte(val bool) byte {
+	if val {
+		return byte(1)
+	}
+	return byte(0)
+}
+
+func encodeLength(length int32, buf *bytes.Buffer) {
+	if length == 0 {
+		buf.WriteByte(0)
+		return
+	}
+
+	for length > 0 {
+		digit := length & 0x7f
+		length = length >> 7
+		if length > 0 {
+			digit = digit | 0x80
+		}
+		buf.WriteByte(byte(digit))
+	}
+}
+
+func decodeLength(r io.Reader) (int32, error) {
+	var v int32
+	var buf [1]byte
+	var shift uint
+	for i := 0; i < 4; i++ {
+		if _, err := io.ReadFull(r, buf[:]); err != nil {
+			return 0, err
+		}
+
+		b := buf[0]
+		v |= int32(b&0x7f) << shift
+
+		if b&0x80 == 0 {
+			return v, nil
+		}
+		shift += 7
+	}
+
+	return 0, errors.New("length decode error")
+}
+
+func setUint8(val uint8, buf *bytes.Buffer) {
+	buf.WriteByte(byte(val))
+}
+
+func setUint16(val uint16, buf *bytes.Buffer) {
+	buf.WriteByte(byte(val & 0xff00 >> 8))
+	buf.WriteByte(byte(val & 0x00ff))
+}
+
+func setString(val string, buf *bytes.Buffer) {
+	length := uint16(len(val))
+	setUint16(length, buf)
+	buf.WriteString(val)
+}
+
+func getUint8(r io.Reader, packetRemaining *int32) (uint8, error) {
+	if *packetRemaining < 1 {
+		return 0, errors.New("dataExceedPacketError")
+	}
+
+	var b [1]byte
+	if _, err := io.ReadFull(r, b[:]); err != nil {
+		return 0, err
+	}
+	*packetRemaining--
+
+	return b[0], nil
+}
+
+func getUint16(r io.Reader, packetRemaining *int32) (uint16, error) {
+	if *packetRemaining < 2 {
+		return 0, errors.New("dataExceedPacketError")
+	}
+
+	var b [2]byte
+	if _, err := io.ReadFull(r, b[:]); err != nil {
+		return 0, err
+	}
+	*packetRemaining -= 2
+
+	return uint16(b[0])<<8 | uint16(b[1]), nil
+}
+
+func getString(r io.Reader, packetRemaining *int32) (string, error) {
+	var retString string
+	len, err := getUint16(r, packetRemaining)
+	if err != nil {
+		return retString, err
+	}
+	strLen := int(len)
+
+	if int(*packetRemaining) < strLen {
+		return retString, errors.New("dataExceedPacketError")
+	}
+
+	b := make([]byte, strLen)
+	if _, err := io.ReadFull(r, b); err != nil {
+		return retString, err
+	}
+	*packetRemaining -= int32(strLen)
+
+	return string(b), nil
+}
+
+func errorf(format string, a ...interface{}) error {
+	err := fmt.Errorf(format, a...)
+	return err
+}

+ 43 - 0
pkg/mysql/client.go

@@ -0,0 +1,43 @@
+package mysql
+
+import (
+	"database/sql"
+	"time"
+
+	_ "github.com/go-sql-driver/mysql"
+)
+
+var mapClients map[string]*sql.DB
+
+func GetClient(dbhost, dbport, dbname, dbuser, dbpass string) (*sql.DB, error) {
+
+	pattern := dbuser + ":" + dbpass + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname
+	_, exist := mapClients[pattern]
+	if !exist {
+		var err error
+		mapClients[pattern], err = sql.Open("mysql", pattern+"?charset=utf8&parseTime=True")
+		if err != nil {
+			return nil, err
+		}
+		err = mapClients[pattern].Ping()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return mapClients[pattern], nil
+}
+
+func init() {
+	mapClients = make(map[string]*sql.DB)
+
+	timer := time.NewTicker(30 * time.Second)
+	go func() {
+		for {
+			<-timer.C
+			for _, db := range mapClients {
+				db.Ping()
+			}
+		}
+	}()
+}

+ 50 - 0
pkg/mysql/migrate.go

@@ -0,0 +1,50 @@
+// database initial and migrate
+package mysql
+
+import (
+	"fmt"
+	"sparrow/pkg/models"
+
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/jinzhu/gorm"
+)
+
+func MigrateDatabase(dbhost, dbport, dbname, dbuser, dbpass string) error {
+	mysqldb, err := GetClient(dbhost, dbport, dbname, dbuser, dbpass)
+	if err != nil {
+		return err
+	}
+	db, err := gorm.Open("mysql", mysqldb)
+	if err != nil {
+		return err
+	}
+
+	// Then you could invoke `*sql.DB`'s functions with it
+	err = db.DB().Ping()
+	if err != nil {
+		return err
+	}
+
+	// Disable table name's pluralization
+	db.SingularTable(true)
+	db.LogMode(false)
+
+	db.DB().Query("CREATE DATABASE SparrowCloud; ")
+	db.DB().Query("USE SparrowCloud;")
+	// Automating Migration
+	err = db.Set("gorm:table_options", "ENGINE=MyISAM").AutoMigrate(
+		&models.Device{},
+		&models.Product{},
+		&models.Vendor{},
+		&models.Application{},
+		&models.Rule{},
+		&models.User{},
+		&models.Privilege{},
+		&models.Role{},
+	).Error
+	if err != nil {
+		fmt.Printf("%s", err.Error())
+	}
+
+	return nil
+}

+ 12 - 0
pkg/mysql/migrate_test.go

@@ -0,0 +1,12 @@
+package mysql
+
+import (
+	"testing"
+)
+
+func TestMigrate(t *testing.T) {
+	err := MigrateDatabase("localhost", "3306", "", "root", "")
+	if err != nil {
+		t.Error(err)
+	}
+}

+ 114 - 0
pkg/online/online.go

@@ -0,0 +1,114 @@
+// package online manage device online state and store it in redis.
+package online
+
+import (
+	"errors"
+	"sparrow/pkg/redispool"
+	"sparrow/pkg/serializer"
+	"github.com/garyburd/redigo/redis"
+	"strconv"
+)
+
+const (
+	OnlineStatusKeyPrefix = "device:onlinestatus:"
+)
+
+type Status struct {
+	ClientIP          string
+	AccessRPCHost     string
+	HeartbeatInterval uint32
+}
+
+type Manager struct {
+	redishost string
+}
+
+func NewManager(host string) *Manager {
+	mgr := &Manager{
+		redishost: host,
+	}
+	return mgr
+}
+
+func (mgr *Manager) GetStatus(id uint64) (*Status, error) {
+	key := OnlineStatusKeyPrefix + strconv.FormatUint(id, 10)
+	conn, err := redispool.GetClient(mgr.redishost)
+	if err != nil {
+		return nil, err
+	}
+
+	status := &Status{}
+	// get status from redis
+	bufferStr, err := redis.String(conn.Do("GET", key))
+	if err != nil {
+		return nil, err
+	}
+	err = serializer.String2Struct(bufferStr, status)
+	if err != nil {
+		return nil, err
+	}
+
+	return status, nil
+}
+
+func (mgr *Manager) GetOnline(id uint64, status Status) error {
+	key := OnlineStatusKeyPrefix + strconv.FormatUint(id, 10)
+	conn, err := redispool.GetClient(mgr.redishost)
+	if err != nil {
+		return err
+	}
+	// serialize and store the device's online status info in redis
+	bufferStr, err := serializer.Struct2String(status)
+	if err != nil {
+		return err
+	}
+	_, err = conn.Do("SET", key, bufferStr)
+	if err != nil {
+		return err
+	}
+	_, err = conn.Do("EXPIRE", key, status.HeartbeatInterval+status.HeartbeatInterval/2)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (mgr *Manager) SetHeartbeat(id uint64) error {
+	status, err := mgr.GetStatus(id)
+	if err != nil {
+		return err
+	}
+
+	if status == nil {
+		return errors.New("device offline.")
+	}
+
+	key := OnlineStatusKeyPrefix + strconv.FormatUint(id, 10)
+	conn, err := redispool.GetClient(mgr.redishost)
+	if err != nil {
+		return err
+	}
+
+	_, err = conn.Do("EXPIRE", key, status.HeartbeatInterval+status.HeartbeatInterval/2)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (mgr *Manager) GetOffline(id uint64) error {
+	key := OnlineStatusKeyPrefix + strconv.FormatUint(id, 10)
+	conn, err := redispool.GetClient(mgr.redishost)
+	if err != nil {
+		return err
+	}
+
+	_, err = conn.Do("DEL", key)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 77 - 0
pkg/online/online_test.go

@@ -0,0 +1,77 @@
+package online
+
+import (
+	"github.com/garyburd/redigo/redis"
+	"reflect"
+	"testing"
+	"time"
+)
+
+var testid = uint64(100)
+
+func checkOnlineStatus(t *testing.T, mgr *Manager, status Status) {
+	readstatus, err := mgr.GetStatus(testid)
+	if err != nil && err != redis.ErrNil {
+		t.Error(err)
+	}
+
+	if readstatus == nil {
+		t.Errorf("device should be online, but is offline.")
+	}
+
+	if !reflect.DeepEqual(status, *readstatus) {
+		t.Errorf("get status test error, want %v, got %v", status, *readstatus)
+	}
+}
+
+func checkOfflineStatus(t *testing.T, mgr *Manager) {
+	readstatus, err := mgr.GetStatus(testid)
+	if err != nil && err != redis.ErrNil {
+		t.Error(err)
+	}
+
+	if readstatus != nil {
+		t.Errorf("device should be offline, but got status: %v", readstatus)
+	}
+
+}
+
+func TestManager(t *testing.T) {
+	mgr := NewManager("localhost:6379")
+
+	status := Status{
+		ClientIP:          "3.3.3.3",
+		AccessRPCHost:     "192.168.9.1:20030",
+		HeartbeatInterval: 2,
+	}
+
+	err := mgr.GetOnline(testid, status)
+	if err != nil {
+		t.Error(err)
+	}
+
+	checkOnlineStatus(t, mgr, status)
+
+	cnt := 0
+	for {
+		time.Sleep(time.Second * 2)
+		if cnt > 2 {
+			break
+		}
+		err := mgr.SetHeartbeat(testid)
+		if err != nil {
+			t.Error(err)
+		}
+		cnt++
+	}
+
+	checkOnlineStatus(t, mgr, status)
+
+	err = mgr.GetOffline(testid)
+	if err != nil {
+		t.Error(err)
+	}
+
+	checkOfflineStatus(t, mgr)
+
+}

+ 212 - 0
pkg/productconfig/productconfig.go

@@ -0,0 +1,212 @@
+package productconfig
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"sparrow/pkg/protocol"
+	"sparrow/pkg/tlv"
+)
+
+type CommandOrEventParam struct {
+	ValueType int32 `json:"value_type"`
+	Name      string
+}
+
+type ProductCommandOrEvent struct {
+	No       int
+	Part     int
+	Name     string
+	Priority int
+	Params   []CommandOrEventParam
+}
+
+type StatusParam struct {
+	ValueType int32 `json:"value_type"`
+	Name      string
+}
+
+type ProductObject struct {
+	Id     int
+	No     int
+	Label  string
+	Part   int
+	Status []StatusParam
+}
+
+// product config parses the JSON product config string.
+type ProductConfig struct {
+	Objects  []ProductObject
+	Commands []ProductCommandOrEvent
+	Events   []ProductCommandOrEvent
+}
+
+func New(config string) (*ProductConfig, error) {
+	v := &ProductConfig{}
+	err := json.Unmarshal([]byte(config), v)
+	if err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+func (config *ProductConfig) ValidateStatus(label string, params []interface{}) (*ProductObject, []interface{}, error) {
+	// search for status name
+	var paramInfo []StatusParam
+	var status *ProductObject
+	found := false
+	for _, obj := range config.Objects {
+		if obj.Label == label {
+			paramInfo = obj.Status
+			status = &obj
+			found = true
+			break
+		}
+	}
+	if found == false {
+		return nil, []interface{}{}, errors.New("object not found.")
+	}
+	if len(paramInfo) != len(params) {
+		return nil, []interface{}{}, errors.New("wrong status parameters.")
+	}
+	realParams := make([]interface{}, len(params))
+	for idx, para := range paramInfo {
+		realParams[idx] = tlv.CastTLV(params[idx], para.ValueType)
+	}
+	return status, realParams, nil
+}
+
+func (config *ProductConfig) ValidateCommandOrEvent(name string, params []interface{}, typ string) (*ProductCommandOrEvent, []interface{}, error) {
+	var target []ProductCommandOrEvent
+	if typ == "command" {
+		target = config.Commands
+	} else if typ == "event" {
+		target = config.Events
+	} else {
+		return nil, []interface{}{}, errors.New("wrong target type.")
+	}
+
+	// search for name
+	var paramInfo []CommandOrEventParam
+	var coe *ProductCommandOrEvent
+	found := false
+	for _, one := range target {
+		if one.Name == name {
+			paramInfo = one.Params
+			coe = &one
+			found = true
+			break
+		}
+	}
+	if found == false {
+		return nil, []interface{}{}, errors.New("command or event not found.")
+	}
+	if len(paramInfo) != len(params) {
+		return nil, []interface{}{}, errors.New("wrong parameters.")
+	}
+	realParams := make([]interface{}, len(params))
+	for idx, para := range paramInfo {
+		realParams[idx] = tlv.CastTLV(params[idx], para.ValueType)
+	}
+	return coe, realParams, nil
+}
+
+func (config *ProductConfig) StatusToMap(status []protocol.SubData) (map[string][]interface{}, error) {
+	result := make(map[string][]interface{})
+
+	for _, sub := range status {
+		val, err := tlv.ReadTLVs(sub.Params)
+		if err != nil {
+			return nil, err
+		}
+		label := ""
+		for _, obj := range config.Objects {
+			if obj.No == int(sub.Head.PropertyNum) {
+				label = obj.Label
+			}
+		}
+		result[label] = val
+	}
+
+	return result, nil
+}
+
+func (config *ProductConfig) EventToMap(event *protocol.Event) (map[string][]interface{}, error) {
+	result := make(map[string][]interface{})
+
+	name := ""
+	for _, ev := range config.Events {
+		if ev.No == int(event.Head.No) {
+			name = ev.Name
+		}
+	}
+	val, err := tlv.ReadTLVs(event.Params)
+	if err != nil {
+		return nil, err
+	}
+
+	result[name] = val
+
+	return result, nil
+}
+
+func (config *ProductConfig) MapToStatus(data map[string]interface{}) ([]protocol.SubData, error) {
+	result := []protocol.SubData{}
+
+	for label, one := range data {
+		params, ok := one.([]interface{})
+		if !ok {
+			return nil, fmt.Errorf("status format error: %v", one)
+		}
+		obj, realParams, err := config.ValidateStatus(label, params)
+		if err != nil {
+			return nil, err
+		}
+
+		tlvs, err := tlv.MakeTLVs(realParams)
+		if err != nil {
+			return nil, err
+		}
+
+		result = append(result, protocol.SubData{
+			Head: protocol.SubDataHead{
+				SubDeviceid: uint16(obj.Part),
+				PropertyNum: uint16(obj.No),
+				ParamsCount: uint16(len(realParams)),
+			},
+			Params: tlvs,
+		})
+	}
+
+	return result, nil
+}
+
+func (config *ProductConfig) MapToCommand(cmd map[string]interface{}) (*protocol.Command, error) {
+	result := &protocol.Command{}
+
+	for name, one := range cmd {
+		params, ok := one.([]interface{})
+		if !ok {
+			return nil, fmt.Errorf("command format error: %v", one)
+		}
+
+		c, realParams, err := config.ValidateCommandOrEvent(name, params, "command")
+		if err != nil {
+			return nil, err
+		}
+
+		tlvs, err := tlv.MakeTLVs(realParams)
+		if err != nil {
+			return nil, err
+		}
+
+		result.Head.No = uint16(c.No)
+		result.Head.Priority = uint16(c.Priority)
+		result.Head.SubDeviceid = uint16(c.Part)
+		result.Head.ParamsCount = uint16(len(realParams))
+		result.Params = tlvs
+
+	}
+
+	return result, nil
+}

+ 167 - 0
pkg/productconfig/productconfig_test.go

@@ -0,0 +1,167 @@
+package productconfig
+
+import (
+	"encoding/json"
+	"sparrow/pkg/protocol"
+	"sparrow/pkg/tlv"
+	"reflect"
+	"testing"
+)
+
+func testStatus(c *ProductConfig, t *testing.T) {
+	status :=
+		`
+    {
+      "switch": [1]
+    }
+    `
+
+	var v interface{}
+	err := json.Unmarshal([]byte(status), &v)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for label, onedata := range v.(map[string]interface{}) {
+		params := onedata.([]interface{})
+		obj, realParams, err := c.ValidateStatus(label, params)
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(obj)
+		t.Log(realParams)
+	}
+
+	one, err := tlv.MakeTLV(uint8(1))
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	params := []tlv.TLV{*one}
+	teststatus := []protocol.SubData{protocol.SubData{
+		Head: protocol.SubDataHead{
+			SubDeviceid: uint16(1),
+			PropertyNum: uint16(1),
+			ParamsCount: uint16(1),
+		},
+		Params: params,
+	}}
+
+	res, err := c.StatusToMap(teststatus)
+	if err != nil {
+		t.Error(err)
+	}
+
+	t.Log(res)
+
+	m := make(map[string]interface{})
+	m["switch"] = []interface{}{float64(1)}
+	_, err = c.MapToStatus(m)
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func testEvent(c *ProductConfig, t *testing.T) {
+	want := `{"alarm":["test"]}`
+
+	testev := &protocol.Event{}
+	testev.Head.No = 1
+	testev.Head.SubDeviceid = 1
+	params, err := tlv.MakeTLVs([]interface{}{"test"})
+	if err != nil {
+		t.Error(err)
+	}
+	testev.Params = params
+
+	m, err := c.EventToMap(testev)
+	if err != nil {
+		t.Error(err)
+	}
+
+	result, err := json.Marshal(m)
+	if err != nil {
+		t.Error(err)
+	}
+
+	got := string(result)
+
+	if got != want {
+		t.Errorf("event to map error: want: %v, got : %v", want, got)
+	}
+
+}
+
+func testCommand(c *ProductConfig, t *testing.T) {
+	input := `{"switch":[1,2]}`
+
+	v := make(map[string]interface{})
+	err := json.Unmarshal([]byte(input), &v)
+	if err != nil {
+		t.Fatal(err)
+	}
+	params, err := tlv.MakeTLVs([]interface{}{uint8(1), uint8(2)})
+	want := &protocol.Command{}
+	want.Head.No = 1
+	want.Head.SubDeviceid = 1
+	want.Head.ParamsCount = 2
+	want.Params = params
+
+	got, err := c.MapToCommand(v)
+
+	if !reflect.DeepEqual(want, got) {
+		t.Errorf("map to command error: want: %v, got %v", want, got)
+	}
+}
+
+func TestParseProductConfig(t *testing.T) {
+	config :=
+		`
+    {
+      "objects": [{
+        "no": 1,
+        "label": "switch",
+        "part": 1,
+        "status": [{
+          "value_type": 7,
+          "name": "onoff"
+        }]
+      }],
+      "commands": [{
+        "no": 1,
+        "part": 1,
+        "name": "switch",
+        "priority": 0,
+        "params": [{
+          "value_type": 7,
+          "name": "p1"
+        },{
+          "value_type": 7,
+          "name": "p2"
+        }]
+      }],
+      "events": [{
+        "no": 1,
+        "part": 1,
+        "name": "alarm",
+        "priority": 0,
+        "params": [{
+          "value_type": 12,
+          "name": "text"
+        }]
+      }]
+    }
+    `
+
+	c, err := New(config)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testStatus(c, t)
+	testEvent(c, t)
+	testCommand(c, t)
+
+}

+ 133 - 0
pkg/protocol/protocol.go

@@ -0,0 +1,133 @@
+package protocol
+
+import (
+	"bytes"
+	"encoding/binary"
+	"sparrow/pkg/tlv"
+)
+
+type Payload interface {
+	Marshal() ([]byte, error)
+	UnMarshal([]byte) error
+}
+
+func (c *Command) Marshal() ([]byte, error) {
+	buffer := new(bytes.Buffer)
+	err := binary.Write(buffer, binary.BigEndian, c.Head)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, param := range c.Params {
+		err = binary.Write(buffer, binary.BigEndian, param.ToBinary())
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return buffer.Bytes(), nil
+}
+
+func (c *Command) UnMarshal(buf []byte) error {
+	n := len(buf)
+	r := bytes.NewReader(buf)
+	err := binary.Read(r, binary.BigEndian, &c.Head)
+	if err != nil {
+		return err
+	}
+	c.Params = []tlv.TLV{}
+	for i := binary.Size(c.Head); i < n; {
+		tlv := tlv.TLV{}
+		tlv.FromBinary(r)
+		i += int(tlv.Length())
+		c.Params = append(c.Params, tlv)
+	}
+
+	return nil
+}
+
+func (e *Event) Marshal() ([]byte, error) {
+	buffer := new(bytes.Buffer)
+	err := binary.Write(buffer, binary.BigEndian, e.Head)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, param := range e.Params {
+		err = binary.Write(buffer, binary.BigEndian, param.ToBinary())
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return buffer.Bytes(), nil
+}
+
+func (e *Event) UnMarshal(buf []byte) error {
+	n := len(buf)
+	r := bytes.NewReader(buf)
+	err := binary.Read(r, binary.BigEndian, &e.Head)
+	if err != nil {
+		return err
+	}
+	e.Params = []tlv.TLV{}
+	for i := binary.Size(e.Head); i < n; {
+		tlv := tlv.TLV{}
+		tlv.FromBinary(r)
+		i += int(tlv.Length())
+		e.Params = append(e.Params, tlv)
+	}
+
+	return nil
+}
+
+func (d *Data) Marshal() ([]byte, error) {
+	buffer := new(bytes.Buffer)
+	err := binary.Write(buffer, binary.BigEndian, d.Head)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, sub := range d.SubData {
+		err = binary.Write(buffer, binary.BigEndian, sub.Head)
+		if err != nil {
+			return nil, err
+		}
+		for _, param := range sub.Params {
+			err = binary.Write(buffer, binary.BigEndian, param.ToBinary())
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	return buffer.Bytes(), nil
+}
+
+func (d *Data) UnMarshal(buf []byte) error {
+	n := len(buf)
+	r := bytes.NewReader(buf)
+	err := binary.Read(r, binary.BigEndian, &d.Head)
+	if err != nil {
+		return err
+	}
+	d.SubData = []SubData{}
+	for i := binary.Size(d.Head); i < n; {
+		sub := SubData{}
+		err = binary.Read(r, binary.BigEndian, &sub.Head)
+		if err != nil {
+			return err
+		}
+		i += int(binary.Size(sub.Head))
+		sub.Params = []tlv.TLV{}
+		for j := 0; j < int(sub.Head.ParamsCount); j++ {
+			param := tlv.TLV{}
+			param.FromBinary(r)
+			i += int(param.Length())
+			sub.Params = append(sub.Params, param)
+		}
+		d.SubData = append(d.SubData, sub)
+	}
+
+	return nil
+}

+ 150 - 0
pkg/protocol/protocol_test.go

@@ -0,0 +1,150 @@
+package protocol
+
+import (
+	"sparrow/pkg/tlv"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestCommand(t *testing.T) {
+	param := []interface{}{uint32(1), float32(3.2), []byte{'1', '2'}}
+	params, err := tlv.MakeTLVs(param)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	payloadHead := CommandEventHead{
+		Flag:        0,
+		Timestamp:   uint64(time.Now().Unix()) * 1000,
+		SubDeviceid: uint16(2),
+		No:          uint16(12),
+		Priority:    uint16(1),
+		ParamsCount: uint16(len(param)),
+	}
+	payload := &Command{
+		Head:   payloadHead,
+		Params: params,
+	}
+
+	buf, err := payload.Marshal()
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	payload2 := &Command{}
+
+	err = payload2.UnMarshal(buf)
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	if !reflect.DeepEqual(payload, payload2) {
+		t.Errorf("test command payload failed, want %v, got %v", payload, payload2)
+	}
+}
+
+func TestEvent(t *testing.T) {
+	param := []interface{}{uint32(1), float32(3.2), []byte{'1', '2'}}
+	params, err := tlv.MakeTLVs(param)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	payloadHead := CommandEventHead{
+		Flag:        0,
+		Timestamp:   uint64(time.Now().Unix()) * 1000,
+		SubDeviceid: uint16(2),
+		No:          uint16(12),
+		Priority:    uint16(1),
+		ParamsCount: uint16(len(param)),
+	}
+	payload := &Event{
+		Head:   payloadHead,
+		Params: params,
+	}
+
+	buf, err := payload.Marshal()
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	payload2 := &Event{}
+
+	err = payload2.UnMarshal(buf)
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	if !reflect.DeepEqual(payload, payload2) {
+		t.Errorf("test event payload failed, want %v, got %v", payload, payload2)
+	}
+}
+
+func TestData(t *testing.T) {
+	payloadHead := DataHead{
+		Flag:      0,
+		Timestamp: uint64(time.Now().Unix() * 1000),
+	}
+	param1 := []interface{}{uint32(3), float32(1.2), int64(10)}
+	params1, err := tlv.MakeTLVs(param1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sub1 := SubData{
+		Head: SubDataHead{
+			SubDeviceid: uint16(1),
+			PropertyNum: uint16(1),
+			ParamsCount: uint16(len(params1)),
+		},
+		Params: params1,
+	}
+	param2 := []interface{}{uint32(4), int64(11)}
+	params2, err := tlv.MakeTLVs(param2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sub2 := SubData{
+		Head: SubDataHead{
+			SubDeviceid: uint16(1),
+			PropertyNum: uint16(2),
+			ParamsCount: uint16(len(params2)),
+		},
+		Params: params2,
+	}
+
+	payload := &Data{
+		Head:    payloadHead,
+		SubData: []SubData{},
+	}
+	payload.SubData = append(payload.SubData, sub1)
+	payload.SubData = append(payload.SubData, sub2)
+
+	buf, err := payload.Marshal()
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	payload2 := &Data{}
+
+	err = payload2.UnMarshal(buf)
+	if err != nil {
+		if err != nil {
+			t.Error(err)
+		}
+	}
+
+	if !reflect.DeepEqual(payload, payload2) {
+		t.Errorf("test data payload failed, want %v, got %v", payload, payload2)
+	}
+}

+ 47 - 0
pkg/protocol/structure.go

@@ -0,0 +1,47 @@
+package protocol
+
+import (
+	"sparrow/pkg/tlv"
+)
+
+type CommandEventHead struct {
+	Flag        uint8
+	Timestamp   uint64
+	Token       [16]byte
+	SubDeviceid uint16
+	No          uint16
+	Priority    uint16
+	ParamsCount uint16
+}
+
+type Command struct {
+	Head   CommandEventHead
+	Params []tlv.TLV
+}
+
+type Event struct {
+	Head   CommandEventHead
+	Params []tlv.TLV
+}
+
+type DataHead struct {
+	Flag      uint8
+	Timestamp uint64
+	Token     [16]byte
+}
+
+type Data struct {
+	Head    DataHead
+	SubData []SubData
+}
+
+type SubDataHead struct {
+	SubDeviceid uint16
+	PropertyNum uint16
+	ParamsCount uint16
+}
+
+type SubData struct {
+	Head   SubDataHead
+	Params []tlv.TLV
+}

+ 53 - 0
pkg/queue/queque_test.go

@@ -0,0 +1,53 @@
+package queue
+
+import (
+	"reflect"
+	"testing"
+	"time"
+)
+
+type test struct {
+	Cmd int
+	Msg string
+}
+
+const testQueueName = "test/queue/somename"
+
+var testChan chan test = make(chan test)
+
+func recv(t *testing.T) {
+	q, err := New("amqp://guest:guest@localhost:5672/", testQueueName)
+	if err != nil {
+		t.Error(err)
+	}
+	msg := test{}
+	err = q.Receive(&msg)
+	if err != nil {
+		t.Error(err)
+	}
+	testChan <- msg
+}
+
+func TestQueue(t *testing.T) {
+	testMessage := test{123, "hello"}
+
+	q, err := New("amqp://guest:guest@localhost:5672/", testQueueName)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	go recv(t)
+
+	time.Sleep(time.Second)
+
+	err = q.Send(testMessage)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	recvMessage := <-testChan
+
+	if !reflect.DeepEqual(testMessage, recvMessage) {
+		t.Errorf("receive message not match, want: %v, got : %v", testMessage, recvMessage)
+	}
+}

+ 140 - 0
pkg/queue/queue.go

@@ -0,0 +1,140 @@
+// package queue implement a message queque api with rabbitmq
+package queue
+
+import (
+	"errors"
+	"sparrow/pkg/serializer"
+	"github.com/streadway/amqp"
+)
+
+const defaultRecvChanLen = 8
+
+type Queue struct {
+	rabbithost   string
+	conn         *amqp.Connection
+	ch           *amqp.Channel
+	queue        amqp.Queue
+	recvChan     chan ([]byte)
+	beginReceive bool
+}
+
+func New(rabbithost string, name string) (*Queue, error) {
+	conn, err := amqp.Dial(rabbithost)
+	if err != nil {
+		return nil, err
+	}
+
+	ch, err := conn.Channel()
+	if err != nil {
+		return nil, err
+	}
+
+	queue, err := ch.QueueDeclare(
+		name,  // name
+		true,  // durable
+		false, // delete when unused
+		false, // exclusive
+		false, // no-wait
+		nil,   // arguments
+	)
+	if err != nil {
+		return nil, errors.New("Failed to declare a queue")
+	}
+
+	err = ch.Qos(
+		1,     // prefetch count
+		0,     // prefetch size
+		false, // global
+	)
+	if err != nil {
+		return nil, errors.New("Failed to set QoS")
+	}
+
+	q := &Queue{rabbithost, conn, ch, queue, nil, false}
+
+	return q, nil
+}
+
+func (q *Queue) keepReceivingFromQueue() {
+	if q.ch == nil || q.recvChan == nil {
+		//Message Queue Not Initialzed.
+		return
+	}
+
+	defer func() {
+		if q.recvChan != nil {
+			close(q.recvChan)
+		}
+	}()
+
+	msgs, err := q.ch.Consume(
+		q.queue.Name, // queue
+		"",           // consumer
+		false,        // auto-ack
+		false,        // exclusive
+		false,        // no-local
+		false,        // no-wait
+		nil,          // args
+	)
+
+	if err != nil {
+		return
+	}
+
+	for d := range msgs {
+		q.recvChan <- d.Body
+		d.Ack(false)
+	}
+
+}
+
+// Send will send a message to the queue.
+func (q *Queue) Send(msg interface{}) error {
+	if q.ch == nil {
+		return errors.New("Message Queue Not Initialzed.")
+	}
+	msgStr, err := serializer.Struct2String(msg)
+	if err != nil {
+		return err
+	}
+	err = q.ch.Publish(
+		"",           // exchange
+		q.queue.Name, // routing key
+		false,        // mandatory
+		false,
+		amqp.Publishing{
+			DeliveryMode: amqp.Persistent,
+			ContentType:  "text/plain",
+			Body:         []byte(msgStr),
+		})
+
+	return nil
+}
+
+// Receive will reveive a message from the queue. may be blocked if there is no message in queue.
+func (q *Queue) Receive(target interface{}) error {
+	if !q.beginReceive {
+		q.recvChan = make(chan ([]byte), defaultRecvChanLen)
+		go q.keepReceivingFromQueue()
+		q.beginReceive = true
+	}
+
+	if q.recvChan == nil {
+		return errors.New("Message Queue Has Not Been Initialized.")
+	}
+
+	msg, ok := <-q.recvChan
+
+	if !ok {
+		return errors.New("Message Queue Has Been Closed.")
+	}
+
+	strMsg := string(msg)
+	err := serializer.String2Struct(strMsg, target)
+	if err != nil {
+		return err
+	}
+
+	return nil
+
+}

+ 36 - 0
pkg/redispool/redispool.go

@@ -0,0 +1,36 @@
+package redispool
+
+import (
+	"github.com/garyburd/redigo/redis"
+	"time"
+)
+
+var mapRedisPool map[string]*redis.Pool
+
+// GetClient get a redis connection by host
+func GetClient(host string) (redis.Conn, error) {
+
+	pool, exist := mapRedisPool[host]
+	if !exist {
+		pool = &redis.Pool{
+			MaxIdle: 10,
+			Dial: func() (redis.Conn, error) {
+				c, err := redis.Dial("tcp", host)
+				if err != nil {
+					return nil, err
+				}
+				return c, err
+			},
+			TestOnBorrow: func(c redis.Conn, t time.Time) error {
+				_, err := c.Do("PING")
+				return err
+			},
+		}
+		mapRedisPool[host] = pool
+	}
+	return pool.Get(), nil
+}
+
+func init() {
+	mapRedisPool = make(map[string]*redis.Pool)
+}

+ 18 - 0
pkg/redispool/redispool_test.go

@@ -0,0 +1,18 @@
+package redispool
+
+import (
+	"github.com/garyburd/redigo/redis"
+	"testing"
+)
+
+func TestRedisCli(t *testing.T) {
+	cli, err := GetClient("localhost:6379")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = redis.String(cli.Do("GET", "testkey"))
+	if err != nil && err != redis.ErrNil {
+		t.Error(err)
+	}
+}

+ 27 - 0
pkg/rpcs/access.go

@@ -0,0 +1,27 @@
+package rpcs
+
+import (
+	"sparrow/pkg/protocol"
+	"sparrow/pkg/tlv"
+)
+
+type ArgsSetStatus struct {
+	DeviceId uint64
+	Status   []protocol.SubData
+}
+type ReplySetStatus ReplyEmptyResult
+
+type ArgsGetStatus ArgsDeviceId
+type ReplyGetStatus struct {
+	Status []protocol.SubData
+}
+
+type ArgsSendCommand struct {
+	DeviceId  uint64
+	SubDevice uint16
+	No        uint16
+	Priority  uint16
+	WaitTime  uint32
+	Params    []tlv.TLV
+}
+type ReplySendCommand ReplyEmptyResult

+ 7 - 0
pkg/rpcs/common.go

@@ -0,0 +1,7 @@
+package rpcs
+
+type ArgsDeviceId struct {
+	Id uint64
+}
+
+type ReplyEmptyResult struct{}

+ 23 - 0
pkg/rpcs/controller.go

@@ -0,0 +1,23 @@
+package rpcs
+
+import (
+	"sparrow/pkg/protocol"
+	"sparrow/pkg/tlv"
+)
+
+type ArgsOnStatus struct {
+	DeviceId  uint64
+	Timestamp uint64
+	Subdata   []protocol.SubData
+}
+type ReplyOnStatus ReplyEmptyResult
+
+type ArgsOnEvent struct {
+	DeviceId  uint64
+	TimeStamp uint64
+	SubDevice uint16
+	No        uint16
+	Priority  uint16
+	Params    []tlv.TLV
+}
+type ReplyOnEvent ReplyEmptyResult

+ 35 - 0
pkg/rpcs/devicemanager.go

@@ -0,0 +1,35 @@
+package rpcs
+
+import (
+	"sparrow/pkg/online"
+)
+
+type ArgsGenerateDeviceAccessToken ArgsDeviceId
+type ReplyGenerateDeviceAccessToken struct {
+	AccessToken []byte
+}
+
+type ArgsValidateDeviceAccessToken struct {
+	Id          uint64
+	AccessToken []byte
+}
+type ReplyValidateDeviceAccessToken ReplyEmptyResult
+
+type ArgsGetOnline struct {
+	Id                uint64
+	ClientIP          string
+	AccessRPCHost     string
+	HeartbeatInterval uint32
+}
+type ReplyGetOnline ReplyEmptyResult
+
+type ArgsGetOffline ArgsDeviceId
+type ReplyGetOffline ReplyEmptyResult
+
+type ArgsHeartBeat struct {
+	Id uint64
+}
+type ReplyHeartBeat ReplyEmptyResult
+
+type ArgsGetDeviceOnlineStatus ArgsDeviceId
+type ReplyGetDeviceOnlineStatus online.Status

+ 15 - 0
pkg/rpcs/registry.go

@@ -0,0 +1,15 @@
+package rpcs
+
+// device register args
+type ArgsDeviceRegister struct {
+	ProductKey    string
+	DeviceCode    string
+	DeviceVersion string
+}
+
+// device update args
+type ArgsDeviceUpdate struct {
+	DeviceIdentifier  string
+	DeviceName        string
+	DeviceDescription string
+}

+ 67 - 0
pkg/rule/ifttt.go

@@ -0,0 +1,67 @@
+// support ifttt action between two devices.
+package rule
+
+import (
+	"sparrow/pkg/models"
+	"sparrow/pkg/productconfig"
+	"sparrow/pkg/server"
+)
+
+type Ifttt struct{}
+
+func NewIfttt() *Ifttt {
+	return &Ifttt{}
+}
+
+func (ift *Ifttt) Check(deviceid uint64, eventid uint16) error {
+	actions := &[]models.Rule{}
+	query := &models.Rule{
+		RuleType: "ifttt",
+		DeviceID: int64(deviceid),
+	}
+	err := server.RPCCallByName("registry", "Registry.QueryRules", query, actions)
+	if err != nil {
+		server.Log.Warnf("load ifttt rules error : %v", err)
+		return err
+	}
+
+	if len(*actions) > 0 {
+		device := &models.Device{}
+		err := server.RPCCallByName("registry", "Registry.FindDeviceById", int64(deviceid), device)
+		if err != nil {
+			server.Log.Errorf("find device error : %v", err)
+			return err
+		}
+
+		product := &models.Product{}
+		err = server.RPCCallByName("registry", "Registry.FindProduct", device.ProductID, product)
+		if err != nil {
+			server.Log.Errorf("find product error : %v", err)
+			return err
+		}
+
+		c, err := productconfig.New(product.ProductConfig)
+		if err != nil {
+			server.Log.Errorf("product config error : %v", err)
+			return err
+		}
+
+		name := ""
+		for _, ev := range c.Events {
+			if ev.No == int(eventid) {
+				name = ev.Name
+			}
+		}
+
+		for _, action := range *actions {
+			if action.Trigger == name {
+				err := performRuleAction(action.Target, action.Action)
+				if err != nil {
+					server.Log.Warnf("ifttt action failed: %v", err)
+				}
+			}
+		}
+	}
+
+	return nil
+}

+ 95 - 0
pkg/rule/rule_action.go

@@ -0,0 +1,95 @@
+package rule
+
+import (
+	"encoding/json"
+	"fmt"
+	"sparrow/pkg/models"
+	"sparrow/pkg/productconfig"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"strings"
+)
+
+func performRuleAction(target string, action string) error {
+	server.Log.Infof("trigger rule action: %v, %v", target, action)
+
+	parts := strings.Split(target, "/")
+	if len(parts) != 3 {
+		return fmt.Errorf("error target format: %v", target)
+	}
+
+	identifier := parts[1]
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.FindDeviceByIdentifier", identifier, device)
+	if err != nil {
+		return err
+	}
+
+	product := &models.Product{}
+	err = server.RPCCallByName("registry", "Registry.FindProduct", device.ProductID, product)
+	if err != nil {
+		return err
+	}
+
+	config, err := productconfig.New(product.ProductConfig)
+	if err != nil {
+		return err
+	}
+
+	var args interface{}
+	err = json.Unmarshal([]byte(action), &args)
+	if err != nil {
+		server.Log.Errorf("marshal action error: %v", err)
+		return err
+	}
+
+	m, ok := args.(map[string]interface{})
+	if !ok {
+		server.Log.Errorf("decode action error:%v", err)
+		return fmt.Errorf("decode action error:%v", err)
+	}
+
+	sendType := parts[2]
+	switch sendType {
+	case "command":
+		command, err := config.MapToCommand(m)
+		if err != nil {
+			server.Log.Errorf("action format error: %v", err)
+			return err
+		}
+
+		cmdargs := rpcs.ArgsSendCommand{
+			DeviceId:  uint64(device.ID),
+			SubDevice: uint16(command.Head.SubDeviceid),
+			No:        uint16(command.Head.No),
+			WaitTime:  uint32(3000),
+			Params:    command.Params,
+		}
+		cmdreply := rpcs.ReplySendCommand{}
+		err = server.RPCCallByName("controller", "Controller.SendCommand", cmdargs, &cmdreply)
+		if err != nil {
+			server.Log.Errorf("send device command error: %v", err)
+			return err
+		}
+	case "status":
+		status, err := config.MapToStatus(m)
+		if err != nil {
+			return err
+		}
+
+		statusargs := rpcs.ArgsSetStatus{
+			DeviceId: uint64(device.ID),
+			Status:   status,
+		}
+		statusreply := rpcs.ReplySetStatus{}
+		err = server.RPCCallByName("controller", "Controller.SetStatus", statusargs, &statusreply)
+		if err != nil {
+			server.Log.Errorf("set devie status error: %v", err)
+			return err
+		}
+	default:
+		server.Log.Errorf("wrong action %v", action)
+	}
+
+	return nil
+}

+ 62 - 0
pkg/rule/timer.go

@@ -0,0 +1,62 @@
+// suport cron like schedule tasks.
+package rule
+
+import (
+	"fmt"
+	"sparrow/pkg/models"
+	"sparrow/pkg/server"
+	"github.com/robfig/cron"
+	"time"
+)
+
+type Timer struct {
+	c *cron.Cron
+}
+
+func NewTimer() *Timer {
+	t := &Timer{}
+
+	return t
+}
+
+func (t *Timer) createTimerFunc(target string, action string) func() {
+	return func() {
+		err := performRuleAction(target, action)
+		if err != nil {
+			server.Log.Warnf("timer action failed: %v", err)
+		}
+	}
+}
+
+func (t *Timer) refresh() {
+	if t.c != nil {
+		t.c.Stop()
+	}
+	t.c = cron.New()
+	timers := &[]models.Rule{}
+	query := &models.Rule{
+		RuleType: "timer",
+	}
+	err := server.RPCCallByName("registry", "Registry.QueryRules", query, timers)
+	if err != nil {
+		server.Log.Warnf("refresh timer rules error : %v", err)
+		return
+	}
+
+	sec := fmt.Sprintf("%d ", (time.Now().Second()+30)%60)
+
+	for _, one := range *timers {
+		t.c.AddFunc(sec+one.Trigger, t.createTimerFunc(one.Target, one.Action))
+	}
+
+	t.c.Start()
+}
+
+func (t *Timer) Run() {
+	go func() {
+		for {
+			t.refresh()
+			time.Sleep(time.Minute)
+		}
+	}()
+}

+ 22 - 0
pkg/serializer/serializer.go

@@ -0,0 +1,22 @@
+package serializer
+
+import (
+	"bytes"
+	"encoding/gob"
+)
+
+// convert string to any kind of struct
+func String2Struct(str string, target interface{}) error {
+	bytes_buffer := bytes.NewBufferString(str)
+	dec := gob.NewDecoder(bytes_buffer)
+	err := dec.Decode(target)
+	return err
+}
+
+// convert any kind of struct to string
+func Struct2String(stru interface{}) (string, error) {
+	var bytes_buffer bytes.Buffer
+	enc := gob.NewEncoder(&bytes_buffer)
+	err := enc.Encode(stru)
+	return bytes_buffer.String(), err
+}

+ 29 - 0
pkg/serializer/serializer_test.go

@@ -0,0 +1,29 @@
+package serializer
+
+import (
+	"reflect"
+	"testing"
+)
+
+type testStruct struct {
+	Int1 int
+	Str1 string
+	Int2 int32
+	Arr  []byte
+}
+
+func TestStringStructConvert(t *testing.T) {
+	test := testStruct{0, "hello", 12, []byte{1, 0, 12}}
+	str, err := Struct2String(test)
+	if err != nil {
+		t.Error(err)
+	}
+	stru := testStruct{}
+	err = String2Struct(str, &stru)
+	if err != nil {
+		t.Error(err)
+	}
+	if !reflect.DeepEqual(test, stru) {
+		t.Errorf("wrong result %v, want %v", stru, test)
+	}
+}

+ 7 - 0
pkg/server/README.md

@@ -0,0 +1,7 @@
+# the server framework. 
+includes:
+
+- tcp/http service framework
+- rpc helper
+- stats api
+- timer task interface

+ 36 - 0
pkg/server/config.go

@@ -0,0 +1,36 @@
+// config flags from command line or ini conf file.
+
+package server
+
+import (
+	"flag"
+)
+
+const (
+	FlagTCPHost  = "tcphost"
+	FlagUseTls   = "usetls"
+	FlagHTTPHost = "httphost"
+	FlagUseHttps = "usehttps"
+	FlagCAFile   = "cafile"
+	FlagKeyFile  = "keyfile"
+	FlagRPCHost  = "rpchost"
+	FlagEtcd     = "etcd"
+	FlagLogLevel = "loglevel"
+)
+
+var (
+	confTCPHost = flag.String(FlagTCPHost, "", "tcp server listen address, format ip:port")
+	confUseTls  = flag.Bool(FlagUseTls, false, "if tcp server uses tls, default false")
+
+	confHTTPHost = flag.String(FlagHTTPHost, "", "http server listen address, format ip:port")
+	confUseHttps = flag.Bool(FlagUseHttps, false, "if http server uses tls, default false")
+
+	confCAFile  = flag.String(FlagCAFile, "cacert.pem", "public ca pem file path")
+	confKeyFile = flag.String(FlagKeyFile, "privkey.pem", "private key pem file path")
+
+	confRPCHost = flag.String(FlagRPCHost, "", "rpc server listen address, format ip:port")
+
+	confEtcd = flag.String(FlagEtcd, "", "etcd service addr, format ip:port;ip:port")
+
+	confLogLevel = flag.String(FlagLogLevel, "info", "default log level, options are panic|fatal|error|warn|info|debug")
+)

+ 15 - 0
pkg/server/errors.go

@@ -0,0 +1,15 @@
+// error messages
+
+package server
+
+const (
+	errServerNotInit           = "Server has not been initialized...You must call server.Init(name) first !"
+	errTCPHandlerNotRegistered = "Start TCP Server error : tcp handler not registered !"
+	errMissingFlag             = "Missing flag: %s !"
+	errLoadSecureKey           = "Load secret key file failed - %s"
+	errListenFailed            = "FATAL: tcp listen (%s) failed - %s"
+	errNewConnection           = "receive new connection error (%s)"
+	errWrongHostAddr           = "wrong address : %s"
+	errWrongEtcdPath           = "wrong path in etcd: %s"
+	errServerManagerNotInit    = "sever manager not init!"
+)

+ 45 - 0
pkg/server/http_server.go

@@ -0,0 +1,45 @@
+// http server library.
+package server
+
+import (
+	"net/http"
+)
+
+type HTTPServer struct {
+	addr     string
+	handler  http.Handler
+	useHttps bool
+}
+
+func (hs *HTTPServer) Start() error {
+	// field check
+	if hs.handler == nil {
+		return errorf("Start HTTP Server error : http handler not registered!")
+	}
+
+	if hs.useHttps {
+		// secure files
+		if *confCAFile == "" {
+			return errorf(errMissingFlag, FlagCAFile)
+		}
+		if *confKeyFile == "" {
+			return errorf(errMissingFlag, FlagKeyFile)
+		}
+	}
+
+	Log.Infof("HTTP Server Listen on %s, use https: %v", hs.addr, hs.useHttps)
+	go func() {
+		var err error
+		if hs.useHttps == false {
+			err = http.ListenAndServe(hs.addr, hs.handler)
+		} else {
+			err = http.ListenAndServeTLS(hs.addr, *confCAFile, *confKeyFile, hs.handler)
+		}
+
+		if err != nil {
+			Log.Fatal(err.Error())
+		}
+	}()
+
+	return nil
+}

+ 83 - 0
pkg/server/http_server_test.go

@@ -0,0 +1,83 @@
+package server
+
+import (
+	"crypto/tls"
+	"io/ioutil"
+	"net/http"
+	"testing"
+	"time"
+)
+
+const (
+	sayHi        = "hello pando"
+	testHTTPHost = "localhost:12347"
+)
+
+type testHttpHandler struct{}
+
+func (h testHttpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	w.Write([]byte(sayHi))
+}
+
+func validateHTTPServer(t *testing.T, url string) {
+	response, err := http.Get(url)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer response.Body.Close()
+	body, _ := ioutil.ReadAll(response.Body)
+
+	if string(body) != sayHi {
+		t.Fatalf("http server test error: want %s, got %s", sayHi, string(body))
+	}
+}
+
+func validateHTTPSServer(t *testing.T, url string) {
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	client := &http.Client{Transport: tr}
+	response, err := client.Get(url)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer response.Body.Close()
+	body, _ := ioutil.ReadAll(response.Body)
+
+	if string(body) != sayHi {
+		t.Errorf("https server test error: want %s, got %s", sayHi, string(body))
+	}
+}
+
+func TestHTTPServer(t *testing.T) {
+	initLog("test", "debug")
+
+	*confCAFile = ""
+	*confKeyFile = ""
+
+	svr := HTTPServer{
+		addr:     testHTTPHost,
+		handler:  testHttpHandler{},
+		useHttps: true,
+	}
+
+	err := svr.Start()
+	if err == nil {
+		t.Errorf("https server should start fail when no keyfile and cafile set.")
+	}
+
+	svr = HTTPServer{
+		addr:     testHTTPHost,
+		handler:  testHttpHandler{},
+		useHttps: false,
+	}
+
+	err = svr.Start()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	time.Sleep(time.Millisecond * 100)
+
+	validateHTTPServer(t, "http://"+testHTTPHost)
+}

+ 35 - 0
pkg/server/log.go

@@ -0,0 +1,35 @@
+// log provides log api.
+// thanks to the helpful log tool logrus(https://github.com/Sirupsen/logrus)
+package server
+
+import (
+	"github.com/Sirupsen/logrus"
+)
+
+var Log *logrus.Entry
+
+func initLog(name string, level string) error {
+	if Log == nil {
+		// Log as JSON instead of the default ASCII formatter.
+		logrus.SetFormatter(&logrus.JSONFormatter{})
+
+		// Output to stderr instead of stdout, could also be a file.
+		// logrus.SetOutput(os.Stderr)
+
+		// logging level
+		lvl, err := logrus.ParseLevel(level)
+		if err != nil {
+			return err
+		}
+
+		logrus.SetLevel(lvl)
+
+		// default fields
+		Log = logrus.WithFields(logrus.Fields{
+			"service": name,
+			"ip":      InternalIP,
+		})
+	}
+
+	return nil
+}

+ 20 - 0
pkg/server/log_test.go

@@ -0,0 +1,20 @@
+package server
+
+import (
+	"testing"
+)
+
+func TestLog(t *testing.T) {
+	Log = nil
+	err := initLog("wrongtest", "wronglevel")
+	if err == nil {
+		t.Errorf("init log should return error when level is wrong.")
+	}
+
+	err = initLog("test", "error")
+	if err != nil {
+		t.Error(err)
+	}
+
+	Log.Error("test log.")
+}

+ 93 - 0
pkg/server/netif.go

@@ -0,0 +1,93 @@
+// netif implements helper functions to read network interfaces.
+// warning: ONLY suport standard Linux interface config.
+
+package server
+
+import (
+	"errors"
+	"net"
+	"strings"
+)
+
+var (
+	InternalIP string
+	ExternalIP string
+)
+
+const (
+	confInternalIP = "internal"
+	confExternalIP = "external"
+)
+
+//to see if an IP is internal ip
+func isInternalIP(ip string) bool {
+	if ip == "127.0.0.1" {
+		return true
+	}
+
+	ipSplit := strings.Split(ip, ".")
+
+	if ipSplit[0] == "10" {
+		return true
+	}
+
+	if ipSplit[0] == "172" && ipSplit[1] >= "16" && ipSplit[1] <= "31" {
+		return true
+	}
+
+	if ipSplit[0] == "192" && ipSplit[1] == "168" {
+		return true
+	}
+
+	return false
+}
+
+//read server IP
+func readNetInterfaces() {
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return
+	}
+
+	for _, inter := range interfaces {
+		addr, err := inter.Addrs()
+		if err != nil {
+			continue
+		}
+
+		if !strings.Contains(inter.Name, "eth") {
+			continue
+		}
+
+		if len(addr) == 0 {
+			continue
+		}
+
+		ip := strings.Split(addr[0].String(), "/")[0]
+		if isInternalIP(ip) {
+			InternalIP = ip
+		} else {
+			ExternalIP = ip
+		}
+	}
+
+	return
+}
+
+// fix host ip with "internal:port" or "external:port" format
+func fixHostIp(addr string) (string, error) {
+	if strings.Contains(addr, confInternalIP) {
+		if InternalIP != "" {
+			addr = strings.Replace(addr, confInternalIP, InternalIP, -1)
+		} else {
+			return addr, errors.New("server has no internal ip")
+		}
+	} else if strings.Contains(addr, confExternalIP) {
+		if ExternalIP != "" {
+			addr = strings.Replace(addr, confExternalIP, ExternalIP, -1)
+		} else {
+			return addr, errors.New("server has no external ip")
+		}
+	}
+	return addr, nil
+}

+ 33 - 0
pkg/server/netif_test.go

@@ -0,0 +1,33 @@
+package server
+
+import (
+	"testing"
+)
+
+func TestNetIf(t *testing.T) {
+	readNetInterfaces()
+	t.Logf("internal ip: %s", InternalIP)
+	t.Logf("external ip: %s", ExternalIP)
+}
+
+func TestIsInternalIP(t *testing.T) {
+	testIPs := []string{"127.0.0.1", "192.168.5.234", "10.23.45.56", "172.17.2.4"}
+	for _, ip := range testIPs {
+		if isInternalIP(ip) == false {
+			t.Errorf("test internal ip failed: %s", ip)
+		}
+	}
+}
+
+func TestFixHostIP(t *testing.T) {
+	InternalIP = "10.1.1.1"
+	ExternalIP = "5.1.1.1"
+	fixedIP, err := fixHostIp("internal:40")
+	if err != nil || fixedIP != "10.1.1.1:40" {
+		t.Errorf("test fix host ip failed: %s, %s", fixedIP, err)
+	}
+	fixedIP, err = fixHostIp("external:40")
+	if err != nil || fixedIP != "5.1.1.1:40" {
+		t.Errorf("test fix host ip failed: %s, %s", fixedIP, err)
+	}
+}

+ 87 - 0
pkg/server/rpc_client.go

@@ -0,0 +1,87 @@
+// RPCClient implements a rpc client tool with reconnect and load balance.
+package server
+
+import (
+	"fmt"
+	"math/rand"
+	"net/rpc"
+	"time"
+)
+
+type RPCClient struct {
+	clients map[string]*rpc.Client
+	random  *rand.Rand
+}
+
+func NewRPCClient() (*RPCClient, error) {
+	if serverInstance == nil {
+		return nil, errorf(errServerNotInit)
+	}
+	if serverInstance.svrmgr == nil {
+		return nil, errorf(errServerManagerNotInit)
+	}
+	return &RPCClient{
+		clients: make(map[string]*rpc.Client),
+		random:  rand.New(rand.NewSource(time.Now().UnixNano())),
+	}, nil
+}
+
+func rpcCallWithReconnect(client *rpc.Client, addr string, serverMethod string, args interface{}, reply interface{}) error {
+	err := client.Call(serverMethod, args, reply)
+	if err == rpc.ErrShutdown {
+		Log.Warn("rpc connection shut down, trying to reconnect...")
+		client, err = rpc.Dial("tcp", addr)
+		if err != nil {
+			return err
+		}
+		return client.Call(serverMethod, args, reply)
+	}
+	return err
+}
+
+// RPC call with reconnect and retry.
+func (client *RPCClient) Call(severName string, serverMethod string, args interface{}, reply interface{}) error {
+	addrs, err := serverInstance.svrmgr.GetServerHosts(severName, FlagRPCHost)
+	if err != nil {
+		return err
+	}
+
+	// pick a random start index for round robin
+	total := len(addrs)
+	start := client.random.Intn(total)
+
+	for idx := 0; idx < total; idx++ {
+		addr := addrs[(start+idx)%total]
+		mapkey := fmt.Sprintf("%s[%s]", severName, addr)
+		if client.clients[mapkey] == nil {
+			client.clients[mapkey], err = rpc.Dial("tcp", addr)
+			if err != nil {
+				Log.Warnf("RPC dial error : %s", err)
+				continue
+			}
+		}
+
+		err = rpcCallWithReconnect(client.clients[mapkey], addr, serverMethod, args, reply)
+		if err != nil {
+			Log.Warnf("RpcCallWithReconnect error : %s", err)
+			continue
+		}
+
+		return nil
+	}
+
+	return errorf(err.Error())
+}
+
+// RPC call by host
+func (client *RPCClient) CallHost(host string, serverMethod string, args interface{}, reply interface{}) error {
+	if client.clients[host] == nil {
+		var err error
+		client.clients[host], err = rpc.Dial("tcp", host)
+		if err != nil {
+			Log.Errorf("RPC dial error : %s", err)
+			return err
+		}
+	}
+	return rpcCallWithReconnect(client.clients[host], host, serverMethod, args, reply)
+}

+ 44 - 0
pkg/server/rpc_client_test.go

@@ -0,0 +1,44 @@
+package server
+
+import (
+	"testing"
+)
+
+func validateRPCClient(t *testing.T) {
+	rpccli, err := NewRPCClient()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	args := &Args{100, 200}
+	var reply int
+
+	err = rpccli.Call("test", "Arith.Multiply", args, &reply)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if reply != testRPCArgs.A*testRPCArgs.B {
+		t.Fatalf("rpc client test faild, want %d, got %d", testRPCArgs.A*testRPCArgs.B, reply)
+	}
+
+	err = RPCCallByName("test", "Arith.Multiply", args, &reply)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = RPCCallByHost(*confRPCHost, "Arith.Multiply", args, &reply)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if reply != testRPCArgs.A*testRPCArgs.B {
+		t.Fatalf("rpc client test faild, want %d, got %d", testRPCArgs.A*testRPCArgs.B, reply)
+	}
+
+	err = rpccli.Call("wrongtest", "Arith.Multiply", args, &reply)
+	t.Log(err)
+	if err == nil {
+		t.Fatalf("rpc client should return error when no server found!")
+	}
+}

+ 17 - 0
pkg/server/rpc_server.go

@@ -0,0 +1,17 @@
+// rpc server
+package server
+
+import (
+	"net"
+	"net/rpc"
+)
+
+type RPCServer struct {
+	TCPServer
+}
+
+type rpcHandler struct{}
+
+func (handler *rpcHandler) Handle(conn net.Conn) {
+	rpc.ServeConn(conn)
+}

+ 72 - 0
pkg/server/rpc_server_test.go

@@ -0,0 +1,72 @@
+package server
+
+import (
+	"net/rpc"
+	"testing"
+	"time"
+)
+
+const (
+	testRPCHost = "localhost:12346"
+)
+
+var testRPCArgs = &Args{100, 200}
+
+type Args struct {
+	A, B int
+}
+
+type Arith int
+
+func (t *Arith) Multiply(args *Args, reply *int) error {
+	*reply = args.A * args.B
+	return nil
+}
+
+func validateRPCServer(t *testing.T, addr string, method string) {
+	rpccli, err := rpc.Dial("tcp", addr)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var reply int
+
+	err = rpccli.Call(method, testRPCArgs, &reply)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if reply != testRPCArgs.A*testRPCArgs.B {
+		t.Fatalf("rpc test faild, want %d, got %d", testRPCArgs.A*testRPCArgs.B, reply)
+	}
+}
+
+func TestRPCServer(t *testing.T) {
+	initLog("test", "debug")
+
+	testRPC := new(Arith)
+
+	err := rpc.Register(testRPC)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	handler := rpcHandler{}
+
+	svr := &RPCServer{
+		TCPServer{
+			addr:    testRPCHost,
+			handler: &handler,
+			useTls:  false,
+		},
+	}
+
+	err = svr.Start()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	time.Sleep(time.Millisecond * 300)
+
+	validateRPCServer(t, testRPCHost, "Arith.Multiply")
+}

+ 257 - 0
pkg/server/server.go

@@ -0,0 +1,257 @@
+// package server provides service interfaces and libraries.
+// including:
+// tcp/http server library.
+// rpc service library with addon functionality.
+// service discory and registration Logic.
+// statistic lib.
+package server
+
+import (
+	// "github.com/vharitonsky/iniflags"
+	"flag"
+	"net/http"
+	"net/rpc"
+	"time"
+)
+
+// server is a singleton
+var serverInstance *Server = nil
+
+// Server
+type Server struct {
+	// required
+	name string
+	// optional
+	rpcsvr    *RPCServer  // RPC server
+	tcpsvr    *TCPServer  // TCP server
+	httpsvr   *HTTPServer // HTTP server
+	timertask TimerTask   // timer task
+	// functions
+	svrmgr *ServerManager // service registration&discovery manager
+	rpccli *RPCClient     // rpc client
+}
+
+// init the server with specific name.
+func Init(name string) error {
+	if serverInstance == nil {
+		// read config
+		flag.Parse()
+
+		// read network info
+		readNetInterfaces()
+
+		// log
+		err := initLog(name, *confLogLevel)
+		if err != nil {
+			return err
+		}
+
+		// server instance
+		serverInstance = &Server{
+			name: name,
+		}
+
+		// init service manager
+		serverInstance.svrmgr, err = NewServerManager(name, *confEtcd)
+		if err != nil {
+			return err
+		}
+
+		// create RPC client
+		serverInstance.rpccli, err = NewRPCClient()
+		if err != nil {
+			return err
+		}
+
+		Log.Infof("server %s init success.", name)
+
+	}
+	return nil
+}
+
+// register TCP handler class
+func RegisterTCPHandler(handler TCPHandler) error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+	if serverInstance.tcpsvr == nil {
+		if *confTCPHost == "" {
+			return errorf(errMissingFlag, FlagTCPHost)
+		}
+
+		addr, err := fixHostIp(*confTCPHost)
+		if err != nil {
+			return errorf(errWrongHostAddr, confTCPHost)
+		}
+
+		serverInstance.tcpsvr = &TCPServer{
+			addr:    addr,
+			handler: handler,
+			useTls:  *confUseTls,
+		}
+	}
+	return nil
+}
+
+// register HTTP handler class
+func RegisterHTTPHandler(handler http.Handler) error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+	if serverInstance.httpsvr == nil {
+		if *confHTTPHost == "" {
+			return errorf(errMissingFlag, FlagHTTPHost)
+		}
+
+		addr, err := fixHostIp(*confHTTPHost)
+		if err != nil {
+			return errorf(errWrongHostAddr, FlagHTTPHost)
+		}
+
+		serverInstance.httpsvr = &HTTPServer{
+			addr:     addr,
+			handler:  handler,
+			useHttps: *confUseHttps,
+		}
+	}
+	return nil
+}
+
+// register RPC handler class
+func RegisterRPCHandler(rcvr interface{}) error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+	if serverInstance.rpcsvr == nil {
+		if *confRPCHost == "" {
+			return errorf(errMissingFlag, FlagRPCHost)
+		}
+
+		addr, err := fixHostIp(*confRPCHost)
+		if err != nil {
+			return errorf(errWrongHostAddr, *confRPCHost)
+		}
+
+		err = rpc.Register(rcvr)
+		if err != nil {
+			return errorf("Cannot Resgister RPC service: %s", err)
+		}
+
+		handler := rpcHandler{}
+
+		serverInstance.rpcsvr = &RPCServer{
+			TCPServer{
+				addr:    addr,
+				handler: &handler,
+				useTls:  false, // rpc service do not use tls because it's in internal network
+			},
+		}
+	}
+	return nil
+}
+
+// register timer task
+func RegisterTimerTask(task TimerTask) error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+	if serverInstance.timertask == nil {
+		serverInstance.timertask = task
+	}
+	return nil
+}
+
+// rpc call by name
+func RPCCallByName(serverName string, serverMethod string, args interface{}, reply interface{}) error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+
+	return serverInstance.rpccli.Call(serverName, serverMethod, args, reply)
+}
+
+// rpc call by host
+func RPCCallByHost(host string, serverMethod string, args interface{}, reply interface{}) error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+
+	return serverInstance.rpccli.CallHost(host, serverMethod, args, reply)
+}
+
+// get server's hosts by server name and service type
+func GetServerHosts(serverName string, hostType string) ([]string, error) {
+	if serverInstance == nil {
+		return nil, errorf(errServerNotInit)
+	}
+
+	return serverInstance.svrmgr.GetServerHosts(serverName, hostType)
+}
+
+// get this server's rpc host
+func GetRPCHost() string {
+	if serverInstance == nil || serverInstance.rpcsvr == nil {
+		return ""
+	}
+
+	return serverInstance.rpcsvr.addr
+}
+
+// start service
+func Run() error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+
+	if serverInstance.tcpsvr != nil {
+		err := serverInstance.tcpsvr.Start()
+		if err != nil {
+			return err
+		}
+		Log.Info("starting tcp server ... OK")
+	}
+
+	if serverInstance.httpsvr != nil {
+		err := serverInstance.httpsvr.Start()
+		if err != nil {
+			return err
+		}
+		Log.Info("starting http server ... OK")
+	}
+
+	if serverInstance.rpcsvr != nil {
+		err := serverInstance.rpcsvr.Start()
+		if err != nil {
+			return err
+		}
+		Log.Info("starting rpc server ... OK")
+	}
+
+	Log.Info("sever launch successfully!")
+
+	// loop to do something
+	for {
+		// server manager update
+		err := serverInstance.svrmgr.RegisterServer()
+		if err != nil {
+			Log.Warnf("RegisterServer error: %s", err)
+		} else {
+			Log.Info("RegisterServer Success")
+		}
+		err = serverInstance.svrmgr.UpdateServerHosts()
+		if err != nil {
+			Log.Error("UpdateServerHosts error: %s", err)
+		} else {
+			Log.Info("UpdateServerHosts Success")
+		}
+
+		//timer task
+		if serverInstance.timertask != nil {
+			serverInstance.timertask.DoTask()
+		}
+
+		time.Sleep(60 * time.Second)
+	}
+
+	return nil
+}

+ 161 - 0
pkg/server/server_manager.go

@@ -0,0 +1,161 @@
+// service registration and discovery
+
+package server
+
+import (
+	"errors"
+	"github.com/coreos/etcd/client"
+	"golang.org/x/net/context"
+	"os"
+	"strings"
+	"time"
+)
+
+const (
+	EtcdServersPrefix    = "/pando/servers/"
+	EtcdServersPrefixCnt = 2
+	EnvTCPProxy          = "TCP_PROXY_ADDR"
+	EnvHTTPProxy         = "HTTP_PROXY_ADDR"
+)
+
+type ServerManager struct {
+	serverName string
+	// servername -> hosttype -> hostlist
+	// eg. var hosts []string = mapServers["testserver"]["rpchost"]
+	mapServers map[string](map[string][]string)
+	etcdHosts  []string
+}
+
+// etcd hosts is config as http://ip1:port1;http://ip2:port2;http://ip3:port3
+func NewServerManager(name string, etcd string) (*ServerManager, error) {
+	if etcd == "" {
+		return nil, errors.New("no etcd host found!")
+	}
+	return &ServerManager{
+		serverName: name,
+		etcdHosts:  strings.Split(etcd, ";"),
+		mapServers: make(map[string](map[string][]string)),
+	}, nil
+}
+
+// register server to etcd
+func (mgr *ServerManager) RegisterServer() error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+	cfg := client.Config{
+		Endpoints: mgr.etcdHosts,
+		Transport: client.DefaultTransport,
+		// set timeout per request to fail fast when the target endpoint is unavailable
+		HeaderTimeoutPerRequest: time.Second,
+	}
+	c, err := client.New(cfg)
+	if err != nil {
+		return err
+	}
+	kapi := client.NewKeysAPI(c)
+	prefix := EtcdServersPrefix + mgr.serverName + "/"
+	var response *client.Response
+	opt := &client.SetOptions{TTL: 90 * time.Second}
+	if serverInstance.tcpsvr != nil {
+		addr := os.Getenv(EnvTCPProxy)
+		if addr == "" {
+			addr, _ = fixHostIp(*confTCPHost)
+		}
+		response, err = kapi.Set(context.Background(), prefix+FlagTCPHost+"/"+addr, addr, opt)
+	}
+	if serverInstance.rpcsvr != nil {
+		addr, _ := fixHostIp(*confRPCHost)
+		response, err = kapi.Set(context.Background(), prefix+FlagRPCHost+"/"+addr, addr, opt)
+	}
+	if serverInstance.httpsvr != nil {
+		addr := os.Getenv(EnvHTTPProxy)
+		if addr == "" {
+			addr, _ = fixHostIp(*confHTTPHost)
+		}
+		response, err = kapi.Set(context.Background(), prefix+FlagHTTPHost+"/"+addr, addr, opt)
+	}
+	if err != nil {
+		return err
+	}
+	// print common key info
+	Log.Infof("RegisterServer is done. Metadata is %q\n", response)
+
+	return nil
+}
+
+// update server hosts
+func (mgr *ServerManager) UpdateServerHosts() error {
+	if serverInstance == nil {
+		return errorf(errServerNotInit)
+	}
+
+	cfg := client.Config{
+		Endpoints: mgr.etcdHosts,
+		Transport: client.DefaultTransport,
+		// set timeout per request to fail fast when the target endpoint is unavailable
+		HeaderTimeoutPerRequest: time.Second,
+	}
+	c, err := client.New(cfg)
+	if err != nil {
+		return err
+	}
+
+	kapi := client.NewKeysAPI(c)
+	prefix := EtcdServersPrefix
+	opt := &client.GetOptions{Recursive: true}
+	response, err := kapi.Get(context.Background(), prefix, opt)
+	if err != nil {
+		return err
+	}
+
+	servers := make(map[string](map[string][]string))
+
+	root := response.Node
+	if root.Dir != true {
+		return errorf(errWrongEtcdPath, root.Key)
+	}
+	for _, server := range root.Nodes {
+		if server.Dir != true {
+			return errorf(errWrongEtcdPath, server.Key)
+		}
+		name := strings.Split(server.Key, "/")[EtcdServersPrefixCnt+1]
+		servers[name] = make(map[string][]string)
+		for _, hosttype := range server.Nodes {
+			if hosttype.Dir != true {
+				return errorf(errWrongEtcdPath, hosttype.Key)
+			}
+			host := strings.Split(hosttype.Key, "/")[EtcdServersPrefixCnt+2]
+			servers[name][host] = []string{}
+			for _, hostaddr := range hosttype.Nodes {
+				addr := strings.Split(hostaddr.Key, "/")[EtcdServersPrefixCnt+3]
+				servers[name][host] = append(servers[name][host], addr)
+			}
+		}
+	}
+
+	mgr.mapServers = servers
+
+	Log.Infof("UpdateServerHosts is done: %v", mgr.mapServers)
+	return nil
+
+}
+
+// get host ips for the server, now return all hosts
+func (mgr *ServerManager) GetServerHosts(serverName string, hostType string) ([]string, error) {
+	server, ok := mgr.mapServers[serverName]
+	if !ok {
+		// try update server hosts mannually
+		mgr.UpdateServerHosts()
+	}
+	server, ok = mgr.mapServers[serverName]
+	if !ok {
+		return nil, errorf("no server for %s", serverName)
+	}
+	hosts, ok := server[hostType]
+	if !ok || len(hosts) == 0 {
+		return nil, errorf("no hosts for %s:%s", serverName, hostType)
+	}
+
+	return hosts, nil
+}

+ 141 - 0
pkg/server/server_test.go

@@ -0,0 +1,141 @@
+package server
+
+import (
+	"reflect"
+	"testing"
+	"time"
+)
+
+type Arith2 Arith
+
+func (t *Arith2) Multiply(args *Args, reply *int) error {
+	*reply = args.A * args.B
+	return nil
+}
+
+type testTimer struct{}
+
+func (t *testTimer) DoTask() {
+	Log.Info("timer task fires.")
+}
+
+func validateGetServerHosts(t *testing.T, flag string, want string) {
+	hosts, err := GetServerHosts("test", flag)
+	if err != nil {
+		t.Error(err)
+	}
+	if !reflect.DeepEqual(hosts, []string{want}) {
+		t.Errorf("error get server hosts, want: %v, got %v", []string{want}, hosts)
+	}
+}
+
+func validateGetRPCHost(t *testing.T) {
+	host := GetRPCHost()
+
+	if host == "" {
+		t.Error("get rpc host test error")
+	}
+}
+
+func validateServerManager(t *testing.T) {
+	validateGetServerHosts(t, FlagTCPHost, *confTCPHost)
+	validateGetServerHosts(t, FlagRPCHost, *confRPCHost)
+	validateGetServerHosts(t, FlagHTTPHost, *confHTTPHost)
+}
+
+func registerBadHandlers(t *testing.T) {
+	// test TCP
+	testtcp := &testEchoHandler{}
+	err := RegisterTCPHandler(testtcp)
+	if err == nil {
+		t.Errorf("RegisterTCPHandler shoud fail when server is not initialized.")
+	}
+
+	// test RPC
+	testrpc := new(Arith2)
+	err = RegisterRPCHandler(testrpc)
+	if err == nil {
+		t.Errorf("RegisterRPCService shoud fail when server is not initialized.")
+	}
+
+	// test HTTP
+	testhttp := &testHttpHandler{}
+	err = RegisterHTTPHandler(testhttp)
+	if err == nil {
+		t.Errorf("RegisterHTTPServer shoud fail when server is not initialized.")
+	}
+
+	// test timer
+	timer := &testTimer{}
+	err = RegisterTimerTask(timer)
+	if err == nil {
+		t.Errorf("RegisterTimerTask shoud fail when server is not initialized.")
+	}
+}
+
+func registerHandlers(t *testing.T) {
+	// test TCP
+	testtcp := &testEchoHandler{}
+	err := RegisterTCPHandler(testtcp)
+	if err != nil {
+		t.Errorf("RegisterTCPHandler : %s", err)
+	}
+
+	// test RPC
+	testrpc := new(Arith2)
+	err = RegisterRPCHandler(testrpc)
+	if err != nil {
+		t.Errorf("RegisterRPCService : %s", err)
+	}
+
+	// test HTTP
+	testhttp := &testHttpHandler{}
+	err = RegisterHTTPHandler(testhttp)
+	if err != nil {
+		t.Errorf("RegisterHTTPServer : %s", err)
+	}
+
+	// test timer
+	timer := &testTimer{}
+	err = RegisterTimerTask(timer)
+	if err != nil {
+		t.Errorf("RegisterTimerTask : %s", err)
+	}
+}
+
+func TestServer(t *testing.T) {
+	*confHTTPHost = "localhost:59000"
+	*confTCPHost = "localhost:59001"
+	*confRPCHost = "localhost:59002"
+	*confUseHttps = true
+	*confUseTls = true
+	*confCAFile = "testdata/cert.pem"
+	*confKeyFile = "testdata/key.pem"
+	*confEtcd = "http://localhost:2379"
+
+	// before init , should all fail
+	registerBadHandlers(t)
+
+	err := Init("test")
+	if err != nil {
+		t.Fatalf("%s", err)
+	}
+
+	registerHandlers(t)
+
+	go func() {
+		err = Run()
+		if err != nil {
+			t.Errorf("Run Server error : %s", err)
+		}
+	}()
+
+	time.Sleep(time.Second * 3)
+
+	validateHTTPSServer(t, "https://"+*confHTTPHost)
+	validateTLSServer(t, *confTCPHost)
+	validateRPCServer(t, *confRPCHost, "Arith2.Multiply")
+	validateRPCClient(t)
+	validateServerManager(t)
+	validateGetRPCHost(t)
+}

+ 77 - 0
pkg/server/tcp_server.go

@@ -0,0 +1,77 @@
+// tcp server library.
+package server
+
+import (
+	"crypto/tls"
+	"net"
+)
+
+type TCPHandler interface {
+	Handle(net.Conn)
+}
+
+type TCPServer struct {
+	addr    string
+	handler TCPHandler
+	useTls  bool
+}
+
+// start will keep accepting and serving tcp connections.
+func (ts *TCPServer) Start() error {
+	// check for conditions
+	if ts.handler == nil {
+		return errorf(errTCPHandlerNotRegistered)
+	}
+	// listen
+	var ln net.Listener
+	var err error
+	if ts.useTls {
+		// if use tls, then load pem files and start server
+		if *confCAFile == "" {
+			return errorf(errMissingFlag, FlagCAFile)
+		}
+		if *confKeyFile == "" {
+			return errorf(errMissingFlag, FlagKeyFile)
+		}
+
+		// process key files
+		cert, err := tls.LoadX509KeyPair(*confCAFile, *confKeyFile)
+		if err != nil {
+			return errorf(errLoadSecureKey, err.Error())
+		}
+
+		// config server with tls
+		config := tls.Config{Certificates: []tls.Certificate{cert}}
+
+		// listen for new connection
+		ln, err = tls.Listen("tcp", ts.addr, &config)
+
+		if err != nil {
+			return errorf(errListenFailed, ts.addr, err)
+		}
+
+	} else {
+		// don't use tls, just listen
+		ln, err = net.Listen("tcp", ts.addr)
+		if err != nil {
+			return errorf(errListenFailed, ts.addr, err)
+		}
+	}
+
+	Log.Infof("TCP Server Listen on %s, use tls: %v", ts.addr, ts.useTls)
+
+	// continously accept connections and serve, nonblock
+	go func() {
+		for {
+			conn, err := ln.Accept()
+			if err != nil {
+				Log.Errorf(errNewConnection, err.Error())
+				continue
+			}
+			Log.Infof("accepting new connection %s", conn.RemoteAddr())
+			go ts.handler.Handle(conn)
+		}
+	}()
+
+	return nil
+}

+ 95 - 0
pkg/server/tcp_server_test.go

@@ -0,0 +1,95 @@
+package server
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net"
+	"testing"
+	"time"
+)
+
+const (
+	testTCPHost = "localhost:12345"
+)
+
+var testEchoData = "hello pando"
+
+type testEchoHandler struct{}
+
+func (h testEchoHandler) Handle(conn net.Conn) {
+	buf := make([]byte, 1024)
+	for {
+		length, err := conn.Read(buf)
+		if err != nil {
+			fmt.Println(err)
+		}
+		length, err = conn.Write(buf[:length])
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+}
+
+func validateTCPServer(t *testing.T, addr string) {
+	cli, err := net.Dial("tcp", addr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = cli.Write([]byte(testEchoData))
+	if err != nil {
+		t.Fatal(err)
+	}
+	buf := make([]byte, 1024)
+	length, err := cli.Read(buf)
+	if err != nil {
+		t.Fatal(err)
+	}
+	gotData := string(buf[:length])
+	if gotData != testEchoData {
+		t.Errorf("echo server test failed. want: %s, got: %s", testEchoData, gotData)
+	}
+}
+
+func validateTLSServer(t *testing.T, addr string) {
+	conf := &tls.Config{
+		InsecureSkipVerify: true,
+	}
+	cli, err := tls.Dial("tcp", addr, conf)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = cli.Write([]byte(testEchoData))
+	if err != nil {
+		t.Fatal(err)
+	}
+	buf := make([]byte, 1024)
+	length, err := cli.Read(buf)
+	if err != nil {
+		t.Fatal(err)
+	}
+	gotData := string(buf[:length])
+	if gotData != testEchoData {
+		t.Errorf("echo server test failed. want: %s, got: %s", testEchoData, gotData)
+	}
+}
+
+func TestTCPServer(t *testing.T) {
+	initLog("test", "debug")
+
+	h := testEchoHandler{}
+
+	svr := &TCPServer{
+		addr:    testTCPHost,
+		handler: h,
+		useTls:  false,
+	}
+
+	err := svr.Start()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	time.Sleep(time.Millisecond * 100)
+
+	validateTCPServer(t, testTCPHost)
+}

+ 18 - 0
pkg/server/testdata/cert.pem

@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+TCCAeGgAwIBAgIQJfY0073hjBlu0vY/xbvYHzANBgkqhkiG9w0BAQsFADAS
+MRAwDgYDVQQKEwdBY21lIENvMB4XDTE1MTAyMjA3MzkwOFoXDTE2MTAyMTA3Mzkw
+OFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMbJZYRbvbevHkFzkVrP8Zg54QAfiaCMx36W9YWVaTf793Y90dqmUAdr
+A7DhB5TO6Dz0L1Zao+7HAqwEHxNSkOjGDxI3zo3/N5Q5dStHmmTRz49+lUR1L1NA
+kX9QlvPPgkAGb9Z40CmtckZStH9QXujxuYqH18HJnbuVxntogbNMfCX98Ix3N6oI
+ktuRE/GtysmJ7YyrPP1JUv358jA3Y44WLFFcJ3+X4MsZEthEqRsb/fqd5WUfRarK
+HgC5AuX226out8QRfd0WWjiuWaXWmV7y9lw2Buzakh4Wu+P0tkbc+iLwtTlhEgx9
+dXMIZfVrO6OcAkNxunbojuCR/wl0REkCAwEAAaNLMEkwDgYDVR0PAQH/BAQDAgWg
+MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJ
+bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQB1fqZEISFArzR5oIiy1tdJu4wO
+YcZxKvemJo7PaZluNE4eAw1ITbVZKEGpSzi5dYC5A13Q5pP8ue7FvOQonm5elEY4
+fLDZhzpfnRj+oDhc+NPBhczCh640gcdxe6BtAcmsOJJEenLEPsGj3NrQZbPl9anL
+94v9NPdvFuy1ZpLMRb8WEZO4f/iGTAj7Hg7tzxvWMIg0mbrZg9spR2wgRqyzXPEK
+A0awbuFuVDmg1+x1SVq6BjY+XL9+B4KiCAOQFA5dX+v1+ruSxsliUzcM8840V8i6
+uRBTV982FZWhbuiFJJY4/c46tjJ0DRvXdkUiEAeLeTaIb1Lkk2Q+XtfvV+2c
+-----END CERTIFICATE-----

+ 27 - 0
pkg/server/testdata/key.pem

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAxsllhFu9t68eQXORWs/xmDnhAB+JoIzHfpb1hZVpN/v3dj3R
+2qZQB2sDsOEHlM7oPPQvVlqj7scCrAQfE1KQ6MYPEjfOjf83lDl1K0eaZNHPj36V
+RHUvU0CRf1CW88+CQAZv1njQKa1yRlK0f1Be6PG5iofXwcmdu5XGe2iBs0x8Jf3w
+jHc3qgiS25ET8a3KyYntjKs8/UlS/fnyMDdjjhYsUVwnf5fgyxkS2ESpGxv9+p3l
+ZR9FqsoeALkC5fbbqi63xBF93RZaOK5ZpdaZXvL2XDYG7NqSHha74/S2Rtz6IvC1
+OWESDH11cwhl9Ws7o5wCQ3G6duiO4JH/CXRESQIDAQABAoIBAHTduBn9WWbgxBfU
+mpLaB33oIRhScjX6LdqFY2iac1Zfgpd4NqSl/AywZGYblbptfek/4YiSFyhsxWxd
+q+tPMjQ3JGsgdgXTEljJAtJj8SfulWkWESlC/4ShRCimN2i1CS0c26kqM68c8j7X
+ppfmpzWpztvbiwO5xUqf/iVVRlMizdDXXPssGJlDI/DtiyVx1tMt0YkUrsrZKW2P
+cTrYWA638GnQ6IDYPp9rQYwkaAeWbvm1aI5CqB7fJdIsyp9XslUGdUiRe1yIoT/G
+q/BMP38NfyxfbZQzVPrniZl67wwVRLvp3Te2PKjOts89/yBN5FaqgdIpbzJ06aOc
+9Mqci9ECgYEA2/GUVMnjmct0Wd1/PaPVWfjyMknVIMP2pLZLN07DEu6x+5i2DOtd
+B6bp23yDyX2okNho0GTmq+l7qRaMNb/pFcOiFuduaJ4H5NM445pw9EWwn9keah6R
+SFE7pq2qoBRi2ogiqJGnMOrWNmBvxBVYDqavWLLEZuvjAaJWJWKb6qUCgYEA51/r
+JCG3bLE4Q3anmaXer0Bpqwr7iDRM1qw+3gAzWN/ePUtEefC4iUPnxRKxRy/ovDGZ
+iUCmYD6p3yR0g9+4pdmgoeekQm81Rrr6jk9T3bMpg15x1h9VqKqnZliXMo4dH67o
+HUTUIPag8YUnFavDi63zZzQgUwTU7YJw0PUIldUCgYEAldz6s/dIRO+zXNQmsepj
+IkYVSRyzwvqjoUGw6IObOdg4n/VC/nSU0/TwXaRu66jaZa9/Y5eM8VBK+UCq0qjr
++e9uD2sr+M7NFCa0GamPE3I00gYPykD/vyXSnlSw8RhS7xJZg0CTiipJQY3eOd5N
+PNZonehZGMPbzq49QoX5NR0CgYEAyIA+bFkrcm1ArHWuV1990bCn4SjrP+TSkVVC
+RW83D9Uv6T3IYUNRJuJJfmXsahwCOtNgkagMhWrIGi6lKYI9qLsmkCcEGO315Q0z
+Aw+LRZt0Zfr5+uu8dyUrW2152L1+T25qhrKWgNo4LhONAyKNmgVr4Asz171gJ1Ha
+Ibm1buECgYEAmNBNkfgm4OcGOABcR/8TU1uBE7aplN8XUOv3rTslD9gs4SCMmK1x
+87MofB4aX6gQ2KNfvIlOQ8vhxc5XgTTYIZt+TL/UlQXQKPeKgX8ok9I3U7YEmhJ+
+98q5D+qpyQOqVO/mcgwN3+YWG8fq9OrEcB/anFYJb7KtQbXWm3p7iX0=
+-----END RSA PRIVATE KEY-----

+ 7 - 0
pkg/server/timer_task.go

@@ -0,0 +1,7 @@
+// the timer task interface.
+
+package server
+
+type TimerTask interface {
+	DoTask()
+}

+ 12 - 0
pkg/server/utils.go

@@ -0,0 +1,12 @@
+package server
+
+import (
+	"fmt"
+)
+
+// will print a log and return error
+func errorf(format string, a ...interface{}) error {
+	err := fmt.Errorf(format, a...)
+	Log.Error(err)
+	return err
+}

+ 13 - 0
pkg/server/utils_test.go

@@ -0,0 +1,13 @@
+package server
+
+import (
+	"errors"
+	"testing"
+)
+
+func TestErrorf(t *testing.T) {
+	err := errorf("err %s %d", "1", 2)
+	if err.Error() != "err 1 2" {
+		t.Errorf("err is %v ,want %v", err, errors.New("err 1 2"))
+	}
+}

+ 346 - 0
pkg/tlv/tlv.go

@@ -0,0 +1,346 @@
+package tlv
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+)
+
+const (
+	TLV_FLOAT64 = 1
+	TLV_FLOAT32 = 2
+	TLV_INT8    = 3
+	TLV_INT16   = 4
+	TLV_INT32   = 5
+	TLV_INT64   = 6
+	TLV_UINT8   = 7
+	TLV_UINT16  = 8
+	TLV_UINT32  = 9
+	TLV_UINT64  = 10
+	TLV_BYTES   = 11
+	TLV_STRING  = 12
+	TLV_BOOL    = 13
+)
+
+type TLV struct {
+	Tag   uint16
+	Value []byte
+}
+
+func Uint16ToByte(value uint16) []byte {
+	buf := bytes.NewBuffer([]byte{})
+	binary.Write(buf, binary.BigEndian, value)
+
+	return buf.Bytes()
+}
+
+func ByteToUint16(buf []byte) uint16 {
+	tmpBuf := bytes.NewBuffer(buf)
+	var value uint16
+	binary.Read(tmpBuf, binary.BigEndian, &value)
+
+	return value
+}
+
+func (tlv *TLV) ToBinary() []byte {
+	buf := new(bytes.Buffer)
+	binary.Write(buf, binary.BigEndian, &tlv.Tag)
+	binary.Write(buf, binary.BigEndian, &tlv.Value)
+
+	return buf.Bytes()
+}
+
+func (tlv *TLV) Length() int {
+	length := int(0)
+	switch tlv.Tag {
+	case TLV_FLOAT64:
+		length = 8
+	case TLV_INT64:
+		length = 8
+	case TLV_UINT64:
+		length = 8
+	case TLV_FLOAT32:
+		length = 4
+	case TLV_INT32:
+		length = 4
+	case TLV_UINT32:
+		length = 4
+	case TLV_INT16:
+		length = 2
+	case TLV_UINT16:
+		length = 2
+	case TLV_INT8:
+		length = 1
+	case TLV_UINT8:
+		length = 1
+	case TLV_BYTES:
+		length = int(ByteToUint16(tlv.Value[0:2]))
+		length += 2
+	case TLV_STRING:
+		length = int(ByteToUint16(tlv.Value[0:2]))
+		length += 2
+	default:
+		length = 0
+	}
+
+	length += 2
+
+	return length
+}
+
+func (tlv *TLV) FromBinary(r io.Reader) error {
+	binary.Read(r, binary.BigEndian, &tlv.Tag)
+	length := uint16(0)
+	switch tlv.Tag {
+	case TLV_FLOAT64:
+		length = 8
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_INT64:
+		length = 8
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_UINT64:
+		length = 8
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_FLOAT32:
+		length = 4
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_INT32:
+		length = 4
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_UINT32:
+		length = 4
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_INT16:
+		length = 2
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_UINT16:
+		length = 2
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_INT8:
+		length = 1
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_UINT8:
+		length = 1
+		tlv.Value = make([]byte, length)
+		binary.Read(r, binary.BigEndian, &tlv.Value)
+	case TLV_BYTES:
+		binary.Read(r, binary.BigEndian, &length)
+		tlv.Value = make([]byte, length+2)
+		copy(tlv.Value[0:2], Uint16ToByte(length))
+		binary.Read(r, binary.BigEndian, tlv.Value[2:])
+	case TLV_STRING:
+		binary.Read(r, binary.BigEndian, &length)
+		tlv.Value = make([]byte, length+2)
+		copy(tlv.Value[0:2], Uint16ToByte(length))
+		binary.Read(r, binary.BigEndian, tlv.Value[2:])
+	default:
+		return errors.New(fmt.Sprintf("unsuport value: %d", tlv.Tag))
+	}
+
+	return nil
+}
+
+func MakeTLV(a interface{}) (*TLV, error) {
+	var tag uint16
+	var length uint16
+	buf := new(bytes.Buffer)
+	switch a.(type) {
+	case float64:
+		tag = TLV_FLOAT64
+		length = 8
+		binary.Write(buf, binary.BigEndian, a.(float64))
+	case float32:
+		tag = TLV_FLOAT32
+		length = 4
+		binary.Write(buf, binary.BigEndian, a.(float32))
+	case int8:
+		tag = TLV_INT8
+		length = 1
+		binary.Write(buf, binary.BigEndian, a.(int8))
+	case int16:
+		tag = TLV_INT16
+		length = 2
+		binary.Write(buf, binary.BigEndian, a.(int16))
+	case int32:
+		tag = TLV_INT32
+		length = 4
+		binary.Write(buf, binary.BigEndian, a.(int32))
+	case int64:
+		tag = TLV_INT64
+		length = 8
+		binary.Write(buf, binary.BigEndian, a.(int64))
+	case uint8:
+		tag = TLV_UINT8
+		length = 1
+		binary.Write(buf, binary.BigEndian, a.(uint8))
+	case uint16:
+		tag = TLV_UINT16
+		length = 2
+		binary.Write(buf, binary.BigEndian, a.(uint16))
+	case uint32:
+		tag = TLV_UINT32
+		length = 4
+		binary.Write(buf, binary.BigEndian, a.(uint32))
+	case uint64:
+		tag = TLV_UINT64
+		length = 8
+		binary.Write(buf, binary.BigEndian, a.(uint64))
+	case []byte:
+		tag = TLV_BYTES
+		length = uint16(len(a.([]byte)))
+		binary.Write(buf, binary.BigEndian, length)
+		binary.Write(buf, binary.BigEndian, a.([]byte))
+	case string:
+		tag = TLV_STRING
+		length = uint16(len(a.(string)))
+		binary.Write(buf, binary.BigEndian, length)
+		binary.Write(buf, binary.BigEndian, []byte(a.(string)))
+	default:
+		return nil, errors.New(fmt.Sprintf("unsuport value: %v", a))
+	}
+
+	tlv := TLV{
+		Tag:   tag,
+		Value: buf.Bytes(),
+	}
+
+	if length == 0 {
+		tlv.Value = []byte{}
+	}
+
+	return &tlv, nil
+}
+
+func ReadTLV(tlv *TLV) (interface{}, error) {
+	tag := tlv.Tag
+	length := uint16(0)
+	value := tlv.Value
+
+	buffer := bytes.NewReader(value)
+	switch tag {
+	case TLV_FLOAT64:
+		retvar := float64(0.0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_FLOAT32:
+		retvar := float32(0.0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_INT8:
+		retvar := int8(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_INT16:
+		retvar := int16(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_INT32:
+		retvar := int32(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_INT64:
+		retvar := int64(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_UINT8:
+		retvar := uint8(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_UINT16:
+		retvar := uint16(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_UINT32:
+		retvar := uint32(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_UINT64:
+		retvar := uint64(0)
+		err := binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_BYTES:
+		err := binary.Read(buffer, binary.BigEndian, &length)
+		if err != nil {
+			return []byte{}, err
+		}
+		retvar := make([]byte, length)
+		err = binary.Read(buffer, binary.BigEndian, &retvar)
+		return retvar, err
+	case TLV_STRING:
+		err := binary.Read(buffer, binary.BigEndian, &length)
+		if err != nil {
+			return string([]byte{}), err
+		}
+		retvar := make([]byte, length)
+		err = binary.Read(buffer, binary.BigEndian, &retvar)
+		return string(retvar), err
+	default:
+		return nil, errors.New("Reading TLV error ,Unkown TLV type: " + string(tag))
+	}
+}
+
+func MakeTLVs(a []interface{}) ([]TLV, error) {
+	tlvs := []TLV{}
+	for _, one := range a {
+		tlv, err := MakeTLV(one)
+		if err != nil {
+			return nil, err
+		}
+		tlvs = append(tlvs, *tlv)
+	}
+	return tlvs, nil
+}
+
+func ReadTLVs(tlvs []TLV) ([]interface{}, error) {
+	values := []interface{}{}
+	for _, tlv := range tlvs {
+		one, err := ReadTLV(&tlv)
+		if err != nil {
+			return values, err
+		}
+		values = append(values, one)
+	}
+	return values, nil
+}
+
+func CastTLV(value interface{}, valueType int32) interface{} {
+	switch valueType {
+	case TLV_FLOAT64:
+		return float64(value.(float64))
+	case TLV_FLOAT32:
+		return float32(value.(float64))
+	case TLV_INT8:
+		return int8(value.(float64))
+	case TLV_INT16:
+		return int16(value.(float64))
+	case TLV_INT32:
+		return int32(value.(float64))
+	case TLV_INT64:
+		return int64(value.(float64))
+	case TLV_UINT8:
+		return uint8(value.(float64))
+	case TLV_UINT16:
+		return uint16(value.(float64))
+	case TLV_UINT32:
+		return uint32(value.(float64))
+	case TLV_UINT64:
+		return uint64(value.(float64))
+	case TLV_BYTES:
+		return []byte(value.(string))
+	case TLV_STRING:
+		return value.(string)
+	default:
+		return nil
+	}
+}

+ 118 - 0
pkg/tlv/tlv_test.go

@@ -0,0 +1,118 @@
+package tlv
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+)
+
+func TestTlvLen(t *testing.T) {
+	float64Tlv, _ := MakeTLV(float64(0.12))
+	if float64Tlv.Length() != 10 {
+		t.Errorf("float64 len is not right\n")
+	}
+
+	int64Tlv, _ := MakeTLV(int64(100))
+	if int64Tlv.Length() != 10 {
+		t.Errorf("int64 len is not right\n")
+	}
+
+	uint64Tlv, _ := MakeTLV(uint64(100))
+	if uint64Tlv.Length() != 10 {
+		t.Errorf("uint64 len is not right\n")
+	}
+
+	float32Tlv, _ := MakeTLV(float32(0.12))
+	if float32Tlv.Length() != 6 {
+		t.Errorf("float32 len is not right\n")
+	}
+
+	int32Tlv, _ := MakeTLV(int32(100))
+	if int32Tlv.Length() != 6 {
+		t.Errorf("int32 len is not right\n")
+	}
+
+	uint32Tlv, _ := MakeTLV(uint32(100))
+	if uint32Tlv.Length() != 6 {
+		t.Errorf("uint32 len is not right\n")
+	}
+
+	int16Tlv, _ := MakeTLV(int16(100))
+	if int16Tlv.Length() != 4 {
+		t.Errorf("int16 len is not right\n")
+	}
+
+	uint16Tlv, _ := MakeTLV(uint16(100))
+	if uint16Tlv.Length() != 4 {
+		t.Errorf("uint16 len is not right\n")
+	}
+
+	int8Tlv, _ := MakeTLV(int8(100))
+	if int8Tlv.Length() != 3 {
+		t.Errorf("int8 len is not right\n")
+	}
+
+	uint8Tlv, _ := MakeTLV(uint8(100))
+	if uint8Tlv.Length() != 3 {
+		t.Errorf("uint8 len is not right\n")
+	}
+
+	byteValue := []byte{'1', '0', '0'}
+	byteTLV, _ := MakeTLV(byteValue)
+	if byteTLV.Length() != len(byteValue)+4 {
+		t.Errorf("byte len is not right\n")
+	}
+
+	str := "100"
+	strTLV, _ := MakeTLV(str)
+	if strTLV.Length() != len(str)+4 {
+		t.Errorf("string len is not right\n")
+	}
+}
+
+func TestUintAndByte(t *testing.T) {
+	value := uint16(100)
+	byteValue := Uint16ToByte(value)
+	newValue := ByteToUint16(byteValue)
+
+	if value != newValue {
+		t.Errorf("origin: %d, now: %d\n", value, newValue)
+	}
+}
+
+func TestTlvs(t *testing.T) {
+	str := "itachili"
+	params := []interface{}{float64(0.1), int64(100), uint64(200), uint32(300), int32(16), float32(3.2), int16(20), uint16(30), int8(1), uint8(2), []byte{'1', '2', '3'}, str}
+
+	tlvs, err := MakeTLVs(params)
+	if err != nil {
+		t.Error(err)
+	}
+
+	newParams, err := ReadTLVs(tlvs)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if !reflect.DeepEqual(params, newParams) {
+		t.Errorf("the origin:\n%x\n, now:\n%x\n", params, newParams)
+	}
+}
+
+func TestTlvBinary(t *testing.T) {
+	str := "itachili"
+	params := []interface{}{float64(0.1), int64(100), uint64(200), uint32(300), int32(16), float32(3.2), int16(20), uint16(30), int8(1), uint8(2), []byte{'1', '2', '3'}, str}
+	tlv, err := MakeTLV(params[0])
+	if err != nil {
+		t.Error(err)
+	}
+
+	bin := tlv.ToBinary()
+	buf := bytes.NewReader(bin)
+	newTlv := &TLV{}
+	newTlv.FromBinary(buf)
+
+	if !reflect.DeepEqual(tlv, newTlv) {
+		t.Errorf("the origin:\n%x\n, now:\n%x\n", tlv, newTlv)
+	}
+}

+ 92 - 0
pkg/token/token.go

@@ -0,0 +1,92 @@
+package token
+
+import (
+	"errors"
+	"sparrow/pkg/generator"
+	"sparrow/pkg/redispool"
+	"reflect"
+	"strconv"
+)
+
+const (
+	DeviceTokenKeyPrefix = "device:token:"
+	DeviceTokenExpires   = 7200
+)
+
+type Helper struct {
+	redishost string
+}
+
+func NewHelper(host string) *Helper {
+	helper := &Helper{
+		redishost: host,
+	}
+	return helper
+}
+
+func (helper *Helper) GenerateToken(id uint64) ([]byte, error) {
+	token, err := generator.GenRandomToken()
+	if err != nil {
+		return nil, err
+	}
+
+	conn, err := redispool.GetClient(helper.redishost)
+	if err != nil {
+		return nil, err
+	}
+
+	key := DeviceTokenKeyPrefix + strconv.FormatUint(id, 10)
+
+	_, err = conn.Do("SET", key, token)
+	if err != nil {
+		return nil, err
+	}
+	_, err = conn.Do("EXPIRE", key, DeviceTokenExpires)
+	if err != nil {
+		return nil, err
+	}
+
+	return token, nil
+
+}
+
+func (helper *Helper) ValidateToken(id uint64, token []byte) error {
+	key := DeviceTokenKeyPrefix + strconv.FormatUint(id, 10)
+
+	conn, err := redispool.GetClient(helper.redishost)
+	if err != nil {
+		return err
+	}
+
+	readToken, err := conn.Do("GET", key)
+	if err != nil {
+		return err
+	}
+
+	if !reflect.DeepEqual(readToken, token) {
+		return errors.New("token not match.")
+	}
+
+	_, err = conn.Do("EXPIRE", key, DeviceTokenExpires)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (helper *Helper) ClearToken(id uint64) error {
+	key := DeviceTokenKeyPrefix + strconv.FormatUint(id, 10)
+
+	conn, err := redispool.GetClient(helper.redishost)
+	if err != nil {
+		return err
+	}
+
+	_, err = conn.Do("DEL", key)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 27 - 0
pkg/token/token_test.go

@@ -0,0 +1,27 @@
+package token
+
+import (
+	"testing"
+)
+
+func TestTokenHelper(t *testing.T) {
+	helper := NewHelper("localhost:6379")
+
+	testid := uint64(123)
+
+	token, err := helper.GenerateToken(testid)
+	if err != nil {
+		t.Error(err)
+	}
+
+	err = helper.ValidateToken(testid, token)
+	if err != nil {
+		t.Error(err)
+	}
+
+	err = helper.ClearToken(testid)
+	if err != nil {
+		t.Error(err)
+	}
+
+}

+ 41 - 0
pkg/utils/http.go

@@ -0,0 +1,41 @@
+package utils
+
+import (
+	"bytes"
+	"crypto/tls"
+	"io/ioutil"
+	"net/http"
+)
+
+/**
+  Params:
+    argUrl: reqeust url
+    argReq: reqeust contents
+    argType: reqeust type
+    argHead: reqeust head
+  Retrun: reqesut result body
+*/
+func SendHttpRequest(argUrl string, argReq string, argType string, argHead map[string]string) ([]byte, error) {
+	bReq := []byte(argReq)
+	req, err := http.NewRequest(argType, argUrl, bytes.NewBuffer(bReq))
+	if err != nil {
+		return []byte{}, err
+	}
+	req.Header.Set("Content-Type", "application/json")
+	if argHead != nil {
+		for key, vaule := range argHead {
+			req.Header.Set(key, vaule)
+		}
+	}
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	client := &http.Client{Transport: tr}
+	resp, err := client.Do(req)
+	if err != nil {
+		return []byte{}, err
+	}
+	defer resp.Body.Close()
+	body, _ := ioutil.ReadAll(resp.Body)
+	return body, nil
+}

+ 18 - 0
pkg/utils/http_test.go

@@ -0,0 +1,18 @@
+package utils
+
+import (
+	"testing"
+)
+
+func TestSendHttpRequest(t *testing.T) {
+	headers := make(map[string]string)
+	headers["test"] = "test"
+
+	res, err := SendHttpRequest("http://www.baidu.com", "", "GET", headers)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	t.Log(len(res))
+}

+ 8 - 0
services/README.md

@@ -0,0 +1,8 @@
+# core services to serve iot devices.
+
+- **mqttaccess**: mqtt access service which accepts device mqtt connections. 
+- **httpaccess**: device api service which offers device http apis like authentication, registration, etc.
+- **devicemanager**: device info and status management.
+- **controller**: logic and route service.
+- **apiprovidor**: http apis for applications, and notify device status changes to applications.
+- **registry**: service that keep global configuration and info.

+ 233 - 0
services/apiprovider/actions.go

@@ -0,0 +1,233 @@
+package main
+
+import (
+	"encoding/json"
+	"sparrow/pkg/productconfig"
+	"sparrow/pkg/rpcs"
+
+	"net/http"
+
+	"sparrow/pkg/models"
+	"sparrow/pkg/server"
+
+	"github.com/go-martini/martini"
+	"github.com/martini-contrib/render"
+)
+
+const (
+	ErrOK                 = 0
+	ErrSystemFault        = 10001
+	ErrProductNotFound    = 10002
+	ErrDeviceNotFound     = 10003
+	ErrDeviceNotOnline    = 10004
+	ErrWrongRequestFormat = 10005
+	ErrWrongProductConfig = 10006
+	ErrWrongQueryFormat   = 10007
+	ErrAccessDenied       = 10008
+)
+
+const (
+	defaultTimeOut = 3 // seconds
+)
+
+func renderError(code int, err error) Common {
+	result := Common{}
+	result.Code = code
+	result.Message = err.Error()
+	server.Log.Error(err.Error())
+	return result
+}
+
+func GetDeviceInfoByKey(params martini.Params, req *http.Request, r render.Render) {
+	key := req.URL.Query().Get("device_key")
+	server.Log.Printf("ACTION GetDeviceInfoByKey, key:: %v", key)
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.ValidateDevice", key, device)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
+		return
+	}
+
+	result := DeviceInfoResponse{
+		Data: DeviceInfoData{
+			Identifier:  device.DeviceIdentifier,
+			Name:        device.DeviceName,
+			Description: device.DeviceDescription,
+			Version:     device.DeviceVersion,
+		},
+	}
+	r.JSON(http.StatusOK, result)
+	return
+}
+
+func GetDeviceInfoByIdentifier(urlparams martini.Params, r render.Render) {
+	identifier := urlparams["identifier"]
+	server.Log.Printf("ACTION GetDeviceInfoByIdentifier, identifier:: %v", identifier)
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.FindDeviceByIdentifier", identifier, device)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
+		return
+	}
+
+	result := DeviceInfoResponse{
+		Data: DeviceInfoData{
+			Identifier:  device.DeviceIdentifier,
+			Name:        device.DeviceName,
+			Description: device.DeviceDescription,
+			Version:     device.DeviceVersion,
+		},
+	}
+	r.JSON(http.StatusOK, result)
+	return
+}
+
+func GetDeviceCurrentStatus(device *models.Device, config *productconfig.ProductConfig,
+	urlparams martini.Params, r render.Render) {
+	server.Log.Printf("ACTION GetDeviceCurrentStatus, identifier:: %v", device.DeviceIdentifier)
+
+	statusargs := rpcs.ArgsGetStatus{
+		Id: uint64(device.ID),
+	}
+	statusreply := rpcs.ReplyGetStatus{}
+	err := server.RPCCallByName("controller", "Controller.GetStatus", statusargs, &statusreply)
+	if err != nil {
+		server.Log.Errorf("get devie status error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	status, err := config.StatusToMap(statusreply.Status)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+	result := DeviceStatusResponse{
+		Data: status,
+	}
+
+	r.JSON(http.StatusOK, result)
+	return
+}
+
+func GetDeviceLatestStatus() {
+
+}
+
+func SetDeviceStatus(device *models.Device, config *productconfig.ProductConfig,
+	urlparams martini.Params, req *http.Request, r render.Render) {
+	server.Log.Printf("ACTION GetDeviceCurrentStatus, identifier:: %v,request: %v", device.DeviceIdentifier, req.Body)
+
+	var args interface{}
+	decoder := json.NewDecoder(req.Body)
+	err := decoder.Decode(&args)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	m, ok := args.(map[string]interface{})
+	if !ok {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	status, err := config.MapToStatus(m)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	statusargs := rpcs.ArgsSetStatus{
+		DeviceId: uint64(device.ID),
+		Status:   status,
+	}
+	statusreply := rpcs.ReplySetStatus{}
+	err = server.RPCCallByName("controller", "Controller.SetStatus", statusargs, &statusreply)
+	if err != nil {
+		server.Log.Errorf("set devie status error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	r.JSON(http.StatusOK, Common{})
+	return
+
+}
+
+func SendCommandToDevice(device *models.Device, config *productconfig.ProductConfig,
+	urlparams martini.Params, req *http.Request, r render.Render) {
+	timeout := req.URL.Query().Get("timeout")
+
+	server.Log.Printf("ACTION SendCommandToDevice, identifier:: %v, request: %v, timeout: %v",
+		device.DeviceIdentifier, req.Body, timeout)
+
+	var args interface{}
+	decoder := json.NewDecoder(req.Body)
+	err := decoder.Decode(&args)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	m, ok := args.(map[string]interface{})
+	if !ok {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	command, err := config.MapToCommand(m)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	cmdargs := rpcs.ArgsSendCommand{
+		DeviceId:  uint64(device.ID),
+		SubDevice: uint16(command.Head.SubDeviceid),
+		No:        uint16(command.Head.No),
+		WaitTime:  uint32(defaultTimeOut),
+		Params:    command.Params,
+	}
+	cmdreply := rpcs.ReplySendCommand{}
+	err = server.RPCCallByName("controller", "Controller.SendCommand", cmdargs, &cmdreply)
+	if err != nil {
+		server.Log.Errorf("send devie command error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	r.JSON(http.StatusOK, Common{})
+	return
+
+}
+
+func AddRule(device *models.Device, req *http.Request, r render.Render) {
+	var ruleReq CreateRuleRequest
+	decoder := json.NewDecoder(req.Body)
+	err := decoder.Decode(&ruleReq)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongRequestFormat, err))
+		return
+	}
+
+	rule := &models.Rule{
+		DeviceID: int64(device.ID),
+		RuleType: ruleReq.Type,
+		Trigger:  ruleReq.Trigger,
+		Target:   ruleReq.Target,
+		Action:   ruleReq.Action,
+	}
+	reply := &rpcs.ReplyEmptyResult{}
+
+	err = server.RPCCallByName("registry", "Registry.CreateRule", rule, reply)
+	if err != nil {
+		server.Log.Errorf("create device rule error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	r.JSON(http.StatusOK, Common{})
+	return
+
+}

+ 40 - 0
services/apiprovider/actions_test.go

@@ -0,0 +1,40 @@
+package main
+
+import (
+	"encoding/json"
+	"sparrow/pkg/models"
+	"sparrow/pkg/utils"
+	"testing"
+
+	"github.com/go-martini/martini"
+	"github.com/martini-contrib/render"
+)
+
+func startServer() {
+	martini.Env = martini.Prod
+	handler := martini.Classic()
+	handler.Use(render.Renderer())
+	route(handler)
+
+	handler.Run()
+}
+func struct2string(tar interface{}) string {
+	bytes, err := json.Marshal(tar)
+	if err != nil {
+		return ""
+	}
+	return string(bytes)
+}
+func TestUserLogin(t *testing.T) {
+	go startServer()
+	req := models.LoginRequest{
+		UserName: "lijian",
+		Password: "lijian",
+	}
+
+	body, err := utils.SendHttpRequest("http://localhost:3000/api/v1/login", struct2string(req), "POST", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Fatal(string(body))
+}

+ 14 - 0
services/apiprovider/flags.go

@@ -0,0 +1,14 @@
+package main
+
+import (
+	"flag"
+)
+
+const (
+	flagRabbitHost    = "rabbithost"
+	defaultRabbitHost = "amqp://pandocloud:123@192.168.175.60:5672/"
+)
+
+var (
+	confRabbitHost = flag.String(flagRabbitHost, defaultRabbitHost, "rabbitmq host address, amqp://user:password@ip:port/")
+)

+ 42 - 0
services/apiprovider/main.go

@@ -0,0 +1,42 @@
+package main
+
+import (
+	"sparrow/pkg/server"
+
+	"github.com/go-martini/martini"
+	"github.com/martini-contrib/render"
+)
+
+func main() {
+	// init server
+	err := server.Init("apiprovidor")
+	if err != nil {
+		server.Log.Fatal(err)
+		return
+	}
+
+	// martini setup
+	martini.Env = martini.Prod
+	handler := martini.Classic()
+	handler.Use(render.Renderer())
+	route(handler)
+
+	// register a http handler
+	err = server.RegisterHTTPHandler(handler)
+	if err != nil {
+		server.Log.Errorf("RegisterHTTPHandler Error: %s", err)
+		return
+	}
+
+	// run notifier
+	// err = RunNotifier()
+	// if err != nil {
+	// 	server.Log.Fatalf("Run Notifier Error: %s", err)
+	// }
+
+	// go
+	err = server.Run()
+	if err != nil {
+		server.Log.Fatal(err)
+	}
+}

+ 147 - 0
services/apiprovider/middleware.go

@@ -0,0 +1,147 @@
+package main
+
+import (
+	"errors"
+	"sparrow/pkg/models"
+	"sparrow/pkg/productconfig"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"github.com/go-martini/martini"
+	"github.com/martini-contrib/render"
+	"net/http"
+	"strconv"
+	"strings"
+)
+
+func checkAppDomain(domain string, identifier string) error {
+	domainPieces := strings.Split(domain, "/")
+	identifierPieces := strings.Split(identifier, "-")
+	if len(domainPieces) == 0 {
+		return errors.New("wrong app domain format.")
+	}
+	if len(identifierPieces) != 3 {
+		return errors.New("wrong identifier format.")
+	}
+	devvendorid, err := strconv.ParseUint(identifierPieces[0], 16, 64)
+	if err != nil {
+		return errors.New("wrong vendor format.")
+	}
+	devproductid, err := strconv.ParseUint(identifierPieces[1], 16, 64)
+	if err != nil {
+		return errors.New("wrong product format.")
+	}
+
+	if len(domainPieces) == 1 {
+		if domainPieces[0] != "*" {
+			return errors.New("wrong app domain " + domainPieces[0])
+		}
+		return nil
+	}
+
+	if len(domainPieces) == 2 {
+		id, err := strconv.ParseUint(domainPieces[1], 10, 64)
+		if err != nil {
+			return errors.New("wrong app domain format..")
+		}
+		if domainPieces[0] == "vendor" {
+			if id != devvendorid {
+				return errors.New("app has no access right on device.")
+			}
+		} else if domainPieces[0] == "product" {
+			if id != devproductid {
+				return errors.New("app has no access right on device.")
+			}
+		} else {
+			return errors.New("wrong app domain" + domain)
+		}
+	}
+
+	if len(domainPieces) > 2 {
+		return errors.New("wrong app domain" + domainPieces[0])
+	}
+
+	return nil
+}
+
+// check if app has access right on device of given identifier( in url params )
+func ApplicationAuthOnDeviceIdentifer(context martini.Context, params martini.Params, req *http.Request, r render.Render) {
+	identifier := params["identifier"]
+	key := req.Header.Get("App-Key")
+
+	if identifier == "" || key == "" {
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, errors.New("missing device identifier or app key.")))
+		return
+	}
+
+	app := &models.Application{}
+	err := server.RPCCallByName("registry", "Registry.ValidateApplication", key, app)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrAccessDenied, err))
+		return
+	}
+
+	err = checkAppDomain(app.AppDomain, identifier)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrAccessDenied, err))
+		return
+	}
+
+}
+
+// check if device is online.
+func CheckDeviceOnline(context martini.Context, params martini.Params, req *http.Request, r render.Render) {
+	identifier := params["identifier"]
+
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.FindDeviceByIdentifier", identifier, device)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
+		return
+	}
+
+	onlineargs := rpcs.ArgsGetDeviceOnlineStatus{
+		Id: uint64(device.ID),
+	}
+	onlinereply := rpcs.ReplyGetDeviceOnlineStatus{}
+	err = server.RPCCallByName("devicemanager", "DeviceManager.GetDeviceOnlineStatus", onlineargs, &onlinereply)
+	if err != nil {
+		server.Log.Errorf("get devie online status error: %v", err)
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotOnline, err))
+		return
+	}
+
+	context.Map(device)
+}
+
+// get device identifier
+func CheckDeviceIdentifier(context martini.Context, params martini.Params, req *http.Request, r render.Render) {
+	identifier := params["identifier"]
+
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.FindDeviceByIdentifier", identifier, device)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
+		return
+	}
+
+	context.Map(device)
+}
+
+// check if proudct is ok and map a product config to context, must by called after CheckDevice
+func CheckProductConfig(context martini.Context, device *models.Device,
+	params martini.Params, req *http.Request, r render.Render) {
+	product := &models.Product{}
+	err := server.RPCCallByName("registry", "Registry.FindProduct", device.ProductID, product)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrProductNotFound, err))
+		return
+	}
+
+	c, err := productconfig.New(product.ProductConfig)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrWrongProductConfig, err))
+		return
+	}
+
+	context.Map(c)
+}

+ 30 - 0
services/apiprovider/middleware_test.go

@@ -0,0 +1,30 @@
+package main
+
+import (
+	"testing"
+)
+
+func checkPair(t *testing.T, domain string, identifier string, shoudpass bool) {
+	err := checkAppDomain(domain, identifier)
+	if shoudpass {
+		if err != nil {
+			t.Errorf("check domain should pass, but failed: domain: %v, identifier: %v, err: %v", domain, identifier, err)
+		}
+	} else {
+		if err == nil {
+			t.Errorf("check domain should fail, but passed: domain: %v, identifier: %v, err: %v", domain, identifier, err)
+		}
+	}
+}
+
+func TestCheckAppDomain(t *testing.T) {
+	//checkPair(t, "", "", false)
+	checkPair(t, "*", "1-2-3333", true)
+	checkPair(t, "vendor/1", "1-2-3333", true)
+	checkPair(t, "product/2", "1-2-3333", true)
+	checkPair(t, "product/2", "1-a-3333", false)
+	checkPair(t, "product/10", "1-a-3333", true)
+	checkPair(t, "vendor/11", "b-a-3333", true)
+	checkPair(t, "fff/product/2", "1-a-3333", false)
+	checkPair(t, "product/10", "1-a-3333-11111", false)
+}

+ 173 - 0
services/apiprovider/notifier.go

@@ -0,0 +1,173 @@
+package main
+
+import (
+	"encoding/json"
+	"sparrow/pkg/models"
+	"sparrow/pkg/productconfig"
+	"sparrow/pkg/protocol"
+	"sparrow/pkg/queue"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"sparrow/pkg/utils"
+	"time"
+)
+
+const (
+	topicEvents = "events"
+	topicStatus = "status"
+)
+
+// report structure
+type ReportPack struct {
+	Tag        string                   `json:"tag"`
+	Identifier string                   `json:"identifier"`
+	TimeStamp  int64                    `json:"timestamp"`
+	Data       map[string][]interface{} `json:"data"`
+}
+
+var notifier *Notifier
+
+type Notifier struct {
+	eventsQueue *queue.Queue
+	statusQueue *queue.Queue
+	apps        []*models.Application
+}
+
+func NewNotifier(rabbithost string) (*Notifier, error) {
+	eq, err := queue.New(rabbithost, topicEvents)
+	if err != nil {
+		return nil, err
+	}
+
+	sq, err := queue.New(rabbithost, topicStatus)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Notifier{
+		eventsQueue: eq,
+		statusQueue: sq,
+		apps:        []*models.Application{},
+	}, nil
+}
+
+// TODO
+func (n *Notifier) reportStatus(event rpcs.ArgsOnStatus) error {
+	return nil
+}
+
+// TODO
+func (n *Notifier) processStatus() error {
+	return nil
+}
+
+func (n *Notifier) updateApplications() error {
+	for {
+
+		err := server.RPCCallByName("registry", "Registry.GetApplications", 0, &n.apps)
+		if err != nil {
+			server.Log.Errorf("get applications error : %v", err)
+		}
+
+		time.Sleep(time.Minute)
+	}
+}
+
+func (n *Notifier) reportEvent(event rpcs.ArgsOnEvent) error {
+	server.Log.Debugf("reporting event %v", event)
+
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.FindDeviceById", int64(event.DeviceId), device)
+	if err != nil {
+		server.Log.Errorf("find device error : %v", err)
+		return err
+	}
+
+	product := &models.Product{}
+	err = server.RPCCallByName("registry", "Registry.FindProduct", device.ProductID, product)
+	if err != nil {
+		server.Log.Errorf("find product error : %v", err)
+		return err
+	}
+
+	c, err := productconfig.New(product.ProductConfig)
+	if err != nil {
+		server.Log.Errorf("product config error : %v", err)
+		return err
+	}
+
+	ev := &protocol.Event{}
+	ev.Head.No = event.No
+	ev.Head.SubDeviceid = event.SubDevice
+	ev.Params = event.Params
+
+	m, err := c.EventToMap(ev)
+	if err != nil {
+		server.Log.Errorf("gen event json error : %v", err)
+		return err
+	}
+
+	res := ReportPack{
+		Tag:        "event",
+		Identifier: device.DeviceIdentifier,
+		Data:       m,
+	}
+
+	jsonRes, err := json.Marshal(res)
+	if err != nil {
+		server.Log.Errorf("json marshal error : %v", err)
+		return err
+	}
+
+	reqHead := map[string]string{}
+	reqHead["Content-Type"] = "application/json"
+
+	for _, app := range n.apps {
+		if nil == checkAppDomain(app.AppDomain, device.DeviceIdentifier) {
+			reqHead["App-Token"] = app.AppToken
+			_, err := utils.SendHttpRequest(app.ReportUrl, string(jsonRes), "POST", reqHead)
+			if err != nil {
+				server.Log.Errorf("http post json error : %v", err)
+			}
+			server.Log.Debugf("http post json succ : %v", string(jsonRes))
+		}
+	}
+
+	return nil
+}
+
+func (n *Notifier) processEvents() error {
+	for {
+		event := rpcs.ArgsOnEvent{}
+		err := n.eventsQueue.Receive(&event)
+		if err != nil {
+			server.Log.Errorf("error when receiving from queue : %v", err)
+			return err
+		}
+		go n.reportEvent(event)
+	}
+
+	return nil
+}
+
+func (n *Notifier) Run() error {
+	go n.updateApplications()
+	go n.processEvents()
+	go n.processStatus()
+
+	return nil
+}
+
+func RunNotifier() error {
+	if notifier == nil {
+		notifier, err := NewNotifier(*confRabbitHost)
+		if err != nil {
+			server.Log.Error(err)
+		}
+		err = notifier.Run()
+		if err != nil {
+			server.Log.Error(err)
+		}
+	}
+	return nil
+}

+ 8 - 0
services/apiprovider/request.go

@@ -0,0 +1,8 @@
+package main
+
+type CreateRuleRequest struct {
+	Type    string `json:"type"`
+	Trigger string `json:"trigger"`
+	Target  string `json:"target"`
+	Action  string `json:"action"`
+}

+ 26 - 0
services/apiprovider/response.go

@@ -0,0 +1,26 @@
+package main
+
+// Common response fields
+type Common struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+}
+
+type DeviceInfoData struct {
+	Identifier  string `json:"identifier"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Version     string `json:"version"`
+}
+
+type DeviceInfoResponse struct {
+	Common
+	Data DeviceInfoData `json:"data"`
+}
+
+type DeviceStatusData map[string][]interface{}
+
+type DeviceStatusResponse struct {
+	Common
+	Data DeviceStatusData `json:"data"`
+}

+ 46 - 0
services/apiprovider/router.go

@@ -0,0 +1,46 @@
+package main
+
+import (
+	"sparrow/pkg/models"
+
+	"github.com/go-martini/martini"
+	"github.com/martini-contrib/binding"
+)
+
+// martini router
+func route(m *martini.ClassicMartini) {
+	// find a device by key
+	m.Get("/application/v1/device/info", GetDeviceInfoByKey)
+
+	// find a device by identifier
+	m.Get("/application/v1/devices/:identifier/info", ApplicationAuthOnDeviceIdentifer, GetDeviceInfoByIdentifier)
+
+	// get devie current status
+	m.Get("/application/v1/devices/:identifier/status/current",
+		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+		GetDeviceCurrentStatus)
+
+	// get devie latest status
+	m.Get("/application/v1/devices/:identifier/status/latest",
+		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+		GetDeviceLatestStatus)
+
+	// set device status
+	m.Put("/application/v1/devices/:identifier/status",
+		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+		SetDeviceStatus)
+
+	// send a command to device
+	m.Post("/application/v1/devices/:identifier/commands",
+		ApplicationAuthOnDeviceIdentifer, CheckDeviceOnline, CheckProductConfig,
+		SendCommandToDevice)
+
+	// and a rule to device
+	m.Post("/application/v1/devices/:identifier/rules",
+		ApplicationAuthOnDeviceIdentifer, CheckDeviceIdentifier,
+		AddRule)
+
+	m.Post("/api/v1/login", binding.Bind(models.LoginRequest{}), UserLogin)
+	m.Post("/api/v1/reg", binding.Bind(models.Request))
+
+}

+ 15 - 0
services/apiprovider/user.go

@@ -0,0 +1,15 @@
+package main
+
+import (
+	"net/http"
+	"sparrow/pkg/models"
+
+	"github.com/martini-contrib/render"
+)
+
+// UserLogin 用户登陆
+func UserLogin(loginRequest models.LoginRequest, r render.Render) {
+	err :=
+		r.JSON(http.StatusOK, loginRequest)
+	return
+}

+ 140 - 0
services/controller/controller.go

@@ -0,0 +1,140 @@
+package main
+
+import (
+	"sparrow/pkg/mongo"
+	"sparrow/pkg/queue"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/rule"
+	"sparrow/pkg/server"
+)
+
+const (
+	mongoSetName = "pandocloud"
+	topicEvents  = "events"
+	topicStatus  = "status"
+)
+
+type Controller struct {
+	commandRecorder *mongo.Recorder
+	eventRecorder   *mongo.Recorder
+	dataRecorder    *mongo.Recorder
+	eventsQueue     *queue.Queue
+	statusQueue     *queue.Queue
+	timer           *rule.Timer
+	ift             *rule.Ifttt
+}
+
+func NewController(mongohost string, rabbithost string) (*Controller, error) {
+	cmdr, err := mongo.NewRecorder(mongohost, mongoSetName, "commands")
+	if err != nil {
+		return nil, err
+	}
+
+	ever, err := mongo.NewRecorder(mongohost, mongoSetName, "events")
+	if err != nil {
+		return nil, err
+	}
+
+	datar, err := mongo.NewRecorder(mongohost, mongoSetName, "datas")
+	if err != nil {
+		return nil, err
+	}
+
+	eq, err := queue.New(rabbithost, topicEvents)
+	if err != nil {
+		return nil, err
+	}
+
+	sq, err := queue.New(rabbithost, topicStatus)
+	if err != nil {
+		return nil, err
+	}
+
+	// timer
+	t := rule.NewTimer()
+	t.Run()
+
+	// ifttt
+	ttt := rule.NewIfttt()
+
+	return &Controller{
+		commandRecorder: cmdr,
+		eventRecorder:   ever,
+		dataRecorder:    datar,
+		eventsQueue:     eq,
+		statusQueue:     sq,
+		timer:           t,
+		ift:             ttt,
+	}, nil
+}
+
+func (c *Controller) SetStatus(args rpcs.ArgsSetStatus, reply *rpcs.ReplySetStatus) error {
+	rpchost, err := getAccessRPCHost(args.DeviceId)
+	if err != nil {
+		return err
+	}
+
+	return server.RPCCallByHost(rpchost, "Access.SetStatus", args, reply)
+}
+
+func (c *Controller) GetStatus(args rpcs.ArgsGetStatus, reply *rpcs.ReplyGetStatus) error {
+	rpchost, err := getAccessRPCHost(args.Id)
+	if err != nil {
+		return err
+	}
+
+	return server.RPCCallByHost(rpchost, "Access.GetStatus", args, reply)
+}
+
+func (c *Controller) OnStatus(args rpcs.ArgsOnStatus, reply *rpcs.ReplyOnStatus) error {
+	err := c.dataRecorder.Insert(args)
+	if err != nil {
+		return err
+	}
+	err = c.statusQueue.Send(args)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *Controller) OnEvent(args rpcs.ArgsOnEvent, reply *rpcs.ReplyOnEvent) error {
+	go func() {
+		err := c.ift.Check(args.DeviceId, args.No)
+		if err != nil {
+			server.Log.Warnf("perform ifttt rules error : %v", err)
+		}
+	}()
+
+	err := c.eventRecorder.Insert(args)
+	if err != nil {
+		return err
+	}
+	err = c.eventsQueue.Send(args)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *Controller) SendCommand(args rpcs.ArgsSendCommand, reply *rpcs.ReplySendCommand) error {
+	rpchost, err := getAccessRPCHost(args.DeviceId)
+	if err != nil {
+		return err
+	}
+
+	return server.RPCCallByHost(rpchost, "Access.SendCommand", args, reply)
+}
+
+func getAccessRPCHost(deviceid uint64) (string, error) {
+	args := rpcs.ArgsGetDeviceOnlineStatus{
+		Id: deviceid,
+	}
+	reply := &rpcs.ReplyGetDeviceOnlineStatus{}
+	err := server.RPCCallByName("devicemanager", "DeviceManager.GetDeviceOnlineStatus", args, reply)
+	if err != nil {
+		return "", err
+	}
+
+	return reply.AccessRPCHost, nil
+}

+ 18 - 0
services/controller/flags.go

@@ -0,0 +1,18 @@
+package main
+
+import (
+	"flag"
+)
+
+const (
+	flagMongoHost    = "mongohost"
+	defaultMongoHost = "192.168.175.110:27017"
+
+	flagRabbitHost    = "rabbithost"
+	defaultRabbitHost = "amqp://pandocloud:123@192.168.175.110:5672/"
+)
+
+var (
+	confMongoHost  = flag.String(flagMongoHost, defaultMongoHost, "mongo host address, ip:port")
+	confRabbitHost = flag.String(flagRabbitHost, defaultRabbitHost, "rabbitmq host address, amqp://user:password@ip:port/")
+)

+ 33 - 0
services/controller/main.go

@@ -0,0 +1,33 @@
+package main
+
+import (
+	"sparrow/pkg/server"
+)
+
+func main() {
+	// init server
+	err := server.Init("controller")
+	if err != nil {
+		server.Log.Fatal(err)
+		return
+	}
+
+	// register a rpc service
+	controller, err := NewController(*confMongoHost, *confRabbitHost)
+	if err != nil {
+		server.Log.Errorf("NewController Error: %s", err)
+		return
+	}
+
+	err = server.RegisterRPCHandler(controller)
+	if err != nil {
+		server.Log.Errorf("Register RPC service Error: %s", err)
+		return
+	}
+
+	// start to run
+	err = server.Run()
+	if err != nil {
+		server.Log.Fatal(err)
+	}
+}

+ 15 - 0
services/devicemanager/flags.go

@@ -0,0 +1,15 @@
+package main
+
+import (
+	"flag"
+)
+
+const (
+	flagRedisHost = "redishost"
+
+	defaultRedisHost = "localhost:6379"
+)
+
+var (
+	confRedisHost = flag.String(flagRedisHost, defaultRedisHost, "redis host address, ip:port")
+)

+ 28 - 0
services/devicemanager/main.go

@@ -0,0 +1,28 @@
+package main
+
+import (
+	"sparrow/pkg/server"
+)
+
+func main() {
+	// init server
+	err := server.Init("devicemanager")
+	if err != nil {
+		server.Log.Fatal(err)
+		return
+	}
+
+	// register a rpc service
+	dm := NewDeviceManager(*confRedisHost)
+	err = server.RegisterRPCHandler(dm)
+	if err != nil {
+		server.Log.Errorf("Register RPC service Error: %s", err)
+		return
+	}
+
+	// start to run
+	err = server.Run()
+	if err != nil {
+		server.Log.Fatal(err)
+	}
+}

+ 66 - 0
services/devicemanager/manager.go

@@ -0,0 +1,66 @@
+package main
+
+import (
+	"sparrow/pkg/online"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/token"
+)
+
+type DeviceManager struct {
+	onlineManager *online.Manager
+	tokenHelper   *token.Helper
+}
+
+func NewDeviceManager(redishost string) *DeviceManager {
+	mgr := online.NewManager(redishost)
+
+	helper := token.NewHelper(redishost)
+
+	return &DeviceManager{
+		onlineManager: mgr,
+		tokenHelper:   helper,
+	}
+}
+
+func (dm *DeviceManager) GenerateDeviceAccessToken(args rpcs.ArgsGenerateDeviceAccessToken, reply *rpcs.ReplyGenerateDeviceAccessToken) error {
+	token, err := dm.tokenHelper.GenerateToken(args.Id)
+	if err != nil {
+		return err
+	}
+
+	reply.AccessToken = token
+	return nil
+}
+
+func (dm *DeviceManager) ValidateDeviceAccessToken(args rpcs.ArgsValidateDeviceAccessToken, reply *rpcs.ReplyValidateDeviceAccessToken) error {
+	dm.onlineManager.SetHeartbeat(args.Id)
+	return dm.tokenHelper.ValidateToken(args.Id, args.AccessToken)
+}
+
+func (dm *DeviceManager) GetOnline(args rpcs.ArgsGetOnline, reply *rpcs.ReplyGetOnline) error {
+	return dm.onlineManager.GetOnline(args.Id, online.Status{
+		ClientIP:          args.ClientIP,
+		AccessRPCHost:     args.AccessRPCHost,
+		HeartbeatInterval: args.HeartbeatInterval,
+	})
+}
+
+func (dm *DeviceManager) HeartBeat(args rpcs.ArgsHeartBeat, reply *rpcs.ReplyHeartBeat) error {
+	return dm.onlineManager.SetHeartbeat(args.Id)
+}
+
+func (dm *DeviceManager) GetOffline(args rpcs.ArgsGetOffline, reply *rpcs.ReplyGetOffline) error {
+	return dm.onlineManager.GetOffline(args.Id)
+}
+
+func (dm *DeviceManager) GetDeviceOnlineStatus(args rpcs.ArgsGetDeviceOnlineStatus, reply *rpcs.ReplyGetDeviceOnlineStatus) error {
+	status, err := dm.onlineManager.GetStatus(args.Id)
+	if err != nil {
+		return err
+	}
+
+	reply.ClientIP = status.ClientIP
+	reply.AccessRPCHost = status.AccessRPCHost
+	reply.HeartbeatInterval = status.HeartbeatInterval
+	return nil
+}

+ 74 - 0
services/devicemanager/manager_test.go

@@ -0,0 +1,74 @@
+package main
+
+import (
+	"sparrow/pkg/rpcs"
+	"testing"
+)
+
+func TestDeviceManager(t *testing.T) {
+	mgr := NewDeviceManager("localhost:6379")
+
+	deviceid := uint64(123456)
+
+	args1 := rpcs.ArgsGenerateDeviceAccessToken{
+		Id: deviceid,
+	}
+	reply1 := rpcs.ReplyGenerateDeviceAccessToken{}
+	err := mgr.GenerateDeviceAccessToken(args1, &reply1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	token := reply1.AccessToken
+
+	args2 := rpcs.ArgsValidateDeviceAccessToken{
+		Id:          deviceid,
+		AccessToken: token,
+	}
+	reply2 := rpcs.ReplyValidateDeviceAccessToken{}
+	err = mgr.ValidateDeviceAccessToken(args2, &reply2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	args3 := rpcs.ArgsGetOnline{
+		Id:                deviceid,
+		ClientIP:          "",
+		AccessRPCHost:     "",
+		HeartbeatInterval: 10,
+	}
+	reply3 := rpcs.ReplyGetOnline{}
+	err = mgr.GetOnline(args3, &reply3)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	args4 := rpcs.ArgsHeartBeat{
+		Id: deviceid,
+	}
+	reply4 := rpcs.ReplyHeartBeat{}
+	err = mgr.HeartBeat(args4, &reply4)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	args5 := rpcs.ArgsGetDeviceStatus{
+		Id: deviceid,
+	}
+	reply5 := rpcs.ReplyGetDeviceStatus{}
+	err = mgr.GetDeviceStatus(args5, &reply5)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(reply5)
+
+	args6 := rpcs.ArgsGetOffline{
+		Id: deviceid,
+	}
+	reply6 := rpcs.ReplyGetOffline{}
+	err = mgr.GetOffline(args6, &reply6)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}

+ 120 - 0
services/httpaccess/actions.go

@@ -0,0 +1,120 @@
+package main
+
+import (
+	"encoding/hex"
+	"errors"
+	"sparrow/pkg/models"
+	"sparrow/pkg/rpcs"
+	"sparrow/pkg/server"
+	"sparrow/pkg/token"
+	"github.com/martini-contrib/render"
+	"math/rand"
+	"net/http"
+)
+
+const (
+	ErrOK                  = 0
+	ErrSystemFault         = 10001
+	ErrDeviceNotFound      = 10002
+	ErrWrongSecret         = 10003
+	ErrProtocolNotSuported = 10004
+)
+
+func renderError(code int, err error) Common {
+	result := Common{}
+	result.Code = code
+	result.Message = err.Error()
+	server.Log.Error(err.Error())
+	return result
+}
+
+// device register args
+type DeviceRegisterArgs struct {
+	ProductKey string `json:"product_key"  binding:"required"`
+	DeviceCode string `json:"device_code"  binding:"required"`
+	Version    string `json:"version"  binding:"required"`
+}
+
+// device authentication args
+type DeviceAuthArgs struct {
+	DeviceId     int64  `json:"device_id" binding:"required"`
+	DeviceSecret string `json:"device_secret" binding:"required"`
+	Protocol     string `json:"protocol" binding:"required"`
+}
+
+func RegisterDevice(args DeviceRegisterArgs, r render.Render) {
+	server.Log.Printf("ACTION RegisterDevice, args:: %v ", args)
+	rpcargs := &rpcs.ArgsDeviceRegister{
+		ProductKey:    args.ProductKey,
+		DeviceCode:    args.DeviceCode,
+		DeviceVersion: args.Version,
+	}
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.RegisterDevice", rpcargs, device)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+	server.Log.Infof("register device success: %v", device)
+
+	result := DeviceRegisterResponse{}
+	result.Data = DeviceRegisterData{
+		DeviceId:         device.ID,
+		DeviceSecret:     device.DeviceSecret,
+		DeviceKey:        device.DeviceKey,
+		DeviceIdentifier: device.DeviceIdentifier,
+	}
+	r.JSON(http.StatusOK, result)
+	return
+}
+
+func AuthDevice(args DeviceAuthArgs, r render.Render) {
+	server.Log.Printf("ACTION AuthDevice, args:: %v", args)
+	device := &models.Device{}
+	err := server.RPCCallByName("registry", "Registry.FindDeviceById", int64(args.DeviceId), device)
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
+		return
+	}
+
+	if device.DeviceSecret != args.DeviceSecret {
+		// device secret is wrong.
+		r.JSON(http.StatusOK, renderError(ErrWrongSecret, errors.New("wrong device secret.")))
+		return
+	}
+
+	hepler := token.NewHelper(*confRedisHost)
+	token, err := hepler.GenerateToken(uint64(device.ID))
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
+		return
+	}
+
+	var hosts []string
+	switch args.Protocol {
+	case "http":
+		hosts, err = server.GetServerHosts(args.Protocol+"access", "httphost")
+	case "mqtt":
+		hosts, err = server.GetServerHosts(args.Protocol+"access", "tcphost")
+	default:
+		err = errors.New("unsuported protocol: " + args.Protocol)
+	}
+	if err != nil {
+		r.JSON(http.StatusOK, renderError(ErrProtocolNotSuported, err))
+		return
+	}
+
+	// just get a random host
+	host := hosts[rand.Intn(len(hosts))]
+
+	result := DeviceAuthResponse{}
+	result.Data = DeviceAuthData{
+		AccessToken: hex.EncodeToString(token),
+		AccessAddr:  host,
+	}
+
+	server.Log.Infof("auth device success: %v, token: %x, access: %v", device, token, host)
+
+	r.JSON(http.StatusOK, result)
+	return
+}

+ 15 - 0
services/httpaccess/flags.go

@@ -0,0 +1,15 @@
+package main
+
+import (
+	"flag"
+)
+
+const (
+	flagRedisHost = "redishost"
+
+	defaultRedisHost = "localhost:6379"
+)
+
+var (
+	confRedisHost = flag.String(flagRedisHost, defaultRedisHost, "redis host address, ip:port")
+)

部分文件因文件數量過多而無法顯示