'use strict';'require baseclass';const svcParamKeyMap={mandatory:0,alpn:1,'no-default-alpn':2,port:3,ipv4hint:4,ech:5,ipv6hint:6};return baseclass.extend({buildSvcbHex(priority,target,params){let buf=[];priority=isNaN(priority)?1:priority;buf.push((priority>>8)&0xff,priority&0xff);if(target!=='.'){if(target.endsWith('.'))target=target.slice(0,-1);target.split('.').forEach(part=>{buf.push(part.length);for(let i=0;i<part.length;i++)
buf.push(part.charCodeAt(i));});}
buf.push(0);if(priority===0){if(target==='.')return null;return buf.map(b=>b.toString(16).padStart(2,'0')).join('');}
const seenKeys=new Set();const paramList=[];let mandatoryKeys=new Set();let definedAlpn=new Set();let noDefaultAlpn=false;params.forEach(line=>{if(!line.trim())return;let[keyName,val='']=line.split('=');keyName=keyName.trim().replace(/^"(.*)"$/,'$1');val=val.trim().replace(/^"(.*)"$/,'$1');let keyNum=this.svcParamKeyToNumber(keyName);if(keyNum==null)return null;if(seenKeys.has(keyName))return null;seenKeys.add(keyName);if(keyNum!==2&&val==='')
return null;if(keyNum===0)mandatoryKeys=new Set(val.split(',').filter(n=>n!=''));if(keyNum===1)definedAlpn=new Set(val.split(',').filter(n=>n!=''));if(keyNum===2)noDefaultAlpn=true;let valueBytes=this.encodeSvcParamValue(keyName,val);paramList.push({keyNum,keyName,valueBytes});});if(noDefaultAlpn&&definedAlpn.size===0)return null;for(const key of mandatoryKeys){if(!seenKeys.has(key)){return null;}}
paramList.sort((a,b)=>a.keyNum-b.keyNum);for(const p of paramList){buf.push((p.keyNum>>8)&0xff,p.keyNum&0xff);buf.push((p.valueBytes.length>>8)&0xff,p.valueBytes.length&0xff);buf.push(...p.valueBytes);}
return buf.map(b=>b.toString(16).padStart(2,'0')).join('');},svcParamKeyToNumber(name){name=name.toLowerCase();if(name in svcParamKeyMap)
return svcParamKeyMap[name];const match=name.match(/^key(\d{1,5})$/);if(match){const n=parseInt(match[1],10);if(n>=0&&n<=65535)return n;}
return null;},encodeSvcParamValue(key,value){switch(key){case'mandatory':const seen=new Set();const keys=value.split(',').map(k=>k.trim()).filter(k=>{if(seen.has(k))return false;seen.add(k);return true;}).map(k=>this.svcParamKeyToNumber(k)).filter(n=>n!=null).filter(n=>n!=0).sort((a,b)=>a-b);return keys.map(n=>[(n>>8)&0xff,n&0xff]).flat();case'ech':return Array.prototype.map.call(atob(value),c=>c.charCodeAt(0));case'alpn':return value.split(',').map(v=>{const len=v.length;return[len,...[...v].map(c=>c.charCodeAt(0))];}).flat();case'no-default-alpn':return[];case'port':const port=parseInt(value,10);return[(port>>8)&0xff,port&0xff];case'ipv4hint':return value.split(',').map(ip=>ip.trim().split('.').map(x=>parseInt(x,10))).flat();case'ipv6hint':return value.split(',').map(ip=>{ip=ip.trim();let ipv4Tail=null;if(ip.match(/\d+\.\d+\.\d+\.\d+$/)){const parts=ip.split(':');ipv4Tail=parts.pop();ip=parts.join(':');const octets=ipv4Tail.split('.').map(n=>parseInt(n,10));if(octets.length!==4)return null;const word1=((octets[0]<<8)|octets[1]).toString(16).padStart(4,'0');const word2=((octets[2]<<8)|octets[3]).toString(16).padStart(4,'0');ip+=`:${word1}:${word2}`;}
let parts=ip.trim().split(':');if(parts.includes('')){const missing=8-parts.filter(p=>p!=='').length;const expanded=[];for(let i=0;i<parts.length;i++){if(parts[i]===''&&(i===0||parts[i-1]!=='')){for(let j=0;j<missing;j++)expanded.push('0000');}else if(parts[i]!==''){expanded.push(parts[i].padStart(4,'0'));}}
parts=expanded;}else{parts=parts.map(p=>p.padStart(4,'0'));}
return parts.map(p=>[parseInt(p.slice(0,2),16),parseInt(p.slice(2,4),16)]).flat();}).flat();default:if(/^key\d{1,5}$/i.test(key)){return value.split(',').map(v=>{return[...v].map(c=>c.charCodeAt(0));}).flat();}
return[];}},parseSvcbHex(hex){if(!hex)return null;let data=hex.replace(/[\s:]/g,'').toLowerCase();let buf=new Uint8Array(data.match(/.{2}/g).map(b=>parseInt(b,16)));let view=new DataView(buf.buffer);let offset=0;if(buf.length<2)return null;let priority=view.getUint16(offset);offset+=2;function parseName(){let labels=[];while(offset<buf.length){let len=buf[offset++];if(len===0)break;if(offset+len>buf.length)return null;let label=String.fromCharCode(...buf.slice(offset,offset+len));labels.push(label);offset+=len;}
return labels.join('.')+'.';}
let target=parseName();if(target===null)return null;let svcParams=[];while(offset+4<=buf.length){let key=view.getUint16(offset);let len=view.getUint16(offset+2);offset+=4;if(offset+len>buf.length)break;let valBuf=buf.slice(offset,offset+len);offset+=len;let keyname=this.svcParamKeyFromNumber(key);if(keyname==='no-default-alpn'&&valBuf.length===0){svcParams.push(keyname);}else{let valstr=this.decodeSvcParamValue(keyname,valBuf);svcParams.push(`${keyname}=${valstr}`);}}
return{priority,target,params:svcParams};},svcParamKeyFromNumber(num){for(const[key,val]of Object.entries(svcParamKeyMap)){if(val===num)return key;}
return`key${num}`;},decodeSvcParamValue(key,buf){switch(key){case'mandatory':const keys=[];for(let i=0;i+1<buf.length;i+=2){const k=(buf[i]<<8)|buf[i+1];keys.push(this.svcParamKeyFromNumber(k));}
return keys.join(',');case'ech':return btoa(String.fromCharCode(...buf));case'alpn':{let pos=0,result=[];while(pos<buf.length){let len=buf[pos++];if(pos+len>buf.length)break;let s=String.fromCharCode(...buf.slice(pos,pos+len));result.push(s);pos+=len;}
return result.join(',');}
case'no-default-alpn':return'';case'port':return(buf[0]<<8|buf[1]).toString();case'ipv4hint':return[...buf].reduce((acc,byte,i)=>{if(i%4===0)acc.push([]);acc[acc.length-1].push(byte);return acc;},[]).map(ip=>ip.join('.')).join(',');case'ipv6hint':const addrs=[];for(let i=0;i+15<=buf.length;i+=16){let addr=[];for(let j=0;j<16;j+=2){const hi=buf[i+j];const lo=buf[i+j+1];const word=((hi<<8)|lo).toString(16).padStart(4,'0');addr.push(word);}
addrs.push(this.compressIPv6(addr));}
return addrs.join(',');default:if(/^key\d{1,5}$/i.test(key)){return String.fromCharCode(...buf);}
return Array.from(buf).map(b=>b.toString(16).padStart(2,'0')).join('');}},compressIPv6(hextets){const normalized=hextets.map(h=>parseInt(h,16).toString(16));let bestStart=-1,bestLen=0;for(let i=0;i<normalized.length;){if(normalized[i]!=='0'){i++;continue;}
let start=i;while(i<normalized.length&&normalized[i]==='0')i++;let len=i-start;if(len>bestLen){bestStart=start;bestLen=len;}}
if(bestLen<2)return normalized.join(':');const head=normalized.slice(0,bestStart).join(':');const tail=normalized.slice(bestStart+bestLen).join(':');if(head&&tail)return`${head}::${tail}`;else if(head)return`${head}::`;else if(tail)return`::${tail}`;else return`::`;}});