/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.smb.server.win32; import java.io.IOException; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.netbios.win32.NetBIOSSocket; import org.alfresco.jlan.netbios.win32.WinsockNetBIOSException; import org.alfresco.jlan.smb.server.CIFSPacketPool; import org.alfresco.jlan.smb.server.PacketHandler; import org.alfresco.jlan.smb.server.SMBSrvPacket; import org.alfresco.jlan.smb.server.SMBSrvPacketQueue; import org.alfresco.jlan.smb.server.SMBSrvPacketQueue.QueuedSMBPacket; import org.alfresco.jlan.smb.server.nio.AsynchronousWritesHandler; /** * Winsock NetBIOS Packet Handler Class * * <p> * Uses a Windows Winsock NetBIOS socket to provide the low level session layer for better * integration with Windows. * * @author gkspencer */ public class WinsockNetBIOSPacketHandler extends PacketHandler implements AsynchronousWritesHandler { // Constants // // Receive error indicating a receive buffer error private static final int ReceiveBufferSizeError = 0x80000000; // Network LAN adapter to use private int m_lana; // NetBIOS session socket private NetBIOSSocket m_sessSock; // Asynchronous I/O packet queue private SMBSrvPacketQueue m_asyncQueue; // Asynchronous mode enabled private boolean m_asyncMode; /** * Class constructor * * @param lana int * @param sock NetBIOSSocket * @param packetPool CIFSPacketPool * @param asyncMode boolean */ public WinsockNetBIOSPacketHandler(int lana, NetBIOSSocket sock, CIFSPacketPool packetPool, boolean asyncMode) { super(SMBSrvPacket.PROTOCOL_WIN32NETBIOS, "WinsockNB", "WSNB", sock.getName().getName(), packetPool); m_lana = lana; m_sessSock = sock; m_asyncMode = asyncMode; // If asynchrnous mode is enabled then allocate the asynchronous packet queue if ( hasAsynchronousMode()) m_asyncQueue = new SMBSrvPacketQueue(); } /** * Return the LANA number * * @return int */ public final int getLANA() { return m_lana; } /** * Return the NetBIOS socket * * @return NetBIOSSocket */ public final NetBIOSSocket getSocket() { return m_sessSock; } /** * Return the count of available bytes in the receive input stream * * @return int * @exception IOException If a network error occurs. */ public int availableBytes() throws IOException { // Do not know the available byte count return -1; } /** * Check if asynchronous mode is enabled * * @return boolean */ public final boolean hasAsynchronousMode() { return m_asyncMode; } /** * Read a packet from the client * * @return SMBSrvPacket * @throws IOException */ public SMBSrvPacket readPacket() throws IOException { // Get the length of the pending receive data, so we can allocate the correct sized buffer int rxlen = m_sessSock.available(); // Check if data is showing as available yet if ( rxlen == 0) { int loop = 0; while ( loop++ < 50 && rxlen == 0) { rxlen = m_sessSock.available(); if ( rxlen == 0) { try { Thread.sleep( 2); } catch (Exception e) { } } } if ( rxlen == 0 && hasDebug()) Debug.println("***** Still no data after 100ms *****"); } SMBSrvPacket pkt = getPacketPool().allocatePacket( rxlen + 8); // Receive an SMB/CIFS request packet via the Winsock NetBIOS socket try { // Read a packet of data rxlen = m_sessSock.read(pkt.getBuffer(), 4, pkt.getBufferLength() - 4); // Check if the buffer is not big enough to receive the entire packet, extend the buffer // and read the remaining part of the packet if ( rxlen == ReceiveBufferSizeError) { // Check if there is a larger buffer size available from the packet pool if ( pkt.getBufferLength() >= getPacketPool().getLargestSize()) { // Release the packet back to the pool getPacketPool().releasePacket( pkt); // Throw an exception throw new RuntimeException("Winsock NetBIOS receive over max available buffer size"); } // Get the remaining data length int rxlen2 = m_sessSock.available(); if ( rxlen2 > 0) { // Allocate a larger buffer to hold the full packet SMBSrvPacket pkt2 = getPacketPool().allocatePacket( getPacketPool().getLargestSize()); // Copy the existing receive data to the new packet rxlen = pkt.getBufferLength(); System.arraycopy(pkt.getBuffer(), 4, pkt2.getBuffer(), 4, rxlen - 4); // Release the original packet buffer, switch to the new packet getPacketPool().releasePacket( pkt); pkt = pkt2; // Read the remaining data rxlen2 = m_sessSock.read( pkt.getBuffer(), rxlen, pkt.getBufferLength() - rxlen); // Update the total received length if ( rxlen2 == ReceiveBufferSizeError) { // Release the packet back to the pool getPacketPool().releasePacket( pkt); // Throw an exception throw new RuntimeException("Winsock NetBIOS receive error on second stage receive"); } // Update the total receive length rxlen += rxlen2 - 4; } } } catch ( WinsockNetBIOSException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println( ex); // Check the Winsock error code if ( ex.getErrorCode() != 0) { // Release the packet back to the pool getPacketPool().releasePacket( pkt); // Clear the received packet to indicate error pkt = null; // Rethrow the exception throw ex; } else { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("Winsock error code zero, ignored, rxlen=" + rxlen + ", pktlen=" + pkt.getBuffer().length); // Indicate a zero length receive rxlen = 0; } } catch ( IOException ex) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println( ex); // Release the packet back to the pool getPacketPool().releasePacket( pkt); // Clear the received packet to indicate error pkt = null; // Rethrow the exception throw ex; } // Set the received packet length if ( pkt != null) pkt.setReceivedLength( rxlen); // Return the received packet return pkt; } /** * Write a packet to the client * * @param pkt SMBSrvPacket * @param len int * @param writeRaw boolean * @throws IOException */ public void writePacket(SMBSrvPacket pkt, int len, boolean writeRaw) throws IOException { // If asynchronous mode is enabled and the queue is not empty then queue this write request if ( hasAsynchronousMode() && m_asyncQueue.numberOfPackets() > 0) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("*** Queued packet for async I/O pkt=" + pkt.getPacketTypeString() + ", len=" + len + ", raw=" + writeRaw + " (QueueLen) ***"); // Queue the request m_asyncQueue.addToQueue(pkt, 4, len, writeRaw); return; } // Output the packet via the Winsock NetBIOS socket // // As Windows is handling the NetBIOS session layer we do not send the 4 byte header that is // used by the NetBIOS over TCP/IP and native SMB packet handlers. int pos = 4; int wrlen = len; int txlen = 0; while ( wrlen > 0) { // Write the packet txlen = m_sessSock.write(pkt.getBuffer(), pos, wrlen); // Check if asynchronous mode is enabled and the socket would block, in this case we queue the response // to be sent when the socket signals that it is writeable if ( hasAsynchronousMode() && txlen == NetBIOSSocket.SocketWouldBlock) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("*** Queued packet for async I/O pkt=" + pkt.getPacketTypeString() + ", len=" + len + ", raw=" + writeRaw + " (WouldBlock) ***"); // Queue the request m_asyncQueue.addToQueue(pkt, pos, wrlen, writeRaw); return; } // If the write length is zero wait a short while before retrying else if ( txlen == 0) { try { Thread.sleep( 10); // DEBUG // Debug.println( "*** Zero length write, wait 10ms wrlen=" + wrlen + ", pktType=" + pkt.getPacketTypeString() + " ***"); } catch ( InterruptedException ex) { } } else { // Adjust the remaining write length wrlen -= txlen; pos += txlen; } } // Do not check the status, if the session has been closed the next receive will fail } /** * Flush the output socket * * @exception IOException If a network error occurs */ public void flushPacket() throws IOException { // Nothing to do } /** * Close the Winsock NetBIOS packet handler. */ public void closeHandler() { super.closeHandler(); // Release any queued packets back to the pool if ( hasAsynchronousMode()) { // Release queued writes back to the packet pool while ( m_asyncQueue.numberOfPackets() > 0) { // Get a packet from the queue and release back to the packet pool QueuedSMBPacket queuedPkt = m_asyncQueue.removeFromQueue(); getPacketPool().releasePacket( queuedPkt.getPacket()); } } // Close the session socket if ( m_sessSock != null) m_sessSock.closeSocket(); } /** * Return the count of queued writes * * @return int */ public int getQueuedWriteCount() { // Check if the asynchronous mode is enabled if ( hasAsynchronousMode() == false || m_asyncQueue == null) return 0; return m_asyncQueue.numberOfPackets(); } /** * Process the write queue and send pending data until outgoing buffers are full * * @return int Number of requests that were removed from the queue */ public int processQueuedWrites() { // Process the queued write requests int procCnt = 0; if ( m_asyncQueue != null) { // Loop until the queue is emptied or the socket buffer is full again boolean wouldBlock = false; while ( m_asyncQueue.numberOfPackets() > 0 && wouldBlock == false) { // Get a request from the queue, leave the request on the queue until the send is successful QueuedSMBPacket queuedPkt = m_asyncQueue.getHeadOfQueue(); // Output the packet via the Winsock NetBIOS socket int pos = queuedPkt.getWriteOffset(); int wrlen = queuedPkt.getWriteLength(); int txlen = 0; while ( wrlen > 0 && txlen != NetBIOSSocket.SocketWouldBlock) { // Write the packet synchronized ( m_sessSock) { try { txlen = m_sessSock.write( queuedPkt.getPacket().getBuffer(), pos, wrlen); } catch ( WinsockNetBIOSException ex) { // Socket error, stop processing the queue txlen = NetBIOSSocket.SocketWouldBlock; } } // Check if the write status indicates the socket would block, in this case we update the queued request and exit if ( txlen == NetBIOSSocket.SocketWouldBlock) { // DEBUG if ( hasDebug()) Debug.println("*** Process queued writes sts=WouldBlock ***"); // Update the request if part of the buffer was sent if ( pos != queuedPkt.getWriteOffset()) { // Update the write offset and length with the new values queuedPkt.updateSettings( pos, wrlen); } } else { // Adjust the remaining write length wrlen -= txlen; pos += txlen; } } // Set the I/O blocking flag if ( txlen == NetBIOSSocket.SocketWouldBlock) { // Stop processing the queue, socket buffer is full again wouldBlock = true; } else if ( wrlen == 0) { // Queued request has been sent, remove it from the queue m_asyncQueue.removeFromQueue(); // DEBUG if ( hasDebug()) Debug.println("*** Sent queued pkt=" + queuedPkt.getPacket() + ", len=" + queuedPkt.getWriteLength() + ", offset=" + queuedPkt.getWriteOffset()); // Release the packet back to the pool queuedPkt.getPacket().setQueuedForAsyncIO( false); getPacketPool().releasePacket( queuedPkt.getPacket()); // Update the count of packets sent procCnt++; } } } // Return the count of write requests that were removed from the queue return procCnt; } }