From 3cf021c345f0ec55fc318fc7aa43e0eb77e099a5 Mon Sep 17 00:00:00 2001 From: Robert Galluccio Date: Sun, 15 Jun 2025 14:22:14 -0400 Subject: [PATCH] Added defensiveness to all calls to bitreader Within MsbBitReader, all calls to consuming bits come with a precondition check, which causes a crash if it fails. We did have some validation within the project for making sure enough bits were left before some calls; however, it was only in select places. This change makes all calls to DataReader throwing, with a check on whether enough bits are left before allowing them to be read. This may be a dent on performance; however, it is probably worth the extra protection against application crashes. This change is added because we found crashes in the wild due to badly formatted SCTE35 messages. These should be corrected; however, the parser should also be more defensive. --- .../ATSC/ATSCContentIdentifier.swift | 10 +- .../BitByteData/Sources/BitReader.swift | 32 +-- .../BitParser/DataBitReader.swift | 4 +- .../SCTE35Parser/BitParser/DataReader.swift | 262 ++++++++++++++---- .../SpliceCommands/PrivateCommand.swift | 4 +- .../SpliceCommands/SpliceCommand.swift | 2 +- .../SpliceCommands/SpliceInsert.swift | 26 +- .../SpliceCommands/SpliceSchedule.swift | 32 +-- .../SpliceDescriptors/AudioDescriptor.swift | 22 +- .../SpliceDescriptors/AvailDescriptor.swift | 6 +- .../SpliceDescriptors/DTMFDescriptor.swift | 12 +- ...mentationDescriptor+SegmentationUPID.swift | 44 +-- .../SegmentationDescriptor.swift | 64 ++--- .../SpliceDescriptors/SpliceDescriptor.swift | 2 +- .../SpliceDescriptors/TimeDescriptor.swift | 10 +- Sources/SCTE35Parser/SpliceInfoSection.swift | 30 +- Sources/SCTE35Parser/Time/BreakDuration.swift | 6 +- Sources/SCTE35Parser/Time/SpliceTime.swift | 8 +- .../SCTE35ParserTests/SCTE35ParserTests.swift | 17 ++ 19 files changed, 377 insertions(+), 216 deletions(-) diff --git a/Sources/SCTE35Parser/ATSC/ATSCContentIdentifier.swift b/Sources/SCTE35Parser/ATSC/ATSCContentIdentifier.swift index 24e51ee..2a78d84 100644 --- a/Sources/SCTE35Parser/ATSC/ATSCContentIdentifier.swift +++ b/Sources/SCTE35Parser/ATSC/ATSCContentIdentifier.swift @@ -73,10 +73,10 @@ extension ATSCContentIdentifier { InvalidATSCContentIdentifierInUPIDInfo(upidLength: Int(upidLength)) ) } - self.tsid = bitReader.uint16(fromBits: 16) - _ = bitReader.bits(count: 2) - self.endOfDay = bitReader.byte(fromBits: 5) - self.uniqueFor = bitReader.uint16(fromBits: 9) - self.contentID = bitReader.string(fromBytes: UInt(contentIDLength)) + self.tsid = try bitReader.uint16(fromBits: 16) + _ = try bitReader.bits(count: 2) + self.endOfDay = try bitReader.byte(fromBits: 5) + self.uniqueFor = try bitReader.uint16(fromBits: 9) + self.contentID = try bitReader.string(fromBytes: UInt(contentIDLength)) } } diff --git a/Sources/SCTE35Parser/BitParser/BitByteData/Sources/BitReader.swift b/Sources/SCTE35Parser/BitParser/BitByteData/Sources/BitReader.swift index 46a472b..5a066f1 100644 --- a/Sources/SCTE35Parser/BitParser/BitByteData/Sources/BitReader.swift +++ b/Sources/SCTE35Parser/BitParser/BitByteData/Sources/BitReader.swift @@ -27,25 +27,25 @@ public protocol BitReader: AnyObject { init(_ byteReader: ByteReader) /// Reads bit and returns it, advancing by one BIT position. - func bit() -> UInt8 + func bit() throws -> UInt8 /// Reads `count` bits and returns them as an array of `UInt8`, advancing by `count` BIT positions. - func bits(count: Int) -> [UInt8] + func bits(count: Int) throws -> [UInt8] /// Reads `fromBits` bits and returns them as an `Int` number, advancing by `fromBits` BIT positions. - func int(fromBits count: Int) -> Int + func int(fromBits count: Int) throws -> Int /// Reads `fromBits` bits and returns them as an `UInt8` number, advancing by `fromBits` BIT positions. - func byte(fromBits count: Int) -> UInt8 + func byte(fromBits count: Int) throws -> UInt8 /// Reads `fromBits` bits and returns them as an `UInt16` number, advancing by `fromBits` BIT positions. - func uint16(fromBits count: Int) -> UInt16 + func uint16(fromBits count: Int) throws -> UInt16 /// Reads `fromBits` bits and returns them as an `UInt32` number, advancing by `fromBits` BIT positions. - func uint32(fromBits count: Int) -> UInt32 + func uint32(fromBits count: Int) throws -> UInt32 /// Reads `fromBits` bits and returns them as an `UInt64` number, advancing by `fromBits` BIT positions. - func uint64(fromBits count: Int) -> UInt64 + func uint64(fromBits count: Int) throws -> UInt64 /// Aligns reader's BIT pointer to the BYTE border, i.e. moves BIT pointer to the first BIT of the next BYTE. func align() @@ -53,30 +53,30 @@ public protocol BitReader: AnyObject { // MARK: ByteReader's methods. /// Reads byte and returns it, advancing by one BYTE position. - func byte() -> UInt8 + func byte() throws -> UInt8 /// Reads `count` bytes and returns them as an array of `UInt8`, advancing by `count` BYTE positions. - func bytes(count: Int) -> [UInt8] + func bytes(count: Int) throws -> [UInt8] /// Reads `fromBytes` bytes and returns them as an `Int` number, advancing by `fromBytes` BYTE positions. - func int(fromBytes count: Int) -> Int + func int(fromBytes count: Int) throws -> Int /// Reads 8 bytes and returns them as a `UInt64` number, advancing by 8 BYTE positions. - func uint64() -> UInt64 + func uint64() throws -> UInt64 /// Reads `fromBytes` bytes and returns them as a `UInt64` number, advancing by 8 BYTE positions. - func uint64(fromBytes count: Int) -> UInt64 + func uint64(fromBytes count: Int) throws -> UInt64 /// Reads 4 bytes and returns them as a `UInt32` number, advancing by 4 BYTE positions. - func uint32() -> UInt32 + func uint32() throws -> UInt32 /// Reads `fromBytes` bytes and returns them as a `UInt32` number, advancing by 8 BYTE positions. - func uint32(fromBytes count: Int) -> UInt32 + func uint32(fromBytes count: Int) throws -> UInt32 /// Reads 2 bytes and returns them as a `UInt16` number, advancing by 2 BYTE positions. - func uint16() -> UInt16 + func uint16() throws -> UInt16 /// Reads `fromBytes` bytes and returns them as a `UInt16` number, advancing by 8 BYTE positions. - func uint16(fromBytes count: Int) -> UInt16 + func uint16(fromBytes count: Int) throws -> UInt16 } diff --git a/Sources/SCTE35Parser/BitParser/DataBitReader.swift b/Sources/SCTE35Parser/BitParser/DataBitReader.swift index de23268..b27e852 100644 --- a/Sources/SCTE35Parser/BitParser/DataBitReader.swift +++ b/Sources/SCTE35Parser/BitParser/DataBitReader.swift @@ -31,7 +31,7 @@ extension DataBitReader { } } - func string(fromBytes bytes: UInt) -> String { - return String(self.bytes(count: Int(bytes)).map { Character(UnicodeScalar($0)) }) + func string(fromBytes bytes: UInt) throws -> String { + return try String(self.bytes(count: Int(bytes)).map { Character(UnicodeScalar($0)) }) } } diff --git a/Sources/SCTE35Parser/BitParser/DataReader.swift b/Sources/SCTE35Parser/BitParser/DataReader.swift index afdda76..9fbe15c 100644 --- a/Sources/SCTE35Parser/BitParser/DataReader.swift +++ b/Sources/SCTE35Parser/BitParser/DataReader.swift @@ -1,6 +1,6 @@ // // DataReader.swift -// +// // // Created by Robert Galluccio on 01/05/2021. // @@ -10,93 +10,237 @@ import Foundation class DataReader: DataBitReader, Equatable { static func == (lhs: DataReader, rhs: DataReader) -> Bool { return lhs.nonFatalErrors == rhs.nonFatalErrors && - lhs.isAligned == rhs.isAligned && - lhs.bitsLeft == rhs.bitsLeft && - lhs.bitsRead == rhs.bitsRead && - lhs.msbBitReader.data == rhs.msbBitReader.data + lhs.isAligned == rhs.isAligned && + lhs.bitsLeft == rhs.bitsLeft && + lhs.bitsRead == rhs.bitsRead && + lhs.msbBitReader.data == rhs.msbBitReader.data } - + var nonFatalErrors = [ParserError]() var isAligned: Bool { msbBitReader.isAligned } var bitsLeft: Int { msbBitReader.bitsLeft } var bitsRead: Int { msbBitReader.bitsRead } - + private let msbBitReader: MsbBitReader - + required init(data: Data) { msbBitReader = MsbBitReader(data: data) } - + required init(_ byteReader: ByteReader) { msbBitReader = MsbBitReader(byteReader) } - - func bit() -> UInt8 { - msbBitReader.bit() + + func bit() throws -> UInt8 { + if msbBitReader.bitsLeft < 1 { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 1, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read bit" + ) + ) + } + return msbBitReader.bit() } - - func bits(count: Int) -> [UInt8] { - msbBitReader.bits(count: count) + + func bits(count: Int) throws -> [UInt8] { + if msbBitReader.bitsLeft < count { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read bits" + ) + ) + } + return msbBitReader.bits(count: count) } - - func int(fromBits count: Int) -> Int { - msbBitReader.int(fromBits: count) + + func int(fromBits count: Int) throws -> Int { + if msbBitReader.bitsLeft < count { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read Int" + ) + ) + } + return msbBitReader.int(fromBits: count) } - - func byte(fromBits count: Int) -> UInt8 { - msbBitReader.byte(fromBits: count) + + func byte(fromBits count: Int) throws -> UInt8 { + if msbBitReader.bitsLeft < count { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read byte" + ) + ) + } + return msbBitReader.byte(fromBits: count) } - - func uint16(fromBits count: Int) -> UInt16 { - msbBitReader.uint16(fromBits: count) + + func uint16(fromBits count: Int) throws -> UInt16 { + if msbBitReader.bitsLeft < count { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt16" + ) + ) + } + return msbBitReader.uint16(fromBits: count) } - - func uint32(fromBits count: Int) -> UInt32 { - msbBitReader.uint32(fromBits: count) + + func uint32(fromBits count: Int) throws -> UInt32 { + if msbBitReader.bitsLeft < count { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt32" + ) + ) + } + return msbBitReader.uint32(fromBits: count) } - - func uint64(fromBits count: Int) -> UInt64 { - msbBitReader.uint64(fromBits: count) + + func uint64(fromBits count: Int) throws -> UInt64 { + if msbBitReader.bitsLeft < count { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt64" + ) + ) + } + return msbBitReader.uint64(fromBits: count) } - + func align() { msbBitReader.align() } - - func byte() -> UInt8 { - msbBitReader.byte() + + func byte() throws -> UInt8 { + if msbBitReader.bitsLeft < 8 { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 8, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read byte" + ) + ) + } + return msbBitReader.byte() } - - func bytes(count: Int) -> [UInt8] { - msbBitReader.bytes(count: count) + + func bytes(count: Int) throws -> [UInt8] { + if msbBitReader.bitsLeft < (8 * count) { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 8 * count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read bytes" + ) + ) + } + return msbBitReader.bytes(count: count) } - - func int(fromBytes count: Int) -> Int { - msbBitReader.int(fromBytes: count) + + func int(fromBytes count: Int) throws -> Int { + if msbBitReader.bitsLeft < (8 * count) { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 8 * count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read Int" + ) + ) + } + return msbBitReader.int(fromBytes: count) } - - func uint64() -> UInt64 { - msbBitReader.uint64() + + func uint64() throws -> UInt64 { + if msbBitReader.bitsLeft < 64 { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 64, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt64" + ) + ) + } + return msbBitReader.uint64() } - - func uint64(fromBytes count: Int) -> UInt64 { - msbBitReader.uint64(fromBytes: count) + + func uint64(fromBytes count: Int) throws -> UInt64 { + if msbBitReader.bitsLeft < (8 * count) { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 8 * count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt64" + ) + ) + } + return msbBitReader.uint64(fromBytes: count) } - - func uint32() -> UInt32 { - msbBitReader.uint32() + + func uint32() throws -> UInt32 { + if msbBitReader.bitsLeft < 32 { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 32, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt32" + ) + ) + } + return msbBitReader.uint32() } - - func uint32(fromBytes count: Int) -> UInt32 { - msbBitReader.uint32(fromBytes: count) + + func uint32(fromBytes count: Int) throws -> UInt32 { + if msbBitReader.bitsLeft < (8 * count) { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 8 * count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt32" + ) + ) + } + return msbBitReader.uint32(fromBytes: count) } - - func uint16() -> UInt16 { - msbBitReader.uint16() + + func uint16() throws -> UInt16 { + if msbBitReader.bitsLeft < 16 { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 16, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt16" + ) + ) + } + return msbBitReader.uint16() } - - func uint16(fromBytes count: Int) -> UInt16 { - msbBitReader.uint16(fromBytes: count) + + func uint16(fromBytes count: Int) throws -> UInt16 { + if msbBitReader.bitsLeft < (8 * count) { + throw ParserError.unexpectedEndOfData( + UnexpectedEndOfDataErrorInfo( + expectedMinimumBitsLeft: 8 * count, + actualBitsLeft: msbBitReader.bitsLeft, + description: "Trying to read UInt16" + ) + ) + } + return msbBitReader.uint16(fromBytes: count) } - + } diff --git a/Sources/SCTE35Parser/SpliceCommands/PrivateCommand.swift b/Sources/SCTE35Parser/SpliceCommands/PrivateCommand.swift index 9e567de..c13188c 100644 --- a/Sources/SCTE35Parser/SpliceCommands/PrivateCommand.swift +++ b/Sources/SCTE35Parser/SpliceCommands/PrivateCommand.swift @@ -49,12 +49,12 @@ extension PrivateCommand { expectedMinimumBitsLeft: spliceCommandLength * 8, parseDescription: "PrivateCommand; validating spliceCommandLength" ) - self.identifier = bitReader.string(fromBytes: 4) + self.identifier = try bitReader.string(fromBytes: 4) var bytesLeft = spliceCommandLength - 4 var privateBytes = [UInt8]() while bytesLeft > 0 { bytesLeft -= 1 - privateBytes.append(bitReader.byte()) + try privateBytes.append(bitReader.byte()) } self.privateBytes = privateBytes } diff --git a/Sources/SCTE35Parser/SpliceCommands/SpliceCommand.swift b/Sources/SCTE35Parser/SpliceCommands/SpliceCommand.swift index 74c6869..d65f625 100644 --- a/Sources/SCTE35Parser/SpliceCommands/SpliceCommand.swift +++ b/Sources/SCTE35Parser/SpliceCommands/SpliceCommand.swift @@ -63,7 +63,7 @@ extension SpliceCommand { /// starts _after_ the `spliceCommandType`). /// - Throws: `ParserError` init(bitReader: DataBitReader, spliceCommandLength: Int) throws { - let spliceCommandTypeRawValue = bitReader.byte() + let spliceCommandTypeRawValue = try bitReader.byte() let bitsReadBeforeSpliceCommand = bitReader.bitsRead let expectedBitsReadAtEndOfSpliceCommand = bitReader.bitsRead + (spliceCommandLength * 8) guard let spliceCommandType = SpliceCommandType(rawValue: spliceCommandTypeRawValue) else { diff --git a/Sources/SCTE35Parser/SpliceCommands/SpliceInsert.swift b/Sources/SCTE35Parser/SpliceCommands/SpliceInsert.swift index ddc9aaa..4ec9adc 100644 --- a/Sources/SCTE35Parser/SpliceCommands/SpliceInsert.swift +++ b/Sources/SCTE35Parser/SpliceCommands/SpliceInsert.swift @@ -164,9 +164,9 @@ public extension SpliceInsert.ScheduledEvent.SpliceMode { extension SpliceInsert { init(bitReader: DataBitReader) throws { - self.eventId = bitReader.uint32(fromBits: 32) - let isSpliceEventCancelled = bitReader.bit() == 1 - _ = bitReader.bits(count: 7) + self.eventId = try bitReader.uint32(fromBits: 32) + let isSpliceEventCancelled = try bitReader.bit() == 1 + _ = try bitReader.bits(count: 7) if isSpliceEventCancelled { self.scheduledEvent = nil } else { @@ -177,12 +177,12 @@ extension SpliceInsert { private extension SpliceInsert.ScheduledEvent { init(bitReader: DataBitReader) throws { - self.outOfNetworkIndicator = bitReader.bit() == 1 - let programSpliceFlag = bitReader.bit() == 1 - let durationFlag = bitReader.bit() == 1 - let spliceImmediateFlag = bitReader.bit() == 1 + self.outOfNetworkIndicator = try bitReader.bit() == 1 + let programSpliceFlag = try bitReader.bit() == 1 + let durationFlag = try bitReader.bit() == 1 + let spliceImmediateFlag = try bitReader.bit() == 1 self.isImmediateSplice = spliceImmediateFlag - _ = bitReader.bits(count: 4) + _ = try bitReader.bits(count: 4) if programSpliceFlag { self.spliceMode = .programSpliceMode( SpliceMode.ProgramMode( @@ -190,10 +190,10 @@ private extension SpliceInsert.ScheduledEvent { ) ) } else { - let componentCount = bitReader.byte() + let componentCount = try bitReader.byte() self.spliceMode = try .componentSpliceMode( (0.. String { + func read(using bitReader: DataBitReader) throws -> String { let checkIndices: [Int] let indexMax: Int switch version { @@ -254,12 +254,12 @@ private struct HyphenSeparatedCheckedHex { checkIndices = [5] indexMax = 5 } - return (0...indexMax) + return try (0...indexMax) .reduce(into: [String]()) { isan, index in if checkIndices.contains(index) { isan.append(checkChar(for: isan)) } else { - isan.append(String(format: "%04X", bitReader.uint16(fromBits: 16))) + isan.append(String(format: "%04X", try bitReader.uint16(fromBits: 16))) } } .joined(separator: "-") diff --git a/Sources/SCTE35Parser/SpliceDescriptors/SegmentationDescriptor.swift b/Sources/SCTE35Parser/SpliceDescriptors/SegmentationDescriptor.swift index e5c97da..012fd48 100644 --- a/Sources/SCTE35Parser/SpliceDescriptors/SegmentationDescriptor.swift +++ b/Sources/SCTE35Parser/SpliceDescriptors/SegmentationDescriptor.swift @@ -248,17 +248,17 @@ public extension SegmentationDescriptor.ScheduledEvent { extension SegmentationDescriptor { // NOTE: It is assumed that the splice_descriptor_tag has already been read. init(bitReader: DataBitReader) throws { - let descriptorLength = bitReader.int(fromBytes: 1) + let descriptorLength = try bitReader.int(fromBytes: 1) let bitsReadBeforeDescriptor = bitReader.bitsRead let expectedBitsReadAtEndOfDescriptor = bitReader.bitsRead + descriptorLength * 8 let bitsLeftAfterDescriptor = bitReader.bitsLeft - (descriptorLength * 8) - self.identifier = bitReader.uint32(fromBits: 32) + self.identifier = try bitReader.uint32(fromBits: 32) guard self.identifier == 1129661769 else { throw ParserError.invalidSegmentationDescriptorIdentifier(Int(self.identifier)) } - self.eventId = bitReader.uint32(fromBits: 32) - let segmentationEventCancelled = bitReader.bit() == 1 - _ = bitReader.bits(count: 7) + self.eventId = try bitReader.uint32(fromBits: 32) + let segmentationEventCancelled = try bitReader.bit() == 1 + _ = try bitReader.bits(count: 7) if segmentationEventCancelled { self.scheduledEvent = nil } else { @@ -280,28 +280,28 @@ extension SegmentationDescriptor { extension SegmentationDescriptor.ScheduledEvent { init(bitReader: DataBitReader, bitsLeftAfterDescriptor: Int) throws { - let programSegmentationFlag = bitReader.bit() == 1 - let segmentationDurationFlag = bitReader.bit() == 1 - let deliveryNotRestrictedFlag = bitReader.bit() == 1 - self.deliveryRestrictions = Self.deliveryRestrictions(bitReader, deliveryNotRestrictedFlag: deliveryNotRestrictedFlag) - self.componentSegments = Self.componentSegments(bitReader, programSegmentationFlag: programSegmentationFlag) - self.segmentationDuration = Self.segmentationDuration(bitReader, segmentationDurationFlag: segmentationDurationFlag) + let programSegmentationFlag = try bitReader.bit() == 1 + let segmentationDurationFlag = try bitReader.bit() == 1 + let deliveryNotRestrictedFlag = try bitReader.bit() == 1 + self.deliveryRestrictions = try Self.deliveryRestrictions(bitReader, deliveryNotRestrictedFlag: deliveryNotRestrictedFlag) + self.componentSegments = try Self.componentSegments(bitReader, programSegmentationFlag: programSegmentationFlag) + self.segmentationDuration = try Self.segmentationDuration(bitReader, segmentationDurationFlag: segmentationDurationFlag) self.segmentationUPID = try SegmentationDescriptor.SegmentationUPID(bitReader: bitReader) self.segmentationTypeID = try Self.segmentationTypeID(bitReader) - self.segmentNum = bitReader.byte() - self.segmentsExpected = bitReader.byte() - self.subSegment = Self.subSegment(bitReader, segmentationTypeID: segmentationTypeID, bitsLeftAfterDescriptor: bitsLeftAfterDescriptor) + self.segmentNum = try bitReader.byte() + self.segmentsExpected = try bitReader.byte() + self.subSegment = try Self.subSegment(bitReader, segmentationTypeID: segmentationTypeID, bitsLeftAfterDescriptor: bitsLeftAfterDescriptor) } - private static func deliveryRestrictions(_ bitReader: DataBitReader, deliveryNotRestrictedFlag: Bool) -> DeliveryRestrictions? { + private static func deliveryRestrictions(_ bitReader: DataBitReader, deliveryNotRestrictedFlag: Bool) throws -> DeliveryRestrictions? { if deliveryNotRestrictedFlag { - _ = bitReader.bits(count: 5) + _ = try bitReader.bits(count: 5) return nil } else { - let webDeliveryAllowedFlag = bitReader.bit() == 1 - let noRegionalBlackoutFlag = bitReader.bit() == 1 - let archiveAllowedFlag = bitReader.bit() == 1 - let deviceRestrictions = DeliveryRestrictions.DeviceRestrictions(rawValue: bitReader.int(fromBits: 2)) ?? .none + let webDeliveryAllowedFlag = try bitReader.bit() == 1 + let noRegionalBlackoutFlag = try bitReader.bit() == 1 + let archiveAllowedFlag = try bitReader.bit() == 1 + let deviceRestrictions = DeliveryRestrictions.DeviceRestrictions(rawValue: try bitReader.int(fromBits: 2)) ?? .none return DeliveryRestrictions( webDeliveryAllowed: webDeliveryAllowedFlag, noRegionalBlackout: noRegionalBlackoutFlag, @@ -311,30 +311,30 @@ extension SegmentationDescriptor.ScheduledEvent { } } - private static func componentSegments(_ bitReader: DataBitReader, programSegmentationFlag: Bool) -> [ComponentSegmentation]? { + private static func componentSegments(_ bitReader: DataBitReader, programSegmentationFlag: Bool) throws -> [ComponentSegmentation]? { if programSegmentationFlag { return nil } else { - let componentCount = bitReader.byte() - return (0.. UInt64? { + private static func segmentationDuration(_ bitReader: DataBitReader, segmentationDurationFlag: Bool) throws -> UInt64? { if segmentationDurationFlag { - return bitReader.uint64(fromBits: 40) + return try bitReader.uint64(fromBits: 40) } else { return nil } } private static func segmentationTypeID(_ bitReader: DataBitReader) throws -> SegmentationDescriptor.SegmentationTypeID { - let segmentationTypeIDRawValue = bitReader.byte() + let segmentationTypeIDRawValue = try bitReader.byte() guard let segmentationTypeID = SegmentationDescriptor.SegmentationTypeID(rawValue: segmentationTypeIDRawValue) else { throw ParserError.unrecognisedSegmentationTypeID(Int(segmentationTypeIDRawValue)) } @@ -345,7 +345,7 @@ extension SegmentationDescriptor.ScheduledEvent { _ bitReader: DataBitReader, segmentationTypeID: SegmentationDescriptor.SegmentationTypeID, bitsLeftAfterDescriptor: Int - ) -> SubSegment? { + ) throws -> SubSegment? { guard bitsLeftAfterDescriptor <= (bitReader.bitsLeft - 16) else { return nil } @@ -354,8 +354,8 @@ extension SegmentationDescriptor.ScheduledEvent { let isPOPO = segmentationTypeID == .providerOverlayPlacementOpportunityStart let isDOPO = segmentationTypeID == .distributorOverlayPlacementOpportunityStart if isPPO || isDPO || isPOPO || isDOPO { - let subSegmentNum = bitReader.byte() - let subSegmentsExpected = bitReader.byte() + let subSegmentNum = try bitReader.byte() + let subSegmentsExpected = try bitReader.byte() return SubSegment( subSegmentNum: subSegmentNum, subSegmentsExpected: subSegmentsExpected diff --git a/Sources/SCTE35Parser/SpliceDescriptors/SpliceDescriptor.swift b/Sources/SCTE35Parser/SpliceDescriptors/SpliceDescriptor.swift index cc46fa4..58c32de 100644 --- a/Sources/SCTE35Parser/SpliceDescriptors/SpliceDescriptor.swift +++ b/Sources/SCTE35Parser/SpliceDescriptors/SpliceDescriptor.swift @@ -124,7 +124,7 @@ extension Array where Element == SpliceDescriptor { extension SpliceDescriptor { init(bitReader: DataBitReader) throws { - let spliceDescriptorTag = bitReader.byte() + let spliceDescriptorTag = try bitReader.byte() switch SpliceDescriptorTag(rawValue: spliceDescriptorTag) { case .audioDescriptor: self = try .audioDescriptor(AudioDescriptor(bitReader: bitReader)) diff --git a/Sources/SCTE35Parser/SpliceDescriptors/TimeDescriptor.swift b/Sources/SCTE35Parser/SpliceDescriptors/TimeDescriptor.swift index 1e381ed..26c6776 100644 --- a/Sources/SCTE35Parser/SpliceDescriptors/TimeDescriptor.swift +++ b/Sources/SCTE35Parser/SpliceDescriptors/TimeDescriptor.swift @@ -76,13 +76,13 @@ public struct TimeDescriptor: Equatable { extension TimeDescriptor { // NOTE: It is assumed that the splice_descriptor_tag has already been read. init(bitReader: DataBitReader) throws { - let descriptorLength = bitReader.byte() + let descriptorLength = try bitReader.byte() let bitsReadBeforeDescriptor = bitReader.bitsRead let expectedBitsReadAtEndOfDescriptor = bitReader.bitsRead + (Int(descriptorLength) * 8) - self.identifier = bitReader.uint32(fromBits: 32) - self.taiSeconds = bitReader.uint64(fromBits: 48) - self.taiNS = bitReader.uint32(fromBits: 32) - self.utcOffset = bitReader.uint16(fromBits: 16) + self.identifier = try bitReader.uint32(fromBits: 32) + self.taiSeconds = try bitReader.uint64(fromBits: 48) + self.taiNS = try bitReader.uint32(fromBits: 32) + self.utcOffset = try bitReader.uint16(fromBits: 16) if bitReader.bitsRead != expectedBitsReadAtEndOfDescriptor { bitReader.nonFatalErrors.append( .unexpectedSpliceDescriptorLength( diff --git a/Sources/SCTE35Parser/SpliceInfoSection.swift b/Sources/SCTE35Parser/SpliceInfoSection.swift index c116a7c..4483ca6 100644 --- a/Sources/SCTE35Parser/SpliceInfoSection.swift +++ b/Sources/SCTE35Parser/SpliceInfoSection.swift @@ -271,37 +271,37 @@ public extension SpliceInfoSection { expectedMinimumBitsLeft: 24, parseDescription: "SpliceInfoSection; need at least 24 bits to get to end of section_length field" ) - self.tableID = bitReader.byte() - guard bitReader.bit() == 0 else { throw ParserError.invalidSectionSyntaxIndicator } - guard bitReader.bit() == 0 else { throw ParserError.invalidPrivateIndicator } - self.sapType = SAPType(rawValue: bitReader.byte(fromBits: 2)) ?? .unspecified - let sectionLengthInBytes = bitReader.int(fromBits: 12) + self.tableID = try bitReader.byte() + guard try bitReader.bit() == 0 else { throw ParserError.invalidSectionSyntaxIndicator } + guard try bitReader.bit() == 0 else { throw ParserError.invalidPrivateIndicator } + self.sapType = SAPType(rawValue: try bitReader.byte(fromBits: 2)) ?? .unspecified + let sectionLengthInBytes = try bitReader.int(fromBits: 12) try bitReader.validate( expectedMinimumBitsLeft: sectionLengthInBytes * 8, parseDescription: "SpliceInfoSection; section_length defined as \(sectionLengthInBytes)" ) - self.protocolVersion = bitReader.byte() - let isEncrypted = bitReader.bit() == 1 + self.protocolVersion = try bitReader.byte() + let isEncrypted = try bitReader.bit() == 1 if isEncrypted { throw ParserError.encryptedMessageNotSupported } - let _ /* encryptionAlgorithm */ = EncryptedPacket.EncryptionAlgorithm(bitReader.byte(fromBits: 6)) - self.ptsAdjustment = bitReader.uint64(fromBits: 33) - let _ /* cwIndex */ = bitReader.byte() - self.tier = bitReader.uint16(fromBits: 12) - let spliceCommandLength = bitReader.int(fromBits: 12) + let _ /* encryptionAlgorithm */ = EncryptedPacket.EncryptionAlgorithm(try bitReader.byte(fromBits: 6)) + self.ptsAdjustment = try bitReader.uint64(fromBits: 33) + let _ /* cwIndex */ = try bitReader.byte() + self.tier = try bitReader.uint16(fromBits: 12) + let spliceCommandLength = try bitReader.int(fromBits: 12) self.spliceCommand = try SpliceCommand(bitReader: bitReader, spliceCommandLength: spliceCommandLength) - let descriptorLoopLength = bitReader.int(fromBits: 16) + let descriptorLoopLength = try bitReader.int(fromBits: 16) self.spliceDescriptors = try [SpliceDescriptor].init(bitReader: bitReader, descriptorLoopLength: descriptorLoopLength) if isEncrypted { throw ParserError.encryptedMessageNotSupported } else { self.encryptedPacket = nil while bitReader.bitsLeft >= 40 { - _ = bitReader.byte() + _ = try bitReader.byte() } } - self.CRC_32 = bitReader.uint32(fromBits: 32) + self.CRC_32 = try bitReader.uint32(fromBits: 32) self.nonFatalErrors = bitReader.nonFatalErrors } catch { guard let parserError = error as? ParserError else { throw error } diff --git a/Sources/SCTE35Parser/Time/BreakDuration.swift b/Sources/SCTE35Parser/Time/BreakDuration.swift index e6cd976..37ef3ce 100644 --- a/Sources/SCTE35Parser/Time/BreakDuration.swift +++ b/Sources/SCTE35Parser/Time/BreakDuration.swift @@ -48,8 +48,8 @@ extension BreakDuration { expectedMinimumBitsLeft: 40, parseDescription: "BreakDuration" ) - autoReturn = bitReader.bit() == 1 - _ = bitReader.bits(count: 6) - duration = bitReader.uint64(fromBits: 33) + autoReturn = try bitReader.bit() == 1 + _ = try bitReader.bits(count: 6) + duration = try bitReader.uint64(fromBits: 33) } } diff --git a/Sources/SCTE35Parser/Time/SpliceTime.swift b/Sources/SCTE35Parser/Time/SpliceTime.swift index 7522913..21b2c6f 100644 --- a/Sources/SCTE35Parser/Time/SpliceTime.swift +++ b/Sources/SCTE35Parser/Time/SpliceTime.swift @@ -38,20 +38,20 @@ extension SpliceTime { expectedMinimumBitsLeft: 1, parseDescription: "SpliceTime; reading timeSpecifiedFlag" ) - let timeSpecifiedFlag = bitReader.bit() == 1 + let timeSpecifiedFlag = try bitReader.bit() == 1 if timeSpecifiedFlag { try bitReader.validate( expectedMinimumBitsLeft: 39, parseDescription: "SpliceTime; timeSpecifiedFlag == 1" ) - _ = bitReader.bits(count: 6) - ptsTime = bitReader.uint64(fromBits: 33) + _ = try bitReader.bits(count: 6) + ptsTime = try bitReader.uint64(fromBits: 33) } else { try bitReader.validate( expectedMinimumBitsLeft: 7, parseDescription: "SpliceTime; timeSpecifiedFlag == 0" ) - _ = bitReader.bits(count: 7) + _ = try bitReader.bits(count: 7) ptsTime = nil } } diff --git a/Tests/SCTE35ParserTests/SCTE35ParserTests.swift b/Tests/SCTE35ParserTests/SCTE35ParserTests.swift index 4b38cc6..3c9e7b5 100644 --- a/Tests/SCTE35ParserTests/SCTE35ParserTests.swift +++ b/Tests/SCTE35ParserTests/SCTE35ParserTests.swift @@ -2,6 +2,23 @@ import XCTest import SCTE35Parser final class SCTE35ParserTests: XCTestCase { + func test_invalid_avail_descriptor_should_throw() throws { + // This test has been added as we found crashing in the wild due to badly formatted SCTE35 messages and no + // defensiveness on checking that enough bits were left to read. + let hexString = "0xfc306000000000000000fff00506ff0549ee4f004a022243554549000000687fff00012064200e0e736b796e6577735f6c696e656172220000022443554549000000687fff00012064200e0e736b796e6577735f6c696e6561723000000000102e7fb8" + XCTAssertThrowsError(try SpliceInfoSection(hexString: hexString)) { error in + guard let error = error as? SCTE35ParserError else { + return XCTFail("Unexpected error type") + } + XCTAssertEqual("Unexpected end of data during parsing.", error.errorDescription) + switch error.error { + case .unexpectedEndOfData(let unexpectedEndOfDataErrorInfo): + XCTAssertEqual(unexpectedEndOfDataErrorInfo.description, "Trying to read UInt32") + default: XCTFail("Unexpected error case") + } + } + } + // MARK: - SCTE-35 2020 - 14. Sample SCTE 35 Messages (Informative) // 14.1. time_signal – Placement Opportunity Start