Compare commits
20 Commits
7bf03b02ae
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fd717d75e | ||
|
|
126f8d0ba6 | ||
|
|
862c52f567 | ||
|
|
e929b5af1e | ||
|
|
079fd61390 | ||
|
|
2fffb905e3 | ||
|
|
0da0701d34 | ||
|
|
0408433fc3 | ||
|
|
1acf98ef54 | ||
|
|
880fd39ea9 | ||
|
|
9c26df784d | ||
|
|
d1d7a4a940 | ||
|
|
16757236c6 | ||
|
|
c50ed2b233 | ||
|
|
6cb858f7d2 | ||
|
|
6a284b89a9 | ||
|
|
be53922861 | ||
|
|
3c5e90d83e | ||
|
|
850baf67cd | ||
|
|
02f64a39d9 |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/FFmpeg
|
||||||
|
/ogg
|
||||||
|
/vorbis
|
||||||
|
wsrA
|
||||||
|
support.wasm
|
||||||
|
support.js
|
||||||
|
*.jpg
|
||||||
10
Makefile
10
Makefile
@@ -3,6 +3,12 @@
|
|||||||
EMSCR := $(shell command -v emcmake 2> /dev/null)
|
EMSCR := $(shell command -v emcmake 2> /dev/null)
|
||||||
LLVM_AR := $(shell command -v llvm-ar 2> /dev/null)
|
LLVM_AR := $(shell command -v llvm-ar 2> /dev/null)
|
||||||
|
|
||||||
|
SYS := $(shell $(CC) -dumpmachine)
|
||||||
|
|
||||||
|
ifneq (,$(findstring mingw,$(SYS)))
|
||||||
|
wsrALDFLAGS := -lws2_32
|
||||||
|
endif
|
||||||
|
|
||||||
emscr:
|
emscr:
|
||||||
ifndef EMSCR
|
ifndef EMSCR
|
||||||
$(error "Emscripten is not in PATH.")
|
$(error "Emscripten is not in PATH.")
|
||||||
@@ -26,8 +32,8 @@ FFmpeg/libswscale/libswscale.a: emscr
|
|||||||
llvm-ar d FFmpeg/libavutil/libavutil.a log2_tab.o
|
llvm-ar d FFmpeg/libavutil/libavutil.a log2_tab.o
|
||||||
llvm-ar d FFmpeg/libavcodec/libavcodec.a reverse.o
|
llvm-ar d FFmpeg/libavcodec/libavcodec.a reverse.o
|
||||||
|
|
||||||
wsrA: main.c mongoose.c
|
wsrA: main2.c
|
||||||
cc -s -O3 -D_GNU_SOURCE -o wsrA main.c mongoose.c
|
$(CC) -s -O3 -D_GNU_SOURCE -D_WIN32_WINNT=0x600 -o wsrA main2.c picohttpparser.c base64.c $(wsrALDFLAGS)
|
||||||
|
|
||||||
support.js: emscr ogg/libogg.a vorbis/lib/libvorbis.a FFmpeg/libswscale/libswscale.a
|
support.js: emscr ogg/libogg.a vorbis/lib/libvorbis.a FFmpeg/libswscale/libswscale.a
|
||||||
emcc -o support -fPIC -flto -IFFmpeg -Iogg/include -Ivorbis/include -LFFmpeg/libavcodec -l:libavcodec.a -LFFmpeg/libswscale -l:libswscale.a -LFFmpeg/libavutil -l:libavutil.a -Lvorbis/lib -l:libvorbis.a -Logg -l:libogg.a support.c -pthread -msimd128 -O3 -sMAYBE_WASM2JS -sUSE_PTHREADS=1 -sEXPORT_ALL=1 -sMAIN_MODULE=1 -sTOTAL_MEMORY=128MB
|
emcc -o support -fPIC -flto -IFFmpeg -Iogg/include -Ivorbis/include -LFFmpeg/libavcodec -l:libavcodec.a -LFFmpeg/libswscale -l:libswscale.a -LFFmpeg/libavutil -l:libavutil.a -Lvorbis/lib -l:libvorbis.a -Logg -l:libogg.a support.c -pthread -msimd128 -O3 -sMAYBE_WASM2JS -sUSE_PTHREADS=1 -sEXPORT_ALL=1 -sMAIN_MODULE=1 -sTOTAL_MEMORY=128MB
|
||||||
|
|||||||
11
README.md
Normal file
11
README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Matroska over WebSocket Streaming
|
||||||
|
|
||||||
|
This repository actually holds three programs, so be careful to not lost in the saurce.
|
||||||
|
|
||||||
|
* After `make` completes, `wsrA` is the relay program, which can be run as so: `./wsrA port=12345 key=MyFancyStreamingKeyIsOVERHERE`.
|
||||||
|
* Using the above example, one should stream to the HTTP path `/push/MyFancyStreamingKeyIsOVERHERE`. All other paths are expected to be used by WebSocket clients.
|
||||||
|
* If using a reverse proxy (highly recommended), make sure to disable request buffering and the maximum request size. For nginx, you want `proxy_request_buffering off; client_max_body_size 0;`.
|
||||||
|
* `index.html`, `blarf.js`, `blarfwork.js`, `support.js`, `support.wasm` and `rawpcmworklet.js` are the frontend, which must be accessible to the browser.
|
||||||
|
* You may insert a file named `intermission.jpg` that is shown when the stream is offline.
|
||||||
|
* Of course you'll have to change the feed and chat endpoints in the `index.html`, if you don't want my stream.
|
||||||
|
* You may also disable chat by setting `ENABLE_CHAT` to `false`.
|
||||||
164
base64.c
Normal file
164
base64.c
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/* This is a public domain base64 implementation written by WEI Zhicheng. */
|
||||||
|
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
#define BASE64_PAD '='
|
||||||
|
#define BASE64DE_FIRST '+'
|
||||||
|
#define BASE64DE_LAST 'z'
|
||||||
|
|
||||||
|
/* BASE 64 encode table */
|
||||||
|
static const char base64en[] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ASCII order for BASE 64 decode, 255 in unused character */
|
||||||
|
static const unsigned char base64de[] = {
|
||||||
|
/* nul, soh, stx, etx, eot, enq, ack, bel, */
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* bs, ht, nl, vt, np, cr, so, si, */
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* dle, dc1, dc2, dc3, dc4, nak, syn, etb, */
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* can, em, sub, esc, fs, gs, rs, us, */
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* sp, '!', '"', '#', '$', '%', '&', ''', */
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* '(', ')', '*', '+', ',', '-', '.', '/', */
|
||||||
|
255, 255, 255, 62, 255, 255, 255, 63,
|
||||||
|
|
||||||
|
/* '0', '1', '2', '3', '4', '5', '6', '7', */
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59,
|
||||||
|
|
||||||
|
/* '8', '9', ':', ';', '<', '=', '>', '?', */
|
||||||
|
60, 61, 255, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', */
|
||||||
|
255, 0, 1, 2, 3, 4, 5, 6,
|
||||||
|
|
||||||
|
/* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', */
|
||||||
|
7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
|
||||||
|
/* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', */
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
|
||||||
|
/* 'X', 'Y', 'Z', '[', '\', ']', '^', '_', */
|
||||||
|
23, 24, 25, 255, 255, 255, 255, 255,
|
||||||
|
|
||||||
|
/* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', */
|
||||||
|
255, 26, 27, 28, 29, 30, 31, 32,
|
||||||
|
|
||||||
|
/* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', */
|
||||||
|
33, 34, 35, 36, 37, 38, 39, 40,
|
||||||
|
|
||||||
|
/* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', */
|
||||||
|
41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
|
||||||
|
/* 'x', 'y', 'z', '{', '|', '}', '~', del, */
|
||||||
|
49, 50, 51, 255, 255, 255, 255, 255
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned int
|
||||||
|
base64_encode(const unsigned char *in, unsigned int inlen, char *out)
|
||||||
|
{
|
||||||
|
int s;
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int j;
|
||||||
|
unsigned char c;
|
||||||
|
unsigned char l;
|
||||||
|
|
||||||
|
s = 0;
|
||||||
|
l = 0;
|
||||||
|
for (i = j = 0; i < inlen; i++) {
|
||||||
|
c = in[i];
|
||||||
|
|
||||||
|
switch (s) {
|
||||||
|
case 0:
|
||||||
|
s = 1;
|
||||||
|
out[j++] = base64en[(c >> 2) & 0x3F];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
s = 2;
|
||||||
|
out[j++] = base64en[((l & 0x3) << 4) | ((c >> 4) & 0xF)];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
s = 0;
|
||||||
|
out[j++] = base64en[((l & 0xF) << 2) | ((c >> 6) & 0x3)];
|
||||||
|
out[j++] = base64en[c & 0x3F];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
l = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (s) {
|
||||||
|
case 1:
|
||||||
|
out[j++] = base64en[(l & 0x3) << 4];
|
||||||
|
out[j++] = BASE64_PAD;
|
||||||
|
out[j++] = BASE64_PAD;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
out[j++] = base64en[(l & 0xF) << 2];
|
||||||
|
out[j++] = BASE64_PAD;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[j] = 0;
|
||||||
|
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int
|
||||||
|
base64_decode(const char *in, unsigned int inlen, unsigned char *out)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int j;
|
||||||
|
unsigned char c;
|
||||||
|
|
||||||
|
if (inlen & 0x3) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = j = 0; i < inlen; i++) {
|
||||||
|
if (in[i] == BASE64_PAD) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = base64de[(unsigned char)in[i]];
|
||||||
|
if (c == 255) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (i & 0x3) {
|
||||||
|
case 0:
|
||||||
|
out[j] = (c << 2) & 0xFF;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
out[j++] |= (c >> 4) & 0x3;
|
||||||
|
out[j] = (c & 0xF) << 4;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
out[j++] |= (c >> 2) & 0xF;
|
||||||
|
out[j] = (c & 0x3) << 6;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
out[j++] |= c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return j;
|
||||||
|
}
|
||||||
20
base64.h
Normal file
20
base64.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef BASE64_H
|
||||||
|
#define BASE64_H
|
||||||
|
|
||||||
|
#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1))
|
||||||
|
#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* out is null-terminated encode string.
|
||||||
|
* return values is out length, exclusive terminating `\0'
|
||||||
|
*/
|
||||||
|
unsigned int
|
||||||
|
base64_encode(const unsigned char *in, unsigned int inlen, char *out);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* return values is out length
|
||||||
|
*/
|
||||||
|
unsigned int
|
||||||
|
base64_decode(const char *in, unsigned int inlen, unsigned char *out);
|
||||||
|
|
||||||
|
#endif /* BASE64_H */
|
||||||
211
blarf.js
211
blarf.js
@@ -2,32 +2,75 @@
|
|||||||
var VideoQueue = []
|
var VideoQueue = []
|
||||||
var AudioQueue = []
|
var AudioQueue = []
|
||||||
|
|
||||||
|
class DynamicTypedArray {
|
||||||
|
constructor(type) {
|
||||||
|
this.type = type
|
||||||
|
this.backend = new type(1024)
|
||||||
|
this.length = 0
|
||||||
|
}
|
||||||
|
add(b) {
|
||||||
|
if(this.length + b.length > this.backend.length) {
|
||||||
|
var newlen = this.backend.length
|
||||||
|
while(this.length + b.length > newlen) { newlen = newlen * 2 }
|
||||||
|
var be2 = new this.type(newlen)
|
||||||
|
be2.set(this.backend, 0)
|
||||||
|
this.backend = be2
|
||||||
|
}
|
||||||
|
this.backend.set(b, this.length)
|
||||||
|
this.length += b.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var BlarfEl = document.getElementById("BLARF")
|
var BlarfEl = document.getElementById("BLARF")
|
||||||
BlarfEl.innerHTML = `
|
BlarfEl.innerHTML = `
|
||||||
<canvas width="1280" height="720"></canvas>
|
<canvas width="1280" height="720"></canvas>
|
||||||
<div class="MKVControls">
|
<div class="MKVControls">
|
||||||
|
<div>
|
||||||
<div class="MKVSpeaker"><span class="MKVSpeakerOff">🔈︎</span><span class="MKVSpeakerOn" style="display:none;">🔊︎</span></div>
|
<div class="MKVSpeaker"><span class="MKVSpeakerOff">🔈︎</span><span class="MKVSpeakerOn" style="display:none;">🔊︎</span></div>
|
||||||
<span class="MKVCurrentTime">00:00:00</span>
|
<span class="MKVCurrentTime">00:00:00</span>
|
||||||
<span class="MKVStats"></span>
|
<span class="MKVStats"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="MKVStatus"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
var Canvus = BlarfEl.querySelector("canvas")
|
var Canvus = BlarfEl.querySelector("canvas")
|
||||||
var CanvCtx = Canvus.getContext("2d")
|
var CanvCtx = Canvus.getContext("2d")
|
||||||
|
var CanvImageData
|
||||||
|
|
||||||
|
var LatencyMS = 1000
|
||||||
|
|
||||||
var AudCtx
|
var AudCtx
|
||||||
var AudScript
|
var AudScript, AudWorklet
|
||||||
var AudHz
|
var AudHz
|
||||||
|
var AudMuted = true
|
||||||
|
var AudSampleIndex = 0
|
||||||
|
|
||||||
function create_audio(hz, channels) {
|
function create_audio(hz, channels) {
|
||||||
if(AudCtx) {
|
if(AudCtx) {
|
||||||
AudCtx.close()
|
AudCtx.close()
|
||||||
|
AudScript = null
|
||||||
|
AudWorklet = null
|
||||||
}
|
}
|
||||||
|
|
||||||
AudHz = hz
|
AudHz = hz
|
||||||
|
|
||||||
|
var DebugSine = 0
|
||||||
|
|
||||||
AudCtx = new AudioContext({sampleRate: hz})
|
AudCtx = new AudioContext({sampleRate: hz})
|
||||||
AudScript = AudCtx.createScriptProcessor(1024, channels, channels)
|
AudCtx.suspend()
|
||||||
|
|
||||||
|
if(AudCtx.audioWorklet) {
|
||||||
|
AudCtx.audioWorklet.addModule("rawpcmworklet.js").then(function() {
|
||||||
|
AudWorklet = new AudioWorkletNode(AudCtx, "rawpcmworklet", {
|
||||||
|
outputChannelCount: [2]
|
||||||
|
})
|
||||||
|
AudWorklet.connect(AudCtx.destination)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
AudScript = AudCtx.createScriptProcessor(4096, channels, channels)
|
||||||
AudScript.onaudioprocess = function(e) {
|
AudScript.onaudioprocess = function(e) {
|
||||||
var outL = e.outputBuffer.getChannelData(0)
|
var outL = e.outputBuffer.getChannelData(0)
|
||||||
var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null
|
var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null
|
||||||
@@ -38,8 +81,10 @@
|
|||||||
while(AudioQueue.length && leftToWrite) {
|
while(AudioQueue.length && leftToWrite) {
|
||||||
var amount = Math.min(leftToWrite, AudioQueue[0].left.length)
|
var amount = Math.min(leftToWrite, AudioQueue[0].left.length)
|
||||||
|
|
||||||
|
if(!AudMuted) {
|
||||||
outL.set(AudioQueue[0].left.subarray(0, amount), offset)
|
outL.set(AudioQueue[0].left.subarray(0, amount), offset)
|
||||||
if(outR) outR.set(AudioQueue[0].right.subarray(0, amount), offset)
|
if(outR) outR.set(AudioQueue[0].right.subarray(0, amount), offset)
|
||||||
|
}
|
||||||
|
|
||||||
AudioQueue[0].left = AudioQueue[0].left.subarray(amount)
|
AudioQueue[0].left = AudioQueue[0].left.subarray(amount)
|
||||||
if(outR) AudioQueue[0].right = AudioQueue[0].right.subarray(amount)
|
if(outR) AudioQueue[0].right = AudioQueue[0].right.subarray(amount)
|
||||||
@@ -51,13 +96,10 @@
|
|||||||
leftToWrite -= amount
|
leftToWrite -= amount
|
||||||
offset += amount
|
offset += amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if(RenderStartTime && leftToWrite) {
|
|
||||||
buffering(1000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AudScript.connect(AudCtx.destination)
|
AudScript.connect(AudCtx.destination)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var LastControlsInterrupt
|
var LastControlsInterrupt
|
||||||
function interruptcontrols() {
|
function interruptcontrols() {
|
||||||
@@ -66,11 +108,16 @@
|
|||||||
interruptcontrols()
|
interruptcontrols()
|
||||||
|
|
||||||
function togglemute() {
|
function togglemute() {
|
||||||
if(AudCtx)
|
if(!AudCtx) {
|
||||||
if(document.querySelector(".MKVSpeakerOn").style.display == "none") {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AudCtx.resume()
|
AudCtx.resume()
|
||||||
} else {
|
|
||||||
AudCtx.suspend()
|
AudMuted = !AudMuted
|
||||||
|
|
||||||
|
if(AudWorklet) {
|
||||||
|
AudWorklet.port.postMessage(AudMuted)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll(".MKVSpeaker *").forEach(function(el) { el.style.display = el.style.display == "none" ? "" : "none" })
|
document.querySelectorAll(".MKVSpeaker *").forEach(function(el) { el.style.display = el.style.display == "none" ? "" : "none" })
|
||||||
@@ -81,7 +128,7 @@
|
|||||||
document.querySelector(".MKVSpeaker").onclick = togglemute
|
document.querySelector(".MKVSpeaker").onclick = togglemute
|
||||||
|
|
||||||
document.onkeypress = function(e) {
|
document.onkeypress = function(e) {
|
||||||
if(e.key.toUpperCase() == "M") {
|
if(document.activeElement.tagName != "TEXTAREA" && e.key.toUpperCase() == "M") {
|
||||||
togglemute()
|
togglemute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,30 +139,12 @@
|
|||||||
|
|
||||||
var RenderStartTime, VideoStartTime
|
var RenderStartTime, VideoStartTime
|
||||||
|
|
||||||
var Statistics = {}
|
function crop_audio_queue(durationToRemove) {
|
||||||
var TheWorker = new Worker("blarfwork.js")
|
|
||||||
TheWorker.onmessage = function(e) {
|
|
||||||
if(e.data.width) {
|
|
||||||
var imgData = new ImageData(new Uint8ClampedArray(e.data.data.buffer), e.data.width, e.data.height, {colorSpace: "srgb"})
|
|
||||||
VideoQueue.push({t: e.data.t, imgData: imgData})
|
|
||||||
} else if(e.data.samples) {
|
|
||||||
AudioQueue.push({left: e.data.left, right: e.data.right || e.data.left})
|
|
||||||
|
|
||||||
// Audio may be loaded but it might not play because of autoplay permissions
|
|
||||||
// In this case the audio queue will fill up and cause ever-increasing AV desync
|
|
||||||
|
|
||||||
// To prevent this, manually crop the audio to the duration in the video queue
|
|
||||||
|
|
||||||
if(AudCtx && AudCtx.state != "running") {
|
|
||||||
var durationInAudioQueue = AudioQueue.length ? AudioQueue.reduce((acc, el) => acc + el.left.length, 0) : 0
|
|
||||||
|
|
||||||
var durationToRemove = Math.max(durationInAudioQueue - (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : 0) * AudHz / 1000, 0)
|
|
||||||
|
|
||||||
while(AudioQueue.length && durationToRemove) {
|
while(AudioQueue.length && durationToRemove) {
|
||||||
var amount = Math.min(durationToRemove, AudioQueue[0].left.length)
|
var amount = Math.min(durationToRemove, AudioQueue[0].left.length)
|
||||||
|
|
||||||
AudioQueue[0].left = AudioQueue[0].left.subarray(amount)
|
AudioQueue[0].left = AudioQueue[0].left.subarray(amount)
|
||||||
AudioQueue[0].right = AudioQueue[0].left.subarray(amount)
|
AudioQueue[0].right = AudioQueue[0].right.subarray(amount)
|
||||||
|
|
||||||
if(AudioQueue[0].left.length == 0) {
|
if(AudioQueue[0].left.length == 0) {
|
||||||
AudioQueue.shift()
|
AudioQueue.shift()
|
||||||
@@ -124,6 +153,33 @@
|
|||||||
durationToRemove -= amount
|
durationToRemove -= amount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var BufferPool = new Set()
|
||||||
|
|
||||||
|
var Statistics = {}
|
||||||
|
var TheWorker = new Worker("blarfwork.js")
|
||||||
|
TheWorker.onmessage = function(e) {
|
||||||
|
if(e.data.width) {
|
||||||
|
// var imgData = new ImageData(new Uint8ClampedArray(e.data.data.buffer), e.data.width, e.data.height, {colorSpace: "srgb"})
|
||||||
|
var b
|
||||||
|
if(BufferPool.size == 0) {
|
||||||
|
b = new Uint8ClampedArray(e.data.data.buffer)
|
||||||
|
} else {
|
||||||
|
for(const v of BufferPool) {
|
||||||
|
b = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
BufferPool.delete(b)
|
||||||
|
b.set(e.data.data)
|
||||||
|
}
|
||||||
|
VideoQueue.push({t: e.data.t, imgData: b, w: e.data.width, h: e.data.height})
|
||||||
|
} else if(e.data.samples) {
|
||||||
|
AudioQueue.push({t: e.data.t, left: e.data.left, right: e.data.right || e.data.left})
|
||||||
|
|
||||||
|
if(AudCtx.state == "running" && AudWorklet && AudioQueue.length) {
|
||||||
|
AudWorklet.port.postMessage(merge_audio_queue())
|
||||||
|
AudioQueue.length = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Statistics[e.data.id]) {
|
if(!Statistics[e.data.id]) {
|
||||||
@@ -140,20 +196,11 @@
|
|||||||
text = text + k + ":" + (Math.floor(100 * Statistics[k].sum / Statistics[k].count) / 100) + ","
|
text = text + k + ":" + (Math.floor(100 * Statistics[k].sum / Statistics[k].count) / 100) + ","
|
||||||
}
|
}
|
||||||
stats.innerText = text*/
|
stats.innerText = text*/
|
||||||
stats.innerHTML = (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : "0") + "v" + (AudioQueue.reduce(function(acc, obj) {return acc + obj.left.length * AudHz / 1000}, 0)|0) + "a"
|
stats.innerText = (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : "0") + "v" + (AudioQueue.reduce(function(acc, obj) {return acc + obj.left.length * 1000 / AudHz}, 0)|0) + "a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvus.onclick = function() {
|
|
||||||
if(AudCtx) AudCtx.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
var VideoBufferingOffset = 0
|
var VideoBufferingOffset = 0
|
||||||
function buffering(millis) {
|
|
||||||
//var silence = new Float32Array(millis * 48);
|
|
||||||
//AudioQueue.push({left: silence, right: silence})
|
|
||||||
//VideoBufferingOffset += millis
|
|
||||||
}
|
|
||||||
|
|
||||||
function toHex(buffer) {
|
function toHex(buffer) {
|
||||||
return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join('');
|
return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join('');
|
||||||
@@ -321,10 +368,7 @@
|
|||||||
this.currentClusterTime = EBMLParser.vi_to_i(data)
|
this.currentClusterTime = EBMLParser.vi_to_i(data)
|
||||||
|
|
||||||
if(!RenderStartTime) {
|
if(!RenderStartTime) {
|
||||||
RenderStartTime = document.timeline.currentTime + 1000
|
RenderStartTime = performance.now()
|
||||||
}
|
|
||||||
if(!VideoStartTime) {
|
|
||||||
VideoStartTime = this.currentClusterTime
|
|
||||||
}
|
}
|
||||||
} else if(elID == 0xA3) {
|
} else if(elID == 0xA3) {
|
||||||
// Cluster -> SimpleBlock
|
// Cluster -> SimpleBlock
|
||||||
@@ -341,10 +385,16 @@
|
|||||||
var TotalTime = (this.currentClusterTime + timestamp) / 1000
|
var TotalTime = (this.currentClusterTime + timestamp) / 1000
|
||||||
document.querySelector(".MKVCurrentTime").innerText = pad(Math.floor(TotalTime / 3600), 2) + ":" + pad(Math.floor(TotalTime / 60 % 60), 2) + ":" + pad(Math.floor(TotalTime % 60), 2)
|
document.querySelector(".MKVCurrentTime").innerText = pad(Math.floor(TotalTime / 3600), 2) + ":" + pad(Math.floor(TotalTime / 60 % 60), 2) + ":" + pad(Math.floor(TotalTime % 60), 2)
|
||||||
|
|
||||||
|
var playerTimestamp = this.currentClusterTime + timestamp
|
||||||
|
|
||||||
if(track) {
|
if(track) {
|
||||||
|
if(!VideoStartTime) {
|
||||||
|
VideoStartTime = playerTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
var packet = data.subarray(4)
|
var packet = data.subarray(4)
|
||||||
|
|
||||||
TheWorker.postMessage({cmd: "decode", id: trackID, t: timestamp + this.currentClusterTime - VideoStartTime, packet: packet, kf: kf})
|
TheWorker.postMessage({cmd: "decode", id: trackID, t: playerTimestamp - VideoStartTime, packet: packet, kf: kf})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,6 +416,9 @@
|
|||||||
if(track.type == "video") {
|
if(track.type == "video") {
|
||||||
Canvus.width = track.width
|
Canvus.width = track.width
|
||||||
Canvus.height = track.height
|
Canvus.height = track.height
|
||||||
|
CanvImageData = new ImageData(new Uint8ClampedArray(Canvus.width * Canvus.height * 4), Canvus.width, Canvus.height, {"colorSpace": "srgb"})
|
||||||
|
RenderStartTime = null
|
||||||
|
VideoStartTime = null
|
||||||
} else {
|
} else {
|
||||||
create_audio(track.samplerate, track.channels)
|
create_audio(track.samplerate, track.channels)
|
||||||
}
|
}
|
||||||
@@ -380,11 +433,48 @@
|
|||||||
ebml.ondata = matr.ondata.bind(matr)
|
ebml.ondata = matr.ondata.bind(matr)
|
||||||
ebml.onexit = matr.onexit.bind(matr)
|
ebml.onexit = matr.onexit.bind(matr)
|
||||||
|
|
||||||
|
function merge_audio_queue() {
|
||||||
|
var s = 0
|
||||||
|
|
||||||
|
for(var i = 0; i < AudioQueue.length; i++) {
|
||||||
|
s += AudioQueue[i].left.length
|
||||||
|
}
|
||||||
|
|
||||||
|
var L = new Float32Array(s)
|
||||||
|
var R = new Float32Array(s)
|
||||||
|
|
||||||
|
s = 0
|
||||||
|
|
||||||
|
for(var i = 0; i < AudioQueue.length; i++) {
|
||||||
|
L.set(AudioQueue[i].left, s)
|
||||||
|
R.set(AudioQueue[i].right, s)
|
||||||
|
s += AudioQueue[i].left.length
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = {msg: "data", t: AudSampleIndex, left: L, right: R}
|
||||||
|
|
||||||
|
AudSampleIndex += L.length
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
function reconnect_ws() {
|
function reconnect_ws() {
|
||||||
var ws = new WebSocket(BlarfEl.getAttribute("data-target"))
|
var ws = new WebSocket(BlarfEl.getAttribute("data-target"))
|
||||||
ws.binaryType = "arraybuffer"
|
ws.binaryType = "arraybuffer"
|
||||||
ws.onmessage = function(ev) {
|
ws.onmessage = function(ev) {
|
||||||
|
if(typeof ev.data === "string") {
|
||||||
|
var obj = JSON.parse(ev.data)
|
||||||
|
if(obj.status) {
|
||||||
|
BlarfEl.querySelector(".MKVStatus").innerHTML = "• " + obj.status.viewer_count
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ebml.poosh(new Uint8Array(ev.data))
|
ebml.poosh(new Uint8Array(ev.data))
|
||||||
|
ebml.parse()
|
||||||
|
|
||||||
|
while(document.hidden && VideoQueue.length > 1 && VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t <= LatencyMS) {
|
||||||
|
BufferPool.add(VideoQueue.shift().imgData)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ws.onclose = function(ev) {
|
ws.onclose = function(ev) {
|
||||||
setTimeout(reconnect_ws, 5000)
|
setTimeout(reconnect_ws, 5000)
|
||||||
@@ -393,16 +483,37 @@
|
|||||||
reconnect_ws()
|
reconnect_ws()
|
||||||
|
|
||||||
function render(timestamp) {
|
function render(timestamp) {
|
||||||
ebml.parse()
|
try {
|
||||||
|
|
||||||
document.querySelector(".MKVControls").style.opacity = Math.max(0, Math.min(1, 5 - (timestamp - LastControlsInterrupt) / 1000))
|
document.querySelector(".MKVControls").style.opacity = Math.max(0, Math.min(1, 5 - (timestamp - LastControlsInterrupt) / 1000))
|
||||||
|
|
||||||
while(RenderStartTime && VideoQueue.length && VideoQueue[0].t + VideoBufferingOffset <= (timestamp - RenderStartTime)) {
|
var nextImg = null
|
||||||
CanvCtx.putImageData(VideoQueue[0].imgData, 0, 0)
|
while(RenderStartTime && VideoQueue.length && VideoQueue[0].t <= (timestamp - RenderStartTime - LatencyMS)) {
|
||||||
|
if(nextImg) BufferPool.add(nextImg.imgData)
|
||||||
|
nextImg = VideoQueue[0]
|
||||||
VideoQueue.shift()
|
VideoQueue.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(nextImg) {
|
||||||
|
document.querySelector(".MKVControls").style.display = null
|
||||||
|
|
||||||
|
// Prevent the audio queue filling up and causing ever-increasing AV desync
|
||||||
|
if(AudCtx && AudCtx.state != "running" && AudioQueue && AudioQueue.length) {
|
||||||
|
if(AudioQueue[0].t < nextImg.t) {
|
||||||
|
crop_audio_queue(Math.round((nextImg.t - AudioQueue[0].t) / 1000 * AudHz))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvImageData.data.set(nextImg.imgData)
|
||||||
|
CanvCtx.putImageData(CanvImageData, 0, 0)
|
||||||
|
BufferPool.add(nextImg.imgData)
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(render)
|
requestAnimationFrame(render)
|
||||||
}
|
}
|
||||||
requestAnimationFrame(render)
|
requestAnimationFrame(render)
|
||||||
|
|
||||||
|
document.querySelector(".MKVControls").style.display = "none"
|
||||||
})()
|
})()
|
||||||
|
|||||||
67
index.html
67
index.html
@@ -42,6 +42,9 @@
|
|||||||
font-size: 0.4cm;
|
font-size: 0.4cm;
|
||||||
background: rgb(0, 0, 0);
|
background: rgb(0, 0, 0);
|
||||||
background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
div#BLARF .MKVControls > * {
|
div#BLARF .MKVControls > * {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@@ -53,8 +56,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.75cm;
|
font-size: 0.75cm;
|
||||||
}
|
}
|
||||||
|
div#BLARF .MKVStatus {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
div#BLARF > canvas {
|
div#BLARF > canvas {
|
||||||
background: url(Intermission2.jpg) black;
|
background: url(intermission.jpg) black;
|
||||||
background-position: 0 30%;
|
background-position: 0 30%;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -71,17 +77,49 @@
|
|||||||
display: block;
|
display: block;
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
}
|
}
|
||||||
|
span.chat-msg__heading {
|
||||||
|
width: inherit !important;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-aspect-ratio: 1) {
|
||||||
|
div.everything {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
div.stream {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
div.stream > div.chat {
|
||||||
|
min-height: 20vh;
|
||||||
|
}
|
||||||
|
converse-root {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/10.1.4/dist/converse.min.css">
|
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/10.1.4/dist/converse.min.css">
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Title</title>
|
<title>MWSS Stream</title>
|
||||||
|
<script>
|
||||||
|
var STREAM_SOURCE_WS = "wss://iki.mid.net.ua/streamout/"
|
||||||
|
|
||||||
|
var ENABLE_CHAT = true
|
||||||
|
var CHAT_HOST_WS_URL = "wss://mid.net.ua/xmpp"
|
||||||
|
var CHAT_HOST = "anon.mid.net.ua"
|
||||||
|
var CHAT_MUC = "stream@muc.anon.mid.net.ua"
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="everything">
|
<div class="everything">
|
||||||
<header></header>
|
<header></header>
|
||||||
<div class="stream">
|
<div class="stream">
|
||||||
<div class="feed">
|
<div class="feed">
|
||||||
<div id="BLARF" data-target="wss://iki.mid.net.ua/streamout/"></div>
|
<div id="BLARF" data-target=""></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat">
|
<div class="chat">
|
||||||
<converse-root style="position: relative;"></converse-root>
|
<converse-root style="position: relative;"></converse-root>
|
||||||
@@ -91,22 +129,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.conversejs.org/10.1.4/dist/converse.min.js" charset="utf-8"></script>
|
<script src="https://cdn.conversejs.org/10.1.4/dist/converse.min.js" charset="utf-8"></script>
|
||||||
<script src="blarf.js"></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.querySelector("#BLARF").setAttribute("data-target", STREAM_SOURCE_WS)
|
||||||
|
|
||||||
function randomHex(size) {
|
function randomHex(size) {
|
||||||
return [...self.crypto.getRandomValues(new Uint8Array(size))].map(b=>b.toString(16).padStart(2, "0")).join("");
|
return [...self.crypto.getRandomValues(new Uint8Array(size))].map(b=>b.toString(16).padStart(2, "0")).join("")
|
||||||
}
|
}
|
||||||
const un = 'lol' + randomHex(16)
|
const un = 'lol' + randomHex(16)
|
||||||
|
|
||||||
|
if(ENABLE_CHAT) {
|
||||||
converse.initialize({
|
converse.initialize({
|
||||||
view_mode: 'embedded',
|
view_mode: 'embedded',
|
||||||
websocket_url: 'wss://mid.net.ua/xmpp',
|
websocket_url: CHAT_HOST_WS_URL,
|
||||||
login: 'anonymous',
|
authentication: 'anonymous',
|
||||||
jid: un + '@anon.mid.net.ua',
|
jid: CHAT_HOST,
|
||||||
auto_login: true,
|
auto_login: true,
|
||||||
password: 'lol',
|
auto_join_rooms: [CHAT_MUC],
|
||||||
auto_join_rooms: ['stream@muc.anon.mid.net.ua'],
|
|
||||||
show_message_avatar: false,
|
show_message_avatar: false,
|
||||||
show_controlbox_by_default: false,
|
show_controlbox_by_default: false,
|
||||||
roster_groups: false,
|
roster_groups: false,
|
||||||
@@ -114,8 +152,13 @@
|
|||||||
singleton: true,
|
singleton: true,
|
||||||
discover_connection_methods: false,
|
discover_connection_methods: false,
|
||||||
keepalive: false,
|
keepalive: false,
|
||||||
auto_reconnect: true
|
auto_reconnect: true,
|
||||||
|
hide_muc_participants: true
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
document.querySelector("div.everything .chat").style.display = "none"
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="blarf.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
167
main.c
167
main.c
@@ -1,167 +0,0 @@
|
|||||||
#include"mongoose.h"
|
|
||||||
|
|
||||||
#include<getopt.h>
|
|
||||||
#include<stdio.h>
|
|
||||||
#include<string.h>
|
|
||||||
#include<stdbool.h>
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
LOADING_HEADER,
|
|
||||||
STREAMING,
|
|
||||||
} State;
|
|
||||||
|
|
||||||
static const uint8_t *STATE_CHANGE_STRING[] = {
|
|
||||||
[LOADING_HEADER] = "\x1F\x43\xB6\x75",
|
|
||||||
[STREAMING] = "\x1A\x45\xDF\xA3",
|
|
||||||
};
|
|
||||||
|
|
||||||
static State state = STREAMING;
|
|
||||||
static int stateChangeIdx;
|
|
||||||
|
|
||||||
static char *header;
|
|
||||||
static size_t headerSize;
|
|
||||||
|
|
||||||
static struct mg_connection *streamerConnected = NULL;
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
char *wslisten;
|
|
||||||
char *tcplisten;
|
|
||||||
char *tlscert;
|
|
||||||
char *tlsca;
|
|
||||||
} settings;
|
|
||||||
|
|
||||||
static void ws_broadcast(struct mg_mgr *mgr, const char *data, size_t len) {
|
|
||||||
for(struct mg_connection *cli = mgr->conns; cli; cli = cli->next) {
|
|
||||||
if(cli->is_websocket) {
|
|
||||||
mg_ws_send(cli, data, len, WEBSOCKET_OP_BINARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
|
||||||
if(ev == MG_EV_ACCEPT) {
|
|
||||||
if(mg_url_is_ssl(c->is_websocket ? settings.wslisten : settings.tcplisten)) {
|
|
||||||
struct mg_tls_opts opts = {.ca = mg_unpacked(settings.tlsca), .cert = mg_unpacked(settings.tlscert), .key = mg_unpacked(settings.tlscert)};
|
|
||||||
mg_tls_init(c, &opts);
|
|
||||||
}
|
|
||||||
} else if(ev == MG_EV_CLOSE) {
|
|
||||||
if(c == streamerConnected) {
|
|
||||||
streamerConnected = NULL;
|
|
||||||
}
|
|
||||||
} else if(ev == MG_EV_HTTP_MSG) {
|
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
|
||||||
mg_ws_upgrade(c, hm, NULL);
|
|
||||||
} else if(ev == MG_EV_WS_OPEN) {
|
|
||||||
if(state == STREAMING && header) {
|
|
||||||
mg_ws_send(c, header, headerSize, WEBSOCKET_OP_BINARY);
|
|
||||||
}
|
|
||||||
} else if(ev == MG_EV_WS_MSG) {
|
|
||||||
// Incoming WS messages are ignored.
|
|
||||||
} else if(ev == MG_EV_READ) {
|
|
||||||
if(!c->is_websocket) {
|
|
||||||
if(streamerConnected && streamerConnected != c) {
|
|
||||||
c->is_closing = 1;
|
|
||||||
} else {
|
|
||||||
streamerConnected = c;
|
|
||||||
}
|
|
||||||
} else return;
|
|
||||||
|
|
||||||
struct mg_iobuf *r = &c->recv;
|
|
||||||
|
|
||||||
if(state == LOADING_HEADER) {
|
|
||||||
header = realloc(header, headerSize + r->len);
|
|
||||||
memcpy(header + headerSize, r->buf, r->len);
|
|
||||||
headerSize += r->len;
|
|
||||||
|
|
||||||
char *clusterEl = memmem(header, headerSize, "\x1F\x43\xB6\x75", 4);
|
|
||||||
if(clusterEl) {
|
|
||||||
ws_broadcast(c->mgr, header, clusterEl - header);
|
|
||||||
ws_broadcast(c->mgr, clusterEl, header + headerSize - clusterEl);
|
|
||||||
|
|
||||||
headerSize = clusterEl - header;
|
|
||||||
state = STREAMING;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int i;
|
|
||||||
for(i = 0; i < r->len; i++) {
|
|
||||||
if(r->buf[i] == STATE_CHANGE_STRING[state][stateChangeIdx]) {
|
|
||||||
stateChangeIdx++;
|
|
||||||
|
|
||||||
if(stateChangeIdx == strlen(STATE_CHANGE_STRING[state])) {
|
|
||||||
i++;
|
|
||||||
stateChangeIdx = 0;
|
|
||||||
state = LOADING_HEADER;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stateChangeIdx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(state == LOADING_HEADER) {
|
|
||||||
if(i > 4) {
|
|
||||||
ws_broadcast(c->mgr, r->buf, i - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
header = realloc(header, headerSize = 4 + (r->len - i));
|
|
||||||
memcpy(header, STATE_CHANGE_STRING[STREAMING], 4);
|
|
||||||
memcpy(header + 4, r->buf + i, r->len - i);
|
|
||||||
} else {
|
|
||||||
ws_broadcast(c->mgr, r->buf, r->len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r->len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
int help = 0, err = 0;
|
|
||||||
|
|
||||||
int c;
|
|
||||||
while((c = getopt(argc, argv, "a:c:i:o:h")) != -1) {
|
|
||||||
if(c == 'i') {
|
|
||||||
settings.tcplisten = optarg;
|
|
||||||
} else if(c == 'o') {
|
|
||||||
settings.wslisten = optarg;
|
|
||||||
} else if(c == 'a') {
|
|
||||||
settings.tlsca = optarg;
|
|
||||||
} else if(c == 'c') {
|
|
||||||
settings.tlscert = optarg;
|
|
||||||
} else if(c == 'h') {
|
|
||||||
help = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(help) {
|
|
||||||
fprintf(stderr, "Example usage: %s [-c /path/to/cert.pem] [-a /path/to/certauthority.pem] [-h] <-i tcp://[::]:1234> <-o ws://[::]:8000>\n", argv[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!settings.wslisten) {
|
|
||||||
fputs("Missing -o parameter. Try -h for help.\n", stderr);
|
|
||||||
err = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!settings.tcplisten) {
|
|
||||||
fputs("Missing -i parameter. Try -h for help\n", stderr);
|
|
||||||
err = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mg_mgr mgr;
|
|
||||||
|
|
||||||
mg_mgr_init(&mgr);
|
|
||||||
|
|
||||||
mg_listen(&mgr, settings.tcplisten, fn, NULL);
|
|
||||||
mg_http_listen(&mgr, settings.wslisten, fn, NULL);
|
|
||||||
|
|
||||||
for (;;) mg_mgr_poll(&mgr, 1000);
|
|
||||||
|
|
||||||
mg_mgr_free(&mgr);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
582
main2.c
Normal file
582
main2.c
Normal file
@@ -0,0 +1,582 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
#include<ws2tcpip.h>
|
||||||
|
#include<windows.h>
|
||||||
|
#include"wepoll.c"
|
||||||
|
#else
|
||||||
|
#include<sys/socket.h>
|
||||||
|
#include<sys/epoll.h>
|
||||||
|
#include<netinet/in.h>
|
||||||
|
#include<netdb.h>
|
||||||
|
#include<sys/types.h>
|
||||||
|
#include<fcntl.h>
|
||||||
|
#include<unistd.h>
|
||||||
|
#include<time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include<stdbool.h>
|
||||||
|
#include<string.h>
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include<stdio.h>
|
||||||
|
#include<assert.h>
|
||||||
|
#include<errno.h>
|
||||||
|
|
||||||
|
#include"picohttpparser.h"
|
||||||
|
#include"base64.h"
|
||||||
|
#include"teeny-sha1.c"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
typedef HANDLE EPoll;
|
||||||
|
typedef int Socket;
|
||||||
|
void *memmem(const void *haystack, size_t haystack_len, const void * const needle, const size_t needle_len) {
|
||||||
|
if(haystack == NULL) return NULL;
|
||||||
|
if(haystack_len == 0) return NULL;
|
||||||
|
if(needle == NULL) return NULL;
|
||||||
|
if(needle_len == 0) return NULL;
|
||||||
|
|
||||||
|
for(const char *h = haystack; haystack_len >= needle_len; ++h, --haystack_len) {
|
||||||
|
if(!memcmp(h, needle, needle_len)) {
|
||||||
|
return (void*) h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
typedef int EPoll;
|
||||||
|
typedef int Socket;
|
||||||
|
#define closesocket close
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum ClientType {
|
||||||
|
UNKNOWN,
|
||||||
|
CLI_STREAMER,
|
||||||
|
CLI_VIEWER
|
||||||
|
} ClientType;
|
||||||
|
|
||||||
|
typedef enum ClientState {
|
||||||
|
REQUEST,
|
||||||
|
ACTIVE,
|
||||||
|
WEBSOCKET,
|
||||||
|
} ClientState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Socket fd;
|
||||||
|
|
||||||
|
ClientType type;
|
||||||
|
ClientState state;
|
||||||
|
|
||||||
|
size_t len, prevlen, cap;
|
||||||
|
uint8_t *buf;
|
||||||
|
|
||||||
|
// Only for streamers
|
||||||
|
struct phr_chunked_decoder chudec;
|
||||||
|
|
||||||
|
// Only for websockets
|
||||||
|
struct {
|
||||||
|
int opcode;
|
||||||
|
uint8_t *incoming;
|
||||||
|
size_t incomingSz;
|
||||||
|
} ws;
|
||||||
|
} Client;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LOADING_HEADER,
|
||||||
|
STREAMING,
|
||||||
|
} StreamState;
|
||||||
|
static struct Stream {
|
||||||
|
StreamState state;
|
||||||
|
|
||||||
|
uint8_t *mkvHeader;
|
||||||
|
size_t mkvHeaderSz;
|
||||||
|
|
||||||
|
int stateChangeIdx;
|
||||||
|
} Stream;
|
||||||
|
|
||||||
|
static size_t clientsSz;
|
||||||
|
static Client **clients;
|
||||||
|
|
||||||
|
static char *ValidStreamPath = NULL;
|
||||||
|
|
||||||
|
static void consume(Client *cli, size_t n) {
|
||||||
|
memmove(cli->buf, cli->buf + n, cli->len - n);
|
||||||
|
cli->len -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int transmit(Client *cli, const char *buf, size_t sz) {
|
||||||
|
while(sz) {
|
||||||
|
ssize_t s = send(cli->fd, buf, sz, MSG_NOSIGNAL);
|
||||||
|
|
||||||
|
if(s >= 0) {
|
||||||
|
buf += s;
|
||||||
|
sz -= s;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void transmit_all(const char *buf, size_t sz) {
|
||||||
|
for(size_t i = 0; i < clientsSz; i++) {
|
||||||
|
if(clients[i]->state == WEBSOCKET) {
|
||||||
|
transmit(clients[i], buf, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WS_TXT 1
|
||||||
|
#define WS_BIN 2
|
||||||
|
#define WS_CLOSE 8
|
||||||
|
#define WS_FIN 128
|
||||||
|
#define WS_HEADER_MAX 10
|
||||||
|
static int ws_header(size_t sz, bool binary, uint8_t hdr[static WS_HEADER_MAX]) {
|
||||||
|
int i;
|
||||||
|
hdr[0] = (binary ? WS_BIN : WS_TXT) | WS_FIN;
|
||||||
|
if(sz < 126) {
|
||||||
|
hdr[1] = sz;
|
||||||
|
i = 2;
|
||||||
|
} else if(sz < 65536) {
|
||||||
|
hdr[1] = 126;
|
||||||
|
hdr[2] = sz >> 8;
|
||||||
|
hdr[3] = sz & 0xFF;
|
||||||
|
i = 4;
|
||||||
|
} else {
|
||||||
|
hdr[1] = 127;
|
||||||
|
hdr[2] = (sz >> 56) & 0xFF;
|
||||||
|
hdr[3] = (sz >> 48) & 0xFF;
|
||||||
|
hdr[4] = (sz >> 40) & 0xFF;
|
||||||
|
hdr[5] = (sz >> 32) & 0xFF;
|
||||||
|
hdr[6] = (sz >> 24) & 0xFF;
|
||||||
|
hdr[7] = (sz >> 16) & 0xFF;
|
||||||
|
hdr[8] = (sz >> 8) & 0xFF;
|
||||||
|
hdr[9] = (sz >> 0) & 0xFF;
|
||||||
|
i = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_send(Client *cli, bool binary, const uint8_t *buf, size_t sz) {
|
||||||
|
if(sz == 0) return;
|
||||||
|
|
||||||
|
uint8_t wshdr[WS_HEADER_MAX];
|
||||||
|
int wshdrsz = ws_header(sz, binary, wshdr);
|
||||||
|
|
||||||
|
transmit(cli, wshdr, wshdrsz);
|
||||||
|
transmit(cli, buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_broadcast(bool binary, const uint8_t *buf, size_t sz) {
|
||||||
|
if(sz == 0) return;
|
||||||
|
|
||||||
|
uint8_t wshdr[WS_HEADER_MAX];
|
||||||
|
int wshdrsz = ws_header(sz, binary, wshdr);
|
||||||
|
|
||||||
|
transmit_all(wshdr, wshdrsz);
|
||||||
|
transmit_all(buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool should_send_status_update() {
|
||||||
|
static uint64_t lastSec = 0;
|
||||||
|
|
||||||
|
struct timespec tv;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &tv);
|
||||||
|
|
||||||
|
if(tv.tv_sec - lastSec < 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSec = tv.tv_sec;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_status_update() {
|
||||||
|
char buf[512] = {};
|
||||||
|
snprintf(buf, sizeof(buf) - 1, "{\"status\": {\"viewer_count\": %lu}}", clientsSz);
|
||||||
|
|
||||||
|
ws_broadcast(false, buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stream_step(const uint8_t *newbuf, size_t newsz) {
|
||||||
|
if(should_send_status_update()) {
|
||||||
|
send_status_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Stream.state == LOADING_HEADER) {
|
||||||
|
Stream.mkvHeader = realloc(Stream.mkvHeader, Stream.mkvHeaderSz + newsz);
|
||||||
|
memcpy(Stream.mkvHeader + Stream.mkvHeaderSz, newbuf, newsz);
|
||||||
|
Stream.mkvHeaderSz += newsz;
|
||||||
|
|
||||||
|
uint8_t *clusterEl = memmem(Stream.mkvHeader, Stream.mkvHeaderSz, "\x1F\x43\xB6\x75", 4);
|
||||||
|
if(clusterEl) {
|
||||||
|
ws_broadcast(true, Stream.mkvHeader, clusterEl - Stream.mkvHeader);
|
||||||
|
ws_broadcast(true, clusterEl, Stream.mkvHeader + Stream.mkvHeaderSz - clusterEl);
|
||||||
|
|
||||||
|
Stream.mkvHeaderSz = clusterEl - Stream.mkvHeader;
|
||||||
|
Stream.state = STREAMING;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
static const uint8_t rootEl[4] = "\x1A\x45\xDF\xA3";
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < newsz; i++) {
|
||||||
|
if(newbuf[i] == rootEl[0]) {
|
||||||
|
Stream.stateChangeIdx = 1;
|
||||||
|
} else if(newbuf[i] == rootEl[Stream.stateChangeIdx]) {
|
||||||
|
Stream.stateChangeIdx++;
|
||||||
|
|
||||||
|
if(Stream.stateChangeIdx == 4) {
|
||||||
|
i++;
|
||||||
|
Stream.stateChangeIdx = 0;
|
||||||
|
Stream.state = LOADING_HEADER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Stream.stateChangeIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Stream.state == LOADING_HEADER) {
|
||||||
|
puts("New header");
|
||||||
|
|
||||||
|
if(i > 4) {
|
||||||
|
ws_broadcast(true, newbuf, i - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream.mkvHeader = realloc(Stream.mkvHeader, Stream.mkvHeaderSz = 4 + (newsz - i));
|
||||||
|
memcpy(Stream.mkvHeader, "\x1A\x45\xDF\xA3", 4);
|
||||||
|
memcpy(Stream.mkvHeader + 4, newbuf + i, newsz - i);
|
||||||
|
} else {
|
||||||
|
ws_broadcast(true, newbuf, newsz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void receive_ws(Client *cli) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle(Client *cli) {
|
||||||
|
while(cli->len != 0) {
|
||||||
|
if(cli->state == REQUEST) {
|
||||||
|
int minor_version;
|
||||||
|
struct phr_header headers[96];
|
||||||
|
const char *method, *path;
|
||||||
|
size_t method_len, path_len, num_headers = sizeof(headers) / sizeof(headers[0]);
|
||||||
|
int pret = phr_parse_request(cli->buf, cli->len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, cli->prevlen);
|
||||||
|
|
||||||
|
if(pret == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pret == -2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool connectionUpgrade = false;
|
||||||
|
bool upgradeWebSocket = false;
|
||||||
|
|
||||||
|
size_t wsAcceptLen;
|
||||||
|
unsigned char *wsAccept = NULL;
|
||||||
|
|
||||||
|
bool chunked = false;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < num_headers; i++) {
|
||||||
|
if(strncmp(headers[i].name, "Upgrade", headers[i].name_len) == 0 && strncmp(headers[i].value, "websocket", headers[i].value_len) == 0) {
|
||||||
|
upgradeWebSocket = true;
|
||||||
|
} else if(strncmp(headers[i].name, "Connection", headers[i].name_len) == 0 && memmem(headers[i].value, headers[i].value_len, "Upgrade", 7)) {
|
||||||
|
connectionUpgrade = true;
|
||||||
|
} else if(strncmp(headers[i].name, "Transfer-Encoding", headers[i].name_len) == 0 && strncmp(headers[i].value, "chunked", headers[i].value_len) == 0) {
|
||||||
|
chunked = true;
|
||||||
|
} else if(strncmp(headers[i].name, "Sec-WebSocket-Key", headers[i].name_len) == 0) {
|
||||||
|
size_t acceptbufsz = headers[i].value_len + 36;
|
||||||
|
char acceptbuf[acceptbufsz];
|
||||||
|
memcpy(acceptbuf, headers[i].value, headers[i].value_len);
|
||||||
|
memcpy(acceptbuf + headers[i].value_len, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36);
|
||||||
|
|
||||||
|
char sha1bin[20];
|
||||||
|
char sha1hex[41];
|
||||||
|
sha1digest(sha1bin, sha1hex, acceptbuf, acceptbufsz);
|
||||||
|
|
||||||
|
wsAcceptLen = BASE64_ENCODE_OUT_SIZE(sizeof(sha1bin));
|
||||||
|
wsAccept = malloc(wsAcceptLen);
|
||||||
|
base64_encode(sha1bin, sizeof(sha1bin), wsAccept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path_len == strlen(ValidStreamPath) && strncmp(path, ValidStreamPath, path_len) == 0) {
|
||||||
|
cli->type = CLI_STREAMER;
|
||||||
|
cli->state = ACTIVE;
|
||||||
|
|
||||||
|
if(upgradeWebSocket || connectionUpgrade || !chunked) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("New streamer client\n");
|
||||||
|
} else {
|
||||||
|
cli->type = CLI_VIEWER;
|
||||||
|
cli->state = WEBSOCKET;
|
||||||
|
|
||||||
|
if(!upgradeWebSocket || !connectionUpgrade || chunked || !wsAccept) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
int bufnum = snprintf(buf, sizeof(buf), "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n\r\n", (int) wsAcceptLen, wsAccept);
|
||||||
|
|
||||||
|
free(wsAccept);
|
||||||
|
|
||||||
|
transmit(cli, buf, bufnum);
|
||||||
|
|
||||||
|
printf("New WS client\n");
|
||||||
|
|
||||||
|
if(Stream.state == STREAMING && Stream.mkvHeader) {
|
||||||
|
printf("Sending header\n");
|
||||||
|
ws_send(cli, true, Stream.mkvHeader, Stream.mkvHeaderSz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(cli, pret);
|
||||||
|
|
||||||
|
cli->prevlen = 0;
|
||||||
|
} else if(cli->state == ACTIVE) {
|
||||||
|
size_t rsize = cli->len;
|
||||||
|
int pret = phr_decode_chunked(&cli->chudec, cli->buf, &rsize);
|
||||||
|
|
||||||
|
if(pret == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_step(cli->buf, rsize);
|
||||||
|
|
||||||
|
cli->len = 0;
|
||||||
|
|
||||||
|
if(pret == -2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if(cli->state == WEBSOCKET) {
|
||||||
|
if(cli->len < 2) return 1;
|
||||||
|
|
||||||
|
uint8_t framehdr = cli->buf[0];
|
||||||
|
|
||||||
|
bool fin = framehdr & 128;
|
||||||
|
int opcode = framehdr & 15;
|
||||||
|
|
||||||
|
if(cli->ws.opcode == 0 && opcode) {
|
||||||
|
cli->ws.opcode = opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t payloadSz = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
uint8_t payload0 = cli->buf[1] & 127;
|
||||||
|
if(payload0 < 126) {
|
||||||
|
payloadSz = payload0;
|
||||||
|
|
||||||
|
i = 2;
|
||||||
|
} else if(payload0 == 126) {
|
||||||
|
if(cli->len < 4) return 1;
|
||||||
|
|
||||||
|
payloadSz = (cli->buf[2] << 8) + cli->buf[3];
|
||||||
|
|
||||||
|
i = 4;
|
||||||
|
} else if(payload0 == 127) {
|
||||||
|
if(cli->len < 10) return 1;
|
||||||
|
|
||||||
|
payloadSz = ((uint64_t) cli->buf[2] << 56) + ((uint64_t) cli->buf[3] << 48) + ((uint64_t) cli->buf[4] << 40) + ((uint64_t) cli->buf[5] << 32) + ((uint64_t) cli->buf[6] << 24) + ((uint64_t) cli->buf[7] << 16) + ((uint64_t) cli->buf[8] << 8) + ((uint64_t) cli->buf[9] << 0);
|
||||||
|
|
||||||
|
i = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(payloadSz > 100) {
|
||||||
|
// Literally just kick
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cli->len < i + 4 + payloadSz) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mask[4] = {cli->buf[i], cli->buf[i + 1], cli->buf[i + 2], cli->buf[i + 3]};
|
||||||
|
|
||||||
|
for(size_t b = 0; b < payloadSz; b++) {
|
||||||
|
cli->buf[i + 4 + b] ^= mask[b % 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
cli->ws.incoming = realloc(cli->ws.incoming, cli->ws.incomingSz + payloadSz);
|
||||||
|
memcpy(cli->ws.incoming + cli->ws.incomingSz, cli->buf + i + 4, payloadSz);
|
||||||
|
|
||||||
|
consume(cli, i + 4 + payloadSz);
|
||||||
|
|
||||||
|
if(fin) {
|
||||||
|
receive_ws(cli);
|
||||||
|
|
||||||
|
if(cli->ws.opcode == WS_CLOSE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cli->ws.incomingSz = 0;
|
||||||
|
cli->ws.opcode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rem_cli(Client *cli) {
|
||||||
|
for(size_t i = 0; i < clientsSz; i++) {
|
||||||
|
if(clients[i] == cli) {
|
||||||
|
memmove(clients + i, clients + i + 1, sizeof(*clients) * (clientsSz - i - 1));
|
||||||
|
clientsSz--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Socket ServSock;
|
||||||
|
static EPoll EP;
|
||||||
|
|
||||||
|
static int Argc;
|
||||||
|
static char **Argv;
|
||||||
|
|
||||||
|
static const char *get_arg(const char *key, const char *def) {
|
||||||
|
int z = strlen(key);
|
||||||
|
|
||||||
|
for(size_t i = 1; i < Argc; i++) {
|
||||||
|
if(strlen(Argv[i]) > z && strstr(Argv[i], key) == Argv[i] && Argv[i][z] == '=') {
|
||||||
|
return Argv[i] + z + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_arg_bool(const char *key) {
|
||||||
|
const char *val = get_arg(key, "0");
|
||||||
|
return strtol(val, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
Argc = argc, Argv = argv;
|
||||||
|
|
||||||
|
const char *streamkey = get_arg("key", NULL);
|
||||||
|
if(!streamkey) {
|
||||||
|
puts("Missing stream key parameter key=...");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidStreamPath = calloc(1, 6 + strlen(streamkey) + 1);
|
||||||
|
strcat(ValidStreamPath, "/push/");
|
||||||
|
strcat(ValidStreamPath, streamkey);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &(WSADATA) {});
|
||||||
|
|
||||||
|
ServSock = socket(AF_INET6, SOCK_STREAM, 0);
|
||||||
|
ioctlsocket(ServSock, FIONBIO, &(u_long) {1});
|
||||||
|
#else
|
||||||
|
ServSock = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||||
|
#endif
|
||||||
|
EP = epoll_create1(0);
|
||||||
|
|
||||||
|
assert(ServSock != -1);
|
||||||
|
|
||||||
|
if(get_arg_bool("reuseaddr")) {
|
||||||
|
setsockopt(ServSock, SOL_SOCKET, SO_REUSEADDR, &(int) {1}, sizeof(int));
|
||||||
|
}
|
||||||
|
setsockopt(ServSock, IPPROTO_IPV6, IPV6_V6ONLY, &(int) {0}, sizeof(int));
|
||||||
|
|
||||||
|
struct addrinfo *res = NULL;
|
||||||
|
assert(getaddrinfo(NULL, get_arg("port", "25404"), &(struct addrinfo) {.ai_flags = AI_PASSIVE, .ai_family = AF_INET6}, &res) == 0);
|
||||||
|
|
||||||
|
assert(bind(ServSock, res->ai_addr, res->ai_addrlen) >= 0);
|
||||||
|
|
||||||
|
freeaddrinfo(res);
|
||||||
|
|
||||||
|
assert(listen(ServSock, 16) >= 0);
|
||||||
|
|
||||||
|
epoll_ctl(EP, EPOLL_CTL_ADD, ServSock, &(struct epoll_event) {.events = EPOLLIN | EPOLLOUT, .data = {.fd = ServSock}});
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
#define BUFSZ 8192
|
||||||
|
char buf[BUFSZ];
|
||||||
|
|
||||||
|
#define EPOLL_EVS 2048
|
||||||
|
struct epoll_event events[EPOLL_EVS];
|
||||||
|
int nfds = epoll_wait(EP, events, EPOLL_EVS, -1);
|
||||||
|
|
||||||
|
for(int i = 0; i < nfds; i++) {
|
||||||
|
if(events[i].data.fd == ServSock) {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t addrlen;
|
||||||
|
|
||||||
|
Socket clisock = accept(ServSock, (struct sockaddr*) &addr, &addrlen);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
ioctlsocket(clisock, FIONBIO, &(u_long) {1});
|
||||||
|
#else
|
||||||
|
if(fcntl(clisock, F_SETFL, fcntl(clisock, F_GETFL, 0) | O_NONBLOCK) == -1) {
|
||||||
|
closesocket(clisock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Client *cli = calloc(1, sizeof(*cli));
|
||||||
|
cli->fd = clisock;
|
||||||
|
cli->len = 0;
|
||||||
|
cli->buf = malloc(cli->cap = 8192);
|
||||||
|
|
||||||
|
epoll_ctl(EP, EPOLL_CTL_ADD, clisock, &(struct epoll_event) {.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP, .data = {.ptr = cli}});
|
||||||
|
|
||||||
|
clients = realloc(clients, sizeof(*clients) * (clientsSz + 1));
|
||||||
|
clients[clientsSz++] = cli;
|
||||||
|
} else {
|
||||||
|
Client *cli = events[i].data.ptr;
|
||||||
|
|
||||||
|
bool forceclose = 0;
|
||||||
|
|
||||||
|
if(events[i].events & EPOLLIN) {
|
||||||
|
while(1) {
|
||||||
|
ssize_t readcount = recv(cli->fd, buf, sizeof(buf), 0);
|
||||||
|
|
||||||
|
if(readcount <= 0) {
|
||||||
|
if(errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||||
|
forceclose = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cli->len + readcount > cli->cap) {
|
||||||
|
cli->buf = realloc(cli->buf, cli->cap = ((cli->len + readcount + 4095) & ~4095));
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(cli->buf + cli->len, buf, readcount);
|
||||||
|
|
||||||
|
cli->prevlen = cli->len;
|
||||||
|
cli->len += readcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handle(cli) == 0) {
|
||||||
|
forceclose = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(forceclose || (events[i].events & (EPOLLRDHUP | EPOLLHUP))) {
|
||||||
|
epoll_ctl(EP, EPOLL_CTL_DEL, cli->fd, NULL);
|
||||||
|
closesocket(cli->fd);
|
||||||
|
|
||||||
|
rem_cli(cli);
|
||||||
|
|
||||||
|
free(cli->buf);
|
||||||
|
|
||||||
|
free(cli->ws.incoming);
|
||||||
|
|
||||||
|
free(cli);
|
||||||
|
|
||||||
|
printf("Client left, now at %lu\n", clientsSz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19313
mongoose.c
19313
mongoose.c
File diff suppressed because it is too large
Load Diff
3220
mongoose.h
3220
mongoose.h
File diff suppressed because it is too large
Load Diff
685
picohttpparser.c
Normal file
685
picohttpparser.c
Normal file
@@ -0,0 +1,685 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||||
|
* Shigeo Mitsunari
|
||||||
|
*
|
||||||
|
* The software is licensed under either the MIT License (below) or the Perl
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifdef __SSE4_2__
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <nmmintrin.h>
|
||||||
|
#else
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#include "picohttpparser.h"
|
||||||
|
|
||||||
|
#if __GNUC__ >= 3
|
||||||
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
#else
|
||||||
|
#define likely(x) (x)
|
||||||
|
#define unlikely(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define ALIGNED(n) _declspec(align(n))
|
||||||
|
#else
|
||||||
|
#define ALIGNED(n) __attribute__((aligned(n)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
|
||||||
|
|
||||||
|
#define CHECK_EOF() \
|
||||||
|
if (buf == buf_end) { \
|
||||||
|
*ret = -2; \
|
||||||
|
return NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXPECT_CHAR_NO_CHECK(ch) \
|
||||||
|
if (*buf++ != ch) { \
|
||||||
|
*ret = -1; \
|
||||||
|
return NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXPECT_CHAR(ch) \
|
||||||
|
CHECK_EOF(); \
|
||||||
|
EXPECT_CHAR_NO_CHECK(ch);
|
||||||
|
|
||||||
|
#define ADVANCE_TOKEN(tok, toklen) \
|
||||||
|
do { \
|
||||||
|
const char *tok_start = buf; \
|
||||||
|
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
|
||||||
|
int found2; \
|
||||||
|
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
|
||||||
|
if (!found2) { \
|
||||||
|
CHECK_EOF(); \
|
||||||
|
} \
|
||||||
|
while (1) { \
|
||||||
|
if (*buf == ' ') { \
|
||||||
|
break; \
|
||||||
|
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
|
||||||
|
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
|
||||||
|
*ret = -1; \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
++buf; \
|
||||||
|
CHECK_EOF(); \
|
||||||
|
} \
|
||||||
|
tok = tok_start; \
|
||||||
|
toklen = buf - tok_start; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
|
||||||
|
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
|
||||||
|
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||||
|
|
||||||
|
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
|
||||||
|
{
|
||||||
|
*found = 0;
|
||||||
|
#if __SSE4_2__
|
||||||
|
if (likely(buf_end - buf >= 16)) {
|
||||||
|
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
|
||||||
|
|
||||||
|
size_t left = (buf_end - buf) & ~15;
|
||||||
|
do {
|
||||||
|
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
|
||||||
|
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
|
||||||
|
if (unlikely(r != 16)) {
|
||||||
|
buf += r;
|
||||||
|
*found = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf += 16;
|
||||||
|
left -= 16;
|
||||||
|
} while (likely(left != 0));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/* suppress unused parameter warning */
|
||||||
|
(void)buf_end;
|
||||||
|
(void)ranges;
|
||||||
|
(void)ranges_size;
|
||||||
|
#endif
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
|
||||||
|
{
|
||||||
|
const char *token_start = buf;
|
||||||
|
|
||||||
|
#ifdef __SSE4_2__
|
||||||
|
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
|
||||||
|
"\012\037" /* allow SP and up to but not including DEL */
|
||||||
|
"\177\177"; /* allow chars w. MSB set */
|
||||||
|
int found;
|
||||||
|
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
|
||||||
|
if (found)
|
||||||
|
goto FOUND_CTL;
|
||||||
|
#else
|
||||||
|
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
|
||||||
|
while (likely(buf_end - buf >= 8)) {
|
||||||
|
#define DOIT() \
|
||||||
|
do { \
|
||||||
|
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
|
||||||
|
goto NonPrintable; \
|
||||||
|
++buf; \
|
||||||
|
} while (0)
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
#undef DOIT
|
||||||
|
continue;
|
||||||
|
NonPrintable:
|
||||||
|
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||||
|
goto FOUND_CTL;
|
||||||
|
}
|
||||||
|
++buf;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (;; ++buf) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
|
||||||
|
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||||
|
goto FOUND_CTL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FOUND_CTL:
|
||||||
|
if (likely(*buf == '\015')) {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
*token_len = buf - 2 - token_start;
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
*token_len = buf - token_start;
|
||||||
|
++buf;
|
||||||
|
} else {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*token = token_start;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
|
||||||
|
{
|
||||||
|
int ret_cnt = 0;
|
||||||
|
buf = last_len < 3 ? buf : buf + last_len - 3;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
++ret_cnt;
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
++ret_cnt;
|
||||||
|
} else {
|
||||||
|
++buf;
|
||||||
|
ret_cnt = 0;
|
||||||
|
}
|
||||||
|
if (ret_cnt == 2) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*ret = -2;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PARSE_INT(valp_, mul_) \
|
||||||
|
if (*buf < '0' || '9' < *buf) { \
|
||||||
|
buf++; \
|
||||||
|
*ret = -1; \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
*(valp_) = (mul_) * (*buf++ - '0');
|
||||||
|
|
||||||
|
#define PARSE_INT_3(valp_) \
|
||||||
|
do { \
|
||||||
|
int res_ = 0; \
|
||||||
|
PARSE_INT(&res_, 100) \
|
||||||
|
*valp_ = res_; \
|
||||||
|
PARSE_INT(&res_, 10) \
|
||||||
|
*valp_ += res_; \
|
||||||
|
PARSE_INT(&res_, 1) \
|
||||||
|
*valp_ += res_; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* returned pointer is always within [buf, buf_end), or null */
|
||||||
|
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
|
||||||
|
int *ret)
|
||||||
|
{
|
||||||
|
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
|
||||||
|
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
|
||||||
|
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
|
||||||
|
"\"\"" /* 0x22 */
|
||||||
|
"()" /* 0x28,0x29 */
|
||||||
|
",," /* 0x2c */
|
||||||
|
"//" /* 0x2f */
|
||||||
|
":@" /* 0x3a-0x40 */
|
||||||
|
"[]" /* 0x5b-0x5d */
|
||||||
|
"{\xff"; /* 0x7b-0xff */
|
||||||
|
const char *buf_start = buf;
|
||||||
|
int found;
|
||||||
|
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
|
||||||
|
if (!found) {
|
||||||
|
CHECK_EOF();
|
||||||
|
}
|
||||||
|
while (1) {
|
||||||
|
if (*buf == next_char) {
|
||||||
|
break;
|
||||||
|
} else if (!token_char_map[(unsigned char)*buf]) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
}
|
||||||
|
*token = buf_start;
|
||||||
|
*token_len = buf - buf_start;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returned pointer is always within [buf, buf_end), or null */
|
||||||
|
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
|
||||||
|
{
|
||||||
|
/* we want at least [HTTP/1.<two chars>] to try to parse */
|
||||||
|
if (buf_end - buf < 9) {
|
||||||
|
*ret = -2;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
EXPECT_CHAR_NO_CHECK('H');
|
||||||
|
EXPECT_CHAR_NO_CHECK('T');
|
||||||
|
EXPECT_CHAR_NO_CHECK('T');
|
||||||
|
EXPECT_CHAR_NO_CHECK('P');
|
||||||
|
EXPECT_CHAR_NO_CHECK('/');
|
||||||
|
EXPECT_CHAR_NO_CHECK('1');
|
||||||
|
EXPECT_CHAR_NO_CHECK('.');
|
||||||
|
PARSE_INT(minor_version, 1);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
|
||||||
|
size_t max_headers, int *ret)
|
||||||
|
{
|
||||||
|
for (;; ++*num_headers) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
break;
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*num_headers == max_headers) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
|
||||||
|
/* parsing name, but do not discard SP before colon, see
|
||||||
|
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
|
||||||
|
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (headers[*num_headers].name_len == 0) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
++buf;
|
||||||
|
for (;; ++buf) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (!(*buf == ' ' || *buf == '\t')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers[*num_headers].name = NULL;
|
||||||
|
headers[*num_headers].name_len = 0;
|
||||||
|
}
|
||||||
|
const char *value;
|
||||||
|
size_t value_len;
|
||||||
|
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* remove trailing SPs and HTABs */
|
||||||
|
const char *value_end = value + value_len;
|
||||||
|
for (; value_end != value; --value_end) {
|
||||||
|
const char c = *(value_end - 1);
|
||||||
|
if (!(c == ' ' || c == '\t')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers[*num_headers].value = value;
|
||||||
|
headers[*num_headers].value_len = value_end - value;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
|
||||||
|
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
|
||||||
|
size_t max_headers, int *ret)
|
||||||
|
{
|
||||||
|
/* skip first empty line (some clients add CRLF after POST content) */
|
||||||
|
CHECK_EOF();
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse request line */
|
||||||
|
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
} while (*buf == ' ');
|
||||||
|
ADVANCE_TOKEN(*path, *path_len);
|
||||||
|
do {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
} while (*buf == ' ');
|
||||||
|
if (*method_len == 0 || *path_len == 0) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
} else {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
|
||||||
|
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||||
|
{
|
||||||
|
const char *buf = buf_start, *buf_end = buf_start + len;
|
||||||
|
size_t max_headers = *num_headers;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
*method = NULL;
|
||||||
|
*method_len = 0;
|
||||||
|
*path = NULL;
|
||||||
|
*path_len = 0;
|
||||||
|
*minor_version = -1;
|
||||||
|
*num_headers = 0;
|
||||||
|
|
||||||
|
/* if last_len != 0, check if the request is complete (a fast countermeasure
|
||||||
|
againt slowloris */
|
||||||
|
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
|
||||||
|
&r)) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(buf - buf_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
|
||||||
|
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
|
||||||
|
{
|
||||||
|
/* parse "HTTP/1.x" */
|
||||||
|
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* skip space */
|
||||||
|
if (*buf != ' ') {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
} while (*buf == ' ');
|
||||||
|
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
|
||||||
|
if (buf_end - buf < 4) {
|
||||||
|
*ret = -2;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PARSE_INT_3(status);
|
||||||
|
|
||||||
|
/* get message including preceding space */
|
||||||
|
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (*msg_len == 0) {
|
||||||
|
/* ok */
|
||||||
|
} else if (**msg == ' ') {
|
||||||
|
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
|
||||||
|
* before running past the end of the given buffer. */
|
||||||
|
do {
|
||||||
|
++*msg;
|
||||||
|
--*msg_len;
|
||||||
|
} while (**msg == ' ');
|
||||||
|
} else {
|
||||||
|
/* garbage found after status code */
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||||
|
struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||||
|
{
|
||||||
|
const char *buf = buf_start, *buf_end = buf + len;
|
||||||
|
size_t max_headers = *num_headers;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
*minor_version = -1;
|
||||||
|
*status = 0;
|
||||||
|
*msg = NULL;
|
||||||
|
*msg_len = 0;
|
||||||
|
*num_headers = 0;
|
||||||
|
|
||||||
|
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||||
|
against slowloris */
|
||||||
|
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(buf - buf_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||||
|
{
|
||||||
|
const char *buf = buf_start, *buf_end = buf + len;
|
||||||
|
size_t max_headers = *num_headers;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
*num_headers = 0;
|
||||||
|
|
||||||
|
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||||
|
against slowloris */
|
||||||
|
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(buf - buf_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CHUNKED_IN_CHUNK_SIZE,
|
||||||
|
CHUNKED_IN_CHUNK_EXT,
|
||||||
|
CHUNKED_IN_CHUNK_DATA,
|
||||||
|
CHUNKED_IN_CHUNK_CRLF,
|
||||||
|
CHUNKED_IN_TRAILERS_LINE_HEAD,
|
||||||
|
CHUNKED_IN_TRAILERS_LINE_MIDDLE
|
||||||
|
};
|
||||||
|
|
||||||
|
static int decode_hex(int ch)
|
||||||
|
{
|
||||||
|
if ('0' <= ch && ch <= '9') {
|
||||||
|
return ch - '0';
|
||||||
|
} else if ('A' <= ch && ch <= 'F') {
|
||||||
|
return ch - 'A' + 0xa;
|
||||||
|
} else if ('a' <= ch && ch <= 'f') {
|
||||||
|
return ch - 'a' + 0xa;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
|
||||||
|
{
|
||||||
|
size_t dst = 0, src = 0, bufsz = *_bufsz;
|
||||||
|
ssize_t ret = -2; /* incomplete */
|
||||||
|
|
||||||
|
decoder->_total_read += bufsz;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
switch (decoder->_state) {
|
||||||
|
case CHUNKED_IN_CHUNK_SIZE:
|
||||||
|
for (;; ++src) {
|
||||||
|
int v;
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if ((v = decode_hex(buf[src])) == -1) {
|
||||||
|
if (decoder->_hex_count == 0) {
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
/* the only characters that may appear after the chunk size are BWS, semicolon, or CRLF */
|
||||||
|
switch (buf[src]) {
|
||||||
|
case ' ':
|
||||||
|
case '\011':
|
||||||
|
case ';':
|
||||||
|
case '\012':
|
||||||
|
case '\015':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (decoder->_hex_count == sizeof(size_t) * 2) {
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
|
||||||
|
++decoder->_hex_count;
|
||||||
|
}
|
||||||
|
decoder->_hex_count = 0;
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_EXT;
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_CHUNK_EXT:
|
||||||
|
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] == '\012')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++src;
|
||||||
|
if (decoder->bytes_left_in_chunk == 0) {
|
||||||
|
if (decoder->consume_trailer) {
|
||||||
|
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
goto Complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_DATA;
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_CHUNK_DATA: {
|
||||||
|
size_t avail = bufsz - src;
|
||||||
|
if (avail < decoder->bytes_left_in_chunk) {
|
||||||
|
if (dst != src)
|
||||||
|
memmove(buf + dst, buf + src, avail);
|
||||||
|
src += avail;
|
||||||
|
dst += avail;
|
||||||
|
decoder->bytes_left_in_chunk -= avail;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
if (dst != src)
|
||||||
|
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
|
||||||
|
src += decoder->bytes_left_in_chunk;
|
||||||
|
dst += decoder->bytes_left_in_chunk;
|
||||||
|
decoder->bytes_left_in_chunk = 0;
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
|
||||||
|
}
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_CHUNK_CRLF:
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] != '\015')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf[src] != '\012') {
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
++src;
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
|
||||||
|
break;
|
||||||
|
case CHUNKED_IN_TRAILERS_LINE_HEAD:
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] != '\015')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf[src++] == '\012')
|
||||||
|
goto Complete;
|
||||||
|
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] == '\012')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++src;
|
||||||
|
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(!"decoder is corrupt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Complete:
|
||||||
|
ret = bufsz - src;
|
||||||
|
Exit:
|
||||||
|
if (dst != src)
|
||||||
|
memmove(buf + dst, buf + src, bufsz - src);
|
||||||
|
*_bufsz = dst;
|
||||||
|
/* if incomplete but the overhead of the chunked encoding is >=100KB and >80%, signal an error */
|
||||||
|
if (ret == -2) {
|
||||||
|
decoder->_total_overhead += bufsz - dst;
|
||||||
|
if (decoder->_total_overhead >= 100 * 1024 && decoder->_total_read - decoder->_total_overhead < decoder->_total_read / 4)
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
|
||||||
|
{
|
||||||
|
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CHECK_EOF
|
||||||
|
#undef EXPECT_CHAR
|
||||||
|
#undef ADVANCE_TOKEN
|
||||||
90
picohttpparser.h
Normal file
90
picohttpparser.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||||
|
* Shigeo Mitsunari
|
||||||
|
*
|
||||||
|
* The software is licensed under either the MIT License (below) or the Perl
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef picohttpparser_h
|
||||||
|
#define picohttpparser_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define ssize_t intptr_t
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* contains name and value of a header (name == NULL if is a continuing line
|
||||||
|
* of a multiline header */
|
||||||
|
struct phr_header {
|
||||||
|
const char *name;
|
||||||
|
size_t name_len;
|
||||||
|
const char *value;
|
||||||
|
size_t value_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* returns number of bytes consumed if successful, -2 if request is partial,
|
||||||
|
* -1 if failed */
|
||||||
|
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
|
||||||
|
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||||
|
|
||||||
|
/* ditto */
|
||||||
|
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||||
|
struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||||
|
|
||||||
|
/* ditto */
|
||||||
|
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||||
|
|
||||||
|
/* should be zero-filled before start */
|
||||||
|
struct phr_chunked_decoder {
|
||||||
|
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
|
||||||
|
char consume_trailer; /* if trailing headers should be consumed */
|
||||||
|
char _hex_count;
|
||||||
|
char _state;
|
||||||
|
uint64_t _total_read;
|
||||||
|
uint64_t _total_overhead;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
|
||||||
|
* encoding headers. When the function returns without an error, bufsz is
|
||||||
|
* updated to the length of the decoded data available. Applications should
|
||||||
|
* repeatedly call the function while it returns -2 (incomplete) every time
|
||||||
|
* supplying newly arrived data. If the end of the chunked-encoded data is
|
||||||
|
* found, the function returns a non-negative number indicating the number of
|
||||||
|
* octets left undecoded, that starts from the offset returned by `*bufsz`.
|
||||||
|
* Returns -1 on error.
|
||||||
|
*/
|
||||||
|
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
|
||||||
|
|
||||||
|
/* returns if the chunked decoder is in middle of chunked data */
|
||||||
|
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
96
rawpcmworklet.js
Normal file
96
rawpcmworklet.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// To explain succinctly, the people who designed AudioWorklet and
|
||||||
|
// deprecated ScriptProcessorNode are retarded and we need a worklet
|
||||||
|
// that does basically nothing
|
||||||
|
|
||||||
|
// Must be careful to create as little garbage as possible otherwise
|
||||||
|
// even this will produce cracks/pops/clicks/blips.
|
||||||
|
// It was like this on Firefox; Chromium managed.
|
||||||
|
|
||||||
|
class RawPCMWorklet extends AudioWorkletProcessor {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.ringL = new Float32Array(144000)
|
||||||
|
this.ringR = new Float32Array(144000)
|
||||||
|
this.ringRead = 0
|
||||||
|
this.mute = true
|
||||||
|
|
||||||
|
for(var z = 0; z < 65536; z++) {
|
||||||
|
this.ringL[z] = Math.sin(z / 128 * 2 * Math.PI) * 0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port.onmessage = (event) => {
|
||||||
|
if(event.data === true || event.data === false) {
|
||||||
|
this.mute = event.data
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newaudioframes = event.data
|
||||||
|
var writeIndex = newaudioframes.t
|
||||||
|
|
||||||
|
var newlen = newaudioframes.left.length
|
||||||
|
|
||||||
|
if(newaudioframes.left.length > this.ringL.length) {
|
||||||
|
newaudioframes.left = newaudioframes.left.slice(newaudioframes.left.length - this.ringL.length)
|
||||||
|
newaudioframes.right = newaudioframes.right.slice(newaudioframes.right.length - this.ringL.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(writeIndex % this.ringL.length + newaudioframes.left.length <= this.ringL.length) {
|
||||||
|
this.ringL.set(newaudioframes.left, writeIndex % this.ringL.length)
|
||||||
|
this.ringR.set(newaudioframes.right, writeIndex % this.ringL.length)
|
||||||
|
} else {
|
||||||
|
var boundary = this.ringL.length - writeIndex % this.ringL.length
|
||||||
|
|
||||||
|
this.ringL.set(newaudioframes.left.slice(0, boundary), writeIndex % this.ringL.length)
|
||||||
|
this.ringL.set(newaudioframes.left.slice(boundary), 0)
|
||||||
|
|
||||||
|
this.ringR.set(newaudioframes.right.slice(0, boundary), writeIndex % this.ringL.length)
|
||||||
|
this.ringR.set(newaudioframes.right.slice(boundary), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
|
const output = outputs[0]
|
||||||
|
|
||||||
|
var left = output[0]
|
||||||
|
var right = output[1]
|
||||||
|
|
||||||
|
/*if(this.ringWrite < 16384) {
|
||||||
|
return true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//var available = Math.min(left.length, Math.max(0, this.ringWrite - this.ringRead))
|
||||||
|
var available = left.length
|
||||||
|
|
||||||
|
if(this.mute === false) {
|
||||||
|
if(this.ringRead % this.ringL.length + available <= this.ringL.length) {
|
||||||
|
left.set(this.ringL.slice(this.ringRead % this.ringL.length, this.ringRead % this.ringL.length + available))
|
||||||
|
right.set(this.ringR.slice(this.ringRead % this.ringL.length, this.ringRead % this.ringL.length + available))
|
||||||
|
} else {
|
||||||
|
left.set(this.ringL.slice(this.ringRead % this.ringL.length))
|
||||||
|
right.set(this.ringR.slice(this.ringRead % this.ringL.length))
|
||||||
|
|
||||||
|
var boundary = this.ringL.length - this.ringRead % this.ringL.length
|
||||||
|
|
||||||
|
left.set(this.ringL.slice(0, available - boundary), boundary)
|
||||||
|
right.set(this.ringR.slice(0, available - boundary), boundary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ringRead += left.length
|
||||||
|
|
||||||
|
//console.log(this.ringRead / 44100)
|
||||||
|
|
||||||
|
/*for(var s = 0; s < available; s++) {
|
||||||
|
var sw = Math.sin((this.debug + s) / 48000 * 440 * 2 * 3.1415926) * 0.3
|
||||||
|
left[s] = sw
|
||||||
|
right[s] = sw
|
||||||
|
}
|
||||||
|
this.debug += available*/
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('rawpcmworklet', RawPCMWorklet);
|
||||||
201
teeny-sha1.c
Normal file
201
teeny-sha1.c
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Teeny SHA-1
|
||||||
|
*
|
||||||
|
* The below sha1digest() calculates a SHA-1 hash value for a
|
||||||
|
* specified data buffer and generates a hex representation of the
|
||||||
|
* result. This implementation is a re-forming of the SHA-1 code at
|
||||||
|
* https://github.com/jinqiangshou/EncryptionLibrary.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 CTrabant
|
||||||
|
*
|
||||||
|
* License: MIT, see included LICENSE file for details.
|
||||||
|
*
|
||||||
|
* To use the sha1digest() function either copy it into an existing
|
||||||
|
* project source code file or include this file in a project and put
|
||||||
|
* the declaration (example below) in the sources files where needed.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Declaration:
|
||||||
|
extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* sha1digest: https://github.com/CTrabant/teeny-sha1
|
||||||
|
*
|
||||||
|
* Calculate the SHA-1 value for supplied data buffer and generate a
|
||||||
|
* text representation in hexadecimal.
|
||||||
|
*
|
||||||
|
* Based on https://github.com/jinqiangshou/EncryptionLibrary, credit
|
||||||
|
* goes to @jinqiangshou, all new bugs are mine.
|
||||||
|
*
|
||||||
|
* @input:
|
||||||
|
* data -- data to be hashed
|
||||||
|
* databytes -- bytes in data buffer to be hashed
|
||||||
|
*
|
||||||
|
* @output:
|
||||||
|
* digest -- the result, MUST be at least 20 bytes
|
||||||
|
* hexdigest -- the result in hex, MUST be at least 41 bytes
|
||||||
|
*
|
||||||
|
* At least one of the output buffers must be supplied. The other, if not
|
||||||
|
* desired, may be set to NULL.
|
||||||
|
*
|
||||||
|
* @return: 0 on success and non-zero on error.
|
||||||
|
******************************************************************************/
|
||||||
|
int
|
||||||
|
sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes)
|
||||||
|
{
|
||||||
|
#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||||
|
|
||||||
|
uint32_t W[80];
|
||||||
|
uint32_t H[] = {0x67452301,
|
||||||
|
0xEFCDAB89,
|
||||||
|
0x98BADCFE,
|
||||||
|
0x10325476,
|
||||||
|
0xC3D2E1F0};
|
||||||
|
uint32_t a;
|
||||||
|
uint32_t b;
|
||||||
|
uint32_t c;
|
||||||
|
uint32_t d;
|
||||||
|
uint32_t e;
|
||||||
|
uint32_t f = 0;
|
||||||
|
uint32_t k = 0;
|
||||||
|
|
||||||
|
uint32_t idx;
|
||||||
|
uint32_t lidx;
|
||||||
|
uint32_t widx;
|
||||||
|
uint32_t didx = 0;
|
||||||
|
|
||||||
|
int32_t wcount;
|
||||||
|
uint32_t temp;
|
||||||
|
uint64_t databits = ((uint64_t)databytes) * 8;
|
||||||
|
uint32_t loopcount = (databytes + 8) / 64 + 1;
|
||||||
|
uint32_t tailbytes = 64 * loopcount - databytes;
|
||||||
|
uint8_t datatail[128] = {0};
|
||||||
|
|
||||||
|
if (!digest && !hexdigest)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Pre-processing of data tail (includes padding to fill out 512-bit chunk):
|
||||||
|
Add bit '1' to end of message (big-endian)
|
||||||
|
Add 64-bit message length in bits at very end (big-endian) */
|
||||||
|
datatail[0] = 0x80;
|
||||||
|
datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF);
|
||||||
|
datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF);
|
||||||
|
datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF);
|
||||||
|
datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF);
|
||||||
|
datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF);
|
||||||
|
datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF);
|
||||||
|
datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF);
|
||||||
|
datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF);
|
||||||
|
|
||||||
|
/* Process each 512-bit chunk */
|
||||||
|
for (lidx = 0; lidx < loopcount; lidx++)
|
||||||
|
{
|
||||||
|
/* Compute all elements in W */
|
||||||
|
memset (W, 0, 80 * sizeof (uint32_t));
|
||||||
|
|
||||||
|
/* Break 512-bit chunk into sixteen 32-bit, big endian words */
|
||||||
|
for (widx = 0; widx <= 15; widx++)
|
||||||
|
{
|
||||||
|
wcount = 24;
|
||||||
|
|
||||||
|
/* Copy byte-per byte from specified buffer */
|
||||||
|
while (didx < databytes && wcount >= 0)
|
||||||
|
{
|
||||||
|
W[widx] += (((uint32_t)data[didx]) << wcount);
|
||||||
|
didx++;
|
||||||
|
wcount -= 8;
|
||||||
|
}
|
||||||
|
/* Fill out W with padding as needed */
|
||||||
|
while (wcount >= 0)
|
||||||
|
{
|
||||||
|
W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount);
|
||||||
|
didx++;
|
||||||
|
wcount -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from:
|
||||||
|
"Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */
|
||||||
|
for (widx = 16; widx <= 31; widx++)
|
||||||
|
{
|
||||||
|
W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1);
|
||||||
|
}
|
||||||
|
for (widx = 32; widx <= 79; widx++)
|
||||||
|
{
|
||||||
|
W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main loop */
|
||||||
|
a = H[0];
|
||||||
|
b = H[1];
|
||||||
|
c = H[2];
|
||||||
|
d = H[3];
|
||||||
|
e = H[4];
|
||||||
|
|
||||||
|
for (idx = 0; idx <= 79; idx++)
|
||||||
|
{
|
||||||
|
if (idx <= 19)
|
||||||
|
{
|
||||||
|
f = (b & c) | ((~b) & d);
|
||||||
|
k = 0x5A827999;
|
||||||
|
}
|
||||||
|
else if (idx >= 20 && idx <= 39)
|
||||||
|
{
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
k = 0x6ED9EBA1;
|
||||||
|
}
|
||||||
|
else if (idx >= 40 && idx <= 59)
|
||||||
|
{
|
||||||
|
f = (b & c) | (b & d) | (c & d);
|
||||||
|
k = 0x8F1BBCDC;
|
||||||
|
}
|
||||||
|
else if (idx >= 60 && idx <= 79)
|
||||||
|
{
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
k = 0xCA62C1D6;
|
||||||
|
}
|
||||||
|
temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx];
|
||||||
|
e = d;
|
||||||
|
d = c;
|
||||||
|
c = SHA1ROTATELEFT (b, 30);
|
||||||
|
b = a;
|
||||||
|
a = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
H[0] += a;
|
||||||
|
H[1] += b;
|
||||||
|
H[2] += c;
|
||||||
|
H[3] += d;
|
||||||
|
H[4] += e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store binary digest in supplied buffer */
|
||||||
|
if (digest)
|
||||||
|
{
|
||||||
|
for (idx = 0; idx < 5; idx++)
|
||||||
|
{
|
||||||
|
digest[idx * 4 + 0] = (uint8_t) (H[idx] >> 24);
|
||||||
|
digest[idx * 4 + 1] = (uint8_t) (H[idx] >> 16);
|
||||||
|
digest[idx * 4 + 2] = (uint8_t) (H[idx] >> 8);
|
||||||
|
digest[idx * 4 + 3] = (uint8_t) (H[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store hex version of digest in supplied buffer */
|
||||||
|
if (hexdigest)
|
||||||
|
{
|
||||||
|
snprintf (hexdigest, 41, "%08x%08x%08x%08x%08x",
|
||||||
|
H[0],H[1],H[2],H[3],H[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} /* End of sha1digest() */
|
||||||
Reference in New Issue
Block a user