2020-05-12 22:35:40 +00:00
|
|
|
# TODO: How can this be tested?
|
2020-05-12 22:37:07 +00:00
|
|
|
proc uncompressFramedStream*(conn: Connection,
|
|
|
|
output: OutputStream,
|
|
|
|
expectedSize: int): Future[Result[void, cstring]]
|
|
|
|
{.async.} =
|
2020-05-12 22:35:40 +00:00
|
|
|
var header: array[STREAM_HEADER.len, byte]
|
|
|
|
try:
|
|
|
|
await conn.readExactly(addr header[0], header.len)
|
|
|
|
except LPStreamEOFError:
|
|
|
|
return err "Unexpected EOF before snappy header"
|
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
if header != STREAM_HEADER.toOpenArrayByte(0, STREAM_HEADER.high):
|
2020-05-12 22:35:40 +00:00
|
|
|
return err "Incorrect snappy header"
|
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
var totalBytesDecompressed = 0
|
2020-05-12 22:35:40 +00:00
|
|
|
var uncompressedData = newSeq[byte](MAX_UNCOMPRESSED_DATA_LEN)
|
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
while totalBytesDecompressed < expectedSize:
|
2020-05-12 22:35:40 +00:00
|
|
|
var frameHeader: array[4, byte]
|
|
|
|
try:
|
|
|
|
await conn.readExactly(addr frameHeader[0], frameHeader.len)
|
|
|
|
except LPStreamEOFError:
|
2020-05-12 22:37:07 +00:00
|
|
|
break
|
2020-05-12 22:35:40 +00:00
|
|
|
|
|
|
|
let x = uint32.fromBytesLE frameHeader
|
|
|
|
let id = x and 0xFF
|
|
|
|
let dataLen = (x shr 8).int
|
|
|
|
|
|
|
|
if dataLen > MAX_COMPRESSED_DATA_LEN:
|
|
|
|
return err "invalid snappy frame length"
|
|
|
|
|
|
|
|
var frameData = newSeq[byte](dataLen)
|
|
|
|
try:
|
|
|
|
await conn.readExactly(addr frameData[0], dataLen)
|
|
|
|
except LPStreamEOFError:
|
|
|
|
return err "Incomplete snappy frame"
|
|
|
|
|
|
|
|
if id == COMPRESSED_DATA_IDENTIFIER:
|
|
|
|
if dataLen < 4:
|
|
|
|
return err "Snappy frame size too low to contain CRC checksum"
|
|
|
|
|
|
|
|
let
|
|
|
|
crc = uint32.fromBytesLE frameData[0..3]
|
2020-05-12 22:37:07 +00:00
|
|
|
uncompressedLen = snappyUncompress(frameData.toOpenArray(4, frameData.high), uncompressedData)
|
2020-05-12 22:35:40 +00:00
|
|
|
|
|
|
|
if uncompressedLen <= 0:
|
|
|
|
return err "Failed to decompress snappy frame"
|
|
|
|
|
|
|
|
if not checkCrcAndAppend(output, uncompressedData.toOpenArray(0, uncompressedLen-1), crc):
|
|
|
|
return err "Snappy content CRC checksum failed"
|
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
totalBytesDecompressed += uncompressedLen
|
|
|
|
|
2020-05-12 22:35:40 +00:00
|
|
|
elif id == UNCOMPRESSED_DATA_IDENTIFIER:
|
|
|
|
if dataLen < 4:
|
|
|
|
return err "Snappy frame size too low to contain CRC checksum"
|
|
|
|
|
|
|
|
let crc = uint32.fromBytesLE frameData[0..3]
|
2020-05-12 22:37:07 +00:00
|
|
|
if not checkCrcAndAppend(output, frameData.toOpenArray(4, frameData.high), crc):
|
2020-05-12 22:35:40 +00:00
|
|
|
return err "Snappy content CRC checksum failed"
|
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
totalBytesDecompressed += frameData.len - 4
|
|
|
|
|
2020-05-12 22:35:40 +00:00
|
|
|
elif id < 0x80:
|
|
|
|
# Reserved unskippable chunks (chunk types 0x02-0x7f)
|
|
|
|
# if we encounter this type of chunk, stop decoding
|
|
|
|
# the spec says it is an error
|
|
|
|
return err "Invalid snappy chunk type"
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Reserved skippable chunks (chunk types 0x80-0xfe)
|
|
|
|
# including STREAM_HEADER (0xff) should be skipped
|
|
|
|
continue
|
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
return ok()
|
2020-05-12 22:35:40 +00:00
|
|
|
|
|
|
|
proc readSizePrefix(conn: Connection,
|
2020-05-12 22:37:07 +00:00
|
|
|
maxSize: uint32): Future[NetRes[uint32]] {.async.} =
|
2020-05-12 22:35:40 +00:00
|
|
|
trace "about to read msg size prefix"
|
2020-05-12 22:37:07 +00:00
|
|
|
var parser: VarintParser[uint32, ProtoBuf]
|
|
|
|
try:
|
|
|
|
while true:
|
|
|
|
var nextByte: byte
|
|
|
|
await conn.readExactly(addr nextByte, 1)
|
|
|
|
case parser.feedByte(nextByte)
|
|
|
|
of Done:
|
|
|
|
let res = parser.getResult
|
|
|
|
if res > maxSize:
|
|
|
|
return neterr SizePrefixOverflow
|
|
|
|
else:
|
|
|
|
return ok res
|
|
|
|
of Overflow:
|
|
|
|
return neterr SizePrefixOverflow
|
|
|
|
of Incomplete:
|
|
|
|
continue
|
|
|
|
except LPStreamEOFError:
|
|
|
|
return neterr UnexpectedEOF
|
|
|
|
|
|
|
|
proc readChunkPayload(conn: Connection,
|
|
|
|
noSnappy: bool,
|
|
|
|
MsgType: type): Future[NetRes[MsgType]] {.async.} =
|
|
|
|
let prefix = await readSizePrefix(conn, MAX_CHUNK_SIZE)
|
|
|
|
let size = if prefix.isOk: prefix.value.int
|
|
|
|
else: return err(prefix.error)
|
|
|
|
|
|
|
|
if size > 0:
|
|
|
|
if noSnappy:
|
|
|
|
var bytes = newSeq[byte](size)
|
|
|
|
await conn.readExactly(addr bytes[0], bytes.len)
|
|
|
|
return ok SSZ.decode(bytes, MsgType)
|
|
|
|
else:
|
|
|
|
var snappyOutput = memoryOutput()
|
|
|
|
let status = await conn.uncompressFramedStream(snappyOutput, size)
|
|
|
|
if status.isOk:
|
|
|
|
var decompressedBytes = snappyOutput.getOutput
|
|
|
|
if decompressedBytes.len != size:
|
|
|
|
return neterr InvalidSnappyBytes
|
|
|
|
else:
|
|
|
|
return ok SSZ.decode(decompressedBytes, MsgType)
|
2020-05-12 22:35:40 +00:00
|
|
|
else:
|
2020-05-12 22:37:07 +00:00
|
|
|
return neterr InvalidSnappyBytes
|
|
|
|
else:
|
|
|
|
return neterr ZeroSizePrefix
|
2020-05-12 22:35:40 +00:00
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
proc readResponseChunk(conn: Connection,
|
|
|
|
noSnappy: bool,
|
|
|
|
MsgType: typedesc): Future[NetRes[MsgType]] {.async.} =
|
2020-05-12 22:35:40 +00:00
|
|
|
try:
|
2020-05-12 22:37:07 +00:00
|
|
|
var responseCodeByte: byte
|
|
|
|
try:
|
|
|
|
await conn.readExactly(addr responseCodeByte, 1)
|
|
|
|
except LPStreamEOFError:
|
|
|
|
return neterr PotentiallyExpectedEOF
|
|
|
|
|
|
|
|
static: assert ResponseCode.low.ord == 0
|
|
|
|
if responseCodeByte > ResponseCode.high.byte:
|
|
|
|
return neterr InvalidResponseCode
|
|
|
|
|
|
|
|
let responseCode = ResponseCode responseCodeByte
|
|
|
|
case responseCode:
|
|
|
|
of InvalidRequest, ServerError:
|
|
|
|
let errorMsgChunk = await readChunkPayload(conn, noSnappy, string)
|
|
|
|
let errorMsg = if errorMsgChunk.isOk: errorMsgChunk.value
|
|
|
|
else: return err(errorMsgChunk.error)
|
|
|
|
return err Eth2NetworkingError(kind: ReceivedErrorResponse,
|
|
|
|
responseCode: responseCode,
|
|
|
|
errorMsg: errorMsg)
|
|
|
|
of Success:
|
|
|
|
discard
|
|
|
|
|
|
|
|
return await readChunkPayload(conn, noSnappy, MsgType)
|
2020-05-12 22:35:40 +00:00
|
|
|
|
2020-05-12 22:37:07 +00:00
|
|
|
except LPStreamEOFError:
|
|
|
|
return neterr UnexpectedEOF
|
|
|
|
|
|
|
|
proc readResponse(conn: Connection,
|
|
|
|
noSnappy: bool,
|
|
|
|
MsgType: type): Future[NetRes[MsgType]] {.gcsafe, async.} =
|
2020-05-12 22:35:40 +00:00
|
|
|
when MsgType is seq:
|
|
|
|
type E = ElemType(MsgType)
|
|
|
|
var results: MsgType
|
|
|
|
while true:
|
2020-05-12 22:37:07 +00:00
|
|
|
let nextRes = await conn.readResponseChunk(noSnappy, E)
|
|
|
|
if nextRes.isErr:
|
|
|
|
if nextRes.error.kind == PotentiallyExpectedEOF:
|
|
|
|
return ok results
|
|
|
|
return err nextRes.error
|
|
|
|
else:
|
|
|
|
results.add nextRes.value
|
2020-05-12 22:35:40 +00:00
|
|
|
else:
|
2020-05-12 22:37:07 +00:00
|
|
|
return await conn.readResponseChunk(noSnappy, MsgType)
|
|
|
|
|