Initial commit
This commit is contained in:
+117
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
Container,
|
||||
AuthenticationRequest,
|
||||
AuthenticationResponse,
|
||||
Message
|
||||
} from './Protocol.js'
|
||||
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
export class Peer extends EventEmitter {
|
||||
#id = 0
|
||||
#socket = null
|
||||
#msgMap = null
|
||||
#salt = null
|
||||
#address = null
|
||||
|
||||
constructor({ socket, salt }) {
|
||||
super()
|
||||
this.#socket = socket
|
||||
this.#salt = salt
|
||||
|
||||
this.#msgMap = new Map()
|
||||
this.#msgMap.set('AuthenticationRequest', this.#onauth)
|
||||
this.#msgMap.set('Message', this.#onmessage)
|
||||
|
||||
this.#setupListeners()
|
||||
}
|
||||
|
||||
#setupListeners() {
|
||||
this.#socket.on('message', data => {
|
||||
let message
|
||||
|
||||
try {
|
||||
message = Container.decode(data)
|
||||
} catch(err) {
|
||||
console.error('(!) malformed message, disconnecting')
|
||||
this.#socket.close()
|
||||
return
|
||||
}
|
||||
|
||||
const container = Container.toObject(message, {
|
||||
oneofs: true
|
||||
})
|
||||
|
||||
const handler = this.#msgMap.get(container.message)
|
||||
if(handler) {
|
||||
handler.call(this, container)
|
||||
} else {
|
||||
console.error(`(!) unknown handler for ${container.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
this.on('incoming', ({ source, message }) => {
|
||||
this.#send({
|
||||
id: this.#id++,
|
||||
message: Message.create({
|
||||
address: source.address.toString(10),
|
||||
port: message.Message.port,
|
||||
data: message.Message.data
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#send({ id, message }) {
|
||||
const container = Container.create({
|
||||
id,
|
||||
[message.constructor.name]: message
|
||||
})
|
||||
|
||||
const buf = Container.encode(container).finish()
|
||||
console.log('sending:', buf)
|
||||
this.#socket.send(buf)
|
||||
}
|
||||
|
||||
#onauth(message) {
|
||||
const { seed } = AuthenticationRequest.toObject(message.AuthenticationRequest, { longs: String })
|
||||
if(this.#address !== null) {
|
||||
this.#send({
|
||||
id: message.id,
|
||||
message: AuthenticationResponse.create({
|
||||
status: 1,
|
||||
address: this.#address
|
||||
})
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const hash = createHash('sha256').update(this.#salt + seed).digest('hex')
|
||||
const address = BigInt('0x' + hash.substring(0, 16))
|
||||
console.log('assigned address', address.toString(16).match(/.{1,4}/g).join(':'))
|
||||
this.#address = address
|
||||
|
||||
console.log(address.toString(10))
|
||||
this.#send({
|
||||
id: message.id,
|
||||
message: AuthenticationResponse.create({
|
||||
status: 0,
|
||||
address: address.toString(10)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#onmessage(message) {
|
||||
const { address } = Message.toObject(message.Message, { longs: String })
|
||||
this.emit('outgoing', {
|
||||
destination: BigInt(address),
|
||||
message
|
||||
})
|
||||
}
|
||||
|
||||
get address() {
|
||||
return this.#address
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import protobuf from 'protobufjs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
const import_proto = async (path, types) => {
|
||||
const proto = await protobuf.load(`src/protocol/protobuf/${path}`)
|
||||
return types.map(type => proto.lookupType(type))
|
||||
}
|
||||
|
||||
export const [
|
||||
Container
|
||||
] = await import_proto('2.0/Container.proto', ['Container'])
|
||||
|
||||
export const [
|
||||
AuthenticationRequest,
|
||||
AuthenticationResponse
|
||||
] = await import_proto('2.0/Authentication.proto', ['AuthenticationRequest', 'AuthenticationResponse'])
|
||||
|
||||
export const [
|
||||
Message
|
||||
] = await import_proto('2.0/Message.proto', ['Message'])
|
||||
|
||||
export const [
|
||||
PublishRequest,
|
||||
PublishResponse,
|
||||
DiscoverRequest,
|
||||
DiscoverResponse
|
||||
] = await import_proto('2.0/PublishDiscover.proto', ['PublishRequest', 'PublishResponse', 'DiscoverRequest', 'DiscoverResponse'])
|
||||
|
||||
export const messages = [
|
||||
Container,
|
||||
AuthenticationRequest,
|
||||
AuthenticationResponse,
|
||||
Message,
|
||||
PublishRequest,
|
||||
PublishResponse,
|
||||
DiscoverRequest,
|
||||
DiscoverResponse
|
||||
]
|
||||
@@ -0,0 +1,72 @@
|
||||
import { WebSocketServer } from 'ws'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { Peer } from './Peer.js'
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
zlibDeflateOptions: {
|
||||
chunkSize: 1024,
|
||||
memLevel: 7,
|
||||
level: 3
|
||||
},
|
||||
zlibInflateOptions: {
|
||||
chunkSize: 10 * 1024
|
||||
},
|
||||
clientNoContextTakeover: true,
|
||||
serverNoContextTakeover: true,
|
||||
serverMaxWindowBits: 10,
|
||||
concurrencyLimit: 10,
|
||||
threshold: 1024
|
||||
}
|
||||
|
||||
export class Server {
|
||||
#salt = null
|
||||
#wss = null
|
||||
#options = Object.create(null)
|
||||
#peers = []
|
||||
|
||||
constructor({ deflate = false, salt }) {
|
||||
if(deflate) {
|
||||
this.#options.perMessageDeflate = DEFAULT_OPTIONS
|
||||
}
|
||||
this.#salt = salt
|
||||
}
|
||||
|
||||
#randomizeAddress() {
|
||||
let address
|
||||
|
||||
while(!address || this.#peers.find(peer => peer.address.compare(address) === 0)) {
|
||||
address = randomBytes(8)
|
||||
}
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
listen(port) {
|
||||
const wss = new WebSocketServer({
|
||||
...this.#options,
|
||||
port,
|
||||
})
|
||||
|
||||
wss.on('connection', socket => {
|
||||
const peer = new Peer({
|
||||
salt: this.#salt,
|
||||
socket
|
||||
})
|
||||
|
||||
peer.on('outgoing', ({ destination, message }) => {
|
||||
const destPeer = this.#peers.find(p => p.address === destination)
|
||||
if(destPeer) {
|
||||
destPeer.emit('incoming', { source: peer, message })
|
||||
}
|
||||
})
|
||||
|
||||
this.#peers.push(peer)
|
||||
|
||||
socket.on('close', () => {
|
||||
this.#peers = this.#peers.filter(p => p !== peer)
|
||||
})
|
||||
})
|
||||
|
||||
this.#wss = wss
|
||||
}
|
||||
}
|
||||
Submodule
+1
Submodule src/protocol added at d0747faf68
Reference in New Issue
Block a user