mirror of
https://github.com/zhaojh329/rttys.git
synced 2026-02-27 09:53:21 +08:00
Optimize code
Transfer tty data in Binary; Transfer control data in Json Text; Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>
This commit is contained in:
@@ -20,192 +20,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"strconv"
|
||||
"math/rand"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/zhaojh329/rttys/rtty"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const RTTY_MESSAGE_VERSION = 2
|
||||
const RTTY_MAX_SESSION_ID = 1000000
|
||||
|
||||
type Broker struct {
|
||||
devices map[string]*Client
|
||||
sessions map[string]*Session
|
||||
|
||||
// Join requests from the clients.
|
||||
join chan *Client
|
||||
|
||||
// Leave requests from clients.
|
||||
leave chan *Client
|
||||
|
||||
// Buffered channel of inbound messages from device.
|
||||
inDevMessage chan *wsMessage
|
||||
|
||||
// Buffered channel of inbound messages from user.
|
||||
inUsrMessage chan *wsMessage
|
||||
devices map[string]*Client
|
||||
sessions map[uint32]*Session
|
||||
register chan *Client /* Register requests from the clients. */
|
||||
unregister chan *Client /* Unregister requests from clients. */
|
||||
inMessage chan *wsInMessage /* Buffered channel of inbound messages. */
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
dev *Client
|
||||
user *Client
|
||||
dev *Client
|
||||
web *Client
|
||||
devsid uint8
|
||||
}
|
||||
|
||||
func newBroker() *Broker {
|
||||
return &Broker{
|
||||
join: make(chan *Client, 100),
|
||||
leave: make(chan *Client, 100),
|
||||
devices: make(map[string]*Client),
|
||||
sessions: make(map[string]*Session),
|
||||
inDevMessage: make(chan *wsMessage, 100),
|
||||
inUsrMessage: make(chan *wsMessage, 100),
|
||||
}
|
||||
return &Broker{
|
||||
register: make(chan *Client, 100),
|
||||
unregister: make(chan *Client, 100),
|
||||
devices: make(map[string]*Client),
|
||||
sessions: make(map[uint32]*Session),
|
||||
inMessage: make(chan *wsInMessage, 1000),
|
||||
}
|
||||
}
|
||||
|
||||
func RttyMessageInit(msg *rtty.RttyMessage) []byte {
|
||||
data, _ := proto.Marshal(msg)
|
||||
return data
|
||||
// return 0 for failed
|
||||
func getFreeSid(br *Broker) uint32 {
|
||||
for sid := uint32(1); sid <= RTTY_MAX_SESSION_ID; sid++ {
|
||||
if _, ok := br.sessions[sid]; !ok {
|
||||
return sid
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func generateSID(devid string) string {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write([]byte(devid + strconv.FormatFloat(rand.Float64(), 'e', 6, 32)))
|
||||
cipherStr := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(cipherStr)
|
||||
}
|
||||
func (br *Broker) newSession(web *Client) bool {
|
||||
devid := web.devid
|
||||
sid := getFreeSid(br)
|
||||
|
||||
func (br *Broker) newSession(user *Client) bool {
|
||||
devid := user.devid
|
||||
sid := generateSID(devid)
|
||||
if sid < 1 {
|
||||
rlog.Println("Not found available sid")
|
||||
return false
|
||||
}
|
||||
|
||||
if dev, ok := br.devices[devid]; ok {
|
||||
br.sessions[sid] = &Session{dev, user}
|
||||
user.sid = sid
|
||||
if dev, ok := br.devices[devid]; ok {
|
||||
devsid := dev.getFreeSid()
|
||||
if devsid < 1 {
|
||||
rlog.Println("Not found available devsid")
|
||||
return false
|
||||
}
|
||||
|
||||
// Write to user
|
||||
msg := RttyMessageInit(&rtty.RttyMessage{
|
||||
Version: RTTY_MESSAGE_VERSION,
|
||||
Type: rtty.RttyMessage_LOGINACK,
|
||||
Sid: sid,
|
||||
Code: rtty.RttyMessage_LoginCode_value["OK"],
|
||||
})
|
||||
user.wsWrite(websocket.BinaryMessage, msg)
|
||||
br.sessions[sid] = &Session{dev, web, devsid}
|
||||
dev.sessions[devsid] = sid
|
||||
web.sid = sid
|
||||
|
||||
|
||||
// Write to device
|
||||
msg = RttyMessageInit(&rtty.RttyMessage{
|
||||
Version: RTTY_MESSAGE_VERSION,
|
||||
Type: rtty.RttyMessage_LOGIN,
|
||||
Sid: sid,
|
||||
})
|
||||
dev.wsWrite(websocket.BinaryMessage, msg)
|
||||
|
||||
rlog.Println("New session:", sid)
|
||||
return true
|
||||
} else {
|
||||
// Write to user
|
||||
msg := RttyMessageInit(&rtty.RttyMessage{
|
||||
Version: RTTY_MESSAGE_VERSION,
|
||||
Type: rtty.RttyMessage_LOGINACK,
|
||||
Sid: sid,
|
||||
Code: rtty.RttyMessage_LoginCode_value["OFFLINE"],
|
||||
})
|
||||
user.wsWrite(websocket.BinaryMessage, msg)
|
||||
msg := fmt.Sprintf(`{"type":"login","sid":%d}`, devsid)
|
||||
|
||||
rlog.Println("Device", devid, "offline")
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Notify the device to create a pty and associate it with a session id
|
||||
dev.wsWrite(websocket.TextMessage, []byte(msg))
|
||||
|
||||
func delSession(sessions map[string]*Session, sid string) {
|
||||
if session, ok := sessions[sid]; ok {
|
||||
delete(sessions, sid)
|
||||
session.user.wsClose()
|
||||
rlog.Println("Delete session: ", sid)
|
||||
|
||||
if session.dev != nil {
|
||||
msg := RttyMessageInit(&rtty.RttyMessage{
|
||||
Version: RTTY_MESSAGE_VERSION,
|
||||
Type: rtty.RttyMessage_LOGOUT,
|
||||
Sid: sid,
|
||||
})
|
||||
session.dev.wsWrite(websocket.BinaryMessage, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dispatchMsg(data []byte, isDev bool, br *Broker) {
|
||||
msg := &rtty.RttyMessage{};
|
||||
proto.Unmarshal(data, msg);
|
||||
|
||||
if msg.Type == rtty.RttyMessage_COMMAND {
|
||||
cmdMutex.Lock()
|
||||
if cmd, ok := command[msg.Id]; ok {
|
||||
cmd <- msg
|
||||
}
|
||||
cmdMutex.Unlock()
|
||||
return;
|
||||
}
|
||||
|
||||
if session, ok := br.sessions[msg.Sid]; ok {
|
||||
if msg.Type == rtty.RttyMessage_LOGOUT {
|
||||
session.dev = nil
|
||||
delSession(br.sessions, msg.Sid)
|
||||
return
|
||||
}
|
||||
|
||||
if isDev {
|
||||
session.user.wsWrite(websocket.BinaryMessage, data)
|
||||
} else {
|
||||
session.dev.wsWrite(websocket.BinaryMessage, data)
|
||||
}
|
||||
}
|
||||
rlog.Println("New session:", sid)
|
||||
return true
|
||||
} else {
|
||||
// Notify the user that the device is offline
|
||||
msg := `{"type":"login","err":1,"msg":"offline"}`
|
||||
web.wsWrite(websocket.TextMessage, []byte(msg))
|
||||
rlog.Println("Device", devid, "offline")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (br *Broker) run() {
|
||||
for {
|
||||
select {
|
||||
case client := <- br.join:
|
||||
if client.isDev {
|
||||
if _, ok := br.devices[client.devid]; ok {
|
||||
rlog.Println("ID conflicting:", client.devid)
|
||||
client.wsClose();
|
||||
} else {
|
||||
client.isJoined = true
|
||||
br.devices[client.devid] = client
|
||||
rlog.Printf("New device:id('%s'), description('%s')", client.devid, client.description)
|
||||
}
|
||||
} else {
|
||||
// From user browse
|
||||
if !br.newSession(client) {
|
||||
time.AfterFunc(500 * time.Millisecond, client.wsClose)
|
||||
}
|
||||
}
|
||||
case client := <- br.leave:
|
||||
if client.isDev {
|
||||
client.wsClose()
|
||||
for {
|
||||
select {
|
||||
case c := <-br.register:
|
||||
if c.isDev {
|
||||
if _, ok := br.devices[c.devid]; ok {
|
||||
rlog.Println("ID conflicting:", c.devid)
|
||||
c.wsClose()
|
||||
} else {
|
||||
br.devices[c.devid] = c
|
||||
rlog.Printf("New device:id('%s'), description('%s')", c.devid, c.desc)
|
||||
}
|
||||
} else {
|
||||
// From user
|
||||
if !br.newSession(c) {
|
||||
time.AfterFunc(500*time.Millisecond, c.wsClose)
|
||||
}
|
||||
}
|
||||
case c := <-br.unregister:
|
||||
if c.isDev {
|
||||
c.wsClose()
|
||||
|
||||
if dev, ok := br.devices[client.devid]; ok {
|
||||
rlog.Printf("Dead device:id('%s'), description('%s')", dev.devid, dev.description)
|
||||
delete(br.devices, dev.devid)
|
||||
}
|
||||
if dev, ok := br.devices[c.devid]; ok {
|
||||
rlog.Printf("Dead device:id('%s'), description('%s')", dev.devid, dev.desc)
|
||||
delete(br.devices, dev.devid)
|
||||
}
|
||||
|
||||
for sid, session := range br.sessions {
|
||||
if session.dev.devid == client.devid {
|
||||
session.dev = nil
|
||||
delSession(br.sessions, sid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delSession(br.sessions, client.sid)
|
||||
}
|
||||
case msg := <- br.inDevMessage:
|
||||
dispatchMsg(msg.data, true, br)
|
||||
case msg := <- br.inUsrMessage:
|
||||
dispatchMsg(msg.data, false, br)
|
||||
}
|
||||
}
|
||||
for sid, session := range br.sessions {
|
||||
if session.dev.devid == c.devid {
|
||||
session.web.wsClose()
|
||||
delete(br.sessions, sid)
|
||||
rlog.Println("Delete session: ", sid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if session, ok := br.sessions[c.sid]; ok {
|
||||
msg := fmt.Sprintf(`{"type":"logout","sid":%d}`, session.devsid)
|
||||
session.dev.wsWrite(websocket.TextMessage, []byte(msg))
|
||||
delete(br.sessions, c.sid)
|
||||
delete(session.dev.sessions, session.devsid)
|
||||
rlog.Println("Delete session: ", c.sid)
|
||||
}
|
||||
}
|
||||
case msg := <-br.inMessage:
|
||||
msgType := msg.msgType
|
||||
data := msg.data
|
||||
var sid uint32
|
||||
c := msg.c
|
||||
|
||||
if c.isDev {
|
||||
var devsid uint8
|
||||
if msgType == websocket.BinaryMessage {
|
||||
devsid = data[0]
|
||||
data = data[1:]
|
||||
} else {
|
||||
tp, _ := jsonparser.GetString(data, "type")
|
||||
if tp == "cmd" {
|
||||
handleCmdResp(data)
|
||||
continue
|
||||
}
|
||||
val, _ := jsonparser.GetInt(data, "sid")
|
||||
devsid = uint8(val)
|
||||
}
|
||||
sid = c.sessions[devsid]
|
||||
} else {
|
||||
sid = c.sid
|
||||
}
|
||||
|
||||
if session, ok := br.sessions[sid]; ok {
|
||||
if c.isDev {
|
||||
c = session.web
|
||||
} else {
|
||||
if msgType == websocket.BinaryMessage {
|
||||
sb := make([]byte, 1)
|
||||
sb[0] = session.devsid
|
||||
data = append(sb, data...)
|
||||
}
|
||||
c = session.dev
|
||||
}
|
||||
c.wsWrite(msgType, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,214 +20,223 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"sync"
|
||||
"errors"
|
||||
"strconv"
|
||||
"net/http"
|
||||
"github.com/gorilla/websocket"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
RTTY_PROTO_VERSION = 1
|
||||
/* Match the version with the device */
|
||||
RTTY_PROTO_VERSION = 1
|
||||
|
||||
// Max lose ping times
|
||||
aliveTimes = 3
|
||||
/* Max lose ping times */
|
||||
RTTY_MAX_LOSE_PING = 3
|
||||
|
||||
/* Max session id for each device */
|
||||
RTTY_MAX_SESSION_ID_DEV = 5
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type wsMessage struct {
|
||||
msgType int
|
||||
data []byte
|
||||
}
|
||||
|
||||
// Representing a device or user browser
|
||||
type Client struct {
|
||||
br *Broker
|
||||
isDev bool
|
||||
// device description
|
||||
description string
|
||||
devid string
|
||||
// Registration time
|
||||
timestamp int64
|
||||
sid string
|
||||
conn *websocket.Conn
|
||||
// Buffered channel of outbound messages.
|
||||
outMessage chan *wsMessage
|
||||
conn *websocket.Conn
|
||||
br *Broker
|
||||
devid string
|
||||
desc string /* description for device */
|
||||
isDev bool
|
||||
timestamp int64 /* Registration time */
|
||||
mutex sync.Mutex /* Avoid repeated closes and concurrent map writes */
|
||||
closed bool
|
||||
closeChan chan byte
|
||||
alive uint32
|
||||
sessions map[uint8]uint32
|
||||
sid uint32
|
||||
outMessage chan *wsOutMessage /* Buffered channel of outbound messages */
|
||||
}
|
||||
|
||||
cmdid uint32
|
||||
cmd map[uint32]chan *wsMessage
|
||||
type wsInMessage struct {
|
||||
msgType int
|
||||
data []byte
|
||||
c *Client
|
||||
}
|
||||
|
||||
isJoined bool
|
||||
type wsOutMessage struct {
|
||||
msgType int
|
||||
data []byte
|
||||
}
|
||||
|
||||
// Avoid repeated closes and concurrent map writes
|
||||
mutex sync.Mutex
|
||||
isClosed bool
|
||||
closeChan chan byte
|
||||
|
||||
alive uint32
|
||||
func (c *Client) getFreeSid() uint8 {
|
||||
for sid := uint8(1); sid <= RTTY_MAX_SESSION_ID_DEV; sid++ {
|
||||
if _, ok := c.sessions[sid]; !ok {
|
||||
return sid
|
||||
}
|
||||
}
|
||||
return uint8(0)
|
||||
}
|
||||
|
||||
func (c *Client) wsClose() {
|
||||
defer c.mutex.Unlock()
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.mutex.Lock()
|
||||
|
||||
if !c.isClosed {
|
||||
c.conn.Close()
|
||||
c.isClosed = true
|
||||
close(c.closeChan)
|
||||
}
|
||||
}
|
||||
func (c *Client) leave() {
|
||||
if c.isJoined {
|
||||
c.br.leave <- c
|
||||
}
|
||||
if !c.closed {
|
||||
c.conn.Close()
|
||||
c.closed = true
|
||||
close(c.closeChan)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) wsWrite(messageType int, data []byte) error {
|
||||
select {
|
||||
case c.outMessage <- &wsMessage{messageType, data}:
|
||||
case <- c.closeChan:
|
||||
return errors.New("websocket closed")
|
||||
}
|
||||
return nil
|
||||
func (c *Client) unregister() {
|
||||
c.br.unregister <- c
|
||||
}
|
||||
|
||||
func (c *Client) wsWrite(msgType int, data []byte) error {
|
||||
select {
|
||||
case c.outMessage <- &wsOutMessage{msgType, data}:
|
||||
case <-c.closeChan:
|
||||
return errors.New("websocket closed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) readPump() {
|
||||
defer func() {
|
||||
c.leave()
|
||||
}()
|
||||
defer func() {
|
||||
c.unregister()
|
||||
}()
|
||||
|
||||
for {
|
||||
msgType, data, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
rlog.Printf("error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
for {
|
||||
msgType, data, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
rlog.Printf("error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
msg := &wsMessage{msgType, data}
|
||||
msg := &wsInMessage{msgType, data, c}
|
||||
|
||||
inMessage := c.br.inUsrMessage
|
||||
if c.isDev {
|
||||
inMessage = c.br.inDevMessage
|
||||
}
|
||||
|
||||
select {
|
||||
case inMessage <- msg:
|
||||
case <- c.closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
select {
|
||||
case c.br.inMessage <- msg:
|
||||
case <-c.closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) writePump() {
|
||||
defer func() {
|
||||
c.leave()
|
||||
}()
|
||||
defer func() {
|
||||
c.unregister()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <- c.outMessage:
|
||||
if err := c.conn.WriteMessage(msg.msgType, msg.data); err != nil {
|
||||
return
|
||||
}
|
||||
case <- c.closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.outMessage:
|
||||
if err := c.conn.WriteMessage(msg.msgType, msg.data); err != nil {
|
||||
return
|
||||
}
|
||||
case <-c.closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) keepAlive(keepalive int64) {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
last := time.Now().Unix()
|
||||
keepalive = keepalive + 3
|
||||
alive := aliveTimes
|
||||
ticker := time.NewTicker(time.Second)
|
||||
last := time.Now().Unix()
|
||||
keepalive = keepalive + 3
|
||||
alive := RTTY_MAX_LOSE_PING
|
||||
|
||||
defer func() {
|
||||
c.leave()
|
||||
}()
|
||||
defer func() {
|
||||
c.unregister()
|
||||
}()
|
||||
|
||||
// Get the current ping handler
|
||||
pingHandler := c.conn.PingHandler()
|
||||
// Get the current ping handler
|
||||
pingHandler := c.conn.PingHandler()
|
||||
|
||||
c.conn.SetPingHandler(func(appData string) error {
|
||||
alive = aliveTimes
|
||||
last = time.Now().Unix()
|
||||
return pingHandler(appData)
|
||||
})
|
||||
c.conn.SetPingHandler(func(appData string) error {
|
||||
alive = RTTY_MAX_LOSE_PING
|
||||
last = time.Now().Unix()
|
||||
return pingHandler(appData)
|
||||
})
|
||||
|
||||
for {
|
||||
select {
|
||||
case <- c.closeChan:
|
||||
return
|
||||
case <- ticker.C:
|
||||
now := time.Now().Unix()
|
||||
if now - last > keepalive {
|
||||
alive--
|
||||
last = now
|
||||
if alive == 0 {
|
||||
rlog.Printf("Inactive device in long time, now kill it(%s)\n", c.devid)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-c.closeChan:
|
||||
return
|
||||
case <-ticker.C:
|
||||
now := time.Now().Unix()
|
||||
if now-last > keepalive {
|
||||
alive--
|
||||
last = now
|
||||
if alive == 0 {
|
||||
rlog.Printf("Inactive device in long time, now kill it(%s)\n", c.devid)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* serveWs handles websocket requests from the peer. */
|
||||
func serveWs(br *Broker, w http.ResponseWriter, r *http.Request) {
|
||||
keepalive, _ := strconv.ParseInt(r.URL.Query().Get("keepalive"), 10, 64)
|
||||
proto,_ := strconv.Atoi(r.URL.Query().Get("proto"))
|
||||
isDev := r.URL.Query().Get("device") != ""
|
||||
devid := r.URL.Query().Get("devid")
|
||||
keepalive, _ := strconv.ParseInt(r.URL.Query().Get("keepalive"), 10, 64)
|
||||
proto, _ := strconv.Atoi(r.URL.Query().Get("proto"))
|
||||
isDev := r.URL.Query().Get("device") != ""
|
||||
devid := r.URL.Query().Get("devid")
|
||||
|
||||
if devid == "" {
|
||||
rlog.Println("devid required")
|
||||
return
|
||||
}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
rlog.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if isDev {
|
||||
if proto != RTTY_PROTO_VERSION {
|
||||
rlog.Printf("proto number is not matched for device '%s', you need to update your server(rttys) or client(rtty) or both them", devid)
|
||||
return
|
||||
}
|
||||
}
|
||||
if devid == "" {
|
||||
msg := fmt.Sprintf(`{"type":"register","err":1,"msg":"devid required"}`)
|
||||
conn.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
rlog.Println("devid required")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
rlog.Println(err)
|
||||
return
|
||||
}
|
||||
if isDev {
|
||||
if proto != RTTY_PROTO_VERSION {
|
||||
msg := fmt.Sprintf(`{"type":"register","err":1,"msg":"proto number is not matched"}`)
|
||||
conn.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
rlog.Printf("proto number is not matched for device '%s', you need to update your server(rttys) or client(rtty) or both them", devid)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
br: br,
|
||||
devid: devid,
|
||||
conn: conn,
|
||||
timestamp: time.Now().Unix(),
|
||||
outMessage: make(chan *wsMessage, 100),
|
||||
closeChan: make(chan byte),
|
||||
isClosed: false,
|
||||
}
|
||||
client := &Client{
|
||||
br: br,
|
||||
conn: conn,
|
||||
devid: devid,
|
||||
timestamp: time.Now().Unix(),
|
||||
outMessage: make(chan *wsOutMessage, 1000),
|
||||
closeChan: make(chan byte),
|
||||
}
|
||||
|
||||
if isDev {
|
||||
client.isDev = true
|
||||
client.description = r.URL.Query().Get("description")
|
||||
client.cmd = make(map[uint32]chan *wsMessage)
|
||||
}
|
||||
if isDev {
|
||||
client.isDev = true
|
||||
client.sessions = make(map[uint8]uint32)
|
||||
client.desc = r.URL.Query().Get("description")
|
||||
|
||||
client.br.join <- client
|
||||
if keepalive > 0 {
|
||||
go client.keepAlive(keepalive)
|
||||
}
|
||||
}
|
||||
|
||||
go client.readPump()
|
||||
go client.writePump()
|
||||
go client.readPump()
|
||||
go client.writePump()
|
||||
|
||||
if client.isDev && keepalive > 0 {
|
||||
go client.keepAlive(keepalive)
|
||||
}
|
||||
client.br.register <- client
|
||||
}
|
||||
|
||||
+92
-92
@@ -20,110 +20,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"sync"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/zhaojh329/rttys/rtty"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type CommandReq struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Devid string `json:"devid"`
|
||||
Cmd string `json:"cmd"`
|
||||
Params []string `json:"params"`
|
||||
Env map[string]string `json:"env"`
|
||||
const RTTY_MAX_CMD_ID = 1000000
|
||||
|
||||
const (
|
||||
RTTY_CMD_ERR_INVALID = 1001
|
||||
RTTY_CMD_ERR_OFFLINE = 1002
|
||||
RTTY_CMD_ERR_BUSY = 1003
|
||||
RTTY_CMD_ERR_TIMEOUT = 1004
|
||||
)
|
||||
|
||||
var cmdErrMsg = map[int]string{
|
||||
RTTY_CMD_ERR_INVALID: "invalid format",
|
||||
RTTY_CMD_ERR_OFFLINE: "device offline",
|
||||
RTTY_CMD_ERR_BUSY: "server is busy",
|
||||
RTTY_CMD_ERR_TIMEOUT: "timeout",
|
||||
}
|
||||
|
||||
type CommandResult struct {
|
||||
ID uint32 `json:"id,omitempty"`
|
||||
Err int32 `json:err`
|
||||
Msg string `json:"msg"`
|
||||
Code int32 `json:"code"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
var commands = struct {
|
||||
sync.Mutex
|
||||
m map[uint32]chan []byte
|
||||
}{m: make(map[uint32]chan []byte)}
|
||||
|
||||
// return nil for failed
|
||||
func getFreeCmdChan() (chan []byte, uint32) {
|
||||
defer func() {
|
||||
commands.Unlock()
|
||||
}()
|
||||
|
||||
commands.Lock()
|
||||
for id := uint32(1); id <= RTTY_MAX_CMD_ID; id++ {
|
||||
_, ok := commands.m[id]
|
||||
if !ok {
|
||||
ch := make(chan []byte)
|
||||
commands.m[id] = ch
|
||||
return ch, id
|
||||
}
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
var commandID uint32 = 0
|
||||
var cmdMutex sync.Mutex
|
||||
var command = make(map[uint32]chan *rtty.RttyMessage)
|
||||
func freeCmd(id uint32) {
|
||||
commands.Lock()
|
||||
delete(commands.m, id)
|
||||
commands.Unlock()
|
||||
}
|
||||
|
||||
func handleCmdResp(data []byte) {
|
||||
id, _ := jsonparser.GetInt(data, "id")
|
||||
if ch, ok := commands.m[uint32(id)]; ok {
|
||||
ch <- data
|
||||
}
|
||||
}
|
||||
|
||||
func cmdErrReply(err int, w http.ResponseWriter) {
|
||||
msg := fmt.Sprintf(`{"err": %d, "msg":"%s"}`, err, cmdErrMsg[err])
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
|
||||
func serveCmd(br *Broker, w http.ResponseWriter, r *http.Request) {
|
||||
ticker := time.NewTicker(time.Second * 10)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
}()
|
||||
timer := time.NewTimer(time.Second * 5)
|
||||
defer func() {
|
||||
timer.Stop()
|
||||
}()
|
||||
|
||||
err := rtty.RttyMessage_CommandErr_value["NONE"]
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
devid, err := jsonparser.GetString(body, "devid")
|
||||
if err != nil {
|
||||
cmdErrReply(RTTY_CMD_ERR_INVALID, w)
|
||||
return
|
||||
}
|
||||
|
||||
req := CommandReq{}
|
||||
json.Unmarshal(body, &req)
|
||||
dev, ok := br.devices[devid]
|
||||
if !ok {
|
||||
cmdErrReply(RTTY_CMD_ERR_OFFLINE, w)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Devid == "" {
|
||||
err = rtty.RttyMessage_CommandErr_value["DEVID_REQUIRED"]
|
||||
} else if req.Cmd == "" {
|
||||
err = rtty.RttyMessage_CommandErr_value["CMD_REQUIRED"]
|
||||
} else if dev, ok := br.devices[req.Devid]; !ok {
|
||||
err = rtty.RttyMessage_CommandErr_value["DEV_OFFLINE"]
|
||||
} else {
|
||||
cmdMutex.Lock()
|
||||
id := commandID
|
||||
command[id] = make(chan *rtty.RttyMessage)
|
||||
commandID = commandID + 1
|
||||
if commandID == 1024 {
|
||||
commandID = 0
|
||||
}
|
||||
cmd := command[id]
|
||||
cmdMutex.Unlock()
|
||||
cmdChan, id := getFreeCmdChan()
|
||||
if cmdChan == nil {
|
||||
cmdErrReply(RTTY_CMD_ERR_BUSY, w)
|
||||
return
|
||||
}
|
||||
|
||||
msg := RttyMessageInit(&rtty.RttyMessage{
|
||||
Version: RTTY_MESSAGE_VERSION,
|
||||
Type: rtty.RttyMessage_COMMAND,
|
||||
Id: id,
|
||||
Name: req.Cmd,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Params: req.Params,
|
||||
Env: req.Env,
|
||||
})
|
||||
msg := fmt.Sprintf(`{"type":"cmd","id":%d,"attrs":%s}`, id, string(body))
|
||||
dev.wsWrite(websocket.TextMessage, []byte(msg))
|
||||
|
||||
dev.wsWrite(websocket.BinaryMessage, msg)
|
||||
|
||||
select {
|
||||
case msg := <- cmd:
|
||||
res := CommandResult{
|
||||
Err: msg.Err,
|
||||
Msg: rtty.RttyMessage_CommandErr_name[msg.Err],
|
||||
Code: msg.Code,
|
||||
Stdout: msg.StdOut,
|
||||
Stderr: msg.StdErr,
|
||||
}
|
||||
|
||||
cmdMutex.Lock()
|
||||
delete(command, msg.Id)
|
||||
cmdMutex.Unlock()
|
||||
|
||||
js, _ := json.Marshal(res)
|
||||
w.Write(js)
|
||||
|
||||
return
|
||||
case <- ticker.C:
|
||||
cmdMutex.Lock()
|
||||
delete(command, id)
|
||||
cmdMutex.Unlock()
|
||||
err = rtty.RttyMessage_CommandErr_value["TIMEOUT"]
|
||||
goto Err
|
||||
}
|
||||
}
|
||||
|
||||
Err:
|
||||
res := CommandResult{Err: err, Msg: rtty.RttyMessage_CommandErr_name[err]}
|
||||
js, _ := json.Marshal(res)
|
||||
w.Write(js)
|
||||
select {
|
||||
case data := <-cmdChan:
|
||||
attrs, _, _, _ := jsonparser.Get(data, "attrs")
|
||||
w.Write(attrs)
|
||||
case <-timer.C:
|
||||
freeCmd(id)
|
||||
cmdErrReply(RTTY_CMD_ERR_TIMEOUT, w)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"iview": "^2.14.1",
|
||||
"pbf": "^3.1.0",
|
||||
"simple-websocket": "^7.0.2",
|
||||
"string-format-easy": "^1.0.1",
|
||||
"vue": "^2.5.13",
|
||||
"vue-axios": "^2.1.1",
|
||||
|
||||
@@ -17,30 +17,15 @@
|
||||
|
||||
<script>
|
||||
|
||||
import * as Socket from 'simple-websocket';
|
||||
import { Terminal } from 'xterm'
|
||||
import 'xterm/lib/xterm.css'
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
import * as overlay from '@/overlay';
|
||||
import Utf8ArrayToStr from '@/utf8array_str'
|
||||
import 'zmodem.js/dist/zmodem.devel'
|
||||
|
||||
Terminal.applyAddon(fit);
|
||||
Terminal.applyAddon(overlay);
|
||||
|
||||
const Pbf = require('pbf');
|
||||
const rttyMsg = require('@/rtty.proto').rtty_message;
|
||||
|
||||
function rttyMsgInit(type, msg) {
|
||||
let pbf = new Pbf();
|
||||
|
||||
msg.version = 2;
|
||||
msg.type = rttyMsg.Type[type].value;
|
||||
rttyMsg.write(msg, pbf);
|
||||
|
||||
return pbf.finish();
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Rtty',
|
||||
data() {
|
||||
@@ -59,7 +44,7 @@ export default {
|
||||
methods: {
|
||||
logout() {
|
||||
if (this.ws) {
|
||||
this.ws.destroy();
|
||||
this.ws.close();
|
||||
delete this.ws;
|
||||
}
|
||||
|
||||
@@ -179,8 +164,7 @@ export default {
|
||||
|
||||
zsession.on("session_end", () => {
|
||||
this.term.write('\n');
|
||||
let msg = rttyMsgInit('TTY', {sid: this.sid, data: Buffer.from('\n')});
|
||||
this.ws.send(msg);
|
||||
this.ws.send(Buffer.from('\n'));
|
||||
});
|
||||
|
||||
zsession.start();
|
||||
@@ -253,10 +237,12 @@ export default {
|
||||
this.username = this.$route.query.username;
|
||||
this.password = this.$route.query.password;
|
||||
|
||||
let ws = new Socket(protocol + location.host + '/ws?devid=' + devid);
|
||||
this.ws = ws;
|
||||
let ws = new WebSocket(protocol + location.host + '/ws?devid=' + devid);
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.binaryType = 'arraybuffer';
|
||||
this.ws = ws;
|
||||
|
||||
ws.on('connect', () => {
|
||||
let term = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 16
|
||||
@@ -276,8 +262,8 @@ export default {
|
||||
|
||||
term.on('resize', (size) => {
|
||||
setTimeout(() => {
|
||||
let msg = rttyMsgInit('WINSIZE', {sid: this.sid, cols: size.cols, rows: size.rows});
|
||||
ws.send(msg);
|
||||
let msg = {type: "winsize", sid: this.sid, cols: size.cols, rows: size.rows};
|
||||
ws.send(JSON.stringify(msg));
|
||||
term.showOverlay(size.cols + 'x' + size.rows);
|
||||
}, 500);
|
||||
});
|
||||
@@ -286,11 +272,10 @@ export default {
|
||||
|
||||
let zsentry = new Zmodem.Sentry({
|
||||
to_terminal: (octets) => {
|
||||
this.term.write(Utf8ArrayToStr(octets));
|
||||
this.term.write(Buffer.from(octets).toString());
|
||||
},
|
||||
sender: (octets) => {
|
||||
let msg = rttyMsgInit('TTY', {sid: this.sid, data: Buffer.from(octets)});
|
||||
this.ws.send(msg);
|
||||
this.ws.send(Buffer.from(octets));
|
||||
},
|
||||
on_retract: () => {
|
||||
this.upfile.modal = false;
|
||||
@@ -309,22 +294,27 @@ export default {
|
||||
});
|
||||
|
||||
this.zsentry = zsentry;
|
||||
};
|
||||
|
||||
ws.on('data', (data) => {
|
||||
let pbf = new Pbf(data);
|
||||
let msg = rttyMsg.read(pbf);
|
||||
ws.onmessage = (ev) => {
|
||||
let zsentry = this.zsentry;
|
||||
let term = this.term;
|
||||
|
||||
if (msg.type == rttyMsg.Type.LOGINACK.value) {
|
||||
if (msg.code == rttyMsg.LoginCode.OFFLINE.value) {
|
||||
this.$Message.error(this.$t('Device offline'));
|
||||
if (typeof ev.data == 'string') {
|
||||
let msg = JSON.parse(ev.data);
|
||||
if (msg.type == "login") {
|
||||
if (msg.err == 1) {
|
||||
this.$Message.error(this.$t('Device offline'));
|
||||
this.logout();
|
||||
return;
|
||||
} else if (msg.err == 2) {
|
||||
this.$Message.error(this.$t('Sessions is full'));
|
||||
this.logout();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sid = msg.sid;
|
||||
|
||||
msg = rttyMsgInit('WINSIZE', {sid: this.sid, cols: term.cols, rows: term.rows});
|
||||
ws.send(msg);
|
||||
msg = {type: 'winsize', sid: this.sid, cols: term.cols, rows: term.rows};
|
||||
ws.send(JSON.stringify(msg));
|
||||
|
||||
term.on('data', (data) => {
|
||||
let zsession = zsentry.get_confirmed_session();
|
||||
@@ -338,43 +328,42 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = rttyMsgInit('TTY', {sid: this.sid, data: Buffer.from(data)});
|
||||
ws.send(msg);
|
||||
this.ws.send(Buffer.from(data));
|
||||
});
|
||||
} else if (msg.type == rttyMsg.Type.TTY.value) {
|
||||
if (!this.recvTTYCnt)
|
||||
this.recvTTYCnt = 0;
|
||||
this.recvTTYCnt++;
|
||||
} else if (msg.type == 'logout') {
|
||||
this.logout();
|
||||
}
|
||||
} else {
|
||||
if (!this.recvTTYCnt)
|
||||
this.recvTTYCnt = 0;
|
||||
this.recvTTYCnt++;
|
||||
|
||||
if (this.recvTTYCnt < 4) {
|
||||
let data = Utf8ArrayToStr(msg.data);
|
||||
if (this.recvTTYCnt < 4) {
|
||||
let data = Buffer.from(ev.data).toString();
|
||||
|
||||
if (data.match('login:') && this.username && this.username != '') {
|
||||
let msg = rttyMsgInit('TTY', {sid: this.sid, data: Buffer.from(this.username + '\n')});
|
||||
ws.send(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.match('Password:') && this.password && this.password != '') {
|
||||
let msg = rttyMsgInit('TTY', {sid: this.sid, data: Buffer.from(this.password + '\n')});
|
||||
ws.send(msg);
|
||||
return;
|
||||
}
|
||||
if (data.match('login:') && this.username && this.username != '') {
|
||||
ws.send(Buffer.from(this.username + '\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
zsentry.consume(msg.data);
|
||||
if (data.match('Password:') && this.password && this.password != '') {
|
||||
ws.send(Buffer.from(this.password + '\n'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('error', () => {
|
||||
zsentry.consume(ev.data);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
this.$Message.error(this.$t('Connect failed'));
|
||||
this.logout();
|
||||
});
|
||||
};
|
||||
|
||||
ws.on('close', () => {
|
||||
ws.onclose = () => {
|
||||
this.logout();
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,8 @@ const RttyI18n = {
|
||||
'Login Fail! username or password wrong.': '登录失败,用户名或密码错误',
|
||||
'Connect failed': '连接失败',
|
||||
'device-count': '在线设备数:{count}',
|
||||
'Cannot be greater than 500MB': '不能大于500MB'
|
||||
'Cannot be greater than 500MB': '不能大于500MB',
|
||||
'Sessions is full':'会话已满'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
/* utf.js - UTF-8 <=> UTF-16 convertion
|
||||
*
|
||||
* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
|
||||
* Version: 1.0
|
||||
* LastModified: Dec 25 1999
|
||||
* This library is free. You can redistribute it and/or modify it.
|
||||
*/
|
||||
|
||||
function Utf8ArrayToStr(array) {
|
||||
var out, i, len, c;
|
||||
var char2, char3;
|
||||
|
||||
out = "";
|
||||
len = array.length;
|
||||
i = 0;
|
||||
while(i < len) {
|
||||
c = array[i++];
|
||||
switch(c >> 4) {
|
||||
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
|
||||
// 0xxxxxxx
|
||||
out += String.fromCharCode(c);
|
||||
break;
|
||||
case 12: case 13:
|
||||
// 110x xxxx 10xx xxxx
|
||||
char2 = array[i++];
|
||||
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
|
||||
break;
|
||||
case 14:
|
||||
// 1110 xxxx 10xx xxxx 10xx xxxx
|
||||
char2 = array[i++];
|
||||
char3 = array[i++];
|
||||
out += String.fromCharCode(((c & 0x0F) << 12) |
|
||||
((char2 & 0x3F) << 6) |
|
||||
((char3 & 0x3F) << 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export default Utf8ArrayToStr
|
||||
@@ -20,186 +20,182 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"sync"
|
||||
"flag"
|
||||
"time"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"crypto/md5"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
_ "github.com/zhaojh329/rttys/statik"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
_ "github.com/zhaojh329/rttys/statik"
|
||||
)
|
||||
|
||||
const MAX_SESSION_TIME = 30 * time.Minute
|
||||
|
||||
type DeviceInfo struct {
|
||||
ID string `json:"id"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type HttpSession struct {
|
||||
active time.Duration
|
||||
active time.Duration
|
||||
}
|
||||
|
||||
func allowOrigin(w http.ResponseWriter) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
|
||||
w.Header().Set("content-type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
|
||||
w.Header().Set("content-type", "application/json")
|
||||
}
|
||||
|
||||
var hsMutex sync.Mutex
|
||||
var httpSessions = make(map[string]*HttpSession)
|
||||
|
||||
func cleanHttpSession() {
|
||||
defer hsMutex.Unlock()
|
||||
defer hsMutex.Unlock()
|
||||
|
||||
hsMutex.Lock()
|
||||
for sid, s := range httpSessions {
|
||||
s.active = s.active - time.Second
|
||||
if s.active == 0 {
|
||||
delete(httpSessions, sid)
|
||||
}
|
||||
}
|
||||
time.AfterFunc(1 * time.Second, cleanHttpSession)
|
||||
hsMutex.Lock()
|
||||
for sid, s := range httpSessions {
|
||||
s.active = s.active - time.Second
|
||||
if s.active == 0 {
|
||||
delete(httpSessions, sid)
|
||||
}
|
||||
}
|
||||
time.AfterFunc(1*time.Second, cleanHttpSession)
|
||||
}
|
||||
|
||||
func generateHttpSID(username, password string) string {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write([]byte(username + strconv.FormatFloat(rand.Float64(), 'e', 6, 32) + password))
|
||||
cipherStr := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(cipherStr)
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write([]byte(username + strconv.FormatFloat(rand.Float64(), 'e', 6, 32) + password))
|
||||
cipherStr := md5Ctx.Sum(nil)
|
||||
return hex.EncodeToString(cipherStr)
|
||||
}
|
||||
|
||||
func httpAuth(w http.ResponseWriter, r *http.Request) bool {
|
||||
c, err := r.Cookie("sid")
|
||||
if err != nil {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
c, err := r.Cookie("sid")
|
||||
if err != nil {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
|
||||
defer hsMutex.Unlock()
|
||||
defer hsMutex.Unlock()
|
||||
|
||||
hsMutex.Lock()
|
||||
hsMutex.Lock()
|
||||
|
||||
s, ok := httpSessions[c.Value]
|
||||
if !ok {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
s, ok := httpSessions[c.Value]
|
||||
if !ok {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
|
||||
s.active = MAX_SESSION_TIME
|
||||
s.active = MAX_SESSION_TIME
|
||||
|
||||
return true
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := flag.Int("port", 5912, "http service port")
|
||||
cert := flag.String("cert", "", "certFile Path")
|
||||
key := flag.String("key", "", "keyFile Path")
|
||||
port := flag.Int("port", 5912, "http service port")
|
||||
cert := flag.String("cert", "", "certFile Path")
|
||||
key := flag.String("key", "", "keyFile Path")
|
||||
|
||||
if !checkUser() {
|
||||
rlog.Println("Operation not permitted")
|
||||
os.Exit(1)
|
||||
}
|
||||
if !checkUser() {
|
||||
rlog.Println("Operation not permitted")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
flag.Parse()
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
rlog.Printf("go version: %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
rlog.Println("rttys version:", rttys_version())
|
||||
rlog.Printf("go version: %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
rlog.Println("rttys version:", rttys_version())
|
||||
|
||||
br := newBroker()
|
||||
go br.run()
|
||||
br := newBroker()
|
||||
go br.run()
|
||||
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
rlog.Fatal(err)
|
||||
return
|
||||
}
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
rlog.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
staticfs := http.FileServer(statikFS)
|
||||
staticfs := http.FileServer(statikFS)
|
||||
|
||||
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveWs(br, w, r)
|
||||
})
|
||||
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveWs(br, w, r)
|
||||
})
|
||||
|
||||
http.HandleFunc("/cmd", func(w http.ResponseWriter, r *http.Request) {
|
||||
allowOrigin(w)
|
||||
serveCmd(br, w, r)
|
||||
})
|
||||
http.HandleFunc("/cmd", func(w http.ResponseWriter, r *http.Request) {
|
||||
allowOrigin(w)
|
||||
serveCmd(br, w, r)
|
||||
})
|
||||
|
||||
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||
username := r.PostFormValue("username")
|
||||
password := r.PostFormValue("password")
|
||||
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||
username := r.PostFormValue("username")
|
||||
password := r.PostFormValue("password")
|
||||
|
||||
if login(username, password) {
|
||||
sid := generateHttpSID(username, password)
|
||||
cookie := http.Cookie{
|
||||
Name: "sid",
|
||||
Value: sid,
|
||||
HttpOnly: true,
|
||||
}
|
||||
if login(username, password) {
|
||||
sid := generateHttpSID(username, password)
|
||||
cookie := http.Cookie{
|
||||
Name: "sid",
|
||||
Value: sid,
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
hsMutex.Lock()
|
||||
httpSessions[sid] = &HttpSession{
|
||||
active: MAX_SESSION_TIME,
|
||||
}
|
||||
hsMutex.Unlock()
|
||||
hsMutex.Lock()
|
||||
httpSessions[sid] = &HttpSession{
|
||||
active: MAX_SESSION_TIME,
|
||||
}
|
||||
hsMutex.Unlock()
|
||||
|
||||
w.Header().Set("Set-Cookie", cookie.String())
|
||||
fmt.Fprint(w, sid)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Set-Cookie", cookie.String())
|
||||
fmt.Fprint(w, sid)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
})
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
})
|
||||
|
||||
http.HandleFunc("/devs", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !httpAuth(w, r) {
|
||||
return
|
||||
}
|
||||
http.HandleFunc("/devs", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !httpAuth(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
devs := make([]DeviceInfo, 0)
|
||||
for _, c := range br.devices {
|
||||
if c.isDev {
|
||||
d := DeviceInfo{c.devid, time.Now().Unix() - c.timestamp, c.description}
|
||||
devs = append(devs, d)
|
||||
}
|
||||
}
|
||||
devs := "["
|
||||
|
||||
allowOrigin(w)
|
||||
for _, c := range br.devices {
|
||||
if c.isDev {
|
||||
devs += fmt.Sprintf(`{"id":"%s","uptime":%d,"description":"%s"}`,
|
||||
c.devid, time.Now().Unix()-c.timestamp, c.desc)
|
||||
}
|
||||
}
|
||||
|
||||
rsp, _ := json.Marshal(devs)
|
||||
w.Write(rsp)
|
||||
})
|
||||
devs += "]"
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
t := r.URL.Query().Get("t")
|
||||
id := r.URL.Query().Get("id")
|
||||
allowOrigin(w)
|
||||
|
||||
if t == "" && id == "" {
|
||||
http.Redirect(w, r, "/?t=" + strconv.FormatInt(time.Now().Unix(), 10), http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.Write([]byte(devs))
|
||||
})
|
||||
|
||||
staticfs.ServeHTTP(w, r)
|
||||
})
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
t := r.URL.Query().Get("t")
|
||||
id := r.URL.Query().Get("id")
|
||||
|
||||
if *cert != "" && *key != "" {
|
||||
rlog.Println("Listen on: ", *port, "SSL on")
|
||||
rlog.Fatal(http.ListenAndServeTLS(":" + strconv.Itoa(*port), *cert, *key, nil))
|
||||
} else {
|
||||
rlog.Println("Listen on: ", *port, "SSL off")
|
||||
rlog.Fatal(http.ListenAndServe(":" + strconv.Itoa(*port), nil))
|
||||
}
|
||||
if t == "" && id == "" {
|
||||
http.Redirect(w, r, "/?t="+strconv.FormatInt(time.Now().Unix(), 10), http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
staticfs.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
if *cert != "" && *key != "" {
|
||||
rlog.Println("Listen on: ", *port, "SSL on")
|
||||
rlog.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(*port), *cert, *key, nil))
|
||||
} else {
|
||||
rlog.Println("Listen on: ", *port, "SSL off")
|
||||
rlog.Fatal(http.ListenAndServe(":"+strconv.Itoa(*port), nil))
|
||||
}
|
||||
}
|
||||
|
||||
-317
@@ -1,317 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: rtty.proto
|
||||
|
||||
/*
|
||||
Package rtty is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
rtty.proto
|
||||
|
||||
It has these top-level messages:
|
||||
RttyMessage
|
||||
*/
|
||||
package rtty
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type RttyMessage_Type int32
|
||||
|
||||
const (
|
||||
RttyMessage_UNKNOWN RttyMessage_Type = 0
|
||||
RttyMessage_LOGIN RttyMessage_Type = 1
|
||||
RttyMessage_LOGINACK RttyMessage_Type = 2
|
||||
RttyMessage_LOGOUT RttyMessage_Type = 3
|
||||
RttyMessage_TTY RttyMessage_Type = 4
|
||||
RttyMessage_COMMAND RttyMessage_Type = 5
|
||||
RttyMessage_WINSIZE RttyMessage_Type = 6
|
||||
)
|
||||
|
||||
var RttyMessage_Type_name = map[int32]string{
|
||||
0: "UNKNOWN",
|
||||
1: "LOGIN",
|
||||
2: "LOGINACK",
|
||||
3: "LOGOUT",
|
||||
4: "TTY",
|
||||
5: "COMMAND",
|
||||
6: "WINSIZE",
|
||||
}
|
||||
var RttyMessage_Type_value = map[string]int32{
|
||||
"UNKNOWN": 0,
|
||||
"LOGIN": 1,
|
||||
"LOGINACK": 2,
|
||||
"LOGOUT": 3,
|
||||
"TTY": 4,
|
||||
"COMMAND": 5,
|
||||
"WINSIZE": 6,
|
||||
}
|
||||
|
||||
func (x RttyMessage_Type) String() string {
|
||||
return proto.EnumName(RttyMessage_Type_name, int32(x))
|
||||
}
|
||||
func (RttyMessage_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }
|
||||
|
||||
type RttyMessage_LoginCode int32
|
||||
|
||||
const (
|
||||
RttyMessage_OK RttyMessage_LoginCode = 0
|
||||
RttyMessage_OFFLINE RttyMessage_LoginCode = 1
|
||||
)
|
||||
|
||||
var RttyMessage_LoginCode_name = map[int32]string{
|
||||
0: "OK",
|
||||
1: "OFFLINE",
|
||||
}
|
||||
var RttyMessage_LoginCode_value = map[string]int32{
|
||||
"OK": 0,
|
||||
"OFFLINE": 1,
|
||||
}
|
||||
|
||||
func (x RttyMessage_LoginCode) String() string {
|
||||
return proto.EnumName(RttyMessage_LoginCode_name, int32(x))
|
||||
}
|
||||
func (RttyMessage_LoginCode) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 1} }
|
||||
|
||||
type RttyMessage_CommandErr int32
|
||||
|
||||
const (
|
||||
RttyMessage_NONE RttyMessage_CommandErr = 0
|
||||
RttyMessage_TIMEOUT RttyMessage_CommandErr = 1
|
||||
RttyMessage_NOTFOUND RttyMessage_CommandErr = 2
|
||||
RttyMessage_READ RttyMessage_CommandErr = 3
|
||||
RttyMessage_PERMISSION RttyMessage_CommandErr = 4
|
||||
RttyMessage_SYSCALL RttyMessage_CommandErr = 5
|
||||
RttyMessage_DEV_OFFLINE RttyMessage_CommandErr = 6
|
||||
RttyMessage_CMD_REQUIRED RttyMessage_CommandErr = 7
|
||||
RttyMessage_DEVID_REQUIRED RttyMessage_CommandErr = 8
|
||||
)
|
||||
|
||||
var RttyMessage_CommandErr_name = map[int32]string{
|
||||
0: "NONE",
|
||||
1: "TIMEOUT",
|
||||
2: "NOTFOUND",
|
||||
3: "READ",
|
||||
4: "PERMISSION",
|
||||
5: "SYSCALL",
|
||||
6: "DEV_OFFLINE",
|
||||
7: "CMD_REQUIRED",
|
||||
8: "DEVID_REQUIRED",
|
||||
}
|
||||
var RttyMessage_CommandErr_value = map[string]int32{
|
||||
"NONE": 0,
|
||||
"TIMEOUT": 1,
|
||||
"NOTFOUND": 2,
|
||||
"READ": 3,
|
||||
"PERMISSION": 4,
|
||||
"SYSCALL": 5,
|
||||
"DEV_OFFLINE": 6,
|
||||
"CMD_REQUIRED": 7,
|
||||
"DEVID_REQUIRED": 8,
|
||||
}
|
||||
|
||||
func (x RttyMessage_CommandErr) String() string {
|
||||
return proto.EnumName(RttyMessage_CommandErr_name, int32(x))
|
||||
}
|
||||
func (RttyMessage_CommandErr) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 2} }
|
||||
|
||||
type RttyMessage struct {
|
||||
Version uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
|
||||
Type RttyMessage_Type `protobuf:"varint,2,opt,name=type,enum=RttyMessage_Type" json:"type,omitempty"`
|
||||
Sid string `protobuf:"bytes,3,opt,name=sid" json:"sid,omitempty"`
|
||||
Code int32 `protobuf:"varint,4,opt,name=code" json:"code,omitempty"`
|
||||
Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Name string `protobuf:"bytes,6,opt,name=name" json:"name,omitempty"`
|
||||
Size uint32 `protobuf:"varint,7,opt,name=size" json:"size,omitempty"`
|
||||
Id uint32 `protobuf:"varint,8,opt,name=id" json:"id,omitempty"`
|
||||
Err int32 `protobuf:"varint,9,opt,name=err" json:"err,omitempty"`
|
||||
Username string `protobuf:"bytes,10,opt,name=username" json:"username,omitempty"`
|
||||
Password string `protobuf:"bytes,11,opt,name=password" json:"password,omitempty"`
|
||||
StdOut string `protobuf:"bytes,12,opt,name=std_out,json=stdOut" json:"std_out,omitempty"`
|
||||
StdErr string `protobuf:"bytes,13,opt,name=std_err,json=stdErr" json:"std_err,omitempty"`
|
||||
Params []string `protobuf:"bytes,14,rep,name=params" json:"params,omitempty"`
|
||||
Env map[string]string `protobuf:"bytes,15,rep,name=env" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Cols uint32 `protobuf:"varint,16,opt,name=cols" json:"cols,omitempty"`
|
||||
Rows uint32 `protobuf:"varint,17,opt,name=rows" json:"rows,omitempty"`
|
||||
}
|
||||
|
||||
func (m *RttyMessage) Reset() { *m = RttyMessage{} }
|
||||
func (m *RttyMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*RttyMessage) ProtoMessage() {}
|
||||
func (*RttyMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *RttyMessage) GetVersion() uint32 {
|
||||
if m != nil {
|
||||
return m.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetType() RttyMessage_Type {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return RttyMessage_UNKNOWN
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetSid() string {
|
||||
if m != nil {
|
||||
return m.Sid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetCode() int32 {
|
||||
if m != nil {
|
||||
return m.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetData() []byte {
|
||||
if m != nil {
|
||||
return m.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetSize() uint32 {
|
||||
if m != nil {
|
||||
return m.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetId() uint32 {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetErr() int32 {
|
||||
if m != nil {
|
||||
return m.Err
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetUsername() string {
|
||||
if m != nil {
|
||||
return m.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetPassword() string {
|
||||
if m != nil {
|
||||
return m.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetStdOut() string {
|
||||
if m != nil {
|
||||
return m.StdOut
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetStdErr() string {
|
||||
if m != nil {
|
||||
return m.StdErr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetParams() []string {
|
||||
if m != nil {
|
||||
return m.Params
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetEnv() map[string]string {
|
||||
if m != nil {
|
||||
return m.Env
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetCols() uint32 {
|
||||
if m != nil {
|
||||
return m.Cols
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RttyMessage) GetRows() uint32 {
|
||||
if m != nil {
|
||||
return m.Rows
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*RttyMessage)(nil), "rtty_message")
|
||||
proto.RegisterEnum("RttyMessage_Type", RttyMessage_Type_name, RttyMessage_Type_value)
|
||||
proto.RegisterEnum("RttyMessage_LoginCode", RttyMessage_LoginCode_name, RttyMessage_LoginCode_value)
|
||||
proto.RegisterEnum("RttyMessage_CommandErr", RttyMessage_CommandErr_name, RttyMessage_CommandErr_value)
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("rtty.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 521 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x52, 0x4f, 0x6b, 0xdb, 0x30,
|
||||
0x14, 0xaf, 0xed, 0xd8, 0x89, 0x5f, 0xd2, 0x54, 0x13, 0xa3, 0x13, 0x3d, 0x99, 0x1c, 0x86, 0x4f,
|
||||
0x3d, 0x74, 0x30, 0xc6, 0x6e, 0xc1, 0x56, 0x8b, 0xa9, 0x23, 0x6d, 0x8a, 0xd3, 0xd2, 0xed, 0x10,
|
||||
0xbc, 0x59, 0x14, 0xb3, 0xc6, 0x0e, 0x92, 0x93, 0x92, 0x7d, 0x89, 0x7d, 0xd6, 0x7d, 0x83, 0x21,
|
||||
0x99, 0x94, 0xee, 0xf6, 0xfb, 0xf3, 0xfe, 0xe9, 0x3d, 0x01, 0xa8, 0xae, 0x3b, 0x5c, 0x6e, 0x55,
|
||||
0xdb, 0xb5, 0xb3, 0xbf, 0x3e, 0x4c, 0x0c, 0x5d, 0x6f, 0xa4, 0xd6, 0xe5, 0xa3, 0xc4, 0x04, 0x86,
|
||||
0x7b, 0xa9, 0x74, 0xdd, 0x36, 0xc4, 0x89, 0x9c, 0xf8, 0x54, 0x1c, 0x29, 0x7e, 0x0f, 0x83, 0xee,
|
||||
0xb0, 0x95, 0xc4, 0x8d, 0x9c, 0x78, 0x7a, 0x85, 0x2f, 0x5f, 0xa7, 0x5d, 0x16, 0x87, 0xad, 0x14,
|
||||
0xd6, 0xc7, 0x08, 0x3c, 0x5d, 0x57, 0xc4, 0x8b, 0x9c, 0x38, 0x14, 0x06, 0x62, 0x0c, 0x83, 0x9f,
|
||||
0x6d, 0x25, 0xc9, 0x20, 0x72, 0x62, 0x5f, 0x58, 0x6c, 0xb4, 0xaa, 0xec, 0x4a, 0xe2, 0x47, 0x4e,
|
||||
0x3c, 0x11, 0x16, 0x1b, 0xad, 0x29, 0x37, 0x92, 0x04, 0x36, 0xd5, 0x62, 0xa3, 0xe9, 0xfa, 0xb7,
|
||||
0x24, 0x43, 0x3b, 0x8c, 0xc5, 0x78, 0x0a, 0x6e, 0x5d, 0x91, 0x91, 0x55, 0xdc, 0xba, 0x32, 0x1d,
|
||||
0xa5, 0x52, 0x24, 0xb4, 0xe5, 0x0d, 0xc4, 0x17, 0x30, 0xda, 0x69, 0xa9, 0x6c, 0x35, 0xb0, 0xd5,
|
||||
0x5e, 0xb8, 0xf1, 0xb6, 0xa5, 0xd6, 0xcf, 0xad, 0xaa, 0xc8, 0xb8, 0xf7, 0x8e, 0x1c, 0xbf, 0x83,
|
||||
0xa1, 0xee, 0xaa, 0x75, 0xbb, 0xeb, 0xc8, 0xc4, 0x5a, 0x81, 0xee, 0x2a, 0xbe, 0xeb, 0x8e, 0x86,
|
||||
0x69, 0x73, 0xfa, 0x62, 0x50, 0xa5, 0xf0, 0x39, 0x04, 0xdb, 0x52, 0x95, 0x1b, 0x4d, 0xa6, 0x91,
|
||||
0x67, 0xf4, 0x9e, 0xe1, 0x18, 0x3c, 0xd9, 0xec, 0xc9, 0x59, 0xe4, 0xc5, 0xe3, 0xab, 0xf3, 0xff,
|
||||
0x97, 0x45, 0x9b, 0x3d, 0x6d, 0x3a, 0x75, 0x10, 0x26, 0xa4, 0xdf, 0xce, 0x93, 0x26, 0xa8, 0x7f,
|
||||
0xa1, 0xc1, 0x46, 0x53, 0xed, 0xb3, 0x26, 0x6f, 0x7a, 0xcd, 0xe0, 0x8b, 0x8f, 0x30, 0x3a, 0x26,
|
||||
0x9a, 0x17, 0xff, 0x92, 0x07, 0x7b, 0xa1, 0x50, 0x18, 0x88, 0xdf, 0x82, 0xbf, 0x2f, 0x9f, 0x76,
|
||||
0xfd, 0x79, 0x42, 0xd1, 0x93, 0xcf, 0xee, 0x27, 0x67, 0xf6, 0x1d, 0x06, 0xe6, 0x3a, 0x78, 0x0c,
|
||||
0xc3, 0x15, 0xbb, 0x65, 0xfc, 0x9e, 0xa1, 0x13, 0x1c, 0x82, 0x9f, 0xf3, 0x9b, 0x8c, 0x21, 0x07,
|
||||
0x4f, 0x60, 0x64, 0xe1, 0x3c, 0xb9, 0x45, 0x2e, 0x06, 0x08, 0x72, 0x7e, 0xc3, 0x57, 0x05, 0xf2,
|
||||
0xf0, 0x10, 0xbc, 0xa2, 0x78, 0x40, 0x03, 0x93, 0x9a, 0xf0, 0xc5, 0x62, 0xce, 0x52, 0xe4, 0x1b,
|
||||
0x72, 0x9f, 0xb1, 0x65, 0xf6, 0x8d, 0xa2, 0x60, 0x16, 0x41, 0x98, 0xb7, 0x8f, 0x75, 0x93, 0x98,
|
||||
0x9b, 0x06, 0xe0, 0xf2, 0x5b, 0x74, 0x62, 0x22, 0xf8, 0xf5, 0x75, 0x9e, 0x31, 0x8a, 0x9c, 0xd9,
|
||||
0x1f, 0x07, 0x20, 0x69, 0x37, 0x9b, 0xb2, 0xb1, 0xfb, 0x1a, 0xc1, 0x80, 0x71, 0x46, 0xfb, 0xa8,
|
||||
0x22, 0x5b, 0x50, 0xd3, 0xca, 0x0e, 0xc1, 0x78, 0x71, 0xcd, 0x57, 0x2c, 0x45, 0xae, 0x09, 0x12,
|
||||
0x74, 0x9e, 0x22, 0x0f, 0x4f, 0x01, 0xbe, 0x50, 0xb1, 0xc8, 0x96, 0xcb, 0x8c, 0xb3, 0x7e, 0x92,
|
||||
0xe5, 0xc3, 0x32, 0x99, 0xe7, 0x39, 0xf2, 0xf1, 0x19, 0x8c, 0x53, 0x7a, 0xb7, 0x3e, 0xf6, 0x0a,
|
||||
0x30, 0x82, 0x49, 0xb2, 0x48, 0xd7, 0x82, 0x7e, 0x5d, 0x65, 0x82, 0xa6, 0x68, 0x88, 0x31, 0x4c,
|
||||
0x53, 0x7a, 0x97, 0xbd, 0xd2, 0x46, 0x3f, 0x02, 0xfb, 0xf5, 0x3f, 0xfc, 0x0b, 0x00, 0x00, 0xff,
|
||||
0xff, 0x4b, 0xb6, 0xc9, 0x16, 0x08, 0x03, 0x00, 0x00,
|
||||
}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user