package gateway import ( "bytes" "encoding/binary" ) const ( apolloVideoMaximumDataShards = 255 apolloVideoMaximumBlocks = 4 apolloVideoShardPayloadSize = apolloVideoRawPacketSize - apolloRTPHeaderSize - 4 - apolloVideoNVHeaderSize ) type apolloVideoShard struct { frame uint32 block uint8 lastBlock uint8 dataPackets int parity int index int sequence uint16 streamIndex uint32 flags byte payload []byte } type apolloVideoFECBlock struct { dataPackets int parity int firstSeq uint16 streamBase uint32 haveBase bool shards [][]byte received []bool count int complete bool } type apolloVideoAssembler struct { haveFrame bool frame uint32 lastBlock uint8 blocks [apolloVideoMaximumBlocks]*apolloVideoFECBlock } func (a *apolloVideoAssembler) Add(shard apolloVideoShard) ([]byte, error) { if len(shard.payload) != apolloVideoShardPayloadSize || shard.dataPackets < 1 || shard.dataPackets > apolloVideoMaximumDataShards || shard.parity < 0 || shard.dataPackets+shard.parity > 255 || shard.block > shard.lastBlock || shard.lastBlock >= apolloVideoMaximumBlocks || shard.index >= shard.dataPackets+shard.parity { return nil, errApolloMedia } if !a.haveFrame || apolloFrameNewer(shard.frame, a.frame) { *a = apolloVideoAssembler{haveFrame: true, frame: shard.frame, lastBlock: shard.lastBlock} } else if shard.frame != a.frame { return nil, nil } if shard.lastBlock != a.lastBlock { return nil, errApolloMedia } block := a.blocks[shard.block] if block == nil { block = &apolloVideoFECBlock{ dataPackets: shard.dataPackets, parity: shard.parity, shards: make([][]byte, shard.dataPackets+shard.parity), received: make([]bool, shard.dataPackets+shard.parity), } a.blocks[shard.block] = block } else if block.dataPackets != shard.dataPackets || block.parity != shard.parity { return nil, errApolloMedia } if shard.index < shard.dataPackets { if shard.index == 0 && shard.flags&0x04 == 0 || shard.index == shard.dataPackets-1 && shard.flags&0x02 == 0 || shard.flags&^byte(0x07) != 0 { return nil, errApolloMedia } base := shard.sequence - uint16(shard.index) streamBase := shard.streamIndex - uint32(shard.index) if !block.haveBase { block.firstSeq, block.streamBase, block.haveBase = base, streamBase, true } else if block.firstSeq != base || block.streamBase != streamBase { return nil, errApolloMedia } } if block.received[shard.index] { if !bytes.Equal(block.shards[shard.index], shard.payload) { return nil, errApolloMedia } return nil, nil } block.shards[shard.index] = append([]byte(nil), shard.payload...) block.received[shard.index] = true block.count++ if block.count < block.dataPackets { return nil, nil } if !block.complete { if err := reconstructApolloVideoBlock(block); err != nil { return nil, err } block.complete = true } for index := uint8(0); index <= a.lastBlock; index++ { if a.blocks[index] == nil || !a.blocks[index].complete { return nil, nil } } capacity := 0 for blockIndex := uint8(0); blockIndex <= a.lastBlock; blockIndex++ { capacity += a.blocks[blockIndex].dataPackets * apolloVideoShardPayloadSize } frame := make([]byte, 0, capacity) for blockIndex := uint8(0); blockIndex <= a.lastBlock; blockIndex++ { for shardIndex := 0; shardIndex < a.blocks[blockIndex].dataPackets; shardIndex++ { frame = append(frame, a.blocks[blockIndex].shards[shardIndex]...) } } if len(frame) < 8 || frame[0] != 0x01 { return nil, errApolloMedia } lastPayloadLength := int(binary.LittleEndian.Uint16(frame[4:6])) end := len(frame) - apolloVideoShardPayloadSize + lastPayloadLength if lastPayloadLength == 0 || lastPayloadLength > apolloVideoShardPayloadSize || end <= 8 || end > len(frame) { return nil, errApolloMedia } *a = apolloVideoAssembler{} return append([]byte(nil), frame[8:end]...), nil } func apolloFrameNewer(first, second uint32) bool { return int32(first-second) > 0 } func reconstructApolloVideoBlock(block *apolloVideoFECBlock) error { if block == nil || block.count < block.dataPackets { return errApolloMedia } missing := false for index := 0; index < block.dataPackets; index++ { if !block.received[index] { missing = true block.shards[index] = make([]byte, apolloVideoShardPayloadSize) } } if !missing { return nil } selectedRows := make([][]byte, 0, block.dataPackets) selectedShards := make([][]byte, 0, block.dataPackets) for index, received := range block.received { if !received { continue } selectedRows = append(selectedRows, apolloVideoFECRow(index, block.dataPackets, block.parity)) selectedShards = append(selectedShards, block.shards[index]) if len(selectedRows) == block.dataPackets { break } } if len(selectedRows) != block.dataPackets { return errApolloMedia } inverse, ok := apolloGFInvert(selectedRows) if !ok { return errApolloMedia } for index := 0; index < block.dataPackets; index++ { if block.received[index] { continue } for source, coefficient := range inverse[index] { apolloGFAXPY(block.shards[index], selectedShards[source], coefficient) } block.received[index] = true } return nil } func apolloVideoFECRow(index, dataPackets, parity int) []byte { row := make([]byte, dataPackets) if index < dataPackets { row[index] = 1 return row } parityIndex := index - dataPackets for dataIndex := range row { row[dataIndex] = apolloGFInverse(byte((parity + dataIndex) ^ parityIndex)) } return row } func apolloGFInvert(matrix [][]byte) ([][]byte, bool) { size := len(matrix) if size == 0 { return nil, false } work := make([][]byte, size) inverse := make([][]byte, size) for row := range matrix { if len(matrix[row]) != size { return nil, false } work[row] = append([]byte(nil), matrix[row]...) inverse[row] = make([]byte, size) inverse[row][row] = 1 } for column := 0; column < size; column++ { pivot := column for pivot < size && work[pivot][column] == 0 { pivot++ } if pivot == size { return nil, false } work[column], work[pivot] = work[pivot], work[column] inverse[column], inverse[pivot] = inverse[pivot], inverse[column] factor := apolloGFInverse(work[column][column]) for index := column; index < size; index++ { work[column][index] = apolloGFMultiply(work[column][index], factor) } for index := range inverse[column] { inverse[column][index] = apolloGFMultiply(inverse[column][index], factor) } for row := 0; row < size; row++ { if row == column || work[row][column] == 0 { continue } factor = work[row][column] for index := column; index < size; index++ { work[row][index] ^= apolloGFMultiply(work[column][index], factor) } for index := range inverse[row] { inverse[row][index] ^= apolloGFMultiply(inverse[column][index], factor) } } } return inverse, true } func apolloGFAXPY(destination, source []byte, coefficient byte) { if coefficient == 0 { return } for index := range destination { destination[index] ^= apolloGFMultiply(source[index], coefficient) } } func apolloGFInverse(value byte) byte { if value == 0 { return 0 } return apolloGFPow(value, 254) } func apolloGFPow(value byte, exponent uint8) byte { result := byte(1) for exponent != 0 { if exponent&1 != 0 { result = apolloGFMultiply(result, value) } value = apolloGFMultiply(value, value) exponent >>= 1 } return result } func apolloGFMultiply(first, second byte) byte { var product byte for second != 0 { if second&1 != 0 { product ^= first } high := first & 0x80 first <<= 1 if high != 0 { first ^= 0x1d } second >>= 1 } return product }