Restructure

No longer use JSON;
Using binary data in TLV format;
Optimization code;

Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>
This commit is contained in:
Jianhui Zhao
2018-02-06 00:35:57 +08:00
parent ba61a45ec9
commit 2eaae7b7f3
11 changed files with 746 additions and 518 deletions
+36 -140
View File
@@ -20,14 +20,6 @@
<Button type="primary" size="large" long :loading="modal_loading" @click="doUpload">{{ modal_loading ? 'Uploading' : 'Click to upload' }}</Button>
</div>
</Modal>
<Modal v-model="filelist_modal" width="600" :mask-closable="false">
<p slot="header">
<span>Please select file to download</span>
</p>
<Tag>{{'/' + dl_path.join('/')}}</Tag>
<Table :columns="filelist_title" height="300" :data="filelist" @on-row-dblclick="filelistDblclick"></Table>
<div slot="footer"></div>
</Modal>
</div>
</template>
@@ -38,6 +30,7 @@ import { Terminal } from 'xterm'
import 'xterm/lib/xterm.css'
import * as fit from 'xterm/lib/addons/fit/fit';
import axios from 'axios'
import * as rtty from './rtty'
Terminal.applyAddon(fit);
@@ -52,8 +45,6 @@ export default {
terminal_loading: false,
modal_loading: false,
upmodal: false,
filelist_modal: false,
dl_path: [],
file: null,
filePos: 0,
fileStep: 2048,
@@ -64,7 +55,7 @@ export default {
recvCnt: 0,
username: '',
password: '',
did: '',
devId: '',
columns: [
{
title: 'ID',
@@ -90,7 +81,7 @@ export default {
click: () => {
this.terminal_loading = true;
this.termOn = true;
this.did = params.row.id;
this.devId = params.row.id;
window.setTimeout(this.login, 200);
}
}
@@ -98,62 +89,11 @@ export default {
}
}
],
devlist: [ ],
filelist_title: [
{
title: 'Name',
key: 'name',
render: (h, params) => {
if (params.row.type == 'dir')
return h('div', [
h('Icon', {props: {type: 'folder', color: '#FFE793', size: 20}}),
h('strong', ' ' + params.row.name)
]);
else
return params.row.name;
}
}, {
title: 'Size',
key: 'size',
sortable: true,
render: (h, params) => {
let size = params.row.size;
let unit = 'B';
if (!size)
return;
if (size > 1024 * 1024 * 1024) {
size /= 1024.0 * 1024 * 1024;
unit = 'GB';
} else if (size > 1024 * 1024) {
size /= 1024.0 * 1024;
unit = 'MB';
} else if (size > 1024) {
size /= 1024.0;
unit = 'KB';
}
return size.toFixed(2) + ' ' + unit;
}
}, {
title: 'modification',
key: 'mtim',
sortable: true
}
],
filelist: []
devlist: [ ]
}
},
methods: {
/* ucs-2 string to base64 encoded ascii */
utoa(str) {
return window.btoa(unescape(encodeURIComponent(str)));
},
/* base64 encoded ascii to ucs-2 string */
atou(str) {
return decodeURIComponent(escape(window.atob(str)));
},
beforeUpload (file) {
this.file = file;
this.filePos = 0;
@@ -171,6 +111,7 @@ export default {
this.cancel_upfile = false;
this.modal_loading = true;
var fr = new FileReader();
fr.onload = (e) => {
if (this.cancel_upfile) {
@@ -180,7 +121,9 @@ export default {
return;
}
this.ws.send(fr.result);
let pkt = rtty.newPacket(rtty.RTTY_PACKET_UPFILE, {sid: this.sid, code: 1, data: fr.result});
this.ws.send(pkt);
this.filePos += e.loaded;
if (this.filePos < this.file.size) {
@@ -200,29 +143,18 @@ export default {
}
};
var msg = {
type: 'upfile',
sid: this.sid,
name: this.file.name,
size: this.file.size
};
this.ws.send(JSON.stringify(msg));
window.setTimeout(() => {
this.readFile(fr);
}, 100);
let pkt = rtty.newPacket(rtty.RTTY_PACKET_UPFILE, {sid: this.sid, name: this.file.name, size: this.file.size, code: 0});
this.ws.send(pkt);
this.readFile(fr);
},
cancelUpfile() {
if (!this.modal_loading)
return;
this.cancel_upfile = true;
this.$Message.info("Upload canceled");
var msg = {
type: 'upfile',
sid: this.sid,
err: 'canceled'
};
this.ws.send(JSON.stringify(msg));
let pkt = rtty.newPacket(rtty.RTTY_PACKET_UPFILE, {sid: this.sid, code: 2});
this.ws.send(pkt);
},
showMenu(show) {
if (!this.termOn)
@@ -235,42 +167,9 @@ export default {
this.modal_loading = false;
this.file = null;
},
filelistDblclick(row, index) {
if (row.type == 'dir') {
if (row.name == '..')
this.dl_path.pop();
else
this.dl_path.push(row.name);
var msg = {
type: 'filelist',
sid: this.sid,
name: '/' + this.dl_path.join('/')
};
this.ws.send(JSON.stringify(msg));
} else {
var msg = {
type: 'downfile',
sid: this.sid
};
if (this.dl_path.length > 0)
msg.name = '/' + this.dl_path.join('/') + '/' + row.name;
else
msg.name = '/' + row.name;
this.ws.send(JSON.stringify(msg));
this.filelist_modal = false;
this.$Message.info("TODO");
}
},
downFile () {
this.contextMenuVisible = false;
this.filelist_modal = true;
this.dl_path = [];
var msg = {
type: 'filelist',
sid: this.sid
};
this.ws.send(JSON.stringify(msg));
this.$Message.info("TODO");
},
getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
@@ -301,46 +200,43 @@ export default {
if (location.protocol == 'https://')
protocol = 'wss://';
var ws = new Socket(protocol + location.host + '/ws/browser?did=' + this.did);
ws.on('connect', ()=> {
ws.on('data', (data)=>{
var resp = JSON.parse(data);
var type = resp.type;
var ws = new Socket(protocol + location.host + '/ws?devid=' + this.devId);
ws.on('connect', () => {
ws.on('data', (data) => {
let pkt = rtty.parsePacket(data);
if (type == 'login') {
if (pkt.typ == rtty.RTTY_PACKET_LOGINACK) {
this.terminal_loading = false;
if (resp.err) {
this.$Message.error(resp.err);
if (pkt.code != 0) {
this.$Message.error('Device offline');
this.logout(null, term);
return;
}
this.ws = ws;
this.sid = resp.sid;
term.on('data', (data)=> {
data = JSON.stringify({type: 'data', sid: this.sid, data: this.utoa(data)});
ws.send(data);
this.sid = pkt.sid;
term.on('data', (data) => {
let pkt = rtty.newPacket(rtty.RTTY_PACKET_TTY, {sid: this.sid, data: Buffer.from(data)});
ws.send(pkt);
});
} else if (type == 'data') {
} else if (pkt.typ == rtty.RTTY_PACKET_TTY) {
this.recvCnt++;
var data = this.atou(resp.data);
var data = pkt.data.toString();
if (this.recvCnt < 4) {
if (data.match('login:') && this.username != '') {
data = JSON.stringify({type: 'data', sid: this.sid, data: this.utoa(this.username + '\n')});
ws.send(data);
let pkt = rtty.newPacket(rtty.RTTY_PACKET_TTY, {sid: this.sid, data: this.username + '\n'});
ws.send(pkt);
return;
}
if (data.match('Password:') && this.password != '') {
data = JSON.stringify({type: 'data', sid: this.sid, data: this.utoa(this.password + '\n')});
ws.send(data);
let pkt = rtty.newPacket(rtty.RTTY_PACKET_TTY, {sid: this.sid, data: this.password + '\n'});
ws.send(pkt);
return;
}
}
term.write(data);
} else if (type == 'filelist') {
this.filelist = resp.list;
}
});
@@ -351,7 +247,7 @@ export default {
}
},
mounted() {
var id = this.getQueryString('id');
var devId = this.getQueryString('id');
var username = this.getQueryString('username');
var password = this.getQueryString('password');
@@ -360,15 +256,15 @@ export default {
if (password)
this.password = password;
if (id) {
if (devId) {
this.terminal_loading = true;
this.termOn = true;
this.did = id;
this.devId = devId;
window.setTimeout(this.login, 200);
}
window.setInterval(() => {
axios.get('/list').then((res => {
axios.get('/devs').then((res => {
this.table_loading = false;
this.devlist = res.data;
}));
+98
View File
@@ -0,0 +1,98 @@
export const RTTY_PROTOCOL_VERSION = 1
export const RTTY_PACKET_LOGIN = 1
export const RTTY_PACKET_LOGINACK = 2
export const RTTY_PACKET_LOGOUT = 3
export const RTTY_PACKET_TTY = 4
export const RTTY_PACKET_ANNOUNCE = 5
export const RTTY_PACKET_UPFILE = 6
export const RTTY_ATTR_SID = 1
export const RTTY_ATTR_CODE = 2
export const RTTY_ATTR_DATA = 3
export const RTTY_ATTR_NAME = 4
export const RTTY_ATTR_SIZE = 5
export let parsePacket = function(buf) {
let pkt = { };
pkt.version = buf.readUInt8(0);
pkt.typ = buf.readUInt8(1);
let i = 2;
while (i < buf.length) {
let typ = buf.readUInt8(i);
let length = buf.readUInt16BE(i + 1);
i += 3;
if (typ == RTTY_ATTR_SID)
pkt.sid = buf.toString('utf8', i, i + length - 1);
else if (typ == RTTY_ATTR_CODE)
pkt.code = buf.readUInt8(i);
else if (typ == RTTY_ATTR_DATA)
pkt.data = buf.slice(i, i + length);
i += length;
}
return pkt
}
function addAttr(buf, typ, data) {
let tmp, length
if (typeof data == 'string') {
length = data.length + 1;
tmp = Buffer.alloc(3 + length);
} else {
length = data.byteLength;
tmp = Buffer.alloc(3);
}
tmp.writeUInt8(typ, 0);
tmp.writeUInt16BE(length, 1);
if (typeof data == 'string') {
tmp.write(data, 3);
return Buffer.concat([buf, tmp]);
} else {
return Buffer.concat([buf, tmp, Buffer.from(data)]);
}
}
function addAttrU8(buf, typ, val) {
let tmp = Buffer.alloc(3 + 1);
tmp.writeUInt8(typ, 0);
tmp.writeUInt16BE(1, 1);
tmp.writeUInt8(val, 3);
return Buffer.concat([buf, tmp]);
}
function addAttrU32(buf, typ, val) {
let tmp = Buffer.alloc(3 + 4);
tmp.writeUInt8(typ, 0);
tmp.writeUInt16BE(4, 1);
tmp.writeUInt32BE(val, 3);
return Buffer.concat([buf, tmp]);
}
export let newPacket = function(typ, attr) {
let buf = Buffer.from([RTTY_PROTOCOL_VERSION, typ]);
if (attr.sid)
buf = addAttr(buf, RTTY_ATTR_SID, attr.sid)
if (attr.code)
buf = addAttrU8(buf, RTTY_ATTR_CODE, attr.code)
if (attr.data)
buf = addAttr(buf, RTTY_ATTR_DATA, attr.data)
if (attr.name)
buf = addAttr(buf, RTTY_ATTR_NAME, attr.name)
if (attr.size)
buf = addAttrU32(buf, RTTY_ATTR_SIZE, attr.size)
return buf;
}
Executable
+159
View File
@@ -0,0 +1,159 @@
/*
* Copyright (C) 2017 Jianhui Zhao <jianhuizhao329@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"log"
"time"
"github.com/gorilla/websocket"
)
type Bridge struct {
// Registered devices.
devices map[string]*Client
// Registered users.
sessions map[string]*Session
// Register requests from the clients.
register chan *Client
// Unregister requests from clients.
unregister chan *Client
// Buffered channel of inbound messages.
inbound chan *wsMessage
}
type Session struct {
dev *Client
user *Client
}
func newBridge() *Bridge {
return &Bridge{
register: make(chan *Client),
unregister: make(chan *Client),
devices: make(map[string]*Client),
sessions: make(map[string]*Session),
inbound: make(chan *wsMessage, 100),
}
}
func (br *Bridge) newSession(user *Client) bool {
devid := user.devid
sid := generateSID(devid)
if dev, ok := br.devices[devid]; ok {
br.sessions[sid] = &Session{dev, user}
user.sid = sid
// Write to user
pkt := rttyPacketNew(RTTY_PACKET_LOGINACK)
pkt.PutString(RTTY_ATTR_SID, sid)
pkt.PutU8(RTTY_ATTR_CODE, 0)
user.wsWrite(websocket.BinaryMessage, pkt.Bytes())
// Write to device
pkt = rttyPacketNew(RTTY_PACKET_LOGIN)
pkt.PutString(RTTY_ATTR_SID, sid)
dev.wsWrite(websocket.BinaryMessage, pkt.Bytes())
log.Println("New session:", sid)
return true
} else {
// Write to user
pkt := rttyPacketNew(RTTY_PACKET_LOGINACK)
pkt.PutString(RTTY_ATTR_SID, sid)
pkt.PutU8(RTTY_ATTR_CODE, 1)
user.wsWrite(websocket.BinaryMessage, pkt.Bytes())
log.Println("Device", devid, "offline")
return false
}
}
func delSession(sessions map[string]*Session, sid string) {
if session, ok := sessions[sid]; ok {
delete(sessions, sid)
session.user.wsClose()
log.Println("Delete session: ", sid)
if session.dev != nil {
pkt := rttyPacketNew(RTTY_PACKET_LOGOUT)
pkt.PutString(RTTY_ATTR_SID, sid)
session.dev.wsWrite(websocket.BinaryMessage, pkt.Bytes())
}
}
}
func (br *Bridge) run() {
for {
select {
case client := <- br.register:
if client.isDev {
if dev, ok := br.devices[client.devid]; ok {
pkt := rttyPacketNew(RTTY_PACKET_ANNOUNCE)
pkt.PutU8(RTTY_ATTR_CODE, 1)
dev.wsWrite(websocket.BinaryMessage, pkt.Bytes())
log.Println("ID conflicting:", dev.devid)
} else {
br.devices[client.devid] = client
log.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.unregister:
if client.isDev {
client.wsClose()
if dev, ok := br.devices[client.devid]; ok {
log.Printf("Dead device:id('%s'), description('%s')", dev.devid, dev.description)
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.inbound:
pkt := rttyPacketParse(msg.data)
if session, ok := br.sessions[pkt.sid]; ok {
if (msg.isDev) {
if pkt.typ == RTTY_PACKET_LOGOUT {
session.dev = nil
delSession(br.sessions, pkt.sid)
} else {
session.user.wsWrite(websocket.BinaryMessage, msg.data)
}
} else {
session.dev.wsWrite(websocket.BinaryMessage, msg.data)
}
}
}
}
}
-19
View File
@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIRAOHX+O6tAdK+DavGzdmbuzwwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xODAxMTgxMzE3MjZaFw0xOTAxMTgxMzE3
MjZaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDc8J6dzO9vwR/MvHKnT+OPlGT4fYNqXoYcLnkdRL4YaB7u2HtUFvKD
X5owTXgi/ZB/6H3sl3Xdfrs0I92liSe6EPZz7uvkgBW4rdkUlm51fRoCqcj5haKg
CcFeoLLS2lorbwpvMu3dSWkgkZvJB6tcdN6QhQBZtZsKEuk457tgCIx6elDTVMNJ
RzK0btO6zdhm/sM8dBxmSw3CKtqX+UpkrqN4y4JW3ClFLsxjbaZfxiqXLRxdmaHM
rIOF4RIbiFiwV6OkZFpFDxHZbDhPCNEme9fNFhk30fjgZ2a9EBTxDnp65wxioJdl
M6tJYdQxhBKFellIuri2BjMf1F9ABmf5AgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIF
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeC
FWppYW5odWl6aGFvLmYzMzIyLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEAk4prfaze
lCFvvidWWvnLQaePYsDVdSnFTaatL6xQpYOkmGTUYPTQ2bVSHmS8s8++AjVV1EF8
Q8vEWanWU9kd8ZpD1Xu1HW/onrzL9j42eUdJ6aQP3QmODfJIGMTXUHJLNYNzkoJy
UTpAEpVoIsKLS4xTIWFdLXhIEtBTSLXYy+fJSoMirwoMbQCVbV0+XSlhu/dLDwFE
MAKmFB50Bwpf7PWzJsNnjShHg5Oat22LY1IvV6lW5Sr0lIJOKK4LS7NrTXnPCz5n
ABhhLqJMOypwJT75frjVsDlf9xMKnHczopdlCo+8ePx08JCI+CStXygxIhTdb7rf
WK+qiNcoJgAd2g==
-----END CERTIFICATE-----
Executable
+188
View File
@@ -0,0 +1,188 @@
/*
* Copyright (C) 2017 Jianhui Zhao <jianhuizhao329@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"log"
"time"
"sync"
"errors"
"net/http"
"encoding/base64"
"github.com/gorilla/websocket"
)
const (
// Time allowed to write a message to the peer.
writeWait = 5 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 1 * time.Second
// pings to peer with this period.
pingPeriod = 5 * time.Second
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type wsMessage struct {
isDev bool
msgType int
data []byte
}
// Representing a device or user browser
type Client struct {
br *Bridge
isDev bool
// device description
description string
devid string
// Registration time
timestamp int64
sid string
conn *websocket.Conn
// Buffered channel of outbound messages.
outbound chan *wsMessage
// Avoid repeated closes
mutex sync.Mutex
isClosed bool
closeChan chan byte
}
func (c *Client) wsClose() {
defer c.mutex.Unlock()
c.mutex.Lock()
if !c.isClosed {
c.conn.Close()
c.isClosed = true
close(c.closeChan)
}
}
func (c *Client) unregister() {
c.br.unregister <- c
}
func (c *Client) wsWrite(messageType int, data []byte) error {
select {
case c.outbound <- &wsMessage{c.isDev, messageType, data}:
case <- c.closeChan:
return errors.New("websocket closed")
}
return nil
}
func (c *Client) readPump() {
defer func() {
c.unregister()
}()
c.conn.SetPongHandler(func(string) error {
c.conn.SetReadDeadline(time.Now().Add(time.Hour));
return nil
})
for {
msgType, data, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
if msgType == websocket.BinaryMessage {
msg := &wsMessage{c.isDev, msgType, data}
select {
case c.br.inbound <- msg:
case <- c.closeChan:
return
}
}
}
}
func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.unregister()
}()
for {
select {
case msg := <- c.outbound:
if err := c.conn.WriteMessage(msg.msgType, msg.data); err != nil {
return
}
case <- ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
c.conn.SetReadDeadline(time.Now().Add(pongWait));
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
case <- c.closeChan:
return
}
}
}
/* serveWs handles websocket requests from the peer. */
func serveWs(br *Bridge, w http.ResponseWriter, r *http.Request) {
devid := r.URL.Query().Get("devid")
if devid == "" {
log.Println("devid required")
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := &Client{
br: br,
devid: devid,
conn: conn,
timestamp: time.Now().Unix(),
outbound: make(chan *wsMessage, 100),
closeChan: make(chan byte),
isClosed: false,
}
isDev := r.URL.Query().Get("device")
if isDev == "1" {
client.isDev = true
description, _ := base64.StdEncoding.DecodeString(r.URL.Query().Get("description"))
client.description = string(description)
}
client.br.register <- client
go client.readPump()
go client.writePump()
}
-8
View File
@@ -1,8 +0,0 @@
package: github.com/zhaojh329/rttys
import:
- package: github.com/gorilla/websocket
version: v1.2.0
- package: github.com/rakyll/statik
version: v0.1.1
subpackages:
- fs
-27
View File
@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3PCenczvb8EfzLxyp0/jj5Rk+H2Dal6GHC55HUS+GGge7th7
VBbyg1+aME14Iv2Qf+h97Jd13X67NCPdpYknuhD2c+7r5IAVuK3ZFJZudX0aAqnI
+YWioAnBXqCy0tpaK28KbzLt3UlpIJGbyQerXHTekIUAWbWbChLpOOe7YAiMenpQ
01TDSUcytG7Tus3YZv7DPHQcZksNwiral/lKZK6jeMuCVtwpRS7MY22mX8Yqly0c
XZmhzKyDheESG4hYsFejpGRaRQ8R2Ww4TwjRJnvXzRYZN9H44GdmvRAU8Q56eucM
YqCXZTOrSWHUMYQShXpZSLq4tgYzH9RfQAZn+QIDAQABAoIBACaeDCtqyaMP7336
plOmPBE5j9h7H8A0214LpXG/NTbQ5C89OXk+NY9O44Vz1P7QsTQaMABtjNV1NEVt
y20GpyH3mwwyg5rB5IoOIPZfQNlaT3SNtqoeeIleiyT+E6OVNNOIqhs7nKXkEfL2
YHNn2sTjTI+YH7KOcpX1LjybDH5BTQ1+3oWEI8N8NmT8o8KwrhiSyzVKev+VjfTB
igDGSZydYsxGJwqDOw4vE+nitJlrrDdyK9NqJBrfo9noDQ4liqMtmbeaZPXkF0ZT
e4tET+rDhUUVOnHRHsgmh7ua935aGD8453AtU/kG8J8gvJvcwZMWoQ3wHBfoW7pY
+/7otJ0CgYEA+EEWirGQvIt3gz7VBWY6OEzHXfmgc2hnIWWG4TvBrz920bRsiLI+
IbrPG/S0APSA4XbZvrWXxxksA3tS5mnT23nae7GyT6JUDjeVvmKG1+mQFhzf1LjU
QTVVhMVZfiLR8uL+5ib5BBBEHUnGMf+F11vME2zLDbyAZwfUxuV6jRsCgYEA49Vc
Q7iI+c9mYf7iwayB6HAxXct4rHRNPSTkizLG5RNoD2lNfeKx4mkjjDTrKMHdQygY
HUevzVBwA2R2/w9l8IlUK+vmKuZ3leo6u9WrwsX+6InfnICzBBfr6l4Y/IzmL0nA
WsWpIvTcvsiInvxwOFmz5sbV8bqlj/YDV4vDlHsCgYB/FWrBsyZhro+Oq0KHUg6p
tXw1uk0N5zssGHoUoO+Ek9tyfIc27u/pemT9baYb+w5i3OGYxAMfk945Jg+JDlG6
v8U9nvil1XZsFL5eSLe4ncL53uwiTD+z5eaSYihu7spFKTjNftPo5Z7I03RXMfRF
C6QLqZzEumOM2rBs5Rp4UwKBgQC5KftNcpmoZX3xIOULwHboAN6uOyK9DdyEWAqC
cucg4A/PjqaTK/2kZAym8483Va/M17YZfdyMQdZ3e1wW2V3hCstk0tkCfsIsDexk
IjjxGFeeiAc7O4HCTurGVGl8P8sIHal+XGyk41rQl0wp4kIWGhlS8sCIMEhEYw2+
RfHTJwKBgQCbqu1pd9jbTsGmfHUeWTqtb3m5mKFFmCxCA/haZKz6I8DfRYlzZwrt
M5HSkYpnF7ZR8SRTIJlXfJsGt4cZN4GsTi2sUH+Q5EOUeL3VzG8gXmWn3L4i9q6l
6hSddhzgC2WDGT8Mv8Cy90tU7HZ+DB1OjDM0ObF/f/jJKw+gK6YQfg==
-----END RSA PRIVATE KEY-----
Executable
+92
View File
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2017 Jianhui Zhao <jianhuizhao329@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"flag"
"log"
"fmt"
"time"
"strconv"
"math/rand"
"net/http"
"crypto/md5"
"encoding/hex"
"encoding/json"
_ "github.com/zhaojh329/rttys/statik"
"github.com/rakyll/statik/fs"
)
type DeviceInfo struct {
ID string `json:"id"`
Uptime int64 `json:"uptime"`
Description string `json:"description"`
}
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 main() {
port := flag.Int("port", 5912, "http service port")
cert := flag.String("cert", "", "certFile Path")
key := flag.String("key", "", "keyFile Path")
flag.Parse()
rand.Seed(time.Now().Unix())
br := newBridge()
go br.run()
statikFS, err := fs.New()
if err != nil {
log.Fatal(err)
return
}
http.Handle("/", http.FileServer(statikFS))
http.HandleFunc("/devs", func(w http.ResponseWriter, r *http.Request) {
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)
}
}
js, _ := json.Marshal(devs)
fmt.Fprintf(w, "%s", js)
})
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(br, w, r)
})
if *cert != "" && *key != "" {
log.Println("Listen on: ", *port, "SSL on")
log.Fatal(http.ListenAndServeTLS(":" + strconv.Itoa(*port), *cert, *key, nil))
} else {
log.Println("Listen on: ", *port, "SSL off")
log.Fatal(http.ListenAndServe(":" + strconv.Itoa(*port), nil))
}
}
Executable
+172
View File
@@ -0,0 +1,172 @@
/*
* Copyright (C) 2017 Jianhui Zhao <jianhuizhao329@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"log"
"bytes"
"encoding/binary"
)
const RTTY_PROTOCOL_VERSION = 1
const (
_ = iota
RTTY_PACKET_LOGIN
RTTY_PACKET_LOGINACK
RTTY_PACKET_LOGOUT
RTTY_PACKET_TTY
RTTY_PACKET_ANNOUNCE
RTTY_PACKET_UPFILE
)
const (
_ = iota
RTTY_ATTR_SID
RTTY_ATTR_CODE
RTTY_ATTR_DATA
RTTY_ATTR_NAME
RTTY_ATTR_SIZE
)
type RttyPacketInfo struct {
version byte
typ byte
sid string
code byte
description string
data []byte
name string
size uint32
}
type RttyPacket struct {
w *bytes.Buffer
}
func (pkt *RttyPacket)Put(typ byte, length uint16, data []byte) {
pkt.w.WriteByte(typ)
binary.Write(pkt.w, binary.BigEndian, length)
pkt.w.Write(data)
}
func (pkt *RttyPacket)PutU8(typ byte, val byte) {
pkt.w.WriteByte(typ)
binary.Write(pkt.w, binary.BigEndian, uint16(1))
binary.Write(pkt.w, binary.BigEndian, val)
}
func (pkt *RttyPacket)PutU16(typ byte, val uint16) {
pkt.w.WriteByte(typ)
binary.Write(pkt.w, binary.BigEndian, uint16(2))
binary.Write(pkt.w, binary.BigEndian, val)
}
func (pkt *RttyPacket)PutU32(typ byte, val uint32) {
pkt.w.WriteByte(typ)
binary.Write(pkt.w, binary.BigEndian, uint16(4))
binary.Write(pkt.w, binary.BigEndian, val)
}
func (pkt *RttyPacket)PutString(typ byte, str string) {
b := []byte(str)
b = append(b, 0)
pkt.Put(typ, uint16(len(str) + 1), b)
}
func (pkt *RttyPacket)Bytes() []byte {
return pkt.w.Bytes()
}
func (pkt *RttyPacket)Init(typ byte) {
pkt.w.Reset()
pkt.w.WriteByte(RTTY_PROTOCOL_VERSION)
pkt.w.WriteByte(typ)
}
func (pkt *RttyPacketInfo)Dump() {
log.Println("version: ", pkt.version)
log.Println("type: ", pkt.typ)
log.Println("sid: ", pkt.sid)
log.Println("code: ", pkt.code)
if pkt.typ == RTTY_PACKET_UPFILE {
log.Println("size: ", pkt.size)
log.Println("name: ", pkt.name)
log.Println("data: ", pkt.data)
}
if pkt.typ == RTTY_PACKET_TTY {
log.Println("data: ", string(pkt.data))
}
}
func rttyPacketNew(typ byte) *RttyPacket {
pkt := new(RttyPacket)
pkt.w = new(bytes.Buffer)
pkt.w.WriteByte(RTTY_PROTOCOL_VERSION)
pkt.w.WriteByte(typ)
return pkt
}
func rttyPacketGetString(b *bytes.Buffer, length uint16) string {
data := make([]byte, length - 1)
b.Read(data)
b.ReadByte()
return string(data)
}
func rttyPacketParse(data []byte) *RttyPacketInfo {
info := new(RttyPacketInfo)
info.version = data[0]
info.typ = data[1]
b := bytes.NewBuffer(data[2:])
for {
var typ byte
var length uint16
var err error
typ, err = b.ReadByte()
if err != nil {
break
}
err = binary.Read(b, binary.BigEndian, &length)
if err != nil {
break
}
if typ == RTTY_ATTR_SID {
info.sid = rttyPacketGetString(b, length)
} else if typ == RTTY_ATTR_DATA {
info.data = make([]byte, length)
b.Read(info.data)
} else if typ == RTTY_ATTR_CODE {
info.code, _ = b.ReadByte()
} else if typ == RTTY_ATTR_NAME {
info.name = rttyPacketGetString(b, length)
} else if typ == RTTY_ATTR_SIZE {
binary.Read(b, binary.BigEndian, &info.size)
}
}
return info
}
-323
View File
@@ -1,323 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"sync"
"time"
"errors"
"strconv"
"net/http"
"math/rand"
"log/syslog"
"crypto/md5"
"encoding/hex"
"encoding/json"
"github.com/gorilla/websocket"
_ "github.com/zhaojh329/rttys/statik"
"github.com/rakyll/statik/fs"
)
var cross *bool
var verbose *bool
var slog *log.Logger
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
var dev2wsConnection = make(map[string] *wsConnection)
var sid2wsConnection = make(map[string] *wsConnection)
const (
FromDevice = 0
FromBrowser = 1
)
type RttyFrame struct {
Type string `json:"type"`
SID string `json:"sid"`
Data string `json:"data"`
Err string `json:"err"`
}
/* Used for /list */
type DeviceInfo struct {
ID string `json:"id"`
Uptime int64 `json:"uptime"`
Description string `json:"description"`
}
type wsMessage struct {
msgType int
data []byte
}
type wsConnection struct {
from int
contime int64 /* connect time */
did string
sid string /* only valid for from browser */
active int /* only valid for from device */
description string /* only valid for from device */
ws *websocket.Conn
inChan chan *wsMessage
outChan chan *wsMessage
mutex sync.Mutex
isClosed bool
closeChan chan byte
}
func generateSID(did string) string {
md5Ctx := md5.New()
md5Ctx.Write([]byte(did + strconv.FormatFloat(rand.Float64(), 'e', 6, 32)))
cipherStr := md5Ctx.Sum(nil)
return hex.EncodeToString(cipherStr)
}
func (wsConn *wsConnection)wsClose() {
if wsConn.from == FromBrowser {
if devCon, ok := dev2wsConnection[wsConn.did]; ok {
f := &RttyFrame{Type: "logout", SID: wsConn.sid}
js, _ := json.Marshal(f)
devCon.wsWrite(websocket.TextMessage, js)
}
} else {
if *verbose {
fmt.Println("Device ", wsConn.did, "offline")
}
slog.Println("Device ", wsConn.did, "offline")
delete(dev2wsConnection, wsConn.did)
}
wsConn.ws.Close()
defer wsConn.mutex.Unlock()
wsConn.mutex.Lock()
if !wsConn.isClosed {
wsConn.isClosed = true
close(wsConn.closeChan)
}
}
func (wsConn *wsConnection)wsWriteLoop() {
for {
select {
case msg := <- wsConn.outChan:
if err := wsConn.ws.WriteMessage(msg.msgType, msg.data); err != nil {
goto error
}
case <- wsConn.closeChan:
goto closed
}
}
error:
wsConn.wsClose()
closed:
}
func (wsConn *wsConnection)wsReadLoop() {
for {
msgType, data, err := wsConn.ws.ReadMessage()
if err != nil {
goto error
}
req := &wsMessage{msgType, data}
select {
case wsConn.inChan <- req:
case <- wsConn.closeChan:
goto closed
}
}
error:
wsConn.wsClose()
closed:
}
func (wsConn *wsConnection)wsWrite(messageType int, data []byte) error {
select {
case wsConn.outChan <- &wsMessage{messageType, data}:
case <- wsConn.closeChan:
return errors.New("websocket closed")
}
return nil
}
func (wsConn *wsConnection)wsRead() (*wsMessage, error) {
select {
case msg := <- wsConn.inChan:
return msg, nil
case <- wsConn.closeChan:
}
return nil, errors.New("websocket closed")
}
func (wsConn *wsConnection)procLoop() {
for {
msg, err := wsConn.wsRead()
if err != nil {
break
}
if msg.msgType == websocket.TextMessage {
f := &RttyFrame{}
json.Unmarshal(msg.data, f)
if wsConn.from == FromDevice {
if f.Type == "data" || f.Type == "filelist" {
if bwCon, ok := sid2wsConnection[f.SID]; ok {
bwCon.wsWrite(websocket.TextMessage, msg.data)
}
} else if f.Type == "logout" {
if bwCon, ok := sid2wsConnection[f.SID]; ok {
bwCon.wsClose();
}
}
} else {
if f.Type == "data" || f.Type == "upfile" || f.Type == "filelist" || f.Type == "downfile" {
if devCon, ok := dev2wsConnection[wsConn.did]; ok {
devCon.wsWrite(msg.msgType, msg.data)
}
}
}
} else if msg.msgType == websocket.BinaryMessage {
if wsConn.from == FromBrowser {
if devCon, ok := dev2wsConnection[wsConn.did]; ok {
devCon.wsWrite(msg.msgType, msg.data)
}
}
}
}
}
func serveWs(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
description := r.URL.Query().Get("des")
did := r.URL.Query().Get("did")
if did == "" {
return
}
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
slog.Println("upgrade:", err)
return
}
wsConn := &wsConnection{
from: FromDevice,
contime: time.Now().Unix(),
did: did,
ws: ws,
inChan: make(chan *wsMessage, 1000),
outChan: make(chan *wsMessage, 1000),
closeChan: make(chan byte),
isClosed: false,
}
if path == "/ws/device" {
if _, ok := dev2wsConnection[did]; ok {
f := &RttyFrame{Type: "add", Err: "ID conflicts"}
js, _ := json.Marshal(f)
ws.WriteMessage(websocket.TextMessage, js)
ws.Close()
return
}
wsConn.description = description
wsConn.active = 3
dev2wsConnection[did] = wsConn
slog.Println("New Device:", did)
if *verbose {
fmt.Println("New Device:", did)
}
} else {
wsConn.from = FromBrowser
f := RttyFrame{Type: "login"}
devCon, ok := dev2wsConnection[did]
if !ok {
f.Err = "Device off-line"
js, _ := json.Marshal(f)
ws.WriteMessage(websocket.TextMessage, js)
ws.Close()
} else {
/* Login */
sid := generateSID(did)
sid2wsConnection[sid] = wsConn
wsConn.sid = sid
f.SID = sid
js, _ := json.Marshal(f)
ws.WriteMessage(websocket.TextMessage, js)
devCon.wsWrite(websocket.TextMessage, js)
}
}
go wsConn.procLoop()
go wsConn.wsReadLoop()
go wsConn.wsWriteLoop()
}
func handlerList(w http.ResponseWriter, r *http.Request) {
devs := make([]DeviceInfo, 0)
for k, con := range dev2wsConnection {
d := DeviceInfo{k, time.Now().Unix() - con.contime, con.description}
devs = append(devs, d)
}
js, _ := json.Marshal(devs)
if *cross {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("content-type", "application/json")
}
fmt.Fprintf(w, "%s", js)
}
func main() {
port := flag.Int("port", 5912, "http service port")
cert := flag.String("cert", "", "certFile Path")
key := flag.String("key", "", "keyFile Path")
cross = flag.Bool("cross", false, "Allow Cross domain")
verbose = flag.Bool("v", false, "Verbose")
flag.Parse()
rand.Seed(time.Now().Unix())
_slog, err := syslog.New(syslog.LOG_INFO, "rttys")
if err != nil {
log.Fatal(err)
return
}
defer _slog.Close()
slog = log.New(_slog, "", log.Lshortfile | log.LstdFlags)
statikFS, err := fs.New()
if err != nil {
slog.Fatal(err)
return
}
http.HandleFunc("/ws/device", serveWs)
http.HandleFunc("/ws/browser", serveWs)
http.HandleFunc("/list", handlerList)
http.Handle("/", http.FileServer(statikFS))
if *cert != "" && *key != "" {
slog.Println("Listen on: ", *port, "SSL on")
if *verbose {
fmt.Println("Listen on: ", *port, "SSL on")
}
slog.Fatal(http.ListenAndServeTLS(":" + strconv.Itoa(*port), *cert, *key, nil))
} else {
slog.Println("Listen on: ", *port, "SSL off")
if *verbose {
fmt.Println("Listen on: ", *port, "SSL off")
}
slog.Fatal(http.ListenAndServe(":" + strconv.Itoa(*port), nil))
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long