From 8f443a38af7ad62809acbc0df4e3ba048777878d Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 9 Nov 2020 08:36:33 +1100 Subject: [PATCH] Re-implement message wrapping --- .../Protos/Generated/SNProto.swift | 2 - .../Protos/Generated/WebSocketProto.swift | 486 ++++++++++++++++++ .../Generated/WebSocketResources.pb.swift | 378 ++++++++++++++ SessionMessagingKit/Protos/Makefile | 8 +- .../Protos/WebSocketResources.proto | 49 ++ .../Sending & Receiving/MessageReceiver.swift | 2 - .../Sending & Receiving/MessageSender.swift | 34 +- .../Utilities/MessageWrapper.swift | 74 +++ Signal.xcodeproj/project.pbxproj | 12 + 9 files changed, 1035 insertions(+), 10 deletions(-) create mode 100644 SessionMessagingKit/Protos/Generated/WebSocketProto.swift create mode 100644 SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift create mode 100644 SessionMessagingKit/Protos/WebSocketResources.proto create mode 100644 SessionMessagingKit/Utilities/MessageWrapper.swift diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 68afedae4..4eb8e8231 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -6,8 +6,6 @@ import Foundation // WARNING: This code is generated. Only edit within the markers. -private let logTag = "SNProto" - public enum SNProtoError: Error { case invalidProtobuf(description: String) } diff --git a/SessionMessagingKit/Protos/Generated/WebSocketProto.swift b/SessionMessagingKit/Protos/Generated/WebSocketProto.swift new file mode 100644 index 000000000..c886ccc95 --- /dev/null +++ b/SessionMessagingKit/Protos/Generated/WebSocketProto.swift @@ -0,0 +1,486 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +// WARNING: This code is generated. Only edit within the markers. + +public enum WebSocketProtoError: Error { + case invalidProtobuf(description: String) +} + +// MARK: - WebSocketProtoWebSocketRequestMessage + +@objc public class WebSocketProtoWebSocketRequestMessage: NSObject { + + // MARK: - WebSocketProtoWebSocketRequestMessageBuilder + + @objc public class func builder(verb: String, path: String, requestID: UInt64) -> WebSocketProtoWebSocketRequestMessageBuilder { + return WebSocketProtoWebSocketRequestMessageBuilder(verb: verb, path: path, requestID: requestID) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> WebSocketProtoWebSocketRequestMessageBuilder { + let builder = WebSocketProtoWebSocketRequestMessageBuilder(verb: verb, path: path, requestID: requestID) + if let _value = body { + builder.setBody(_value) + } + builder.setHeaders(headers) + return builder + } + + @objc public class WebSocketProtoWebSocketRequestMessageBuilder: NSObject { + + private var proto = WebSocketProtos_WebSocketRequestMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(verb: String, path: String, requestID: UInt64) { + super.init() + + setVerb(verb) + setPath(path) + setRequestID(requestID) + } + + @objc public func setVerb(_ valueParam: String) { + proto.verb = valueParam + } + + @objc public func setPath(_ valueParam: String) { + proto.path = valueParam + } + + @objc public func setBody(_ valueParam: Data) { + proto.body = valueParam + } + + @objc public func addHeaders(_ valueParam: String) { + var items = proto.headers + items.append(valueParam) + proto.headers = items + } + + @objc public func setHeaders(_ wrappedItems: [String]) { + proto.headers = wrappedItems + } + + @objc public func setRequestID(_ valueParam: UInt64) { + proto.requestID = valueParam + } + + @objc public func build() throws -> WebSocketProtoWebSocketRequestMessage { + return try WebSocketProtoWebSocketRequestMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try WebSocketProtoWebSocketRequestMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: WebSocketProtos_WebSocketRequestMessage + + @objc public let verb: String + + @objc public let path: String + + @objc public let requestID: UInt64 + + @objc public var body: Data? { + guard proto.hasBody else { + return nil + } + return proto.body + } + @objc public var hasBody: Bool { + return proto.hasBody + } + + @objc public var headers: [String] { + return proto.headers + } + + private init(proto: WebSocketProtos_WebSocketRequestMessage, + verb: String, + path: String, + requestID: UInt64) { + self.proto = proto + self.verb = verb + self.path = path + self.requestID = requestID + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> WebSocketProtoWebSocketRequestMessage { + let proto = try WebSocketProtos_WebSocketRequestMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketRequestMessage) throws -> WebSocketProtoWebSocketRequestMessage { + guard proto.hasVerb else { + throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: verb") + } + let verb = proto.verb + + guard proto.hasPath else { + throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: path") + } + let path = proto.path + + guard proto.hasRequestID else { + throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID") + } + let requestID = proto.requestID + + // MARK: - Begin Validation Logic for WebSocketProtoWebSocketRequestMessage - + + // MARK: - End Validation Logic for WebSocketProtoWebSocketRequestMessage - + + let result = WebSocketProtoWebSocketRequestMessage(proto: proto, + verb: verb, + path: path, + requestID: requestID) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension WebSocketProtoWebSocketRequestMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension WebSocketProtoWebSocketRequestMessage.WebSocketProtoWebSocketRequestMessageBuilder { + @objc public func buildIgnoringErrors() -> WebSocketProtoWebSocketRequestMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - WebSocketProtoWebSocketResponseMessage + +@objc public class WebSocketProtoWebSocketResponseMessage: NSObject { + + // MARK: - WebSocketProtoWebSocketResponseMessageBuilder + + @objc public class func builder(requestID: UInt64, status: UInt32) -> WebSocketProtoWebSocketResponseMessageBuilder { + return WebSocketProtoWebSocketResponseMessageBuilder(requestID: requestID, status: status) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> WebSocketProtoWebSocketResponseMessageBuilder { + let builder = WebSocketProtoWebSocketResponseMessageBuilder(requestID: requestID, status: status) + if let _value = message { + builder.setMessage(_value) + } + builder.setHeaders(headers) + if let _value = body { + builder.setBody(_value) + } + return builder + } + + @objc public class WebSocketProtoWebSocketResponseMessageBuilder: NSObject { + + private var proto = WebSocketProtos_WebSocketResponseMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(requestID: UInt64, status: UInt32) { + super.init() + + setRequestID(requestID) + setStatus(status) + } + + @objc public func setRequestID(_ valueParam: UInt64) { + proto.requestID = valueParam + } + + @objc public func setStatus(_ valueParam: UInt32) { + proto.status = valueParam + } + + @objc public func setMessage(_ valueParam: String) { + proto.message = valueParam + } + + @objc public func addHeaders(_ valueParam: String) { + var items = proto.headers + items.append(valueParam) + proto.headers = items + } + + @objc public func setHeaders(_ wrappedItems: [String]) { + proto.headers = wrappedItems + } + + @objc public func setBody(_ valueParam: Data) { + proto.body = valueParam + } + + @objc public func build() throws -> WebSocketProtoWebSocketResponseMessage { + return try WebSocketProtoWebSocketResponseMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try WebSocketProtoWebSocketResponseMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: WebSocketProtos_WebSocketResponseMessage + + @objc public let requestID: UInt64 + + @objc public let status: UInt32 + + @objc public var message: String? { + guard proto.hasMessage else { + return nil + } + return proto.message + } + @objc public var hasMessage: Bool { + return proto.hasMessage + } + + @objc public var headers: [String] { + return proto.headers + } + + @objc public var body: Data? { + guard proto.hasBody else { + return nil + } + return proto.body + } + @objc public var hasBody: Bool { + return proto.hasBody + } + + private init(proto: WebSocketProtos_WebSocketResponseMessage, + requestID: UInt64, + status: UInt32) { + self.proto = proto + self.requestID = requestID + self.status = status + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> WebSocketProtoWebSocketResponseMessage { + let proto = try WebSocketProtos_WebSocketResponseMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketResponseMessage) throws -> WebSocketProtoWebSocketResponseMessage { + guard proto.hasRequestID else { + throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID") + } + let requestID = proto.requestID + + guard proto.hasStatus else { + throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: status") + } + let status = proto.status + + // MARK: - Begin Validation Logic for WebSocketProtoWebSocketResponseMessage - + + // MARK: - End Validation Logic for WebSocketProtoWebSocketResponseMessage - + + let result = WebSocketProtoWebSocketResponseMessage(proto: proto, + requestID: requestID, + status: status) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension WebSocketProtoWebSocketResponseMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension WebSocketProtoWebSocketResponseMessage.WebSocketProtoWebSocketResponseMessageBuilder { + @objc public func buildIgnoringErrors() -> WebSocketProtoWebSocketResponseMessage? { + return try! self.build() + } +} + +#endif + +// MARK: - WebSocketProtoWebSocketMessage + +@objc public class WebSocketProtoWebSocketMessage: NSObject { + + // MARK: - WebSocketProtoWebSocketMessageType + + @objc public enum WebSocketProtoWebSocketMessageType: Int32 { + case unknown = 0 + case request = 1 + case response = 2 + } + + private class func WebSocketProtoWebSocketMessageTypeWrap(_ value: WebSocketProtos_WebSocketMessage.TypeEnum) -> WebSocketProtoWebSocketMessageType { + switch value { + case .unknown: return .unknown + case .request: return .request + case .response: return .response + } + } + + private class func WebSocketProtoWebSocketMessageTypeUnwrap(_ value: WebSocketProtoWebSocketMessageType) -> WebSocketProtos_WebSocketMessage.TypeEnum { + switch value { + case .unknown: return .unknown + case .request: return .request + case .response: return .response + } + } + + // MARK: - WebSocketProtoWebSocketMessageBuilder + + @objc public class func builder(type: WebSocketProtoWebSocketMessageType) -> WebSocketProtoWebSocketMessageBuilder { + return WebSocketProtoWebSocketMessageBuilder(type: type) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> WebSocketProtoWebSocketMessageBuilder { + let builder = WebSocketProtoWebSocketMessageBuilder(type: type) + if let _value = request { + builder.setRequest(_value) + } + if let _value = response { + builder.setResponse(_value) + } + return builder + } + + @objc public class WebSocketProtoWebSocketMessageBuilder: NSObject { + + private var proto = WebSocketProtos_WebSocketMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(type: WebSocketProtoWebSocketMessageType) { + super.init() + + setType(type) + } + + @objc public func setType(_ valueParam: WebSocketProtoWebSocketMessageType) { + proto.type = WebSocketProtoWebSocketMessageTypeUnwrap(valueParam) + } + + @objc public func setRequest(_ valueParam: WebSocketProtoWebSocketRequestMessage) { + proto.request = valueParam.proto + } + + @objc public func setResponse(_ valueParam: WebSocketProtoWebSocketResponseMessage) { + proto.response = valueParam.proto + } + + @objc public func build() throws -> WebSocketProtoWebSocketMessage { + return try WebSocketProtoWebSocketMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try WebSocketProtoWebSocketMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: WebSocketProtos_WebSocketMessage + + @objc public let type: WebSocketProtoWebSocketMessageType + + @objc public let request: WebSocketProtoWebSocketRequestMessage? + + @objc public let response: WebSocketProtoWebSocketResponseMessage? + + private init(proto: WebSocketProtos_WebSocketMessage, + type: WebSocketProtoWebSocketMessageType, + request: WebSocketProtoWebSocketRequestMessage?, + response: WebSocketProtoWebSocketResponseMessage?) { + self.proto = proto + self.type = type + self.request = request + self.response = response + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> WebSocketProtoWebSocketMessage { + let proto = try WebSocketProtos_WebSocketMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketMessage) throws -> WebSocketProtoWebSocketMessage { + guard proto.hasType else { + throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + } + let type = WebSocketProtoWebSocketMessageTypeWrap(proto.type) + + var request: WebSocketProtoWebSocketRequestMessage? = nil + if proto.hasRequest { + request = try WebSocketProtoWebSocketRequestMessage.parseProto(proto.request) + } + + var response: WebSocketProtoWebSocketResponseMessage? = nil + if proto.hasResponse { + response = try WebSocketProtoWebSocketResponseMessage.parseProto(proto.response) + } + + // MARK: - Begin Validation Logic for WebSocketProtoWebSocketMessage - + + // MARK: - End Validation Logic for WebSocketProtoWebSocketMessage - + + let result = WebSocketProtoWebSocketMessage(proto: proto, + type: type, + request: request, + response: response) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension WebSocketProtoWebSocketMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension WebSocketProtoWebSocketMessage.WebSocketProtoWebSocketMessageBuilder { + @objc public func buildIgnoringErrors() -> WebSocketProtoWebSocketMessage? { + return try! self.build() + } +} + +#endif diff --git a/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift b/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift new file mode 100644 index 000000000..e713065fd --- /dev/null +++ b/SessionMessagingKit/Protos/Generated/WebSocketResources.pb.swift @@ -0,0 +1,378 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: WebSocketResources.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +//* +// Copyright (C) 2014-2016 Open Whisper Systems +// +// Licensed according to the LICENSE file in this repository. + +/// iOS - since we use a modern proto-compiler, we must specify +/// the legacy proto format. + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct WebSocketProtos_WebSocketRequestMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var verb: String { + get {return _verb ?? String()} + set {_verb = newValue} + } + /// Returns true if `verb` has been explicitly set. + var hasVerb: Bool {return self._verb != nil} + /// Clears the value of `verb`. Subsequent reads from it will return its default value. + mutating func clearVerb() {self._verb = nil} + + /// @required + var path: String { + get {return _path ?? String()} + set {_path = newValue} + } + /// Returns true if `path` has been explicitly set. + var hasPath: Bool {return self._path != nil} + /// Clears the value of `path`. Subsequent reads from it will return its default value. + mutating func clearPath() {self._path = nil} + + var body: Data { + get {return _body ?? SwiftProtobuf.Internal.emptyData} + set {_body = newValue} + } + /// Returns true if `body` has been explicitly set. + var hasBody: Bool {return self._body != nil} + /// Clears the value of `body`. Subsequent reads from it will return its default value. + mutating func clearBody() {self._body = nil} + + var headers: [String] = [] + + /// @required + var requestID: UInt64 { + get {return _requestID ?? 0} + set {_requestID = newValue} + } + /// Returns true if `requestID` has been explicitly set. + var hasRequestID: Bool {return self._requestID != nil} + /// Clears the value of `requestID`. Subsequent reads from it will return its default value. + mutating func clearRequestID() {self._requestID = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _verb: String? = nil + fileprivate var _path: String? = nil + fileprivate var _body: Data? = nil + fileprivate var _requestID: UInt64? = nil +} + +struct WebSocketProtos_WebSocketResponseMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var requestID: UInt64 { + get {return _requestID ?? 0} + set {_requestID = newValue} + } + /// Returns true if `requestID` has been explicitly set. + var hasRequestID: Bool {return self._requestID != nil} + /// Clears the value of `requestID`. Subsequent reads from it will return its default value. + mutating func clearRequestID() {self._requestID = nil} + + /// @required + var status: UInt32 { + get {return _status ?? 0} + set {_status = newValue} + } + /// Returns true if `status` has been explicitly set. + var hasStatus: Bool {return self._status != nil} + /// Clears the value of `status`. Subsequent reads from it will return its default value. + mutating func clearStatus() {self._status = nil} + + var message: String { + get {return _message ?? String()} + set {_message = newValue} + } + /// Returns true if `message` has been explicitly set. + var hasMessage: Bool {return self._message != nil} + /// Clears the value of `message`. Subsequent reads from it will return its default value. + mutating func clearMessage() {self._message = nil} + + var headers: [String] = [] + + var body: Data { + get {return _body ?? SwiftProtobuf.Internal.emptyData} + set {_body = newValue} + } + /// Returns true if `body` has been explicitly set. + var hasBody: Bool {return self._body != nil} + /// Clears the value of `body`. Subsequent reads from it will return its default value. + mutating func clearBody() {self._body = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _requestID: UInt64? = nil + fileprivate var _status: UInt32? = nil + fileprivate var _message: String? = nil + fileprivate var _body: Data? = nil +} + +struct WebSocketProtos_WebSocketMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var type: WebSocketProtos_WebSocketMessage.TypeEnum { + get {return _type ?? .unknown} + set {_type = newValue} + } + /// Returns true if `type` has been explicitly set. + var hasType: Bool {return self._type != nil} + /// Clears the value of `type`. Subsequent reads from it will return its default value. + mutating func clearType() {self._type = nil} + + var request: WebSocketProtos_WebSocketRequestMessage { + get {return _request ?? WebSocketProtos_WebSocketRequestMessage()} + set {_request = newValue} + } + /// Returns true if `request` has been explicitly set. + var hasRequest: Bool {return self._request != nil} + /// Clears the value of `request`. Subsequent reads from it will return its default value. + mutating func clearRequest() {self._request = nil} + + var response: WebSocketProtos_WebSocketResponseMessage { + get {return _response ?? WebSocketProtos_WebSocketResponseMessage()} + set {_response = newValue} + } + /// Returns true if `response` has been explicitly set. + var hasResponse: Bool {return self._response != nil} + /// Clears the value of `response`. Subsequent reads from it will return its default value. + mutating func clearResponse() {self._response = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum TypeEnum: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case request // = 1 + case response // = 2 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .request + case 2: self = .response + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .request: return 1 + case .response: return 2 + } + } + + } + + init() {} + + fileprivate var _type: WebSocketProtos_WebSocketMessage.TypeEnum? = nil + fileprivate var _request: WebSocketProtos_WebSocketRequestMessage? = nil + fileprivate var _response: WebSocketProtos_WebSocketResponseMessage? = nil +} + +#if swift(>=4.2) + +extension WebSocketProtos_WebSocketMessage.TypeEnum: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "WebSocketProtos" + +extension WebSocketProtos_WebSocketRequestMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".WebSocketRequestMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "verb"), + 2: .same(proto: "path"), + 3: .same(proto: "body"), + 5: .same(proto: "headers"), + 4: .same(proto: "requestId"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularStringField(value: &self._verb) + case 2: try decoder.decodeSingularStringField(value: &self._path) + case 3: try decoder.decodeSingularBytesField(value: &self._body) + case 4: try decoder.decodeSingularUInt64Field(value: &self._requestID) + case 5: try decoder.decodeRepeatedStringField(value: &self.headers) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._verb { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } + if let v = self._path { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } + if let v = self._body { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } + if let v = self._requestID { + try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4) + } + if !self.headers.isEmpty { + try visitor.visitRepeatedStringField(value: self.headers, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: WebSocketProtos_WebSocketRequestMessage, rhs: WebSocketProtos_WebSocketRequestMessage) -> Bool { + if lhs._verb != rhs._verb {return false} + if lhs._path != rhs._path {return false} + if lhs._body != rhs._body {return false} + if lhs.headers != rhs.headers {return false} + if lhs._requestID != rhs._requestID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension WebSocketProtos_WebSocketResponseMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".WebSocketResponseMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "requestId"), + 2: .same(proto: "status"), + 3: .same(proto: "message"), + 5: .same(proto: "headers"), + 4: .same(proto: "body"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt64Field(value: &self._requestID) + case 2: try decoder.decodeSingularUInt32Field(value: &self._status) + case 3: try decoder.decodeSingularStringField(value: &self._message) + case 4: try decoder.decodeSingularBytesField(value: &self._body) + case 5: try decoder.decodeRepeatedStringField(value: &self.headers) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._requestID { + try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) + } + if let v = self._status { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } + if let v = self._message { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } + if let v = self._body { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } + if !self.headers.isEmpty { + try visitor.visitRepeatedStringField(value: self.headers, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: WebSocketProtos_WebSocketResponseMessage, rhs: WebSocketProtos_WebSocketResponseMessage) -> Bool { + if lhs._requestID != rhs._requestID {return false} + if lhs._status != rhs._status {return false} + if lhs._message != rhs._message {return false} + if lhs.headers != rhs.headers {return false} + if lhs._body != rhs._body {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension WebSocketProtos_WebSocketMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".WebSocketMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "type"), + 2: .same(proto: "request"), + 3: .same(proto: "response"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularEnumField(value: &self._type) + case 2: try decoder.decodeSingularMessageField(value: &self._request) + case 3: try decoder.decodeSingularMessageField(value: &self._response) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if let v = self._type { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } + if let v = self._request { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } + if let v = self._response { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: WebSocketProtos_WebSocketMessage, rhs: WebSocketProtos_WebSocketMessage) -> Bool { + if lhs._type != rhs._type {return false} + if lhs._request != rhs._request {return false} + if lhs._response != rhs._response {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension WebSocketProtos_WebSocketMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN"), + 1: .same(proto: "REQUEST"), + 2: .same(proto: "RESPONSE"), + ] +} diff --git a/SessionMessagingKit/Protos/Makefile b/SessionMessagingKit/Protos/Makefile index 7f89be1f1..9c0965cd4 100644 --- a/SessionMessagingKit/Protos/Makefile +++ b/SessionMessagingKit/Protos/Makefile @@ -4,9 +4,15 @@ PROTOC=protoc \ WRAPPER_SCRIPT=../../Scripts/ProtoWrappers.py \ --proto-dir='./' --verbose -all: session_protos +all: session_protos websocket_protos session_protos: SessionProtos.proto $(PROTOC) --swift_out=./Generated \SessionProtos.proto $(WRAPPER_SCRIPT) --dst-dir=./Generated \ --wrapper-prefix=SNProto --proto-prefix=SessionProtos --proto-file=SessionProtos.proto + + +websocket_protos: WebSocketResources.proto + $(PROTOC) --swift_out=./Generated \WebSocketResources.proto + $(WRAPPER_SCRIPT) --dst-dir=./Generated \ + --wrapper-prefix=WebSocketProto --proto-prefix=WebSocketProtos --proto-file=WebSocketResources.proto diff --git a/SessionMessagingKit/Protos/WebSocketResources.proto b/SessionMessagingKit/Protos/WebSocketResources.proto new file mode 100644 index 000000000..709e200d7 --- /dev/null +++ b/SessionMessagingKit/Protos/WebSocketResources.proto @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2014-2016 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ + +// iOS - since we use a modern proto-compiler, we must specify +// the legacy proto format. +syntax = "proto2"; + +// iOS - package name determines class prefix +package WebSocketProtos; + +option java_package = "org.whispersystems.signalservice.internal.websocket"; +option java_outer_classname = "WebSocketProtos"; + +message WebSocketRequestMessage { + // @required + optional string verb = 1; + // @required + optional string path = 2; + optional bytes body = 3; + repeated string headers = 5; + // @required + optional uint64 requestId = 4; +} + +message WebSocketResponseMessage { + // @required + optional uint64 requestId = 1; + // @required + optional uint32 status = 2; + optional string message = 3; + repeated string headers = 5; + optional bytes body = 4; +} + +message WebSocketMessage { + enum Type { + UNKNOWN = 0; + REQUEST = 1; + RESPONSE = 2; + } + + // @required + optional Type type = 1; + optional WebSocketRequestMessage request = 2; + optional WebSocketResponseMessage response = 3; +} diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 473a99449..8d57dbaf1 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,7 +1,5 @@ import SessionUtilities -// TODO: Decryption - internal enum MessageReceiver { internal enum Error : LocalizedError { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 3b44a8793..81cfa340c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -2,9 +2,6 @@ import PromiseKit import SessionSnodeKit import SessionUtilities -// TODO: Open group encryption -// TODO: Signal protocol encryption - internal enum MessageSender { internal enum Error : LocalizedError { @@ -24,6 +21,13 @@ internal enum MessageSender { } internal static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise { + switch destination { + case .contact(_), .closedGroup(_): return sendToSnodeDestination(destination, message: message, using: transaction) + default: fatalError("Not implemented.") + } + } + + internal static func sendToSnodeDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise { // Validate the message guard message.isValid else { return Promise(error: Error.invalidMessage) } // Convert it to protobuf @@ -47,12 +51,32 @@ internal enum MessageSender { switch destination { case .contact(let publicKey): ciphertext = try encryptWithSignalProtocol(plaintext, for: publicKey, using: transaction) case .closedGroup(let groupPublicKey): ciphertext = try encryptWithSharedSenderKeys(plaintext, for: groupPublicKey, using: transaction) - case .openGroup(_, _): fatalError("Not implemented.") + case .openGroup(_, _): preconditionFailure() } } catch { SNLog("Couldn't encrypt message for destination: \(destination) due to error: \(error).") return Promise(error: error) } + // Wrap the result + let kind: SNProtoEnvelope.SNProtoEnvelopeType + let senderPublicKey: String + switch destination { + case .contact(_): + kind = .unidentifiedSender + senderPublicKey = "" + case .closedGroup(let groupPublicKey): + kind = .closedGroupCiphertext + senderPublicKey = groupPublicKey + case .openGroup(_, _): preconditionFailure() + } + let wrappedMessage: Data + do { + wrappedMessage = try MessageWrapper.wrap(type: kind, timestamp: message.sentTimestamp!, + senderPublicKey: senderPublicKey, base64EncodedContent: ciphertext.base64EncodedString()) + } catch { + SNLog("Couldn't wrap message due to error: \(error).") + return Promise(error: error) + } // Calculate proof of work if case .contact(_) = destination { DispatchQueue.main.async { @@ -60,7 +84,7 @@ internal enum MessageSender { } } let recipient = message.recipient! - let base64EncodedData = ciphertext.base64EncodedString() + let base64EncodedData = wrappedMessage.base64EncodedString() guard let (timestamp, nonce) = ProofOfWork.calculate(ttl: type(of: message).ttl, publicKey: recipient, data: base64EncodedData) else { SNLog("Proof of work calculation failed.") return Promise(error: Error.proofOfWorkCalculationFailed) diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift new file mode 100644 index 000000000..28c5f4603 --- /dev/null +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -0,0 +1,74 @@ +import SessionSnodeKit +import SessionUtilities + +public enum MessageWrapper { + + public enum Error : LocalizedError { + case failedToWrapData + case failedToWrapMessageInEnvelope + case failedToWrapEnvelopeInWebSocketMessage + case failedToUnwrapData + + public var errorDescription: String? { + switch self { + case .failedToWrapData: return "Failed to wrap data." + case .failedToWrapMessageInEnvelope: return "Failed to wrap message in envelope." + case .failedToWrapEnvelopeInWebSocketMessage: return "Failed to wrap envelope in web socket message." + case .failedToUnwrapData: return "Failed to unwrap data." + } + } + } + + /// Wraps the given parameters in an `SNProtoEnvelope` and then a `WebSocketProtoWebSocketMessage` to match the desktop application. + public static func wrap(type: SNProtoEnvelope.SNProtoEnvelopeType, timestamp: UInt64, senderPublicKey: String, base64EncodedContent: String) throws -> Data { + do { + let envelope = try createEnvelope(type: type, timestamp: timestamp, senderPublicKey: senderPublicKey, base64EncodedContent: base64EncodedContent) + let webSocketMessage = try createWebSocketMessage(around: envelope) + return try webSocketMessage.serializedData() + } catch let error { + throw error as? Error ?? Error.failedToWrapData + } + } + + private static func createEnvelope(type: SNProtoEnvelope.SNProtoEnvelopeType, timestamp: UInt64, senderPublicKey: String, base64EncodedContent: String) throws -> SNProtoEnvelope { + do { + let builder = SNProtoEnvelope.builder(type: type, timestamp: timestamp) + builder.setSource(senderPublicKey) + builder.setSourceDevice(1) + if let content = Data(base64Encoded: base64EncodedContent, options: .ignoreUnknownCharacters) { + builder.setContent(content) + } else { + throw Error.failedToWrapMessageInEnvelope + } + return try builder.build() + } catch let error { + SNLog("Failed to wrap message in envelope: \(error).") + throw Error.failedToWrapMessageInEnvelope + } + } + + private static func createWebSocketMessage(around envelope: SNProtoEnvelope) throws -> WebSocketProtoWebSocketMessage { + do { + let requestBuilder = WebSocketProtoWebSocketRequestMessage.builder(verb: "PUT", path: "/api/v1/message", requestID: UInt64.random(in: 1.. SNProtoEnvelope { + do { + let webSocketMessage = try WebSocketProtoWebSocketMessage.parseData(data) + let envelope = webSocketMessage.request!.body! + return try SNProtoEnvelope.parseData(envelope) + } catch let error { + SNLog("Failed to unwrap data: \(error).") + throw Error.failedToUnwrapData + } + } +} diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index b6602c157..44b025fdc 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -631,6 +631,9 @@ C39DD28824F3318C008590FC /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; C39DD28A24F3336E008590FC /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; C39DD28B24F3336F008590FC /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C39DD28724F3318C008590FC /* Colors.xcassets */; }; + C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D0A2558989C0043A11F /* MessageWrapper.swift */; }; + C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1C25589AC30043A11F /* WebSocketProto.swift */; }; + C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */; }; C3AABDDF2553ECF00042FF4C /* Array+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Description.swift */; }; C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE0752554CDA60050F1E3 /* Configuration.swift */; }; C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE07F2554CDD70050F1E3 /* Storage.swift */; }; @@ -1645,6 +1648,9 @@ C396DAED2518408B00FF6DC5 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; C396DAEE2518408B00FF6DC5 /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; }; C39DD28724F3318C008590FC /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; + C3A71D0A2558989C0043A11F /* MessageWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageWrapper.swift; sourceTree = ""; }; + C3A71D1C25589AC30043A11F /* WebSocketProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketProto.swift; sourceTree = ""; }; + C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketResources.pb.swift; sourceTree = ""; }; C3AA6BB824CE8F1B002358B6 /* Migrating Translations from Android.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Migrating Translations from Android.md"; sourceTree = ""; }; C3AECBEA24EF5244005743DE /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = translations/fa.lproj/Localizable.strings; sourceTree = ""; }; C3BBE0752554CDA60050F1E3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; @@ -3308,6 +3314,7 @@ isa = PBXGroup; children = ( C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */, + C3A71D0A2558989C0043A11F /* MessageWrapper.swift */, C3BBE0B42554F0E10050F1E3 /* ProofOfWork.swift */, C3471F4125553A4D00297E91 /* Threading.swift */, ); @@ -3424,6 +3431,8 @@ children = ( C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */, C3C2A7822553AAF200C340D1 /* SNProto.swift */, + C3A71D1C25589AC30043A11F /* WebSocketProto.swift */, + C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */, ); path = Generated; sourceTree = ""; @@ -4973,6 +4982,8 @@ C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */, C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */, C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */, + C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, + C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */, C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */, C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */, C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */, @@ -4980,6 +4991,7 @@ C3471F4225553A4D00297E91 /* Threading.swift in Sources */, C300A5DD2554B06600555489 /* ClosedGroupUpdate.swift in Sources */, C3471FA42555439E00297E91 /* Notification+MessageSender.swift in Sources */, + C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */, C352A349255781F400338F3E /* AttachmentDownloadJob.swift in Sources */, C352A30925574D8500338F3E /* Message+Destination.swift in Sources */, C300A5E72554B07300555489 /* ExpirationTimerUpdate.swift in Sources */,