/***************************************************************************
* DBS: Distributed Benchmark System
* Copyright (c) 1995, 1996, 1997 Yukio Murayama
* Copyright (c) 1995, 1996, 1997 Nara Institute of Science and Technology
* All rights reserved.
*
* Permission to use, copy, modify and distribute this software and its
* documentation is hereby granted, provided only with the following
* conditions are satisfied:
*
* 1. Both the copyright notice and this permission notice appear in
* all copies of the software, derivative works or modified versions,
* and any portions thereof, and that both notices appear in
* supporting documentation.
* 2. All advertising materials mentioning features or use of this
* software must display the following acknowledgement:
* This product includes software developed by Nara Institute of
* Science and Technology and its contributors.
* 3. Neither the name of Nara Institute of Science and Technology nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``AS IS'' AND NARA
* INSTITUTE OF SCIENCE AND TECHNOLOGY DISCLAIMS ANY LIABILITY OF
* ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF
* THIS SOFTWARE. ALSO, THERE IS NO WARRANTY IMPLIED OR OTHERWISE,
* NOR IS SUPPORT PROVIDED.
*
* Feedback of the results generated from any improvements or
* extensions made to this software would be much appreciated.
* Any such feedback should be sent to:
*
* Yukio Murayama
* E-mail: <yukio-m@is.aist-nara.ac.jp>
* URL: <http://shika.aist-nara.ac.jp/member/yukio-m/index.html>
* Address: Graduate School of Information Science,
* Nara Institute of Science and Technology,
* Takayama 8916-5, Ikoma, Nara, Japan
*
* Nara Institute of Science and Technology has the rights to
* redistribute these changes.
***************************************************************************/
/*****************************************************************
* Distributed Benchmark System
* Scan Command File
* $Revision: 1.22 $
* $Date: 1997/07/11 00:54:12 $
* $Author: yukio-m $
*****************************************************************/
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include<stdio.h>
#include<stdlib.h>
#if !defined(sony_news) && !defined(__sun)
#include<strings.h>
#else
#include <string.h>
#endif
#if (!defined(BSD) || (BSD < 199306))
#error BSD
#include <malloc.h>
#endif
#include <math.h>
#include "dbs.h"
#include "dbsc.h"
/*
* Prototype Definition
*/
int ffgetc __P((FILE *fp));
int uungetc __P((int c, FILE *fp));
void p_error __P((char *s));
void skip __P((char *str, FILE *f));
int getword __P((char *buf, char *str, FILE *fp));
void init_sendrecv_param __P((struct send_recv *sr));
void init_scan_param __P((struct scan_cmd *scan));
void check_sendrecv_param __P((struct send_recv *sr));
void check_scan __P((struct scan_cmd *scan));
int scan_sub_out __P((char *word, struct scan_cmd **scan));
int scan_sub_root __P((FILE *fp, char *word, struct scan_cmd **scan));
int scan_sub_sendrecv __P((FILE *fp, char *word, struct send_recv *sr, int state));
int total_size __P((struct scan_cmd *scan));
int get_pattern __P((struct scan_traffic **traffic, FILE *fp));
int total_message __P((struct scan_cmd *scan));
/*
* Grobal Variable for error messages Setting
*/
static int line = 1;
static int column = 1;
static int bcolumn = 1;
static char error_buf[MAX_COLUMN+1]="";
static int cmd_n = 0;
/*
* Grobal Variable for automaton
*/
enum state { OUT=0, ROOT=1, SENDER=2, RECEIVER=3 } state;
/*****************************************************************
* Scan MAIN-ROUTINE
*
* Parser Command file
*****************************************************************/
int scan(scan, fp)
struct scan_cmd **scan;
FILE *fp;
{
char word[CHAR_ARRAY];
int c;
state = OUT;
DEBUGMSG2(8, fprintf(stderr, "State %d\n",state));
DEBUGMSG2(8, fprintf(stderr, "Scan File ----------------------------------------------\n"));
/*
* Scan Main Routine
*/
while (1) {
skip(" \n\t;", fp);
if (getword(word, " \n\t;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", fp) == EOF)
return cmd_n;
while (1) {
DEBUGMSG2(8, fprintf(stderr, "State %d\n",state));
switch (state) {
case OUT:
state = scan_sub_out(word, scan);
break;
case ROOT:
state = scan_sub_root(fp, word, scan);
break;
case SENDER:
state = scan_sub_sendrecv(fp, word, &((*scan)->sender), SENDER);
break;
case RECEIVER:
state = scan_sub_sendrecv(fp, word, &((*scan)->receiver), RECEIVER);
break;
default:
p_error("Internal Error!! (scan)");
exit(1);
}
skip(" \n\t;", fp);
if (getword(word, " {=\n\t;}", fp) == EOF)
break;
if (state == OUT)
break;
if (strcmp(word, "") == 0) {
c=ffgetc(fp);
DEBUGMSG2(8, fprintf(stderr, "(G:%c:G)",c));
if (c == '}')
strcpy(word, "}");
else if (c == '{')
strcpy(word, "{");
else {
p_error("missing '}' or '{'");
exit(1);
}
}
}
check_scan(*scan);
/* out:; GOTO LABEL */
(*scan)->total_size = total_size(*scan);
(*scan)->total_message = total_message(*scan);
if (debug >= 8) {
fprintf(stderr, "Command Data -------------------------------------------\n");
fprintf(stderr, "Host Name = %-12s %-12s\n", (*scan)->sender.hostname, (*scan)->receiver.hostname);
fprintf(stderr, "Host Name Command= %-12s %-12s\n", (*scan)->sender.hostname_cmd, (*scan)->receiver.hostname_cmd);
fprintf(stderr, "Transport = %-12d\n", (*scan)->transport);
fprintf(stderr, "Port Number = %-12d %-12d\n", (*scan)->sender.port, (*scan)->receiver.port);
fprintf(stderr, "Start Time =(%-12d %-12d)\n",(*scan)->start_time.tv_sec, (*scan)->start_time.tv_usec);
fprintf(stderr, "End Time =(%-12d %-12d)\n",(*scan)->end_time.tv_sec, (*scan)->start_time.tv_usec);
fprintf(stderr, "Send Times = %-12d \n", (*scan)->send_times);
fprintf(stderr, "Traffic Number = %-12d %-12d\n", (*scan)->sender.traffic_n, (*scan)->receiver.traffic_n);
fprintf(stderr, "Total Size = %-12d\n", (*scan)->total_size);
fprintf(stderr, "Total Message = %-12d\n", (*scan)->total_message);
fprintf(stderr, "Output File Name = %-12s\n", (*scan)->filename);
fprintf(stderr, "Connection Mode = %-12d\n", (*scan)->connection_mode);
fprintf(stderr, "Send Buffer Size = %-12d %-12d\n", (*scan)->sender.send_buff, (*scan)->sender.recv_buff);
fprintf(stderr, "Recv Buffer Size = %-12d %-12d\n", (*scan)->receiver.send_buff, (*scan)->receiver.recv_buff);
fprintf(stderr, "Soket Debug Mode = %-12d %-12d\n", (*scan)->sender.so_debug, (*scan)->receiver.so_debug);
fprintf(stderr, "TCP Trace Mode = %-12d %-12d\n", (*scan)->sender.tcp_trace, (*scan)->receiver.tcp_trace);
fprintf(stderr, "TCP No Delay = %-12d %-12d\n", (*scan)->sender.no_delay, (*scan)->receiver.no_delay);
fprintf(stderr, "--------------------------------------------------------\n");
}
scan = &((*scan)->next);
}
}
/*****************************************************************
* Scan subroutine
*****************************************************************/
/*
* state == OUT
*/
int scan_sub_out(word, scan)
char *word;
struct scan_cmd **scan;
{
if (strcmp(word, "{") == 0) {
/*
* Create Scan_Cmd
*/
if ((*scan = (struct scan_cmd *)malloc(sizeof(struct scan_cmd))) == NULL) {
perror("malloc: scan_cmd");
exit(1);
}
cmd_n++;
init_scan_param(*scan);
return ROOT;
}
p_error("syntax error.");
exit(1);
}
/*
* state == ROOT
*/
int scan_sub_root(fp, word, scan)
FILE *fp;
char *word;
struct scan_cmd **scan;
{
double a;
if (strcmp(word, "sender") == 0) {
skip(" \n\t{", fp);
return SENDER;
}
if (strcmp(word, "receiver") == 0) {
skip(" \n\t{", fp);
return RECEIVER;
}
if (strcmp(word, "file") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
strcpy((*scan)->filename, word);
} else if (strcmp(word, "protocol") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
if (strcmp("TCP", word) == 0) {
(*scan)->transport = TCP;
} else if (strcmp("UDP", word) == 0) {
(*scan)->transport = UDP;
} else {
p_error("Parameter error. You can set TCP or UDP.");
exit(1);
}
} else if (strcmp(word, "connection_mode") == 0 || strcmp(word, "connect_mode") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
if (strcmp("BEFORE", word) == 0) {
(*scan)->connection_mode = BEFORE;
} else if (strcmp("AFTER", word) == 0) {
(*scan)->connection_mode = AFTER;
} else {
p_error("Parameter error. You can set BEFORE or AFTER.");
exit(1);
}
} else if (strcmp(word, "server") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
if (strcmp("RECEIVER", word) == 0) {
(*scan)->serverflg = RECEIVE;
} else if (strcmp("SENDER", word) == 0) {
(*scan)->serverflg = SEND;
} else {
p_error("Parameter error. You can set SENDER or RECEIVER.");
exit(1);
}
} else if (strcmp(word, "start_time") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
timeval_put(&((*scan)->start_time), (a = (double)atof((char *)word)));
if (a < - 0.1) {
p_error("start_time error. start_time must be greater or equal than 0.0");
exit(1);
}
} else if (strcmp(word, "end_time") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
timeval_put(&((*scan)->end_time), (a = (double)atof((char *)word)));
if (a < 0.0) {
p_error("end_time error. end_time must be greater than 0.0");
exit(1);
}
if (a > WARN_END_TIME)
fprintf(stderr, "WARNING:end_time = %f is large\n",a );
if (a > MAX_END_TIME) { /* XXX You can modify MAX_END_TIME in dbs.h */
p_error("end_time error. end_time must be less than 3600.0");
exit(1);
}
} else if (strcmp(word, "send_times") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
(*scan)->send_times = atoi(word);
if ((*scan)->send_times < 0) {
p_error("send_times error. send_times must be greater or equal than 0");
exit(1);
}
} else if (strcmp(word, "}") == 0) {
return OUT; /* GGG GOTO OUT */
} else {
p_error("Unknown keyword.");
exit(1);
}
return ROOT;
}
/*
* state == SEND or RECV
*/
int scan_sub_sendrecv(fp, word, sr, state)
FILE *fp;
char *word;
struct send_recv *sr;
int state;
{
if (strcmp(word, "hostname_cmd") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
strcpy(sr->hostname_cmd, word);
} else if (strcmp(word, "hostname") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
strcpy(sr->hostname, word);
} else if (strcmp(word, "port") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->port = atoi(word);
if (sr->port < 0 || sr->port > 0xffff) {
p_error("port number error.");
exit(1);
}
} else if (strcmp(word, "send_buff") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->send_buff = atoi(word);
} else if (strcmp(word, "recv_buff") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->recv_buff = atoi(word);
} else if (strcmp(word, "mem_align") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->mem_align = atoi(word);
} else if (strcmp(word, "align_offset") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->align_offset = atoi(word);
} else if (strcmp(word, "align_pad") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->align_pad = atoi(word);
} else if (strcmp(word, "no_delay") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
if (strcmp(word, "ON") == 0) {
sr->no_delay = ON;
} else if (strcmp(word, "OFF") == 0) {
sr->no_delay = OFF;
} else {
p_error("Parameter error. You can set ON or OFF.");
exit(1);
}
} else if (strcmp(word, "mss") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->mss = atoi(word);
} else if (strcmp(word, "so_debug") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
if (strcmp(word, "ON") == 0) {
sr->so_debug = ON;
} else if (strcmp(word, "OFF") == 0) {
sr->so_debug = OFF;
} else {
p_error("Parameter error. You can set ON or OFF.");
exit(1);
}
} else if (strcmp(word, "tcp_trace") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
if (strcmp(word, "ON") == 0)
sr->tcp_trace = ON;
else if (strcmp(word, "OFF") == 0)
sr->tcp_trace = OFF;
else if (strcmp(word, "AFTER") == 0)
sr->tcp_trace = AFTER;
else {
p_error("Parameter error. You can set ON, OFF or AFTER.");
exit(1);
}
} else if (strcmp(word, "record_buff") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->record_buff = atoi(word);
} else if (strcmp(word, "trace_buff") == 0) {
skip(" =\n\t", fp);
getword(word, " \n\t;", fp);
sr->trace_buff = atoi(word);
} else if (strcmp(word, "pattern") == 0)
sr->traffic_n = get_pattern(&sr->traffic, fp);
else if (strcmp(word, "}") == 0) {
check_sendrecv_param(sr);
return ROOT;
} else {
p_error("Unknown Keyword.");
exit(1);
}
return state;
}
/*****************************************************************
* Get traffic pattern
*****************************************************************/
/*
* Calculate Sending Total Size (byte)
*/
int total_size(scan)
struct scan_cmd *scan;
{
int i, sum;
struct scan_traffic **traffic;
traffic = &(scan->sender.traffic);
sum=0;
for (i=0; i < scan->sender.traffic_n; i++, traffic = &(*traffic)->next)
sum += (*traffic)->size;
return sum*scan->send_times;
}
/*
* Calculate Sending Message (Times)
*/
int total_message(scan)
struct scan_cmd *scan;
{
int i, sum;
struct scan_traffic **traffic;
traffic = &(scan->sender.traffic);
sum = 0;
for (i=0; i < scan->sender.traffic_n; i++, traffic = &(*traffic)->next) {
sum += ((int)ceil((double)((*traffic)->size)/(double)((*traffic)->packet)));
}
return sum*scan->send_times;
}
/*
* Get Traffic pattern
*/
int get_pattern(traffic_org, fp)
struct scan_traffic **traffic_org;
FILE *fp;
{
struct scan_traffic **traffic;
int n=0, i, s=OFF;
char word[256];
traffic = traffic_org;
skip(" =\n\t{", fp);
while(1) {
if (getword(word, " \n\t,;}", fp) == EOF) {
fprintf(stderr, "ERROR \'%s\'\n",word);
fprintf(stderr, "line %d column %d\n", line, column);
exit(1);
}
if (strcmp(word, "") == 0) {
if (ffgetc(fp) == '}')
break;
else {
fprintf(stderr, "ERROR \'%s\'\n",word);
fprintf(stderr, "line %d column %d\n", line, column);
exit(1);
}
}
if ((*traffic = (struct scan_traffic *)malloc(sizeof(struct scan_traffic))) == NULL) {
p_error("Memory Allocation Error");
perror("malloc:traffic");
exit(1);
}
(*traffic)->next = (struct scan_traffic *) NULL;
(*traffic)->size = atoi(word);
if ((*traffic)->size <= 0) {
p_error("Data Size must be greater then 0");
exit(1);
}
skip(" \n\t,", fp);
getword(word, " \n\t,;}", fp);
(*traffic)->packet = atoi(word);
if ((*traffic)->packet < 0) {
if (state == RECEIVER) {
s = ON;
} else {
p_error("Sender's Packet Size must be greater then 0");
exit(1);
}
}
skip(" \n\t,", fp);
getword(word, " \n\t,;}", fp);
(*traffic)->esleep = atof(word);
skip(" \n\t,", fp);
getword(word, " \n\t,;}", fp);
(*traffic)->isleep = atof(word);
skip(" =\n\t;", fp);
traffic = &(*traffic)->next;
n++;
if (s == ON && n != 1) {
p_error("When you set stream mode, you can set one traffic pattern only.");
exit(1);
}
}
if (debug >= 8) {
traffic = traffic_org;
fprintf(stderr, "\n");
fprintf(stderr, "Traffic Pattern-----------------------------------------\n");
fprintf(stderr, "%8s %8s %8s %8s\n",
"SIZE", "PAKET", "ESLEEP", "ISLEEP");
for (i=0; i<n; i++) {
fprintf(stderr, "%8d %8d %8f %8f\n",
(*traffic)->size, (*traffic)->packet, (*traffic)->esleep, (*traffic)->isleep);
traffic = &(*traffic)->next;
}
fprintf(stderr, "--------------------------------------------------------\n");
}
return n;
}
/*****************************************************************
* Initializeatoin of Parameter
*****************************************************************/
/*
* Initialize struct scan_cmd
*/
void init_scan_param(scan)
struct scan_cmd *scan;
{
init_sendrecv_param(&scan->sender);
init_sendrecv_param(&scan->receiver);
timeval_put(&(scan->start_time), 0.0);
timeval_put(&(scan->end_time), 10.0);
strcpy(scan->filename, "");
scan->transport = 0;
scan->connection_mode = BEFORE;
scan->serverflg = RECEIVE;
scan->next = (struct scan_cmd *) NULL;
}
/*
* Initialize struct send_recv
*/
void init_sendrecv_param(sr)
struct send_recv *sr;
{
strcpy(sr->hostname, "");
strcpy(sr->hostname_cmd, "");
sr->traffic_n = 0;
sr->port = 0;
sr->send_buff = 0;
sr->recv_buff = 0;
sr->mss = 0;
sr->mem_align = 0;
sr->align_offset= 0;
sr->align_pad = 0;
sr->record_buff = 0;
sr->trace_buff = 0;
sr->so_debug = OFF;
sr->tcp_trace = OFF;
sr->no_delay = OFF;
sr->status = S_CLOSE;
}
/*****************************************************************
* Check Parameter
*****************************************************************/
/*
* Check send_recv
*/
void check_sendrecv_param(sr)
struct send_recv *sr;
{
if (strcmp(sr->hostname_cmd, "") == 0) {
if (strcmp(sr->hostname, "") != 0) {
strcpy(sr->hostname_cmd, sr->hostname);
} else {
p_error("'hostname' is not defined.");
exit(1);
}
}
if (sr->traffic_n == 0) {
p_error("'pattern' is not defined.");
exit(1);
}
}
/*
* Check scan_cmd
*/
void check_scan(scan)
struct scan_cmd *scan;
{
if (strcmp(scan->sender.hostname, "") == 0) {
p_error("'sender' is not defined.");
exit(1);
}
if (strcmp(scan->receiver.hostname, "") == 0) {
p_error("'receiver' is not defined.");
exit(1);
}
if (strcmp(scan->filename, "") == 0) {
p_error("'file' is not defined.");
exit(1);
}
if (scan->transport == 0) {
p_error("'protocol' is not defined.");
exit(1);
}
if (scan->transport == UDP) {
scan->sender.so_debug = OFF;
scan->sender.tcp_trace = OFF;
scan->sender.no_delay = OFF;
scan->receiver.so_debug = OFF;
scan->receiver.tcp_trace = OFF;
scan->receiver.no_delay = OFF;
}
if (((scan->transport == UDP || scan->serverflg == RECEIVE) && scan->receiver.port == 0)||
((scan->transport == UDP || scan->serverflg == SEND ) && scan->sender.port == 0)) {
p_error("server 'port = 0' error.");
exit(1);
}
}
/*****************************************************************
* Parser
*****************************************************************/
/*
* skip "Strings"
*/
void skip(str, fp)
char *str;
FILE *fp;
{
char *p;
int c;
DEBUGMSG2(8, fprintf(stderr, "(S:"));
while ((c=ffgetc(fp)) != EOF) {
if (c == '#') { /* # */
if (debug >= 8) {
fprintf(stderr, "(C:");
while (c != EOF && c != '\n') {
fputc(c, stderr);
c = ffgetc(fp);
}
fprintf(stderr, ":C)\n");
} else {
while (c != EOF && c != '\n')
c = ffgetc(fp);
}
} else {
p = str;
while (*p != c) {
if (*p == '\0') {
DEBUGMSG2(8, fprintf(stderr, ":S)"));
uungetc(c, fp);
return;
}
p++;
}
DEBUGMSG2(8, fputc(c, stderr));
}
}
}
/*
* get a word before "str" -> buf
*/
int getword(buf, str, fp)
char *buf, *str;
FILE *fp;
{
int c, l=0;
char *p;
if (feof(fp) != 0)
return EOF;
DEBUGMSG2(8, fprintf(stderr, "(G:"));
while ((c = *buf = ffgetc(fp)) != EOF) {
/*
* '#' is a mark meaning the beginning of comments.
*
*/
if (c == '#') { /* # */
if (debug >= 8) {
fprintf(stderr, "(C:");
while (c != EOF && c != '\n') {
fputc(c, stderr);
c = ffgetc(fp);
}
fprintf(stderr, ":C)\n");
} else {
while(c != EOF && c != '\n')
c = ffgetc(fp);
}
} else {
for (p = str; *p != '\0'; p++) {
if (*p == c) {
uungetc(c, fp);
*buf = '\0';
DEBUGMSG2(8, fprintf(stderr, ":G)"));
return !EOF;
}
}
DEBUGMSG2(8, fputc(c, stderr));
if (l >= CHAR_ARRAY-1) {
p_error("word is too long");
exit(1);
}
l++;
buf++;
}
}
DEBUGMSG2(8, (fprintf(stderr, ":G)"),fputc('\n', stderr)));
*buf = '\0';
return !EOF;
}
/*****************************************************************
* mini subroutine
*****************************************************************/
/*
* fgetc + count(line, column)
* Size of error_buf is MAX_COLUMN + 1.
*/
int ffgetc(fp)
FILE *fp;
{
int c,i;
c = fgetc(fp);
if (c == '\n') {
line++;
bcolumn = column;
column = 1;
error_buf[column-1] = c;
error_buf[column] = '\0';
} else if (c == '\t') {
for (i = column; i <= (column/8+1)*8; i++) {
if (i >= CHAR_ARRAY) {
p_error("line is too long");
exit(1);
}
error_buf[i-1] = ' ';
}
error_buf[i-1] = '\0';
column = i;
} else {
if (column >= CHAR_ARRAY) {
p_error("line is too long");
exit(1);
}
error_buf[column-1] = c;
error_buf[column] = '\0';
column++;
}
return c;
}
/*
* ungetc + uncount(line, column)
*/
int uungetc(c, fp)
int c;
FILE *fp;
{
if (c == '\n') {
line--;
column = bcolumn;
} else {
column--;
}
return ungetc(c, fp);
}
/*
* print error(line, column)
*/
void p_error(s)
char *s;
{
int i;
fprintf(stderr, "Error: line %d column %d (or before):\n %s\n", line, column, s);
fprintf(stderr, "%s\n", error_buf);
for (i=1; i < column; i++) {
printf(" ");
}
printf("^\n");
}
syntax highlighted by Code2HTML, v. 0.9.1