Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/neuron/jaffer/AFP_Session.java


1   /* 
2    * Copyright (c) 2003 Stewart Allen <stewart@neuron.com>. All rights reserved.
3    * This program is free software. See the 'License' file for details.
4    */
5   
6   package com.neuron.jaffer;
7   
8   import java.io.*;
9   import java.net.*;
10  import java.util.*;
11  import java.math.*;
12  
13  /*
14   * The session server is currently comprised of a listener thread,
15   * a command-handler thread and (soon) a sender thread. This should be
16   * re-written (or abstracted) at some point to take advantage of Java 1.4
17   * NIO facilities.
18   * 
19   * TODO: session: nio, locking
20   * TODO: osvolume: eliminate most file caching. solve dir id's w/ id db.
21   * TODO: open/close dir deprecated. perhaps remove support.
22   */
23  
24  final class AFP_Session extends Utility implements AFP_Constants
25  {
26    private final static boolean printOnlyUnknown = System.getProperty("debug.pou") != null;
27    private final static boolean nothreads = System.getProperty("nothreads") != null;
28    private final static Random random = new Random();;
29    private final static BigInteger bigOne = new BigInteger("1");
30    private final static BigInteger bigMask = new BigInteger("ffffffffffffffff", 16);
31    private final static BigInteger serverPrivate = new BigInteger(256, random);
32    private final static BigInteger serverPublic = DHX_G.modPow(serverPrivate, DHX_P);
33    private final static byte[] DHX_Encode = "CJalbert".getBytes();
34    private final static byte[] DHX_Decode = "LWallace".getBytes();
35  
36    private final static int MODE_OLD  = 0;
37    private final static int MODE_EXT  = 1;
38    private final static int MODE_NONE = 2;
39  
40    private AFP_Server server;
41    private Socket socket;
42    private InputStream input;
43    private OutputStream output;
44    private Thread recvThread;
45    private Thread cmmdThread;
46    private Thread sendThread;
47    private int maxAttnQuantum;
48    private int nextReqID = 0x1;
49    private int nextForkID = 0x1;
50    private int maxPacket = 0x8000;
51    private Hashtable openForks;
52    private CommandQueue cmmdQueue;
53    private CommandQueue sendQueue;
54    private boolean running;
55    private boolean validated;
56    private String userName;
57    private Stack packets;
58    // for UAMs
59    private long randNum;
60    private int loginType;
61    private BigInteger nonce;
62    private BigInteger sessionKey;
63    private BigInteger clientPublic;
64  
65    // ----------------------------------------------------------------------------------------
66  
67    AFP_Session(AFP_Server server, Socket socket)
68    {
69      this.server = server;
70      this.socket = socket;
71      this.openForks = new Hashtable();
72      this.cmmdQueue = new CommandLoop();
73      this.sendQueue = new SendLoop();
74      this.packets = new Stack();
75    }
76  
77    public synchronized void start()
78    {
79      if (running)
80      {
81        return;
82      }
83      recvThread = new Thread(new ReceiveLoop(), "AFP Session ["+getSessionID()+"] Receiver");
84      cmmdThread = new Thread(cmmdQueue, "AFP Session ["+getSessionID()+"] Command Dispatch");
85      sendThread = new Thread(sendQueue, "AFP Session ["+getSessionID()+"] Sender");
86      recvThread.start();
87      cmmdThread.start();
88      sendThread.start();
89      running = true;
90    }
91  
92    private void print(String msg)
93    {
94      if (server.DEBUG_PRINT)
95      {
96        System.out.println(msg);
97      }
98    }
99  
100   private void debug(String msg)
101   {
102     if (server.DEBUG_DEBUG)
103     {
104       print("-- "+msg);
105     }
106   }
107 
108   private void error(boolean value, String msg)
109     throws Exception
110   {
111     if (value)
112     {
113       throw new Exception(msg);
114     }
115   }
116 
117   private void printPacket(DSI_Packet dp)
118   {
119     synchronized (socket)
120     {
121       if (dp.isRequest())
122       {
123         if (server.DEBUG_DSI_LINE)
124         {
125           print("---["+getSessionID()+"]-----------------------------------------------------------");
126         }
127         if (server.DEBUG_DSI)
128         {
129           print("<< client "+dp);
130         }
131         if (server.DEBUG_DSI_REQUEST)
132         {
133           dp.dumpRecvPayload(" < ");
134         }
135       }
136       else
137       {
138         if (server.DEBUG_DSI)
139         {
140           print(">> server "+dp);
141         }
142         if (server.DEBUG_DSI_REPLY)
143         {
144           dp.dumpSendPayload(" > ");
145         }
146       }
147     }
148   }
149 
150   private DSI_Packet recvPacket()
151     throws IOException
152   {
153     DSI_Packet dp = null;
154     synchronized (packets)
155     {
156       if (packets.empty())
157       {
158         dp = new DSI_Packet(maxPacket);
159       }
160       else
161       {
162         dp = (DSI_Packet)packets.pop();
163       }
164     }
165     dp.reset();
166     synchronized (input)
167     {
168       dp.read(input);
169     }
170     return dp;
171   }
172 
173   private void sendPacket(DSI_Packet dp)
174     throws IOException
175   {
176     synchronized (output)
177     {
178       dp.write(output);
179     }
180     // do not reuse packets that are too small
181     if (dp.getBufferSize() >= maxPacket)
182     {
183       synchronized (packets)
184       {
185         packets.push(dp);
186       }
187     }
188     if (!printOnlyUnknown)
189     {
190       printPacket(dp);
191     }
192   }
193 
194   private int getSessionID()
195   {
196     return socket.getPort();
197   }
198 
199   private int nextRequestID()
200   {
201     nextReqID++;
202     if (nextReqID > 0xffff)
203     {
204       nextReqID = 0;
205     }
206     return nextReqID;
207   }
208 
209   private int nextForkID()
210   {
211     nextForkID++;
212     if (nextForkID > 0xffff)
213     {
214       nextForkID = 0;
215     }
216     return nextForkID;
217   }
218 
219   private synchronized void terminateSession()
220   {
221     if (!running)
222     {
223       return;
224     }
225     //debug("["+getSessionID()+"] Session Terminating");
226     print("session ["+getSessionID()+"] terminating");
227     recvThread.interrupt();
228     cmmdThread.interrupt();
229     synchronized (cmmdThread)
230     {
231       cmmdThread.notify();
232     }
233     sendThread.interrupt();
234     synchronized (sendThread)
235     {
236       sendThread.notify();
237     }
238     try
239     {
240       socket.getInputStream().close();
241       socket.getOutputStream().close();
242       socket.close();
243     }
244     catch (Exception ex)
245     {
246       //ex.printStackTrace();
247     }
248     running = false;
249   }
250 
251   // ----------------------------------------------------------------------------------------
252 
253   private abstract class CommandQueue extends Queue implements Runnable
254   {
255     public void run()
256     {
257       while (true)
258       {
259         DSI_Packet dp = (DSI_Packet)dequeue();
260         if (dp == null)
261         {
262           break;
263         }
264         try
265         {
266           handleCommand(dp);
267         }
268         catch (IOException ex)
269         {
270           ex.printStackTrace();
271           break;
272         }
273       }
274       //debug("["+getSessionID()+"] Loop ("+getClass()+") Terminated");
275       terminateSession();
276     }
277 
278     public abstract void handleCommand(DSI_Packet dp)
279       throws IOException
280       ;
281   }
282 
283   // ----------------------------------------------------------------------------------------
284 
285   private class ReceiveLoop implements Runnable
286   {
287     public void run()
288     {
289       try
290       {
291         input = socket.getInputStream();
292         output = socket.getOutputStream();
293         print("session ["+getSessionID()+"] started");
294         boolean valid = true;
295         while (valid)
296         {
297           DSI_Packet dp = recvPacket();
298           if (!printOnlyUnknown)
299           {
300             printPacket(dp);
301           }
302           if (dp.isReply())
303           {
304             continue;
305           }
306           switch (dp.getCommand())
307           {
308             case DSI_Constants.CMD_GET_STATUS:
309               dp.setReply();
310               server.getServerInfo().write(dp.getWriter());
311               //dp.getWriter().writeBytes(server.getServerInfo().encode());
312               sendPacket(dp);
313               break;
314             case DSI_Constants.CMD_OPEN_SESSION:
315               ByteReader rr = dp.getReader();
316               int cmd = rr.readUnsignedByte();
317               int opt = rr.readUnsignedByte();
318               int qnt = rr.readInt();
319               switch (cmd)
320               {
321                 // sent by a client
322                 case DSI_Constants.OPT_ATTN_QUANT:
323                   error(opt != 4, "Option length != 4");
324                   maxAttnQuantum = qnt;
325                   dp.setReply();
326                   ByteWriter ww = dp.getWriter();
327                   ww.writeByte(DSI_Constants.OPT_SERV_QUANT);
328                   ww.writeByte(4);        // length
329                   ww.writeInt(maxPacket); // server quantum (max client request)
330                   sendPacket(dp);
331                   break;
332                 // sent by a server
333                 case DSI_Constants.OPT_SERV_QUANT:
334                   print("We are not a client");
335                   terminateSession();
336                   break;
337                 default:
338                   print("Unknown type: "+hex(cmd));
339                   terminateSession();
340                   break;
341               }
342               break;
343             case DSI_Constants.CMD_WRITE:
344             case DSI_Constants.CMD_COMMAND:
345               if (nothreads)
346               {
347                 cmmdQueue.handleCommand(dp);
348               }
349               else
350               {
351                 cmmdQueue.enqueue(dp);
352               }
353               break;
354             case DSI_Constants.CMD_CLOSE_SESSION:
355               valid = false;
356               break;
357             case DSI_Constants.CMD_ATTENTION:
358               // we are not a client
359               break;
360             case DSI_Constants.CMD_TICKLE:
361               dp.setRequestID(nextRequestID());
362               sendPacket(dp);
363               break;
364             default:
365               print("!!!!! Invalid DSI command: "+hex(dp.getCommand())+" !!!!!");
366               dp.setReply();
367               sendPacket(dp);
368               return;
369           }
370         }
371       }
372       catch (Exception ex)
373       {
374         print("session ["+getSessionID()+"] error '"+ex+"'");
375         //ex.printStackTrace();
376       }
377       //print("** ["+getSessionID()+"] Receive Loop Exited");
378       terminateSession();
379     }
380   }
381 
382   // ----------------------------------------------------------------------------------------
383 
384   private class CommandLoop extends CommandQueue
385   {
386     public void handleCommand(DSI_Packet dp)
387       throws IOException
388     {
389       ByteReader rr = dp.getReader();
390       ByteWriter ww = dp.getWriter();
391       int err = ERR_NO_ERR;
392       int cmd = rr.readUnsignedByte();
393       try
394       {
395         // auth pre-check check
396         switch (cmd)
397         {
398           case CMD_LOGIN:
399           case CMD_LOGIN_EXT:
400           case CMD_LOGIN_CONT:
401             break;
402           default:
403             if (!validated)
404             {
405               throw new AFP_Error(ERR_PARAM_ERR);
406             }
407             break;
408         }
409         // command dispatch
410         switch (cmd)
411         {
412           case CMD_LOGIN:              err = cmdLogin(rr, ww);            break;
413           case CMD_LOGIN_EXT:          err = cmdLoginExt(rr, ww);         break;
414           case CMD_LOGIN_CONT:         err = cmdLoginCont(rr, ww);        break;
415           case CMD_LOGOUT:             err = cmdLogout(rr, ww);           break;
416           case CMD_MAP_ID:             err = cmdMapID(rr, ww);            break;
417           case CMD_CREATE_DIR:         err = cmdCreateDir(rr, ww);        break;
418           case CMD_CREATE_FILE:        err = cmdCreateFile(rr, ww);       break;
419           case CMD_CREATE_ID:          err = ERR_PARAM_ERR;               break; // TODO
420           case CMD_GET_USER_INFO:      err = cmdGetUserInfo(rr, ww);      break;
421           case CMD_GET_SRVR_PARMS:     err = cmdGetServerParams(rr, ww);  break;
422           case CMD_GET_VOL_PARMS:      err = cmdGetVolumeParams(rr, ww);  break;
423           case CMD_GET_FILE_DIR_PARMS: err = cmdGetFileDirParams(rr, ww); break;
424           case CMD_GET_SESSION_TOKEN:  err = cmdGetSessionToken(rr, ww);  break;
425           case CMD_SET_VOL_PARMS:      err = ERR_NO_ERR;                  break; // TODO
426           case CMD_SET_DIR_PARMS:      err = cmdSetDirParams(rr, ww);     break;
427           case CMD_SET_FORK_PARMS:     err = cmdSetForkParams(rr, ww);    break;
428           case CMD_SET_FILE_PARMS:     err = cmdSetFileParams(rr, ww);    break;
429           case CMD_SET_FILE_DIR_PARMS: err = cmdSetFileDirParams(rr, ww); break;
430           case CMD_OPEN_DT:            err = ERR_MISC_ERR;                break; // TODO
431           case CMD_OPEN_VOL:           err = cmdOpenVolume(rr, ww);       break;
432           case CMD_OPEN_DIR:           err = cmdOpenDir(rr, ww);          break;
433           case CMD_OPEN_FORK:          err = cmdOpenFork(rr, ww);         break;
434           case CMD_ENUMERATE:          err = cmdEnumerate(rr, ww);        break;
435           case CMD_ENUMERATE_EXT2:     err = cmdEnumerateExt2(rr, ww);    break;
436           case CMD_READ:               err = cmdRead(rr, ww);             break;
437           case CMD_READ_EXT:           err = cmdReadExt(rr, ww);          break;
438           case CMD_WRITE:              err = cmdWrite(rr, ww);            break;
439           case CMD_WRITE_EXT:          err = cmdWriteExt(rr, ww);         break;
440           case CMD_FLUSH_FORK:         err = cmdFlushFork(rr, ww);        break;
441           case CMD_CLOSE_VOL:          err = cmdCloseVolume(rr, ww);      break;
442           case CMD_CLOSE_DIR:          err = cmdCloseDir(rr, ww);         break;
443           case CMD_CLOSE_FORK:         err = cmdCloseFork(rr, ww);        break;
444           case CMD_MOVE_AND_RENAME:    err = cmdMoveAndRename(rr, ww);    break;
445           case CMD_RENAME:             err = cmdRename(rr, ww);           break;
446           case CMD_DELETE:             err = cmdDelete(rr, ww);           break;
447           default:
448             print("!!!!! Unhandled AFP command: "+hex(cmd)+" !!!!!");
449             if (printOnlyUnknown)
450             {
451               printPacket(dp);
452             }
453             err = ERR_CALL_NOT_SUPPORTED;
454             break;
455         }
456       }
457       catch (AFP_Error ae)
458       {
459         err = ae.getError();
460       }
461       catch (IOException ex)
462       {
463         err = ERR_MISC_ERR;
464         ex.printStackTrace();
465       }
466       catch (Exception ex)
467       {
468         // TODO: we should perhaps shut down the session here
469         err = ERR_MISC_ERR;
470         ex.printStackTrace();
471       }
472       dp.setErrorCode(err);
473       dp.setReply();
474       if (nothreads)
475       {
476         sendPacket(dp);
477       }
478       else
479       {
480         sendQueue.enqueue(dp);
481       }
482     }
483   }
484 
485   // ----------------------------------------------------------------------------------------
486 
487   private class SendLoop extends CommandQueue
488   {
489     public void handleCommand(DSI_Packet dp)
490       throws IOException
491     {
492       sendPacket(dp);
493     }
494   }
495 
496   // ----------------------------------------------------------------------------------------
497 
498   // CMD_LOGIN
499   private int cmdLogin(ByteReader rr, ByteWriter ww)
500     throws IOException
501   {
502     //note: pad byte not being sent from OSX per spec
503     String afp = rr.readPString();
504     String uam = rr.readPString();
505     debug("login afp="+afp+",uam="+uam+",ssn="+getSessionID());
506     return loginCommon(uam, null, rr, ww);
507   }
508 
509   // CMD_LOGIN_EXT
510   private int cmdLoginExt(ByteReader rr, ByteWriter ww)
511     throws IOException
512   {
513     rr.skip(1);
514     int flags = rr.readUnsignedShort();
515     String afp = rr.readPString();
516     String uam = rr.readPString();
517     String usName = rr.readTypedString();
518     String paName = rr.readTypedString();
519     rr.skipBytes(rr.getPosition()%2);
520     debug("loginext afp="+afp+",uam="+uam+",user="+usName+",path="+paName+",ssn="+getSessionID());
521     return loginCommon(uam, usName, rr, ww);
522   }
523 
524   // login common helper method
525   private int loginCommon(String uam, String user, ByteReader rr, ByteWriter ww)
526     throws IOException
527   {
528     ww.writeShort(getSessionID());
529     loginType = getUAM(uam);
530     switch (loginType)
531     {
532       case UAM_GUEST:
533         user = server.getGuestUser();
534         if (user != null && server.hasUser(user))
535         {
536           userName = user;
537           validated = true;
538         }
539         else
540         {
541           return ERR_USER_NOT_AUTH;
542         }
543         break;
544       case UAM_CLEARTEXT:
545         if (user == null)
546         {
547           user = rr.readPString();
548         }
549         rr.skip(rr.getPosition() % 2);
550         String pass = rr.readCString(8);
551         if (!server.checkPassword(user, pass))
552         {
553           return ERR_USER_NOT_AUTH;
554         }
555         userName = user;
556         server.setThreadOwner(userName);
557         validated = true;
558         break;
559       case UAM_RANDOM_NUM1:
560         if (!server.hasCleartextPasswords())
561         {
562           return ERR_BAD_UAM;
563         }
564         if (user == null)
565         {
566           user = rr.readPString();
567         }
568         if (!server.hasUser(user))
569         {
570           return ERR_USER_NOT_AUTH;
571         }
572         userName = user;
573         randNum = random.nextLong();
574         ww.writeLong(randNum);
575         return ERR_AUTH_CONTINUE;
576       case UAM_DHX_128:
577         if (user == null)
578         {
579           user = rr.readPString();
580         }
581         if (!server.hasUser(user))
582         {
583           return ERR_USER_NOT_AUTH;
584         }
585         rr.skip(rr.getPosition() % 2);
586         clientPublic = new BigInteger(1, rr.readBytes(16));
587         sessionKey = clientPublic.modPow(serverPrivate, DHX_P);
588         nonce = new BigInteger(128, random).abs();
589         byte inbuf[] = new byte[32];
590         byte outbuf[] = new byte[32];
591         System.arraycopy(keyBytes(nonce),0,inbuf,0,16);
592         new CAST128(keyBytes(sessionKey)).encrypt(inbuf, 0, outbuf, 0, 32, DHX_Encode);
593         ww.writeBytes(keyBytes(serverPublic));
594         ww.writeBytes(outbuf);
595         userName = user;
596         return ERR_AUTH_CONTINUE;
597       case UAM_UNKNOWN:
598       default:
599         return ERR_BAD_UAM;
600     }
601     return ERR_NO_ERR;
602   }
603 
604   // CMD_LOGIN_CONT
605   private int cmdLoginCont(ByteReader rr, ByteWriter ww)
606     throws IOException
607   {
608     rr.skip(1);
609     int context = rr.readUnsignedShort();
610     debug("logincont context="+hex(context));
611     switch (loginType)
612     {
613       case UAM_RANDOM_NUM1:
614         if (!server.hasCleartextPasswords())
615         {
616           return ERR_BAD_UAM;
617         }
618         byte[] cryptRand = rr.readBytes(8);
619         debug("crypted random = "+hex(cryptRand));
620         String pass = server.getPassword(userName);
621         if (pass != null)
622         {
623           byte pb[] = new byte[8];
624           System.arraycopy(pass.getBytes(), 0, pb, 0, pass.length());
625           new DES(pb).decrypt(cryptRand);
626           if (readInt8(cryptRand, 0) != randNum)
627           {
628             return ERR_USER_NOT_AUTH;
629           }
630         }
631         server.setThreadOwner(userName);
632         validated = true;
633         break;
634       case UAM_DHX_128:
635         byte inbuf[] = new byte[16+64];
636         byte outbuf[] = new byte[16+64];
637         rr.readBytes(inbuf);
638         CAST128 c = new CAST128(keyBytes(sessionKey));
639         c.decrypt(inbuf, 0, outbuf, 0, 32, DHX_Decode);
640         byte nonceb[] = new byte[16];
641         System.arraycopy(outbuf, 0, nonceb, 0, 16);
642         BigInteger noncep1 = new BigInteger(1, nonceb);
643         //TODO: it appears that after the first auth, only the
644         // last 8 bytes are incremented by the client. thus the 'and'
645         if (!noncep1.subtract(nonce).and(bigMask).equals(bigOne))
646         {
647           return ERR_PARAM_ERR;
648         }
649         pass = readCString(outbuf, 16);
650         if (!server.checkPassword(userName, pass))
651         {
652           return ERR_USER_NOT_AUTH;
653         }
654         server.setThreadOwner(userName);
655         validated = true;
656         break;
657       case UAM_UNKNOWN:
658       case UAM_GUEST:
659       case UAM_CLEARTEXT:
660       default:
661         return ERR_BAD_UAM;
662     }
663     return ERR_NO_ERR;
664   }
665 
666   // CMD_LOGOUT
667   private int cmdLogout(ByteReader rr, ByteWriter ww)
668     throws IOException
669   {
670     return ERR_NO_ERR;
671   }
672 
673   // CMD_GET_USER_INFO
674   private int cmdGetUserInfo(ByteReader rr, ByteWriter ww)
675     throws IOException
676   {
677     int thisUser = rr.readUnsignedByte();
678     int uid = rr.readInt();
679     int flags = rr.readUnsignedShort(); // bits: 0=user, 1=group
680     debug("getuserinfo this="+thisUser+",uid="+uid+",flags="+hex(flags));
681     ww.writeShort(flags);
682     if (hasBits(flags, 0x1)) { ww.writeInt(0x00000000); } // user = guest
683     if (hasBits(flags, 0x2)) { ww.writeInt(0x00000000); } // group = guest
684     return ERR_NO_ERR;
685   }
686 
687   // CMD_GET_SRVR_PARMS
688   private int cmdGetServerParams(ByteReader rr, ByteWriter ww)
689     throws IOException
690   {
691     AFP_Volume v[] = server.getVolumes();
692     ww.writeInt(unix2afpTime(System.currentTimeMillis()));
693     ww.writeByte(v.length); 
694     for (int i=0; i<v.length; i++)
695     {
696       ww.writeByte( ((v[i].hasUnixPrivs() ? 0x1 : 0) | (v[i].getPassword() != null ? 0x80 : 0)) );
697       ww.writePString(v[i].getName());
698     }
699     return ERR_NO_ERR;
700   }
701 
702   // CMD_OPEN_VOL
703   private int cmdOpenVolume(ByteReader rr, ByteWriter ww)
704     throws IOException
705   {
706     rr.skip(1);
707     int flags = rr.readUnsignedShort();
708     String volName = rr.readPString();
709     String volPass = rr.readCString(8);
710     debug("openvol name=("+volName+") pass=("+volPass+")");
711     AFP_Volume vol = server.getVolume(volName);
712     if (vol == null)
713     {
714       return ERR_OBJECT_NOT_FOUND;
715     }
716     String passCheck = vol.getPassword();
717     if (passCheck != null && (volPass == null || !passCheck.equals(volPass)))
718     {
719       return ERR_ACCESS_DENIED;
720     }
721     sendVolumeInfo(ww, vol, flags);
722     return ERR_NO_ERR;
723   }
724 
725   // CMD_DELETE
726   private int cmdDelete(ByteReader rr, ByteWriter ww)
727     throws IOException
728   {
729     rr.skip(1);
730     int volID = rr.readUnsignedShort();
731     int dirID = rr.readInt();
732     String pathName = rr.readTypedString();
733     debug("delete vol="+volID+",dir="+dirID+",path="+pathName);
734     AFP_CNode node = openPath(volID, dirID, pathName).getNode();
735     if (node.isDirectory() && node.getOffspringCount() > 0)
736     {
737       return ERR_DIR_NOT_EMPTY;
738     }
739     if (node.delete())
740     {
741       return ERR_NO_ERR;
742     }
743     else
744     {
745       return ERR_ACCESS_DENIED;
746     }
747   }
748 
749   // CMD_GET_FILE_DIR_PARMS
750   private int cmdGetFileDirParams(ByteReader rr, ByteWriter ww)
751     throws IOException
752   {
753     rr.skip(1);
754     int volID = rr.readUnsignedShort();
755     int dirID = rr.readInt();
756     int fileFlags = rr.readUnsignedShort();
757     int dirFlags = rr.readUnsignedShort();
758     String pathName = rr.readTypedString();
759 
760     debug("getparm vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",df="+hex(dirFlags)+",path="+pathName);
761 
762     AFP_CNode node = openPath(volID, dirID, pathName).getNode();
763     ww.writeShort(fileFlags);
764     ww.writeShort(dirFlags);
765     if (node.isDirectory())
766     {
767       sendDirectoryInfo(ww, node, dirFlags, MODE_EXT);
768     }
769     else
770     if (node.isFile())
771     {
772       sendFileInfo(ww, node, fileFlags, MODE_EXT);
773     }
774     return ERR_NO_ERR;
775   }
776 
777   // CMD_SET_FORK_PARMS
778   private int cmdSetForkParams(ByteReader rr, ByteWriter ww)
779     throws IOException
780   {
781     rr.skip(1);
782     int forkRef = rr.readUnsignedShort();
783     int flags = rr.readUnsignedShort();
784     int length = rr.readInt();
785     debug("setforkparms fork="+forkRef+",flags="+hex(flags)+",len="+hex(length));
786     // TODO -- validate fork type and make sure other flags aren't set.
787     getFork(forkRef).setLength(length);
788     return ERR_NO_ERR;
789   }
790 
791   // CMD_SET_FILE_PARMS
792   private int cmdSetFileParams(ByteReader rr, ByteWriter ww)
793     throws IOException
794   {
795     rr.skip(1);
796     int volID = rr.readUnsignedShort();
797     int dirID = rr.readInt();
798     int fileFlags = rr.readUnsignedShort();
799     String pathName = rr.readTypedString();
800     debug("setfileparm vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",path="+pathName);
801     AFP_CNode node = openPath(volID, dirID, pathName).getNode();
802     recvFileInfo(rr, node, fileFlags);
803     return ERR_NO_ERR;
804   }
805 
806   // CMD_SET_DIR_PARMS
807   private int cmdSetDirParams(ByteReader rr, ByteWriter ww)
808     throws IOException
809   {
810     rr.skip(1);
811     int volID = rr.readUnsignedShort();
812     int dirID = rr.readInt();
813     int dirFlags = rr.readUnsignedShort();
814     String pathName = rr.readTypedString();
815     debug("setdirparm vol="+volID+",dir="+dirID+",df="+hex(dirFlags)+",path="+pathName);
816     AFP_CNode node = openPath(volID, dirID, pathName).getNode();
817     recvDirectoryInfo(rr, node, dirFlags);
818     return ERR_NO_ERR;
819   }
820 
821   // CMD_SET_FILE_DIR_PARMS
822   private int cmdSetFileDirParams(ByteReader rr, ByteWriter ww)
823     throws IOException
824   {
825     rr.skip(1);
826     int volID = rr.readUnsignedShort();
827     int dirID = rr.readInt();
828     int flags = rr.readUnsignedShort();
829     String pathName = rr.readTypedString();
830     debug("setfiledirparm vol="+volID+",dir="+dirID+",df="+hex(flags)+",path="+pathName);
831     AFP_CNode node = openPath(volID, dirID, pathName).getNode();
832     if (node.isDirectory())
833     {
834       recvDirectoryInfo(rr, node, flags);
835     }
836     else
837     {
838       recvFileInfo(rr, node, flags);
839     }
840     return ERR_NO_ERR;
841   }
842 
843   // CMD_GET_VOL_PARMS
844   private int cmdGetVolumeParams(ByteReader rr, ByteWriter ww)
845     throws IOException
846   {
847     rr.skip(1);
848     int volID = rr.readUnsignedShort();
849     int flags = rr.readUnsignedShort();
850     AFP_Volume vol = server.getVolume(volID);
851     if (vol == null)
852     {
853       return ERR_PARAM_ERR;
854     }
855     sendVolumeInfo(ww, vol, flags);
856     return ERR_NO_ERR;
857   }
858 
859   // CMD_GET_SESSION_TOKEN
860   private int cmdGetSessionToken(ByteReader rr, ByteWriter ww)
861     throws IOException
862   {
863     rr.skip(1);
864     int type = rr.readUnsignedShort();
865     int dlen = rr.readInt();
866     ww.writeShort(type);
867     ww.writeInt(4);
868     ww.writeInt(0x77665544);
869     return ERR_NO_ERR;
870   }
871 
872   // CMD_CLOSE_VOL
873   private int cmdCloseVolume(ByteReader rr, ByteWriter ww)
874     throws IOException
875   {
876     rr.skip(1);
877     int volID = rr.readUnsignedShort();
878     return ERR_NO_ERR;
879   }
880 
881   // CMD_MAP_ID
882   private int cmdMapID(ByteReader rr, ByteWriter ww)
883     throws IOException
884   {
885     rr.skip(1);
886     int userID = rr.readInt();
887     ww.writePString("doodleman");
888     return ERR_NO_ERR;
889   }
890 
891   // CMD_CREATE_DIR
892   private int cmdCreateDir(ByteReader rr, ByteWriter ww)
893     throws IOException
894   {
895     rr.skip(1);
896     int volID = rr.readUnsignedShort();
897     int dirID = rr.readInt();
898     String pathName = rr.readTypedString();
899     debug("createdir vol="+volID+",dir="+dirID+",path="+pathName);
900     ww.writeInt(createDirPath(volID, dirID, pathName).getNode().getNodeID());
901     return ERR_NO_ERR;
902   }
903 
904   // CMD_CREATE_FILE
905   private int cmdCreateFile(ByteReader rr, ByteWriter ww)
906     throws IOException
907   {
908     rr.skip(1);
909     int volID = rr.readUnsignedShort();
910     int dirID = rr.readInt();
911     String pathName = rr.readTypedString();
912     debug("createfile vol="+volID+",dir="+dirID+",path="+pathName);
913     createFilePath(volID, dirID, pathName);
914     return ERR_NO_ERR;
915   }
916 
917   // CMD_OPEN_DIR
918   private int cmdOpenDir(ByteReader rr, ByteWriter ww)
919     throws IOException
920   {
921     rr.skip(1);
922     int volID = rr.readUnsignedShort();
923     int dirID = rr.readInt();
924     String pathName = rr.readTypedString();
925     debug("opendir vol="+hex(volID)+",dir="+hex(dirID)+",path="+pathName);
926     AFP_CNode node = openPath(volID, dirID, pathName).open().getNode();
927     if (!node.isDirectory())
928     {
929       return ERR_OBJECT_TYPE_ERR;
930     }
931     ww.writeInt(node.getNodeID());
932     return ERR_NO_ERR;
933   }
934 
935   // CMD_OPEN_FORK
936   private int cmdOpenFork(ByteReader rr, ByteWriter ww)
937     throws IOException
938   {
939     int which = rr.readUnsignedByte();  // bits: 7=fork (0=file, 1=resource)
940     int volID = rr.readUnsignedShort();
941     int dirID = rr.readInt();
942     int flags = rr.readUnsignedShort(); // bits: getFileParams bits
943     int mode = rr.readUnsignedShort();  // bits: 0=read, 1=write, 4=readlock, 5=writelock
944     String pathName = rr.readTypedString();
945     debug("openfork flags="+hex(flags)+",which="+hex(which)+",mode="+hex(mode)+",path="+pathName);
946     AFP_CNode node = openPath(volID, dirID, pathName).getNode();
947     if (node.isDirectory())
948     {
949       return ERR_OBJECT_TYPE_ERR;
950     }
951     int fid = nextForkID();
952     AFP_Fork fork = hasBits(which,0x80) ? node.openResourceFork(mode) : node.openFileFork(mode);
953     if (fork == null)
954     {
955       return ERR_ACCESS_DENIED;
956     }
957     debug("openfork ref="+hex(fid));
958     openForks.put(new Integer(fid), fork);
959     ww.writeShort(flags);
960     ww.writeShort(fid);
961     sendFileInfo(ww, node, flags, MODE_NONE);
962     return ERR_NO_ERR;
963   }
964 
965   // CMD_RENAME
966   private int cmdRename(ByteReader rr, ByteWriter ww)
967     throws IOException
968   {
969     rr.skip(1);
970     int volID = rr.readUnsignedShort();
971     int dirID = rr.readInt();
972     String path = rr.readTypedString();
973     String newName = rr.readTypedString();
974     debug("rename vol="+volID+",dir="+dirID+",path="+path+",newName="+newName);
975     Path dir = openPath(volID, dirID, path);
976     if (!dir.getNode().moveTo(dir.getNode(), newName))
977     {
978       return ERR_ACCESS_DENIED;
979     }
980     return ERR_NO_ERR;
981   }
982 
983   // CMD_MOVE_AND_RENAME
984   private int cmdMoveAndRename(ByteReader rr, ByteWriter ww)
985     throws IOException
986   {
987     rr.skip(1);
988     int volID = rr.readUnsignedShort();
989     int srcDirID = rr.readInt();
990     int dstDirID = rr.readInt();
991     String srcPath = rr.readTypedString();
992     String dstPath = rr.readTypedString();
993     String newName = rr.readTypedString();
994     debug("move/rename vol="+volID+",srcDir="+srcDirID+",dstDir="+dstDirID+",srcPath="+srcPath+",dstPath="+dstPath+",name="+newName);
995     Path src = openPath(volID, srcDirID, srcPath);
996     Path dst = openPath(volID, dstDirID, dstPath);
997     if (!src.getNode().moveTo(dst.getNode(), newName))
998     {
999       return ERR_ACCESS_DENIED;
1000    }
1001    return ERR_NO_ERR;
1002  }
1003
1004  // CMD_READ
1005  private int cmdRead(ByteReader rr, ByteWriter ww)
1006    throws IOException
1007  {
1008    rr.skip(1);
1009    int forkRef = rr.readUnsignedShort();
1010    long offset = rr.readInt();
1011    long length = rr.readInt();
1012    int nlMask = rr.readUnsignedByte();
1013    int nlChar = rr.readUnsignedByte();
1014    debug("read fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length)+",nlm="+hex(nlMask)+",nlc="+hex(nlChar));
1015    getFork(forkRef).readRange(offset, length, ww);
1016    return ERR_NO_ERR;
1017  }
1018
1019  // CMD_READ_EXT
1020  private int cmdReadExt(ByteReader rr, ByteWriter ww)
1021    throws IOException
1022  {
1023    rr.skip(1);
1024    int forkRef = rr.readUnsignedShort();
1025    long offset = rr.readLong();
1026    long length = rr.readLong();
1027    debug("readx fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length));
1028    getFork(forkRef).readRange(offset, length, ww);
1029    return ERR_NO_ERR;
1030  }
1031
1032  // CMD_WRITE
1033  private int cmdWrite(ByteReader rr, ByteWriter ww)
1034    throws IOException
1035  {
1036    int flag = rr.readUnsignedByte(); // bits: 7=relative to (0=from start, 1=from end)
1037    int forkRef = rr.readUnsignedShort();
1038    long offset = rr.readInt();
1039    long length = rr.readInt();
1040    debug("write fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length));
1041    AFP_Fork fork = getFork(forkRef);
1042    if (offset < 0)
1043    {
1044      offset = fork.getLength() - offset;
1045    }
1046    if (flag == 0x80)
1047    {
1048      offset += fork.getLength();
1049    }
1050    long wrote = fork.writeRange(offset, length, rr);
1051    ww.writeInt((int)(offset + wrote));
1052    return ERR_NO_ERR;
1053  }
1054
1055  // CMD_WRITE_EXT
1056  private int cmdWriteExt(ByteReader rr, ByteWriter ww)
1057    throws IOException
1058  {
1059    int flag = rr.readUnsignedByte(); // bits: 7=relative to (0=from start, 1=from end)
1060    int forkRef = rr.readUnsignedShort();
1061    long offset = rr.readLong();
1062    long length = rr.readLong();
1063    debug("writex fork="+hex(forkRef)+",off="+hex(offset)+",len="+hex(length));
1064    AFP_Fork fork = getFork(forkRef);
1065    if (offset < 0)
1066    {
1067      offset = fork.getLength() - offset;
1068    }
1069    if (flag == 0x80)
1070    {
1071      offset += fork.getLength();
1072    }
1073    long wrote = fork.writeRange(offset, length, rr);
1074    ww.writeLong(offset + wrote);
1075    return ERR_NO_ERR;
1076  }
1077
1078  // CMD_FLUSH_FORK
1079  private int cmdFlushFork(ByteReader rr, ByteWriter ww)
1080    throws IOException
1081  {
1082    rr.skip(1);
1083    int forkRef = rr.readUnsignedShort();
1084    debug("flush fork="+hex(forkRef));
1085    getFork(forkRef).flush();
1086    return ERR_NO_ERR;
1087  }
1088
1089  // CMD_CLOSE_DIR
1090  private int cmdCloseDir(ByteReader rr, ByteWriter ww)
1091    throws IOException
1092  {
1093    rr.skip(1);
1094    int volID = rr.readUnsignedShort();
1095    int dirID = rr.readInt();
1096    debug("closedir vol="+hex(volID)+",dir="+hex(dirID));
1097    return ERR_NO_ERR;
1098  }
1099
1100  // CMD_CLOSE_FORK
1101  private int cmdCloseFork(ByteReader rr, ByteWriter ww)
1102    throws IOException
1103  {
1104    rr.skip(1);
1105    int forkRef = rr.readUnsignedShort();
1106    AFP_Fork fork = getFork(forkRef);
1107    openForks.remove(new Integer(forkRef));
1108    debug("closefork ref="+hex(forkRef)+" fork="+fork);
1109    fork.close();
1110    return ERR_NO_ERR;
1111  }
1112
1113  // TODO: uses too many byte writers. optimize byte writers and re-use.
1114  // CMD_ENUMERATE
1115  private int cmdEnumerate(ByteReader rr, ByteWriter ww)
1116    throws IOException
1117  {
1118    rr.skip(1);
1119    int volID = rr.readUnsignedShort();
1120    int dirID = rr.readInt();
1121    int fileFlags = rr.readUnsignedShort();
1122    int dirFlags = rr.readUnsignedShort();
1123    int maxRecords = rr.readUnsignedShort();
1124    int startIndex = (rr.readUnsignedShort() - 1);
1125    int maxReply = rr.readUnsignedShort();
1126    String pathName = rr.readTypedString();
1127
1128    debug("enum vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",df="+hex(dirFlags)+",xrec="+hex(maxRecords)+",idx="+hex(startIndex)+",xrep="+hex(maxReply)+",path="+pathName);
1129
1130    AFP_CNode node = openPath(volID, dirID, pathName).getNode();
1131    int numOffspring = node.getOffspringCount();
1132    int returnRecords = Math.min(maxRecords, numOffspring - startIndex);
1133    if (returnRecords <= 0)
1134    {
1135      return ERR_OBJECT_NOT_FOUND;
1136    }
1137    ww.writeShort(fileFlags);
1138    ww.writeShort(dirFlags);
1139    Enumeration en = node.getOffspringEnumeration();
1140    if (en == null)
1141    {
1142      return ERR_MISC_ERR;
1143    }
1144    int sent = 0;
1145    ByteWriter w3 = new ByteWriter(maxReply);
1146    for (int i=0; i<numOffspring; i++)
1147    {
1148      if (!en.hasMoreElements())
1149      {
1150        return ERR_OBJECT_NOT_FOUND;
1151      }
1152      AFP_CNode next = (AFP_CNode)en.nextElement();
1153      if (i < startIndex)
1154      {
1155        continue;
1156      }
1157      ByteWriter w2 = new ByteWriter(128);
1158      if (next.isDirectory())
1159      {
1160        sendDirectoryInfo(w2, next, dirFlags, MODE_OLD);
1161      }
1162      else
1163      if (next.isFile())
1164      {
1165        sendFileInfo(w2, next, fileFlags, MODE_OLD);
1166      }
1167      else
1168      {
1169        // faulty AFP_CNode implementation
1170        debug("!! node is neither file nor directory !! "+next);
1171        continue;
1172      }
1173      byte data[] = w2.toByteArray();
1174      if (data.length % 2 == 0)
1175      {
1176        w3.writeByte(data.length+2);
1177        w3.writeBytes(data);
1178        w3.writeByte(0);
1179      }
1180      else
1181      {
1182        w3.writeByte(data.length+1);
1183        w3.writeBytes(data);
1184      }
1185      sent++;
1186      if (w3.getSize() > maxReply - 128)
1187      {
1188        break;
1189      }
1190    }
1191
1192    ww.writeShort(sent);
1193    ww.writeBytes(w3.toByteArray());
1194    return ERR_NO_ERR;
1195  }
1196
1197  // TODO: uses too many byte writers. optimize byte writers and re-use.
1198  // CMD_ENUMERATE_EXT2
1199  private int cmdEnumerateExt2(ByteReader rr, ByteWriter ww)
1200    throws IOException
1201  {
1202    rr.skip(1);
1203    int volID = rr.readUnsignedShort();
1204    int dirID = rr.readInt();
1205    int fileFlags = rr.readUnsignedShort();
1206    int dirFlags = rr.readUnsignedShort();
1207    int maxRecords = rr.readUnsignedShort();
1208    int startIndex = (rr.readInt() - 1);
1209    int maxReply = rr.readInt();
1210    String pathName = rr.readTypedString();
1211
1212    debug("enum vol="+volID+",dir="+dirID+",ff="+hex(fileFlags)+",df="+hex(dirFlags)+",xrec="+hex(maxRecords)+",idx="+hex(startIndex)+",xrep="+hex(maxReply)+",path="+pathName);
1213
1214    AFP_CNode node = openPath(volID, dirID, pathName).getNode();
1215    int numOffspring = node.getOffspringCount();
1216    int returnRecords = Math.min(maxRecords, numOffspring - startIndex);
1217    if (returnRecords <= 0)
1218    {
1219      return ERR_OBJECT_NOT_FOUND;
1220    }
1221    ww.writeShort(fileFlags);
1222    ww.writeShort(dirFlags);
1223    Enumeration en = node.getOffspringEnumeration();
1224    if (en == null)
1225    {
1226      return ERR_MISC_ERR;
1227    }
1228    int sent = 0;
1229    ByteWriter w3 = new ByteWriter(maxReply);
1230    for (int i=0; i<numOffspring; i++)
1231    {
1232      if (!en.hasMoreElements())
1233      {
1234        return ERR_OBJECT_NOT_FOUND;
1235      }
1236      AFP_CNode next = (AFP_CNode)en.nextElement();
1237      if (i < startIndex)
1238      {
1239        continue;
1240      }
1241      ByteWriter w2 = new ByteWriter(128);
1242      if (next.isDirectory())
1243      {
1244        sendDirectoryInfo(w2, next, dirFlags, MODE_EXT);
1245      }
1246      else
1247      if (next.isFile())
1248      {
1249        sendFileInfo(w2, next, fileFlags, MODE_EXT);
1250      }
1251      else
1252      {
1253        // faulty AFP_CNode implementation
1254        debug("!! node is neither file nor directory !! "+next);
1255        continue;
1256      }
1257      byte data[] = w2.toByteArray();
1258      if (data.length % 2 == 0)
1259      {
1260        w3.writeShort(data.length+2);
1261        w3.writeBytes(data);
1262      }
1263      else
1264      {
1265        w3.writeShort(data.length+3);
1266        w3.writeBytes(data);
1267        w3.writeByte(0);
1268      }
1269      sent++;
1270      if (w3.getSize() > maxReply - 128)
1271      {
1272        break;
1273      }
1274    }
1275
1276    ww.writeShort(sent);
1277    ww.writeBytes(w3.toByteArray());
1278    return ERR_NO_ERR;
1279  }
1280
1281  // ----------------------------------------------------------------------------------------
1282
1283  // USED BY: OpenVolume and GetVolumeParams
1284  private void sendVolumeInfo(ByteWriter ww, AFP_Volume vol, int flags)
1285    throws IOException
1286  {
1287    ww.writeShort(flags);
1288    ww.markDeferredOffset();
1289    if (hasBits(flags, VOL_BIT_ATTRIBUTE))    { ww.writeShort(vol.getAttributes());     }
1290    if (hasBits(flags, VOL_BIT_SIGNATURE))    { ww.writeShort(vol.getSignature());      }
1291    if (hasBits(flags, VOL_BIT_CREATE_DATE))  { ww.writeInt(vol.getCreateDate());       }
1292    if (hasBits(flags, VOL_BIT_MOD_DATE))     { ww.writeInt(vol.getModifiedDate());     }
1293    if (hasBits(flags, VOL_BIT_BACKUP_DATE))  { ww.writeInt(vol.getBackupDate());       }
1294    if (hasBits(flags, VOL_BIT_ID))           { ww.writeShort(vol.getID());             }
1295    if (hasBits(flags, VOL_BIT_BYTES_FREE))   { ww.writeInt(vol.getBytesFree());        }
1296    if (hasBits(flags, VOL_BIT_BYTES_TOTAL))  { ww.writeInt(vol.getBytesTotal());       }
1297    if (hasBits(flags, VOL_BIT_NAME))         { ww.writePStringDeferred(vol.getName()); }
1298    if (hasBits(flags, VOL_BIT_XBYTES_FREE))  { ww.writeLong(vol.getExtBytesFree());    }
1299    if (hasBits(flags, VOL_BIT_XBYTES_TOTAL)) { ww.writeLong(vol.getExtBytesTotal());   }
1300    if (hasBits(flags, VOL_BIT_BLOCK_SIZE))   { ww.writeInt(vol.getBlockSize());        }
1301  }
1302
1303  // USED BY: GetFileDirParams
1304  private void sendDirectoryInfo(ByteWriter ww, AFP_CNode node, int flags, int mode)
1305    throws IOException
1306  {
1307    // 0x80 = directory + 0x00 = pad
1308    switch (mode)
1309    {
1310      case MODE_OLD: ww.writeByte(0x80); break;
1311      case MODE_EXT: ww.writeShort(0x8000); break;
1312    }
1313    ww.markDeferredOffset();
1314    if (hasBits(flags, DIR_BIT_ATTRIBUTE))       { ww.writeShort(node.getAttributes());           }
1315    if (hasBits(flags, DIR_BIT_PARENT_DIR_ID))   { ww.writeInt(node.getParentNodeID());           }
1316    if (hasBits(flags, DIR_BIT_CREATE_DATE))     { ww.writeInt(node.getCreateDate());             }
1317    if (hasBits(flags, DIR_BIT_MOD_DATE))        { ww.writeInt(node.getModifiedDate());           }
1318    if (hasBits(flags, DIR_BIT_BACKUP_DATE))     { ww.writeInt(node.getBackupDate());             }
1319    if (hasBits(flags, DIR_BIT_FINDER_INFO))     { ww.writeBytes(node.finderInfo());              }
1320    if (hasBits(flags, DIR_BIT_LONG_NAME))       { ww.writePStringDeferred(node.longName());      }
1321    if (hasBits(flags, DIR_BIT_SHORT_NAME))      { ww.writePStringDeferred(node.shortName());     }
1322    if (hasBits(flags, DIR_BIT_NODE_ID))         { ww.writeInt(node.getNodeID());                 }
1323    if (hasBits(flags, DIR_BIT_OFFSPRING_COUNT)) { ww.writeShort(node.getOffspringCount());       }
1324    if (hasBits(flags, DIR_BIT_OWNER_ID))        { ww.writeInt(node.getOwnerID());                }
1325    if (hasBits(flags, DIR_BIT_GROUP_ID))        { ww.writeInt(node.getGroupID());                }
1326    if (hasBits(flags, DIR_BIT_ACCESS_RIGHTS))   { ww.writeInt(node.getAccessRights());           }
1327    if (hasBits(flags, DIR_BIT_UTF8_NAME))       { ww.writeAFPStringDeferred(node.getUTF8Name()); }
1328    if (hasBits(flags, DIR_BIT_UNIX_PRIVS))      { ww.writeBytes(node.unixPrivs());               }
1329  }
1330
1331  // USED BY: SetDirParams
1332  private void recvDirectoryInfo(ByteReader rr, AFP_CNode node, int flags)
1333    throws IOException
1334  {
1335    rr.skipBytes(rr.getPosition() % 2);
1336    if (hasBits(flags,  DIR_BIT_ATTRIBUTE))      { node.setAttributes(rr.readShort());            }
1337    if (hasBits(flags,  DIR_BIT_CREATE_DATE))    { node.setCreateDate(rr.readInt());              }
1338    if (hasBits(flags,  DIR_BIT_MOD_DATE))       { node.setModifiedDate(rr.readInt());            }
1339    if (hasBits(flags,  DIR_BIT_BACKUP_DATE))    { node.setBackupDate(rr.readInt());              }
1340    if (hasBits(flags,  DIR_BIT_FINDER_INFO))    { node.setFinderInfo(rr.readBytes(32));          }
1341    if (hasBits(flags,  DIR_BIT_UNIX_PRIVS))     { node.setUnixPrivs(rr.readBytes(16));           }
1342    if (hasBits(flags,  DIR_BIT_PARENT_DIR_ID |
1343                DIR_BIT_LONG_NAME |
1344                DIR_BIT_SHORT_NAME |
1345                DIR_BIT_NODE_ID |
1346                DIR_BIT_OFFSPRING_COUNT |
1347                DIR_BIT_OWNER_ID |
1348                DIR_BIT_GROUP_ID |
1349                DIR_BIT_ACCESS_RIGHTS |
1350                DIR_BIT_UTF8_NAME))      { throw new AFP_Error(ERR_BITMAP_ERR);           }
1351  }
1352
1353  // USED BY: GetFileDirParams, EnumerateExt2
1354  private void sendFileInfo(ByteWriter ww, AFP_CNode node, int flags, int mode)
1355    throws IOException
1356  {
1357    // 0x00 = file + 0x00 = pad
1358    switch (mode)
1359    {
1360      case MODE_OLD: ww.writeByte(0x00); break;
1361      case MODE_EXT: ww.writeShort(0x0000); break;
1362    }
1363    ww.markDef