Announcement.swift 7.26 KB
Newer Older
1
import Foundation
2
import CocoaAsyncSocket
3

4
/**
5
 * Announcement is the announcement packet of the
6 7
 * [Device Discovery Protocol v2](https://github.com/syncthing/specs/blob/master/DISCOVERYv2.md).
 */
8
public struct Announcement : XdrWritable, XdrReadable, Equatable {
9
    /// The magic number of the Announcement packet.
10
    public static let magic : UInt32 = 0x9D79BC39
11

12
    /// The Device record representing the sending device.
13
    public let thisDevice : Device
14 15
    
    /// An array of Device records containing information about other devices known to the sender.
16
    public let extraDevices : [Device]
17

18 19 20 21 22 23 24 25
    /**
     * Creates a new Announcement packet.
     *
     * - parameters:
     *   - deviceId: the DeviceId of the sender
     *   - addresses: the addresses of the sender
     *   - extraDevices: Device records representing other devices known to the sender
     */
26 27 28 29 30
    public init(deviceId:DeviceId, addresses:[Device.Address] = [], extraDevices:[Device] = []) {
        self.thisDevice = Device(deviceId:deviceId, addresses:addresses)
        self.extraDevices = extraDevices
    }

31 32 33 34 35
    private init(device:Device, extraDevices:[Device] = []) {
        self.thisDevice = device
        self.extraDevices = extraDevices
    }

36
    /// XdrWritable
37
    public func writeTo(writer: XdrWriter) {
38
        writer.writeUInt32(Announcement.magic)
39
        writer.write(thisDevice)
Stefan van den Oord's avatar
Stefan van den Oord committed
40 41
        writer.writeUInt32(UInt32(extraDevices.count))
        for i in 0 ..< extraDevices.count {
42 43 44 45
            writer.write(extraDevices[i])
        }
    }

46
    /// XdrReadable
47
    public static func readFrom(reader: XdrReader) -> Announcement? {
48 49 50
        guard let magic = reader.readUInt32() where magic == Announcement.magic else { return nil }
        
        if let d = reader.read(Device.self),
51 52 53
           let extraCount = reader.readUInt32()
        {
            var extra : [Device] = []
Stefan van den Oord's avatar
Stefan van den Oord committed
54
            for _ in 0 ..< Int(extraCount) {
55 56
                if let e = reader.read(Device.self) {
                    extra.append(e)
57 58
                }
            }
Stefan van den Oord's avatar
Stefan van den Oord committed
59
            if UInt32(extra.count) == extraCount {
60 61
                return Announcement(device:d, extraDevices:extra)
            }
62 63 64 65
        }
        return nil
    }

66 67 68
    /**
     * A device record inside an Announcement packet.
     */
69
    public struct Device : XdrWritable, XdrReadable, Equatable {
70
        /// The DeviceId of the device
71
        public let deviceId : DeviceId
72 73
        
        /// The addresses of the device
74
        public let addresses : [Address]
75 76 77 78 79 80 81 82
        
        /**
         * Creates a new Device structure.
         * 
         * - parameters:
         *   - deviceId: the DeviceId of this device structure
         *   - addresses: the addresses of the device
         */
83
        public init(deviceId:DeviceId, addresses:[Address] = []) {
84 85 86 87
            self.deviceId = deviceId;
            self.addresses = addresses;
        }

88
        /// XdrWritable
89 90
        public func writeTo(writer: XdrWriter) {
            writer.writeData(deviceId.sha256)
Stefan van den Oord's avatar
Stefan van den Oord committed
91
            writer.writeUInt32(UInt32(addresses.count))
92 93 94 95 96
            for a in addresses {
                writer.write(a)
            }
        }

97
        /// XdrReadable
98
        public static func readFrom(reader: XdrReader) -> Device? {
99 100 101 102 103
            if let sha256 = reader.readData(), let count:UInt32 = reader.readUInt32() {
                var addresses : [Address] = []
                for _ in 0 ..< Int(count) {
                    if let a = reader.read(Address.self) {
                        addresses.append(a)
104 105
                    }
                }
106 107 108
                if UInt32(addresses.count) == count {
                    return Device(deviceId:DeviceId(hash:sha256), addresses:addresses)
                }
109 110 111 112
            }
            return nil
        }

113 114 115
        /**
         * An address structure inside a device structure.
         */
116
        public struct Address : XdrWritable, XdrReadable, Equatable {
117
            /// The IP address of this Address struct
118
            public let ip : [UInt8]
119 120
            
            /// The port of this Address struct
121
            public let port : UInt16
122
            
123 124 125 126 127 128 129 130 131 132 133 134
            /**
             * Creates an Address that has no IP address. According to the
             * [specification](https://github.com/syncthing/specs/blob/master/DISCOVERYv2.md):
             *
             * > A zero length indicates that the IP address should be taken from the source address of the announcement packet, be it IPv4 or IPv6.
             * > The source address must be a valid unicast address. This is only valid in the first device structure, not in the list of extras. In
             * > case of global discovery, the discovery server will reply to a Query with an announcement packet containing the expanded address of
             * > the queried device ID as seen from the server, allowing to traverse the majority of NAT devices.
             *
             * - parameters:
             *   - port: the port of the Address
             */
135 136
            public init(port:UInt16) {
                self.init(ip:[], port:port)
137
            }
138 139 140 141 142 143 144 145
            
            /**
             * Creates an Address with an IPv4 address and a port.
             *
             * - parameters:
             *   - ipv4: the IPv4 address
             *   - port: the port number
             */
146 147
            public init(ipv4:(b1:UInt8,b2:UInt8,b3:UInt8,b4:UInt8), port:UInt16) {
                self.init(ip:[ipv4.b1,ipv4.b2,ipv4.b3,ipv4.b4], port:port)
148
            }
149 150 151 152 153 154 155 156 157 158
            
            /**
             * Creates an Address with a IP address and a port.
             * 
             * - parameters:
             *   - ip: array of bytes; either 4 bytes long for an IPv4 address,
             *         or 16 bytes long for an IPv6 address)
             *   - port: the port number
             */
            public init(ip:[UInt8], port:UInt16) {
159
                self.ip = ip
160 161
                self.port = port
            }
162
            
163 164 165 166
            init(reader:XdrReader) throws {
                guard let ip = reader.readData(), let port = reader.readUInt32() else {
                    throw PulseError.XdrReader("failed to read Address")
                }
167
                self.init(ip:ip, port:UInt16(port))
168
            }
169

170
            /// XdrReadable
171
            public static func readFrom(reader: XdrReader) -> Address? {
172
                var a:Address? = nil
173
                do {
174
                    a = try Address(reader: reader)
175
                }
176 177
                catch {}
                return a
178 179
            }

180
            /// XdrWritable
181 182 183 184
            public func writeTo(writer: XdrWriter) {
                writer.writeData(ip)
                writer.writeUInt32(UInt32(port))
            }
185
            
186 187 188 189 190
        }
    }

}

191
/// Checks two Announcement.Device.Address structs for equality.
192 193 194 195
public func ==(lhs: Announcement.Device.Address, rhs: Announcement.Device.Address) -> Bool {
    return (lhs.ip == rhs.ip) && (lhs.port == rhs.port)
}

196
/// Checks two Announcement.Device structs for equality
197 198 199 200
public func ==(lhs: Announcement.Device, rhs: Announcement.Device) -> Bool {
    return (lhs.deviceId == rhs.deviceId) && (lhs.addresses == rhs.addresses)
}

201
/// Checks two Announcement structs for equality
202 203 204
public func ==(lhs: Announcement, rhs: Announcement) -> Bool {
    return (lhs.thisDevice == rhs.thisDevice) && (lhs.extraDevices == rhs.extraDevices)
}