(One intermediate revision by the same user not shown) | |||
Line 2: | Line 2: | ||
(function(){ | (function(){ | ||
+ | var mkErr = function(msg, code, level){ | ||
+ | var ret = new Error(msg); | ||
+ | ret.code = code; | ||
+ | ret.level = level; | ||
+ | return ret; | ||
+ | } | ||
+ | |||
+ | // Source: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/repeat | ||
+ | if (!String.prototype.repeat) { | ||
+ | String.prototype.repeat = function(count) { | ||
+ | 'use strict'; | ||
+ | if (this == null) { | ||
+ | throw new TypeError('can\'t convert ' + this + ' to object'); | ||
+ | } | ||
+ | var str = '' + this; | ||
+ | count = +count; | ||
+ | if (count != count) { | ||
+ | count = 0; | ||
+ | } | ||
+ | if (count < 0) { | ||
+ | throw new RangeError('repeat count must be non-negative'); | ||
+ | } | ||
+ | if (count == Infinity) { | ||
+ | throw new RangeError('repeat count must be less than infinity'); | ||
+ | } | ||
+ | count = Math.floor(count); | ||
+ | if (str.length == 0 || count == 0) { | ||
+ | return ''; | ||
+ | } | ||
+ | // Ensuring count is a 31-bit integer allows us to heavily optimize the | ||
+ | // main part. But anyway, most current (August 2014) browsers can't handle | ||
+ | // strings 1 << 28 chars or longer, so: | ||
+ | if (str.length * count >= 1 << 28) { | ||
+ | throw new RangeError('repeat count must not overflow maximum string size'); | ||
+ | } | ||
+ | var rpt = ''; | ||
+ | for (;;) { | ||
+ | if ((count % 2) == 1) { | ||
+ | rpt += str; | ||
+ | } | ||
+ | count >>>= 1; | ||
+ | if (count == 0) { | ||
+ | break; | ||
+ | } | ||
+ | str += str; | ||
+ | } | ||
+ | // Could we try: | ||
+ | // return Array(count + 1).join(this); | ||
+ | return rpt; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
/* Structure: | /* Structure: | ||
* | * | ||
Line 48: | Line 101: | ||
var det = DNA.Restrict.detect(msg); | var det = DNA.Restrict.detect(msg); | ||
if(det.length > 0){ | if(det.length > 0){ | ||
− | throw | + | throw mkErr("Restriction substitution failed", 'ERESTRICT'); |
} | } | ||
Line 54: | Line 107: | ||
return { | return { | ||
'msg': msg, | 'msg': msg, | ||
+ | 'data': dna, | ||
'subst': subst.length, | 'subst': subst.length, | ||
'len': len, | 'len': len, | ||
Line 74: | Line 128: | ||
if(start !== 'Spy'){ // start mismatch | if(start !== 'Spy'){ // start mismatch | ||
− | throw | + | throw mkErr("Start mismatch: '"+start+"' / 'Spy'", 'ESTART', 1); |
return null; | return null; | ||
} | } | ||
Line 89: | Line 143: | ||
if(CRC.crc16(data) !== crc){ // checksum mismatch | if(CRC.crc16(data) !== crc){ // checksum mismatch | ||
− | throw | + | throw mkErr('CRC mismatch: '+CRC.crc16(data)+' / '+crc+" ("+data.length+" / "+len+")", 'ECRC', 2); |
return null; | return null; | ||
} | } | ||
Line 97: | Line 151: | ||
if(end !== 'GEM'){ | if(end !== 'GEM'){ | ||
− | throw | + | throw mkErr("End mismatch: '"+end+"' / 'GEM'", 'EEND', 3); |
return null; | return null; | ||
} | } | ||
Line 103: | Line 157: | ||
if(ext){ | if(ext){ | ||
return { | return { | ||
+ | 'msg': msg, | ||
'data': data, | 'data': data, | ||
'subst': subst, | 'subst': subst, | ||
Line 116: | Line 171: | ||
return data; | return data; | ||
+ | }; | ||
+ | |||
+ | var findStart = function(dna, offset){ | ||
+ | return dna.indexOf(headDNA, offset); | ||
}; | }; | ||
Message = { | Message = { | ||
'packDNA': packDNA, | 'packDNA': packDNA, | ||
− | 'unpackDNA': unpackDNA | + | 'unpackDNA': unpackDNA, |
+ | 'findStart': findStart, | ||
+ | 'minLength': lengths.start + lengths.pad + lengths.subst + lengths.len + | ||
+ | lengths.crc + lengths.end | ||
}; | }; | ||
})(); | })(); |
Latest revision as of 08:12, 7 October 2016
var Message = {};
(function(){ var mkErr = function(msg, code, level){ var ret = new Error(msg); ret.code = code; ret.level = level; return ret; }
// Source: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/repeat if (!String.prototype.repeat) { String.prototype.repeat = function(count) { 'use strict'; if (this == null) { throw new TypeError('can\'t convert ' + this + ' to object'); } var str = + this; count = +count; if (count != count) { count = 0; } if (count < 0) { throw new RangeError('repeat count must be non-negative'); } if (count == Infinity) { throw new RangeError('repeat count must be less than infinity'); } count = Math.floor(count); if (str.length == 0 || count == 0) { return ; } // Ensuring count is a 31-bit integer allows us to heavily optimize the // main part. But anyway, most current (August 2014) browsers can't handle // strings 1 << 28 chars or longer, so: if (str.length * count >= 1 << 28) { throw new RangeError('repeat count must not overflow maximum string size'); } var rpt = ; for (;;) { if ((count % 2) == 1) { rpt += str; } count >>>= 1; if (count == 0) { break; } str += str; } // Could we try: // return Array(count + 1).join(this); return rpt; } }
/* Structure:
*
* Start: 'Spy': 3 * 4 bases
* Pad: 'AAAA': 4 bases
* Subst: 1 character 1 * 4 bases
* Length: 16 bit number: 8 bases
* CRC32: 16 bit number: 8 bases
* Data: '...': length * 4 bases
* End: 'GEM': 3 * 4 bases
*/
var headDNA = EightBit.encodeStr('Spy'),
tailDNA = EightBit.encodeStr('GEM'),
padDNA = 'AAAA',
lengths = {
'start': headDNA.length,
'pad': 4,
'subst': 4,
'len': 8,
'crc': 8,
'end': tailDNA.length
},
offsets = {},
subOffs = {},
subBase = 'A';
offsets.start = 0; offsets.pad = offsets.start + lengths.start; offsets.subst = offsets.pad + lengths.pad; offsets.len = offsets.subst + lengths.subst; subOffs.len = 0; subOffs.crc = subOffs.len + lengths.len; subOffs.data = subOffs.crc + lengths.crc;
var packDNA = function(dna, ext){ var subst = 0, len = dna.length, crc = CRC.crc16(dna), lenDNA = DNA.encodeUInt(len, lengths.len), crcDNA = DNA.encodeUInt(crc, lengths.crc), suffixDNA = lenDNA + crcDNA + dna + tailDNA, subst = DNA.Restrict.shortestAbsent(suffixDNA, subBase), substDNA = DNA.encodeUInt(subst.length, 4), msg = headDNA + padDNA + substDNA + DNA.Restrict.substitute(suffixDNA, subBase, subst);
var det = DNA.Restrict.detect(msg); if(det.length > 0){ throw mkErr("Restriction substitution failed", 'ERESTRICT'); }
if(ext){ return { 'msg': msg, 'data': dna, 'subst': subst.length, 'len': len, 'crc': crc, 'dnaStart': headDNA, 'dnaEnd': tailDNA, 'dnaSubst': substDNA, 'dnaLen': lenDNA, 'dnaCrc': crcDNA }; }
return msg; };
var unpackDNA = function(msg, ext){ var dnaStart = msg.substr(offsets.start, lengths.start), start = EightBit.decodeStr(dnaStart), pad = msg.substr(offsets.pad, lengths.pad);
if(start !== 'Spy'){ // start mismatch throw mkErr("Start mismatch: '"+start+"' / 'Spy'", 'ESTART', 1); return null; }
var dnaSubst = msg.substr(offsets.subst, lengths.subst), subst = DNA.decodeUInt(dnaSubst), suffixDNA = msg.substr(offsets.len), suffix = DNA.Restrict.restore(suffixDNA, subBase.repeat(subst)), dnaLen = suffix.substr(subOffs.len, lengths.len), len = DNA.decodeUInt(dnaLen), dnaCrc = suffix.substr(subOffs.crc, lengths.crc), crc = DNA.decodeUInt(dnaCrc), data = suffix.substr(subOffs.data, len);
if(CRC.crc16(data) !== crc){ // checksum mismatch throw mkErr('CRC mismatch: '+CRC.crc16(data)+' / '+crc+" ("+data.length+" / "+len+")", 'ECRC', 2); return null; }
var dnaEnd = suffix.substr(subOffs.data + len, lengths.end), end = EightBit.decodeStr(dnaEnd);
if(end !== 'GEM'){ throw mkErr("End mismatch: '"+end+"' / 'GEM'", 'EEND', 3); return null; }
if(ext){ return { 'msg': msg, 'data': data, 'subst': subst, 'len': len, 'crc': crc, 'dnaStart': dnaStart, 'dnaSubst': dnaSubst, 'dnaEnd': dnaEnd, 'dnaLen': dnaLen, 'dnaCrc': dnaCrc }; }
return data; };
var findStart = function(dna, offset){ return dna.indexOf(headDNA, offset); };
Message = { 'packDNA': packDNA, 'unpackDNA': unpackDNA, 'findStart': findStart, 'minLength': lengths.start + lengths.pad + lengths.subst + lengths.len + lengths.crc + lengths.end }; })();