mirror of
https://github.com/netfun2000/rttys_zhaojh329.git
synced 2026-02-27 09:53:24 +08:00
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:
+36
-140
@@ -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;
|
||||
}));
|
||||
|
||||
Executable
+98
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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-----
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user