HaishinKit.swift/Sources/RTMP/AMF0Serializer.swift

407 lines
11 KiB
Swift

import Foundation
final class AMFSerializerUtil {
private static var classes: [String: AnyClass] = [:]
static func getClassByAlias(_ name: String) -> AnyClass? {
objc_sync_enter(classes)
let clazz: AnyClass? = classes[name]
objc_sync_exit(classes)
return clazz
}
static func registerClassAlias(_ name: String, clazz: AnyClass) {
objc_sync_enter(classes)
classes[name] = clazz
objc_sync_exit(classes)
}
}
enum AMFSerializerError: Error {
case deserialize
case outOfIndex
}
// MARK: -
protocol AMFSerializer: ByteArrayConvertible {
var reference: AMFReference { get set }
@discardableResult
func serialize(_ value: Bool) -> Self
func deserialize() throws -> Bool
@discardableResult
func serialize(_ value: String) -> Self
func deserialize() throws -> String
@discardableResult
func serialize(_ value: Int) -> Self
func deserialize() throws -> Int
@discardableResult
func serialize(_ value: Double) -> Self
func deserialize() throws -> Double
@discardableResult
func serialize(_ value: Date) -> Self
func deserialize() throws -> Date
@discardableResult
func serialize(_ value: [Any?]) -> Self
func deserialize() throws -> [Any?]
@discardableResult
func serialize(_ value: ASArray) -> Self
func deserialize() throws -> ASArray
@discardableResult
func serialize(_ value: ASObject) -> Self
func deserialize() throws -> ASObject
@discardableResult
func serialize(_ value: ASXMLDocument) -> Self
func deserialize() throws -> ASXMLDocument
@discardableResult
func serialize(_ value: Any?) -> Self
func deserialize() throws -> Any?
}
// MARK: -
/**
AMF0Serializer
-seealso: http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/amf/pdf/amf0-file-format-specification.pdf
*/
final class AMF0Serializer: ByteArray {
enum `Type`: UInt8 {
case number = 0x00
case bool = 0x01
case string = 0x02
case object = 0x03
// case MovieClip = 0x04
case null = 0x05
case undefined = 0x06
case reference = 0x07
case ecmaArray = 0x08
case objectEnd = 0x09
case strictArray = 0x0a
case date = 0x0b
case longString = 0x0c
case unsupported = 0x0d
// case RecordSet = 0x0e
case xmlDocument = 0x0f
case typedObject = 0x10
case avmplush = 0x11
}
var reference = AMFReference()
}
extension AMF0Serializer: AMFSerializer {
// MARK: AMFSerializer
@discardableResult
func serialize(_ value: Any?) -> Self {
if value == nil {
return writeUInt8(Type.null.rawValue)
}
switch value {
case let value as Int:
return serialize(Double(value))
case let value as UInt:
return serialize(Double(value))
case let value as Int8:
return serialize(Double(value))
case let value as UInt8:
return serialize(Double(value))
case let value as Int16:
return serialize(Double(value))
case let value as UInt16:
return serialize(Double(value))
case let value as Int32:
return serialize(Double(value))
case let value as UInt32:
return serialize(Double(value))
case let value as Float:
return serialize(Double(value))
case let value as Double:
return serialize(Double(value))
case let value as Date:
return serialize(value)
case let value as String:
return serialize(value)
case let value as Bool:
return serialize(value)
case let value as ASArray:
return serialize(value)
case let value as ASObject:
return serialize(value)
default:
return writeUInt8(Type.undefined.rawValue)
}
}
func deserialize() throws -> Any? {
guard let type = Type(rawValue: try readUInt8()) else {
return nil
}
position -= 1
switch type {
case .number:
return try deserialize() as Double
case .bool:
return try deserialize() as Bool
case .string:
return try deserialize() as String
case .object:
return try deserialize() as ASObject
case .null:
position += 1
return nil
case .undefined:
position += 1
return kASUndefined
case .reference:
assertionFailure("TODO")
return nil
case .ecmaArray:
return try deserialize() as ASArray
case .objectEnd:
assertionFailure()
return nil
case .strictArray:
return try deserialize() as [Any?]
case .date:
return try deserialize() as Date
case .longString:
return try deserialize() as String
case .unsupported:
assertionFailure("Unsupported")
return nil
case .xmlDocument:
return try deserialize() as ASXMLDocument
case .typedObject:
return try deserialize() as Any
case .avmplush:
assertionFailure("TODO")
return nil
}
}
/**
* @see 2.2 Number Type
*/
func serialize(_ value: Double) -> Self {
writeUInt8(Type.number.rawValue).writeDouble(value)
}
func deserialize() throws -> Double {
guard try readUInt8() == Type.number.rawValue else {
throw AMFSerializerError.deserialize
}
return try readDouble()
}
func serialize(_ value: Int) -> Self {
serialize(Double(value))
}
func deserialize() throws -> Int {
Int(try deserialize() as Double)
}
/**
* @see 2.3 Boolean Type
*/
func serialize(_ value: Bool) -> Self {
writeBytes(Data([Type.bool.rawValue, value ? 0x01 : 0x00]))
}
func deserialize() throws -> Bool {
guard try readUInt8() == Type.bool.rawValue else {
throw AMFSerializerError.deserialize
}
return try readUInt8() == 0x01 ? true : false
}
/**
* @see 2.4 String Type
*/
func serialize(_ value: String) -> Self {
let isLong: Bool = UInt32(UInt16.max) < UInt32(value.count)
writeUInt8(isLong ? Type.longString.rawValue : Type.string.rawValue)
return serializeUTF8(value, isLong)
}
func deserialize() throws -> String {
switch try readUInt8() {
case Type.string.rawValue:
return try deserializeUTF8(false)
case Type.longString.rawValue:
return try deserializeUTF8(true)
default:
assertionFailure()
return ""
}
}
/**
* 2.5 Object Type
* typealias ECMAObject = Dictionary<String, Any?>
*/
func serialize(_ value: ASObject) -> Self {
writeUInt8(Type.object.rawValue)
for (key, data) in value {
serializeUTF8(key, false).serialize(data)
}
return serializeUTF8("", false).writeUInt8(Type.objectEnd.rawValue)
}
func deserialize() throws -> ASObject {
var result = ASObject()
switch try readUInt8() {
case Type.null.rawValue:
return result
case Type.object.rawValue:
break
default:
throw AMFSerializerError.deserialize
}
while true {
let key: String = try deserializeUTF8(false)
guard !key.isEmpty else {
position += 1
break
}
result[key] = try deserialize()
}
return result
}
/**
* @see 2.10 ECMA Array Type
*/
func serialize(_ value: ASArray) -> Self {
self
}
func deserialize() throws -> ASArray {
switch try readUInt8() {
case Type.null.rawValue:
return ASArray()
case Type.ecmaArray.rawValue:
break
default:
throw AMFSerializerError.deserialize
}
var result = ASArray(count: Int(try readUInt32()))
while true {
let key: String = try deserializeUTF8(false)
guard !key.isEmpty else {
position += 1
break
}
result[key] = try deserialize()
}
return result
}
/**
* @see 2.12 Strict Array Type
*/
func serialize(_ value: [Any?]) -> Self {
writeUInt8(Type.strictArray.rawValue)
if value.isEmpty {
writeBytes(Data([0x00, 0x00, 0x00, 0x00]))
return self
}
writeUInt32(UInt32(value.count))
for v in value {
serialize(v)
}
return self
}
func deserialize() throws -> [Any?] {
guard try readUInt8() == Type.strictArray.rawValue else {
throw AMFSerializerError.deserialize
}
var result: [Any?] = []
let count = Int(try readUInt32())
for _ in 0..<count {
result.append(try deserialize())
}
return result
}
/**
* @see 2.13 Date Type
*/
func serialize(_ value: Date) -> Self {
writeUInt8(Type.date.rawValue).writeDouble(value.timeIntervalSince1970 * 1000).writeBytes(Data([0x00, 0x00]))
}
func deserialize() throws -> Date {
guard try readUInt8() == Type.date.rawValue else {
throw AMFSerializerError.deserialize
}
let date = Date(timeIntervalSince1970: try readDouble() / 1000)
position += 2 // timezone offset
return date
}
/**
* @see 2.17 XML Document Type
*/
func serialize(_ value: ASXMLDocument) -> Self {
writeUInt8(Type.xmlDocument.rawValue).serializeUTF8(value.description, true)
}
func deserialize() throws -> ASXMLDocument {
guard try readUInt8() == Type.xmlDocument.rawValue else {
throw AMFSerializerError.deserialize
}
return ASXMLDocument(data: try deserializeUTF8(true))
}
func deserialize() throws -> Any {
guard try readUInt8() == Type.typedObject.rawValue else {
throw AMFSerializerError.deserialize
}
let typeName = try deserializeUTF8(false)
var result = ASObject()
while true {
let key: String = try deserializeUTF8(false)
guard !key.isEmpty else {
position += 1
break
}
result[key] = try deserialize()
}
return try ASTypedObject.decode(typeName: typeName, data: result)
}
@discardableResult
private func serializeUTF8(_ value: String, _ isLong: Bool) -> Self {
let utf8 = Data(value.utf8)
if isLong {
writeUInt32(UInt32(utf8.count))
} else {
writeUInt16(UInt16(utf8.count))
}
return writeBytes(utf8)
}
private func deserializeUTF8(_ isLong: Bool) throws -> String {
let length: Int = isLong ? Int(try readUInt32()) : Int(try readUInt16())
return try readUTF8Bytes(length)
}
}