// Direct-HTTPS public edge for the five-route vNext runtime, reconstructed on
// the accepted base per the runtime relock activation. One exact binary HTTPS
// contract serves DESCRIBE, CELL, INBOX, CORE and the bounded one-hop FORWARD
// route: no operation-specific alternate URLs, no cookies, Authorization,
// referrer, redirect, compression, chunking or protocol fallback. The edge
// owns TLS, derives only the exporter binding, and forwards exact opaque
// bytes over peercred-authenticated private IPC; it never learns application
// schema or plaintext and never publishes a readiness or descriptor bit.
import http from 'node:http'
import https from 'node:https'
import net from 'node:net'
import b4a from 'b4a'
import { socketPeerCredentials } from '@hiverelay/blind-peercred'
import {
FAMILY,
FAMILY_ROUTES,
OUTER_CLASS,
PROTOCOL,
TRANSPORT_ID,
TRANSPORT_SUPPORT,
assertAdvertisedOperation,
operationProfile
} from '@hiverelay/blind-protocol/wire-runtime-authority'
import { decodeOuterEnvelope } from '@hiverelay/blind-protocol/outer-envelope'
import {
FORWARD_HTTPS_REQUEST_ROLE_V1,
FORWARD_HTTPS_TLS_EXPORTER_LABEL_V1,
FORWARD_HTTPS_V3_LIMITS,
blindForwardHttpsOriginForwardTurnRequestV1,
decodeCanonical,
forwardHttpsForwardedRequestCommitmentV1,
forwardHttpsOriginRequestCommitmentV1,
forwardHttpsTlsExporterBindingHashV1,
forwardHttpsTlsExporterContextV1
} from '@hiverelay/blind-protocol'
import {
FORWARD_HTTPS_TARGET_TLS_EXPORTER_LABEL_V4,
LOCAL_FORWARD_HTTPS_DIRECTION_V4,
PRIVATE_IPC_V4_LIMITS,
assertLocalForwardHttpsResultTranscriptV4,
createLocalForwardHttpsOriginAuthorityV4,
createLocalForwardHttpsTargetIngressV4,
decodeLocalForwardHttpsTurnV4,
encodeLocalForwardHttpsSourceOriginTranscriptV4,
encodeLocalForwardHttpsTargetIngressV4,
forwardHttpsTargetTlsExporterBindingHashV4,
forwardHttpsTargetTlsExporterContextV4,
encodeLocalRequest,
decodeLocalResponse,
LOCAL_RESPONSE_KIND
} from '@hiverelay/blind-ipc'
import sodium from 'sodium-universal'
const REQUEST_BYTES = FORWARD_HTTPS_V3_LIMITS.EXACT_REQUEST_BYTES
const RESULT_TRANSCRIPT_BYTES = PRIVATE_IPC_V4_LIMITS.RESULT_TRANSCRIPT_BYTES
const SOURCE_ORIGIN_TRANSCRIPT_BYTES = PRIVATE_IPC_V4_LIMITS.SOURCE_ORIGIN_TRANSCRIPT_BYTES
const TARGET_INGRESS_TRANSCRIPT_BYTES = PRIVATE_IPC_V4_LIMITS.TARGET_INGRESS_TRANSCRIPT_BYTES
const MAX_DEADLINE_MILLIS = PRIVATE_IPC_V4_LIMITS.MAX_DEADLINE_MILLIS
const MAX_REQUEST_LINE_BYTES = 1024
const MAX_HEADER_FIELDS = 32
const MAX_AGGREGATE_HEADER_BYTES = 16384
const ROUTE_TO_FAMILY = Object.freeze(Object.fromEntries(
Object.entries(FAMILY_ROUTES).map(([familyId, route]) => [route, Number(familyId)])))
export const FORWARD_HTTPS_EDGE_ROLE_VNEXT = Object.freeze({
SOURCE: 'SOURCE',
TARGET: 'TARGET'
})
export const FORWARD_HTTPS_EDGE_VNEXT_LIMITS = Object.freeze({
exactRequestBytes: REQUEST_BYTES,
exactResultBytes: FORWARD_HTTPS_V3_LIMITS.EXACT_RESULT_BYTES,
sourceOriginTranscriptBytes: SOURCE_ORIGIN_TRANSCRIPT_BYTES,
targetIngressTranscriptBytes: TARGET_INGRESS_TRANSCRIPT_BYTES,
resultTranscriptBytes: RESULT_TRANSCRIPT_BYTES,
maxExchangeDeadlineMillis: MAX_DEADLINE_MILLIS,
maxRequestLineBytes: MAX_REQUEST_LINE_BYTES,
maxHeaderFields: MAX_HEADER_FIELDS,
maxAggregateHeaderBytes: MAX_AGGREGATE_HEADER_BYTES,
descriptorOperationBits: 0,
advertisedOperationBits: 0,
readinessOperationBits: 0,
runtimeReady: false,
releaseReady: false
})
export class ForwardHttpsEdgeVnextError extends Error {
constructor (message, code = 'BLIND_FORWARD_HTTPS_EDGE_VNEXT', status = null) {
super(message)
this.name = 'ForwardHttpsEdgeVnextError'
this.code = code
if (status != null) this.status = status
}
}
function fail (message, code = 'BLIND_FORWARD_HTTPS_EDGE_VNEXT', status = null) {
throw new ForwardHttpsEdgeVnextError(message, code, status)
}
function transport (status, message) {
fail(message, 'BLIND_FORWARD_HTTPS_EDGE_VNEXT_TRANSPORT', status)
}
function exactBytes (value, length, field, nonzero = false) {
if (!value || typeof value.byteLength !== 'number') fail(`${field} must be bytes`)
value = b4a.from(value)
if (value.byteLength !== length) fail(`${field} must be exactly ${length} bytes`)
if (nonzero) {
let found = false
for (const byte of value) if (byte !== 0) { found = true; break }
if (!found) fail(`${field} must be nonzero`)
}
return value
}
function randomNonzero (length, field) {
const output = b4a.alloc(length)
for (let attempt = 0; attempt < 8; attempt++) {
sodium.randombytes_buf(output)
if (output.some(byte => byte !== 0)) return output
}
fail(`${field} remained zero`, 'BLIND_FORWARD_HTTPS_EDGE_VNEXT_RNG')
}
function defaultMonotonicMillis () {
return Number(process.hrtime.bigint() / 1_000_000n)
}
// Exact credential-free header policy shared by every route: one canonical
// content-length, the blind media type, and no chunking, compression,
// credentials, referrer, range or conditional negotiation of any kind.
export function assertForwardHttpsHeadersVnext (headers, exactLength) {
if (!headers || typeof headers !== 'object') fail('request headers are required')
for (const field of ['transfer-encoding', 'content-encoding', 'authorization', 'cookie', 'referer', 'range']) {
if (headers[field] != null) transport(400, `FORWARD/UNARY request header ${field} is forbidden`)
}
if (headers.expect != null || headers.trailer != null) {
transport(400, 'chunking or trailer negotiation is forbidden')
}
const declared = headers['content-length']
if (declared == null || !/^(0|[1-9][0-9]*)$/.test(declared)) {
transport(400, 'an exact canonical content-length is required')
}
if (Number(declared) !== exactLength) transport(400, 'request body is outside its exact byte class')
return true
}
// Derive the source-side TLS exporter binding from the live client TLS socket.
// The raw exporter leaves this function only to be zeroed; only the binding
// hash crosses the IPC boundary.
export function deriveForwardHttpsOriginExporterBindingVnext (socket, request, requestBytes) {
if (!socket || socket.encrypted !== true || typeof socket.exportKeyingMaterial !== 'function') {
fail('FORWARD requires a live Edge-owned TLS exporter', 'BLIND_FORWARD_TLS_REQUIRED', 400)
}
const commitment = forwardHttpsOriginRequestCommitmentV1(requestBytes)
const context = forwardHttpsTlsExporterContextV1(request.stableSessionId, request.sequence, commitment)
let exporter
try {
exporter = b4a.from(socket.exportKeyingMaterial(32, FORWARD_HTTPS_TLS_EXPORTER_LABEL_V1, context))
return {
tlsExporterBindingHash: forwardHttpsTlsExporterBindingHashV1(exporter, context),
originRequestCommitment: commitment
}
} catch (error) {
if (error && error.status === 400) throw error
fail('FORWARD TLS exporter derivation failed', 'BLIND_FORWARD_TLS_REQUIRED', 400)
} finally {
if (exporter) exporter.fill(0)
}
}
// Derive the target-side TLS exporter binding from the live source-daemon TLS
// session. The target edge is a bounded byte relay: no caller URL, host, IP,
// address or credentials are accepted or observed beyond the exact body.
export function deriveForwardHttpsTargetExporterBindingVnext (socket, request, requestBytes) {
if (!socket || socket.encrypted !== true || typeof socket.exportKeyingMaterial !== 'function') {
fail('FORWARD requires a live Edge-owned TLS exporter', 'BLIND_FORWARD_TLS_REQUIRED', 400)
}
const commitment = forwardHttpsForwardedRequestCommitmentV1(requestBytes)
const context = forwardHttpsTargetTlsExporterContextV4(request.stableSessionId, request.sequence, commitment)
let exporter
try {
exporter = b4a.from(socket.exportKeyingMaterial(32, FORWARD_HTTPS_TARGET_TLS_EXPORTER_LABEL_V4, context))
return {
targetTlsExporterBindingHash: forwardHttpsTargetTlsExporterBindingHashV4(exporter, context),
forwardedRequestCommitment: commitment
}
} catch (error) {
if (error && error.status === 400) throw error
fail('FORWARD TLS exporter derivation failed', 'BLIND_FORWARD_TLS_REQUIRED', 400)
} finally {
if (exporter) exporter.fill(0)
}
}
// One bounded exact transcript exchange over the private Unix socket: write
// the transcript, half-close, and read exactly one result transcript. The
// daemon's peercred identity is verified on the live connected socket before
// any byte is written.
export function exchangeForwardHttpsTranscriptVnext (socketPath, transcriptBytes, options = {}) {
transcriptBytes = exactBytes(transcriptBytes, transcriptBytes.byteLength, 'IPC transcript')
const timeoutMs = options.timeoutMs == null ? 15_000 : options.timeoutMs
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 35_000) fail('IPC timeout is outside its bound')
if (typeof socketPath !== 'string' || !socketPath.startsWith('/') || socketPath.includes('\0')) {
fail('forward IPC socket path is invalid')
}
return new Promise((resolve, reject) => {
const socketFactory = options.socketFactory || (socketOptions => net.createConnection(socketOptions))
const socket = socketFactory({ path: socketPath, allowHalfOpen: true })
const chunks = []
let total = 0
let settled = false
const timer = setTimeout(() => finish(Object.assign(new Error('forward IPC deadline elapsed'), { code: 'BLIND_FORWARD_IPC_TIMEOUT' })), timeoutMs)
if (timer.unref) timer.unref()
const onAbort = () => finish(options.signal.reason || Object.assign(new Error('forward IPC aborted'), { code: 'ABORT_ERR' }))
const finish = (error, value) => {
if (settled) return
settled = true
clearTimeout(timer)
if (options.signal) options.signal.removeEventListener('abort', onAbort)
socket.destroy()
if (error) reject(error)
else resolve(value)
}
socket.once('connect', async () => {
try {
if (typeof options.verifyConnectedSocket === 'function') await options.verifyConnectedSocket(socket)
socket.end(transcriptBytes)
} catch (error) {
finish(error)
}
})
socket.on('data', chunk => {
if (total + chunk.byteLength > RESULT_TRANSCRIPT_BYTES) {
finish(Object.assign(new Error('forward IPC result exceeded its exact bound'), { code: 'BLIND_FORWARD_IPC_LENGTH' }))
return
}
chunks.push(b4a.from(chunk))
total += chunk.byteLength
})
socket.once('end', () => {
if (total !== RESULT_TRANSCRIPT_BYTES) {
finish(Object.assign(new Error('forward IPC result was not one exact turn'), { code: 'BLIND_FORWARD_IPC_LENGTH' }))
return
}
finish(null, b4a.concat(chunks, total))
})
socket.once('error', finish)
if (options.signal) {
if (options.signal.aborted) return onAbort()
options.signal.addEventListener('abort', onAbort, { once: true })
}
})
}
export class ForwardHttpsEdgeVnext {
constructor (options = {}) {
this.host = options.host || '127.0.0.1'
this.port = options.port == null ? 0 : options.port
this.endpointId = options.endpointId == null ? 1 : options.endpointId
this.role = options.role || FORWARD_HTTPS_EDGE_ROLE_VNEXT.SOURCE
this.unarySocketPath = options.unarySocketPath || null
this.forwardSocketPath = options.forwardSocketPath || null
this.tls = options.tls || null
this.allowInsecureLoopback = options.allowInsecureLoopback === true
this.launchTopologyHash = options.launchTopologyHash
? exactBytes(options.launchTopologyHash, 32, 'launchTopologyHash', true)
: null
this.wireV3AbiHash = options.wireV3AbiHash
? exactBytes(options.wireV3AbiHash, 32, 'wireV3AbiHash', true)
: null
this.edgeProcessNonce = options.edgeProcessNonce
? exactBytes(options.edgeProcessNonce, 32, 'edgeProcessNonce', true)
: randomNonzero(32, 'edgeProcessNonce')
this.expectedDaemonUid = options.expectedDaemonUid
this.expectedDaemonGid = options.expectedDaemonGid
this.monotonicMillis = options.monotonicMillis || defaultMonotonicMillis
this.onError = options.onError || (() => {})
this.onUnaryExchange = options.onUnaryExchange || null
this.onForwardExchange = options.onForwardExchange || null
this.socketFactory = options.socketFactory || null
this.inFlight = 0
this.maxInFlight = Number.isSafeInteger(options.maxInFlight) ? options.maxInFlight : 1024
this.server = null
this.sockets = new Set()
if (!Number.isInteger(this.endpointId) || this.endpointId < 1 || this.endpointId > 255) {
throw new TypeError('endpointId is outside 1..255')
}
if (this.role !== FORWARD_HTTPS_EDGE_ROLE_VNEXT.SOURCE && this.role !== FORWARD_HTTPS_EDGE_ROLE_VNEXT.TARGET) {
throw new TypeError('role must be SOURCE or TARGET')
}
if (this.tls == null && !this.allowInsecureLoopback) {
fail('public edge requires operator TLS key material', 'BLIND_TLS_REQUIRED')
}
if (!Number.isInteger(this.expectedDaemonUid) || !Number.isInteger(this.expectedDaemonGid)) {
throw new TypeError('expected daemon UID/GID are required for the peercred boundary')
}
}
address () {
return this.server && this.server.address()
}
_verifyDaemonPeer (socket) {
const credentials = socketPeerCredentials(socket)
if (credentials.uid !== this.expectedDaemonUid || credentials.gid !== this.expectedDaemonGid) {
fail('daemon peer credentials do not match the expected launch identity', 'BLIND_FORWARD_PEERCRED')
}
return credentials
}
async start () {
if (this.server) return this
const listener = (request, response) => {
this._handle(request, response).catch(error => {
// A dead socket must never turn the floating handler promise into an
// unhandled rejection; the exact status was already best-effort.
this.onError(error)
try {
response.destroy()
} catch {}
})
}
const server = this.tls
? https.createServer({ ...this.tls, handshakeTimeout: 5_000 }, listener)
: http.createServer(listener)
server.maxRequestsPerSocket = 1
server.keepAliveTimeout = 1
server.maxHeadersCount = 0
server.requestTimeout = 0
server.on('connection', socket => {
socket.on('error', () => {})
this.sockets.add(socket)
socket.once('close', () => this.sockets.delete(socket))
})
server.on('secureConnection', socket => {
socket.on('error', () => {})
this.sockets.add(socket)
socket.once('close', () => this.sockets.delete(socket))
})
server.on('clientError', (error, socket) => {
this.onError(error)
socket.destroy()
})
await new Promise((resolve, reject) => {
server.once('error', reject)
server.listen(this.port, this.host, resolve)
})
this.server = server
return this
}
async close () {
const server = this.server
if (!server) return
for (const socket of this.sockets) socket.destroy()
await new Promise(resolve => server.close(resolve))
this.server = null
}
_transportFailure (response, status, request) {
// Early rejections must still flush before the connection closes: drain
// the bounded request first so the client always observes the exact
// status, then close. Chunked ingress is discarded, never decoded.
let settled = false
const finish = () => {
if (settled) return
settled = true
try {
response.writeHead(status, {
'content-type': 'text/plain; charset=utf-8',
'content-length': 0,
'cache-control': 'no-store',
connection: 'close'
})
response.end()
} catch {}
}
if (request && !request.readableEnded && typeof request.resume === 'function') {
request.once('end', finish)
request.once('error', finish)
const timer = setTimeout(finish, 2_000)
if (timer.unref) timer.unref()
request.resume()
return
}
finish()
}
_respond (response, bytes) {
response.writeHead(200, {
'content-type': PROTOCOL.mediaType,
'content-length': bytes.byteLength,
'cache-control': 'no-store',
'x-content-type-options': 'nosniff',
connection: 'close'
})
response.end(bytes)
}
async _readBody (request, exactLength) {
const chunks = []
let total = 0
for await (const chunk of request) {
total += chunk.byteLength
if (total > exactLength) transport(400, 'request body exceeds its exact byte class')
chunks.push(b4a.from(chunk))
}
if (total !== exactLength) transport(400, 'request body is shorter than its exact byte class')
return b4a.concat(chunks, total)
}
async _handle (request, response) {
try {
if (this.inFlight >= this.maxInFlight) return this._transportFailure(response, 429, request)
this.inFlight++
try {
const family = ROUTE_TO_FAMILY[request.url]
if (family == null) return this._transportFailure(response, 404, request)
if (request.method !== 'POST') return this._transportFailure(response, 405, request)
const contentType = request.headers['content-type']
if (contentType !== PROTOCOL.mediaType) return this._transportFailure(response, 400, request)
if (request.rawHeaders.length / 2 > MAX_HEADER_FIELDS) return this._transportFailure(response, 400, request)
let aggregate = 0
for (let index = 0; index < request.rawHeaders.length; index += 2) {
aggregate += request.rawHeaders[index].length + request.rawHeaders[index + 1].length + 4
}
if (aggregate > MAX_AGGREGATE_HEADER_BYTES) return this._transportFailure(response, 400, request)
if (family === FAMILY.FORWARD) {
const body = await this._readTurn(request)
return await this._forward(request, response, body)
}
const outerClass = this._outerClassFor(request)
const body = await this._readEnvelope(request, outerClass)
return await this._unary(request, response, family, outerClass, body)
} finally {
this.inFlight--
}
} catch (error) {
if (error && Number.isInteger(error.status)) return this._transportFailure(response, error.status, request)
this.onError(error)
if (!response.headersSent) return this._transportFailure(response, 503, request)
response.destroy()
}
}
_outerClassFor (request) {
const declared = request.headers['content-length']
if (declared == null || !/^(0|[1-9][0-9]*)$/.test(declared)) transport(400, 'an exact canonical content-length is required')
const outerClass = Object.entries(OUTER_CLASS).find(([, bytes]) => bytes === Number(declared))
if (!outerClass) transport(400, 'request body is not an exact outer class')
return Number(outerClass[0])
}
async _readTurn (request) {
assertForwardHttpsHeadersVnext(request.headers, REQUEST_BYTES)
return this._readBody(request, REQUEST_BYTES)
}
async _readEnvelope (request, outerClass) {
assertForwardHttpsHeadersVnext(request.headers, OUTER_CLASS[outerClass])
const body = await this._readBody(request, OUTER_CLASS[outerClass])
let envelope
try {
envelope = decodeOuterEnvelope(body, { copyInner: true, copyBody: false })
} catch {
transport(400, 'malformed blind envelope')
}
return { body, frame: envelope.frame, outerClass: envelope.outerClass }
}
async _unary (request, response, family, outerClass, read) {
const frame = read.frame
// Exact opaque operation/transport qualification before any IPC contact:
// the envelope family must equal the route family, the operation must be
// advertised by the active release profile, and the operation must
// support the direct HTTP transport. Reserved operations fail closed with
// a bare transport 400 (the corrected fixture behavior).
if (frame.familyId !== family) transport(400, 'envelope family does not match the route family')
try {
assertAdvertisedOperation(frame.familyId, frame.operationId)
} catch {
transport(400, 'operation is outside the advertised release profile')
}
const profile = operationProfile(frame.familyId, frame.operationId)
if (!profile || (profile.transportSupportBits & TRANSPORT_SUPPORT.DIRECT_HTTP) === 0) {
transport(400, 'operation is not qualified for the direct HTTP transport')
}
if (!this.unarySocketPath) transport(503, 'unary IPC is unavailable')
const accepted = BigInt(this.monotonicMillis())
const deadline = accepted + BigInt(family === FAMILY.INBOX ? 35_000 : 15_000)
const requestFrame = encodeLocalRequest({
family,
transportId: TRANSPORT_ID.HTTPS_DIRECT,
transportSupportBit: TRANSPORT_SUPPORT.DIRECT_HTTP,
endpointId: this.endpointId,
outerClass,
acceptedMonotonicMillis: accepted,
absoluteDeadlineMonotonicMillis: deadline,
body: read.body
})
if (this.onUnaryExchange) this.onUnaryExchange({ phase: 'request', bytes: requestFrame, body: read.body })
const responseBytes = await this._exchangeUnary(requestFrame, family)
if (this.onUnaryExchange) this.onUnaryExchange({ phase: 'response', bytes: responseBytes })
this._respond(response, responseBytes)
}
async _exchangeUnary (requestFrame, family) {
const timeoutMs = family === FAMILY.INBOX ? 35_000 : 15_000
return new Promise((resolve, reject) => {
const socketFactory = this.socketFactory || (socketOptions => net.createConnection(socketOptions))
const socket = socketFactory({ path: this.unarySocketPath, allowHalfOpen: true })
const chunks = []
let total = 0
let settled = false
const timer = setTimeout(() => finish(Object.assign(new Error('unary IPC deadline elapsed'), { code: 'BLIND_UNARY_IPC_TIMEOUT' })), timeoutMs)
if (timer.unref) timer.unref()
const finish = (error, value) => {
if (settled) return
settled = true
clearTimeout(timer)
socket.destroy()
if (error) reject(error)
else resolve(value)
}
socket.once('connect', () => {
try {
this._verifyDaemonPeer(socket)
socket.end(requestFrame)
} catch (error) {
finish(error)
}
})
socket.on('data', chunk => {
chunks.push(b4a.from(chunk))
total += chunk.byteLength
})
socket.once('end', () => {
try {
const decoded = decodeLocalResponse(b4a.concat(chunks, total))
if (decoded.responseKind === LOCAL_RESPONSE_KIND.LOCAL_BROKER_ERROR) {
const error = new Error('daemon broker rejected the exchange')
error.code = 'LOCAL_BROKER_ERROR'
error.localBrokerError = decoded.localBrokerError
finish(error)
return
}
finish(null, b4a.from(decoded.externalCanonicalBytes))
} catch (error) {
finish(error)
}
})
socket.once('error', finish)
})
}
async _forward (request, response, body) {
let turn
try {
turn = decodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, body, { copyBytes: true })
} catch {
transport(400, 'FORWARD request is not the exact canonical turn body')
}
const expectedRole = this.role === FORWARD_HTTPS_EDGE_ROLE_VNEXT.SOURCE
? FORWARD_HTTPS_REQUEST_ROLE_V1.ORIGIN_TEMPLATE
: FORWARD_HTTPS_REQUEST_ROLE_V1.FORWARDED
if (turn.requestRole !== expectedRole) transport(400, 'FORWARD request role is not accepted by this edge')
if (!this.forwardSocketPath) transport(503, 'forward IPC is unavailable')
const accepted = BigInt(this.monotonicMillis())
const deadline = accepted + 10_000n
const localChannelNonce = randomNonzero(32, 'localChannelNonce')
let transcript
let authority
if (this.role === FORWARD_HTTPS_EDGE_ROLE_VNEXT.SOURCE) {
const derived = deriveForwardHttpsOriginExporterBindingVnext(request.socket, turn, body)
authority = createLocalForwardHttpsOriginAuthorityV4({
version: 4,
authorityKind: 1,
transportId: 1,
endpointId: this.endpointId,
flags: 0,
wireV3AbiHash: this.wireV3AbiHash,
signedLaunchTopologyHash: this.launchTopologyHash,
edgeProcessNonce: this.edgeProcessNonce,
localChannelNonce,
tlsExporterBindingHash: derived.tlsExporterBindingHash,
originRequestCommitment: derived.originRequestCommitment,
stableSessionId: turn.stableSessionId,
sequence: turn.sequence,
acceptedMonotonicMillis: accepted,
absoluteDeadlineMonotonicMillis: deadline
})
transcript = encodeLocalForwardHttpsSourceOriginTranscriptV4(authority, {
version: 4,
direction: LOCAL_FORWARD_HTTPS_DIRECTION_V4.ORIGIN_REQUEST,
wireRole: FORWARD_HTTPS_REQUEST_ROLE_V1.ORIGIN_TEMPLATE,
flags: 0,
wireV3AbiHash: this.wireV3AbiHash,
localExchangeId: authority.localExchangeId,
originRequestCommitment: derived.originRequestCommitment,
stableSessionId: turn.stableSessionId,
sequence: turn.sequence,
body
})
} else {
const derived = deriveForwardHttpsTargetExporterBindingVnext(request.socket, turn, body)
authority = createLocalForwardHttpsTargetIngressV4({
endpointId: this.endpointId,
wireV3AbiHash: this.wireV3AbiHash,
signedLaunchTopologyHash: this.launchTopologyHash,
edgeProcessNonce: this.edgeProcessNonce,
localChannelNonce,
targetTlsExporterBindingHash: derived.targetTlsExporterBindingHash,
acceptedMonotonicMillis: accepted,
absoluteDeadlineMonotonicMillis: deadline,
body
})
transcript = encodeLocalForwardHttpsTargetIngressV4(authority)
}
if (this.onForwardExchange) this.onForwardExchange({ phase: 'request', bytes: transcript, body })
const resultTranscript = await exchangeForwardHttpsTranscriptVnext(this.forwardSocketPath, transcript, {
verifyConnectedSocket: socket => this._verifyDaemonPeer(socket),
socketFactory: this.socketFactory || undefined
})
if (this.onForwardExchange) this.onForwardExchange({ phase: 'response', bytes: resultTranscript })
const resultTurn = decodeLocalForwardHttpsTurnV4(resultTranscript)
assertLocalForwardHttpsResultTranscriptV4(resultTranscript, authority)
this._respond(response, b4a.from(resultTurn.body))
}
}
export const FORWARD_HTTPS_EXPORTER_LABEL_VNEXT = FORWARD_HTTPS_TLS_EXPORTER_LABEL_V1