open source – Grey Panthers Savannah https://grey-panther.net Just another WordPress site Mon, 05 Oct 2009 13:08:00 +0000 en-US hourly 1 https://wordpress.org/?v=7.0.4 206299117 My opinion about Microsoft, software piracy and everything https://grey-panther.net/2009/10/my-opinion-about-microsoft-software-piracy-and-everything.html https://grey-panther.net/2009/10/my-opinion-about-microsoft-software-piracy-and-everything.html#comments Mon, 05 Oct 2009 13:08:00 +0000 https://grey-panther.net/?p=202 This post is a response to a blogpost on tudor g’s blog about software piracy issues in Romania, and as such it might not be of interest to you, dear international reader. If this is the case, feel free to skip this post.

Disclaimer: arguments are very emotional things. As much as we would like to think that they consists of logical statements and counter statements with the “best” arguments winning, in real life the acceptance or rejection of a given argument very much depends on the frame of reference of individual persons. With this in mind, I believe that Tudor and me have very different frames of reference (him being a Microsoft employee and me being an open-source enthusiast) and as such I’m quite sure that nothing written by me here will change his mind (and conversely, nothing written by him will change my mind). Still, I think that this is a useful exercise to get things off my chest and to document arguments for more open-minded people 🙂

  1. His first argument is that installing and using pirated software is harder that legitimate software because mechanisms like WGA – I don’t think that this argument holds water, since most people (the “average user”) can’t install an OS, regardless if it is pirated or legitimate. Just ask yourself the following question: how many of the non-technical people you know installed themselves the OS on their computer? I would bet you that the number is very, very close to 0.

    If the OS is installed by the “neighbor kid from the 2nd floor”, then this argument doesn’t hold. Even more, many geeks pride themselves with being able to perform complicated tasks, like disabling the WGA, and as such, for them the existence of protection element is a positive thing (a challenge to be solved).

    Finally, the inverse of the argument (that legitimate MS software is easier to use than pirated one) isn’t true either in my experience. I had numerous occasions where (completely legitimate, bought with the computer OEM) Windows failed to validate, a (again, completely legit, boxed version) Win 2k3 SBS suddenly refused to work because it needed to be a DC (and it told me after 3 months!), the Windows 7 beta deactivated itself periodically, VM’s deactivated themselves after moving from one machine to the other for purely technical reasons (even though the one-machine / one-owner / one-copy rule was always observed), etc.

  2. The second argument is that there is no peer-pressure to pirate in Romania (that not “everybody is doing it”) – I would suggest him to visit any campus in Romania and check out the (pirated) software which can be found on the network. And not only that, but music, movies, books, etc. Or to go to repair shops and ask for MS Windows / MS Office being preinstalled on the computer – the answer will almost always be positive. Even more, the next generation feels entitled to these freebies (and it isn’t something specific to Romania either, thanks to the abundance of the freemium business model on the Internet).

    In the long term (IMHO) less and less people will be willing to pay for things which they perceive as basic needs. The only options for old-style software companies (like Microsoft) are to include more and more technical measures to try to prevent this (even though the current measures already make MS Windows annoying to use) or to raise the level of punishment associated with piracy (which shouldn’t be possible in democratic countries because of public backlash)

  3. Piracy doesn’t help the software companies by making their product more well known – if this would to be true, why do you think that there are associations in people’s mind like Microsoft – Windows, Office – Microsoft, image editing – Photoshop, CD burning – Nero and so on? Most people use whatever is already installed on the computer to accomplish their jobs. This is why OEMs get big bucks from software companies to preinstall their product.

    I don’t buy the “starving programmer” argument either. The cost of copying software is minuscule. Which means that over 80% (this is of course a number pulled out of my rear, but I’m quite sure that the real number is somewhere in the ballpark) of each sale is pure profit. Which means that (a) a freemium type model can easily be sustained and (b) that even a few sales mean that the company makes a nice profit, and excessive focus on this part (ie. “we are loosing X billion of USD to piracy” – which BTW is not true for at least two reasons – first because the method to determine the number of pirated copies is questionable and second, because it assumes that every person who “pirates” would buy the product if s/he didn’t have access to the pirated version) is pure greed – which, let us remember, is one of the seven deadly sins.

    Also, as a programmer, you don’t have to write commodity software. Let me tell you, there is very good money to be made from writing custom software for a small number of clients.

    A final point I would like to make with regards the relation of piracy to innovation: remember that all three “big” powers (USA, Russia and China) started out (and some are still) by rejecting patents to bootstrap their industry. Something worth thinking about…

  4. That people buy because somehow they are convinced that it is “the right thing”, not because of fear – I’m not seeing it. At least at the individual level I don’t know anybody who bought a single Microsoft product (including myself, I’m living off my MSDN AA licenses). At a company level the motivation (arguably) is mostly fear. They buy licenses for the same reason they pay taxes. Also (as I’ve already said at point 2), the willingness of people will only go down, not up.

  5. Software is not overpriced, especially when considering the income level of Romanians – this is IMHO the best example for the “pink sunglasses” Tudor is wearing and how his frame of reference distorts his perception. The average (net) income per month in Romania for 2008 was somewhere around 400 USD (this would mean 4800 USD per year for those of you who use this frame of reference). Given this figure, is it reasonable to expect that people give more than half of their monthly income (or even all it, if we consider that a computer would need MS Windows + MS Office + AV) for software? May I venture a guess that (a) Tudor has at least five time the average net income and (b) he has free access to all of Microsoft’s software, and as such, he might not see the real situation? My challenge to Tudor would be: how much of the software he has right now on his personal machine did he pay for?

My conclusion is that software piracy is here to stay. Especially in more poor countries like ours. To give Microsoft what is due: they do really excellent software (not that they don’t do mistakes, like Vista – which is abysmal – I’m speaking from a first hand experience, having played with it on two “Vista certified” laptops and in VMs). Even so, their expectation is unrealistic at a minimum and even unethical. Also, as a developer, if you develop using a technology (OS, libraries, components, etc) for which you don’t have the source code, you will hit “undebuggable” issues sooner or later.

PS. Vista is the new ME – just worse:

Update: fixed some typos and errors in expression – thank you to my dear readers.

]]>
https://grey-panther.net/2009/10/my-opinion-about-microsoft-software-piracy-and-everything.html/feed 3 202
Webhoneypot https://grey-panther.net/2009/03/webhoneypot.html https://grey-panther.net/2009/03/webhoneypot.html#respond Wed, 04 Mar 2009 13:59:00 +0000 https://grey-panther.net/?p=371 3319204751_0528531c5c_oIn the last couple of months I’ve been helping out with the webhoneypot project.  From the Google code website:

DShield.org is offering this honeypot for users to capture automated web application exploits. It is a very simple "semi interactive" honeypot implemented in PHP.

The core idea is the following:

  • you install it on a webserver and configure it such that all requests are routed to a single file (index.php). This can be done with something like mod_rewrite or mod_alias for Apache and similar methods for other webservers (nginx for example has a built-in rewrite statement)
  • URL’s of “vulnerable looking” web applications are served up to spiders.
  • When a URL is accessed, it is matched against a set of regular expressions and, depending on which regex matches the longest part from the string, a static file is served up. The request is captured and sent to SANS in the background

If you want to play with it, here are a couple of resources:

An automatic update mechanism for the templates is in the works, however it is not working yet. The documentation is also a little out of date, but we are working hard on refreshing it. In the future we will probably include some more emulation (the idea was taken from the Glastopf project) to elicit responses from automated RFI/LFI scanning bots. Also look forward to a tutorial on how to run it on routers running OpenWrt.

Picture taken from Tavallai’s photostream with permission.

]]>
https://grey-panther.net/2009/03/webhoneypot.html/feed 0 371
Pidgin (Gaim) needs your support! https://grey-panther.net/2008/12/pidgin-gaim-needs-your-support.html https://grey-panther.net/2008/12/pidgin-gaim-needs-your-support.html#comments Mon, 29 Dec 2008 14:12:00 +0000 https://grey-panther.net/?p=501 The author of the free, open-source multi-protocol IM client Pidgin (formerly known as Gaim – OS X users might know it at Adium) have put up a survey. From the announcement:

2008 has been a slightly unusual year for the Pidgin chat client. Improvements were made, but the biggest news was caused by unhappy users. That’s one of the reasons why I created a user survey to figure out what the big issues are.

If you’re a Pidgin user, please fill the survey out! It should only take a couple of minutes and will be invaluable in figuring out how to improve Pidgin.

So, if you are a Pidgin user, please take 2-3 minutes off your time and fill out the survey for the benefit of all of us!

]]>
https://grey-panther.net/2008/12/pidgin-gaim-needs-your-support.html/feed 2 501
Sending authenticated mail with Net::SMTP https://grey-panther.net/2008/09/sending-authenticated-mail-with-netsmtp.html https://grey-panther.net/2008/09/sending-authenticated-mail-with-netsmtp.html#respond Sat, 27 Sep 2008 07:15:00 +0000 https://grey-panther.net/?p=689 As I’ve said earlier, one thing about open source is that you can fix fairly easily. In Perl this is especially easy, since it is turtles all the way down – many of the libraries are written in pure Perl, meaning that you can step through them with the Perl debugger.

This helped me recently for example, when I was trying to use Net::SMTP with a server which required authentication, and it kept failing on me. Finally I used the debugger to step into the library, and saw the following code:

eval {
  require MIME::Base64;
  require Authen::SASL;
} or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0;

So, I installed the Authen::SASL module and everything worked. While I would have preferred for the module to say it more explicitly (dying with a suggestive error or in the documentation), but still, the fact that I could look at the source and understand the problem is great.

]]>
https://grey-panther.net/2008/09/sending-authenticated-mail-with-netsmtp.html/feed 0 689
Implementing Web Services with Open Source Software https://grey-panther.net/2007/01/implementing-web-services-with-open-source-software.html https://grey-panther.net/2007/01/implementing-web-services-with-open-source-software.html#respond Fri, 12 Jan 2007 10:51:00 +0000 https://grey-panther.net/?p=925 Today many services are available (both internal and external to a company) as Web Services, more specifically as SOAP. Companies like Microsoft, IBM or Sun have heavily invested in this field and made many of their products compatible with it (as a client and/or as a server). In this article I will study the different possibilities of implementing a SOAP server with Open Source solutions.

The specific requirements are:

  • It should use the HTTP transport layer (the most commonly used in SOAP)
  • It should either have an embedded HTTP server or be usable with Apache
  • It should be platform independent

But let me step back for a moment and ask: why would you want to go this route? Why not use the product of well known companies which offer integration with developer tools and in some cases are available for free? While those products are certainly more mature and easier to use, when going the OSS route you have:

  • more flexibility (because you have the full source code available – and even if you don’t want to actively participate in the development process, it helps a lot for debugging),
  • more deployment options (just think how many webhosts offer Apache / MySQL / PHP / Perl as opposed to IIS, WebSphere or Java)
  • when extending the possible interfacing options of a product written for this platform (adding a SOAP API for a wiki for example) it is easier to use something like this rather than requiring the installation of a whole new framework
  • and finally the issue of the cost: while not a big problem because (a) academic institutions already have or can get free licenses for much of the products and (b) the companies themselves distribute their products (or at least some versions) free, it may still be an argument.

By doing research following these guidelines the following three possibilities emerged:

  • The SOAP::Lite library for Perl

    Advantages:

    • Very easy to use
    • Available across platforms (both from the CPAN and PPM repositories)
    • Has an extensive “cookbook” (set of short HOWTOs): http://cookbook.soaplite.com/
    • Runs in Apache (either as CGI or with mod_perl – in the later case you may need to replace SOAP::Transport::HTTP::CGI with SOAP::Transport::HTTP::Apache in the examples)
    • Has tracing functionality (to enable it at the client side, use the following way to include the library: use SOAP::Lite +trace => 'all'; and then redirect the stderr output (where the tracing info is dumped) to a file like this soap-clien.pl 2>debug-info.txt

    Disadvantages:

    • Does not support automatic generation of the WSDL file
    • Sometimes it insists on sending the variables as a certain type (integer) even though I would like to send them as string
  • The SOAP library included with PHP

    Advantages:

    • Usually readily included with PHP
    • Cross platform
    • Under active development

    Disadvantages:

    • Does not support automatic generation of the WSDL file
    • There are few tutorials for it
    • Very basic debugging support. PHP has a weak debugging support out of the box, but the fact that the majority of the functions are implemented in a binary library makes things even worse (because you would need a hybrid PHP/binary debugger for proper debugging)
  • The NuSOAP project for PHP

    Advantages:

    • Cross platform (written in PHP)
    • Automatic WSDL generation
    • When accessed with a browser, it presents a friendly HTML interface which lists all the published methods / objects and their parameters
    • Distributed as PHP source files which can be easily installed to most hosts (the user doesn't need to ask the server administrator to load extra binary modules)
    • While PHP has no integrated debugging support, the library itself tries to output debugging information. To activate this mode set the debug variable to 1 (like this: $debug = 1;). The debugging information will be appended to the reponse XML as a comment.

    Disadvantages:

    • Not very well maintained (probably because of the existence of the "official" PHP SOAP module)
    • Few examples and many of the examples don't work. For working examples go to the authors webiste
    • Conflicts with the official PHP SOAP module. If you get an error saying something along the lines of can not redefine class soap_client, you have to unload the PHP SOAP module. An other option would be to go through the source and rename this class.
    • No real debugging support (because PHP doesn't have one).

The final choice was NuSOAP. The deciding factor was the (semi)automatic - because you have to give it hints about the parameter types - WSDL generation. This is essential if you wish to make your service available to the largest possible audience, especially those using statically typed languages. A perfect example is the .NET / Visual Studio environment, which needs the WSDL file to automatically generate the stub for the web service.

A little side note: if your web service is accessed through SSL / HTTPS and the certificate authority who
signed the certificate of the server is not trusted (ie. it's not Verisign), you get some warnings while
generating the stub in Visual Studio and the final program will halt with an exception saying something like Could not establish a trusted connection over this SSL/TLS connection. The most common cause of this is the fact that the developer uses a self-signed certificate for the server. As far as I know there is no way to stop this from happening from inside the framework. However, because the framework shares its network access architecture with Internet Explorer, you can correct it from there. First you will need the certificate from the server (a .crt file, server.crt). Then go to Tools->Internet Options and select the Content tab. Click on Certificates, go to the Trusted Root Certification Authorities and select Import. Point it to the server certificate and answer affirmatively to the confirmation dialog. From now on that certificate will also be considered a trusted root certificate, you won't get warnings while browsing sites with it (and those sites might even have elevated privileges - depending on your Internet Explorer configuration), but most importantly your .NET client side code will work just fine.

The test project was to implement a web service which simulated some simple state machine(s). The project was implemented on the following platform:

The test was done on a Windows XP Pro machine using XAMPP to quickly install all the required components, however there is nothing platform specific in the code or the components, so it should be easy to replicate it on a different platform (Linux for example). The PHP code for the server side can be seen in Appendix A and an example for a state machine definition file can be found in Appendix B. An example client program written in C# can be seen in Appendix C.

The structure of a state machine definition file is as follows:

  • The root node is stateMachine. It has one mandatory parameter: initialState which specifies the initial state it is in
  • In the messages section it defines all possible messages (identified by name) which can be sent to this machine. This enumeration is needed to be able to check the validity of the message names provided later to guard against miss-typing.
  • The list of states identified by name. The states can be of two type: auto and message. Those of type auto automatically advance from the current state to the next state depending on the contained action elements. Those of type message wait for a message to advance.
  • The action elements contain the following attributes:
    • nextState - mandatory, the name of the text state if this action is chosen
    • waitBefore and waitAfter - optional, the amount of period to pause before and after executing this actions, in milliseconds. If omitted, zero is assumed. It must be a positive integer.
    • probability - a number greater than 0 but less or equal to 1.0. Determines the probability of this action being chosen. 0 means never and 1 means always. The sum of probabilities for a group (state element for auto states and message element for message states) must be 1.0. If some probabilities are omitted, the remaining probability is distributed amongst them (so if we have 5 action items and the first has a probability of 0.1, the second one of 0.3 and the rest are omitted, the last three will each have a probability of 0.2)

The exported functions by the server are:

  • postMessage(stateMachineName: string, message: string): string

    posts a message to the state machine identified by the stateMachineName. The definition for this state machine must be stored on the server in the file <stateMachineName>.xml. It is assumed that there is only one instance "running" of each server. This is guaranteed by the fact that the state of them is stored in a database table protected by write locks during the transitions. The method is synchronous and returns the name of the current state resulted from processing the message and any other automatic steps (states of type auto) which followed. One thing to keep in mind is that if you specify a state of
    type auto for the initial state, this will also be evaluated at the first message posted. On error it returns an empty string and you can use the getErrorMessage function to get the error message.

  • getMachineState(stateMachineName: string): string

    Gets the current state of the given state machine. It's asynchronous (with respect to the state machine, not the caller). If an error has occurred in the state machine it returns the empty string. You can use the getErrorMessage function to get the error message.

  • getErrorMessage(stateMachineName: string): string

    Returns the error message for the given state machine or empty if no error exists

  • resetMachineState(stateMachineName: string): void

    Resets the machine to its initial state (as specified by the initialState attribute of the correspoding definition file)

  • resetAllMachines: void

    Resets all the state machines to their initial state

Appendix A - PHP Server side code

To install it you would need the following items:

  • The NuSOAP library in the lib subdirectory (or anywhere else, just be sure to adjust the include directive accordingly)
  • The PearDB package for database access
  • Adjust the $data_directory variable so that it points to directory where the XML files describing the state machines are located. Important: include the trailing slash or backslash depending on the platform
  • Create two database tables to store the current state of the automatas (the second table is for locking purposes only, because MySQL doesn't support writing while in a read lock). The SQL statements to create these tables are (you might need to tweak these a little bit to get them to work if you are using something other than MySQL or a different version of it):

    CREATE TABLE `state_machines`.`state_machines` (
    `machine_name` VARCHAR(255) NOT NULL DEFAULT '',
    `machine_state` VARCHAR(255) NOT NULL DEFAULT '',
    `error_message` VARCHAR(255) NOT NULL DEFAULT '',
    PRIMARY KEY(`machine_name`)
    )
    ENGINE = MEMORY;
    
    CREATE TABLE `state_machines`.`lock_table` (
     `dummy_column` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
     PRIMARY KEY(`dummy_column`)
    )
    ENGINE = MEMORY;
    

  • Adjust the connection string accordingly in the DB::connect call

<?php
  require_once 'lib/nusoap.php';
  require_once 'DB.php';
  
  $server = new soap_server();
  $server->configureWSDL('statemachine', 'urn:statemachine');
  
  $data_directory = "C:\xampp\htdocs\webservice\data\";  
  $db_connection = DB::connect("mysql://state_machine:password@localhost/state_machines");
  if (DB::isError($db_connection)) 
    stopWithErrorMessage("failed to connect to the database - " . $db_connection->getMessage());
  
  // Register the methods to expose
  $server->register('postMessage',    
      array('stateMachineName' => 'xsd:string', 'message' => 'xsd:string'),
      array('return' => 'xsd:string'),
      'urn:statemachine',             
      'urn:statemachine#postMessage', 
      'rpc',                          
      'encoded',                      
      'send a message to a given state machine. returns the new state'      
  );
  $server->register('getMachineState',    
      array('stateMachineName' => 'xsd:string'),
      array('return' => 'xsd:string'),
      'urn:statemachine',             
      'urn:statemachine#getMachineState', 
      'rpc',                          
      'encoded',                      
      'returns the current state of the automaton'      
  );
  $server->register('getErrorMessage',    
      array('stateMachineName' => 'xsd:string'),
      array('return' => 'xsd:string'),
      'urn:statemachine',             
      'urn:statemachine#getErrorMessage', 
      'rpc',                          
      'encoded',                      
      'returns the error message for a given state machine ("" if no error exists)'      
  );  
  $server->register('resetMachineState',    
      array('stateMachineName' => 'xsd:string'),
      array(),
      'urn:statemachine',             
      'urn:statemachine#resetMachineState', 
      'rpc',                          
      'encoded',                      
      'resets the given state machine'      
  );  
  $server->register('resetAllMachines',    
      array(),
      array(),
      'urn:statemachine',             
      'urn:statemachine#resetMachineState', 
      'rpc',                          
      'encoded',                      
      'resets all the state machines'      
  );    

  $server->service($HTTP_RAW_POST_DATA);

  //send a message to a given state machine. returns the new state
  function postMessage($stateMachineName, $message) {
    if (!preg_match('/^[w-s]+$/', $stateMachineName)) 
      stopWithErrorMessage('state machine name contains illegal characters');
    global $data_directory;
    if (!is_file($data_directory . $stateMachineName . ".xml")) 
      stopWithErrorMessage('specified state machine does not exists');
      
    //load up the state machine
    $stateMachine = loadStateMachineFromFile($data_directory . $stateMachineName . ".xml");
    global $db_connection;
    //from now on we need to be synchronized with other threads - lock the database table
    $db_connection->query('LOCK TABLE lock_table WRITE');
    //synchronize it with the database
    $stateMachine = synchronizeStateMachine($stateMachine, $stateMachineName);
    //now post the message to the state machine
    $stateMachine = postMessageWhilePossible($stateMachine, $message);
    if (is_string($stateMachine)) {
      //an error has occured! store the error message and return the empty string
      $db_connection->query('REPLACE INTO state_machines (machine_state, error_message) VALUES ("", "' . addslashes($stateMachine['currentState']) . 
        '") WHERE machine_name="' . addlashes($stateMachineName) . '"');
      $db_connection->query('UNLOCK TABLES');
      return '';
    } else {
      //everything went ok, store the new state and return it
      $db_connection->query('REPLACE INTO state_machines (machine_state, error_message) VALUES ("' . addslashes($stateMachine['currentState']) . 
        '", "") WHERE machine_name="' . addslashes($stateMachineName) . '"');
      $db_connection->query('UNLOCK TABLES');
      return $stateMachine['currentState'];
    }     
  }
  
  //returns the current state of the automaton
  function getMachineState($stateMachineName) {
    if (!preg_match('/^[w-s]+$/', $stateMachineName)) 
      stopWithErrorMessage('state machine name contains illegal characters');
    global $data_directory;
    if (!is_file($data_directory . $stateMachineName . ".xml")) 
      stopWithErrorMessage('specified state machine does not exists');
      
    //load up the state machine
    $stateMachine = loadStateMachineFromFile($data_directory . $stateMachineName . ".xml");
    //synchronize it with the database
    $stateMachine = synchronizeStateMachine($stateMachine, $stateMachineName);
    //return the current state
    return $stateMachine['currentState'];
  }
  
  //returns the error message for a given state machine ('' if no error exists)
  function getErrorMessage($stateMachineName) {
    if (!preg_match('/^[w-s]+$/', $stateMachineName)) 
      stopWithErrorMessage('state machine name contains illegal characters');
    global $db_connection;
    return $db_connection->getOne("SELECT error_message FROM state_machines WHERE machine_name="" . addslashes($stateMachineName) . """);
  }
  
  //resets the given state machine
  function resetMachineState($stateMachineName) {
    if (!preg_match('/^[w-s]+$/', $stateMachineName)) 
      stopWithErrorMessage('state machine name contains illegal characters');
    global $db_connection;
    $db_connection->query('DELETE FROM state_machines WHERE machine_name="' . addslashes($stateMachineName) . '"');
  }
  
  //resets all the state machines
  function resetAllMachines() {
    global $db_connection;
    $db_connection->query('DELETE FROM state_machines');
  }
  
  //internal helper function which outputs the error message to the header and then exits
  function stopWithErrorMessage($error_message) {
    header("HTTP/1.1 500 Internal Server Error: " . $error_message, true, 500);
    exit;     
  }
  
  //internal helper function - synchronizes the state of a an automaton with the one stored in the databas
  //(if it's stored there)
  function synchronizeStateMachine($state_machine, $stateMachineName) {
    global $db_connection;
    $machine_state = $db_connection->getRow("SELECT * FROM state_machines WHERE machine_name="" . addslashes($stateMachineName) . """);
    if (is_array($machine_state)) {
      //it is present in the database, try to synchronize with it
      if ( ('' == $machine_state['machine_state']) || array_key_exists($machine_state['machine_state'], $state_machine['states'])) {
        $state_machine['currentState'] = $machine_state['machine_state'];
        return $state_machine;
      } else {
        stopWithErrorMessage("Erroneous state in the database: " . $machine_state['machine_state']);        
        exit;
      }
    }
    //it's not present in the database, leave it as it is
    return $state_machine;
  }
  
  //internal helper function. Checks if the given node has the specified attribute
  //if not, returns null, if it does, it returns it
  function getAttributeOrNull($node, $attr_name) {
    if ($node->hasAttribute($attr_name))
      return $node->getAttribute($attr_name);
    return null;      
  }
  
  //internal helper function. Extract and validate the action elements from
  //a node (message or state). Return an array structure on success,
  //an error messag (string) on failure
  function extractActions($parent_node, $states_list, $state_name, $message_name) {
    //process the probable actions - make sure that the sum of the probabilities if 1.0
    //when no probaility is specified, the remaining probability is distributed between them
    $error_message_suffix = ('' == $message_name) ? '' : " at message '$message_name'";
    $actions = $parent_node->getElementsByTagName('action');
    $result = array();
    $probability_sum = 0.0; $actions_with_no_probability = 0;
    foreach ($actions as $action) {
      $new_action = array();
      if (null !== ($action_probability = getAttributeOrNull($action, 'probability'))) {
        if ($action_probability <= 0)
          return "Negative probability of action in state '$state_name'$error_message_suffix";
        $probability_sum += 0.0 + $action_probability;
        $new_action['probability'] = 0.0 + $action_probability;
      } else {
        ++$actions_with_no_probability;
      }
      if (null === ($action_wait_before = getAttributeOrNull($action, 'waitBefore'))) {
        $new_action['waitBefore'] = 0;
      } else {
        if ($action_wait_before < 0)
          return "Negative 'waitBefore' of action in state '$state_name'$error_message_suffix";
        $new_action['waitBefore'] = intval($action_wait_before);
      }
      if (null === ($action_wait_after = getAttributeOrNull($action, 'waitAfter'))) {
        $new_action['waitAfter'] = 0;
      } else {
        if ($action_wait_after < 0)
          return "Negative 'waitAfter' of action in state '$state_name'$error_message_suffix";
        $new_action['waitAfter'] = intval($action_wait_after);
      }     
      if (null === ($action_next_state = getAttributeOrNull($action, 'nextState')))
        return "Unspecified nextState in state '$state_name'$error_message_suffix";
      if (!array_key_exists($action_next_state, $states_list))
        return "Invalid nextState specified in action at state '$state_name'$error_message_suffix: '$action_next_state'";
      $new_action['nextState'] = $action_next_state;
                  
      $result[] = $new_action;
    }
    //now redistribute the remaining probability :)
    if ($actions_with_no_probability > 0) {
      foreach (array_keys($result) as $action_key) {
        if (!array_key_exists('probability', $result[$action_key])) {
          $result[$action_key]['probability'] = (1.0 - $probability_sum) / $actions_with_no_probability;
        }
      }
    }
    //finally sum up the probability and check it (must be 1.0)
    $probability_sum = 0.0;
    foreach ($result as $action)
      $probability_sum += $action['probability'];
    if (abs(1.0 - $probability_sum) > 0.001)
      return "The sum of probabilities for state '$state_name'$error_message_suffix is way off from 1.0"; 
    
    return $result;
  }

  //returns a structure which completly describes the state machine
  //returns a string with an error message if the XML failed to follow
  //the rules
  function loadStateMachine($state_machine_xml) {
    $doc = new DOMDocument();
    $doc->loadXML($state_machine_xml);    
    
    //this will be the result is all goes well
    $result = array();
    
    //find all the valid message
    $xpath = new DOMXPath($doc);
    $valid_messages = array();
    foreach ($xpath->query('//stateMachine/messages/message') as $valid_message) {
      if (null === getAttributeOrNull($valid_message, 'name'))
        return "Found message element which doesn't have the 'name' attribute!";
      $valid_messages[getAttributeOrNull($valid_message, 'name')] = 1;
    } 
    if (0 >= count($valid_messages))
      return 'No valid message names found!';   
          
    //now parse states
    $result['states'] = array();
    $states = $doc->getElementsByTagName('state');
    if (0 >= $states->length)
      return 'No state elements found!';
    
    //first store the state names so that we can validate them later on
    foreach ($states as $state) {
      if (null === ($state_name = getAttributeOrNull($state, 'name')))
        return 'Found state with no name!';
      if (array_key_exists($state_name, $result['states']))
        return "Found state with duplicate name: '$state_name'";
      $result['states'][$state_name] = array();
    }
    
    foreach ($states as $state) {
      //validate the basic parameters for the state
      $state_name = getAttributeOrNull($state, 'name');
      if (null === ($state_type = getAttributeOrNull($state, 'type')))
        return 'Found state with no type!';
      if ( ('message' != $state_type) && ('auto' != $state_type) )
        return "Found state with invalid type: '$state_type'";
        
      //save the validated stuff
      $result['states'][$state_name]['type'] = $state_type;
      
      //process the available state transitions
      if ('message' == $state_type) {
        $messages = $state->getElementsByTagName('message');
        $result['states'][$state_name]['messages'] = array();
        foreach ($messages as $message) {
          //message name: - it should exists, - it should be valid and - it shouldn't be used before (in this state)
          if (null === ($message_name = getAttributeOrNull($message, 'name')))
            return "Found message with no name in state '$state_name'";
          if (!array_key_exists($message_name, $valid_messages))
            return "Found invalid message name '$message_name' in state '$state_name'";
          if (array_key_exists($message_name, $result['states'][$state_name]['messages']))          
            return "Found duplicate message name '$message_name' in state '$state_name'";
          $result['states'][$state_name]['messages'][$message_name] = array();
          
          $result['states'][$state_name]['messages'][$message_name]['actions'] = 
            extractActions($message, $result['states'], $state_name, $message_name);
          if (is_string($result['states'][$state_name]['messages'][$message_name]['actions']))
            //an error has occured
            return $result['states'][$state_name]['messages'][$message_name]['actions'];
        }       
      } else {
        //load the actions we can chose from
        $result['states'][$state_name]['actions'] = 
          extractActions($state, $result['states'], $state_name, '');
        if (is_string($result['states'][$state_name]['actions']))
          //an error has occurred
          return $result['states'][$state_name]['actions'];
      }
    }
      
    //get the starting state and make sure that it's a valid state
    if (null === ($initial_state = getAttributeOrNull($doc->documentElement, 'initialState')))
      return 'Document has no initial state!';      
    if (!array_key_exists($initial_state, $result['states']))
      return 'Initial state is invalid!';   
    $result['currentState'] = $initial_state;   
    
    return $result;
  }
  
  function loadStateMachineFromFile($file_machine) {
    $xml_file_contents = file_get_contents($file_machine);
    if (get_magic_quotes_gpc()) $xml_file_contents = stripslashes($xml_file_contents);
    return loadStateMachine($xml_file_contents);
  }
  
  //applies a given message to a given state machine. it executes the specified waits
  //returns the modified state machine. If an error occured, the currentState will be set to ''
  function internalPostMessage($state_machine, $message = '') {
    //print "Applying message: '$message'n";
    //print "Current state: $state_machine[currentState]n";
    
    //we are in an invalid state - we can't do anything
    if (!array_key_exists($state_machine['currentState'], $state_machine['states']))
      return $state_machine;
        
    if ('message' == $state_machine['states'][$state_machine['currentState']]['type']) {
      if (!array_key_exists($message, $state_machine['states'][$state_machine['currentState']]['messages'])) {
        //this message can not be applied now
        $state_machine['currentState'] = '';
        return $state_machine;
      }
      $actions = $state_machine['states'][$state_machine['currentState']]['messages'][$message]['actions'];
    } else {
      $actions = $state_machine['states'][$state_machine['currentState']]['actions'];
    }
        
    //now chose an action, by "throwing a dice"
    $rand_value = rand(0, 32768) / 32768;
    $action_to_execute = null;
    foreach ($actions as $action) {
      $action_to_execute = $action;
      if ($rand_value <= $action['probability'])        
        break;      
      $rand_value -= $action['probability'];
    }   
    
    //print "Going to state $action[nextState]n";
    //now execute the action        
    sleep(intval($action['waitBefore'] / 1000 + 0.5));
    $state_machine['currentState'] = $action['nextState'];    
    sleep(intval($action['waitAfter'] / 1000 + 0.5));
    //print "Gone to state $action[nextState]n";   
    
    return $state_machine;
  }
  
  //the same as above, however it continues while possible after the first move
  //(while the current state is an automatic one)
  function postMessageWhilePossible($state_machine, $message) {
    //process any automatic statest BEFORE
    while ( ('' != $state_machine['currentState']) &&
      ('auto' == $state_machine['states'][$state_machine['currentState']]['type']) ) {
      $state_machine = internalPostMessage($state_machine);
    }
    if ('' != $state_machine['currentState'])
      $state_machine = internalPostMessage($state_machine, $message);
    //process any automatic statest AFTER
    while ( ('' != $state_machine['currentState']) &&
      ('auto' == $state_machine['states'][$state_machine['currentState']]['type']) ) {
      $state_machine = internalPostMessage($state_machine);
    }
    return $state_machine;
  } 
?>

Appendix B - Example state machine file

<stateMachine initialState="Off">
  <messages>
    <message name="Flip" />
  </messages>
  <state name="Off" type="message">
    <message name="Flip">
      <action probability="0.5" nextState="Transient" />
      <action probability="0.5" nextState="Off" />
    </message>
  </state>
  <state name="Transient" type="auto">
    <action probability="0.9" nextState="On" waitBefore="1000" />
    <action probability="0.1" nextState="Off" waitBefore="1000" />  
  </state>
  <state name="On" type="message">
    <message name="Flip">
      <action nextState="Off" />  
    </message>
  </state>
</stateMachine>

Appendix C - Example client program in .NET (C#, VB .NET, Delphi and Managed C++)

Before you can use these examples, you have to add a Web Reference to your project. You can do this by right-clicking on your Reference folder in your Visual Studio and selecting Web Reference. You should put in the link with a ?wsdl appended (to get the WSDL file). For example if you are hosting the service locally, you would put in http://localhost/webservice/index.php?wsdl

C#
private static void Main(string[] args)
{
      Console.WriteLine("Startin up...");
      statemachine statemachine1 = new statemachine();
      Console.WriteLine("Startup done...");
      for (int num1 = 0; num1 < 10; num1++)
      {
            Console.WriteLine("The current state is: " + statemachine1.getMachineState("testAutomata"));
            Console.WriteLine("Passing message: Flip");
            Console.WriteLine("Test automata returned: " + statemachine1.postMessage("testAutomata", "Flip"));
            Console.WriteLine("---");
      }
      Console.WriteLine("Press any key to exit...");
      Console.ReadKey();
}
VB .NET
Private Shared Sub Main(ByVal args As String())
      Console.WriteLine("Startin up...")
      Dim statemachine1 As New statemachine
      Console.WriteLine("Startup done...")
      Dim num1 As Integer = 0
      Do While (num1 < 10)
            Console.WriteLine(("The current state is: " & statemachine1.getMachineState("testAutomata")))
            Console.WriteLine("Passing message: Flip")
            Console.WriteLine(("Test automata returned: " & statemachine1.postMessage("testAutomata", "Flip")))
            Console.WriteLine("---")
            num1 += 1
      Loop
      Console.WriteLine("Press any key to exit...")
      Console.ReadKey
End Sub
Delphi
procedure Program.Main(args: string[]);
begin
      Console.WriteLine('Startin up...');
      statemachine1 := statemachine.Create;
      Console.WriteLine('Startup done...');
      num1 := 0;
      while ((num1 < 10)) do
      begin
            Console.WriteLine(string.Concat('The current state is: ', statemachine1.getMachineState('testAutomata')));
            Console.WriteLine('Passing message: Flip');
            Console.WriteLine(string.Concat('Test automata returned: ', statemachine1.postMessage('testAutomata', 'Flip')));
            Console.WriteLine('---');
            inc(num1)
      end;
      Console.WriteLine('Press any key to exit...');
      Console.ReadKey
end;
Managed C++
private: static void __gc* Main(System::String __gc* args __gc [])
{
      System::Console::WriteLine(S"Startin up...");
      ContactWebservice::stateMachine::statemachine __gc* statemachine1 = __gc new ContactWebservice::stateMachine::statemachine();
      System::Console::WriteLine(S"Startup done...");
      for (System::Int32 __gc* num1 = 0; (num1 < 10); num1++)
      {
            System::Console::WriteLine(System::String::Concat(S"The current state is: ", statemachine1->getMachineState(S"testAutomata")));
            System::Console::WriteLine(S"Passing message: Flip");
            System::Console::WriteLine(System::String::Concat(S"Test automata returned: ", statemachine1->postMessage(S"testAutomata", S"Flip")));
            System::Console::WriteLine(S"---");
      }
      System::Console::WriteLine(S"Press any key to exit...");
      System::Console::ReadKey();
}

Appendix D - Example client written in Perl

use warnings;
use diagnostics;
use SOAP::Lite +trace => 'all';

print ">>" . SOAP::Lite
  -> uri('http://www.soaplite.com/Demo')
  -> proxy('http://localhost/webservice/index.php')
  -> getMachineState("testAutomata")
  -> result;
]]>
https://grey-panther.net/2007/01/implementing-web-services-with-open-source-software.html/feed 0 925
An other nice looking utility https://grey-panther.net/2006/11/an-other-nice-looking-utility.html https://grey-panther.net/2006/11/an-other-nice-looking-utility.html#respond Tue, 21 Nov 2006 17:02:00 +0000 https://grey-panther.net/?p=1008 Here is an other utility that looks great: HT Editor. Editor / Hex Editor / Disassembler / Assembler. I’ll take it for a spin and get back to you with the results.

]]>
https://grey-panther.net/2006/11/an-other-nice-looking-utility.html/feed 0 1008
The perfect solution? https://grey-panther.net/2006/11/the-perfect-solution.html https://grey-panther.net/2006/11/the-perfect-solution.html#comments Sun, 05 Nov 2006 20:35:00 +0000 https://grey-panther.net/?p=1023 I’ve been experimenting with different collaboration solutions, and I think I may just have found the one: Socialtext. What I really like:

  • It is open source (just click the for developers link)
  • It is free (if you don’t want the support)
  • It is written in Perl
  • It has an API for manipulating the pages (I don’t know yet how powerful it is)
  • It has a WYSIWYG editor
  • Commets can be attached to every page
  • It integrates with Active Directory

What I don’t like (these are my first perceptions while browsing around the site, I didn’t have time yet to install it and do an extensive testing):

  • The access control seems to be a little limited (like admin / non-admin), I would have preferred something more along the lines of Active Directory or at least the Unix model with groups and group members
  • It doesn’t seem to support code blocks. I thing one of the major features of DokuWiki is the integration with GeSHi for syntax highlighting of source code.

I’ve heard about it over at Perlcast

]]>
https://grey-panther.net/2006/11/the-perfect-solution.html/feed 1 1023