From b1edf0a3e09fbd4e844ed60e1ffd1c1871a3ce73 Mon Sep 17 00:00:00 2001 From: notch Date: Mon, 28 Dec 2020 13:42:39 +0800 Subject: [PATCH] remove wsp service --- demos/wsp/free.player.1.8.js | 9466 ---------------------------------- demos/wsp/index.html | 251 - demos/wsp/style.css | 28 - service/service.go | 6 - service/streamapis.go | 6 - service/wsp/protocol.go | 170 - service/wsp/protocol_test.go | 69 - service/wsp/session.go | 461 -- service/wsp/types.go | 10 - service/wsp/wsp.go | 124 - 10 files changed, 10591 deletions(-) delete mode 100644 demos/wsp/free.player.1.8.js delete mode 100755 demos/wsp/index.html delete mode 100755 demos/wsp/style.css delete mode 100755 service/wsp/protocol.go delete mode 100755 service/wsp/protocol_test.go delete mode 100755 service/wsp/session.go delete mode 100644 service/wsp/types.go delete mode 100755 service/wsp/wsp.go diff --git a/demos/wsp/free.player.1.8.js b/demos/wsp/free.player.1.8.js deleted file mode 100644 index bce5dd4..0000000 --- a/demos/wsp/free.player.1.8.js +++ /dev/null @@ -1,9466 +0,0 @@ -(function () { -'use strict'; - -// ERROR=0, WARN=1, LOG=2, DEBUG=3 -const LogLevel = { - Error: 0, - Warn: 1, - Log: 2, - Debug: 3 -}; - -let DEFAULT_LOG_LEVEL = LogLevel.Debug; - -function setDefaultLogLevel(level) { - DEFAULT_LOG_LEVEL = level; -} -class Logger { - constructor(level = DEFAULT_LOG_LEVEL, tag) { - this.tag = tag; - this.setLevel(level); - } - - setLevel(level) { - this.level = level; - } - - static get level_map() { return { - [LogLevel.Debug]:'log', - [LogLevel.Log]:'log', - [LogLevel.Warn]:'warn', - [LogLevel.Error]:'error' - }}; - - _log(lvl, args) { - args = Array.prototype.slice.call(args); - if (this.tag) { - args.unshift(`[${this.tag}]`); - } - if (this.level>=lvl) console[Logger.level_map[lvl]].apply(console, args); - } - log(){ - this._log(LogLevel.Log, arguments); - } - debug(){ - this._log(LogLevel.Debug, arguments); - } - error(){ - this._log(LogLevel.Error, arguments); - } - warn(){ - this._log(LogLevel.Warn, arguments); - } -} - -const taggedLoggers = new Map(); -function getTagged(tag) { - if (!taggedLoggers.has(tag)) { - taggedLoggers.set(tag, new Logger(DEFAULT_LOG_LEVEL, tag)); - } - return taggedLoggers.get(tag); -} -const Log = new Logger(); - -// export * from 'bp_logger'; - -class Url { - static parse(url) { - let ret = {}; - - let regex = /^([^:]+):\/\/([^\/]+)(.*)$/; //protocol, login, urlpath - let result = regex.exec(url); - - if (!result) { - throw new Error("bad url"); - } - - ret.full = url; - ret.protocol = result[1]; - ret.urlpath = result[3]; - - let parts = ret.urlpath.split('/'); - ret.basename = parts.pop().split(/\?|#/)[0]; - ret.basepath = parts.join('/'); - - let loginSplit = result[2].split('@'); - let hostport = loginSplit[0].split(':'); - let userpass = [ null, null ]; - if (loginSplit.length === 2) { - userpass = loginSplit[0].split(':'); - hostport = loginSplit[1].split(':'); - } - - ret.user = userpass[0]; - ret.pass = userpass[1]; - ret.host = hostport[0]; - ret.auth = (ret.user && ret.pass) ? `${ret.user}:${ret.pass}` : ''; - - ret.port = (null == hostport[1]) ? Url.protocolDefaultPort(ret.protocol) : hostport[1]; - ret.portDefined = (null != hostport[1]); - ret.location = `${ret.host}:${ret.port}`; - - if (ret.protocol == 'unix') { - ret.socket = ret.port; - ret.port = undefined; - } - - return ret; - } - - static full(parsed) { - return `${parsed.protocol}://${parsed.location}/${parsed.urlpath}`; - } - - static isAbsolute(url) { - return /^[^:]+:\/\//.test(url); - } - - static protocolDefaultPort(protocol) { - switch (protocol) { - case 'rtsp': return 554; - case 'http': return 80; - case 'https': return 443; - } - - return 0; - } -} - -const listener = Symbol("event_listener"); -const listeners = Symbol("event_listeners"); - -class DestructibleEventListener { - constructor(eventListener) { - this[listener] = eventListener; - this[listeners] = new Map(); - } - - clear() { - if (this[listeners]) { - for (let entry of this[listeners]) { - for (let fn of entry[1]) { - this[listener].removeEventListener(entry[0], fn); - } - } - } - this[listeners].clear(); - } - - destroy() { - this.clear(); - this[listeners] = null; - } - - on(event, selector, fn) { - if (fn == undefined) { - fn = selector; - selector = null; - } - if (selector) { - return this.addEventListener(event, (e) => { - if (e.target.matches(selector)) { - fn(e); - } - }); - } else { - return this.addEventListener(event, fn); - } - } - - addEventListener(event, fn) { - if (!this[listeners].has(event)) { - this[listeners].set(event, new Set()); - } - this[listeners].get(event).add(fn); - this[listener].addEventListener(event, fn, false); - return fn; - } - - removeEventListener(event, fn) { - this[listener].removeEventListener(event, fn, false); - if (this[listeners].has(event)) { - //this[listeners].set(event, new Set()); - let ev = this[listeners].get(event); - ev.delete(fn); - if (!ev.size) { - this[listeners].delete(event); - } - } - } - - dispatchEvent(event) { - if (this[listener]) { - this[listener].dispatchEvent(event); - } - } -} - -class EventEmitter { - constructor(element=null) { - this[listener] = new DestructibleEventListener(element || document.createElement('div')); - } - - clear() { - if (this[listener]) { - this[listener].clear(); - } - } - - destroy() { - if (this[listener]) { - this[listener].destroy(); - this[listener] = null; - } - } - - on(event, selector, fn) { - if (this[listener]) { - return this[listener].on(event, selector, fn); - } - return null; - } - - addEventListener(event, fn) { - if (this[listener]) { - return this[listener].addEventListener(event, fn, false); - } - return null; - } - - removeEventListener(event, fn) { - if (this[listener]) { - this[listener].removeEventListener(event, fn, false); - } - } - - dispatchEvent(event, data) { - if (this[listener]) { - this[listener].dispatchEvent(new CustomEvent(event, {detail: data})); - } - } -} - -class EventSourceWrapper { - constructor(eventSource) { - this.eventSource = eventSource; - this[listeners] = new Map(); - } - - on(event, selector, fn) { - if (!this[listeners].has(event)) { - this[listeners].set(event, new Set()); - } - let listener = this.eventSource.on(event, selector, fn); - if (listener) { - this[listeners].get(event).add(listener); - } - } - - off(event, fn){ - this.eventSource.removeEventListener(event, fn); - } - - clear() { - this.eventSource.clear(); - this[listeners].clear(); - } - - destroy() { - this.eventSource.clear(); - this[listeners] = null; - this.eventSource = null; - } -} - -// export * from 'bp_event'; - -/** - * Generate MP4 Box - * got from: https://github.com/dailymotion/hls.js - */ - -class MP4 { - static init() { - MP4.types = { - avc1: [], // codingname - avcC: [], - btrt: [], - dinf: [], - dref: [], - esds: [], - ftyp: [], - hdlr: [], - mdat: [], - mdhd: [], - mdia: [], - mfhd: [], - minf: [], - moof: [], - moov: [], - mp4a: [], - mvex: [], - mvhd: [], - sdtp: [], - stbl: [], - stco: [], - stsc: [], - stsd: [], - stsz: [], - stts: [], - tfdt: [], - tfhd: [], - traf: [], - trak: [], - trun: [], - trex: [], - tkhd: [], - vmhd: [], - smhd: [] - }; - - var i; - for (i in MP4.types) { - if (MP4.types.hasOwnProperty(i)) { - MP4.types[i] = [ - i.charCodeAt(0), - i.charCodeAt(1), - i.charCodeAt(2), - i.charCodeAt(3) - ]; - } - } - - var videoHdlr = new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x56, 0x69, 0x64, 0x65, - 0x6f, 0x48, 0x61, 0x6e, - 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' - ]); - - var audioHdlr = new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x53, 0x6f, 0x75, 0x6e, - 0x64, 0x48, 0x61, 0x6e, - 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' - ]); - - MP4.HDLR_TYPES = { - 'video': videoHdlr, - 'audio': audioHdlr - }; - - var dref = new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x01, // entry_count - 0x00, 0x00, 0x00, 0x0c, // entry_size - 0x75, 0x72, 0x6c, 0x20, // 'url' type - 0x00, // version 0 - 0x00, 0x00, 0x01 // entry_flags - ]); - - var stco = new Uint8Array([ - 0x00, // version - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x00 // entry_count - ]); - - MP4.STTS = MP4.STSC = MP4.STCO = stco; - - MP4.STSZ = new Uint8Array([ - 0x00, // version - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x00, // sample_size - 0x00, 0x00, 0x00, 0x00, // sample_count - ]); - MP4.VMHD = new Uint8Array([ - 0x00, // version - 0x00, 0x00, 0x01, // flags - 0x00, 0x00, // graphicsmode - 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x00 // opcolor - ]); - MP4.SMHD = new Uint8Array([ - 0x00, // version - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, // balance - 0x00, 0x00 // reserved - ]); - - MP4.STSD = new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x01]);// entry_count - - var majorBrand = new Uint8Array([105,115,111,109]); // isom - var avc1Brand = new Uint8Array([97,118,99,49]); // avc1 - var minorVersion = new Uint8Array([0, 0, 0, 1]); - - MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand); - MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref)); - } - - static box(type, ...payload) { - var size = 8, - i = payload.length, - len = i, - result; - // calculate the total size we need to allocate - while (i--) { - size += payload[i].byteLength; - } - result = new Uint8Array(size); - result[0] = (size >> 24) & 0xff; - result[1] = (size >> 16) & 0xff; - result[2] = (size >> 8) & 0xff; - result[3] = size & 0xff; - result.set(type, 4); - // copy the payload into the result - for (i = 0, size = 8; i < len; ++i) { - // copy payload[i] array @ offset size - result.set(payload[i], size); - size += payload[i].byteLength; - } - return result; - } - - static hdlr(type) { - return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]); - } - - static mdat(data) { - return MP4.box(MP4.types.mdat, data); - } - - static mdhd(timescale, duration) { - return MP4.box(MP4.types.mdhd, new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x02, // creation_time - 0x00, 0x00, 0x00, 0x03, // modification_time - (timescale >> 24) & 0xFF, - (timescale >> 16) & 0xFF, - (timescale >> 8) & 0xFF, - timescale & 0xFF, // timescale - (duration >> 24), - (duration >> 16) & 0xFF, - (duration >> 8) & 0xFF, - duration & 0xFF, // duration - 0x55, 0xc4, // 'und' language (undetermined) - 0x00, 0x00 - ])); - } - - static mdia(track) { - return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track)); - } - - static mfhd(sequenceNumber) { - return MP4.box(MP4.types.mfhd, new Uint8Array([ - 0x00, - 0x00, 0x00, 0x00, // flags - (sequenceNumber >> 24), - (sequenceNumber >> 16) & 0xFF, - (sequenceNumber >> 8) & 0xFF, - sequenceNumber & 0xFF, // sequence_number - ])); - } - - static minf(track) { - if (track.type === 'audio') { - return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track)); - } else { - return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track)); - } - } - - static moof(sn, baseMediaDecodeTime, track) { - return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track,baseMediaDecodeTime)); - } - /** - * @param tracks... (optional) {array} the tracks associated with this movie - */ - static moov(tracks, duration, timescale) { - var - i = tracks.length, - boxes = []; - - while (i--) { - boxes[i] = MP4.trak(tracks[i]); - } - - return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(timescale, duration)].concat(boxes).concat(MP4.mvex(tracks))); - } - - static mvex(tracks) { - var - i = tracks.length, - boxes = []; - - while (i--) { - boxes[i] = MP4.trex(tracks[i]); - } - return MP4.box.apply(null, [MP4.types.mvex].concat(boxes)); - } - - static mvhd(timescale,duration) { - var - bytes = new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - 0x00, 0x00, 0x00, 0x01, // creation_time - 0x00, 0x00, 0x00, 0x02, // modification_time - (timescale >> 24) & 0xFF, - (timescale >> 16) & 0xFF, - (timescale >> 8) & 0xFF, - timescale & 0xFF, // timescale - (duration >> 24) & 0xFF, - (duration >> 16) & 0xFF, - (duration >> 8) & 0xFF, - duration & 0xFF, // duration - 0x00, 0x01, 0x00, 0x00, // 1.0 rate - 0x01, 0x00, // 1.0 volume - 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0xff, 0xff, 0xff, 0xff // next_track_ID - ]); - return MP4.box(MP4.types.mvhd, bytes); - } - - static sdtp(track) { - var - samples = track.samples || [], - bytes = new Uint8Array(4 + samples.length), - flags, - i; - // leave the full box header (4 bytes) all zero - // write the sample table - for (i = 0; i < samples.length; i++) { - flags = samples[i].flags; - bytes[i + 4] = (flags.dependsOn << 4) | - (flags.isDependedOn << 2) | - (flags.hasRedundancy); - } - - return MP4.box(MP4.types.sdtp, bytes); - } - - static stbl(track) { - return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO)); - } - - static avc1(track) { - var sps = [], pps = [], i, data, len; - // assemble the SPSs - - for (i = 0; i < track.sps.length; i++) { - data = track.sps[i]; - len = data.byteLength; - sps.push((len >>> 8) & 0xFF); - sps.push((len & 0xFF)); - sps = sps.concat(Array.prototype.slice.call(data)); // SPS - } - - // assemble the PPSs - for (i = 0; i < track.pps.length; i++) { - data = track.pps[i]; - len = data.byteLength; - pps.push((len >>> 8) & 0xFF); - pps.push((len & 0xFF)); - pps = pps.concat(Array.prototype.slice.call(data)); - } - - var avcc = MP4.box(MP4.types.avcC, new Uint8Array([ - 0x01, // version - sps[3], // profile - sps[4], // profile compat - sps[5], // level - 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes - 0xE0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets - ].concat(sps).concat([ - track.pps.length // numOfPictureParameterSets - ]).concat(pps))), // "PPS" - width = track.width, - height = track.height; - //console.log('avcc:' + Hex.hexDump(avcc)); - return MP4.box(MP4.types.avc1, new Uint8Array([ - 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, // reserved - 0x00, 0x01, // data_reference_index - 0x00, 0x00, // pre_defined - 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, // pre_defined - (width >> 8) & 0xFF, - width & 0xff, // width - (height >> 8) & 0xFF, - height & 0xff, // height - 0x00, 0x48, 0x00, 0x00, // horizresolution - 0x00, 0x48, 0x00, 0x00, // vertresolution - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x01, // frame_count - 0x12, - 0x62, 0x69, 0x6E, 0x65, //binelpro.ru - 0x6C, 0x70, 0x72, 0x6F, - 0x2E, 0x72, 0x75, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, // compressorname - 0x00, 0x18, // depth = 24 - 0x11, 0x11]), // pre_defined = -1 - avcc, - MP4.box(MP4.types.btrt, new Uint8Array([ - 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB - 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate - 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate - ); - } - - static esds(track) { - var configlen = track.config.byteLength; - let data = new Uint8Array(26+configlen+3); - data.set([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - - 0x03, // descriptor_type - 0x17+configlen, // length - 0x00, 0x01, //es_id - 0x00, // stream_priority - - 0x04, // descriptor_type - 0x0f+configlen, // length - 0x40, //codec : mpeg4_audio - 0x15, // stream_type - 0x00, 0x00, 0x00, // buffer_size - 0x00, 0x00, 0x00, 0x00, // maxBitrate - 0x00, 0x00, 0x00, 0x00, // avgBitrate - - 0x05, // descriptor_type - configlen - ]); - data.set(track.config, 26); - data.set([0x06, 0x01, 0x02], 26+configlen); - // return new Uint8Array([ - // 0x00, // version 0 - // 0x00, 0x00, 0x00, // flags - // - // 0x03, // descriptor_type - // 0x17+configlen, // length - // 0x00, 0x01, //es_id - // 0x00, // stream_priority - // - // 0x04, // descriptor_type - // 0x0f+configlen, // length - // 0x40, //codec : mpeg4_audio - // 0x15, // stream_type - // 0x00, 0x00, 0x00, // buffer_size - // 0x00, 0x00, 0x00, 0x00, // maxBitrate - // 0x00, 0x00, 0x00, 0x00, // avgBitrate - // - // 0x05 // descriptor_type - // ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor - return data; - } - - static mp4a(track) { - var audiosamplerate = track.audiosamplerate; - return MP4.box(MP4.types.mp4a, new Uint8Array([ - 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, // reserved - 0x00, 0x01, // data_reference_index - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, track.channelCount, // channelcount - 0x00, 0x10, // sampleSize:16bits - 0x00, 0x00, // pre_defined - 0x00, 0x00, // reserved2 - (audiosamplerate >> 8) & 0xFF, - audiosamplerate & 0xff, // - 0x00, 0x00]), - MP4.box(MP4.types.esds, MP4.esds(track))); - } - - static stsd(track) { - if (track.type === 'audio') { - return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track)); - } else { - return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track)); - } - } - - static tkhd(track) { - var id = track.id, - duration = track.duration, - width = track.width, - height = track.height, - volume = track.volume; - return MP4.box(MP4.types.tkhd, new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x07, // flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - (id >> 24) & 0xFF, - (id >> 16) & 0xFF, - (id >> 8) & 0xFF, - id & 0xFF, // track_ID - 0x00, 0x00, 0x00, 0x00, // reserved - (duration >> 24), - (duration >> 16) & 0xFF, - (duration >> 8) & 0xFF, - duration & 0xFF, // duration - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, // layer - 0x00, 0x00, // alternate_group - (volume>>0)&0xff, (((volume%1)*10)>>0)&0xff, // track volume // FIXME - 0x00, 0x00, // reserved - 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix - (width >> 8) & 0xFF, - width & 0xFF, - 0x00, 0x00, // width - (height >> 8) & 0xFF, - height & 0xFF, - 0x00, 0x00 // height - ])); - } - - static traf(track,baseMediaDecodeTime) { - var sampleDependencyTable = MP4.sdtp(track), - id = track.id; - return MP4.box(MP4.types.traf, - MP4.box(MP4.types.tfhd, new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - (id >> 24), - (id >> 16) & 0XFF, - (id >> 8) & 0XFF, - (id & 0xFF) // track_ID - ])), - MP4.box(MP4.types.tfdt, new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - (baseMediaDecodeTime >>24), - (baseMediaDecodeTime >> 16) & 0XFF, - (baseMediaDecodeTime >> 8) & 0XFF, - (baseMediaDecodeTime & 0xFF) // baseMediaDecodeTime - ])), - MP4.trun(track, - sampleDependencyTable.length + - 16 + // tfhd - 16 + // tfdt - 8 + // traf header - 16 + // mfhd - 8 + // moof header - 8), // mdat header - sampleDependencyTable); - } - - /** - * Generate a track box. - * @param track {object} a track definition - * @return {Uint8Array} the track box - */ - static trak(track) { - track.duration = track.duration || 0xffffffff; - return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track)); - } - - static trex(track) { - var id = track.id; - return MP4.box(MP4.types.trex, new Uint8Array([ - 0x00, // version 0 - 0x00, 0x00, 0x00, // flags - (id >> 24), - (id >> 16) & 0XFF, - (id >> 8) & 0XFF, - (id & 0xFF), // track_ID - 0x00, 0x00, 0x00, 0x01, // default_sample_description_index - 0x00, 0x00, 0x00, 0x00, // default_sample_duration - 0x00, 0x00, 0x00, 0x00, // default_sample_size - 0x00, 0x01, 0x00, 0x01 // default_sample_flags - ])); - } - - static trun(track, offset) { - var samples= track.samples || [], - len = samples.length, - arraylen = 12 + (16 * len), - array = new Uint8Array(arraylen), - i,sample,duration,size,flags,cts; - offset += 8 + arraylen; - array.set([ - 0x00, // version 0 - 0x00, 0x0f, 0x01, // flags - (len >>> 24) & 0xFF, - (len >>> 16) & 0xFF, - (len >>> 8) & 0xFF, - len & 0xFF, // sample_count - (offset >>> 24) & 0xFF, - (offset >>> 16) & 0xFF, - (offset >>> 8) & 0xFF, - offset & 0xFF // data_offset - ],0); - for (i = 0; i < len; i++) { - sample = samples[i]; - duration = sample.duration; - size = sample.size; - flags = sample.flags; - cts = sample.cts; - array.set([ - (duration >>> 24) & 0xFF, - (duration >>> 16) & 0xFF, - (duration >>> 8) & 0xFF, - duration & 0xFF, // sample_duration - (size >>> 24) & 0xFF, - (size >>> 16) & 0xFF, - (size >>> 8) & 0xFF, - size & 0xFF, // sample_size - (flags.isLeading << 2) | flags.dependsOn, - (flags.isDependedOn << 6) | - (flags.hasRedundancy << 4) | - (flags.paddingValue << 1) | - flags.isNonSync, - flags.degradPrio & 0xF0 << 8, - flags.degradPrio & 0x0F, // sample_flags - (cts >>> 24) & 0xFF, - (cts >>> 16) & 0xFF, - (cts >>> 8) & 0xFF, - cts & 0xFF // sample_composition_time_offset - ],12+16*i); - } - return MP4.box(MP4.types.trun, array); - } - - static initSegment(tracks, duration, timescale) { - if (!MP4.types) { - MP4.init(); - } - var movie = MP4.moov(tracks, duration, timescale), result; - result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength); - result.set(MP4.FTYP); - result.set(movie, MP4.FTYP.byteLength); - return result; - } -} - -//import {MP4Inspect} from '../iso-bmff/mp4-inspector.js'; - -const LOG_TAG = "mse"; -const Log$1 = getTagged(LOG_TAG); - -class MSEBuffer { - constructor(parent, codec) { - this.mediaSource = parent.mediaSource; - this.players = parent.players; - this.cleaning = false; - this.parent = parent; - this.queue = []; - this.cleanResolvers = []; - this.codec = codec; - this.cleanRanges = []; - - Log$1.debug(`Use codec: ${codec}`); - - this.sourceBuffer = this.mediaSource.addSourceBuffer(codec); - this.eventSource = new EventEmitter(this.sourceBuffer); - - this.eventSource.addEventListener('updatestart', (e)=> { - // this.updating = true; - // Log.debug('update start'); - if (this.cleaning) { - Log$1.debug(`${this.codec} cleaning start`); - } - }); - - this.eventSource.addEventListener('update', (e)=> { - // this.updating = true; - if (this.cleaning) { - Log$1.debug(`${this.codec} cleaning update`); - } - }); - - this.eventSource.addEventListener('updateend', (e)=> { - // Log.debug('update end'); - // this.updating = false; - if (this.cleaning) { - Log$1.debug(`${this.codec} cleaning end`); - - try { - if (this.sourceBuffer.buffered.length && this.players[0].currentTime < this.sourceBuffer.buffered.start(0)) { - this.players[0].currentTime = this.sourceBuffer.buffered.start(0); - } - } catch (e) { - // TODO: do something? - } - while (this.cleanResolvers.length) { - let resolver = this.cleanResolvers.shift(); - resolver(); - } - this.cleaning = false; - - if (this.cleanRanges.length) { - this.doCleanup(); - return; - } - } else { - // Log.debug(`buffered: ${this.sourceBuffer.buffered.end(0)}, current ${this.players[0].currentTime}`); - } - this.feedNext(); - }); - - this.eventSource.addEventListener('error', (e)=> { - Log$1.debug(`Source buffer error: ${this.mediaSource.readyState}`); - if (this.mediaSource.sourceBuffers.length) { - this.mediaSource.removeSourceBuffer(this.sourceBuffer); - } - this.parent.eventSource.dispatchEvent('error'); - }); - - this.eventSource.addEventListener('abort', (e)=> { - Log$1.debug(`Source buffer aborted: ${this.mediaSource.readyState}`); - if (this.mediaSource.sourceBuffers.length) { - this.mediaSource.removeSourceBuffer(this.sourceBuffer); - } - this.parent.eventSource.dispatchEvent('error'); - }); - - if (!this.sourceBuffer.updating) { - this.feedNext(); - } - // TODO: cleanup every hour for live streams - } - - destroy() { - this.eventSource.destroy(); - this.clear(); - this.queue = []; - this.mediaSource.removeSourceBuffer(this.sourceBuffer); - } - - clear() { - this.queue = []; - let promises = []; - for (let i=0; i< this.sourceBuffer.buffered.length; ++i) { - // TODO: await remove - this.cleaning = true; - promises.push(new Promise((resolve, reject)=>{ - this.cleanResolvers.push(resolve); - if (!this.sourceBuffer.updating) { - this.sourceBuffer.remove(this.sourceBuffer.buffered.start(i), this.sourceBuffer.buffered.end(i)); - resolve(); - } else { - this.sourceBuffer.onupdateend = () => { - if (this.sourceBuffer) { - this.sourceBuffer.remove(this.sourceBuffer.buffered.start(i), this.sourceBuffer.buffered.end(i)); - } - resolve(); - }; - } - })); - } - return Promise.all(promises); - } - - setLive(is_live) { - this.is_live = is_live; - } - - feedNext() { - // Log.debug("feed next ", this.sourceBuffer.updating); - if (!this.sourceBuffer.updating && !this.cleaning && this.queue.length) { - this.doAppend(this.queue.shift()); - // TODO: if is live and current position > 1hr => clean all and restart - } - } - - doCleanup() { - if (!this.cleanRanges.length) { - this.cleaning = false; - this.feedNext(); - return; - } - let range = this.cleanRanges.shift(); - Log$1.debug(`${this.codec} remove range [${range[0]} - ${range[1]}). - \nUpdating: ${this.sourceBuffer.updating} - `); - this.cleaning = true; - this.sourceBuffer.remove(range[0], range[1]); - } - - initCleanup() { - if (this.sourceBuffer.buffered.length && !this.sourceBuffer.updating && !this.cleaning) { - Log$1.debug(`${this.codec} cleanup`); - let removeBound = this.sourceBuffer.buffered.end(this.sourceBuffer.buffered.length-1) - 2; - - for (let i=0; i< this.sourceBuffer.buffered.length; ++i) { - let removeStart = this.sourceBuffer.buffered.start(i); - let removeEnd = this.sourceBuffer.buffered.end(i); - if ((this.players[0].currentTime <= removeStart) || (removeBound <= removeStart)) continue; - - if ((removeBound <= removeEnd) && (removeBound >= removeStart)) { - Log$1.debug(`Clear [${removeStart}, ${removeBound}), leave [${removeBound}, ${removeEnd}]`); - removeEnd = removeBound; - if (removeEnd!=removeStart) { - this.cleanRanges.push([removeStart, removeEnd]); - } - continue; // Do not cleanup buffered range after current position - } - this.cleanRanges.push([removeStart, removeEnd]); - } - - this.doCleanup(); - - // let bufferStart = this.sourceBuffer.buffered.start(0); - // let removeEnd = this.sourceBuffer.buffered.start(0) + (this.sourceBuffer.buffered.end(0) - this.sourceBuffer.buffered.start(0))/2; - // if (this.players[0].currentTime < removeEnd) { - // this.players[0].currentTime = removeEnd; - // } - // let removeEnd = Math.max(this.players[0].currentTime - 3, this.sourceBuffer.buffered.end(0) - 3); - // - // if (removeEnd < bufferStart) { - // removeEnd = this.sourceBuffer.buffered.start(0) + (this.sourceBuffer.buffered.end(0) - this.sourceBuffer.buffered.start(0))/2; - // if (this.players[0].currentTime < removeEnd) { - // this.players[0].currentTime = removeEnd; - // } - // } - - // if (removeEnd > bufferStart && (removeEnd - bufferStart > 0.5 )) { - // // try { - // Log.debug(`${this.codec} remove range [${bufferStart} - ${removeEnd}). - // \nBuffered end: ${this.sourceBuffer.buffered.end(0)} - // \nUpdating: ${this.sourceBuffer.updating} - // `); - // this.cleaning = true; - // this.sourceBuffer.remove(bufferStart, removeEnd); - // // } catch (e) { - // // // TODO: implement - // // Log.error(e); - // // } - // } else { - // this.feedNext(); - // } - } else { - this.feedNext(); - } - } - - doAppend(data) { - // console.log(MP4Inspect.mp4toJSON(data)); - let err = this.players[0].error; - if (err) { - Log$1.error(`Error occured: ${MSE.ErrorNotes[err.code]}`); - try { - this.players.forEach((video)=>{video.stop();}); - this.mediaSource.endOfStream(); - } catch (e){ - - } - this.parent.eventSource.dispatchEvent('error'); - } else { - try { - this.sourceBuffer.appendBuffer(data); - } catch (e) { - if (e.name === 'QuotaExceededError') { - Log$1.debug(`${this.codec} quota fail`); - this.queue.unshift(data); - this.initCleanup(); - return; - } - - // reconnect on fail - Log$1.error(`Error occured while appending buffer. ${e.name}: ${e.message}`); - this.parent.eventSource.dispatchEvent('error'); - } - } - - } - - feed(data) { - this.queue = this.queue.concat(data); - // Log.debug(this.sourceBuffer.updating, this.updating, this.queue.length); - if (this.sourceBuffer && !this.sourceBuffer.updating && !this.cleaning) { - // Log.debug('enq feed'); - this.feedNext(); - } - } -} - -class MSE { - // static CODEC_AVC_BASELINE = "avc1.42E01E"; - // static CODEC_AVC_MAIN = "avc1.4D401E"; - // static CODEC_AVC_HIGH = "avc1.64001E"; - // static CODEC_VP8 = "vp8"; - // static CODEC_AAC = "mp4a.40.2"; - // static CODEC_VORBIS = "vorbis"; - // static CODEC_THEORA = "theora"; - - static get ErrorNotes() {return { - [MediaError.MEDIA_ERR_ABORTED]: 'fetching process aborted by user', - [MediaError.MEDIA_ERR_NETWORK]: 'error occurred when downloading', - [MediaError.MEDIA_ERR_DECODE]: 'error occurred when decoding', - [MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]: 'audio/video not supported' - }}; - - static isSupported(codecs) { - return (window.MediaSource && window.MediaSource.isTypeSupported(`video/mp4; codecs="${codecs.join(',')}"`)); - } - - constructor (players) { - this.players = players; - const playing = this.players.map((video, idx) => { - video.onplaying = function () { - playing[idx] = true; - }; - video.onpause = function () { - playing[idx] = false; - }; - return !video.paused; - }); - this.playing = playing; - this.mediaSource = new MediaSource(); - this.eventSource = new EventEmitter(this.mediaSource); - this.reset(); - } - - destroy() { - this.reset(); - this.eventSource.destroy(); - this.mediaSource = null; - this.eventSource = null; - } - - play() { - this.players.forEach((video, idx)=>{ - if (video.paused && !this.playing[idx]) { - Log$1.debug(`player ${idx}: play`); - video.play(); - } - }); - } - - setLive(is_live) { - for (let idx in this.buffers) { - this.buffers[idx].setLive(is_live); - } - this.is_live = is_live; - } - - resetBuffers() { - this.players.forEach((video, idx)=>{ - if (!video.paused && this.playing[idx]) { - video.pause(); - video.currentTime = 0; - } - }); - - let promises = []; - for (let buffer of this.buffers.values()) { - promises.push(buffer.clear()); - } - return Promise.all(promises).then(()=>{ - this.mediaSource.endOfStream(); - this.mediaSource.duration = 0; - this.mediaSource.clearLiveSeekableRange(); - this.play(); - }); - } - - clear() { - this.reset(); - this.players.forEach((video)=>{video.src = URL.createObjectURL(this.mediaSource);}); - - return this.setupEvents(); - } - - setupEvents() { - this.eventSource.clear(); - this.resolved = false; - this.mediaReady = new Promise((resolve, reject)=> { - this._sourceOpen = ()=> { - Log$1.debug(`Media source opened: ${this.mediaSource.readyState}`); - if (!this.resolved) { - this.resolved = true; - resolve(); - } - }; - this._sourceEnded = ()=>{ - Log$1.debug(`Media source ended: ${this.mediaSource.readyState}`); - }; - this._sourceClose = ()=>{ - Log$1.debug(`Media source closed: ${this.mediaSource.readyState}`); - if (this.resolved) { - this.eventSource.dispatchEvent('sourceclosed'); - } - }; - this.eventSource.addEventListener('sourceopen', this._sourceOpen); - this.eventSource.addEventListener('sourceended', this._sourceEnded); - this.eventSource.addEventListener('sourceclose', this._sourceClose); - }); - return this.mediaReady; - } - - reset() { - this.ready = false; - for (let track in this.buffers) { - this.buffers[track].destroy(); - delete this.buffers[track]; - } - if (this.mediaSource.readyState == 'open') { - this.mediaSource.duration = 0; - this.mediaSource.endOfStream(); - } - this.updating = false; - this.resolved = false; - this.buffers = {}; - // this.players.forEach((video)=>{video.src = URL.createObjectURL(this.mediaSource)}); - // TODO: remove event listeners for existing media source - // this.setupEvents(); - // this.clear(); - } - - setCodec(track, mimeCodec) { - return this.mediaReady.then(()=>{ - this.buffers[track] = new MSEBuffer(this, mimeCodec); - this.buffers[track].setLive(this.is_live); - }); - } - - feed(track, data) { - if (this.buffers[track]) { - this.buffers[track].feed(data); - } - } -} - -const Log$2 = getTagged('remuxer:base'); -let track_id = 1; -class BaseRemuxer { - - static get MP4_TIMESCALE() { return 90000;} - - // TODO: move to ts parser - // static PTSNormalize(value, reference) { - // - // let offset; - // if (reference === undefined) { - // return value; - // } - // if (reference < value) { - // // - 2^33 - // offset = -8589934592; - // } else { - // // + 2^33 - // offset = 8589934592; - // } - // /* PTS is 33bit (from 0 to 2^33 -1) - // if diff between value and reference is bigger than half of the amplitude (2^32) then it means that - // PTS looping occured. fill the gap */ - // while (Math.abs(value - reference) > 4294967296) { - // value += offset; - // } - // return value; - // } - - static getTrackID() { - return track_id++; - } - - constructor(timescale, scaleFactor, params) { - this.timeOffset = 0; - this.timescale = timescale; - this.scaleFactor = scaleFactor; - this.readyToDecode = false; - this.samples = []; - this.seq = 1; - this.tsAlign = 1; - } - - scaled(timestamp) { - return timestamp / this.scaleFactor; - } - - unscaled(timestamp) { - return timestamp * this.scaleFactor; - } - - remux(unit) { - if (unit) { - this.samples.push({ - unit: unit, - pts: unit.pts, - dts: unit.dts - }); - return true; - } - return false; - } - - static toMS(timestamp) { - return timestamp/90; - } - - setConfig(config) { - - } - - insertDscontinuity() { - this.samples.push(null); - } - - init(initPTS, initDTS, shouldInitialize=true) { - this.initPTS = Math.min(initPTS, this.samples[0].dts /*- this.unscaled(this.timeOffset)*/); - this.initDTS = Math.min(initDTS, this.samples[0].dts /*- this.unscaled(this.timeOffset)*/); - Log$2.debug(`Initial pts=${this.initPTS} dts=${this.initDTS} offset=${this.unscaled(this.timeOffset)}`); - this.initialized = shouldInitialize; - } - - flush() { - this.seq++; - this.mp4track.len = 0; - this.mp4track.samples = []; - } - - static dtsSortFunc(a,b) { - return (a.dts-b.dts); - } - - getPayloadBase(sampleFunction, setupSample) { - if (!this.readyToDecode || !this.initialized || !this.samples.length) return null; - this.samples.sort(BaseRemuxer.dtsSortFunc); - return true; - // - // let payload = new Uint8Array(this.mp4track.len); - // let offset = 0; - // let samples=this.mp4track.samples; - // let mp4Sample, lastDTS, pts, dts; - // - // while (this.samples.length) { - // let sample = this.samples.shift(); - // if (sample === null) { - // // discontinuity - // this.nextDts = undefined; - // break; - // } - // - // let unit = sample.unit; - // - // pts = Math.round((sample.pts - this.initDTS)/this.tsAlign)*this.tsAlign; - // dts = Math.round((sample.dts - this.initDTS)/this.tsAlign)*this.tsAlign; - // // ensure DTS is not bigger than PTS - // dts = Math.min(pts, dts); - // - // // sampleFunction(pts, dts); // TODO: - // - // // mp4Sample = setupSample(unit, pts, dts); // TODO: - // - // payload.set(unit.getData(), offset); - // offset += unit.getSize(); - // - // samples.push(mp4Sample); - // lastDTS = dts; - // } - // if (!samples.length) return null; - // - // // samplesPostFunction(samples); // TODO: - // - // return new Uint8Array(payload.buffer, 0, this.mp4track.len); - } -} - -const Log$3 = getTagged("remuxer:aac"); -// TODO: asm.js -class AACRemuxer extends BaseRemuxer { - - constructor(timescale, scaleFactor = 1, params={}) { - super(timescale, scaleFactor); - - this.codecstring=MSE.CODEC_AAC; - this.units = []; - this.initDTS = undefined; - this.nextAacPts = undefined; - this.lastPts = 0; - this.firstDTS = 0; - this.firstPTS = 0; - this.duration = params.duration || 1; - this.initialized = false; - - this.mp4track={ - id:BaseRemuxer.getTrackID(), - type: 'audio', - fragmented:true, - channelCount:0, - audiosamplerate: this.timescale, - duration: 0, - timescale: this.timescale, - volume: 1, - samples: [], - config: '', - len: 0 - }; - if (params.config) { - this.setConfig(params.config); - } - } - - setConfig(config) { - this.mp4track.channelCount = config.channels; - this.mp4track.audiosamplerate = config.samplerate; - if (!this.mp4track.duration) { - this.mp4track.duration = (this.duration?this.duration:1)*config.samplerate; - } - this.mp4track.timescale = config.samplerate; - this.mp4track.config = config.config; - this.mp4track.codec = config.codec; - this.timescale = config.samplerate; - this.scaleFactor = BaseRemuxer.MP4_TIMESCALE / config.samplerate; - this.expectedSampleDuration = 1024 * this.scaleFactor; - this.readyToDecode = true; - } - - remux(aac) { - if (super.remux.call(this, aac)) { - this.mp4track.len += aac.getSize(); - } - } - - getPayload() { - if (!this.readyToDecode || !this.samples.length) return null; - this.samples.sort(function(a, b) { - return (a.dts-b.dts); - }); - - let payload = new Uint8Array(this.mp4track.len); - let offset = 0; - let samples=this.mp4track.samples; - let mp4Sample, lastDTS, pts, dts; - - while (this.samples.length) { - let sample = this.samples.shift(); - if (sample === null) { - // discontinuity - this.nextDts = undefined; - break; - } - let unit = sample.unit; - pts = sample.pts - this.initDTS; - dts = sample.dts - this.initDTS; - - if (lastDTS === undefined) { - if (this.nextDts) { - let delta = Math.round(this.scaled(pts - this.nextAacPts)); - // if fragment are contiguous, or delta less than 600ms, ensure there is no overlap/hole between fragments - if (/*contiguous || */Math.abs(delta) < 600) { - // log delta - if (delta) { - if (delta > 0) { - Log$3.log(`${delta} ms hole between AAC samples detected,filling it`); - // if we have frame overlap, overlapping for more than half a frame duraion - } else if (delta < -12) { - // drop overlapping audio frames... browser will deal with it - Log$3.log(`${(-delta)} ms overlapping between AAC samples detected, drop frame`); - this.mp4track.len -= unit.getSize(); - continue; - } - // set DTS to next DTS - pts = dts = this.nextAacPts; - } - } - } - // remember first PTS of our aacSamples, ensure value is positive - this.firstDTS = Math.max(0, dts); - } - - mp4Sample = { - size: unit.getSize(), - cts: 0, - duration:1024, - flags: { - isLeading: 0, - isDependedOn: 0, - hasRedundancy: 0, - degradPrio: 0, - dependsOn: 1 - } - }; - - payload.set(unit.getData(), offset); - offset += unit.getSize(); - samples.push(mp4Sample); - lastDTS = dts; - } - if (!samples.length) return null; - this.nextDts =pts+this.expectedSampleDuration; - return new Uint8Array(payload.buffer, 0, this.mp4track.len); - } -} -//test.bundle.js:42 [remuxer:h264] skip frame from the past at DTS=18397972271140676 with expected DTS=18397998040950484 - -/** - * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264. -*/ -// TODO: asm.js -class ExpGolomb { - - constructor(data) { - this.data = data; - // the number of bytes left to examine in this.data - this.bytesAvailable = this.data.byteLength; - // the current word being examined - this.word = 0; // :uint - // the number of bits left to examine in the current word - this.bitsAvailable = 0; // :uint - } - - // ():void - loadWord() { - var - position = this.data.byteLength - this.bytesAvailable, - workingBytes = new Uint8Array(4), - availableBytes = Math.min(4, this.bytesAvailable); - if (availableBytes === 0) { - throw new Error('no bytes available'); - } - workingBytes.set(this.data.subarray(position, position + availableBytes)); - this.word = new DataView(workingBytes.buffer, workingBytes.byteOffset, workingBytes.byteLength).getUint32(0); - // track the amount of this.data that has been processed - this.bitsAvailable = availableBytes * 8; - this.bytesAvailable -= availableBytes; - } - - // (count:int):void - skipBits(count) { - var skipBytes; // :int - if (this.bitsAvailable > count) { - this.word <<= count; - this.bitsAvailable -= count; - } else { - count -= this.bitsAvailable; - skipBytes = count >> 3; - count -= (skipBytes << 3); - this.bytesAvailable -= skipBytes; - this.loadWord(); - this.word <<= count; - this.bitsAvailable -= count; - } - } - - // (size:int):uint - readBits(size) { - var - bits = Math.min(this.bitsAvailable, size), // :uint - valu = this.word >>> (32 - bits); // :uint - if (size > 32) { - Log.error('Cannot read more than 32 bits at a time'); - } - this.bitsAvailable -= bits; - if (this.bitsAvailable > 0) { - this.word <<= bits; - } else if (this.bytesAvailable > 0) { - this.loadWord(); - } - bits = size - bits; - if (bits > 0) { - return valu << bits | this.readBits(bits); - } else { - return valu; - } - } - - // ():uint - skipLZ() { - var leadingZeroCount; // :uint - for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) { - if (0 !== (this.word & (0x80000000 >>> leadingZeroCount))) { - // the first bit of working word is 1 - this.word <<= leadingZeroCount; - this.bitsAvailable -= leadingZeroCount; - return leadingZeroCount; - } - } - // we exhausted word and still have not found a 1 - this.loadWord(); - return leadingZeroCount + this.skipLZ(); - } - - // ():void - skipUEG() { - this.skipBits(1 + this.skipLZ()); - } - - // ():void - skipEG() { - this.skipBits(1 + this.skipLZ()); - } - - // ():uint - readUEG() { - var clz = this.skipLZ(); // :uint - return this.readBits(clz + 1) - 1; - } - - // ():int - readEG() { - var valu = this.readUEG(); // :int - if (0x01 & valu) { - // the number is odd if the low order bit is set - return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2 - } else { - return -1 * (valu >>> 1); // divide by two then make it negative - } - } - - // Some convenience functions - // :Boolean - readBoolean() { - return 1 === this.readBits(1); - } - - // ():int - readUByte() { - return this.readBits(8); - } - - // ():int - readUShort() { - return this.readBits(16); - } - // ():int - readUInt() { - return this.readBits(32); - } -} - -// TODO: asm.js - -function appendByteArray(buffer1, buffer2) { - let tmp = new Uint8Array((buffer1.byteLength|0) + (buffer2.byteLength|0)); - tmp.set(buffer1, 0); - tmp.set(buffer2, buffer1.byteLength|0); - return tmp; -} - - -function base64ToArrayBuffer(base64) { - var binary_string = window.atob(base64); - var len = binary_string.length; - var bytes = new Uint8Array( len ); - for (var i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - return bytes.buffer; -} - -function hexToByteArray(hex) { - let len = hex.length >> 1; - var bufView = new Uint8Array(len); - for (var i = 0; i < len; i++) { - bufView[i] = parseInt(hex.substr(i<<1,2),16); - } - return bufView; -} - - - -function bitSlice(bytearray, start=0, end=bytearray.byteLength*8) { - let byteLen = Math.ceil((end-start)/8); - let res = new Uint8Array(byteLen); - let startByte = start >>> 3; // /8 - let endByte = (end>>>3) - 1; // /8 - let bitOffset = start & 0x7; // %8 - let nBitOffset = 8 - bitOffset; - let endOffset = 8 - end & 0x7; // %8 - for (let i=0; i> nBitOffset; - if (i == endByte-1 && endOffset < 8) { - tail >>= endOffset; - tail <<= endOffset; - } - } - res[i]=(bytearray[startByte+i]< 0; --i) { - - /* Shift result one left to make room for another bit, - then add the next bit on the stream. */ - result = ((result|0) << 1) | (((this.byte|0) >> (8 - (++this.bitpos))) & 0x01); - if ((this.bitpos|0)>=8) { - this.byte = this.src.getUint8(++this.bytepos); - this.bitpos &= 0x7; - } - } - - return result; - } - skipBits(length) { - this.bitpos += (length|0) & 0x7; // %8 - this.bytepos += (length|0) >>> 3; // *8 - if (this.bitpos > 7) { - this.bitpos &= 0x7; - ++this.bytepos; - } - - if (!this.finished()) { - this.byte = this.src.getUint8(this.bytepos); - return 0; - } else { - return this.bytepos-this.src.byteLength-this.src.bitpos; - } - } - - finished() { - return this.bytepos >= this.src.byteLength; - } -} - -class NALU { - - static get NDR() {return 1;} - static get SLICE_PART_A() {return 2;} - static get SLICE_PART_B() {return 3;} - static get SLICE_PART_C() {return 4;} - static get IDR() {return 5;} - static get SEI() {return 6;} - static get SPS() {return 7;} - static get PPS() {return 8;} - static get DELIMITER() {return 9;} - static get EOSEQ() {return 10;} - static get EOSTR() {return 11;} - static get FILTER() {return 12;} - static get STAP_A() {return 24;} - static get STAP_B() {return 25;} - static get FU_A() {return 28;} - static get FU_B() {return 29;} - - static get TYPES() {return { - [NALU.IDR]: 'IDR', - [NALU.SEI]: 'SEI', - [NALU.SPS]: 'SPS', - [NALU.PPS]: 'PPS', - [NALU.NDR]: 'NDR' - }}; - - static type(nalu) { - if (nalu.ntype in NALU.TYPES) { - return NALU.TYPES[nalu.ntype]; - } else { - return 'UNKNOWN'; - } - } - - constructor(ntype, nri, data, dts, pts) { - - this.data = data; - this.ntype = ntype; - this.nri = nri; - this.dts = dts; - this.pts = pts ? pts : this.dts; - this.sliceType = null; - } - - appendData(idata) { - this.data = appendByteArray(this.data, idata); - } - - toString() { - return `${NALU.type(this)}(${this.data.byteLength}): NRI: ${this.getNri()}, PTS: ${this.pts}, DTS: ${this.dts}`; - } - - getNri() { - return this.nri >> 5; - } - - type() { - return this.ntype; - } - - isKeyframe() { - return this.ntype === NALU.IDR || this.sliceType === 7; - } - - getSize() { - return 4 + 1 + this.data.byteLength; - } - - getData() { - let header = new Uint8Array(5 + this.data.byteLength); - let view = new DataView(header.buffer); - view.setUint32(0, this.data.byteLength + 1); - view.setUint8(4, (0x0 & 0x80) | (this.nri & 0x60) | (this.ntype & 0x1F)); - header.set(this.data, 5); - return header; - } -} - -class H264Parser { - - constructor(remuxer) { - this.remuxer = remuxer; - this.track = remuxer.mp4track; - this.firstFound = false; - } - - msToScaled(timestamp) { - return (timestamp - this.remuxer.timeOffset) * this.remuxer.scaleFactor; - } - - parseSPS(sps) { - var config = H264Parser.readSPS(new Uint8Array(sps)); - - this.track.width = config.width; - this.track.height = config.height; - this.track.sps = [new Uint8Array(sps)]; - // this.track.timescale = this.remuxer.timescale; - // this.track.duration = this.remuxer.timescale; // TODO: extract duration for non-live client - this.track.codec = 'avc1.'; - - let codecarray = new DataView(sps.buffer, sps.byteOffset+1, 4); - for (let i = 0; i < 3; ++i) { - var h = codecarray.getUint8(i).toString(16); - if (h.length < 2) { - h = '0' + h; - } - this.track.codec += h; - } - } - - parsePPS(pps) { - this.track.pps = [new Uint8Array(pps)]; - } - - parseNAL(unit) { - if (!unit) return false; - - let push = null; - // console.log(unit.toString()); - switch (unit.type()) { - case NALU.NDR: - case NALU.IDR: - unit.sliceType = H264Parser.parceSliceHeader(unit.data); - if (unit.isKeyframe() && !this.firstFound) { - this.firstFound = true; - } - if (this.firstFound) { - push = true; - } else { - push = false; - } - break; - case NALU.PPS: - push = false; - if (!this.track.pps) { - this.parsePPS(unit.getData().subarray(4)); - if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) { - this.remuxer.readyToDecode = true; - } - } - break; - case NALU.SPS: - push = false; - if(!this.track.sps) { - this.parseSPS(unit.getData().subarray(4)); - if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) { - this.remuxer.readyToDecode = true; - } - } - break; - case NALU.SEI: - push = false; - let data = new DataView(unit.data.buffer, unit.data.byteOffset, unit.data.byteLength); - let byte_idx = 0; - let pay_type = data.getUint8(byte_idx); - ++byte_idx; - let pay_size = 0; - let sz = data.getUint8(byte_idx); - ++byte_idx; - while (sz === 255) { - pay_size+=sz; - sz = data.getUint8(byte_idx); - ++byte_idx; - } - pay_size+=sz; - - let uuid = unit.data.subarray(byte_idx, byte_idx+16); - byte_idx+=16; - console.log(`PT: ${pay_type}, PS: ${pay_size}, UUID: ${Array.from(uuid).map(function(i) { - return ('0' + i.toString(16)).slice(-2); - }).join('')}`); - // debugger; - break; - case NALU.EOSEQ: - case NALU.EOSTR: - push = false; - default: - } - if (push === null && unit.getNri() > 0 ) { - push=true; - } - return push; - } - - static parceSliceHeader(data) { - let decoder = new ExpGolomb(data); - let first_mb = decoder.readUEG(); - let slice_type = decoder.readUEG(); - let ppsid = decoder.readUEG(); - let frame_num = decoder.readUByte(); - // console.log(`first_mb: ${first_mb}, slice_type: ${slice_type}, ppsid: ${ppsid}, frame_num: ${frame_num}`); - return slice_type; - } - - /** - * Advance the ExpGolomb decoder past a scaling list. The scaling - * list is optionally transmitted as part of a sequence parameter - * set and is not relevant to transmuxing. - * @param decoder {ExpGolomb} exp golomb decoder - * @param count {number} the number of entries in this scaling list - * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 - */ - static skipScalingList(decoder, count) { - let lastScale = 8, - nextScale = 8, - deltaScale; - for (let j = 0; j < count; j++) { - if (nextScale !== 0) { - deltaScale = decoder.readEG(); - nextScale = (lastScale + deltaScale + 256) % 256; - } - lastScale = (nextScale === 0) ? lastScale : nextScale; - } - } - - /** - * Read a sequence parameter set and return some interesting video - * properties. A sequence parameter set is the H264 metadata that - * describes the properties of upcoming video frames. - * @param data {Uint8Array} the bytes of a sequence parameter set - * @return {object} an object with configuration parsed from the - * sequence parameter set, including the dimensions of the - * associated video frames. - */ - static readSPS(data) { - let decoder = new ExpGolomb(data); - let frameCropLeftOffset = 0, - frameCropRightOffset = 0, - frameCropTopOffset = 0, - frameCropBottomOffset = 0, - sarScale = 1, - profileIdc,profileCompat,levelIdc, - numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1, - picHeightInMapUnitsMinus1, - frameMbsOnlyFlag, - scalingListCount; - decoder.readUByte(); - profileIdc = decoder.readUByte(); // profile_idc - profileCompat = decoder.readBits(5); // constraint_set[0-4]_flag, u(5) - decoder.skipBits(3); // reserved_zero_3bits u(3), - levelIdc = decoder.readUByte(); //level_idc u(8) - decoder.skipUEG(); // seq_parameter_set_id - // some profiles have more optional data we don't need - if (profileIdc === 100 || - profileIdc === 110 || - profileIdc === 122 || - profileIdc === 244 || - profileIdc === 44 || - profileIdc === 83 || - profileIdc === 86 || - profileIdc === 118 || - profileIdc === 128) { - var chromaFormatIdc = decoder.readUEG(); - if (chromaFormatIdc === 3) { - decoder.skipBits(1); // separate_colour_plane_flag - } - decoder.skipUEG(); // bit_depth_luma_minus8 - decoder.skipUEG(); // bit_depth_chroma_minus8 - decoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag - if (decoder.readBoolean()) { // seq_scaling_matrix_present_flag - scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; - for (let i = 0; i < scalingListCount; ++i) { - if (decoder.readBoolean()) { // seq_scaling_list_present_flag[ i ] - if (i < 6) { - H264Parser.skipScalingList(decoder, 16); - } else { - H264Parser.skipScalingList(decoder, 64); - } - } - } - } - } - decoder.skipUEG(); // log2_max_frame_num_minus4 - var picOrderCntType = decoder.readUEG(); - if (picOrderCntType === 0) { - decoder.readUEG(); //log2_max_pic_order_cnt_lsb_minus4 - } else if (picOrderCntType === 1) { - decoder.skipBits(1); // delta_pic_order_always_zero_flag - decoder.skipEG(); // offset_for_non_ref_pic - decoder.skipEG(); // offset_for_top_to_bottom_field - numRefFramesInPicOrderCntCycle = decoder.readUEG(); - for(let i = 0; i < numRefFramesInPicOrderCntCycle; ++i) { - decoder.skipEG(); // offset_for_ref_frame[ i ] - } - } - decoder.skipUEG(); // max_num_ref_frames - decoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag - picWidthInMbsMinus1 = decoder.readUEG(); - picHeightInMapUnitsMinus1 = decoder.readUEG(); - frameMbsOnlyFlag = decoder.readBits(1); - if (frameMbsOnlyFlag === 0) { - decoder.skipBits(1); // mb_adaptive_frame_field_flag - } - decoder.skipBits(1); // direct_8x8_inference_flag - if (decoder.readBoolean()) { // frame_cropping_flag - frameCropLeftOffset = decoder.readUEG(); - frameCropRightOffset = decoder.readUEG(); - frameCropTopOffset = decoder.readUEG(); - frameCropBottomOffset = decoder.readUEG(); - } - if (decoder.readBoolean()) { - // vui_parameters_present_flag - if (decoder.readBoolean()) { - // aspect_ratio_info_present_flag - let sarRatio; - const aspectRatioIdc = decoder.readUByte(); - switch (aspectRatioIdc) { - case 1: sarRatio = [1,1]; break; - case 2: sarRatio = [12,11]; break; - case 3: sarRatio = [10,11]; break; - case 4: sarRatio = [16,11]; break; - case 5: sarRatio = [40,33]; break; - case 6: sarRatio = [24,11]; break; - case 7: sarRatio = [20,11]; break; - case 8: sarRatio = [32,11]; break; - case 9: sarRatio = [80,33]; break; - case 10: sarRatio = [18,11]; break; - case 11: sarRatio = [15,11]; break; - case 12: sarRatio = [64,33]; break; - case 13: sarRatio = [160,99]; break; - case 14: sarRatio = [4,3]; break; - case 15: sarRatio = [3,2]; break; - case 16: sarRatio = [2,1]; break; - case 255: { - sarRatio = [decoder.readUByte() << 8 | decoder.readUByte(), decoder.readUByte() << 8 | decoder.readUByte()]; - break; - } - } - if (sarRatio) { - sarScale = sarRatio[0] / sarRatio[1]; - } - } - if (decoder.readBoolean()) {decoder.skipBits(1);} - - if (decoder.readBoolean()) { - decoder.skipBits(4); - if (decoder.readBoolean()) { - decoder.skipBits(24); - } - } - if (decoder.readBoolean()) { - decoder.skipUEG(); - decoder.skipUEG(); - } - if (decoder.readBoolean()) { - let unitsInTick = decoder.readUInt(); - let timeScale = decoder.readUInt(); - let fixedFrameRate = decoder.readBoolean(); - let frameDuration = timeScale/(2*unitsInTick); - console.log(`timescale: ${timeScale}; unitsInTick: ${unitsInTick}; fixedFramerate: ${fixedFrameRate}; avgFrameDuration: ${frameDuration}`); - } - } - return { - width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), - height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)) - }; - } - - static readSliceType(decoder) { - // skip NALu type - decoder.readUByte(); - // discard first_mb_in_slice - decoder.readUEG(); - // return slice_type - return decoder.readUEG(); - } -} - -const Log$4 = getTagged("remuxer:h264"); -// TODO: asm.js -class H264Remuxer extends BaseRemuxer { - - constructor(timescale, scaleFactor=1, params={}) { - super(timescale, scaleFactor); - - this.nextDts = undefined; - this.readyToDecode = false; - this.initialized = false; - - this.firstDTS=0; - this.firstPTS=0; - this.lastDTS=undefined; - this.lastSampleDuration = 0; - this.lastDurations = []; - // this.timescale = 90000; - this.tsAlign = Math.round(this.timescale/60); - - this.mp4track={ - id:BaseRemuxer.getTrackID(), - type: 'video', - len:0, - fragmented:true, - sps:'', - pps:'', - width:0, - height:0, - timescale: timescale, - duration: timescale, - samples: [] - }; - this.samples = []; - this.lastGopDTS = -99999999999999; - this.gop=[]; - this.firstUnit = true; - - this.h264 = new H264Parser(this); - - if (params.sps) { - let arr = new Uint8Array(params.sps); - if ((arr[0] & 0x1f) === 7) { - this.setSPS(arr); - } else { - Log$4.warn("bad SPS in SDP"); - } - } - if (params.pps) { - let arr = new Uint8Array(params.pps); - if ((arr[0] & 0x1f) === 8) { - this.setPPS(arr); - } else { - Log$4.warn("bad PPS in SDP"); - } - } - - if (this.mp4track.pps && this.mp4track.sps) { - this.readyToDecode = true; - } - } - - _scaled(timestamp) { - return timestamp >>> this.scaleFactor; - } - - _unscaled(timestamp) { - return timestamp << this.scaleFactor; - } - - setSPS(sps) { - this.h264.parseSPS(sps); - } - - setPPS(pps) { - this.h264.parsePPS(pps); - } - - remux(nalu) { - if (this.lastGopDTS < nalu.dts) { - this.gop.sort(BaseRemuxer.dtsSortFunc); - for (let unit of this.gop) { - // if (this.firstUnit) { - // unit.ntype = 5;//NALU.IDR; - // this.firstUnit = false; - // } - if (super.remux.call(this, unit)) { - this.mp4track.len += unit.getSize(); - } - } - this.gop = []; - this.lastGopDTS = nalu.dts; - } - if (this.h264.parseNAL(nalu)) { - this.gop.push(nalu); - } - } - - getPayload() { - if (!this.getPayloadBase()) { - return null; - } - - let payload = new Uint8Array(this.mp4track.len); - let offset = 0; - let samples=this.mp4track.samples; - let mp4Sample, lastDTS, pts, dts; - - - // Log.debug(this.samples.map((e)=>{ - // return Math.round((e.dts - this.initDTS)); - // })); - - // let minDuration = Number.MAX_SAFE_INTEGER; - while (this.samples.length) { - let sample = this.samples.shift(); - if (sample === null) { - // discontinuity - this.nextDts = undefined; - break; - } - - let unit = sample.unit; - - pts = sample.pts- this.initDTS; // /*Math.round(*/(sample.pts - this.initDTS)/*/this.tsAlign)*this.tsAlign*/; - dts = sample.dts - this.initDTS; ///*Math.round(*/(sample.dts - this.initDTS)/*/this.tsAlign)*this.tsAlign*/; - // ensure DTS is not bigger than PTS - dts = Math.min(pts,dts); - // if not first AVC sample of video track, normalize PTS/DTS with previous sample value - // and ensure that sample duration is positive - if (lastDTS !== undefined) { - let sampleDuration = this.scaled(dts - lastDTS); - // Log.debug(`Sample duration: ${sampleDuration}`); - if (sampleDuration < 0) { - Log$4.log(`invalid AVC sample duration at PTS/DTS: ${pts}/${dts}|lastDTS: ${lastDTS}:${sampleDuration}`); - this.mp4track.len -= unit.getSize(); - continue; - } - // minDuration = Math.min(sampleDuration, minDuration); - this.lastDurations.push(sampleDuration); - if (this.lastDurations.length > 100) { - this.lastDurations.shift(); - } - mp4Sample.duration = sampleDuration; - } else { - if (this.nextDts) { - let delta = dts - this.nextDts; - // if fragment are contiguous, or delta less than 600ms, ensure there is no overlap/hole between fragments - if (/*contiguous ||*/ Math.abs(Math.round(BaseRemuxer.toMS(delta))) < 600) { - - if (delta) { - // set DTS to next DTS - // Log.debug(`Video/PTS/DTS adjusted: ${pts}->${Math.max(pts - delta, this.nextDts)}/${dts}->${this.nextDts},delta:${delta}`); - dts = this.nextDts; - // offset PTS as well, ensure that PTS is smaller or equal than new DTS - pts = Math.max(pts - delta, dts); - } - } else { - if (delta < 0) { - Log$4.log(`skip frame from the past at DTS=${dts} with expected DTS=${this.nextDts}`); - this.mp4track.len -= unit.getSize(); - continue; - } - } - } - // remember first DTS of our avcSamples, ensure value is positive - this.firstDTS = Math.max(0, dts); - } - - mp4Sample = { - size: unit.getSize(), - duration: 0, - cts: this.scaled(pts - dts), - flags: { - isLeading: 0, - isDependedOn: 0, - hasRedundancy: 0, - degradPrio: 0 - } - }; - let flags = mp4Sample.flags; - if (sample.unit.isKeyframe() === true) { - // the current sample is a key frame - flags.dependsOn = 2; - flags.isNonSync = 0; - } else { - flags.dependsOn = 1; - flags.isNonSync = 1; - } - - payload.set(unit.getData(), offset); - offset += unit.getSize(); - - samples.push(mp4Sample); - lastDTS = dts; - } - - if (!samples.length) return null; - - let avgDuration = this.lastDurations.reduce(function(a, b) { return (a|0) + (b|0); }, 0) / (this.lastDurations.length||1)|0; - if (samples.length >= 2) { - this.lastSampleDuration = avgDuration; - mp4Sample.duration = avgDuration; - } else { - mp4Sample.duration = this.lastSampleDuration; - } - - if(samples.length && (!this.nextDts || navigator.userAgent.toLowerCase().indexOf('chrome') > -1)) { - let flags = samples[0].flags; - // chrome workaround, mark first sample as being a Random Access Point to avoid sourcebuffer append issue - // https://code.google.com/p/chromium/issues/detail?id=229412 - flags.dependsOn = 2; - flags.isNonSync = 0; - } - - // next AVC sample DTS should be equal to last sample DTS + last sample duration - this.nextDts = dts + this.unscaled(this.lastSampleDuration); - // Log.debug(`next dts: ${this.nextDts}, last duration: ${this.lastSampleDuration}, last dts: ${dts}`); - - return new Uint8Array(payload.buffer, 0, this.mp4track.len); - } -} - -class PayloadType { - static get H264() {return 1;} - static get AAC() {return 2;} - - static get map() {return { - [PayloadType.H264]: 'video', - [PayloadType.AAC]: 'audio' - }}; - - static get string_map() {return { - H264: PayloadType.H264, - AAC: PayloadType.AAC, - 'MP4A-LATM': PayloadType.AAC, - 'MPEG4-GENERIC': PayloadType.AAC - }} -} - -const LOG_TAG$1 = "remuxer"; -const Log$5 = getTagged(LOG_TAG$1); - -class Remuxer { - static get TrackConverters() {return { - [PayloadType.H264]: H264Remuxer, - [PayloadType.AAC]: AACRemuxer - }}; - - static get TrackScaleFactor() {return { - [PayloadType.H264]: 1,//4, - [PayloadType.AAC]: 0 - }}; - - static get TrackTimescale() {return { - [PayloadType.H264]: 90000,//22500, - [PayloadType.AAC]: 0 - }}; - - constructor(mediaElement) { - this.mse = new MSE([mediaElement]); - this.eventSource = new EventEmitter(); - this.mseEventSource = new EventSourceWrapper(this.mse.eventSource); - this.mse_ready = true; - - this.reset(); - - this.errorListener = this.mseClose.bind(this); - this.closeListener = this.mseClose.bind(this); - - this.eventSource.addEventListener('ready', this.init.bind(this)); - } - - initMSEHandlers() { - this.mseEventSource.on('error', this.errorListener); - this.mseEventSource.on('sourceclosed', this.closeListener); - } - - async reset() { - this.tracks = {}; - this.initialized = false; - this.initSegments = {}; - this.codecs = []; - this.streams = {}; - this.enabled = false; - await this.mse.clear(); - this.initMSEHandlers(); - } - - destroy() { - this.mseEventSource.destroy(); - this.mse.destroy(); - this.mse = null; - - this.detachClient(); - - this.eventSource.destroy(); - } - - onTracks(tracks) { - Log$5.debug(`ontracks: `, tracks.detail); - // store available track types - for (let track of tracks.detail) { - this.tracks[track.type] = new Remuxer.TrackConverters[track.type](Remuxer.TrackTimescale[track.type], Remuxer.TrackScaleFactor[track.type], track.params); - if (track.offset) { - this.tracks[track.type].timeOffset = track.offset; - } - if (track.duration) { - this.tracks[track.type].mp4track.duration = track.duration*(this.tracks[track.type].timescale || Remuxer.TrackTimescale[track.type]); - this.tracks[track.type].duration = track.duration; - } else { - this.tracks[track.type].duration = 1; - } - - // this.tracks[track.type].duration - } - this.mse.setLive(!this.client.seekable); - } - - setTimeOffset(timeOffset, track) { - if (this.tracks[track.type]) { - this.tracks[track.type].timeOffset = timeOffset;///this.tracks[track.type].scaleFactor; - } - } - - init() { - let tracks = []; - this.codecs = []; - let initmse = []; - let initPts = Infinity; - let initDts = Infinity; - for (let track_type in this.tracks) { - let track = this.tracks[track_type]; - if (!MSE.isSupported([track.mp4track.codec])) { - throw new Error(`${track.mp4track.type} codec ${track.mp4track.codec} is not supported`); - } - tracks.push(track.mp4track); - this.codecs.push(track.mp4track.codec); - track.init(initPts, initDts/*, false*/); - // initPts = Math.min(track.initPTS, initPts); - // initDts = Math.min(track.initDTS, initDts); - } - - for (let track_type in this.tracks) { - let track = this.tracks[track_type]; - //track.init(initPts, initDts); - this.initSegments[track_type] = MP4.initSegment([track.mp4track], track.duration*track.timescale, track.timescale); - initmse.push(this.initMSE(track_type, track.mp4track.codec)); - } - this.initialized = true; - return Promise.all(initmse).then(()=>{ - //this.mse.play(); - this.enabled = true; - }); - - } - - initMSE(track_type, codec) { - if (MSE.isSupported(this.codecs)) { - return this.mse.setCodec(track_type, `${PayloadType.map[track_type]}/mp4; codecs="${codec}"`).then(()=>{ - this.mse.feed(track_type, this.initSegments[track_type]); - // this.mse.play(); - // this.enabled = true; - }); - } else { - throw new Error('Codecs are not supported'); - } - } - - mseClose() { - // this.mse.clear(); - this.client.stop(); - this.eventSource.dispatchEvent('stopped'); - } - - flush() { - this.onSamples(); - - if (!this.initialized) { - // Log.debug(`Initialize...`); - if (Object.keys(this.tracks).length) { - for (let track_type in this.tracks) { - if (!this.tracks[track_type].readyToDecode || !this.tracks[track_type].samples.length) return; - Log$5.debug(`Init MSE for track ${this.tracks[track_type].mp4track.type}`); - } - this.eventSource.dispatchEvent('ready'); - } - } else { - - for (let track_type in this.tracks) { - let track = this.tracks[track_type]; - let pay = track.getPayload(); - if (pay && pay.byteLength) { - this.mse.feed(track_type, [MP4.moof(track.seq, track.scaled(track.firstDTS), track.mp4track), MP4.mdat(pay)]); - track.flush(); - } - } - } - } - - onSamples(ev) { - // TODO: check format - // let data = ev.detail; - // if (this.tracks[data.pay] && this.client.sampleQueues[data.pay].length) { - // console.log(`video ${data.units[0].dts}`); - for (let qidx in this.client.sampleQueues) { - let queue = this.client.sampleQueues[qidx]; - while (queue.length) { - let units = queue.shift(); - for (let chunk of units) { - this.tracks[qidx].remux(chunk); - } - } - } - // } - } - - onAudioConfig(ev) { - if (this.tracks[ev.detail.pay]) { - this.tracks[ev.detail.pay].setConfig(ev.detail.config); - } - } - - attachClient(client) { - this.detachClient(); - this.client = client; - this.clientEventSource = new EventSourceWrapper(this.client.eventSource); - this.clientEventSource.on('samples', this.samplesListener); - this.clientEventSource.on('audio_config', this.audioConfigListener); - this.clientEventSource.on('tracks', this.onTracks.bind(this)); - this.clientEventSource.on('flush', this.flush.bind(this)); - this.clientEventSource.on('clear', ()=>{ - this.reset(); - this.mse.clear().then(()=>{ - //this.mse.play(); - this.initMSEHandlers(); - }); - }); - } - - detachClient() { - if (this.client) { - this.clientEventSource.destroy(); - // this.client.eventSource.removeEventListener('samples', this.onSamples.bind(this)); - // this.client.eventSource.removeEventListener('audio_config', this.onAudioConfig.bind(this)); - // // TODO: clear other listeners - // this.client.eventSource.removeEventListener('clear', this._clearListener); - // this.client.eventSource.removeEventListener('tracks', this._tracksListener); - // this.client.eventSource.removeEventListener('flush', this._flushListener); - this.client = null; - } - } -} - -class State { - constructor(name, stateMachine) { - this.stateMachine = stateMachine; - this.transitions = new Set(); - this.name = name; - } - - - activate() { - return Promise.resolve(null); - } - - finishTransition() {} - - failHandler() {} - - deactivate() { - return Promise.resolve(null); - } -} - -class StateMachine { - constructor() { - this.storage = {}; - this.currentState = null; - this.states = new Map(); - } - - addState(name, {activate, finishTransition, deactivate}) { - let state = new State(name, this); - if (activate) state.activate = activate; - if (finishTransition) state.finishTransition = finishTransition; - if (deactivate) state.deactivate = deactivate; - this.states.set(name, state); - return this; - } - - addTransition(fromName, toName){ - if (!this.states.has(fromName)) { - throw ReferenceError(`No such state: ${fromName} while connecting to ${toName}`); - } - if (!this.states.has(toName)) { - throw ReferenceError(`No such state: ${toName} while connecting from ${fromName}`); - } - this.states.get(fromName).transitions.add(toName); - return this; - } - - _promisify(res) { - let promise; - try { - promise = res; - if (!promise.then) { - promise = Promise.resolve(promise); - } - } catch (e) { - promise = Promise.reject(e); - } - return promise; - } - - transitionTo(stateName) { - if (this.currentState == null) { - let state = this.states.get(stateName); - return this._promisify(state.activate.call(this)) - .then((data)=> { - this.currentState = state; - return data; - }).then(state.finishTransition.bind(this)).catch((e)=>{ - state.failHandler(); - throw e; - }); - } - if (this.currentState.name == stateName) return Promise.resolve(); - if (this.currentState.transitions.has(stateName)) { - let state = this.states.get(stateName); - return this._promisify(state.deactivate.call(this)) - .then(state.activate.bind(this)).then((data)=> { - this.currentState = state; - return data; - }).then(state.finishTransition.bind(this)).catch((e)=>{ - state.failHandler(); - throw e; - }); - } else { - return Promise.reject(`No such transition: ${this.currentState.name} to ${stateName}`); - } - } - -} - -// export * from 'bp_statemachine'; - -const Log$6 = getTagged("parser:sdp"); - -class SDPParser { - constructor() { - this.version = -1; - this.origin = null; - this.sessionName = null; - this.timing = null; - this.sessionBlock = {}; - this.media = {}; - this.tracks = {}; - this.mediaMap = {}; - } - - parse(content) { - // Log.debug(content); - return new Promise((resolve, reject) => { - var dataString = content; - var success = true; - var currentMediaBlock = this.sessionBlock; - - // TODO: multiple audio/video tracks - - for (let line of dataString.split("\n")) { - line = line.replace(/\r/, ''); - if (0 === line.length) { - /* Empty row (last row perhaps?), skip to next */ - continue; - } - - switch (line.charAt(0)) { - case 'v': - if (-1 !== this.version) { - Log$6.log('Version present multiple times in SDP'); - reject(); - return false; - } - success = success && this._parseVersion(line); - break; - - case 'o': - if (null !== this.origin) { - Log$6.log('Origin present multiple times in SDP'); - reject(); - return false; - } - success = success && this._parseOrigin(line); - break; - - case 's': - if (null !== this.sessionName) { - Log$6.log('Session Name present multiple times in SDP'); - reject(); - return false; - } - success = success && this._parseSessionName(line); - break; - - case 't': - if (null !== this.timing) { - Log$6.log('Timing present multiple times in SDP'); - reject(); - return false; - } - success = success && this._parseTiming(line); - break; - - case 'm': - if (null !== currentMediaBlock && this.sessionBlock !== currentMediaBlock) { - /* Complete previous block and store it */ - this.media[currentMediaBlock.type] = currentMediaBlock; - } - - /* A wild media block appears */ - currentMediaBlock = {}; - currentMediaBlock.rtpmap = {}; - this._parseMediaDescription(line, currentMediaBlock); - break; - - case 'a': - SDPParser._parseAttribute(line, currentMediaBlock); - break; - - default: - Log$6.log('Ignored unknown SDP directive: ' + line); - break; - } - - if (!success) { - reject(); - return; - } - } - - this.media[currentMediaBlock.type] = currentMediaBlock; - - success ? resolve() : reject(); - }); - } - - _parseVersion(line) { - let matches = line.match(/^v=([0-9]+)$/); - if (!matches || !matches.length) { - Log$6.log('\'v=\' (Version) formatted incorrectly: ' + line); - return false; - } - - this.version = matches[1]; - if (0 != this.version) { - Log$6.log('Unsupported SDP version:' + this.version); - return false; - } - - return true; - } - - _parseOrigin(line) { - let matches = line.match(/^o=([^ ]+) (-?[0-9]+) (-?[0-9]+) (IN) (IP4|IP6) ([^ ]+)$/); - if (!matches || !matches.length) { - Log$6.log('\'o=\' (Origin) formatted incorrectly: ' + line); - return false; - } - - this.origin = {}; - this.origin.username = matches[1]; - this.origin.sessionid = matches[2]; - this.origin.sessionversion = matches[3]; - this.origin.nettype = matches[4]; - this.origin.addresstype = matches[5]; - this.origin.unicastaddress = matches[6]; - - return true; - } - - _parseSessionName(line) { - let matches = line.match(/^s=([^\r\n]+)$/); - if (!matches || !matches.length) { - Log$6.log('\'s=\' (Session Name) formatted incorrectly: ' + line); - return false; - } - - this.sessionName = matches[1]; - - return true; - } - - _parseTiming(line) { - let matches = line.match(/^t=([0-9]+) ([0-9]+)$/); - if (!matches || !matches.length) { - Log$6.log('\'t=\' (Timing) formatted incorrectly: ' + line); - return false; - } - - this.timing = {}; - this.timing.start = matches[1]; - this.timing.stop = matches[2]; - - return true; - } - - _parseMediaDescription(line, media) { - let matches = line.match(/^m=([^ ]+) ([^ ]+) ([^ ]+)[ ]/); - if (!matches || !matches.length) { - Log$6.log('\'m=\' (Media) formatted incorrectly: ' + line); - return false; - } - - media.type = matches[1]; - media.port = matches[2]; - media.proto = matches[3]; - media.fmt = line.substr(matches[0].length).split(' ').map(function (fmt, index, array) { - return parseInt(fmt); - }); - - for (let fmt of media.fmt) { - this.mediaMap[fmt] = media; - } - - return true; - } - - static _parseAttribute(line, media) { - if (null === media) { - /* Not in a media block, can't be bothered parsing attributes for session */ - return true; - } - - var matches; - /* Used for some cases of below switch-case */ - var separator = line.indexOf(':'); - var attribute = line.substr(0, (-1 === separator) ? 0x7FFFFFFF : separator); - /* 0x7FF.. is default */ - - switch (attribute) { - case 'a=recvonly': - case 'a=sendrecv': - case 'a=sendonly': - case 'a=inactive': - media.mode = line.substr('a='.length); - break; - case 'a=range': - matches = line.match(/^a=range:\s*([a-zA-Z-]+)=([0-9.]+|now)\s*-\s*([0-9.]*)$/); - media.range = [Number(matches[2] == "now" ? -1 : matches[2]), Number(matches[3]), matches[1]]; - break; - case 'a=control': - media.control = line.substr('a=control:'.length); - break; - - case 'a=rtpmap': - matches = line.match(/^a=rtpmap:(\d+) (.*)$/); - if (null === matches) { - Log$6.log('Could not parse \'rtpmap\' of \'a=\''); - return false; - } - - var payload = parseInt(matches[1]); - media.rtpmap[payload] = {}; - - var attrs = matches[2].split('/'); - media.rtpmap[payload].name = attrs[0].toUpperCase(); - media.rtpmap[payload].clock = attrs[1]; - if (undefined !== attrs[2]) { - media.rtpmap[payload].encparams = attrs[2]; - } - media.ptype = PayloadType.string_map[attrs[0].toUpperCase()]; - - break; - - case 'a=fmtp': - matches = line.match(/^a=fmtp:(\d+) (.*)$/); - if (0 === matches.length) { - Log$6.log('Could not parse \'fmtp\' of \'a=\''); - return false; - } - - media.fmtp = {}; - for (var param of matches[2].split(';')) { - var idx = param.indexOf('='); - media.fmtp[param.substr(0, idx).toLowerCase().trim()] = param.substr(idx + 1).trim(); - } - break; - } - - return true; - } - - getSessionBlock() { - return this.sessionBlock; - } - - hasMedia(mediaType) { - return this.media[mediaType] != undefined; - } - - getMediaBlock(mediaType) { - return this.media[mediaType]; - } - - getMediaBlockByPayloadType(pt) { - // for (var m in this.media) { - // if (-1 !== this.media[m].fmt.indexOf(pt)) { - // return this.media[m]; - // } - // } - return this.mediaMap[pt] || null; - - //ErrorManager.dispatchError(826, [pt], true); - // Log.error(`failed to find media with payload type ${pt}`); - // - // return null; - } - - getMediaBlockList() { - var res = []; - for (var m in this.media) { - res.push(m); - } - - return res; - } -} - -const LOG_TAG$2 = "rtsp:stream"; -const Log$7 = getTagged(LOG_TAG$2); - -class RTSPStream { - - constructor(client, track) { - this.state = null; - this.client = client; - this.track = track; - this.rtpChannel = 1; - - this.stopKeepAlive(); - this.keepaliveInterval = null; - this.keepaliveTime = 30000; - } - - reset() { - this.stopKeepAlive(); - this.client.forgetRTPChannel(this.rtpChannel); - this.client = null; - this.track = null; - } - - start() { - return this.sendSetup();//.then(this.sendPlay.bind(this)); - } - - stop() { - return this.sendTeardown(); - } - - getSetupURL(track) { - let sessionBlock = this.client.sdp.getSessionBlock(); - if (Url.isAbsolute(track.control)) { - return track.control; - } else if (Url.isAbsolute(`${sessionBlock.control}${track.control}`)) { - return `${sessionBlock.control}${track.control}`; - } else if (Url.isAbsolute(`${this.client.contentBase}${track.control}`)) { - /* Should probably check session level control before this */ - return `${this.client.contentBase}${track.control}`; - } - else {//need return default - return track.control; - } - Log$7.error('Can\'t determine track URL from ' + - 'block.control:' + track.control + ', ' + - 'session.control:' + sessionBlock.control + ', and ' + - 'content-base:' + this.client.contentBase); - } - - getControlURL() { - let ctrl = this.client.sdp.getSessionBlock().control; - if (Url.isAbsolute(ctrl)) { - return ctrl; - } else if (!ctrl || '*' === ctrl) { - return this.client.contentBase; - } else { - return `${this.client.contentBase}${ctrl}`; - } - } - - sendKeepalive() { - if (this.client.methods.includes('GET_PARAMETER')) { - return this.client.sendRequest('GET_PARAMETER', this.getSetupURL(this.track), { - 'Session': this.session - }); - } else { - return this.client.sendRequest('OPTIONS', '*'); - } - } - - stopKeepAlive() { - clearInterval(this.keepaliveInterval); - } - - startKeepAlive() { - this.keepaliveInterval = setInterval(() => { - this.sendKeepalive().catch((e) => { - Log$7.error(e); - if (e instanceof RTSPError) { - if (Number(e.data.parsed.code) == 501) { - return; - } - } - this.client.reconnect(); - }); - }, this.keepaliveTime); - } - - sendRequest(_cmd, _params = {}) { - let params = {}; - if (this.session) { - params['Session'] = this.session; - } - Object.assign(params, _params); - return this.client.sendRequest(_cmd, this.getControlURL(), params); - } - - sendSetup() { - this.state = RTSPClientSM.STATE_SETUP; - this.rtpChannel = this.client.interleaveChannelIndex; - let interleavedChannels = this.client.interleaveChannelIndex++ + "-" + this.client.interleaveChannelIndex++; - return this.client.sendRequest('SETUP', this.getSetupURL(this.track), { - 'Transport': `RTP/AVP/TCP;unicast;interleaved=${interleavedChannels}`, - 'Date': new Date().toUTCString() - }).then((_data) => { - this.session = _data.headers['session']; - let transport = _data.headers['transport']; - if (transport) { - let interleaved = transport.match(/interleaved=([0-9]+)-([0-9]+)/)[1]; - if (interleaved) { - this.rtpChannel = Number(interleaved); - } - } - let sessionParamsChunks = this.session.split(';').slice(1); - let sessionParams = {}; - for (let chunk of sessionParamsChunks) { - let kv = chunk.split('='); - sessionParams[kv[0]]=kv[1]; - } - if (sessionParams['timeout']) { - this.keepaliveInterval = Number(sessionParams['timeout']) * 500; // * 1000 / 2 - } - /*if (!/RTP\/AVP\/TCP;unicast;interleaved=/.test(_data.headers["transport"])) { - // TODO: disconnect stream and notify client - throw new Error("Connection broken"); - }*/ - this.client.useRTPChannel(this.rtpChannel); - this.startKeepAlive(); - return {track: this.track, data: _data}; - }); - } -} - -/* - * JavaScript MD5 - * https://github.com/blueimp/JavaScript-MD5 - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * https://opensource.org/licenses/MIT - * - * Based on - * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message - * Digest Algorithm, as defined in RFC 1321. - * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for more info. - */ - - -/* -* Add integers, wrapping at 2^32. This uses 16-bit operations internally -* to work around bugs in some JS interpreters. -*/ -function safeAdd(x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF); - var msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF) -} - -/* -* Bitwise rotate a 32-bit number to the left. -*/ -function bitRotateLeft(num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)) -} - -/* -* These functions implement the four basic operations the algorithm uses. -*/ -function md5cmn(q, a, b, x, s, t) { - return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) -} -function md5ff(a, b, c, d, x, s, t) { - return md5cmn((b & c) | ((~b) & d), a, b, x, s, t) -} -function md5gg(a, b, c, d, x, s, t) { - return md5cmn((b & d) | (c & (~d)), a, b, x, s, t) -} -function md5hh(a, b, c, d, x, s, t) { - return md5cmn(b ^ c ^ d, a, b, x, s, t) -} -function md5ii(a, b, c, d, x, s, t) { - return md5cmn(c ^ (b | (~d)), a, b, x, s, t) -} - -/* -* Calculate the MD5 of an array of little-endian words, and a bit length. -*/ -function binlMD5(x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << (len % 32); - x[(((len + 64) >>> 9) << 4) + 14] = len; - - var i; - var olda; - var oldb; - var oldc; - var oldd; - var a = 1732584193; - var b = -271733879; - var c = -1732584194; - var d = 271733878; - - for (i = 0; i < x.length; i += 16) { - olda = a; - oldb = b; - oldc = c; - oldd = d; - - a = md5ff(a, b, c, d, x[i], 7, -680876936); - d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); - c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); - b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); - a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); - d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); - c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); - b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); - a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); - d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); - c = md5ff(c, d, a, b, x[i + 10], 17, -42063); - b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); - a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); - d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); - c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); - b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); - - a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); - d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); - c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); - b = md5gg(b, c, d, a, x[i], 20, -373897302); - a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); - d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); - c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); - b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); - a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); - d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); - c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); - b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); - a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); - d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); - c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); - b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); - - a = md5hh(a, b, c, d, x[i + 5], 4, -378558); - d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); - c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); - b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); - a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); - d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); - c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); - b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); - a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); - d = md5hh(d, a, b, c, x[i], 11, -358537222); - c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); - b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); - a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); - d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); - c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); - b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); - - a = md5ii(a, b, c, d, x[i], 6, -198630844); - d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); - c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); - b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); - a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); - d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); - c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); - b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); - a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); - d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); - c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); - b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); - a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); - d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); - c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); - b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); - - a = safeAdd(a, olda); - b = safeAdd(b, oldb); - c = safeAdd(c, oldc); - d = safeAdd(d, oldd); - } - return [a, b, c, d] -} - -/* -* Convert an array of little-endian words to a string -*/ -function binl2rstr(input) { - var i; - var output = ''; - var length32 = input.length * 32; - for (i = 0; i < length32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); - } - return output -} - -/* -* Convert a raw string to an array of little-endian words -* Characters >255 have their high-byte silently ignored. -*/ -function rstr2binl(input) { - var i; - var output = []; - output[(input.length >> 2) - 1] = undefined; - for (i = 0; i < output.length; i += 1) { - output[i] = 0; - } - var length8 = input.length * 8; - for (i = 0; i < length8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); - } - return output -} - -/* -* Calculate the MD5 of a raw string -*/ -function rstrMD5(s) { - return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)) -} - -/* -* Calculate the HMAC-MD5, of a key and some data (raw strings) -*/ -function rstrHMACMD5(key, data) { - var i; - var bkey = rstr2binl(key); - var ipad = []; - var opad = []; - var hash; - ipad[15] = opad[15] = undefined; - if (bkey.length > 16) { - bkey = binlMD5(bkey, key.length * 8); - } - for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636; - opad[i] = bkey[i] ^ 0x5C5C5C5C; - } - hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); - return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)) -} - -/* -* Convert a raw string to a hex string -*/ -function rstr2hex(input) { - var hexTab = '0123456789abcdef'; - var output = ''; - var x; - var i; - for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i); - output += hexTab.charAt((x >>> 4) & 0x0F) + - hexTab.charAt(x & 0x0F); - } - return output -} - -/* -* Encode a string as utf-8 -*/ -function str2rstrUTF8(input) { - return unescape(encodeURIComponent(input)) -} - -/* -* Take string arguments and return either raw or hex encoded strings -*/ -function rawMD5(s) { - return rstrMD5(str2rstrUTF8(s)) -} -function hexMD5(s) { - return rstr2hex(rawMD5(s)) -} -function rawHMACMD5(k, d) { - return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)) -} -function hexHMACMD5(k, d) { - return rstr2hex(rawHMACMD5(k, d)) -} - -function md5(string, key, raw) { - if (!key) { - if (!raw) { - return hexMD5(string) - } - return rawMD5(string) - } - if (!raw) { - return hexHMACMD5(key, string) - } - return rawHMACMD5(key, string) -} - -// TODO: asm.js -class RTP { - constructor(pkt/*uint8array*/, sdp) { - let bytes = new DataView(pkt.buffer, pkt.byteOffset, pkt.byteLength); - - this.version = bytes.getUint8(0) >>> 6; - this.padding = bytes.getUint8(0) & 0x20 >>> 5; - this.has_extension = bytes.getUint8(0) & 0x10 >>> 4; - this.csrc = bytes.getUint8(0) & 0x0F; - this.marker = bytes.getUint8(1) >>> 7; - this.pt = bytes.getUint8(1) & 0x7F; - this.sequence = bytes.getUint16(2) ; - this.timestamp = bytes.getUint32(4); - this.ssrc = bytes.getUint32(8); - this.csrcs = []; - - let pktIndex=12; - if (this.csrc>0) { - this.csrcs.push(bytes.getUint32(pktIndex)); - pktIndex+=4; - } - if (this.has_extension==1) { - this.extension = bytes.getUint16(pktIndex); - this.ehl = bytes.getUint16(pktIndex+2); - pktIndex+=4; - this.header_data = pkt.slice(pktIndex, this.ehl); - pktIndex += this.ehl; - } - - this.headerLength = pktIndex; - let padLength = 0; - if (this.padding) { - padLength = bytes.getUint8(pkt.byteLength-1); - } - - // this.bodyLength = pkt.byteLength-this.headerLength-padLength; - - this.media = sdp.getMediaBlockByPayloadType(this.pt); - if (null === this.media) { - Log.log(`Media description for payload type: ${this.pt} not provided.`); - } else { - this.type = this.media.ptype;//PayloadType.string_map[this.media.rtpmap[this.media.fmt[0]].name]; - } - - this.data = pkt.subarray(pktIndex); - // this.timestamp = 1000 * (this.timestamp / this.media.rtpmap[this.pt].clock); - // console.log(this); - } - getPayload() { - return this.data; - } - - getTimestampMS() { - return this.timestamp; //1000 * (this.timestamp / this.media.rtpmap[this.pt].clock); - } - - toString() { - return "RTP(" + - "version:" + this.version + ", " + - "padding:" + this.padding + ", " + - "has_extension:" + this.has_extension + ", " + - "csrc:" + this.csrc + ", " + - "marker:" + this.marker + ", " + - "pt:" + this.pt + ", " + - "sequence:" + this.sequence + ", " + - "timestamp:" + this.timestamp + ", " + - "ssrc:" + this.ssrc + ")"; - } - - isVideo(){return this.media.type == 'video';} - isAudio(){return this.media.type == 'audio';} - - -} - -class RTPFactory { - constructor(sdp) { - this.tsOffsets={}; - for (let pay in sdp.media) { - for (let pt of sdp.media[pay].fmt) { - this.tsOffsets[pt] = {last: 0, overflow: 0}; - } - } - } - - build(pkt/*uint8array*/, sdp) { - let rtp = new RTP(pkt, sdp); - - let tsOffset = this.tsOffsets[rtp.pt]; - if (tsOffset) { - rtp.timestamp += tsOffset.overflow; - if (tsOffset.last && Math.abs(rtp.timestamp - tsOffset.last) > 0x7fffffff) { - console.log(`\nlast ts: ${tsOffset.last}\n - new ts: ${rtp.timestamp}\n - new ts adjusted: ${rtp.timestamp+0xffffffff}\n - last overflow: ${tsOffset.overflow}\n - new overflow: ${tsOffset.overflow+0xffffffff}\n - `); - tsOffset.overflow += 0xffffffff; - rtp.timestamp += 0xffffffff; - } - /*if (rtp.timestamp>0xffffffff) { - console.log(`ts: ${rtp.timestamp}, seq: ${rtp.sequence}`); - }*/ - tsOffset.last = rtp.timestamp; - } - - return rtp; - } -} - -class RTSPMessage { - static get RTSP_1_0() {return "RTSP/1.0";} - - constructor(_rtsp_version) { - this.version = _rtsp_version; - } - - build(_cmd, _host, _params={}, _payload=null) { - let requestString = `${_cmd} ${_host} ${this.version}\r\n`; - for (let param in _params) { - requestString+=`${param}: ${_params[param]}\r\n`; - } - // TODO: binary payload - if (_payload) { - requestString+=`Content-Length: ${_payload.length}\r\n`; - } - requestString+='\r\n'; - if (_payload) { - requestString+=_payload; - } - return requestString; - } - - parse(_data) { - let lines = _data.split('\r\n'); - let parsed = { - headers:{}, - body:null, - code: 0, - statusLine: '' - }; - - let match; - [match, parsed.code, parsed.statusLine] = lines[0].match(new RegExp(`${this.version}[ ]+([0-9]{3})[ ]+(.*)`)); - parsed.code = Number(parsed.code); - let lineIdx = 1; - - while (lines[lineIdx]) { - let [k,v] = lines[lineIdx].split(/:(.+)/); - parsed.headers[k.toLowerCase()] = v.trim(); - lineIdx++; - } - - parsed.body = lines.slice(lineIdx).join('\n\r'); - - return parsed; - } - -} - -const MessageBuilder = new RTSPMessage(RTSPMessage.RTSP_1_0); - -// TODO: asm.js -class NALUAsm { - - constructor() { - this.fragmented_nalu = null; - } - - - static parseNALHeader(hdr) { - return { - nri: hdr & 0x60, - type: hdr & 0x1F - } - } - - parseSingleNALUPacket(rawData, header, dts, pts) { - return new NALU(header.type, header.nri, rawData.subarray(0), dts, pts); - } - - parseAggregationPacket(rawData, header, dts, pts) { - let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength); - let nal_start_idx = 0; - let don = null; - if (NALU.STAP_B === header.type) { - don = data.getUint16(nal_start_idx); - nal_start_idx += 2; - } - let ret = []; - while (nal_start_idx < data.byteLength) { - let size = data.getUint16(nal_start_idx); - nal_start_idx += 2; - let header = NALUAsm.parseNALHeader(data.getInt8(nal_start_idx)); - nal_start_idx++; - let nalu = this.parseSingleNALUPacket(rawData.subarray(nal_start_idx, nal_start_idx+size), header, dts, pts); - if (nalu !== null) { - ret.push(nalu); - } - nal_start_idx+=size; - } - return ret; - } - - parseFragmentationUnit(rawData, header, dts, pts) { - let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength); - let nal_start_idx = 0; - let fu_header = data.getUint8(nal_start_idx); - let is_start = (fu_header & 0x80) >>> 7; - let is_end = (fu_header & 0x40) >>> 6; - let payload_type = fu_header & 0x1F; - let ret = null; - - nal_start_idx++; - let don = 0; - if (NALU.FU_B === header.type) { - don = data.getUint16(nal_start_idx); - nal_start_idx += 2; - } - - if (is_start) { - this.fragmented_nalu = new NALU(payload_type, header.nri, rawData.subarray(nal_start_idx), dts, pts); - } - if (this.fragmented_nalu && this.fragmented_nalu.ntype === payload_type) { - if (!is_start) { - this.fragmented_nalu.appendData(rawData.subarray(nal_start_idx)); - } - if (is_end) { - ret = this.fragmented_nalu; - this.fragmented_nalu = null; - return ret; - } - } - return null; - } - - onNALUFragment(rawData, dts, pts) { - - let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength); - - let header = NALUAsm.parseNALHeader(data.getUint8(0)); - - let nal_start_idx = 1; - - let unit = null; - if (header.type > 0 && header.type < 24) { - unit = this.parseSingleNALUPacket(rawData.subarray(nal_start_idx), header, dts, pts); - } else if (NALU.FU_A === header.type || NALU.FU_B === header.type) { - unit = this.parseFragmentationUnit(rawData.subarray(nal_start_idx), header, dts, pts); - } else if (NALU.STAP_A === header.type || NALU.STAP_B === header.type) { - return this.parseAggregationPacket(rawData.subarray(nal_start_idx), header, dts, pts); - } else { - /* 30 - 31 is undefined, ignore those (RFC3984). */ - Log.log('Undefined NAL unit, type: ' + header.type); - return null; - } - if (unit) { - return [unit]; - } - return null; - } -} - -class AACFrame { - - constructor(data, dts, pts) { - this.dts = dts; - this.pts = pts ? pts : this.dts; - - this.data=data;//.subarray(offset); - } - - getData() { - return this.data; - } - - getSize() { - return this.data.byteLength; - } -} - -// import {AACParser} from "../parsers/aac.js"; -// TODO: asm.js -class AACAsm { - constructor() { - this.config = null; - } - - onAACFragment(pkt) { - let rawData = pkt.getPayload(); - if (!pkt.media) { - return null; - } - let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength); - - let sizeLength = Number(pkt.media.fmtp['sizelength'] || 0); - let indexLength = Number(pkt.media.fmtp['indexlength'] || 0); - let indexDeltaLength = Number(pkt.media.fmtp['indexdeltalength'] || 0); - let CTSDeltaLength = Number(pkt.media.fmtp['ctsdeltalength'] || 0); - let DTSDeltaLength = Number(pkt.media.fmtp['dtsdeltalength'] || 0); - let RandomAccessIndication = Number(pkt.media.fmtp['randomaccessindication'] || 0); - let StreamStateIndication = Number(pkt.media.fmtp['streamstateindication'] || 0); - let AuxiliaryDataSizeLength = Number(pkt.media.fmtp['auxiliarydatasizelength'] || 0); - - let configHeaderLength = - sizeLength + Math.max(indexLength, indexDeltaLength) + CTSDeltaLength + DTSDeltaLength + - RandomAccessIndication + StreamStateIndication + AuxiliaryDataSizeLength; - - - let auHeadersLengthPadded = 0; - let offset = 0; - let ts = (Math.round(pkt.getTimestampMS()/1024) << 10) * 90000 / this.config.samplerate; - if (0 !== configHeaderLength) { - /* The AU header section is not empty, read it from payload */ - let auHeadersLengthInBits = data.getUint16(0); // Always 2 octets, without padding - auHeadersLengthPadded = 2 + (auHeadersLengthInBits>>>3) + ((auHeadersLengthInBits & 0x7)?1:0); // Add padding - - //this.config = AACParser.parseAudioSpecificConfig(new Uint8Array(rawData, 0 , auHeadersLengthPadded)); - // TODO: parse config - let frames = []; - let frameOffset=0; - let bits = new BitArray(rawData.subarray(2 + offset)); - let cts = 0; - let dts = 0; - for (let offset=0; offset{ - if (this.connected) { - while (this.transport.dataQueue.length) { - this.onData(this.transport.dataQueue.pop()); - } - } - }; - this._onConnect = this.onConnected.bind(this); - this._onDisconnect = this.onDisconnected.bind(this); - } - - static streamType() { - return null; - } - - destroy() { - this.detachTransport(); - } - - attachTransport(transport) { - if (this.transport) { - this.detachTransport(); - } - this.transport = transport; - this.transport.eventSource.addEventListener('data', this._onData); - this.transport.eventSource.addEventListener('connected', this._onConnect); - this.transport.eventSource.addEventListener('disconnected', this._onDisconnect); - } - - detachTransport() { - if (this.transport) { - this.transport.eventSource.removeEventListener('data', this._onData); - this.transport.eventSource.removeEventListener('connected', this._onConnect); - this.transport.eventSource.removeEventListener('disconnected', this._onDisconnect); - this.transport = null; - } - } - reset() { - - } - - start() { - Log.log('Client started'); - this.paused = false; - // this.startStreamFlush(); - } - - stop() { - Log.log('Client paused'); - this.paused = true; - // this.stopStreamFlush(); - } - - seek(timeOffset) { - - } - - setSource(source) { - this.stop(); - this.endpoint = source; - this.sourceUrl = source.urlpath; - } - - startStreamFlush() { - this.flushInterval = setInterval(()=>{ - if (!this.paused) { - this.eventSource.dispatchEvent('flush'); - } - }, this.options.flush); - } - - stopStreamFlush() { - clearInterval(this.flushInterval); - } - - onData(data) { - - } - - onConnected() { - if (!this.seekable) { - this.transport.dataQueue = []; - this.eventSource.dispatchEvent('clear'); - } - this.connected = true; - } - - onDisconnected() { - this.connected = false; - } - - queryCredentials() { - return Promise.resolve(); - } - - setCredentials(user, password) { - this.endpoint.user = user; - this.endpoint.pass = password; - this.endpoint.auth = `${user}:${password}`; - } -} - -class AACParser { - static get SampleRates() {return [ - 96000, 88200, - 64000, 48000, - 44100, 32000, - 24000, 22050, - 16000, 12000, - 11025, 8000, - 7350];} - - // static Profile = [ - // 0: Null - // 1: AAC Main - // 2: AAC LC (Low Complexity) - // 3: AAC SSR (Scalable Sample Rate) - // 4: AAC LTP (Long Term Prediction) - // 5: SBR (Spectral Band Replication) - // 6: AAC Scalable - // ] - - static parseAudioSpecificConfig(bytesOrBits) { - let config; - if (bytesOrBits.byteLength) { // is byteArray - config = new BitArray(bytesOrBits); - } else { - config = bytesOrBits; - } - - let bitpos = config.bitpos+(config.src.byteOffset+config.bytepos)*8; - let prof = config.readBits(5); - this.codec = `mp4a.40.${prof}`; - let sfi = config.readBits(4); - if (sfi == 0xf) config.skipBits(24); - let channels = config.readBits(4); - - return { - config: bitSlice(new Uint8Array(config.src.buffer), bitpos, bitpos+16), - codec: `mp4a.40.${prof}`, - samplerate: AACParser.SampleRates[sfi], - channels: channels - } - } - - static parseStreamMuxConfig(bytes) { - // ISO_IEC_14496-3 Part 3 Audio. StreamMuxConfig - let config = new BitArray(bytes); - - if (!config.readBits(1)) { - config.skipBits(14); - return AACParser.parseAudioSpecificConfig(config); - } - } -} - -const LOG_TAG$3 = "rtsp:session"; -const Log$8 = getTagged(LOG_TAG$3); - -class RTSPSession { - - constructor(client, sessionId) { - this.state = null; - this.client = client; - this.sessionId = sessionId; - this.url = this.getControlURL(); - } - - reset() { - this.client = null; - } - - start() { - return this.sendPlay(); - } - - stop() { - return this.sendTeardown(); - } - - getControlURL() { - let ctrl = this.client.sdp.getSessionBlock().control; - if (Url.isAbsolute(ctrl)) { - return ctrl; - } else if (!ctrl || '*' === ctrl) { - return this.client.contentBase; - } else { - return `${this.client.contentBase}${ctrl}`; - } - } - - sendRequest(_cmd, _params = {}) { - let params = {}; - if (this.sessionId) { - params['Session'] = this.sessionId; - } - Object.assign(params, _params); - return this.client.sendRequest(_cmd, this.getControlURL(), params); - } - - async sendPlay(pos = 0) { - this.state = RTSPClientSM.STATE_PLAY; - let params = {}; - let range = this.client.sdp.sessionBlock.range; - if (range) { - // TODO: seekable - if (range[0] == -1) { - range[0] = 0;// Do not handle now at the moment - } - // params['Range'] = `${range[2]}=${range[0]}-`; - } - let data = await this.sendRequest('PLAY', params); - this.state = RTSPClientSM.STATE_PLAYING; - return {data: data}; - } - - async sendPause() { - if (!this.client.supports("PAUSE")) { - return; - } - this.state = RTSPClientSM.STATE_PAUSE; - await this.sendRequest("PAUSE"); - this.state = RTSPClientSM.STATE_PAUSED; - } - - async sendTeardown() { - if (this.state != RTSPClientSM.STATE_TEARDOWN) { - this.state = RTSPClientSM.STATE_TEARDOWN; - await this.sendRequest("TEARDOWN"); - Log$8.log('RTSPClient: STATE_TEARDOWN'); - ///this.client.connection.disconnect(); - // TODO: Notify client - } - } -} - -// import {RTP} from './rtp/rtp'; -const LOG_TAG$4 = "client:rtsp"; -const Log$9 = getTagged(LOG_TAG$4); - - - -class RTSPClient extends BaseClient { - constructor(options={flush: 200}) { - super(options); - this.clientSM = new RTSPClientSM(this); - this.clientSM.ontracks = (tracks) => { - this.eventSource.dispatchEvent('tracks', tracks); - this.startStreamFlush(); - }; - this.sampleQueues={}; - } - - static streamType() { - return 'rtsp'; - } - - setSource(url) { - super.setSource(url); - this.clientSM.setSource(url); - } - attachTransport(transport) { - super.attachTransport(transport); - this.clientSM.transport = transport; - } - - detachTransport() { - super.detachTransport(); - this.clientSM.transport = null; - } - - reset() { - super.reset(); - this.sampleQueues={}; - } - - destroy() { - this.clientSM.destroy(); - return super.destroy(); - } - - start() { - super.start(); - if (this.transport) { - return this.transport.ready.then(() => { - return this.clientSM.start(); - }); - } else { - return Promise.reject("no transport attached"); - } - } - - stop() { - super.stop(); - return this.clientSM.stop(); - } - - onData(data) { - this.clientSM.onData(data); - } - - onConnected() { - this.clientSM.onConnected(); - super.onConnected(); - } - - onDisconnected() { - super.onDisconnected(); - this.clientSM.onDisconnected(); - } -} - -class AuthError extends Error { - constructor(msg) { - super(msg); - } -} - -class RTSPError extends Error { - constructor(data) { - super(data.msg); - this.data = data; - } -} - -class RTSPClientSM extends StateMachine { - static get USER_AGENT() {return 'SFRtsp 0.3';} - static get STATE_INITIAL() {return 1 << 0;} - static get STATE_OPTIONS() {return 1 << 1;} - static get STATE_DESCRIBE () {return 1 << 2;} - static get STATE_SETUP() {return 1 << 3;} - static get STATE_STREAMS() {return 1 << 4;} - static get STATE_TEARDOWN() {return 1 << 5;} - static get STATE_PLAY() {return 1 << 6;} - static get STATE_PLAYING() {return 1 << 7;} - static get STATE_PAUSE() {return 1 << 8;} - static get STATE_PAUSED() {return 1 << 9;} - // static STATE_PAUSED = 1 << 6; - - constructor(parent) { - super(); - - this.parent = parent; - this.transport = null; - this.payParser = new RTPPayloadParser(); - this.rtp_channels = new Set(); - this.sessions = {}; - this.ontracks = null; - - this.addState(RTSPClientSM.STATE_INITIAL,{ - }).addState(RTSPClientSM.STATE_OPTIONS, { - activate: this.sendOptions, - finishTransition: this.onOptions - }).addState(RTSPClientSM.STATE_DESCRIBE, { - activate: this.sendDescribe, - finishTransition: this.onDescribe - }).addState(RTSPClientSM.STATE_SETUP, { - activate: this.sendSetup, - finishTransition: this.onSetup - }).addState(RTSPClientSM.STATE_STREAMS, { - - }).addState(RTSPClientSM.STATE_TEARDOWN, { - activate: ()=>{ - this.started = false; - }, - finishTransition: ()=>{ - return this.transitionTo(RTSPClientSM.STATE_INITIAL) - } - }).addTransition(RTSPClientSM.STATE_INITIAL, RTSPClientSM.STATE_OPTIONS) - .addTransition(RTSPClientSM.STATE_INITIAL, RTSPClientSM.STATE_TEARDOWN) - .addTransition(RTSPClientSM.STATE_OPTIONS, RTSPClientSM.STATE_DESCRIBE) - .addTransition(RTSPClientSM.STATE_DESCRIBE, RTSPClientSM.STATE_SETUP) - .addTransition(RTSPClientSM.STATE_SETUP, RTSPClientSM.STATE_STREAMS) - .addTransition(RTSPClientSM.STATE_TEARDOWN, RTSPClientSM.STATE_INITIAL) - // .addTransition(RTSPClientSM.STATE_STREAMS, RTSPClientSM.STATE_PAUSED) - // .addTransition(RTSPClientSM.STATE_PAUSED, RTSPClientSM.STATE_STREAMS) - .addTransition(RTSPClientSM.STATE_STREAMS, RTSPClientSM.STATE_TEARDOWN) - // .addTransition(RTSPClientSM.STATE_PAUSED, RTSPClientSM.STATE_TEARDOWN) - .addTransition(RTSPClientSM.STATE_SETUP, RTSPClientSM.STATE_TEARDOWN) - .addTransition(RTSPClientSM.STATE_DESCRIBE, RTSPClientSM.STATE_TEARDOWN) - .addTransition(RTSPClientSM.STATE_OPTIONS, RTSPClientSM.STATE_TEARDOWN); - - this.reset(); - - this.shouldReconnect = false; - - // TODO: remove listeners - // this.connection.eventSource.addEventListener('connected', ()=>{ - // if (this.shouldReconnect) { - // this.reconnect(); - // } - // }); - // this.connection.eventSource.addEventListener('disconnected', ()=>{ - // if (this.started) { - // this.shouldReconnect = true; - // } - // }); - // this.connection.eventSource.addEventListener('data', (data)=>{ - // let channel = new DataView(data).getUint8(1); - // if (this.rtp_channels.has(channel)) { - // this.onRTP({packet: new Uint8Array(data, 4), type: channel}); - // } - // - // }); - } - - destroy() { - this.parent = null; - } - - setSource(url) { - this.reset(); - this.endpoint = url; - this.url = `${url.protocol}://${url.location}${url.urlpath}`; - } - - onConnected() { - if (this.rtpFactory) { - this.rtpFactory = null; - } - if (this.shouldReconnect) { - this.start(); - } - } - - async onDisconnected() { - this.reset(); - this.shouldReconnect = true; - await this.transitionTo(RTSPClientSM.STATE_TEARDOWN); - await this.transitionTo(RTSPClientSM.STATE_INITIAL); - } - - start() { - if (this.currentState.name !== RTSPClientSM.STATE_STREAMS) { - return this.transitionTo(RTSPClientSM.STATE_OPTIONS); - } else { - // TODO: seekable - let promises = []; - for (let session in this.sessions) { - promises.push(this.sessions[session].sendPlay()); - } - return Promise.all(promises); - } - } - - onData(data) { - let channel = data[1]; - if (this.rtp_channels.has(channel)) { - this.onRTP({packet: data.subarray(4), type: channel}); - } - } - - useRTPChannel(channel) { - this.rtp_channels.add(channel); - } - - forgetRTPChannel(channel) { - this.rtp_channels.delete(channel); - } - - stop() { - this.shouldReconnect = false; - let promises = []; - for (let session in this.sessions) { - promises.push(this.sessions[session].sendPause()); - } - return Promise.all(promises); - // this.mse = null; - } - - async reset() { - this.authenticator = ''; - this.methods = []; - this.tracks = []; - this.rtpBuffer={}; - for (let stream in this.streams) { - this.streams[stream].reset(); - } - for (let session in this.sessions) { - this.sessions[session].reset(); - } - this.streams={}; - this.sessions={}; - this.contentBase = ""; - if (this.currentState) { - if (this.currentState.name != RTSPClientSM.STATE_INITIAL) { - await this.transitionTo(RTSPClientSM.STATE_TEARDOWN); - await this.transitionTo(RTSPClientSM.STATE_INITIAL); - } - } else { - await this.transitionTo(RTSPClientSM.STATE_INITIAL); - } - this.sdp = null; - this.interleaveChannelIndex = 0; - this.session = null; - this.timeOffset = {}; - this.lastTimestamp = {}; - } - - async reconnect() { - //this.parent.eventSource.dispatchEvent('clear'); - await this.reset(); - if (this.currentState.name != RTSPClientSM.STATE_INITIAL) { - await this.transitionTo(RTSPClientSM.STATE_TEARDOWN); - return this.transitionTo(RTSPClientSM.STATE_OPTIONS); - } else { - return this.transitionTo(RTSPClientSM.STATE_OPTIONS); - } - } - - supports(method) { - return this.methods.includes(method) - } - - parse(_data) { - Log$9.debug(_data.payload); - let d=_data.payload.split('\r\n\r\n'); - let parsed = MessageBuilder.parse(d[0]); - let len = Number(parsed.headers['content-length']); - if (len) { - let d=_data.payload.split('\r\n\r\n'); - parsed.body = d[1]; - } else { - parsed.body=""; - } - return parsed - } - - sendRequest(_cmd, _host, _params={}, _payload=null) { - this.cSeq++; - Object.assign(_params, { - CSeq: this.cSeq, - 'User-Agent': RTSPClientSM.USER_AGENT - }); - if (this.authenticator) { - _params['Authorization'] = this.authenticator(_cmd); - } - return this.send(MessageBuilder.build(_cmd, _host, _params, _payload), _cmd).catch((e)=>{ - if ((e instanceof AuthError) && !_params['Authorization'] ) { - return this.sendRequest(_cmd, _host, _params, _payload); - } else { - throw e; - } - }); - } - - async send(_data, _method) { - if (this.transport) { - try { - await this.transport.ready; - } catch(e) { - this.onDisconnected(); - throw e; - } - Log$9.debug(_data); - let response = await this.transport.send(_data); - let parsed = this.parse(response); - // TODO: parse status codes - if (parsed.code == 401 /*&& !this.authenticator */) { - Log$9.debug(parsed.headers['www-authenticate']); - let auth = parsed.headers['www-authenticate']; - let method = auth.substring(0, auth.indexOf(' ')); - auth = auth.substr(method.length+1); - let chunks = auth.split(','); - - let ep = this.parent.endpoint; - if (!ep.user || !ep.pass) { - try { - await this.parent.queryCredentials.call(this.parent); - } catch (e) { - throw new AuthError(); - } - } - - if (method.toLowerCase() == 'digest') { - let parsedChunks = {}; - for (let chunk of chunks) { - let c = chunk.trim(); - let [k,v] = c.split('='); - parsedChunks[k] = v.substr(1, v.length-2); - } - this.authenticator = (_method)=>{ - let ep = this.parent.endpoint; - let ha1 = md5(`${ep.user}:${parsedChunks.realm}:${ep.pass}`); - let ha2 = md5(`${_method}:${this.url}`); - let response = md5(`${ha1}:${parsedChunks.nonce}:${ha2}`); - let tail=''; // TODO: handle other params - return `Digest username="${ep.user}", realm="${parsedChunks.realm}", nonce="${parsedChunks.nonce}", uri="${this.url}", response="${response}"${tail}`; - }; - } else { - this.authenticator = ()=>{return `Basic ${btoa(this.parent.endpoint.auth)}`;}; - } - - throw new AuthError(parsed); - } - if (parsed.code >= 300) { - Log$9.error(parsed.statusLine); - throw new RTSPError({msg: `RTSP error: ${parsed.code} ${parsed.statusLine}`, parsed: parsed}); - } - return parsed; - } else { - return Promise.reject("No transport attached"); - } - } - - sendOptions() { - this.reset(); - this.started = true; - this.cSeq = 0; - return this.sendRequest('OPTIONS', '*', {}); - } - - onOptions(data) { - this.methods = data.headers['public'].split(',').map((e)=>e.trim()); - this.transitionTo(RTSPClientSM.STATE_DESCRIBE); - } - - sendDescribe() { - return this.sendRequest('DESCRIBE', this.url, { - 'Accept': 'application/sdp' - }).then((data)=>{ - this.sdp = new SDPParser(); - return this.sdp.parse(data.body).catch(()=>{ - throw new Error("Failed to parse SDP"); - }).then(()=>{return data;}); - }); - } - - onDescribe(data) { - this.contentBase = data.headers['content-base'] || this.url;// `${this.endpoint.protocol}://${this.endpoint.location}${this.endpoint.urlpath}/`; - this.tracks = this.sdp.getMediaBlockList(); - this.rtpFactory = new RTPFactory(this.sdp); - - Log$9.log('SDP contained ' + this.tracks.length + ' track(s). Calling SETUP for each.'); - - if (data.headers['session']) { - this.session = data.headers['session']; - } - - if (!this.tracks.length) { - throw new Error("No tracks in SDP"); - } - - this.transitionTo(RTSPClientSM.STATE_SETUP); - } - - sendSetup() { - let streams=[]; - - // TODO: select first video and first audio tracks - for (let track_type of this.tracks) { - Log$9.log("setup track: "+track_type); - // if (track_type=='audio') continue; - // if (track_type=='video') continue; - let track = this.sdp.getMediaBlock(track_type); - if (!PayloadType.string_map[track.rtpmap[track.fmt[0]].name]) continue; - - this.streams[track_type] = new RTSPStream(this, track); - let setupPromise = this.streams[track_type].start(); - this.parent.sampleQueues[PayloadType.string_map[track.rtpmap[track.fmt[0]].name]]=[]; - this.rtpBuffer[track.fmt[0]]=[]; - streams.push(setupPromise.then(({track, data})=>{ - this.timeOffset[track.fmt[0]] = 0; - try { - let rtp_info = data.headers["rtp-info"].split(';'); - for (let chunk of rtp_info) { - let [key, val] = chunk.split("="); - if (key === "rtptime") { - this.timeOffset[track.fmt[0]] = 0;//Number(val); - } - } - } catch (e) { - // new Date().getTime(); - } - let params = { - timescale: 0, - scaleFactor: 0 - }; - if (track.fmtp['sprop-parameter-sets']) { - let sps_pps = track.fmtp['sprop-parameter-sets'].split(','); - params = { - sps:base64ToArrayBuffer(sps_pps[0]), - pps:base64ToArrayBuffer(sps_pps[1]) - }; - } else if (track.fmtp['config']) { - let config = track.fmtp['config']; - this.has_config = track.fmtp['cpresent']!='0'; - let generic = track.rtpmap[track.fmt[0]].name == 'MPEG4-GENERIC'; - if (generic) { - params={config: - AACParser.parseAudioSpecificConfig(hexToByteArray(config)) - }; - this.payParser.aacparser.setConfig(params.config); - } else if (config) { - // todo: parse audio specific config for mpeg4-generic - params={config: - AACParser.parseStreamMuxConfig(hexToByteArray(config)) - }; - this.payParser.aacparser.setConfig(params.config); - } - } - params.duration = this.sdp.sessionBlock.range?this.sdp.sessionBlock.range[1]-this.sdp.sessionBlock.range[0]:1; - this.parent.seekable = (params.duration > 1); - let res = { - track: track, - offset: this.timeOffset[track.fmt[0]], - type: PayloadType.string_map[track.rtpmap[track.fmt[0]].name], - params: params, - duration: params.duration - }; - console.log(res, this.timeOffset); - let session = data.headers.session.split(';')[0]; - if (!this.sessions[session]) { - this.sessions[session] = new RTSPSession(this, session); - } - return res; - })); - } - return Promise.all(streams).then((tracks)=>{ - let sessionPromises = []; - for (let session in this.sessions) { - sessionPromises.push(this.sessions[session].start()); - } - return Promise.all(sessionPromises).then(()=>{ - if (this.ontracks) { - this.ontracks(tracks); - } - }) - }).catch((e)=>{ - console.error(e); - this.stop(); - this.reset(); - }); - } - - onSetup() { - this.transitionTo(RTSPClientSM.STATE_STREAMS); - } - - onRTP(_data) { - if (!this.rtpFactory) return; - - let rtp = this.rtpFactory.build(_data.packet, this.sdp); - if (!rtp.type) { - return; - } - - if (this.timeOffset[rtp.pt] === undefined) { - //console.log(rtp.pt, this.timeOffset[rtp.pt]); - this.rtpBuffer[rtp.pt].push(rtp); - return; - } - - if (this.lastTimestamp[rtp.pt] === undefined) { - this.lastTimestamp[rtp.pt] = rtp.timestamp-this.timeOffset[rtp.pt]; - } - - let queue = this.rtpBuffer[rtp.pt]; - queue.push(rtp); - - while (queue.length) { - let rtp = queue.shift(); - - rtp.timestamp = rtp.timestamp-this.timeOffset[rtp.pt]-this.lastTimestamp[rtp.pt]; - // TODO: overflow - // if (rtp.timestamp < 0) { - // rtp.timestamp = (rtp.timestamp + Number.MAX_SAFE_INTEGER) % 0x7fffffff; - // } - if (rtp.media) { - let pay = this.payParser.parse(rtp); - if (pay) { - // if (typeof pay == typeof []) { - this.parent.sampleQueues[rtp.type].push(pay); - // } else { - // this.parent.sampleQueues[rtp.type].push([pay]); - // } - } - } - } - // this.remuxer.feedRTP(); - } -} - -// ASN.1 JavaScript decoder -// Copyright (c) 2008-2013 Lapo Luchini - -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ -/*global oids */ - -var hardLimit = 100; -var ellipsis = "\u2026"; -var DOM = { - tag: function (tagName, className) { - var t = document.createElement(tagName); - t.className = className; - return t; - }, - text: function (str) { - return document.createTextNode(str); - } - }; - -class Stream { - static get hexDigits() { - return "0123456789ABCDEF"; - }; - - static get reTime() { - return /^((?:1[89]|2\d)?\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/; - } - - - constructor(enc, pos) { - if (enc instanceof Stream) { - this.enc = enc.enc; - this.pos = enc.pos; - } else { - this.enc = enc; - this.pos = pos; - } - } - - get(pos) { - if (pos === undefined) - pos = this.pos++; - if (pos >= this.enc.length) - throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; - return this.enc[pos]; - }; - - hexByte(b) { - return Stream.hexDigits.charAt((b >> 4) & 0xF) + Stream.hexDigits.charAt(b & 0xF); - }; - - hexDump(start, end, raw) { - var s = ""; - for (var i = start; i < end; ++i) { - s += this.hexByte(this.get(i)); - if (raw !== true) - switch (i & 0xF) { - case 0x7: - s += " "; - break; - case 0xF: - s += "\n"; - break; - default: - s += " "; - } - } - return s; - }; - - parseStringISO(start, end) { - var s = ""; - for (var i = start; i < end; ++i) - s += String.fromCharCode(this.get(i)); - return s; - }; - - parseStringUTF(start, end) { - var s = ""; - for (var i = start; i < end;) { - var c = this.get(i++); - if (c < 128) - s += String.fromCharCode(c); - else if ((c > 191) && (c < 224)) - s += String.fromCharCode(((c & 0x1F) << 6) | (this.get(i++) & 0x3F)); - else - s += String.fromCharCode(((c & 0x0F) << 12) | ((this.get(i++) & 0x3F) << 6) | (this.get(i++) & 0x3F)); - } - return s; - }; - - parseStringBMP(start, end) { - var str = ""; - for (var i = start; i < end; i += 2) { - var high_byte = this.get(i); - var low_byte = this.get(i + 1); - str += String.fromCharCode((high_byte << 8) + low_byte); - } - - return str; - }; - - parseTime(start, end) { - var s = this.parseStringISO(start, end), - m = Stream.reTime.exec(s); - if (!m) - return "Unrecognized time: " + s; - s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4]; - if (m[5]) { - s += ":" + m[5]; - if (m[6]) { - s += ":" + m[6]; - if (m[7]) - s += "." + m[7]; - } - } - if (m[8]) { - s += " UTC"; - if (m[8] != 'Z') { - s += m[8]; - if (m[9]) - s += ":" + m[9]; - } - } - return s; - }; - - parseInteger(start, end) { - //TODO support negative numbers - var len = end - start; - if (len > 4) { - len <<= 3; - var s = this.get(start); - if (s === 0) - len -= 8; - else - while (s < 128) { - s <<= 1; - --len; - } - return "(" + len + " bit)"; - } - var n = 0; - for (var i = start; i < end; ++i) - n = (n << 8) | this.get(i); - return n; - }; - - parseBitString(start, end) { - var unusedBit = this.get(start), - lenBit = ((end - start - 1) << 3) - unusedBit, - s = "(" + lenBit + " bit)"; - if (lenBit <= 20) { - var skip = unusedBit; - s += " "; - for (var i = end - 1; i > start; --i) { - var b = this.get(i); - for (var j = skip; j < 8; ++j) - s += (b >> j) & 1 ? "1" : "0"; - skip = 0; - } - } - return s; - }; - - parseOctetString(start, end) { - var len = end - start, - s = "(" + len + " byte) "; - if (len > hardLimit) - end = start + hardLimit; - for (var i = start; i < end; ++i) - s += this.hexByte(this.get(i)); //TODO: also try Latin1? - if (len > hardLimit) - s += ellipsis; - return s; - }; - - parseOID(start, end) { - var s = '', - n = 0, - bits = 0; - for (var i = start; i < end; ++i) { - var v = this.get(i); - n = (n << 7) | (v & 0x7F); - bits += 7; - if (!(v & 0x80)) { // finished - if (s === '') { - var m = n < 80 ? n < 40 ? 0 : 1 : 2; - s = m + "." + (n - m * 40); - } else - s += "." + ((bits >= 31) ? "bigint" : n); - n = bits = 0; - } - } - return s; - }; -} - -class ASN1 { - static get reSeemsASCII() { - return /^[ -~]+$/; - } - - constructor(stream, header, length, tag, sub) { - this.stream = stream; - this.header = header; - this.length = length; - this.tag = tag; - this.sub = sub; - } - - typeName() { - if (this.tag === undefined) - return "unknown"; - var tagClass = this.tag >> 6, - tagConstructed = (this.tag >> 5) & 1, - tagNumber = this.tag & 0x1F; - switch (tagClass) { - case 0: // universal - switch (tagNumber) { - case 0x00: - return "EOC"; - case 0x01: - return "BOOLEAN"; - case 0x02: - return "INTEGER"; - case 0x03: - return "BIT_STRING"; - case 0x04: - return "OCTET_STRING"; - case 0x05: - return "NULL"; - case 0x06: - return "OBJECT_IDENTIFIER"; - case 0x07: - return "ObjectDescriptor"; - case 0x08: - return "EXTERNAL"; - case 0x09: - return "REAL"; - case 0x0A: - return "ENUMERATED"; - case 0x0B: - return "EMBEDDED_PDV"; - case 0x0C: - return "UTF8String"; - case 0x10: - return "SEQUENCE"; - case 0x11: - return "SET"; - case 0x12: - return "NumericString"; - case 0x13: - return "PrintableString"; // ASCII subset - case 0x14: - return "TeletexString"; // aka T61String - case 0x15: - return "VideotexString"; - case 0x16: - return "IA5String"; // ASCII - case 0x17: - return "UTCTime"; - case 0x18: - return "GeneralizedTime"; - case 0x19: - return "GraphicString"; - case 0x1A: - return "VisibleString"; // ASCII subset - case 0x1B: - return "GeneralString"; - case 0x1C: - return "UniversalString"; - case 0x1E: - return "BMPString"; - default: - return "Universal_" + tagNumber.toString(16); - } - case 1: - return "Application_" + tagNumber.toString(16); - case 2: - return "[" + tagNumber + "]"; // Context - case 3: - return "Private_" + tagNumber.toString(16); - } - } - - content() { - if (this.tag === undefined) - return null; - var tagClass = this.tag >> 6, - tagNumber = this.tag & 0x1F, - content = this.posContent(), - len = Math.abs(this.length); - if (tagClass !== 0) { // universal - if (this.sub !== null) - return "(" + this.sub.length + " elem)"; - //TODO: TRY TO PARSE ASCII STRING - var s = this.stream.parseStringISO(content, content + Math.min(len, hardLimit)); - if (ASN1.reSeemsASCII.test(s)) - return s.substring(0, 2 * hardLimit) + ((s.length > 2 * hardLimit) ? ellipsis : ""); - else - return this.stream.parseOctetString(content, content + len); - } - switch (tagNumber) { - case 0x01: // BOOLEAN - return (this.stream.get(content) === 0) ? "false" : "true"; - case 0x02: // INTEGER - return this.stream.parseInteger(content, content + len); - case 0x03: // BIT_STRING - return this.sub ? "(" + this.sub.length + " elem)" : - this.stream.parseBitString(content, content + len); - case 0x04: // OCTET_STRING - return this.sub ? "(" + this.sub.length + " elem)" : - this.stream.parseOctetString(content, content + len); - //case 0x05: // NULL - case 0x06: // OBJECT_IDENTIFIER - return this.stream.parseOID(content, content + len); - //case 0x07: // ObjectDescriptor - //case 0x08: // EXTERNAL - //case 0x09: // REAL - //case 0x0A: // ENUMERATED - //case 0x0B: // EMBEDDED_PDV - case 0x10: // SEQUENCE - case 0x11: // SET - return "(" + this.sub.length + " elem)"; - case 0x0C: // UTF8String - return this.stream.parseStringUTF(content, content + len); - case 0x12: // NumericString - case 0x13: // PrintableString - case 0x14: // TeletexString - case 0x15: // VideotexString - case 0x16: // IA5String - //case 0x19: // GraphicString - case 0x1A: // VisibleString - //case 0x1B: // GeneralString - //case 0x1C: // UniversalString - return this.stream.parseStringISO(content, content + len); - case 0x1E: // BMPString - return this.stream.parseStringBMP(content, content + len); - case 0x17: // UTCTime - case 0x18: // GeneralizedTime - return this.stream.parseTime(content, content + len); - } - return null; - }; - - toString() { - return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]"; - }; - - print(indent) { - if (indent === undefined) indent = ''; - document.writeln(indent + this); - if (this.sub !== null) { - indent += ' '; - for (var i = 0, max = this.sub.length; i < max; ++i) - this.sub[i].print(indent); - } - }; - - toPrettyString(indent) { - if (indent === undefined) indent = ''; - var s = indent + this.typeName() + " @" + this.stream.pos; - if (this.length >= 0) - s += "+"; - s += this.length; - if (this.tag & 0x20) - s += " (constructed)"; - else if (((this.tag == 0x03) || (this.tag == 0x04)) && (this.sub !== null)) - s += " (encapsulates)"; - s += "\n"; - if (this.sub !== null) { - indent += ' '; - for (var i = 0, max = this.sub.length; i < max; ++i) - s += this.sub[i].toPrettyString(indent); - } - return s; - }; - - toDOM() { - var node = DOM.tag("div", "node"); - node.asn1 = this; - var head = DOM.tag("div", "head"); - var s = this.typeName().replace(/_/g, " "); - head.innerHTML = s; - var content = this.content(); - if (content !== null) { - content = String(content).replace(/"; - s += "Length: " + this.header + "+"; - if (this.length >= 0) - s += this.length; - else - s += (-this.length) + " (undefined)"; - if (this.tag & 0x20) - s += "
(constructed)"; - else if (((this.tag == 0x03) || (this.tag == 0x04)) && (this.sub !== null)) - s += "
(encapsulates)"; - //TODO if (this.tag == 0x03) s += "Unused bits: " - if (content !== null) { - s += "
Value:
" + content + ""; - if ((typeof oids === 'object') && (this.tag == 0x06)) { - var oid = oids[content]; - if (oid) { - if (oid.d) s += "
" + oid.d; - if (oid.c) s += "
" + oid.c; - if (oid.w) s += "
(warning!)"; - } - } - } - value.innerHTML = s; - node.appendChild(value); - var sub = DOM.tag("div", "sub"); - if (this.sub !== null) { - for (var i = 0, max = this.sub.length; i < max; ++i) - sub.appendChild(this.sub[i].toDOM()); - } - node.appendChild(sub); - head.onclick = function () { - node.className = (node.className == "node collapsed") ? "node" : "node collapsed"; - }; - return node; - }; - - posStart() { - return this.stream.pos; - }; - - posContent() { - return this.stream.pos + this.header; - }; - - posEnd() { - return this.stream.pos + this.header + Math.abs(this.length); - }; - - fakeHover(current) { - this.node.className += " hover"; - if (current) - this.head.className += " hover"; - }; - - fakeOut(current) { - var re = / ?hover/; - this.node.className = this.node.className.replace(re, ""); - if (current) - this.head.className = this.head.className.replace(re, ""); - }; - - toHexDOM_sub(node, className, stream, start, end) { - if (start >= end) - return; - var sub = DOM.tag("span", className); - sub.appendChild(DOM.text( - stream.hexDump(start, end))); - node.appendChild(sub); - }; - - toHexDOM(root) { - var node = DOM.tag("span", "hex"); - if (root === undefined) root = node; - this.head.hexNode = node; - this.head.onmouseover = function () { - this.hexNode.className = "hexCurrent"; - }; - this.head.onmouseout = function () { - this.hexNode.className = "hex"; - }; - node.asn1 = this; - node.onmouseover = function () { - var current = !root.selected; - if (current) { - root.selected = this.asn1; - this.className = "hexCurrent"; - } - this.asn1.fakeHover(current); - }; - node.onmouseout = function () { - var current = (root.selected == this.asn1); - this.asn1.fakeOut(current); - if (current) { - root.selected = null; - this.className = "hex"; - } - }; - this.toHexDOM_sub(node, "tag", this.stream, this.posStart(), this.posStart() + 1); - this.toHexDOM_sub(node, (this.length >= 0) ? "dlen" : "ulen", this.stream, this.posStart() + 1, this.posContent()); - if (this.sub === null) - node.appendChild(DOM.text( - this.stream.hexDump(this.posContent(), this.posEnd()))); - else if (this.sub.length > 0) { - var first = this.sub[0]; - var last = this.sub[this.sub.length - 1]; - this.toHexDOM_sub(node, "intro", this.stream, this.posContent(), first.posStart()); - for (var i = 0, max = this.sub.length; i < max; ++i) - node.appendChild(this.sub[i].toHexDOM(root)); - this.toHexDOM_sub(node, "outro", this.stream, last.posEnd(), this.posEnd()); - } - return node; - }; - - toHexString(root) { - return this.stream.hexDump(this.posStart(), this.posEnd(), true); - }; - -} - -ASN1.decodeLength = function (stream) { - var buf = stream.get(), - len = buf & 0x7F; - if (len == buf) - return len; - if (len > 3) - throw "Length over 24 bits not supported at position " + (stream.pos - 1); - if (len === 0) - return -1; // undefined - buf = 0; - for (var i = 0; i < len; ++i) - buf = (buf << 8) | stream.get(); - return buf; -}; -ASN1.hasContent = function (tag, len, stream) { - if (tag & 0x20) // constructed - return true; - if ((tag < 0x03) || (tag > 0x04)) - return false; - var p = new Stream(stream); - if (tag == 0x03) p.get(); // BitString unused bits, must be in [0, 7] - var subTag = p.get(); - if ((subTag >> 6) & 0x01) // not (universal or context) - return false; - try { - var subLength = ASN1.decodeLength(p); - return ((p.pos - stream.pos) + subLength == len); - } catch (exception) { - return false; - } -}; -ASN1.decode = function (stream) { - if (!(stream instanceof Stream)) - stream = new Stream(stream, 0); - var streamStart = new Stream(stream), - tag = stream.get(), - len = ASN1.decodeLength(stream), - header = stream.pos - streamStart.pos, - sub = null; - if (ASN1.hasContent(tag, len, stream)) { - // it has content, so we decode it - var start = stream.pos; - if (tag == 0x03) stream.get(); // skip BitString unused bits, must be in [0, 7] - sub = []; - if (len >= 0) { - // definite length - var end = start + len; - while (stream.pos < end) - sub[sub.length] = ASN1.decode(stream); - if (stream.pos != end) - throw "Content size is not correct for container starting at offset " + start; - } else { - // undefined length - try { - for (; ;) { - var s = ASN1.decode(stream); - if (s.tag === 0) - break; - sub[sub.length] = s; - } - len = start - stream.pos; - } catch (e) { - throw "Exception while decoding undefined length content: " + e; - } - } - } else - stream.pos += len; // skip content - return new ASN1(streamStart, header, len, tag, sub); -}; -ASN1.test = function () { - var test = [ - {value: [0x27], expected: 0x27}, - {value: [0x81, 0xC9], expected: 0xC9}, - {value: [0x83, 0xFE, 0xDC, 0xBA], expected: 0xFEDCBA} - ]; - for (var i = 0, max = test.length; i < max; ++i) { - var pos = 0, - stream = new Stream(test[i].value, 0), - res = ASN1.decodeLength(stream); - if (res != test[i].expected) - document.write("In test[" + i + "] expected " + test[i].expected + " got " + res + "\n"); - } -}; - -// prng4.js - uses Arcfour as a PRNG - -class Arcfour { - constructor() { - this.i = 0; - this.j = 0; - this.S = []; - } -} - -// Initialize arcfour context from key, an array of ints, each from [0..255] -function ARC4init(key) { - var i, j, t; - for(i = 0; i < 256; ++i) - this.S[i] = i; - j = 0; - for(i = 0; i < 256; ++i) { - j = (j + this.S[i] + key[i % key.length]) & 255; - t = this.S[i]; - this.S[i] = this.S[j]; - this.S[j] = t; - } - this.i = 0; - this.j = 0; -} - -function ARC4next() { - var t; - this.i = (this.i + 1) & 255; - this.j = (this.j + this.S[this.i]) & 255; - t = this.S[this.i]; - this.S[this.i] = this.S[this.j]; - this.S[this.j] = t; - return this.S[(t + this.S[this.i]) & 255]; -} - -Arcfour.prototype.init = ARC4init; -Arcfour.prototype.next = ARC4next; - -// Plug in your RNG constructor here -function prng_newstate() { - return new Arcfour(); -} - -// Pool size must be a multiple of 4 and greater than 32. -// An array of bytes the size of the pool will be passed to init() -var rng_psize = 256; - -// Random number generator - requires a PRNG backend, e.g. prng4.js -var rng_state; -var rng_pool; -var rng_pptr; - -// Initialize the pool with junk if needed. -if(rng_pool == null) { - rng_pool = new Array(); - rng_pptr = 0; - var t; - if(window.crypto && window.crypto.getRandomValues) { - // Extract entropy (2048 bits) from RNG if available - var z = new Uint32Array(256); - window.crypto.getRandomValues(z); - for (t = 0; t < z.length; ++t) - rng_pool[rng_pptr++] = z[t] & 255; - } - - // Use mouse events for entropy, if we do not have enough entropy by the time - // we need it, entropy will be generated by Math.random. - var onMouseMoveListener = function(ev) { - this.count = this.count || 0; - if (this.count >= 256 || rng_pptr >= rng_psize) { - if (window.removeEventListener) - window.removeEventListener("mousemove", onMouseMoveListener, false); - else if (window.detachEvent) - window.detachEvent("onmousemove", onMouseMoveListener); - return; - } - try { - var mouseCoordinates = ev.x + ev.y; - rng_pool[rng_pptr++] = mouseCoordinates & 255; - this.count += 1; - } catch (e) { - // Sometimes Firefox will deny permission to access event properties for some reason. Ignore. - } - }; - if (window.addEventListener) - window.addEventListener("mousemove", onMouseMoveListener, false); - else if (window.attachEvent) - window.attachEvent("onmousemove", onMouseMoveListener); - -} - -function rng_get_byte() { - if(rng_state == null) { - rng_state = prng_newstate(); - // At this point, we may not have collected enough entropy. If not, fall back to Math.random - while (rng_pptr < rng_psize) { - var random = Math.floor(65536 * Math.random()); - rng_pool[rng_pptr++] = random & 255; - } - rng_state.init(rng_pool); - for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) - rng_pool[rng_pptr] = 0; - rng_pptr = 0; - } - // TODO: allow reseeding after first request - return rng_state.next(); -} - -function rng_get_bytes(ba) { - var i; - for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); -} - -class SecureRandom { - constructor(){} -} - -SecureRandom.prototype.nextBytes = rng_get_bytes; - -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -class BigInteger { - constructor(a,b,c) { - if (a != null) - if ("number" == typeof a) this.fromNumber(a, b, c); - else if (b == null && "string" != typeof a) this.fromString(a, 256); - else this.fromString(a, b); - } -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+this.DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return (this.s<0)?-r:r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -class Classic{ - constructor(m){ - this.m = m; - } -} -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -class Montgomery { - constructor(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp & 0x7fff; - this.mph = this.mp >> 15; - this.um = (1 << (m.DB - 15)) - 1; - this.mt2 = 2 * m.t; - } -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); - -// Copyright (c) 2005-2009 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Extended JavaScript BN functions, required for RSA private ops. - -// Version 1.1: new BigInteger("0", 10) returns "proper" zero -// Version 1.2: square() API, isProbablePrime fix - -// (public) -function bnClone() { var r = nbi(); this.copyTo(r); return r; } - -// (public) return value as integer -function bnIntValue() { - if(this.s < 0) { - if(this.t == 1) return this[0]-this.DV; - else if(this.t == 0) return -1; - } - else if(this.t == 1) return this[0]; - else if(this.t == 0) return 0; - // assumes 16 < DB < 32 - return ((this[1]&((1<<(32-this.DB))-1))<>24; } - -// (public) return value as short (assumes DB>=16) -function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } - -// (protected) return x s.t. r^x < DV -function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } - -// (public) 0 if this == 0, 1 if this > 0 -function bnSigNum() { - if(this.s < 0) return -1; - else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; - else return 1; -} - -// (protected) convert to radix string -function bnpToRadix(b) { - if(b == null) b = 10; - if(this.signum() == 0 || b < 2 || b > 36) return "0"; - var cs = this.chunkSize(b); - var a = Math.pow(b,cs); - var d = nbv(a), y = nbi(), z = nbi(), r = ""; - this.divRemTo(d,y,z); - while(y.signum() > 0) { - r = (a+z.intValue()).toString(b).substr(1) + r; - y.divRemTo(d,y,z); - } - return z.intValue().toString(b) + r; -} - -// (protected) convert from radix string -function bnpFromRadix(s,b) { - this.fromInt(0); - if(b == null) b = 10; - var cs = this.chunkSize(b); - var d = Math.pow(b,cs), mi = false, j = 0, w = 0; - for(var i = 0; i < s.length; ++i) { - var x = intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-" && this.signum() == 0) mi = true; - continue; - } - w = b*w+x; - if(++j >= cs) { - this.dMultiply(d); - this.dAddOffset(w,0); - j = 0; - w = 0; - } - } - if(j > 0) { - this.dMultiply(Math.pow(b,j)); - this.dAddOffset(w,0); - } - if(mi) BigInteger.ZERO.subTo(this,this); -} - -// (protected) alternate constructor -function bnpFromNumber(a,b,c) { - if("number" == typeof b) { - // new BigInteger(int,int,RNG) - if(a < 2) this.fromInt(1); - else { - this.fromNumber(a,c); - if(!this.testBit(a-1)) // force MSB set - this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); - if(this.isEven()) this.dAddOffset(1,0); // force odd - while(!this.isProbablePrime(b)) { - this.dAddOffset(2,0); - if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); - } - } - } - else { - // new BigInteger(int,RNG) - var x = [], t = a&7; - x.length = (a>>3)+1; - b.nextBytes(x); - if(t > 0) x[0] &= ((1< 0) { - if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) - r[k++] = d|(this.s<<(this.DB-p)); - while(i >= 0) { - if(p < 8) { - d = (this[i]&((1<>(p+=this.DB-8); - } - else { - d = (this[i]>>(p-=8))&0xff; - if(p <= 0) { p += this.DB; --i; } - } - if((d&0x80) != 0) d |= -256; - if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; - if(k > 0 || d != this.s) r[k++] = d; - } - } - return r; -} - -function bnEquals(a) { return(this.compareTo(a)==0); } -function bnMin(a) { return(this.compareTo(a)<0)?this:a; } -function bnMax(a) { return(this.compareTo(a)>0)?this:a; } - -// (protected) r = this op a (bitwise) -function bnpBitwiseTo(a,op,r) { - var i, f, m = Math.min(a.t,this.t); - for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); - if(a.t < this.t) { - f = a.s&this.DM; - for(i = m; i < this.t; ++i) r[i] = op(this[i],f); - r.t = this.t; - } - else { - f = this.s&this.DM; - for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); - r.t = a.t; - } - r.s = op(this.s,a.s); - r.clamp(); -} - -// (public) this & a -function op_and(x,y) { return x&y; } -function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } - -// (public) this | a -function op_or(x,y) { return x|y; } -function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } - -// (public) this ^ a -function op_xor(x,y) { return x^y; } -function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } - -// (public) this & ~a -function op_andnot(x,y) { return x&~y; } -function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } - -// (public) ~this -function bnNot() { - var r = nbi(); - for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; - r.t = this.t; - r.s = ~this.s; - return r; -} - -// (public) this << n -function bnShiftLeft(n) { - var r = nbi(); - if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); - return r; -} - -// (public) this >> n -function bnShiftRight(n) { - var r = nbi(); - if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); - return r; -} - -// return index of lowest 1-bit in x, x < 2^31 -function lbit(x) { - if(x == 0) return -1; - var r = 0; - if((x&0xffff) == 0) { x >>= 16; r += 16; } - if((x&0xff) == 0) { x >>= 8; r += 8; } - if((x&0xf) == 0) { x >>= 4; r += 4; } - if((x&3) == 0) { x >>= 2; r += 2; } - if((x&1) == 0) ++r; - return r; -} - -// (public) returns index of lowest 1-bit (or -1 if none) -function bnGetLowestSetBit() { - for(var i = 0; i < this.t; ++i) - if(this[i] != 0) return i*this.DB+lbit(this[i]); - if(this.s < 0) return this.t*this.DB; - return -1; -} - -// return number of 1 bits in x -function cbit(x) { - var r = 0; - while(x != 0) { x &= x-1; ++r; } - return r; -} - -// (public) return number of set bits -function bnBitCount() { - var r = 0, x = this.s&this.DM; - for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); - return r; -} - -// (public) true iff nth bit is set -function bnTestBit(n) { - var j = Math.floor(n/this.DB); - if(j >= this.t) return(this.s!=0); - return((this[j]&(1<<(n%this.DB)))!=0); -} - -// (protected) this op (1<>= this.DB; - } - if(a.t < this.t) { - c += a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c += a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += a.s; - } - r.s = (c<0)?-1:0; - if(c > 0) r[i++] = c; - else if(c < -1) r[i++] = this.DV+c; - r.t = i; - r.clamp(); -} - -// (public) this + a -function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } - -// (public) this - a -function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } - -// (public) this * a -function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } - -// (public) this^2 -function bnSquare() { var r = nbi(); this.squareTo(r); return r; } - -// (public) this / a -function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } - -// (public) this % a -function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } - -// (public) [this/a,this%a] -function bnDivideAndRemainder(a) { - var q = nbi(), r = nbi(); - this.divRemTo(a,q,r); - return new Array(q,r); -} - -// (protected) this *= n, this >= 0, 1 < n < DV -function bnpDMultiply(n) { - this[this.t] = this.am(0,n-1,this,0,0,this.t); - ++this.t; - this.clamp(); -} - -// (protected) this += n << w words, this >= 0 -function bnpDAddOffset(n,w) { - if(n == 0) return; - while(this.t <= w) this[this.t++] = 0; - this[w] += n; - while(this[w] >= this.DV) { - this[w] -= this.DV; - if(++w >= this.t) this[this.t++] = 0; - ++this[w]; - } -} - -// A "null" reducer -function NullExp() {} -function nNop(x) { return x; } -function nMulTo(x,y,r) { x.multiplyTo(y,r); } -function nSqrTo(x,r) { x.squareTo(r); } - -NullExp.prototype.convert = nNop; -NullExp.prototype.revert = nNop; -NullExp.prototype.mulTo = nMulTo; -NullExp.prototype.sqrTo = nSqrTo; - -// (public) this^e -function bnPow(e) { return this.exp(e,new NullExp()); } - -// (protected) r = lower n words of "this * a", a.t <= n -// "this" should be the larger one if appropriate. -function bnpMultiplyLowerTo(a,n,r) { - var i = Math.min(this.t+a.t,n); - r.s = 0; // assumes a,this >= 0 - r.t = i; - while(i > 0) r[--i] = 0; - var j; - for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); - for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); - r.clamp(); -} - -// (protected) r = "this * a" without lower n words, n > 0 -// "this" should be the larger one if appropriate. -function bnpMultiplyUpperTo(a,n,r) { - --n; - var i = r.t = this.t+a.t-n; - r.s = 0; // assumes a,this >= 0 - while(--i >= 0) r[i] = 0; - for(i = Math.max(n-this.t,0); i < a.t; ++i) - r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); - r.clamp(); - r.drShiftTo(1,r); -} - -// Barrett modular reduction -function Barrett(m) { - // setup Barrett - this.r2 = nbi(); - this.q3 = nbi(); - BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); - this.m = m; -} - -function barrettConvert(x) { - if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); - else if(x.compareTo(this.m) < 0) return x; - else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } -} - -function barrettRevert(x) { return x; } - -// x = x mod m (HAC 14.42) -function barrettReduce(x) { - x.drShiftTo(this.m.t-1,this.r2); - if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } - this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); - this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); - while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); - x.subTo(this.r2,x); - while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = x^2 mod m; x != r -function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = x*y mod m; x,y != r -function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Barrett.prototype.convert = barrettConvert; -Barrett.prototype.revert = barrettRevert; -Barrett.prototype.reduce = barrettReduce; -Barrett.prototype.mulTo = barrettMulTo; -Barrett.prototype.sqrTo = barrettSqrTo; - -// (public) this^e % m (HAC 14.85) -function bnModPow(e,m) { - var i = e.bitLength(), k, r = nbv(1), z; - if(i <= 0) return r; - else if(i < 18) k = 1; - else if(i < 48) k = 3; - else if(i < 144) k = 4; - else if(i < 768) k = 5; - else k = 6; - if(i < 8) - z = new Classic(m); - else if(m.isEven()) - z = new Barrett(m); - else - z = new Montgomery(m); - - // precomputation - var g = [], n = 3, k1 = k-1, km = (1< 1) { - var g2 = nbi(); - z.sqrTo(g[1],g2); - while(n <= km) { - g[n] = nbi(); - z.mulTo(g2,g[n-2],g[n]); - n += 2; - } - } - - var j = e.t-1, w, is1 = true, r2 = nbi(), t; - i = nbits(e[j])-1; - while(j >= 0) { - if(i >= k1) w = (e[j]>>(i-k1))&km; - else { - w = (e[j]&((1<<(i+1))-1))<<(k1-i); - if(j > 0) w |= e[j-1]>>(this.DB+i-k1); - } - - n = k; - while((w&1) == 0) { w >>= 1; --n; } - if((i -= n) < 0) { i += this.DB; --j; } - if(is1) { // ret == 1, don't bother squaring or multiplying it - g[w].copyTo(r); - is1 = false; - } - else { - while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } - if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } - z.mulTo(r2,g[w],r); - } - - while(j >= 0 && (e[j]&(1< 0) { - x.rShiftTo(g,x); - y.rShiftTo(g,y); - } - while(x.signum() > 0) { - if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); - if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); - if(x.compareTo(y) >= 0) { - x.subTo(y,x); - x.rShiftTo(1,x); - } - else { - y.subTo(x,y); - y.rShiftTo(1,y); - } - } - if(g > 0) y.lShiftTo(g,y); - return y; -} - -// (protected) this % n, n < 2^26 -function bnpModInt(n) { - if(n <= 0) return 0; - var d = this.DV%n, r = (this.s<0)?n-1:0; - if(this.t > 0) - if(d == 0) r = this[0]%n; - else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; - return r; -} - -// (public) 1/this % m (HAC 14.61) -function bnModInverse(m) { - var ac = m.isEven(); - if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; - var u = m.clone(), v = this.clone(); - var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); - while(u.signum() != 0) { - while(u.isEven()) { - u.rShiftTo(1,u); - if(ac) { - if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } - a.rShiftTo(1,a); - } - else if(!b.isEven()) b.subTo(m,b); - b.rShiftTo(1,b); - } - while(v.isEven()) { - v.rShiftTo(1,v); - if(ac) { - if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } - c.rShiftTo(1,c); - } - else if(!d.isEven()) d.subTo(m,d); - d.rShiftTo(1,d); - } - if(u.compareTo(v) >= 0) { - u.subTo(v,u); - if(ac) a.subTo(c,a); - b.subTo(d,b); - } - else { - v.subTo(u,v); - if(ac) c.subTo(a,c); - d.subTo(b,d); - } - } - if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; - if(d.compareTo(m) >= 0) return d.subtract(m); - if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; -} - -var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; -var lplim = (1<<26)/lowprimes[lowprimes.length-1]; - -// (public) test primality with certainty >= 1-.5^t -function bnIsProbablePrime(t) { - var i, x = this.abs(); - if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { - for(i = 0; i < lowprimes.length; ++i) - if(x[0] == lowprimes[i]) return true; - return false; - } - if(x.isEven()) return false; - i = 1; - while(i < lowprimes.length) { - var m = lowprimes[i], j = i+1; - while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; - m = x.modInt(m); - while(i < j) if(m%lowprimes[i++] == 0) return false; - } - return x.millerRabin(t); -} - -// (protected) true if probably prime (HAC 4.24, Miller-Rabin) -function bnpMillerRabin(t) { - var n1 = this.subtract(BigInteger.ONE); - var k = n1.getLowestSetBit(); - if(k <= 0) return false; - var r = n1.shiftRight(k); - t = (t+1)>>1; - if(t > lowprimes.length) t = lowprimes.length; - var a = nbi(); - for(var i = 0; i < t; ++i) { - //Pick bases at random, instead of starting at 2 - a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); - var y = a.modPow(r,this); - if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { - var j = 1; - while(j++ < k && y.compareTo(n1) != 0) { - y = y.modPowInt(2,this); - if(y.compareTo(BigInteger.ONE) == 0) return false; - } - if(y.compareTo(n1) != 0) return false; - } - } - return true; -} - -// protected -BigInteger.prototype.chunkSize = bnpChunkSize; -BigInteger.prototype.toRadix = bnpToRadix; -BigInteger.prototype.fromRadix = bnpFromRadix; -BigInteger.prototype.fromNumber = bnpFromNumber; -BigInteger.prototype.bitwiseTo = bnpBitwiseTo; -BigInteger.prototype.changeBit = bnpChangeBit; -BigInteger.prototype.addTo = bnpAddTo; -BigInteger.prototype.dMultiply = bnpDMultiply; -BigInteger.prototype.dAddOffset = bnpDAddOffset; -BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; -BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; -BigInteger.prototype.modInt = bnpModInt; -BigInteger.prototype.millerRabin = bnpMillerRabin; - -// public -BigInteger.prototype.clone = bnClone; -BigInteger.prototype.intValue = bnIntValue; -BigInteger.prototype.byteValue = bnByteValue; -BigInteger.prototype.shortValue = bnShortValue; -BigInteger.prototype.signum = bnSigNum; -BigInteger.prototype.toByteArray = bnToByteArray; -BigInteger.prototype.equals = bnEquals; -BigInteger.prototype.min = bnMin; -BigInteger.prototype.max = bnMax; -BigInteger.prototype.and = bnAnd; -BigInteger.prototype.or = bnOr; -BigInteger.prototype.xor = bnXor; -BigInteger.prototype.andNot = bnAndNot; -BigInteger.prototype.not = bnNot; -BigInteger.prototype.shiftLeft = bnShiftLeft; -BigInteger.prototype.shiftRight = bnShiftRight; -BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; -BigInteger.prototype.bitCount = bnBitCount; -BigInteger.prototype.testBit = bnTestBit; -BigInteger.prototype.setBit = bnSetBit; -BigInteger.prototype.clearBit = bnClearBit; -BigInteger.prototype.flipBit = bnFlipBit; -BigInteger.prototype.add = bnAdd; -BigInteger.prototype.subtract = bnSubtract; -BigInteger.prototype.multiply = bnMultiply; -BigInteger.prototype.divide = bnDivide; -BigInteger.prototype.remainder = bnRemainder; -BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; -BigInteger.prototype.modPow = bnModPow; -BigInteger.prototype.modInverse = bnModInverse; -BigInteger.prototype.pow = bnPow; -BigInteger.prototype.gcd = bnGCD; -BigInteger.prototype.isProbablePrime = bnIsProbablePrime; - -// JSBN-specific extension -BigInteger.prototype.square = bnSquare; - -// BigInteger interfaces not implemented in jsbn: - -// BigInteger(int signum, byte[] magnitude) -// double doubleValue() -// float floatValue() -// int hashCode() -// long longValue() -// static BigInteger valueOf(long val) - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object - -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - console.error("Message too long for RSA"); - return null; - } - var ba = []; - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = []; - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// "empty" RSA key constructor -class RSAKey { - constructor() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; - } -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - console.error("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; - -// Version 1.1: support utf-8 decoding in pkcs1unpad2 - -// Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext - -function pkcs1unpad2(d,n) { - var b = d.toByteArray(); - var i = 0; - while(i < b.length && b[i] == 0) ++i; - if(b.length-i != n-1 || b[i] != 2) - return null; - ++i; - while(b[i] != 0) - if(++i >= b.length) return null; - var ret = ""; - while(++i < b.length) { - var c = b[i] & 255; - if(c < 128) { // utf-8 decode - ret += String.fromCharCode(c); - } - else if((c > 191) && (c < 224)) { - ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63)); - ++i; - } - else { - ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63)); - i += 2; - } - } - return ret; -} - -// Set the private key fields N, e, and d from hex strings -function RSASetPrivate(N,E,D) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - this.d = parseBigInt(D,16); - } - else - console.error("Invalid RSA private key"); -} - -// Set the private key fields N, e, d and CRT params from hex strings -function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - this.d = parseBigInt(D,16); - this.p = parseBigInt(P,16); - this.q = parseBigInt(Q,16); - this.dmp1 = parseBigInt(DP,16); - this.dmq1 = parseBigInt(DQ,16); - this.coeff = parseBigInt(C,16); - } - else - console.error("Invalid RSA private key"); -} - -// Generate a new random private key B bits long, using public expt E -function RSAGenerate(B,E) { - var rng = new SecureRandom(); - var qs = B>>1; - this.e = parseInt(E,16); - var ee = new BigInteger(E,16); - for(;;) { - for(;;) { - this.p = new BigInteger(B-qs,1,rng); - if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; - } - for(;;) { - this.q = new BigInteger(qs,1,rng); - if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; - } - if(this.p.compareTo(this.q) <= 0) { - var t = this.p; - this.p = this.q; - this.q = t; - } - var p1 = this.p.subtract(BigInteger.ONE); - var q1 = this.q.subtract(BigInteger.ONE); - var phi = p1.multiply(q1); - if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { - this.n = this.p.multiply(this.q); - this.d = ee.modInverse(phi); - this.dmp1 = this.d.mod(p1); - this.dmq1 = this.d.mod(q1); - this.coeff = this.q.modInverse(this.p); - break; - } - } -} - -// Perform raw private operation on "x": return x^d (mod n) -function RSADoPrivate(x) { - if(this.p == null || this.q == null) - return x.modPow(this.d, this.n); - - // TODO: re-calculate any missing CRT params - var xp = x.mod(this.p).modPow(this.dmp1, this.p); - var xq = x.mod(this.q).modPow(this.dmq1, this.q); - - while(xp.compareTo(xq) < 0) - xp = xp.add(this.p); - return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); -} - -// Return the PKCS#1 RSA decryption of "ctext". -// "ctext" is an even-length hex string and the output is a plain string. -function RSADecrypt(ctext) { - var c = parseBigInt(ctext, 16); - var m = this.doPrivate(c); - if(m == null) return null; - return pkcs1unpad2(m, (this.n.bitLength()+7)>>3); -} - -// Return the PKCS#1 RSA decryption of "ctext". -// "ctext" is a Base64-encoded string and the output is a plain string. -//function RSAB64Decrypt(ctext) { -// var h = b64tohex(ctext); -// if(h) return this.decrypt(h); else return null; -//} - -// protected -RSAKey.prototype.doPrivate = RSADoPrivate; - -// public -RSAKey.prototype.setPrivate = RSASetPrivate; -RSAKey.prototype.setPrivateEx = RSASetPrivateEx; -RSAKey.prototype.generate = RSAGenerate; -RSAKey.prototype.decrypt = RSADecrypt; -//RSAKey.prototype.b64_decrypt = RSAB64Decrypt; - -// Base64 JavaScript decoder -// Copyright (c) 2008-2013 Lapo Luchini - -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ - -const Base64 = {}; -let decoder; - -Base64.decode = function (a) { - var i; - if (decoder === undefined) { - var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", - ignore = "= \f\n\r\t\u00A0\u2028\u2029"; - decoder = []; - for (i = 0; i < 64; ++i) - decoder[b64.charAt(i)] = i; - for (i = 0; i < ignore.length; ++i) - decoder[ignore.charAt(i)] = -1; - } - var out = []; - var bits = 0, char_count = 0; - for (i = 0; i < a.length; ++i) { - var c = a.charAt(i); - if (c == '=') - break; - c = decoder[c]; - if (c == -1) - continue; - if (c === undefined) - throw 'Illegal character at offset ' + i; - bits |= c; - if (++char_count >= 4) { - out[out.length] = (bits >> 16); - out[out.length] = (bits >> 8) & 0xFF; - out[out.length] = bits & 0xFF; - bits = 0; - char_count = 0; - } else { - bits <<= 6; - } - } - switch (char_count) { - case 1: - throw "Base64 encoding incomplete: at least 2 bits missing"; - case 2: - out[out.length] = (bits >> 10); - break; - case 3: - out[out.length] = (bits >> 16); - out[out.length] = (bits >> 8) & 0xFF; - break; - } - return out; -}; - -Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/; -Base64.unarmor = function (a) { - var m = Base64.re.exec(a); - if (m) { - if (m[1]) - a = m[1]; - else if (m[2]) - a = m[2]; - else - throw "RegExp out of sync"; - } - return Base64.decode(a); -}; - -// Hex JavaScript decoder -// Copyright (c) 2008-2013 Lapo Luchini - -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -/*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */ -const Hex = {}; -let decoder$1; - -Hex.decode = function(a) { - var i; - if (decoder$1 === undefined) { - var hex = "0123456789ABCDEF", - ignore = " \f\n\r\t\u00A0\u2028\u2029"; - decoder$1 = []; - for (i = 0; i < 16; ++i) - decoder$1[hex.charAt(i)] = i; - hex = hex.toLowerCase(); - for (i = 10; i < 16; ++i) - decoder$1[hex.charAt(i)] = i; - for (i = 0; i < ignore.length; ++i) - decoder$1[ignore.charAt(i)] = -1; - } - var out = [], - bits = 0, - char_count = 0; - for (i = 0; i < a.length; ++i) { - var c = a.charAt(i); - if (c == '=') - break; - c = decoder$1[c]; - if (c == -1) - continue; - if (c === undefined) - throw 'Illegal character at offset ' + i; - bits |= c; - if (++char_count >= 2) { - out[out.length] = bits; - bits = 0; - char_count = 0; - } else { - bits <<= 4; - } - } - if (char_count) - throw "Hex encoding incomplete: 4 bits missing"; - return out; -}; - -/*! asn1-1.0.2.js (c) 2013 Kenji Urushima | kjur.github.com/jsrsasign/license - */ - -const JSX = /*window.JSX || */{}; -JSX.env = JSX.env || {}; - -var L = JSX; -var OP = Object.prototype; -var FUNCTION_TOSTRING = '[object Function]'; -var ADD = ["toString", "valueOf"]; - -JSX.env.parseUA = function(agent) { - - var numberify = function(s) { - var c = 0; - return parseFloat(s.replace(/\./g, function() { - return (c++ == 1) ? '' : '.'; - })); - }, - - nav = navigator, - o = { - ie: 0, - opera: 0, - gecko: 0, - webkit: 0, - chrome: 0, - mobile: null, - air: 0, - ipad: 0, - iphone: 0, - ipod: 0, - ios: null, - android: 0, - webos: 0, - caja: nav && nav.cajaVersion, - secure: false, - os: null - - }, - - ua = agent || (navigator && navigator.userAgent), - loc = window && window.location, - href = loc && loc.href, - m; - - o.secure = href && (href.toLowerCase().indexOf("https") === 0); - - if (ua) { - - if ((/windows|win32/i).test(ua)) { - o.os = 'windows'; - } else if ((/macintosh/i).test(ua)) { - o.os = 'macintosh'; - } else if ((/rhino/i).test(ua)) { - o.os = 'rhino'; - } - if ((/KHTML/).test(ua)) { - o.webkit = 1; - } - m = ua.match(/AppleWebKit\/([^\s]*)/); - if (m && m[1]) { - o.webkit = numberify(m[1]); - if (/ Mobile\//.test(ua)) { - o.mobile = 'Apple'; // iPhone or iPod Touch - m = ua.match(/OS ([^\s]*)/); - if (m && m[1]) { - m = numberify(m[1].replace('_', '.')); - } - o.ios = m; - o.ipad = o.ipod = o.iphone = 0; - m = ua.match(/iPad|iPod|iPhone/); - if (m && m[0]) { - o[m[0].toLowerCase()] = o.ios; - } - } else { - m = ua.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/); - if (m) { - o.mobile = m[0]; - } - if (/webOS/.test(ua)) { - o.mobile = 'WebOS'; - m = ua.match(/webOS\/([^\s]*);/); - if (m && m[1]) { - o.webos = numberify(m[1]); - } - } - if (/ Android/.test(ua)) { - o.mobile = 'Android'; - m = ua.match(/Android ([^\s]*);/); - if (m && m[1]) { - o.android = numberify(m[1]); - } - } - } - m = ua.match(/Chrome\/([^\s]*)/); - if (m && m[1]) { - o.chrome = numberify(m[1]); // Chrome - } else { - m = ua.match(/AdobeAIR\/([^\s]*)/); - if (m) { - o.air = m[0]; // Adobe AIR 1.0 or better - } - } - } - if (!o.webkit) { - m = ua.match(/Opera[\s\/]([^\s]*)/); - if (m && m[1]) { - o.opera = numberify(m[1]); - m = ua.match(/Version\/([^\s]*)/); - if (m && m[1]) { - o.opera = numberify(m[1]); // opera 10+ - } - m = ua.match(/Opera Mini[^;]*/); - if (m) { - o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316 - } - } else { // not opera or webkit - m = ua.match(/MSIE\s([^;]*)/); - if (m && m[1]) { - o.ie = numberify(m[1]); - } else { // not opera, webkit, or ie - m = ua.match(/Gecko\/([^\s]*)/); - if (m) { - o.gecko = 1; // Gecko detected, look for revision - m = ua.match(/rv:([^\s\)]*)/); - if (m && m[1]) { - o.gecko = numberify(m[1]); - } - } - } - } - } - } - return o; -}; - -JSX.env.ua = JSX.env.parseUA(); - -JSX.isFunction = function(o) { - return (typeof o === 'function') || OP.toString.apply(o) === FUNCTION_TOSTRING; -}; - -JSX._IEEnumFix = (JSX.env.ua.ie) ? function(r, s) { - var i, fname, f; - for (i=0;iMIT License - */ - -/** - * kjur's class library name space - *

- * This name space provides following name spaces: - *

    - *
  • {@link KJUR.asn1} - ASN.1 primitive hexadecimal encoder
  • - *
  • {@link KJUR.asn1.x509} - ASN.1 structure for X.509 certificate and CRL
  • - *
  • {@link KJUR.crypto} - Java Cryptographic Extension(JCE) style MessageDigest/Signature - * class and utilities
  • - *
- *

- * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. - * @name KJUR - * @namespace kjur's class library name space - */ -// if (typeof KJUR == "undefined" || !KJUR) -const KJUR = {}; - -/** - * kjur's ASN.1 class library name space - *

- * This is ITU-T X.690 ASN.1 DER encoder class library and - * class structure and methods is very similar to - * org.bouncycastle.asn1 package of - * well known BouncyCaslte Cryptography Library. - * - *

PROVIDING ASN.1 PRIMITIVES

- * Here are ASN.1 DER primitive classes. - *
    - *
  • {@link KJUR.asn1.DERBoolean}
  • - *
  • {@link KJUR.asn1.DERInteger}
  • - *
  • {@link KJUR.asn1.DERBitString}
  • - *
  • {@link KJUR.asn1.DEROctetString}
  • - *
  • {@link KJUR.asn1.DERNull}
  • - *
  • {@link KJUR.asn1.DERObjectIdentifier}
  • - *
  • {@link KJUR.asn1.DERUTF8String}
  • - *
  • {@link KJUR.asn1.DERNumericString}
  • - *
  • {@link KJUR.asn1.DERPrintableString}
  • - *
  • {@link KJUR.asn1.DERTeletexString}
  • - *
  • {@link KJUR.asn1.DERIA5String}
  • - *
  • {@link KJUR.asn1.DERUTCTime}
  • - *
  • {@link KJUR.asn1.DERGeneralizedTime}
  • - *
  • {@link KJUR.asn1.DERSequence}
  • - *
  • {@link KJUR.asn1.DERSet}
  • - *
- * - *

OTHER ASN.1 CLASSES

- *
    - *
  • {@link KJUR.asn1.ASN1Object}
  • - *
  • {@link KJUR.asn1.DERAbstractString}
  • - *
  • {@link KJUR.asn1.DERAbstractTime}
  • - *
  • {@link KJUR.asn1.DERAbstractStructured}
  • - *
  • {@link KJUR.asn1.DERTaggedObject}
  • - *
- *

- * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. - * @name KJUR.asn1 - * @namespace - */ -if (typeof KJUR.asn1 == "undefined" || !KJUR.asn1) KJUR.asn1 = {}; - -/** - * ASN1 utilities class - * @name KJUR.asn1.ASN1Util - * @classs ASN1 utilities class - * @since asn1 1.0.2 - */ -KJUR.asn1.ASN1Util = new function() { - this.integerToByteHex = function(i) { - var h = i.toString(16); - if ((h.length % 2) == 1) h = '0' + h; - return h; - }; - this.bigIntToMinTwosComplementsHex = function(bigIntegerValue) { - var h = bigIntegerValue.toString(16); - if (h.substr(0, 1) != '-') { - if (h.length % 2 == 1) { - h = '0' + h; - } else { - if (! h.match(/^[0-7]/)) { - h = '00' + h; - } - } - } else { - var hPos = h.substr(1); - var xorLen = hPos.length; - if (xorLen % 2 == 1) { - xorLen += 1; - } else { - if (! h.match(/^[0-7]/)) { - xorLen += 2; - } - } - var hMask = ''; - for (var i = 0; i < xorLen; i++) { - hMask += 'f'; - } - var biMask = new BigInteger(hMask, 16); - var biNeg = biMask.xor(bigIntegerValue).add(BigInteger.ONE); - h = biNeg.toString(16).replace(/^-/, ''); - } - return h; - }; - /** - * get PEM string from hexadecimal data and header string - * @name getPEMStringFromHex - * @memberOf KJUR.asn1.ASN1Util - * @function - * @param {String} dataHex hexadecimal string of PEM body - * @param {String} pemHeader PEM header string (ex. 'RSA PRIVATE KEY') - * @return {String} PEM formatted string of input data - * @description - * @example - * var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex('616161', 'RSA PRIVATE KEY'); - * // value of pem will be: - * -----BEGIN PRIVATE KEY----- - * YWFh - * -----END PRIVATE KEY----- - */ - this.getPEMStringFromHex = function(dataHex, pemHeader) { - var dataWA = CryptoJS.enc.Hex.parse(dataHex); - var dataB64 = CryptoJS.enc.Base64.stringify(dataWA); - var pemBody = dataB64.replace(/(.{64})/g, "$1\r\n"); - pemBody = pemBody.replace(/\r\n$/, ''); - return "-----BEGIN " + pemHeader + "-----\r\n" + - pemBody + - "\r\n-----END " + pemHeader + "-----\r\n"; - }; -}; - -// ******************************************************************** -// Abstract ASN.1 Classes -// ******************************************************************** - -// ******************************************************************** - -/** - * base class for ASN.1 DER encoder object - * @name KJUR.asn1.ASN1Object - * @class base class for ASN.1 DER encoder object - * @property {Boolean} isModified flag whether internal data was changed - * @property {String} hTLV hexadecimal string of ASN.1 TLV - * @property {String} hT hexadecimal string of ASN.1 TLV tag(T) - * @property {String} hL hexadecimal string of ASN.1 TLV length(L) - * @property {String} hV hexadecimal string of ASN.1 TLV value(V) - * @description - */ -KJUR.asn1.ASN1Object = function() { - var hV = ''; - - /** - * get hexadecimal ASN.1 TLV length(L) bytes from TLV value(V) - * @name getLengthHexFromValue - * @memberOf KJUR.asn1.ASN1Object - * @function - * @return {String} hexadecimal string of ASN.1 TLV length(L) - */ - this.getLengthHexFromValue = function() { - if (typeof this.hV == "undefined" || this.hV == null) { - throw "this.hV is null or undefined."; - } - if (this.hV.length % 2 == 1) { - throw "value hex must be even length: n=" + hV.length + ",v=" + this.hV; - } - var n = this.hV.length / 2; - var hN = n.toString(16); - if (hN.length % 2 == 1) { - hN = "0" + hN; - } - if (n < 128) { - return hN; - } else { - var hNlen = hN.length / 2; - if (hNlen > 15) { - throw "ASN.1 length too long to represent by 8x: n = " + n.toString(16); - } - var head = 128 + hNlen; - return head.toString(16) + hN; - } - }; - - /** - * get hexadecimal string of ASN.1 TLV bytes - * @name getEncodedHex - * @memberOf KJUR.asn1.ASN1Object - * @function - * @return {String} hexadecimal string of ASN.1 TLV - */ - this.getEncodedHex = function() { - if (this.hTLV == null || this.isModified) { - this.hV = this.getFreshValueHex(); - this.hL = this.getLengthHexFromValue(); - this.hTLV = this.hT + this.hL + this.hV; - this.isModified = false; - //console.error("first time: " + this.hTLV); - } - return this.hTLV; - }; - - /** - * get hexadecimal string of ASN.1 TLV value(V) bytes - * @name getValueHex - * @memberOf KJUR.asn1.ASN1Object - * @function - * @return {String} hexadecimal string of ASN.1 TLV value(V) bytes - */ - this.getValueHex = function() { - this.getEncodedHex(); - return this.hV; - }; - - this.getFreshValueHex = function() { - return ''; - }; -}; - -// == BEGIN DERAbstractString ================================================ -/** - * base class for ASN.1 DER string classes - * @name KJUR.asn1.DERAbstractString - * @class base class for ASN.1 DER string classes - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @property {String} s internal string of value - * @extends KJUR.asn1.ASN1Object - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • str - specify initial ASN.1 value(V) by a string
  • - *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERAbstractString = function(params) { - KJUR.asn1.DERAbstractString.superclass.constructor.call(this); - this.getString = function() { - return this.s; - }; - - /** - * set value by a string - * @name setString - * @memberOf KJUR.asn1.DERAbstractString - * @function - * @param {String} newS value by a string to set - */ - this.setString = function(newS) { - this.hTLV = null; - this.isModified = true; - this.s = newS; - this.hV = stohex(this.s); - }; - - /** - * set value by a hexadecimal string - * @name setStringHex - * @memberOf KJUR.asn1.DERAbstractString - * @function - * @param {String} newHexString value by a hexadecimal string to set - */ - this.setStringHex = function(newHexString) { - this.hTLV = null; - this.isModified = true; - this.s = null; - this.hV = newHexString; - }; - - this.getFreshValueHex = function() { - return this.hV; - }; - - if (typeof params != "undefined") { - if (typeof params['str'] != "undefined") { - this.setString(params['str']); - } else if (typeof params['hex'] != "undefined") { - this.setStringHex(params['hex']); - } - } -}; -JSX.extend(KJUR.asn1.DERAbstractString, KJUR.asn1.ASN1Object); -// == END DERAbstractString ================================================ - -// == BEGIN DERAbstractTime ================================================== -/** - * base class for ASN.1 DER Generalized/UTCTime class - * @name KJUR.asn1.DERAbstractTime - * @class base class for ASN.1 DER Generalized/UTCTime class - * @param {Array} params associative array of parameters (ex. {'str': '130430235959Z'}) - * @extends KJUR.asn1.ASN1Object - * @description - * @see KJUR.asn1.ASN1Object - superclass - */ -KJUR.asn1.DERAbstractTime = function(params) { - KJUR.asn1.DERAbstractTime.superclass.constructor.call(this); - this.localDateToUTC = function(d) { - utc = d.getTime() + (d.getTimezoneOffset() * 60000); - var utcDate = new Date(utc); - return utcDate; - }; - - this.formatDate = function(dateObject, type) { - var pad = this.zeroPadding; - var d = this.localDateToUTC(dateObject); - var year = String(d.getFullYear()); - if (type == 'utc') year = year.substr(2, 2); - var month = pad(String(d.getMonth() + 1), 2); - var day = pad(String(d.getDate()), 2); - var hour = pad(String(d.getHours()), 2); - var min = pad(String(d.getMinutes()), 2); - var sec = pad(String(d.getSeconds()), 2); - return year + month + day + hour + min + sec + 'Z'; - }; - - this.zeroPadding = function(s, len) { - if (s.length >= len) return s; - return new Array(len - s.length + 1).join('0') + s; - }; - - // --- PUBLIC METHODS -------------------- - /** - * get string value of this string object - * @name getString - * @memberOf KJUR.asn1.DERAbstractTime - * @function - * @return {String} string value of this time object - */ - this.getString = function() { - return this.s; - }; - - /** - * set value by a string - * @name setString - * @memberOf KJUR.asn1.DERAbstractTime - * @function - * @param {String} newS value by a string to set such like "130430235959Z" - */ - this.setString = function(newS) { - this.hTLV = null; - this.isModified = true; - this.s = newS; - this.hV = stohex(this.s); - }; - - /** - * set value by a Date object - * @name setByDateValue - * @memberOf KJUR.asn1.DERAbstractTime - * @function - * @param {Integer} year year of date (ex. 2013) - * @param {Integer} month month of date between 1 and 12 (ex. 12) - * @param {Integer} day day of month - * @param {Integer} hour hours of date - * @param {Integer} min minutes of date - * @param {Integer} sec seconds of date - */ - this.setByDateValue = function(year, month, day, hour, min, sec) { - var dateObject = new Date(Date.UTC(year, month - 1, day, hour, min, sec, 0)); - this.setByDate(dateObject); - }; - - this.getFreshValueHex = function() { - return this.hV; - }; -}; -JSX.extend(KJUR.asn1.DERAbstractTime, KJUR.asn1.ASN1Object); -// == END DERAbstractTime ================================================== - -// == BEGIN DERAbstractStructured ============================================ -/** - * base class for ASN.1 DER structured class - * @name KJUR.asn1.DERAbstractStructured - * @class base class for ASN.1 DER structured class - * @property {Array} asn1Array internal array of ASN1Object - * @extends KJUR.asn1.ASN1Object - * @description - * @see KJUR.asn1.ASN1Object - superclass - */ -KJUR.asn1.DERAbstractStructured = function(params) { - KJUR.asn1.DERAbstractString.superclass.constructor.call(this); - this.setByASN1ObjectArray = function(asn1ObjectArray) { - this.hTLV = null; - this.isModified = true; - this.asn1Array = asn1ObjectArray; - }; - - /** - * append an ASN1Object to internal array - * @name appendASN1Object - * @memberOf KJUR.asn1.DERAbstractStructured - * @function - * @param {ASN1Object} asn1Object to add - */ - this.appendASN1Object = function(asn1Object) { - this.hTLV = null; - this.isModified = true; - this.asn1Array.push(asn1Object); - }; - - this.asn1Array = new Array(); - if (typeof params != "undefined") { - if (typeof params['array'] != "undefined") { - this.asn1Array = params['array']; - } - } -}; -JSX.extend(KJUR.asn1.DERAbstractStructured, KJUR.asn1.ASN1Object); - - -// ******************************************************************** -// ASN.1 Object Classes -// ******************************************************************** - -// ******************************************************************** -/** - * class for ASN.1 DER Boolean - * @name KJUR.asn1.DERBoolean - * @class class for ASN.1 DER Boolean - * @extends KJUR.asn1.ASN1Object - * @description - * @see KJUR.asn1.ASN1Object - superclass - */ -KJUR.asn1.DERBoolean = function() { - KJUR.asn1.DERBoolean.superclass.constructor.call(this); - this.hT = "01"; - this.hTLV = "0101ff"; -}; -JSX.extend(KJUR.asn1.DERBoolean, KJUR.asn1.ASN1Object); - -// ******************************************************************** -/** - * class for ASN.1 DER Integer - * @name KJUR.asn1.DERInteger - * @class class for ASN.1 DER Integer - * @extends KJUR.asn1.ASN1Object - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • int - specify initial ASN.1 value(V) by integer value
  • - *
  • bigint - specify initial ASN.1 value(V) by BigInteger object
  • - *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERInteger = function(params) { - KJUR.asn1.DERInteger.superclass.constructor.call(this); - this.hT = "02"; - - /** - * set value by Tom Wu's BigInteger object - * @name setByBigInteger - * @memberOf KJUR.asn1.DERInteger - * @function - * @param {BigInteger} bigIntegerValue to set - */ - this.setByBigInteger = function(bigIntegerValue) { - this.hTLV = null; - this.isModified = true; - this.hV = KJUR.asn1.ASN1Util.bigIntToMinTwosComplementsHex(bigIntegerValue); - }; - - /** - * set value by integer value - * @name setByInteger - * @memberOf KJUR.asn1.DERInteger - * @function - * @param {Integer} integer value to set - */ - this.setByInteger = function(intValue) { - var bi = new BigInteger(String(intValue), 10); - this.setByBigInteger(bi); - }; - - /** - * set value by integer value - * @name setValueHex - * @memberOf KJUR.asn1.DERInteger - * @function - * @param {String} hexadecimal string of integer value - * @description - *
- * NOTE: Value shall be represented by minimum octet length of - * two's complement representation. - */ - this.setValueHex = function(newHexString) { - this.hV = newHexString; - }; - - this.getFreshValueHex = function() { - return this.hV; - }; - - if (typeof params != "undefined") { - if (typeof params['bigint'] != "undefined") { - this.setByBigInteger(params['bigint']); - } else if (typeof params['int'] != "undefined") { - this.setByInteger(params['int']); - } else if (typeof params['hex'] != "undefined") { - this.setValueHex(params['hex']); - } - } -}; -JSX.extend(KJUR.asn1.DERInteger, KJUR.asn1.ASN1Object); - -// ******************************************************************** -/** - * class for ASN.1 DER encoded BitString primitive - * @name KJUR.asn1.DERBitString - * @class class for ASN.1 DER encoded BitString primitive - * @extends KJUR.asn1.ASN1Object - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • bin - specify binary string (ex. '10111')
  • - *
  • array - specify array of boolean (ex. [true,false,true,true])
  • - *
  • hex - specify hexadecimal string of ASN.1 value(V) including unused bits
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERBitString = function(params) { - KJUR.asn1.DERBitString.superclass.constructor.call(this); - this.hT = "03"; - - /** - * set ASN.1 value(V) by a hexadecimal string including unused bits - * @name setHexValueIncludingUnusedBits - * @memberOf KJUR.asn1.DERBitString - * @function - * @param {String} newHexStringIncludingUnusedBits - */ - this.setHexValueIncludingUnusedBits = function(newHexStringIncludingUnusedBits) { - this.hTLV = null; - this.isModified = true; - this.hV = newHexStringIncludingUnusedBits; - }; - - /** - * set ASN.1 value(V) by unused bit and hexadecimal string of value - * @name setUnusedBitsAndHexValue - * @memberOf KJUR.asn1.DERBitString - * @function - * @param {Integer} unusedBits - * @param {String} hValue - */ - this.setUnusedBitsAndHexValue = function(unusedBits, hValue) { - if (unusedBits < 0 || 7 < unusedBits) { - throw "unused bits shall be from 0 to 7: u = " + unusedBits; - } - var hUnusedBits = "0" + unusedBits; - this.hTLV = null; - this.isModified = true; - this.hV = hUnusedBits + hValue; - }; - - /** - * set ASN.1 DER BitString by binary string - * @name setByBinaryString - * @memberOf KJUR.asn1.DERBitString - * @function - * @param {String} binaryString binary value string (i.e. '10111') - * @description - * Its unused bits will be calculated automatically by length of - * 'binaryValue'.
- * NOTE: Trailing zeros '0' will be ignored. - */ - this.setByBinaryString = function(binaryString) { - binaryString = binaryString.replace(/0+$/, ''); - var unusedBits = 8 - binaryString.length % 8; - if (unusedBits == 8) unusedBits = 0; - for (var i = 0; i <= unusedBits; i++) { - binaryString += '0'; - } - var h = ''; - for (var i = 0; i < binaryString.length - 1; i += 8) { - var b = binaryString.substr(i, 8); - var x = parseInt(b, 2).toString(16); - if (x.length == 1) x = '0' + x; - h += x; - } - this.hTLV = null; - this.isModified = true; - this.hV = '0' + unusedBits + h; - }; - - /** - * set ASN.1 TLV value(V) by an array of boolean - * @name setByBooleanArray - * @memberOf KJUR.asn1.DERBitString - * @function - * @param {array} booleanArray array of boolean (ex. [true, false, true]) - * @description - * NOTE: Trailing falses will be ignored. - */ - this.setByBooleanArray = function(booleanArray) { - var s = ''; - for (var i = 0; i < booleanArray.length; i++) { - if (booleanArray[i] == true) { - s += '1'; - } else { - s += '0'; - } - } - this.setByBinaryString(s); - }; - - /** - * generate an array of false with specified length - * @name newFalseArray - * @memberOf KJUR.asn1.DERBitString - * @function - * @param {Integer} nLength length of array to generate - * @return {array} array of boolean faluse - * @description - * This static method may be useful to initialize boolean array. - */ - this.newFalseArray = function(nLength) { - var a = new Array(nLength); - for (var i = 0; i < nLength; i++) { - a[i] = false; - } - return a; - }; - - this.getFreshValueHex = function() { - return this.hV; - }; - - if (typeof params != "undefined") { - if (typeof params['hex'] != "undefined") { - this.setHexValueIncludingUnusedBits(params['hex']); - } else if (typeof params['bin'] != "undefined") { - this.setByBinaryString(params['bin']); - } else if (typeof params['array'] != "undefined") { - this.setByBooleanArray(params['array']); - } - } -}; -JSX.extend(KJUR.asn1.DERBitString, KJUR.asn1.ASN1Object); - -// ******************************************************************** -/** - * class for ASN.1 DER OctetString - * @name KJUR.asn1.DEROctetString - * @class class for ASN.1 DER OctetString - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @extends KJUR.asn1.DERAbstractString - * @description - * @see KJUR.asn1.DERAbstractString - superclass - */ -KJUR.asn1.DEROctetString = function(params) { - KJUR.asn1.DEROctetString.superclass.constructor.call(this, params); - this.hT = "04"; -}; -JSX.extend(KJUR.asn1.DEROctetString, KJUR.asn1.DERAbstractString); - -// ******************************************************************** -/** - * class for ASN.1 DER Null - * @name KJUR.asn1.DERNull - * @class class for ASN.1 DER Null - * @extends KJUR.asn1.ASN1Object - * @description - * @see KJUR.asn1.ASN1Object - superclass - */ -KJUR.asn1.DERNull = function() { - KJUR.asn1.DERNull.superclass.constructor.call(this); - this.hT = "05"; - this.hTLV = "0500"; -}; -JSX.extend(KJUR.asn1.DERNull, KJUR.asn1.ASN1Object); - -// ******************************************************************** -/** - * class for ASN.1 DER ObjectIdentifier - * @name KJUR.asn1.DERObjectIdentifier - * @class class for ASN.1 DER ObjectIdentifier - * @param {Array} params associative array of parameters (ex. {'oid': '2.5.4.5'}) - * @extends KJUR.asn1.ASN1Object - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • oid - specify initial ASN.1 value(V) by a oid string (ex. 2.5.4.13)
  • - *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERObjectIdentifier = function(params) { - var itox = function(i) { - var h = i.toString(16); - if (h.length == 1) h = '0' + h; - return h; - }; - var roidtox = function(roid) { - var h = ''; - var bi = new BigInteger(roid, 10); - var b = bi.toString(2); - var padLen = 7 - b.length % 7; - if (padLen == 7) padLen = 0; - var bPad = ''; - for (var i = 0; i < padLen; i++) bPad += '0'; - b = bPad + b; - for (var i = 0; i < b.length - 1; i += 7) { - var b8 = b.substr(i, 7); - if (i != b.length - 7) b8 = '1' + b8; - h += itox(parseInt(b8, 2)); - } - return h; - }; - - KJUR.asn1.DERObjectIdentifier.superclass.constructor.call(this); - this.hT = "06"; - - /** - * set value by a hexadecimal string - * @name setValueHex - * @memberOf KJUR.asn1.DERObjectIdentifier - * @function - * @param {String} newHexString hexadecimal value of OID bytes - */ - this.setValueHex = function(newHexString) { - this.hTLV = null; - this.isModified = true; - this.s = null; - this.hV = newHexString; - }; - - /** - * set value by a OID string - * @name setValueOidString - * @memberOf KJUR.asn1.DERObjectIdentifier - * @function - * @param {String} oidString OID string (ex. 2.5.4.13) - */ - this.setValueOidString = function(oidString) { - if (! oidString.match(/^[0-9.]+$/)) { - throw "malformed oid string: " + oidString; - } - var h = ''; - var a = oidString.split('.'); - var i0 = parseInt(a[0]) * 40 + parseInt(a[1]); - h += itox(i0); - a.splice(0, 2); - for (var i = 0; i < a.length; i++) { - h += roidtox(a[i]); - } - this.hTLV = null; - this.isModified = true; - this.s = null; - this.hV = h; - }; - - /** - * set value by a OID name - * @name setValueName - * @memberOf KJUR.asn1.DERObjectIdentifier - * @function - * @param {String} oidName OID name (ex. 'serverAuth') - * @since 1.0.1 - * @description - * OID name shall be defined in 'KJUR.asn1.x509.OID.name2oidList'. - * Otherwise raise error. - */ - this.setValueName = function(oidName) { - if (typeof KJUR.asn1.x509.OID.name2oidList[oidName] != "undefined") { - var oid = KJUR.asn1.x509.OID.name2oidList[oidName]; - this.setValueOidString(oid); - } else { - throw "DERObjectIdentifier oidName undefined: " + oidName; - } - }; - - this.getFreshValueHex = function() { - return this.hV; - }; - - if (typeof params != "undefined") { - if (typeof params['oid'] != "undefined") { - this.setValueOidString(params['oid']); - } else if (typeof params['hex'] != "undefined") { - this.setValueHex(params['hex']); - } else if (typeof params['name'] != "undefined") { - this.setValueName(params['name']); - } - } -}; -JSX.extend(KJUR.asn1.DERObjectIdentifier, KJUR.asn1.ASN1Object); - -// ******************************************************************** -/** - * class for ASN.1 DER UTF8String - * @name KJUR.asn1.DERUTF8String - * @class class for ASN.1 DER UTF8String - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @extends KJUR.asn1.DERAbstractString - * @description - * @see KJUR.asn1.DERAbstractString - superclass - */ -KJUR.asn1.DERUTF8String = function(params) { - KJUR.asn1.DERUTF8String.superclass.constructor.call(this, params); - this.hT = "0c"; -}; -JSX.extend(KJUR.asn1.DERUTF8String, KJUR.asn1.DERAbstractString); - -// ******************************************************************** -/** - * class for ASN.1 DER NumericString - * @name KJUR.asn1.DERNumericString - * @class class for ASN.1 DER NumericString - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @extends KJUR.asn1.DERAbstractString - * @description - * @see KJUR.asn1.DERAbstractString - superclass - */ -KJUR.asn1.DERNumericString = function(params) { - KJUR.asn1.DERNumericString.superclass.constructor.call(this, params); - this.hT = "12"; -}; -JSX.extend(KJUR.asn1.DERNumericString, KJUR.asn1.DERAbstractString); - -// ******************************************************************** -/** - * class for ASN.1 DER PrintableString - * @name KJUR.asn1.DERPrintableString - * @class class for ASN.1 DER PrintableString - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @extends KJUR.asn1.DERAbstractString - * @description - * @see KJUR.asn1.DERAbstractString - superclass - */ -KJUR.asn1.DERPrintableString = function(params) { - KJUR.asn1.DERPrintableString.superclass.constructor.call(this, params); - this.hT = "13"; -}; -JSX.extend(KJUR.asn1.DERPrintableString, KJUR.asn1.DERAbstractString); - -// ******************************************************************** -/** - * class for ASN.1 DER TeletexString - * @name KJUR.asn1.DERTeletexString - * @class class for ASN.1 DER TeletexString - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @extends KJUR.asn1.DERAbstractString - * @description - * @see KJUR.asn1.DERAbstractString - superclass - */ -KJUR.asn1.DERTeletexString = function(params) { - KJUR.asn1.DERTeletexString.superclass.constructor.call(this, params); - this.hT = "14"; -}; -JSX.extend(KJUR.asn1.DERTeletexString, KJUR.asn1.DERAbstractString); - -// ******************************************************************** -/** - * class for ASN.1 DER IA5String - * @name KJUR.asn1.DERIA5String - * @class class for ASN.1 DER IA5String - * @param {Array} params associative array of parameters (ex. {'str': 'aaa'}) - * @extends KJUR.asn1.DERAbstractString - * @description - * @see KJUR.asn1.DERAbstractString - superclass - */ -KJUR.asn1.DERIA5String = function(params) { - KJUR.asn1.DERIA5String.superclass.constructor.call(this, params); - this.hT = "16"; -}; -JSX.extend(KJUR.asn1.DERIA5String, KJUR.asn1.DERAbstractString); - -// ******************************************************************** -/** - * class for ASN.1 DER UTCTime - * @name KJUR.asn1.DERUTCTime - * @class class for ASN.1 DER UTCTime - * @param {Array} params associative array of parameters (ex. {'str': '130430235959Z'}) - * @extends KJUR.asn1.DERAbstractTime - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • str - specify initial ASN.1 value(V) by a string (ex.'130430235959Z')
  • - *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • - *
  • date - specify Date object.
  • - *
- * NOTE: 'params' can be omitted. - *

EXAMPLES

- * @example - * var d1 = new KJUR.asn1.DERUTCTime(); - * d1.setString('130430125959Z'); - * - * var d2 = new KJUR.asn1.DERUTCTime({'str': '130430125959Z'}); - * - * var d3 = new KJUR.asn1.DERUTCTime({'date': new Date(Date.UTC(2015, 0, 31, 0, 0, 0, 0))}); - */ -KJUR.asn1.DERUTCTime = function(params) { - KJUR.asn1.DERUTCTime.superclass.constructor.call(this, params); - this.hT = "17"; - - /** - * set value by a Date object - * @name setByDate - * @memberOf KJUR.asn1.DERUTCTime - * @function - * @param {Date} dateObject Date object to set ASN.1 value(V) - */ - this.setByDate = function(dateObject) { - this.hTLV = null; - this.isModified = true; - this.date = dateObject; - this.s = this.formatDate(this.date, 'utc'); - this.hV = stohex(this.s); - }; - - if (typeof params != "undefined") { - if (typeof params['str'] != "undefined") { - this.setString(params['str']); - } else if (typeof params['hex'] != "undefined") { - this.setStringHex(params['hex']); - } else if (typeof params['date'] != "undefined") { - this.setByDate(params['date']); - } - } -}; -JSX.extend(KJUR.asn1.DERUTCTime, KJUR.asn1.DERAbstractTime); - -// ******************************************************************** -/** - * class for ASN.1 DER GeneralizedTime - * @name KJUR.asn1.DERGeneralizedTime - * @class class for ASN.1 DER GeneralizedTime - * @param {Array} params associative array of parameters (ex. {'str': '20130430235959Z'}) - * @extends KJUR.asn1.DERAbstractTime - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • str - specify initial ASN.1 value(V) by a string (ex.'20130430235959Z')
  • - *
  • hex - specify initial ASN.1 value(V) by a hexadecimal string
  • - *
  • date - specify Date object.
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERGeneralizedTime = function(params) { - KJUR.asn1.DERGeneralizedTime.superclass.constructor.call(this, params); - this.hT = "18"; - - /** - * set value by a Date object - * @name setByDate - * @memberOf KJUR.asn1.DERGeneralizedTime - * @function - * @param {Date} dateObject Date object to set ASN.1 value(V) - * @example - * When you specify UTC time, use 'Date.UTC' method like this:
- * var o = new DERUTCTime(); - * var date = new Date(Date.UTC(2015, 0, 31, 23, 59, 59, 0)); #2015JAN31 23:59:59 - * o.setByDate(date); - */ - this.setByDate = function(dateObject) { - this.hTLV = null; - this.isModified = true; - this.date = dateObject; - this.s = this.formatDate(this.date, 'gen'); - this.hV = stohex(this.s); - }; - - if (typeof params != "undefined") { - if (typeof params['str'] != "undefined") { - this.setString(params['str']); - } else if (typeof params['hex'] != "undefined") { - this.setStringHex(params['hex']); - } else if (typeof params['date'] != "undefined") { - this.setByDate(params['date']); - } - } -}; -JSX.extend(KJUR.asn1.DERGeneralizedTime, KJUR.asn1.DERAbstractTime); - -// ******************************************************************** -/** - * class for ASN.1 DER Sequence - * @name KJUR.asn1.DERSequence - * @class class for ASN.1 DER Sequence - * @extends KJUR.asn1.DERAbstractStructured - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • array - specify array of ASN1Object to set elements of content
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERSequence = function(params) { - KJUR.asn1.DERSequence.superclass.constructor.call(this, params); - this.hT = "30"; - this.getFreshValueHex = function() { - var h = ''; - for (var i = 0; i < this.asn1Array.length; i++) { - var asn1Obj = this.asn1Array[i]; - h += asn1Obj.getEncodedHex(); - } - this.hV = h; - return this.hV; - }; -}; -JSX.extend(KJUR.asn1.DERSequence, KJUR.asn1.DERAbstractStructured); - -// ******************************************************************** -/** - * class for ASN.1 DER Set - * @name KJUR.asn1.DERSet - * @class class for ASN.1 DER Set - * @extends KJUR.asn1.DERAbstractStructured - * @description - *
- * As for argument 'params' for constructor, you can specify one of - * following properties: - *
    - *
  • array - specify array of ASN1Object to set elements of content
  • - *
- * NOTE: 'params' can be omitted. - */ -KJUR.asn1.DERSet = function(params) { - KJUR.asn1.DERSet.superclass.constructor.call(this, params); - this.hT = "31"; - this.getFreshValueHex = function() { - var a = new Array(); - for (var i = 0; i < this.asn1Array.length; i++) { - var asn1Obj = this.asn1Array[i]; - a.push(asn1Obj.getEncodedHex()); - } - a.sort(); - this.hV = a.join(''); - return this.hV; - }; -}; -JSX.extend(KJUR.asn1.DERSet, KJUR.asn1.DERAbstractStructured); - -// ******************************************************************** -/** - * class for ASN.1 DER TaggedObject - * @name KJUR.asn1.DERTaggedObject - * @class class for ASN.1 DER TaggedObject - * @extends KJUR.asn1.ASN1Object - * @description - *
- * Parameter 'tagNoNex' is ASN.1 tag(T) value for this object. - * For example, if you find '[1]' tag in a ASN.1 dump, - * 'tagNoHex' will be 'a1'. - *
- * As for optional argument 'params' for constructor, you can specify *ANY* of - * following properties: - *
    - *
  • explicit - specify true if this is explicit tag otherwise false - * (default is 'true').
  • - *
  • tag - specify tag (default is 'a0' which means [0])
  • - *
  • obj - specify ASN1Object which is tagged
  • - *
- * @example - * d1 = new KJUR.asn1.DERUTF8String({'str':'a'}); - * d2 = new KJUR.asn1.DERTaggedObject({'obj': d1}); - * hex = d2.getEncodedHex(); - */ -KJUR.asn1.DERTaggedObject = function(params) { - KJUR.asn1.DERTaggedObject.superclass.constructor.call(this); - this.hT = "a0"; - this.hV = ''; - this.isExplicit = true; - this.asn1Object = null; - - /** - * set value by an ASN1Object - * @name setString - * @memberOf KJUR.asn1.DERTaggedObject - * @function - * @param {Boolean} isExplicitFlag flag for explicit/implicit tag - * @param {Integer} tagNoHex hexadecimal string of ASN.1 tag - * @param {ASN1Object} asn1Object ASN.1 to encapsulate - */ - this.setASN1Object = function(isExplicitFlag, tagNoHex, asn1Object) { - this.hT = tagNoHex; - this.isExplicit = isExplicitFlag; - this.asn1Object = asn1Object; - if (this.isExplicit) { - this.hV = this.asn1Object.getEncodedHex(); - this.hTLV = null; - this.isModified = true; - } else { - this.hV = null; - this.hTLV = asn1Object.getEncodedHex(); - this.hTLV = this.hTLV.replace(/^../, tagNoHex); - this.isModified = false; - } - }; - - this.getFreshValueHex = function() { - return this.hV; - }; - - if (typeof params != "undefined") { - if (typeof params['tag'] != "undefined") { - this.hT = params['tag']; - } - if (typeof params['explicit'] != "undefined") { - this.isExplicit = params['explicit']; - } - if (typeof params['obj'] != "undefined") { - this.asn1Object = params['obj']; - this.setASN1Object(this.isExplicit, this.hT, this.asn1Object); - } - } -}; -JSX.extend(KJUR.asn1.DERTaggedObject, KJUR.asn1.ASN1Object); - -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64pad="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - while((ret.length & 3) > 0) ret += b64pad; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = ""; - var i; - var k = 0; // b64 state, 0-3 - var slop; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64pad) break; - let v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array - -/** - * Retrieve the hexadecimal value (as a string) of the current ASN.1 element - * @returns {string} - * @public - */ -ASN1.prototype.getHexStringValue = function () { - var hexString = this.toHexString(); - var offset = this.header * 2; - var length = this.length * 2; - return hexString.substr(offset, length); -}; - -/** - * Method to parse a pem encoded string containing both a public or private key. - * The method will translate the pem encoded string in a der encoded string and - * will parse private key and public key parameters. This method accepts public key - * in the rsaencryption pkcs #1 format (oid: 1.2.840.113549.1.1.1). - * - * @todo Check how many rsa formats use the same format of pkcs #1. - * - * The format is defined as: - * PublicKeyInfo ::= SEQUENCE { - * algorithm AlgorithmIdentifier, - * PublicKey BIT STRING - * } - * Where AlgorithmIdentifier is: - * AlgorithmIdentifier ::= SEQUENCE { - * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm - * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) - * } - * and PublicKey is a SEQUENCE encapsulated in a BIT STRING - * RSAPublicKey ::= SEQUENCE { - * modulus INTEGER, -- n - * publicExponent INTEGER -- e - * } - * it's possible to examine the structure of the keys obtained from openssl using - * an asn.1 dumper as the one used here to parse the components: http://lapo.it/asn1js/ - * @argument {string} pem the pem encoded string, can include the BEGIN/END header/footer - * @private - */ -RSAKey.prototype.parseKey = function (pem) { - try { - var modulus = 0; - var public_exponent = 0; - var reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/; - var der = reHex.test(pem) ? Hex.decode(pem) : Base64.unarmor(pem); - var asn1 = ASN1.decode(der); - - //Fixes a bug with OpenSSL 1.0+ private keys - if(asn1.sub.length === 3){ - asn1 = asn1.sub[2].sub[0]; - } - if (asn1.sub.length === 9) { - - // Parse the private key. - modulus = asn1.sub[1].getHexStringValue(); //bigint - this.n = parseBigInt(modulus, 16); - - public_exponent = asn1.sub[2].getHexStringValue(); //int - this.e = parseInt(public_exponent, 16); - - var private_exponent = asn1.sub[3].getHexStringValue(); //bigint - this.d = parseBigInt(private_exponent, 16); - - var prime1 = asn1.sub[4].getHexStringValue(); //bigint - this.p = parseBigInt(prime1, 16); - - var prime2 = asn1.sub[5].getHexStringValue(); //bigint - this.q = parseBigInt(prime2, 16); - - var exponent1 = asn1.sub[6].getHexStringValue(); //bigint - this.dmp1 = parseBigInt(exponent1, 16); - - var exponent2 = asn1.sub[7].getHexStringValue(); //bigint - this.dmq1 = parseBigInt(exponent2, 16); - - var coefficient = asn1.sub[8].getHexStringValue(); //bigint - this.coeff = parseBigInt(coefficient, 16); - - } - else if (asn1.sub.length === 2) { - - // Parse the public key. - var bit_string = asn1.sub[1]; - var sequence = bit_string.sub[0]; - - modulus = sequence.sub[0].getHexStringValue(); - this.n = parseBigInt(modulus, 16); - public_exponent = sequence.sub[1].getHexStringValue(); - this.e = parseInt(public_exponent, 16); - - } - else { - return false; - } - return true; - } - catch (ex) { - return false; - } -}; - -/** - * Translate rsa parameters in a hex encoded string representing the rsa key. - * - * The translation follow the ASN.1 notation : - * RSAPrivateKey ::= SEQUENCE { - * version Version, - * modulus INTEGER, -- n - * publicExponent INTEGER, -- e - * privateExponent INTEGER, -- d - * prime1 INTEGER, -- p - * prime2 INTEGER, -- q - * exponent1 INTEGER, -- d mod (p1) - * exponent2 INTEGER, -- d mod (q-1) - * coefficient INTEGER, -- (inverse of q) mod p - * } - * @returns {string} DER Encoded String representing the rsa private key - * @private - */ -RSAKey.prototype.getPrivateBaseKey = function () { - var options = { - 'array': [ - new KJUR.asn1.DERInteger({'int': 0}), - new KJUR.asn1.DERInteger({'bigint': this.n}), - new KJUR.asn1.DERInteger({'int': this.e}), - new KJUR.asn1.DERInteger({'bigint': this.d}), - new KJUR.asn1.DERInteger({'bigint': this.p}), - new KJUR.asn1.DERInteger({'bigint': this.q}), - new KJUR.asn1.DERInteger({'bigint': this.dmp1}), - new KJUR.asn1.DERInteger({'bigint': this.dmq1}), - new KJUR.asn1.DERInteger({'bigint': this.coeff}) - ] - }; - var seq = new KJUR.asn1.DERSequence(options); - return seq.getEncodedHex(); -}; - -/** - * base64 (pem) encoded version of the DER encoded representation - * @returns {string} pem encoded representation without header and footer - * @public - */ -RSAKey.prototype.getPrivateBaseKeyB64 = function () { - return hex2b64(this.getPrivateBaseKey()); -}; - -/** - * Translate rsa parameters in a hex encoded string representing the rsa public key. - * The representation follow the ASN.1 notation : - * PublicKeyInfo ::= SEQUENCE { - * algorithm AlgorithmIdentifier, - * PublicKey BIT STRING - * } - * Where AlgorithmIdentifier is: - * AlgorithmIdentifier ::= SEQUENCE { - * algorithm OBJECT IDENTIFIER, the OID of the enc algorithm - * parameters ANY DEFINED BY algorithm OPTIONAL (NULL for PKCS #1) - * } - * and PublicKey is a SEQUENCE encapsulated in a BIT STRING - * RSAPublicKey ::= SEQUENCE { - * modulus INTEGER, -- n - * publicExponent INTEGER -- e - * } - * @returns {string} DER Encoded String representing the rsa public key - * @private - */ -RSAKey.prototype.getPublicBaseKey = function () { - var options = { - 'array': [ - new KJUR.asn1.DERObjectIdentifier({'oid': '1.2.840.113549.1.1.1'}), //RSA Encryption pkcs #1 oid - new KJUR.asn1.DERNull() - ] - }; - var first_sequence = new KJUR.asn1.DERSequence(options); - - options = { - 'array': [ - new KJUR.asn1.DERInteger({'bigint': this.n}), - new KJUR.asn1.DERInteger({'int': this.e}) - ] - }; - var second_sequence = new KJUR.asn1.DERSequence(options); - - options = { - 'hex': '00' + second_sequence.getEncodedHex() - }; - var bit_string = new KJUR.asn1.DERBitString(options); - - options = { - 'array': [ - first_sequence, - bit_string - ] - }; - var seq = new KJUR.asn1.DERSequence(options); - return seq.getEncodedHex(); -}; - -/** - * base64 (pem) encoded version of the DER encoded representation - * @returns {string} pem encoded representation without header and footer - * @public - */ -RSAKey.prototype.getPublicBaseKeyB64 = function () { - return hex2b64(this.getPublicBaseKey()); -}; - -/** - * wrap the string in block of width chars. The default value for rsa keys is 64 - * characters. - * @param {string} str the pem encoded string without header and footer - * @param {Number} [width=64] - the length the string has to be wrapped at - * @returns {string} - * @private - */ -RSAKey.prototype.wordwrap = function (str, width) { - width = width || 64; - if (!str) { - return str; - } - var regex = '(.{1,' + width + '})( +|$\n?)|(.{1,' + width + '})'; - return str.match(RegExp(regex, 'g')).join('\n'); -}; - -/** - * Retrieve the pem encoded private key - * @returns {string} the pem encoded private key with header/footer - * @public - */ -RSAKey.prototype.getPrivateKey = function () { - var key = "-----BEGIN RSA PRIVATE KEY-----\n"; - key += this.wordwrap(this.getPrivateBaseKeyB64()) + "\n"; - key += "-----END RSA PRIVATE KEY-----"; - return key; -}; - -/** - * Retrieve the pem encoded public key - * @returns {string} the pem encoded public key with header/footer - * @public - */ -RSAKey.prototype.getPublicKey = function () { - var key = "-----BEGIN PUBLIC KEY-----\n"; - key += this.wordwrap(this.getPublicBaseKeyB64()) + "\n"; - key += "-----END PUBLIC KEY-----"; - return key; -}; - -/** - * Check if the object contains the necessary parameters to populate the rsa modulus - * and public exponent parameters. - * @param {Object} [obj={}] - An object that may contain the two public key - * parameters - * @returns {boolean} true if the object contains both the modulus and the public exponent - * properties (n and e) - * @todo check for types of n and e. N should be a parseable bigInt object, E should - * be a parseable integer number - * @private - */ -RSAKey.prototype.hasPublicKeyProperty = function (obj) { - obj = obj || {}; - return ( - obj.hasOwnProperty('n') && - obj.hasOwnProperty('e') - ); -}; - -/** - * Check if the object contains ALL the parameters of an RSA key. - * @param {Object} [obj={}] - An object that may contain nine rsa key - * parameters - * @returns {boolean} true if the object contains all the parameters needed - * @todo check for types of the parameters all the parameters but the public exponent - * should be parseable bigint objects, the public exponent should be a parseable integer number - * @private - */ -RSAKey.prototype.hasPrivateKeyProperty = function (obj) { - obj = obj || {}; - return ( - obj.hasOwnProperty('n') && - obj.hasOwnProperty('e') && - obj.hasOwnProperty('d') && - obj.hasOwnProperty('p') && - obj.hasOwnProperty('q') && - obj.hasOwnProperty('dmp1') && - obj.hasOwnProperty('dmq1') && - obj.hasOwnProperty('coeff') - ); -}; - -/** - * Parse the properties of obj in the current rsa object. Obj should AT LEAST - * include the modulus and public exponent (n, e) parameters. - * @param {Object} obj - the object containing rsa parameters - * @private - */ -RSAKey.prototype.parsePropertiesFrom = function (obj) { - this.n = obj.n; - this.e = obj.e; - - if (obj.hasOwnProperty('d')) { - this.d = obj.d; - this.p = obj.p; - this.q = obj.q; - this.dmp1 = obj.dmp1; - this.dmq1 = obj.dmq1; - this.coeff = obj.coeff; - } -}; - -/** - * Create a new JSEncryptRSAKey that extends Tom Wu's RSA key object. - * This object is just a decorator for parsing the key parameter - * @param {string|Object} key - The key in string format, or an object containing - * the parameters needed to build a RSAKey object. - * @constructor - */ -class JSEncryptRSAKey extends RSAKey { - constructor(key) { - // Call the super constructor. - super(); - // If a key key was provided. - if (key) { - // If this is a string... - if (typeof key === 'string') { - this.parseKey(key); - } - else if ( - this.hasPrivateKeyProperty(key) || - this.hasPublicKeyProperty(key) - ) { - // Set the values for the key. - this.parsePropertiesFrom(key); - } - } - } -} - -/** - * - * @param {Object} [options = {}] - An object to customize JSEncrypt behaviour - * possible parameters are: - * - default_key_size {number} default: 1024 the key size in bit - * - default_public_exponent {string} default: '010001' the hexadecimal representation of the public exponent - * - log {boolean} default: false whether log warn/error or not - * @constructor - */ -class JSEncrypt{ - constructor (options) { - options = options || {}; - this.default_key_size = parseInt(options.default_key_size) || 1024; - this.default_public_exponent = options.default_public_exponent || '010001'; //65537 default openssl public exponent for rsa key type - this.log = options.log || false; - // The private and public key. - this.key = null; - } -} - -/** - * Method to set the rsa key parameter (one method is enough to set both the public - * and the private key, since the private key contains the public key paramenters) - * Log a warning if logs are enabled - * @param {Object|string} key the pem encoded string or an object (with or without header/footer) - * @public - */ -JSEncrypt.prototype.setKey = function (key) { - if (this.log && this.key) { - console.warn('A key was already set, overriding existing.'); - } - this.key = new JSEncryptRSAKey(key); -}; - -/** - * Proxy method for setKey, for api compatibility - * @see setKey - * @public - */ -JSEncrypt.prototype.setPrivateKey = function (privkey) { - // Create the key. - this.setKey(privkey); -}; - -/** - * Proxy method for setKey, for api compatibility - * @see setKey - * @public - */ -JSEncrypt.prototype.setPublicKey = function (pubkey) { - // Sets the public key. - this.setKey(pubkey); -}; - -/** - * Proxy method for RSAKey object's decrypt, decrypt the string using the private - * components of the rsa key object. Note that if the object was not set will be created - * on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor - * @param {string} string base64 encoded crypted string to decrypt - * @return {string} the decrypted string - * @public - */ -JSEncrypt.prototype.decrypt = function (string) { - // Return the decrypted string. - try { - return this.getKey().decrypt(b64tohex(string)); - } - catch (ex) { - return false; - } -}; - -/** - * Proxy method for RSAKey object's encrypt, encrypt the string using the public - * components of the rsa key object. Note that if the object was not set will be created - * on the fly (by the getKey method) using the parameters passed in the JSEncrypt constructor - * @param {string} string the string to encrypt - * @return {string} the encrypted string encoded in base64 - * @public - */ -JSEncrypt.prototype.encrypt = function (string) { - // Return the encrypted string. - try { - return hex2b64(this.getKey().encrypt(string)); - } - catch (ex) { - return false; - } -}; - -/** - * Getter for the current JSEncryptRSAKey object. If it doesn't exists a new object - * will be created and returned - * @param {callback} [cb] the callback to be called if we want the key to be generated - * in an async fashion - * @returns {JSEncryptRSAKey} the JSEncryptRSAKey object - * @public - */ -JSEncrypt.prototype.getKey = function (cb) { - // Only create new if it does not exist. - if (!this.key) { - // Get a new private key. - this.key = new JSEncryptRSAKey(); - if (cb && {}.toString.call(cb) === '[object Function]') { - this.key.generateAsync(this.default_key_size, this.default_public_exponent, cb); - return; - } - // Generate the key. - this.key.generate(this.default_key_size, this.default_public_exponent); - } - return this.key; -}; - -/** - * Returns the pem encoded representation of the private key - * If the key doesn't exists a new key will be created - * @returns {string} pem encoded representation of the private key WITH header and footer - * @public - */ -JSEncrypt.prototype.getPrivateKey = function () { - // Return the private representation of this key. - return this.getKey().getPrivateKey(); -}; - -/** - * Returns the pem encoded representation of the private key - * If the key doesn't exists a new key will be created - * @returns {string} pem encoded representation of the private key WITHOUT header and footer - * @public - */ -JSEncrypt.prototype.getPrivateKeyB64 = function () { - // Return the private representation of this key. - return this.getKey().getPrivateBaseKeyB64(); -}; - - -/** - * Returns the pem encoded representation of the public key - * If the key doesn't exists a new key will be created - * @returns {string} pem encoded representation of the public key WITH header and footer - * @public - */ -JSEncrypt.prototype.getPublicKey = function () { - // Return the private representation of this key. - return this.getKey().getPublicKey(); -}; - -/** - * Returns the pem encoded representation of the public key - * If the key doesn't exists a new key will be created - * @returns {string} pem encoded representation of the public key WITHOUT header and footer - * @public - */ -JSEncrypt.prototype.getPublicKeyB64 = function () { - // Return the private representation of this key. - return this.getKey().getPublicBaseKeyB64(); -}; - -// export * from 'jsencrypt'; - -class BaseTransport { - constructor(endpoint, stream_type, config={}) { - this.stream_type = stream_type; - this.endpoint = endpoint; - this.eventSource = new EventEmitter(); - this.dataQueue = []; - } - - static canTransfer(stream_type) { - return BaseTransport.streamTypes().includes(stream_type); - } - - static streamTypes() { - return []; - } - - destroy() { - this.eventSource.destroy(); - } - - connect() { - // TO be impemented - } - - disconnect() { - // TO be impemented - } - - reconnect() { - return this.disconnect().then(()=>{ - return this.connect(); - }); - } - - setEndpoint(endpoint) { - this.endpoint = endpoint; - return this.reconnect(); - } - - send(data) { - // TO be impemented - // return this.prepare(data).send(); - } - - prepare(data) { - // TO be impemented - // return new Request(data); - } - - // onData(type, data) { - // this.eventSource.dispatchEvent(type, data); - // } -} - -const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); - -//navigator.hardwareConcurrency || 3; - -const LOG_TAG$5 = "transport:ws"; -const Log$10 = getTagged(LOG_TAG$5); -class WebsocketTransport extends BaseTransport { - constructor(endpoint, stream_type, options={ - socket:`${location.protocol.replace('http', 'ws')}//${location.host}/ws/`, - workers: 1 - }) { - super(endpoint, stream_type); - this.proxies = []; - this.currentProxy = 0; - this.workers = 1; - this.socket_url = options.socket; - this.ready = this.connect(); - } - - destroy() { - return this.disconnect().then(()=>{ - return super.destroy(); - }); - - } - - static canTransfer(stream_type) { - return WebsocketTransport.streamTypes().includes(stream_type); - } - - static streamTypes() { - return ['hls', 'rtsp']; - } - - connect() { - return this.disconnect().then(()=>{ - let promises = []; - // TODO: get mirror list - for (let i=0; i { - this.eventSource.dispatchEvent('disconnected', {code: e.code, reason: e.reason}); - // TODO: only reconnect on demand - if ([1000, 1006, 1013, 1011].includes(e.code)) { - setTimeout(()=> { - if (this.ready && this.ready.reject) { - this.ready.reject(); - } - this.ready = this.connect(); - }, 3000); - } - }); - - proxy.set_data_handler((data)=> { - this.dataQueue.push(new Uint8Array(data)); - this.eventSource.dispatchEvent('data'); - }); - - promises.push(proxy.connect().then(()=> { - this.eventSource.dispatchEvent('connected'); - }).catch((e)=> { - this.eventSource.dispatchEvent('error'); - throw new Error(e); - })); - this.proxies.push(proxy); - } - return Promise.all(promises); - }); - } - - disconnect() { - let promises = []; - for (let i=0; i{}; - this.disconnect_handler = ()=>{}; - this.builder = new WSPProtocol(WSPProtocol.V1_1); - this.awaitingPromises = {}; - this.seq = 0; - this.encryptor = new JSEncrypt(); - } - - set_data_handler(handler) { - this.data_handler = handler; - } - - set_disconnect_handler(handler) { - this.disconnect_handler = handler; - } - - close() { - Log$10.log('closing connection'); - return new Promise((resolve)=>{ - this.ctrlChannel.onclose = ()=>{ - if (this.dataChannel) { - this.dataChannel.onclose = ()=>{ - Log$10.log('closed'); - resolve(); - }; - this.dataChannel.close(); - } else { - Log$10.log('closed'); - resolve(); - } - }; - this.ctrlChannel.close(); - }); - } - - onDisconnect(){ - this.ctrlChannel.onclose=null; - this.ctrlChannel.close(); - if (this.dataChannel) { - this.dataChannel.onclose = null; - this.dataChannel.close(); - } - this.disconnect_handler(this); - } - - initDataChannel(channel_id) { - return new Promise((resolve, reject)=>{ - this.dataChannel = new WebSocket(this.url, WebSocketProxy.CHN_DATA); - this.dataChannel.binaryType = 'arraybuffer'; - this.dataChannel.onopen = ()=>{ - let msg = this.builder.build(WSPProtocol.CMD_JOIN, { - channel: channel_id - }); - Log$10.debug(msg); - this.dataChannel.send(msg); - }; - this.dataChannel.onmessage = (ev)=>{ - Log$10.debug(`[data]\r\n${ev.data}`); - let res = WSPProtocol.parse(ev.data); - if (!res) { - return reject(); - } - - this.dataChannel.onmessage=(e)=>{ - // Log.debug('got data'); - if (this.data_handler) { - this.data_handler(e.data); - } - }; - resolve(); - }; - this.dataChannel.onerror = (e)=>{ - Log$10.error(`[data] ${e.type}`); - this.dataChannel.close(); - }; - this.dataChannel.onclose = (e)=>{ - Log$10.error(`[data] ${e.type}. code: ${e.code}, reason: ${e.reason || 'unknown reason'}`); - this.onDisconnect(e); - }; - }); - } - - connect() { - this.encryptionKey = null; - return new Promise((resolve, reject)=>{ - this.ctrlChannel = new WebSocket(this.url, WebSocketProxy.CHN_CONTROL); - - this.connected = false; - - this.ctrlChannel.onopen = ()=>{ - let headers = { - proto: this.stream_type - }; - if (this.endpoint.socket) { - headers.socket = this.endpoint.socket; - } else { - Object.assign(headers, { - host: this.endpoint.host, - port: this.endpoint.port - }); - } - let msg = this.builder.build(WSPProtocol.CMD_INIT, headers); - Log$10.debug(msg); - this.ctrlChannel.send(msg); - }; - - this.ctrlChannel.onmessage = (ev)=>{ - Log$10.debug(`[ctrl]\r\n${ev.data}`); - - let res = WSPProtocol.parse(ev.data); - if (!res) { - return reject(); - } - - if (res.code >= 300) { - Log$10.error(`[ctrl]\r\n${res.code}: ${res.msg}`); - return reject(); - } - this.ctrlChannel.onmessage = (e)=> { - let res = WSPProtocol.parse(e.data); - Log$10.debug(`[ctrl]\r\n${e.data}`); - if (res.data.seq in this.awaitingPromises) { - if (res.code < 300) { - this.awaitingPromises[res.data.seq].resolve(res); - } else { - this.awaitingPromises[res.data.seq].reject(res); - } - delete this.awaitingPromises[res.data.seq]; - } - }; - this.encryptionKey = res.data.pubkey || null; - if (this.encryptionKey) { - this.encryptor.setPublicKey(this.encryptionKey); - // TODO: check errors - } - this.initDataChannel(res.data.channel).then(resolve).catch(reject); - }; - - this.ctrlChannel.onerror = (e)=>{ - Log$10.error(`[ctrl] ${e.type}`); - this.ctrlChannel.close(); - }; - this.ctrlChannel.onclose = (e)=>{ - Log$10.error(`[ctrl] ${e.type}. code: ${e.code} ${e.reason || 'unknown reason'}`); - this.onDisconnect(e); - }; - }); - } - - encrypt(msg) { - if (this.encryptionKey) { - let crypted = this.encryptor.encrypt(msg); - if (crypted === false) { - throw new Error("Encryption failed. Stopping") - } - return crypted; - } - return msg; - } - - send(payload) { - if (this.ctrlChannel.readyState != WebSocket.OPEN) { - this.close(); - // .then(this.connect.bind(this)); - // return; - throw new Error('disconnected'); - } - // Log.debug(payload); - let data = { - contentLength: payload.length, - seq: ++WSPProtocol.seq - }; - return { - seq:data.seq, - promise: new Promise((resolve, reject)=>{ - this.awaitingPromises[data.seq] = {resolve, reject}; - let msg = this.builder.build(WSPProtocol.CMD_WRAP, data, payload); - Log$10.debug(msg); - this.ctrlChannel.send(this.encrypt(msg)); - })}; - } -} - -const Log$11 = getTagged('wsp'); - -class StreamType$1 { - static get HLS() {return 'hls';} - static get RTSP() {return 'rtsp';} - - static isSupported(type) { - return [StreamType$1.HLS, StreamType$1.RTSP].includes(type); - } - - static fromUrl(url) { - let parsed; - try { - parsed = Url.parse(url); - } catch (e) { - return null; - } - switch (parsed.protocol) { - case 'rtsp': - return StreamType$1.RTSP; - case 'http': - case 'https': - if (url.indexOf('.m3u8')>=0) { - return StreamType$1.HLS; - } else { - return null; - } - default: - return null; - } - } - - static fromMime(mime) { - switch (mime) { - case 'application/x-rtsp': - return StreamType$1.RTSP; - case 'application/vnd.apple.mpegurl': - case 'application/x-mpegurl': - return StreamType$1.HLS; - default: - return null; - } - } -} - -class WSPlayer { - - constructor(node, opts) { - if (typeof node == typeof '') { - this.player = document.getElementById(node); - } else { - this.player = node; - } - - let modules = opts.modules || { - client: RTSPClient, - transport: { - constructor: WebsocketTransport - } - }; - this.errorHandler = opts.errorHandler || null; - this.queryCredentials = opts.queryCredentials || null; - - this.modules = {}; - for (let module of modules) { - let transport = module.transport || WebsocketTransport; - let client = module.client || RTSPClient; - if (transport.constructor.canTransfer(client.streamType())) { - this.modules[client.streamType()] = { - client: client, - transport: transport - }; - } else { - Log$11.warn(`Client stream type ${client.streamType()} is incompatible with transport types [${transport.streamTypes().join(', ')}]. Skip`); - } - } - - this.type = StreamType$1.RTSP; - this.url = null; - if (opts.url && opts.type) { - this.url = opts.url; - this.type = opts.type; - } else { - if (!this._checkSource(this.player)) { - for (let i=0; i{ - if (!this.isPlaying()) { - this.client.start(); - } - }, false); - - this.player.addEventListener('pause', ()=>{ - this.client.stop(); - }, false); - } - - // TODO: check native support - - isPlaying() { - return !(this.player.paused || this.client.paused); - } - - static canPlayWithModules(mimeType, modules) { - - let filteredModules = {}; - for (let module of modules) { - let transport = module.transport || WebsocketTransport; - let client = module.client || RTSPClient; - if (transport.canTransfer(client.streamType())) { - filteredModules[client.streamType()] = true; - } - } - - for (let type in filteredModules) { - if (type == StreamType$1.fromMime(mimeType)) { - return true; - } - } - return false; - } - - /// TODO: deprecate it? - static canPlay(resource) { - return StreamType$1.fromMime(resource.type) || StreamType$1.fromUrl(resource.src); - } - - canPlayUrl(src) { - let type = StreamType$1.fromUrl(src); - return (type in this.modules); - } - - _checkSource(src) { - if (!src.dataset['ignore'] && src.src && !this.player.canPlayType(src.type) && (StreamType$1.fromMime(src.type) || StreamType$1.fromUrl(src.src))) { - this.url = src.src; - this.type = src.type ? StreamType$1.fromMime(src.type) : StreamType$1.fromUrl(src.src); - return true; - } - return false; - } - - async setSource(url, type) { - if (this.transport) { - if (this.client) { - await this.client.detachTransport(); - } - await this.transport.destroy(); - } - try { - this.endpoint = Url.parse(url); - } catch (e) { - return; - } - this.url = url; - let transport = this.modules[type].transport; - this.transport = new transport.constructor(this.endpoint, this.type, transport.options); - - - let lastType = this.type; - this.type = (StreamType$1.isSupported(type)?type:false) || StreamType$1.fromMime(type); - if (!this.type) { - throw new Error("Bad stream type"); - } - - if (lastType!=this.type || !this.client) { - if (this.client) { - await this.client.destroy(); - } - let client = this.modules[type].client; - this.client = new client(); - } else { - this.client.reset(); - } - - if (this.queryCredentials) { - this.client.queryCredentials = this.queryCredentials; - } - if (this.remuxer) { - this.remuxer.destroy(); - this.remuxer = null; - } - this.remuxer = new Remuxer(this.player); - this.remuxer.attachClient(this.client); - - this.client.attachTransport(this.transport); - this.client.setSource(this.endpoint); - - if (this.player.autoplay) { - this.start(); - } - } - - start() { - if (this.client) { - this.client.start().catch((e)=>{ - if (this.errorHandler) { - this.errorHandler(e); - } - }); - } - } - - stop() { - if (this.client) { - this.client.stop(); - } - } - - async destroy() { - if (this.transport) { - if (this.client) { - await this.client.detachTransport(); - } - await this.transport.destroy(); - } - if (this.client) { - await this.client.destroy(); - } - if (this.remuxer) { - this.remuxer.destroy(); - this.remuxer = null; - } - } - -} - -setDefaultLogLevel(LogLevel.Error); -getTagged("transport:ws").setLevel(LogLevel.Error); -getTagged("client:rtsp").setLevel(LogLevel.Debug); -getTagged("mse").setLevel(LogLevel.Debug); - -window.Streamedian = { - logger(tag) { - return getTagged(tag) - }, - player(node, opts) { - if (!opts.socket) { - throw new Error("socket parameter is not set"); - } - let _options = { - modules: [ - { - client: RTSPClient, - transport: { - constructor: WebsocketTransport, - options: { - socket: opts.socket - } - } - } - ], - errorHandler(e) { - alert(`Failed to start player: ${e.message}`); - }, - queryCredentials(client) { - return new Promise((resolve, reject) => { - let c = prompt('input credentials in format user:password'); - if (c) { - client.setCredentials.apply(client, c.split(':')); - resolve(); - } else { - reject(); - } - }); - } - }; - return new WSPlayer(node, _options); - } -}; - -}()); -//# sourceMappingURL=streamedian.min.js.map diff --git a/demos/wsp/index.html b/demos/wsp/index.html deleted file mode 100755 index d824291..0000000 --- a/demos/wsp/index.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - RTSP player example(based streamedian) - - - - -
-
- - -
-
-

Enter your rtsp link to the stream, for example: "rtsp://localhost:1554/test/live1"

-

If need token,for example: "rtsp://localhost:1554/test/live1?token=4df8f5d5d680385cb07c2e354dd0f3f3"

-
- -
- - 120sec. -
- -
-

Change buffer duration

-
- - - -
-
- Playback rate:  - - live -
-
- -
-
- -

View HTML5 RTSP video player log

-
- - - - -

- - - - - - - - diff --git a/demos/wsp/style.css b/demos/wsp/style.css deleted file mode 100755 index dac210c..0000000 --- a/demos/wsp/style.css +++ /dev/null @@ -1,28 +0,0 @@ -body { - max-width: 720px; - margin: 50px auto; -} - -#test_video { - width: 720px; -} - -.controls { - display: flex; - justify-content: space-around; - align-items: center; -} -input.input, .form-inline .input-group>.form-control { - width: 300px; -} -.logs { - overflow: auto; - width: 720px; - height: 150px; - padding: 5px; - border-top: solid 1px gray; - border-bottom: solid 1px gray; -} -button { - margin: 5px -} \ No newline at end of file diff --git a/service/service.go b/service/service.go index f666af7..4573e7b 100755 --- a/service/service.go +++ b/service/service.go @@ -22,7 +22,6 @@ import ( "github.com/cnotch/ipchub/provider/auth" "github.com/cnotch/ipchub/provider/route" "github.com/cnotch/ipchub/service/rtsp" - "github.com/cnotch/ipchub/service/wsp" "github.com/cnotch/scheduler" "github.com/cnotch/xlog" "github.com/emitter-io/address" @@ -37,7 +36,6 @@ type Service struct { tlsusing bool http *http.Server rtsp *tcp.Server - wsp *tcp.Server tokens *auth.TokenManager } @@ -50,7 +48,6 @@ func NewService(ctx context.Context, l *xlog.Logger) (s *Service, err error) { logger: l, http: new(http.Server), rtsp: new(tcp.Server), - wsp: new(tcp.Server), tokens: new(auth.TokenManager), } @@ -82,9 +79,6 @@ func NewService(ctx context.Context, l *xlog.Logger) (s *Service, err error) { // 设置 rtsp AcceptHandler s.rtsp.OnAccept = rtsp.CreateAcceptHandler() - // 设置 wsp AcceptHandler - s.wsp.OnAccept = wsp.CreateAcceptHandler() - // 启动定时存储拉流信息 scheduler.PeriodFunc(time.Minute*5, time.Minute*5, func() { route.Flush() diff --git a/service/streamapis.go b/service/streamapis.go index 4af36e8..41d925c 100755 --- a/service/streamapis.go +++ b/service/streamapis.go @@ -39,12 +39,6 @@ func (s *Service) onWebSocketRequest(w http.ResponseWriter, r *http.Request) { return } - if ws.Subprotocol() == "control" || ws.Subprotocol() == "data" { - // 代理访问 - s.wsp.OnAccept(ws) - return - } - if ext == ".flv" { go flv.ConsumeByWebsocket(s.logger, streamPath, r.RemoteAddr, ws) return diff --git a/service/wsp/protocol.go b/service/wsp/protocol.go deleted file mode 100755 index 390e704..0000000 --- a/service/wsp/protocol.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2019,CAOHONGJU All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package wsp - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "unicode" - - "github.com/cnotch/ipchub/utils/scan" - "github.com/cnotch/xlog" -) - -const ( - wspProto = "WSP/1.1" // WSP协议版本 - prefixBody = "\r\n\r\n" // Header和Body分割符 -) - -// WSP 协议命令 -const ( - CmdInit = "INIT" // 初始化建立通道 - CmdJoin = "JOIN" // 数据通道使用 - CmdWrap = "WRAP" // 包装其他协议的命令 - CmdGetInfo = "GET_INFO" // 获取客户及license信息 -) - -// WSP 协议字段 -const ( - FieldProto = "proto" // 初始化的协议 如:rtsp - FieldSeq = "seq" // 命令序列 - FieldHost = "host" // 需要代理服务访问的远端host - FieldPort = "port" // 需要代理服务访问的远端port - FieldClient = "client" // 客户信息 - FieldChannel = "channel" // 数据通道编号,相当于一个session - FieldSocket = "socket" // 代替上面的host和port -) - -type badStringError struct { - what string - str string -} - -func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } - -// Request WSP 协议请求 -type Request struct { - Cmd string - Header map[string]string - Body string -} - -var ( - spacePair = scan.NewPair(' ', - func(r rune) bool { - return unicode.IsSpace(r) - }) - - validCmds = map[string]bool{ - CmdGetInfo: true, - CmdInit: true, - CmdJoin: true, - CmdWrap: true, - } - - bspool = &sync.Pool{ - New: func() interface{} { - return make([]byte, 8*1024) - }, - } - buffers = sync.Pool{ - New: func() interface{} { - return bytes.NewBuffer(make([]byte, 0, 1024*2)) - }, - } -) - -// DecodeStringRequest 解码字串请求 -func DecodeStringRequest(input string) (*Request, error) { - index := strings.Index(input, prefixBody) - if index < 0 { - return nil, &badStringError{"malformed WSP request,missing '\\r\\n\\r\\n'", input} - } - - req := &Request{ - Body: input[index+4:], - Header: make(map[string]string, 4), - } - - scanner := scan.Line - - // 先取首行 - tailing, substr, ok := scanner.Scan(input[:index]) - if !ok { - return nil, &badStringError{"malformed WSP request first line", substr} - } - proto, cmd, ok := spacePair.Scan(substr) - if proto != wspProto { - return nil, &badStringError{"malformed WSP request proto ", proto} - } - if _, ok := validCmds[cmd]; !ok { - return nil, &badStringError{"malformed WSP request command ", cmd} - } - req.Cmd = cmd - - // 循环取header - for ok { - tailing, substr, ok = scanner.Scan(tailing) - k, v, found := scan.ColonPair.Scan(substr) - if found { - req.Header[k] = v - } - } - - return req, nil -} - -// DecodeRequest 解码请求 -func DecodeRequest(r io.Reader, logger *xlog.Logger) (*Request, error) { - buf := bspool.Get().([]byte) - defer bspool.Put(buf) - - n, err := r.Read(buf) - if n == 0 && err == nil { // 上一个报文结束,再读一次 - n, err = r.Read(buf) - } - - if err != nil { - return nil, err - } - - input := string(buf[:n]) - logger.Debugf("wsp <<<=== \r\n%s", input) - - return DecodeStringRequest(input) -} - -// IsWrap 是否是包装协议,如果是,可以从Body提取被包装的协议 -func (req *Request) IsWrap() bool { - return req.Cmd == CmdWrap -} - -// ResponseOK 响应请求成功 -func (req *Request) ResponseOK(buf *bytes.Buffer, header map[string]string, payload string) { - req.ResponseTo(buf, 200, "OK", header, payload) -} - -// ResponseTo 响应请求到buf -func (req *Request) ResponseTo(buf *bytes.Buffer, statusCode int, statusText string, header map[string]string, payload string) { - // 写首行 - buf.WriteString(fmt.Sprintf("%s %d %s\r\n", wspProto, statusCode, statusText)) - // 写头 - header[FieldSeq] = req.Header[FieldSeq] - for k, v := range header { - buf.WriteString(k) - buf.WriteString(": ") - buf.WriteString(v) - buf.WriteString("\r\n") - } - // 写header和body分割 - buf.WriteString("\r\n") - // 写payload - if len(payload) > 0 { - buf.WriteString(payload) - } -} diff --git a/service/wsp/protocol_test.go b/service/wsp/protocol_test.go deleted file mode 100755 index 081c8cb..0000000 --- a/service/wsp/protocol_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2019,CAOHONGJU All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package wsp - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/cnotch/xlog" -) - -func TestDecodeRequest(t *testing.T) { - var reqStr = "WSP/1.1 GET_INFO\r\nproto: rtsp\r\nhost: 192.168.1.1\r\nport: 554\r\nclient: \r\nseq: 1\r\n\r\n" - t.Run("decode", func(t *testing.T) { - r := bytes.NewBufferString(reqStr) - got, err := DecodeRequest(r, xlog.L()) - if err != nil { - t.Errorf("DecodeRequest() error = %v", err) - return - } - assert.Equal(t, CmdGetInfo, got.Cmd) - assert.Equal(t, "1", got.Header[FieldSeq]) - }) - -} - -func TestRequest_ResponseOK(t *testing.T) { - respStr1 := "WSP/1.1 200 OK\r\nchannel: 334\r\nseq: 1\r\n\r\n" - respStr2 := "WSP/1.1 404 NOT FOUND\r\nchannel: 334\r\nseq: 1\r\n\r\n123" - t.Run("no payload", func(t *testing.T) { - req := &Request{ - Header: make(map[string]string), - } - req.Header[FieldSeq] = "1" - buf := &bytes.Buffer{} - header := make(map[string]string) - header[FieldChannel] = "334" - req.ResponseOK(buf, header, "") - resp := buf.String() - assert.Equal(t, respStr1, resp) - }) - t.Run("payload", func(t *testing.T) { - req := &Request{ - Header: make(map[string]string), - } - req.Header[FieldSeq] = "1" - buf := &bytes.Buffer{} - header := make(map[string]string) - header[FieldChannel] = "334" - req.ResponseTo(buf, 404, "NOT FOUND", header, "123") - resp := buf.String() - assert.Equal(t, respStr2, resp) - }) - -} - -func Benchmark_DecodeRequest(b *testing.B) { - var reqStr = "WSP/1.1 GET_INFO\r\nproto: rtsp\r\nhost: 192.168.1.1\r\nport: 554\r\nclient: \r\nseq: 1\r\n\r\n" - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - got, _ := DecodeStringRequest(reqStr) - _ = got - } - }) -} diff --git a/service/wsp/session.go b/service/wsp/session.go deleted file mode 100755 index 30cbfc6..0000000 --- a/service/wsp/session.go +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) 2019,CAOHONGJU All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package wsp - -import ( - "bufio" - "bytes" - "fmt" - "io" - "net/url" - "runtime/debug" - "strings" - "sync" - "time" - - "github.com/cnotch/ipchub/config" - "github.com/cnotch/ipchub/media" - "github.com/cnotch/ipchub/network/websocket" - "github.com/cnotch/ipchub/provider/security" - "github.com/cnotch/ipchub/service/rtsp" - "github.com/cnotch/ipchub/stats" - "github.com/cnotch/xlog" - "github.com/pixelbender/go-sdp/sdp" -) - -const ( - statusInit = iota - statusReady - statusPlaying -) -const ( - rtspURLPrefix = "rtsp://" // RTSP地址前缀 -) - -// Session RTSP 会话 -type Session struct { - // 创建时设置 - svr *Server - channelID string - logger *xlog.Logger - closed bool - paused bool - lsession string // 本地会话标识 - timeout time.Duration - conn websocket.Conn - lockW sync.Mutex - dataChannel websocket.Conn - - // DESCRIBE 后设置 - url *url.URL - path string - rawSdp string - sdp *sdp.Session - aControl string - vControl string - aCodec string - vCodec string - - // Setup 后设置 - transport rtsp.RTPTransport - - // 启动流媒体传输后设置 - status int // session状态 - source *media.Stream - cid *media.CID -} - -func newSession(svr *Server, conn websocket.Conn, channelID string) *Session { - - session := &Session{ - svr: svr, - channelID: channelID, - lsession: security.NewID().Base64(), - timeout: config.NetTimeout() * time.Duration(2), - conn: conn, - transport: rtsp.RTPTransport{ - Mode: rtsp.PlaySession, // 默认为播放 - Type: rtsp.RTPUnknownTrans, - }, - status: statusInit, - } - - for i := 0; i < 4; i++ { - session.transport.Channels[i] = -1 - session.transport.ClientPorts[i] = -1 - } - session.logger = svr.logger.With(xlog.Fields( - xlog.F("channel", channelID), - xlog.F("path", conn.Path()))) - - return session -} - -// 设置rtp数据通道 -func (s *Session) setDataChannel(dc websocket.Conn) { - s.lockW.Lock() - s.dataChannel = dc - s.lockW.Unlock() -} - -// Addr Session地址 -func (s *Session) Addr() string { - return s.conn.RemoteAddr().String() -} - -// Consume 消费媒体包 -func (s *Session) Consume(p Pack) { - if s.closed || s.paused { - return - } - - buf := buffers.Get().(*bytes.Buffer) - buf.Reset() - defer buffers.Put(buf) - p2 := p.(*rtsp.RTPPack) - p2.Write(buf, s.transport.Channels[:]) - - var err error - s.lockW.Lock() - if s.dataChannel != nil { - _, err = s.dataChannel.Write(buf.Bytes()) - } - s.lockW.Unlock() - - if err != nil { - s.logger.Errorf("send pack error = %v , close socket", err) - s.Close() - return - } -} - -// Close 关闭会话 -func (s *Session) Close() error { - if s.closed { - return nil - } - - s.closed = true - s.paused = false - s.conn.Close() - s.lockW.Lock() - if s.dataChannel != nil { - s.dataChannel.Close() - } - s.lockW.Unlock() - return nil -} - -func (s *Session) process() { - var err error - defer func() { - if r := recover(); r != nil { - s.logger.Errorf("wsp channel panic, %v \n %s", r, debug.Stack()) - } - - if err != nil { - if err == io.EOF { // 如果客户端断开提醒 - s.logger.Warn("websocket disconnect actively") - } else if !s.closed { // 如果主动关闭,不提示 - s.logger.Error(err.Error()) - } - } - - // 删除通道 - s.svr.sessions.Delete(s.channelID) - // 停止消费 - if s.cid != nil { - s.source.StopConsume(*s.cid) - s.cid = nil - s.source = nil - } - // 关闭连接 - s.Close() - - // 重置到初始状态 - s.conn = nil - s.dataChannel = nil - s.status = statusInit - stats.WspConns.Release() - s.logger.Info("close wsp channel") - }() - - s.logger.Info("open wsp channel") - - stats.WspConns.Add() // 增加一个 RTSP 连接计数 - - for !s.closed { - deadLine := time.Time{} - if s.timeout > 0 { - deadLine = time.Now().Add(s.timeout) - } - if err = s.conn.SetReadDeadline(deadLine); err != nil { - break - } - - var req *Request - req, err = DecodeRequest(s.conn, s.logger) - if err != nil { - break - } - - if req.Cmd != CmdWrap { - s.logger.Error("must is WRAP command request") - break - } - - // 从包装命令中提取 rtsp 请求 - var rtspReq *rtsp.Request - rtspReq, err = rtsp.ReadRequest(bufio.NewReader(bytes.NewBufferString(req.Body))) - if err != nil { - break - } - - // 处理请求,并获得响应 - rtspResp := s.onRequest(rtspReq) - - // 发送响应 - buf := buffers.Get().(*bytes.Buffer) - buf.Reset() - defer buffers.Put(buf) - req.ResponseOK(buf, map[string]string{FieldChannel: s.channelID}, "") - rtspResp.Write(buf) - _, err = s.conn.Write(buf.Bytes()) - if err != nil { - break - } - - s.logger.Debugf("wsp ===>>>\r\n%s", buf.String()) - - // 关闭通道 - if rtspReq.Method == rtsp.MethodTeardown { - break - } - } -} - -func (s *Session) onRequest(req *rtsp.Request) *rtsp.Response { - resp := s.newResponse(rtsp.StatusOK, req) - // 预处理 - continueProcess := s.onPreprocess(resp, req) - if !continueProcess { - return resp - } - - switch req.Method { - case rtsp.MethodDescribe: - s.onDescribe(resp, req) - case rtsp.MethodSetup: - s.onSetup(resp, req) - case rtsp.MethodPlay: - s.onPlay(resp, req) - case rtsp.MethodPause: - s.onPause(resp, req) - default: - // 状态不支持的方法 - resp.StatusCode = rtsp.StatusMethodNotValidInThisState - } - return resp -} - -func (s *Session) onDescribe(resp *rtsp.Response, req *rtsp.Request) { - - // TODO: 检查 accept 中的类型是否包含 sdp - s.url = req.URL - s.path = s.conn.Path() // 使用websocket路径 - // s.path = utils.CanonicalPath(req.URL.Path) - stream := media.GetOrCreate(s.path) - if stream == nil { - resp.StatusCode = rtsp.StatusNotFound - return - } - - // 从流中取 sdp - sdpRaw := stream.Sdp() - if len(sdpRaw) == 0 { - resp.StatusCode = rtsp.StatusNotFound - return - } - err := s.parseSdp(sdpRaw) - if err != nil { // TODO:需要更好的处理方式 - resp.StatusCode = rtsp.StatusNotFound - return - } - - resp.Header.Set(rtsp.FieldContentType, "application/sdp") - resp.Body = s.rawSdp -} - -func (s *Session) onSetup(resp *rtsp.Response, req *rtsp.Request) { - // a=control:streamid=1 - // a=control:rtsp://192.168.1.165/trackID=1 - // a=control:?ctype=video - setupURL := &url.URL{} - *setupURL = *req.URL - if setupURL.Port() == "" { - setupURL.Host = fmt.Sprintf("%s:554", setupURL.Host) - } - setupPath := setupURL.String() - - //setupPath = setupPath[strings.LastIndex(setupPath, "/")+1:] - vPath := getControlPath(s.vControl) - if vPath == "" { - resp.StatusCode = rtsp.StatusInternalServerError - resp.Status = "Invalid VControl" - return - } - - aPath := getControlPath(s.aControl) - - ts := req.Header.Get(rtsp.FieldTransport) - resp.Header.Set(rtsp.FieldTransport, ts) // 先回写transport - - // 检查控制路径 - chindex := -1 - if setupPath == aPath || (aPath != "" && strings.LastIndex(setupPath, aPath) == len(setupPath)-len(aPath)) { - chindex = int(rtsp.ChannelAudio) - } else if setupPath == vPath || (vPath != "" && strings.LastIndex(setupPath, vPath) == len(setupPath)-len(vPath)) { - chindex = int(rtsp.ChannelVideo) - } else { // 找不到被 Setup 的资源 - resp.StatusCode = rtsp.StatusInternalServerError - resp.Status = fmt.Sprintf("SETUP Unkown control:%s", setupPath) - return - } - - err := s.transport.ParseTransport(chindex, ts) - if err != nil { - resp.StatusCode = rtsp.StatusInvalidParameter - resp.Status = err.Error() - return - } - - // 检查必须是play模式 - if rtsp.PlaySession != s.transport.Mode { - resp.StatusCode = rtsp.StatusInvalidParameter - resp.Status = "can't setup as record" - return - } - - if s.transport.Type != rtsp.RTPTCPUnicast { // 需要修改回复的transport - resp.StatusCode = rtsp.StatusUnsupportedTransport - resp.Status = "websocket only support tcp unicast" - return - } - - if s.status < statusReady { // 初始状态切换到Ready - s.status = statusReady - } -} - -func (s *Session) onPlay(resp *rtsp.Response, req *rtsp.Request) { - if s.status == statusPlaying { - s.paused = false - return - } - - stream := media.GetOrCreate(s.path) - if stream == nil { - resp.StatusCode = rtsp.StatusNotFound - return - } - - resp.Header.Set(rtsp.FieldRange, req.Header.Get(rtsp.FieldRange)) - if s.cid == nil { - s.source = stream - // cid := stream.StartConsume(s) - cid := stream.StartConsumeNoGopCache(s, media.RTPPacket, "wsp") - s.cid = &cid - } - s.status = statusPlaying - s.paused = false - return -} - -func (s *Session) onPause(resp *rtsp.Response, req *rtsp.Request) { - if s.status == statusPlaying { - s.paused = true - } -} - -func (s *Session) onPreprocess(resp *rtsp.Response, req *rtsp.Request) (continueProcess bool) { - // Options - if req.Method == rtsp.MethodOptions { - resp.Header.Set(rtsp.FieldPublic, "DESCRIBE, SETUP, TEARDOWN, PLAY, OPTIONS, ANNOUNCE") - return false - } - - // 关闭请求 - if req.Method == rtsp.MethodTeardown { - return false - } - - // 检查状态下的方法 - switch s.status { - case statusReady: - continueProcess = req.Method == rtsp.MethodSetup || - req.Method == rtsp.MethodPlay - case statusPlaying: - continueProcess = (req.Method == rtsp.MethodPlay || - req.Method == rtsp.MethodPause) - default: - continueProcess = !(req.Method == rtsp.MethodPlay || - req.Method == rtsp.MethodRecord) - } - - if !continueProcess { - resp.StatusCode = rtsp.StatusMethodNotValidInThisState - return false - } - - return true -} - -func (s *Session) newResponse(code int, req *rtsp.Request) *rtsp.Response { - resp := &rtsp.Response{ - StatusCode: code, - Header: make(rtsp.Header), - Request: req, - } - - resp.Header.Set(rtsp.FieldCSeq, req.Header.Get(rtsp.FieldCSeq)) - resp.Header.Set(rtsp.FieldSession, s.lsession) - return resp -} - -func (s *Session) parseSdp(rawSdp string) (err error) { - // 从流中取 sdp - s.rawSdp = rawSdp - // 解析 - s.sdp, err = sdp.ParseString(s.rawSdp) - if err != nil { - return - } - - for _, media := range s.sdp.Media { - switch media.Type { - case "video": - s.vControl = media.Attributes.Get("control") - s.vCodec = media.Format[0].Name - case "audio": - s.aControl = media.Attributes.Get("control") - s.aCodec = media.Format[0].Name - } - } - return -} - -func getControlPath(ctrl string) (path string) { - if len(ctrl) >= len(rtspURLPrefix) && strings.EqualFold(ctrl[:len(rtspURLPrefix)], rtspURLPrefix) { - ctrlURL, err := url.Parse(ctrl) - if err != nil { - return - } - if ctrlURL.Port() == "" { - ctrlURL.Host = fmt.Sprintf("%s:554", ctrlURL.Hostname()) - } - return ctrlURL.String() - } - return ctrl -} diff --git a/service/wsp/types.go b/service/wsp/types.go deleted file mode 100644 index 8b3b1dc..0000000 --- a/service/wsp/types.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2019,CAOHONGJU All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package wsp - -import "github.com/cnotch/ipchub/protos" - -// Pack . -type Pack = protos.Pack diff --git a/service/wsp/wsp.go b/service/wsp/wsp.go deleted file mode 100755 index b94f209..0000000 --- a/service/wsp/wsp.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2019,CAOHONGJU All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package wsp - -import ( - "bytes" - "net" - "sync" - - "github.com/cnotch/ipchub/network/websocket" - "github.com/cnotch/ipchub/provider/security" - "github.com/cnotch/xlog" - "github.com/kelindar/tcp" -) - -// Server https://github.com/Streamedian/html5_rtsp_player 客户端配套的服务器 -type Server struct { - logger *xlog.Logger - - sessions sync.Map -} - -// CreateAcceptHandler 创建连接接入处理器 -func CreateAcceptHandler() tcp.OnAccept { - svr := &Server{ - logger: xlog.L(), - } - return svr.onAcceptConn -} - -// onAcceptConn 当新连接接入时触发 -func (svr *Server) onAcceptConn(c net.Conn) { - wsc := c.(websocket.Conn) - if wsc.Subprotocol() == "control" { - go svr.handshakeControlChannel(wsc) - } else { - go svr.handshakeDataChannel(wsc) - } -} - -func (svr *Server) handshakeControlChannel(wsc websocket.Conn) { - svr.logger.Info("wsp control channel handshake.") - wsc = wsc.TextTransport() - for { - req, err := DecodeRequest(wsc, svr.logger) - if err != nil { - svr.logger.Error(err.Error()) - wsc.Close() - break - } - - if req.Cmd == CmdGetInfo { - continue - } - - if req.Cmd != CmdInit { - svr.logger.Errorf("wsp control channel handshake failed, malformed WSP request command: %s.", req.Cmd) - wsc.Close() - break - } - - // 初始化 - channelID := security.NewID().String() - buf := buffers.Get().(*bytes.Buffer) - buf.Reset() - defer buffers.Put(buf) - req.ResponseOK(buf, map[string]string{FieldChannel: channelID}, "") - _, err = wsc.Write(buf.Bytes()) - if err != nil { - svr.logger.Error(err.Error()) - wsc.Close() - break - } - session := newSession(svr, wsc, channelID) - svr.sessions.Store(channelID, session) - svr.logger.Debugf("wsp ===>>> \r\n%s", buf.String()) - go session.process() - break - } -} - -func (svr *Server) handshakeDataChannel(wsc websocket.Conn) { - tc := wsc.TextTransport() - req, err := DecodeRequest(tc, svr.logger) - if err != nil { - svr.logger.Error(err.Error()) - tc.Close() - return - } - - channelID := req.Header[FieldChannel] - code := 200 - text := "OK" - var session *Session - si, ok := svr.sessions.Load(channelID) - if ok { - session = si.(*Session) - } else { - code = 404 - text = "NOT FOUND" - } - - buf := buffers.Get().(*bytes.Buffer) - buf.Reset() - defer buffers.Put(buf) - req.ResponseTo(buf, code, text, map[string]string{}, "") - _, err = tc.Write(buf.Bytes()) - if err != nil { - svr.logger.Error(err.Error()) - tc.Close() - return - } - - svr.logger.Debugf("wsp ===>>> \r\n%s", buf.String()) - if session == nil { - tc.Close() - return - } - - // 添加到session - session.setDataChannel(wsc) -}