1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.nio;
19
20import java.io.FileDescriptor;
21import java.io.IOException;
22import java.io.InterruptedIOException;
23import java.net.ConnectException;
24import java.net.DatagramPacket;
25import java.net.DatagramSocket;
26import java.net.DatagramSocketImpl;
27import java.net.InetAddress;
28import java.net.InetSocketAddress;
29import java.net.PlainDatagramSocketImpl;
30import java.net.SocketAddress;
31import java.net.SocketException;
32import java.nio.channels.AlreadyConnectedException;
33import java.nio.channels.ClosedChannelException;
34import java.nio.channels.DatagramChannel;
35import java.nio.channels.IllegalBlockingModeException;
36import java.nio.channels.NotYetConnectedException;
37import java.nio.channels.spi.SelectorProvider;
38import java.util.Arrays;
39import libcore.io.ErrnoException;
40import libcore.io.IoBridge;
41import libcore.io.IoUtils;
42import libcore.io.Libcore;
43import libcore.util.EmptyArray;
44
45/*
46 * The default implementation class of java.nio.channels.DatagramChannel.
47 */
48class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChannel {
49    // The fd to interact with native code
50    private final FileDescriptor fd;
51
52    // Our internal DatagramSocket.
53    private DatagramSocket socket = null;
54
55    // The address to be connected.
56    InetSocketAddress connectAddress = null;
57
58    // local port
59    private int localPort;
60
61    // At first, uninitialized.
62    boolean connected = false;
63
64    // whether the socket is bound
65    boolean isBound = false;
66
67    private final Object readLock = new Object();
68    private final Object writeLock = new Object();
69
70    /*
71     * Constructor
72     */
73    protected DatagramChannelImpl(SelectorProvider selectorProvider) throws IOException {
74        super(selectorProvider);
75        fd = IoBridge.socket(false);
76    }
77
78    /*
79     * for native call
80     */
81    @SuppressWarnings("unused")
82    private DatagramChannelImpl() {
83        super(SelectorProvider.provider());
84        fd = new FileDescriptor();
85        connectAddress = new InetSocketAddress(0);
86    }
87
88    /*
89     * Getting the internal DatagramSocket If we have not the socket, we create
90     * a new one.
91     */
92    @Override
93    synchronized public DatagramSocket socket() {
94        if (socket == null) {
95            socket = new DatagramSocketAdapter(new PlainDatagramSocketImpl(fd, localPort), this);
96        }
97        return socket;
98    }
99
100    /**
101     * Returns the local address to which the socket is bound.
102     */
103    InetAddress getLocalAddress() {
104        return IoBridge.getSocketLocalAddress(fd);
105    }
106
107    /**
108     * @see java.nio.channels.DatagramChannel#isConnected()
109     */
110    @Override
111    synchronized public boolean isConnected() {
112        return connected;
113    }
114
115    /**
116     * @see java.nio.channels.DatagramChannel#connect(java.net.SocketAddress)
117     */
118    @Override
119    synchronized public DatagramChannel connect(SocketAddress address) throws IOException {
120        // must open
121        checkOpen();
122        // status must be un-connected.
123        if (connected) {
124            throw new IllegalStateException();
125        }
126
127        // check the address
128        InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
129        try {
130            begin();
131            IoBridge.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort());
132        } catch (ConnectException e) {
133            // ConnectException means connect fail, not exception
134        } finally {
135            end(true);
136        }
137
138        // set the connected address.
139        connectAddress = inetSocketAddress;
140        connected = true;
141        isBound = true;
142        return this;
143    }
144
145    /**
146     * @see java.nio.channels.DatagramChannel#disconnect()
147     */
148    @Override
149    synchronized public DatagramChannel disconnect() throws IOException {
150        if (!isConnected() || !isOpen()) {
151            return this;
152        }
153        connected = false;
154        connectAddress = null;
155        try {
156            Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0);
157        } catch (ErrnoException errnoException) {
158            throw errnoException.rethrowAsIOException();
159        }
160        if (socket != null) {
161            socket.disconnect();
162        }
163        return this;
164    }
165
166    @Override
167    public SocketAddress receive(ByteBuffer target) throws IOException {
168        target.checkWritable();
169        checkOpen();
170
171        if (!isBound) {
172            return null;
173        }
174
175        SocketAddress retAddr = null;
176        try {
177            begin();
178
179            // receive real data packet, (not peek)
180            synchronized (readLock) {
181                boolean loop = isBlocking();
182                if (!target.isDirect()) {
183                    retAddr = receiveImpl(target, loop);
184                } else {
185                    retAddr = receiveDirectImpl(target, loop);
186                }
187            }
188        } catch (InterruptedIOException e) {
189            // this line used in Linux
190            return null;
191        } finally {
192            end(retAddr != null);
193        }
194        return retAddr;
195    }
196
197    private SocketAddress receiveImpl(ByteBuffer target, boolean loop) throws IOException {
198        SocketAddress retAddr = null;
199        DatagramPacket receivePacket;
200        int oldposition = target.position();
201        int received = 0;
202        // TODO: disallow mapped buffers and lose this conditional?
203        if (target.hasArray()) {
204            receivePacket = new DatagramPacket(target.array(), target.position() + target.arrayOffset(), target.remaining());
205        } else {
206            receivePacket = new DatagramPacket(new byte[target.remaining()], target.remaining());
207        }
208        do {
209            received = IoBridge.recvfrom(false, fd, receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), 0, receivePacket, isConnected());
210            if (receivePacket != null && receivePacket.getAddress() != null) {
211                if (received > 0) {
212                    if (target.hasArray()) {
213                        target.position(oldposition + received);
214                    } else {
215                        // copy the data of received packet
216                        target.put(receivePacket.getData(), 0, received);
217                    }
218                }
219                retAddr = receivePacket.getSocketAddress();
220                break;
221            }
222        } while (loop);
223        return retAddr;
224    }
225
226    private SocketAddress receiveDirectImpl(ByteBuffer target, boolean loop) throws IOException {
227        SocketAddress retAddr = null;
228        DatagramPacket receivePacket = new DatagramPacket(EmptyArray.BYTE, 0);
229        int oldposition = target.position();
230        int received = 0;
231        do {
232            received = IoBridge.recvfrom(false, fd, target, 0, receivePacket, isConnected());
233            if (receivePacket != null && receivePacket.getAddress() != null) {
234                // copy the data of received packet
235                if (received > 0) {
236                    target.position(oldposition + received);
237                }
238                retAddr = receivePacket.getSocketAddress();
239                break;
240            }
241        } while (loop);
242        return retAddr;
243    }
244
245    @Override
246    public int send(ByteBuffer source, SocketAddress socketAddress) throws IOException {
247        checkNotNull(source);
248        checkOpen();
249
250        InetSocketAddress isa = (InetSocketAddress) socketAddress;
251        if (isa.getAddress() == null) {
252            throw new IOException();
253        }
254
255        if (isConnected() && !connectAddress.equals(isa)) {
256            throw new IllegalArgumentException();
257        }
258
259        synchronized (writeLock) {
260            int sendCount = 0;
261            try {
262                begin();
263                int oldPosition = source.position();
264                sendCount = IoBridge.sendto(fd, source, 0, isa.getAddress(), isa.getPort());
265                if (sendCount > 0) {
266                    source.position(oldPosition + sendCount);
267                }
268            } finally {
269                end(sendCount >= 0);
270            }
271            return sendCount;
272        }
273    }
274
275    @Override
276    public int read(ByteBuffer target) throws IOException {
277        target.checkWritable();
278        checkOpenConnected();
279
280        if (!target.hasRemaining()) {
281            return 0;
282        }
283
284        int readCount = 0;
285        if (target.isDirect() || target.hasArray()) {
286            readCount = readImpl(target);
287            if (readCount > 0) {
288                target.position(target.position() + readCount);
289            }
290
291        } else {
292            byte[] readArray = new byte[target.remaining()];
293            ByteBuffer readBuffer = ByteBuffer.wrap(readArray);
294            readCount = readImpl(readBuffer);
295            if (readCount > 0) {
296                target.put(readArray, 0, readCount);
297            }
298        }
299        return readCount;
300    }
301
302    @Override
303    public long read(ByteBuffer[] targets, int offset, int length) throws IOException {
304        Arrays.checkOffsetAndCount(targets.length, offset, length);
305
306        // status must be open and connected
307        checkOpenConnected();
308        int totalCount = FileChannelImpl.calculateTotalRemaining(targets, offset, length, true);
309        if (totalCount == 0) {
310            return 0;
311        }
312
313        // read data to readBuffer, and then transfer data from readBuffer to
314        // targets.
315        ByteBuffer readBuffer = ByteBuffer.allocate(totalCount);
316        int readCount;
317        readCount = readImpl(readBuffer);
318        int left = readCount;
319        int index = offset;
320        // transfer data from readBuffer to targets
321        byte[] readArray = readBuffer.array();
322        while (left > 0) {
323            int putLength = Math.min(targets[index].remaining(), left);
324            targets[index].put(readArray, readCount - left, putLength);
325            index++;
326            left -= putLength;
327        }
328        return readCount;
329    }
330
331    /*
332     * read from channel, and store the result in the target.
333     */
334    private int readImpl(ByteBuffer dst) throws IOException {
335        synchronized (readLock) {
336            int readCount = 0;
337            try {
338                begin();
339                readCount = IoBridge.recvfrom(false, fd, dst, 0, null, isConnected());
340            } catch (InterruptedIOException e) {
341                // InterruptedIOException will be thrown when timeout.
342                return 0;
343            } finally {
344                end(readCount > 0);
345            }
346            return readCount;
347        }
348    }
349
350    @Override public int write(ByteBuffer src) throws IOException {
351        checkNotNull(src);
352        checkOpenConnected();
353        if (!src.hasRemaining()) {
354            return 0;
355        }
356
357        int writeCount = writeImpl(src);
358        if (writeCount > 0) {
359            src.position(src.position() + writeCount);
360        }
361        return writeCount;
362    }
363
364    /**
365     * @see java.nio.channels.DatagramChannel#write(java.nio.ByteBuffer[], int,
366     *      int)
367     */
368    @Override
369    public long write(ByteBuffer[] sources, int offset, int length) throws IOException {
370        Arrays.checkOffsetAndCount(sources.length, offset, length);
371
372        // status must be open and connected
373        checkOpenConnected();
374        int count = FileChannelImpl.calculateTotalRemaining(sources, offset, length, false);
375        if (count == 0) {
376            return 0;
377        }
378        ByteBuffer writeBuf = ByteBuffer.allocate(count);
379        for (int val = offset; val < length + offset; val++) {
380            ByteBuffer source = sources[val];
381            int oldPosition = source.position();
382            writeBuf.put(source);
383            source.position(oldPosition);
384        }
385        writeBuf.flip();
386        int result = writeImpl(writeBuf);
387        int val = offset;
388        int written = result;
389        while (result > 0) {
390            ByteBuffer source = sources[val];
391            int gap = Math.min(result, source.remaining());
392            source.position(source.position() + gap);
393            val++;
394            result -= gap;
395        }
396        return written;
397    }
398
399    private int writeImpl(ByteBuffer buf) throws IOException {
400        synchronized (writeLock) {
401            int result = 0;
402            try {
403                begin();
404                result = IoBridge.sendto(fd, buf, 0, null, 0);
405            } finally {
406                end(result > 0);
407            }
408            return result;
409        }
410    }
411
412    @Override protected synchronized void implCloseSelectableChannel() throws IOException {
413        connected = false;
414        if (socket != null && !socket.isClosed()) {
415            socket.close();
416        } else {
417            IoBridge.closeSocket(fd);
418        }
419    }
420
421    @Override protected void implConfigureBlocking(boolean blocking) throws IOException {
422        synchronized (blockingLock()) {
423            IoUtils.setBlocking(fd, blocking);
424        }
425    }
426
427    /*
428     * Status check, must be open.
429     */
430    private void checkOpen() throws IOException {
431        if (!isOpen()) {
432            throw new ClosedChannelException();
433        }
434    }
435
436    /*
437     * Status check, must be open and connected, for read and write.
438     */
439    private void checkOpenConnected() throws IOException {
440        checkOpen();
441        if (!isConnected()) {
442            throw new NotYetConnectedException();
443        }
444    }
445
446    /*
447     * Buffer check, must not null
448     */
449    private void checkNotNull(ByteBuffer source) {
450        if (source == null) {
451            throw new NullPointerException("source == null");
452        }
453    }
454
455    /*
456     * Get the fd for internal use.
457     */
458    public FileDescriptor getFD() {
459        return fd;
460    }
461
462    /*
463     * The adapter class of DatagramSocket
464     */
465    private static class DatagramSocketAdapter extends DatagramSocket {
466
467        /*
468         * The internal datagramChannelImpl.
469         */
470        private DatagramChannelImpl channelImpl;
471
472        /*
473         * Constructor initialize the datagramSocketImpl and datagramChannelImpl
474         */
475        DatagramSocketAdapter(DatagramSocketImpl socketimpl,
476                DatagramChannelImpl channelImpl) {
477            super(socketimpl);
478            this.channelImpl = channelImpl;
479        }
480
481        /*
482         * Get the internal datagramChannelImpl
483         */
484        @Override
485        public DatagramChannel getChannel() {
486            return channelImpl;
487        }
488
489        /**
490         * @see java.net.DatagramSocket#isBound()
491         */
492        @Override
493        public boolean isBound() {
494            return channelImpl.isBound;
495        }
496
497        /**
498         * @see java.net.DatagramSocket#isConnected()
499         */
500        @Override
501        public boolean isConnected() {
502            return channelImpl.isConnected();
503        }
504
505        /**
506         * @see java.net.DatagramSocket#getInetAddress()
507         */
508        @Override
509        public InetAddress getInetAddress() {
510            if (channelImpl.connectAddress == null) {
511                return null;
512            }
513            return channelImpl.connectAddress.getAddress();
514        }
515
516        /**
517         * @see java.net.DatagramSocket#getLocalAddress()
518         */
519        @Override
520        public InetAddress getLocalAddress() {
521            return channelImpl.getLocalAddress();
522        }
523
524        /**
525         * @see java.net.DatagramSocket#getPort()
526         */
527        @Override
528        public int getPort() {
529            if (channelImpl.connectAddress == null) {
530                return -1;
531            }
532            return channelImpl.connectAddress.getPort();
533        }
534
535        /**
536         * @see java.net.DatagramSocket#bind(java.net.SocketAddress)
537         */
538        @Override
539        public void bind(SocketAddress localAddr) throws SocketException {
540            if (channelImpl.isConnected()) {
541                throw new AlreadyConnectedException();
542            }
543            super.bind(localAddr);
544            channelImpl.isBound = true;
545        }
546
547        /**
548         * @see java.net.DatagramSocket#receive(java.net.DatagramPacket)
549         */
550        @Override
551        public void receive(DatagramPacket packet) throws IOException {
552            if (!channelImpl.isBlocking()) {
553                throw new IllegalBlockingModeException();
554            }
555            super.receive(packet);
556        }
557
558        /**
559         * @see java.net.DatagramSocket#send(java.net.DatagramPacket)
560         */
561        @Override
562        public void send(DatagramPacket packet) throws IOException {
563            if (!channelImpl.isBlocking()) {
564                throw new IllegalBlockingModeException();
565            }
566            super.send(packet);
567        }
568
569        /**
570         * @see java.net.DatagramSocket#close()
571         */
572        @Override
573        public void close() {
574            synchronized (channelImpl) {
575                if (channelImpl.isOpen()) {
576                    try {
577                        channelImpl.close();
578                    } catch (IOException e) {
579                        // Ignore
580                    }
581                }
582                super.close();
583            }
584        }
585
586        /**
587         * @see java.net.DatagramSocket#disconnect()
588         */
589        @Override
590        public void disconnect() {
591            try {
592                channelImpl.disconnect();
593            } catch (IOException e) {
594                // Ignore
595            }
596            super.disconnect();
597        }
598    }
599}
600