Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
WebSocketサーバ

概要

Arduino Unoを簡易なWebSocketサーバにしてみました。Ethernetシールドが必要です。

WebSocketサーバ化したArduinoから情報をプッシュしてウェブブラウザに表示する例はこちら

  • IEだと動かなかったバグを修正しました。(2014/9/13)
  • 複数クライアントに対応しました。(2014/10/13)

目的

ArduinoをWebSocketサーバにし、ブラウザにArduinoから情報を送ってみます。

WebSocket

WebSocketは、ウェブブラウザとウェブサーバ間で双方向通信・非同期通信を簡単に実行するためのプロトコルです。RFC6455で規定されています。

動的なウェブページを作成する技術として、AjaxやCommetなどが利用されてきました。これらの技術は、ウェブブラウザからウェブサーバに対して情報の取得を依頼するものでした。一方、WebSocketは、ウェブブラウザとウェブサーバとの間で一旦コネクションを確立した後は、ウェブブラウザからウェブサーバ、ウェブサーバからウェブブラウザのどちらの方向の通信も可能となります。これにより、ウェブサーバからウェブブラウザに対する情報のプッシュも容易に実現することができます。

WebSocketを利用するには、まず、ブラウザからサーバにアクセスを行い、コネクションの確立(ハンドシェイク)を行います。その後は、ブラウザからでもサーバからでもデータを送信することが可能となります。

ハンドシェイク

WebSocketサーバで一番面倒なのは、ハンドシェイクでした。概要は以下の通りです。

  • クライアントはサーバに対してハンドシェイクを送信する。
  • サーバはハンドシェイクを読み、クライアントにハンドシェイクを送信する。

ハンドシェイクは、HTTPヘッダで構成されています。クライアントが送信するハンドシェイクは以下のような構成です。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

これに対して、サーバが送信するハンドシェイクは以下のような構成です。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

サーバが送信するSec-WebSocket-Accept:ヘッダの値は、以下のようにして生成します。

  • クライアントが送信したSec-WebSocket-Key:ヘッダの値に、"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"というGUIDを付加する。
  • GUIDを付加後の文字列のSHA-1ハッシュを計算する。
  • 計算したSHA-1ハッシュをBASE64エンコードする。

ということで、SHA-1ハッシュ計算とBASE64エンコードのプログラムを作成する必要があります。

SHA-1は、RFC3174で規定されています。その中で、サンプル実装まで記載されていたので、ほぼそのまま利用しました。Arduino(avr-gcc)で動かすには、SHA1ProcessMessageBlock()という関数の中の以下のコードを

for(t = 0; t < 16; t++)
{
  W[t] = context->Message_Block[t * 4] << 24;
  W[t] |= context->Message_Block[t * 4 + 1] << 16;
  W[t] |= context->Message_Block[t * 4 + 2] << 8;
  W[t] |= context->Message_Block[t * 4 + 3];
}

以下のように修正する必要がありました。

for (t = 0; t < 16; t++)
{
  W[t]  = (uint32_t)context->Message_Block[t * 4    ] << 24;
  W[t] |= (uint32_t)context->Message_Block[t * 4 + 1] << 16;
  W[t] |= (uint32_t)context->Message_Block[t * 4 + 2] <<  8;
  W[t] |= (uint32_t)context->Message_Block[t * 4 + 3];
}

W[]は、uint32_t型の変数で、context->Message_Block[]はuint8_t型の変数です。このため、context->Message_Block[]を24ビット左シフトしてしまうと0になってしまい、想定した動作とは異なってしまっていました。

BASE64エンコードは、RFC4648で規定されています。こちらは、Wikipediaの解説なども参照しながら、車輪の再発明と知りつつ作成しました。

実行例

3秒ごとにアナログの0番ピンの値をコンソールに表示し、接続している全てのブラウザに送信します。また、ブラウザから送信されたテキストメッセージをコンソールに表示するとともに、他のブラウザに送信します。

ブラウザ側の実装は、こちらのページを利用しました。下の図は画面の一部をキャプチャしたものです。左側がclientId = 0, 右側がclientId = 1です。LocationにArduinoのURLを設定し、Connectボタンを押すと接続が確立し、アナログの0番ピンのの値が表示されます。Messageに文字列を入力してSendボタンを押すとArduinoにテキストメッセージが送信され、他のクライアントにテキストメッセ―jを送信します。

Arduinoのコンソールのキャプチャを以下に示します。。ブラウザに送信するメッセージ(Value = XXX)とブラウザから送信されたメッセージが表示されています。

WebSocektクラスの使い方

WebSocketクラスを作成して、ある程度使いまわしができるようにしました。使い方は以下の通りです。サンプルコードを見ると、簡単に理解できると思います。

制限事項

今回作成したプログラムは、少なくとも以下の制限事項があります。

  • 125バイト以内のテキストメッセージ/バイナリメッセージの送受信のみサポートしています。ping/pongなどはサポートしていません。
  • 同時に接続できるクライアント数は4つです。(Ethernetシールドの制約です)
  • セキュリティ面での考慮はしていません。
  • メモリを大量に消費します。
  • エラー処理は弱いです。

WebSocketクラスを使う前に、Ethernet.begin()を利用して、Ethernetシールドの初期設定(MACアドレスとIPアドレスの設定)を行っておく必要があります。

コンストラクタ

説明

WebSocektオブジェクトを作成します。この際、イベントハンドラを登録することもできます。

書式

WebSocket::WebSocket(uint16_t port, char *supportedProtocol, onOpen_t onOpen, onMessage_t onMessage, onClose_t onClose, onError_t onError) : server(port)

引数

port WebSocketサーバが利用するポート番号を指定します。
supportedProtocol Sec-WebSocket-Protocol: ヘッダで返却するプロトコルを指定します。
onOpen クライアントと接続が確立した際に呼び出すイベントハンドラです。型などは後述します。
onMessage クライアントからメッセージを受信した際に呼び出すイベントハンドラです。型などは後述します。
onClose クライアントが接続を切断した際に呼び出すイベントハンドラです。型などは後述します。
onError プロトコル処理でエラーが発生した際に呼び出すイベントハンドラです。型などは後述します。

戻り値

作成したWebSocketオブジェクト。

WebSocket::available()

説明

クライアントとの接続の確立やデータの受信を行います。

状態の変化に応じてイベントハンドラを呼び出します。

onOpen クライアントと接続が確立した際に呼び出す。
onMessage クライアントからメッセージを受信した際に呼び出す。
onClose クライアントが接続を切断した際に呼び出す。
onError プロトコル処理でエラーが発生した際に呼び出す。

書式

int WebSocket::available(int *clientId);

引数

clientId 処理したクライアントID

戻り値

WS_CONNECTED クライアントと接続が確立した。
WS_NO_CLIENT 接続するクライアントが存在しない。
WS_DATA_RECEIVCED クライアントからメッセージを受信した。
WS_NO_DATA クライアントからメッセージが送信されていない。
WS_CLOSED クライアントが接続を切断した。
WS_PROTOCOL_ERROR フレームの読み取りに失敗した。
WS_ERROR その他のエラー。

WebSocket::sendText()

説明

クライアントに対してテキストメッセージを送信する。

書式

int WebSocket::sendText(char *text, int clientId);

引数

text 送信するテキストメッセージ。最大125文字。
clientId テキストメッセージを送信するクライアントID。

戻り値

WS_OK 送信成功。
WS_LINE_TOO_LONG メッセージ長が126文字以上。
WS_STATUS_MISMATCH クライアントと接続されていない。

WebSocket::sendBinary()

説明

クライアントに対してバイナリメッセージを送信する。

書式

int WebSocket::sendBinary(uint8_t *data, uint8_t dataLength, int clientId);

引数

data 送信するデータ。最大125バイト。
dataLength 送信するデータ長。
clientId バイナリメッセージを送信するクライアントID。

戻り値

WS_OK 送信成功。
WS_LINE_TOO_LONG メッセージ長が126文字以上。
WS_STATUS_MISMATCH クライアントと接続されていない。

WebSocket::sendClose()

説明

クライアントに対して接続の切断メッセージを送信する。

書式

int WebSocket::sendClose(uint16_t statusCode, int clientId)

引数

statusCode 切断する際に送信するステータスコード。以下の値を定義済み。
WS_CLOSE_NORMAL:通常終了
clientId 切断メッセージを送信するクライアントID。

戻り値

イベントハンドラ

イベント発生時に呼び出される関数を登録することができます。関数名は任意です。

onOpen

呼出契機

クライアントとの接続が確立した際に呼び出されるイベントハンドラです。

void (*onOpen_t)(char *requestURI, int clientId)

引数

requestURI クライアントが接続要求を出した接続端点。
clientId 接続したクライアントID。

onMessage

呼出契機

クライアントからメッセージを受信した際に呼び出されるイベントハンドラです。

void (*onMessage_t)(char *payload, int payloadLength, int clientId)

引数

payload 受信したメッセージ
payloadLength 受信したメッセージ長
clientId メッセージを受信したクライアントID。

onClose

呼出契機

クライアントが接続を切断した際に呼び出されるイベントハンドラです。

void (*onClose_t)(int clientId)

引数

clientId 切断したクライアントID。

onError

呼出契機

プロトコル処理でエラーが発生した際に呼び出されるイベントハンドラです。

void (*onError_t)(int clientId)

引数

clientId エラーが発生したクライアントID。

スケッチ

GitHubのリポジトリはこちら。古い(同時コネクション数1)WebSocket.hとWebSocket.cppは、こちら

test.ino

#include <SPI.h>
#include <Ethernet.h>
#include "WebSocket.h"

WebSocket wsServer(80, "chat", connectHandler, messageHandler, closeHandler, errorHandler);

void connectHandler(char *requestURI, int clientId) {
  Serial.print("ClientId ");
  Serial.print(clientId);
  Serial.println(": connected");
}

void messageHandler(char *message, int payloadLength, int clientId) {
  Serial.print("ClientId ");
  Serial.print(clientId);
  Serial.print(": length = "); 
  Serial.print(payloadLength);
  Serial.print(", message = ");
  Serial.println(message);
  
  for (int i = 0; i < MAX_SOCK_NUM; i++) {
    if (i != clientId) {
      wsServer.sendText(message, i);
    }
  }
}

void closeHandler(int clientId) {
  Serial.print("ClientId ");
  Serial.print(clientId);
  Serial.println(": closed");
}

void errorHandler(int clientId) {
  wsServer.sendClose(WS_CLOSE_PROTOCOL_ERROR, clientId);
  Serial.print("ClientId ");
  Serial.println(clientId);
  Serial.println(": error");
}

void sendAnalogData() {
  static unsigned long lastTime = 0;
  unsigned long currentTime;
  
  char buffer[WS_MAX_PAYLOAD_LENGTH];
  
  currentTime = millis();
  if ((currentTime - lastTime) > 3000) {
    sprintf(buffer, "A0: %d", analogRead(0));
    Serial.println(buffer);
    for (int i = 0; i < MAX_SOCK_NUM; i++) {
      wsServer.sendText(buffer, i);
      lastTime = currentTime;
    }
  }
}

void setup() {
  byte macAddress[] = {0x90, 0xa2, 0xda, 0x0d, 0xd2, 0xef};
  byte ipAddress[] = {192, 168, 11, 200};

  Serial.begin(9600);
  Ethernet.begin(macAddress, ipAddress);
  Serial.print("Server is at ");
  Serial.println(Ethernet.localIP());
  
  wsServer.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  int clientId;
  wsServer.available(clientId);
  delay(1000);
  sendAnalogData();
}

WebSocket.h

#ifndef WEBSOCKET_H
#define WEBSOCKET_H

#include <Ethernet.h>
#include <Arduino.h>

#define WS_MAX_PAYLOAD_LENGTH  125
#define WS_MAX_LINE_LENGTH     128
#define WS_KEY_LENGTH           32

#define WS_OK 1
#define WS_CONNECTED 2
#define WS_NO_CLIENT 3
#define WS_DATA_RECEIVCED 4
#define WS_NO_DATA 5
#define WS_CLOSED 6
#define WS_PROTOCOL_ERROR 7
#define WS_LINE_TOO_LONG  -1
#define WS_STATUS_MISMATCH -2
#define WS_NOT_SUPPORTED -3
#define WS_ERROR -127

#define WS_SENDTO_ALL -1

#define WS_HAS_GET                    0x01
#define WS_HAS_HOST                   0x02     
#define WS_HAS_UPGRADE                0x04
#define WS_HAS_CONNECTION             0x08
#define WS_HAS_SEC_WEBSOCKET_KEY      0x10
#define WS_HAS_SEC_WEBSOCKET_VERSION  0x20
#define WS_HAS_ALL_HEADERS            0x3f
#define WS_HAS_SUBPROTOCOL            0x40

#define WS_FRAME_TEXT   0x01
#define WS_FRAME_BINARY 0x02
#define WS_FRAME_CLOSE  0x08
#define WS_FRAME_FIN    0x80

#define WS_CLOSE_NORMAL          1000
#define WS_CLOSE_PROTOCOL_ERROR  1002
#define WS_CLOSE_MESSAGE_TOO_BIG 1009

#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

typedef enum {
  CONNECTING = 0,
  OPEN = 1,
  CLOSING = 2,
  CLOSED = 3, 
} wsStatus;

typedef struct {
  char *header;
  char *value;
  uint8_t validation;
  char *variable;
} wsHeader;

typedef void (*onOpen_t)(char *requestURI, int clientId);
typedef void (*onMessage_t)(char *payload, int payloadLength, int clientId);
typedef void (*onClose_t)(int clientId);
typedef void (*onError_t)(int clientId);

class WebSocket {
public:
  WebSocket(uint16_t port, char *supportedProtocol, onOpen_t onOpen = NULL, onMessage_t onMessage = NULL, onClose_t onClose = NULL, onError_t onError = NULL);
  wsStatus status[MAX_SOCK_NUM];
  void begin();
  int available(int *clientId);
  int sendText(char *text, int clientId);
  int sendBinary(uint8_t *data, uint8_t dataLength, int clientId);
  int sendPayload(uint8_t *payLoadData, uint8_t payloadLength, uint8_t opcode, int clientId);
  int sendClose(uint16_t statusCode, int clientId);
private:
  EthernetServer server;
  EthernetClient client[MAX_SOCK_NUM];
  uint16_t port;
  char *supportedProtocol;
  onOpen_t onOpen;
  onMessage_t onMessage;
  onClose_t onClose;
  onError_t onError;
  int handshake(char *requestURI, int clientId);
  int readHTMLHeader(uint8_t *buffer, uint8_t bufferLength, int clientId); 
  int readFrame(char *frame, int *payloadLength, int clientId);
};

#endif /* WEBSOCKET_H */

WebSocket.cpp

#include "WebSocket.h"
#include "sha1.h"
#include "base64.h"

WebSocket::WebSocket(uint16_t port, char *supportedProtocol, onOpen_t onOpen, onMessage_t onMessage, onClose_t onClose, onError_t onError) : server(port) {
  this->port = port;
  this->supportedProtocol = strdup(supportedProtocol);
  this->onOpen = onOpen;
  this->onMessage = onMessage;
  this->onClose = onClose;
  this->onError = onError;

  for (int i = 0; i < MAX_SOCK_NUM; i++) {
    status[i] = CLOSED;
  }
}

void WebSocket::begin() {
  server.begin();
}

int WebSocket::available(int *clientId) {
  char payloadData[WS_MAX_PAYLOAD_LENGTH + 1];
  int payloadLength;
  char requestURI[WS_MAX_LINE_LENGTH];
  int opcode;
  EthernetClient c;
  int retval = WS_ERROR;
  
  *clientId = -1;

  if (c = server.available()) {
    // check for the connection 
    for (int i = 0; i < MAX_SOCK_NUM; i++) {
      if (c == client[i]) { // existing connection
        *clientId = i;
        if (status[i] == OPEN) {
          if (client[*clientId].available()) {
            opcode = readFrame(payloadData, &payloadLength, *clientId);
            switch (opcode) {
              case WS_FRAME_TEXT:
              case WS_FRAME_BINARY:
                if (onMessage) {
                  onMessage(payloadData, payloadLength, *clientId);
                }
                return WS_DATA_RECEIVCED;
              case WS_FRAME_CLOSE :
                if (onClose) {
                  onClose(*clientId);
                }
                sendClose(WS_CLOSE_NORMAL, *clientId);
                client[*clientId].stop();
                return WS_CLOSED;
              default: // got unsupported or unknown message
                retval = WS_PROTOCOL_ERROR;
                goto wsAvailableError;
            }
          } else {  // server is available but client is not available
            retval = WS_STATUS_MISMATCH;
            goto wsAvailableError;
          }
        } else { // status is not OPENl
          retval = WS_STATUS_MISMATCH;
          goto wsAvailableError;
        }
      }
    }
    
    // New connection.
    for (int i = 0; i < MAX_SOCK_NUM; i++) {
      if (status[i] == CLOSED) {
        *clientId = i;
        client[i] = c;
        if (handshake(requestURI, *clientId) == WS_OK) {
          if (onOpen) {
            onOpen(requestURI, *clientId);
          }
          status[*clientId] = OPEN;
          return WS_CONNECTED;
        } else {
          status[*clientId] = CLOSED;
          return WS_ERROR;
        }
      }
    }
    
    wsAvailableError:
    if (onError) {
      onError(*clientId);
    }
    return retval;
  }
}

int WebSocket::sendText(char *text, int clientId) {
  return sendPayload((uint8_t *)text, strlen(text), WS_FRAME_TEXT, clientId);
}

int WebSocket::sendBinary(uint8_t *data, uint8_t dataLength, int clientId) {
  return sendPayload(data, dataLength, WS_FRAME_BINARY, clientId);
}

int WebSocket::sendPayload(uint8_t *payLoadData, uint8_t payloadLength, uint8_t opcode, int clientId) {
  if (status[clientId] == OPEN) {
    if (payloadLength > WS_MAX_PAYLOAD_LENGTH) {
      return WS_LINE_TOO_LONG;
    }

    client[clientId].write(WS_FRAME_FIN | opcode);
    client[clientId].write(payloadLength & 0x7f);

    for (int i = 0; i < payloadLength; i++) {
      client[clientId].write(payLoadData[i]);
    }
    return WS_OK;
  } else {
    return WS_STATUS_MISMATCH;
  }
}

int WebSocket::sendClose(uint16_t statusCode, int clientId) {
  if (status[clientId] == OPEN) {
    client[clientId].write(WS_FRAME_FIN | WS_FRAME_CLOSE);
    client[clientId].write((uint8_t)(statusCode >> 8));
    client[clientId].write((uint8_t)(statusCode & 0xff));
    status[clientId] = CLOSED;
    return WS_OK;
  } else {
    return WS_STATUS_MISMATCH;
  }
}

int WebSocket::handshake(char * requestURI, int clientId) {
  char buffer[WS_MAX_LINE_LENGTH];
  char wsKey[WS_MAX_LINE_LENGTH];
  char charRead;
  int numRead = 0;
  uint8_t headerValidation = 0;
  SHA1Context sha;

  while (readHTMLHeader((uint8_t *)buffer, WS_MAX_LINE_LENGTH, clientId) > 0) {
    if (strncmp((char *)buffer, "GET", 3) == 0) {
      strtok((char *)buffer, " \t");
      strcpy(requestURI, strtok(NULL, " \t"));
      headerValidation |= WS_HAS_GET;
    } else if (strncasecmp((char *)buffer, "host:", 5) == 0) {
      headerValidation |= WS_HAS_HOST;
    } else if (strncasecmp((char *)buffer, "upgrade:", 8) == 0) {
      strtok((char *)buffer, " \t");
      if (strncasecmp(strtok(NULL, " \t"), "websocket", 9) == 0) {
        headerValidation |= WS_HAS_UPGRADE;
      }
    } else if (strncasecmp((char *)buffer, "connection:", 11) == 0) {
      headerValidation |= WS_HAS_CONNECTION;
    } else if (strncasecmp((char *)buffer, "sec-websocket-protocol:", 23) == 0) {
      headerValidation |= WS_HAS_SUBPROTOCOL;
    } else if (strncasecmp((char *)buffer, "sec-websocket-key:", 18) == 0) {
      strtok((char *)buffer, " \t");
      strcpy((char *)wsKey, strtok(NULL, " \t"));
      headerValidation |= WS_HAS_SEC_WEBSOCKET_KEY;
    } else if (strncasecmp((char *)buffer, "sec-websocket-version:", 22) == 0) {
      strtok((char *)buffer, " \t");
      if (strncasecmp(strtok(NULL, " \t"), "13", 2) == 0) {
        headerValidation |= WS_HAS_SEC_WEBSOCKET_VERSION;
      }
    }
  }

  if (headerValidation == WS_HAS_ALL_HEADERS) {
    strcat((char *)wsKey, WS_GUID);
    SHA1Reset(&sha);
    SHA1Input(&sha, (uint8_t *)wsKey, strlen(wsKey));
    SHA1Result(&sha, (uint8_t *)buffer);
    buffer[20] = 0;

    base64Encode(buffer, wsKey);
    client[clientId].print("HTTP/1.1 101 Switching Protocols\r\n");
    client[clientId].print("Upgrade: websocket\r\n");
    client[clientId].print("Connection: Upgrade\r\n");
    client[clientId].print("Sec-WebSocket-Accept: ");
    client[clientId].print(wsKey);
    client[clientId].print("\r\n");
    if (headerValidation & WS_HAS_SUBPROTOCOL) {
      client[clientId].print("Sec-WebSocket-Protocol: ");
      client[clientId].print(supportedProtocol);
      client[clientId].print("\r\n");
    }
    client[clientId].print("\r\n");
    return WS_OK;
  } else {
    return WS_ERROR;
  }
}

int WebSocket::readHTMLHeader(uint8_t * buffer, uint8_t bufferLength, int clientId) {
  int dataRead;
  int numRead = 0;

  while ((dataRead = client[clientId].read()) != -1) {
    buffer[numRead++] = dataRead;
    if (dataRead == '\n') {
      buffer[numRead - 2] = '\0';
      return WS_OK;
    } else if (numRead > bufferLength) {
      return WS_CLOSE_MESSAGE_TOO_BIG;
    }
  }

  return WS_ERROR;
}

int WebSocket::readFrame(char * payloadData, int * payloadLength, int clientId) {
  uint8_t data;
  int opcode;
  int mask;
  char maskingKey[4];

  data = client[clientId].read();
  if (!(data & 0x80)) {
    return WS_NOT_SUPPORTED;
  }
  opcode = data & 0x0f;

  data = client[clientId].read();
  mask = data & 0x80 ? true : false;
  *payloadLength = data & 0x7f;

  if (*payloadLength > 125) {
    return WS_NOT_SUPPORTED;
  }

  if (mask) {
    for (int i = 0; i < 4; i++) {
      maskingKey[i] = client[clientId].read();
    }
  }

  for (int i = 0; i < *payloadLength; i++) {
    if (mask) {
      payloadData[i] = client[clientId].read() ^ maskingKey[i % 4];
    } else {
      payloadData[i] = client[clientId].read();
    }
  }
  payloadData[*payloadLength] = '\0';

  return opcode;
}

base64.h

#ifndef BASE64_H
#define BASE64_H

void base64Encode(char *input, char *output);

#endif /* BASE64_H */

base64.cpp

void base64Encode(char *input, char *output) {
  const char *encTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  int inPos = 0, outPos = 0;
  int remainder = 0;

  for (char *p = input; *p; p++, inPos++) {
    switch (remainder = inPos % 3) {
    case 0:
      output[outPos++] = encTable[((input[inPos] >> 2) & 0x3f)];
      break;
    case 1:
      output[outPos++] = encTable[((input[inPos - 1] << 4) & 0x30) | ((input[inPos] >> 4) & 0x0f)];
      break;
    case 2:
      output[outPos++] = encTable[((input[inPos - 1] << 2) & 0x3c) | ((input[inPos] >> 6) & 0x03)];
      output[outPos++] = encTable[(input[inPos] & 0x3f)];
      break;
    }
  }
  
  if (remainder != 2) { /* inPos is incremented at the for loop above. */
    output[outPos++] = encTable[(input[inPos - 1] << (4 - 2 * remainder)) & 0x3f]; /* Pads 0s */
  }
  
  while (outPos % 4) {
    output[outPos++] = '='; /* Pads '='s */
  }

  output[outPos] = '\0';
}

sha1.h

このスケッチ、RFC6455に記載のサンプル実装です。インデントは変更しました。

/*
 *  sha1.h
 *
 *  Description:
 *      This is the header file for code which implements the Secure
 *      Hashing Algorithm 1 as defined in FIPS PUB 180-1 published
 *      April 17, 1995.
 *
 *      Many of the variable names in this code, especially the
 *      single character names, were used because those were the names
 *      used in the publication.
 *
 *      Please read the file sha1.c for more information.
 *
 */
 
#ifndef SHA1_H
#include <Arduino.h>

enum {
  shaSuccess = 0,
  shaNull,            /* Null pointer parameter */
  shaInputTooLong,    /* input data too long */
  shaStateError       /* called Input after Result */
};
#define SHA1HashSize 20

/*
 *  This structure will hold context information for the SHA-1
 *  hashing operation
 */
typedef struct SHA1Context {
  uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest  */
  uint32_t Length_Low;                        /* Message length in bits */
  uint32_t Length_High;                       /* Message length in bits */
  int_least16_t Message_Block_Index;          /* Index into message block array */
  uint8_t Message_Block[64];                  /* 512-bit message blocks */
  int Computed;                               /* Is the digest computed? */
  int Corrupted;                              /* Is the message digest corrupted? */
} SHA1Context;

/*
 *  Function Prototypes
 */
int SHA1Reset(SHA1Context *);
int SHA1Input(SHA1Context *, const uint8_t *, unsigned int);
int SHA1Result(SHA1Context *, uint8_t Message_Digest[SHA1HashSize]);
int SHA1ResultL(SHA1Context *, uint8_t Message_Digest[SHA1HashSize]);
#endif

sha1.cpp

このスケッチ、RFC6455に記載のサンプル実装を一部改造したものです。インデントは変更しました。

/*
 *  sha1.c
 *
 *  Description:
 *      This file implements the Secure Hashing Algorithm 1 as
 *      defined in FIPS PUB 180-1 published April 17, 1995.
 *
 *      The SHA-1, produces a 160-bit message digest for a given
 *      data stream.  It should take about 2**n steps to find a
 *      message with the same digest as a given message and
 *      2**(n/2) to find any two messages with the same digest,
 *      when n is the digest size in bits.  Therefore, this
 *      algorithm can serve as a means of providing a
 *      "fingerprint" for a message.
 *
 *  Portability Issues:
 *      SHA-1 is defined in terms of 32-bit "words".  This code
 *      uses <stdint.h> (included via "sha1.h" to define 32 and 8
 *      bit unsigned integer types.  If your C compiler does not
 *      support 32 bit unsigned integers, this code is not
 *      appropriate.
 *
 *  Caveats:
 *      SHA-1 is designed to work with messages less than 2^64 bits
 *      long.  Although SHA-1 allows a message digest to be generated
 *      for messages of any number of bits less than 2^64, this
 *      implementation only works with messages with a length that is
 *      a multiple of the size of an 8-bit character.
 *
 */
#include "sha1.h"

/*
 *  Define the SHA1 circular left shift macro
 */
#define SHA1CircularShift(bits,word) (((word) << (bits)) | ((word) >> (32-(bits))))

/* Local Function Prototyptes */
void SHA1PadMessage(SHA1Context *);
void SHA1ProcessMessageBlock(SHA1Context *);

/*
 *  SHA1Reset
 *
 *  Description:
 *      This function will initialize the SHA1Context in preparation
 *      for computing a new SHA1 message digest.
 *
 *  Parameters:
 *      context: [in/out]
 *          The context to reset.
 *
 *  Returns:
 *      sha Error Code.
 *
 */
int SHA1Reset(SHA1Context *context) {
  if (!context) {
    return shaNull;
  }

  context->Length_Low             = 0;
  context->Length_High            = 0;
  context->Message_Block_Index    = 0;

  context->Intermediate_Hash[0]   = 0x67452301;
  context->Intermediate_Hash[1]   = 0xEFCDAB89;
  context->Intermediate_Hash[2]   = 0x98BADCFE;
  context->Intermediate_Hash[3]   = 0x10325476;
  context->Intermediate_Hash[4]   = 0xC3D2E1F0;

  context->Computed   = 0;
  context->Corrupted  = 0;
  return shaSuccess;
}

/*
 *  SHA1Result
 *
 *  Description:
 *      This function will return the 160-bit message digest into the
 *      Message_Digest array  provided by the caller.
 *      NOTE: The first octet of hash is stored in the 0th element,
 *            the last octet of hash in the 19th element.
 *
 *  Parameters:
 *      context: [in/out]
 *          The context to use to calculate the SHA-1 hash.
 *      Message_Digest: [out]
 *          Where the digest is returned.
 *
 *  Returns:
 *      sha Error Code.
 *
 */
int SHA1Result( SHA1Context *context, uint8_t Message_Digest[SHA1HashSize]) {
  int i;

  if (!context || !Message_Digest) {
    return shaNull;
  }

  if (context->Corrupted) {
    return context->Corrupted;
  }

  if (!context->Computed) {
    SHA1PadMessage(context);
    for (i=0; i<64; ++i) {
      context->Message_Block[i] = 0; /* message may be sensitive, clear it out */
    }
    context->Length_Low = 0;    /* and clear length */
    context->Length_High = 0;
    context->Computed = 1;
  }

  for (i = 0; i < SHA1HashSize; ++i) {
    Message_Digest[i] = context->Intermediate_Hash[i>>2] >> 8 * (3 - (i & 0x03));
  }

  return shaSuccess;
}

/*
 *  SHA1Input
 *
 *  Description:
 *      This function accepts an array of octets as the next portion
 *      of the message.
 *
 *  Parameters:
 *      context: [in/out]
 *          The SHA context to update
 *      message_array: [in]
 *          An array of characters representing the next portion of
 *          the message.
 *      length: [in]
 *          The length of the message in message_array
 *
 *  Returns:
 *      sha Error Code.
 *
 */
int SHA1Input(SHA1Context *context, const uint8_t  *message_array, unsigned length) {
  if (!length) {
    return shaSuccess;
  }

  if (!context || !message_array) {
    return shaNull;
  }

  if (context->Computed) {
    context->Corrupted = shaStateError;
    return shaStateError;
  }

  if (context->Corrupted) {
    return context->Corrupted;
  }
  
  while (length-- && !context->Corrupted) {
    context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF);
    context->Length_Low += 8;
    if (context->Length_Low == 0) {
      context->Length_High++;
      if (context->Length_High == 0) {
        context->Corrupted = 1;      /* Message is too long */
      }
    }

    if (context->Message_Block_Index == 64) {
      SHA1ProcessMessageBlock(context);
    }

    message_array++;
  }
  return shaSuccess;
}

/*
 *  SHA1ProcessMessageBlock
 *
 *  Description:
 *      This function will process the next 512 bits of the message
 *      stored in the Message_Block array.
 *
 *  Parameters:
 *      None.
 *
 *  Returns:
 *      Nothing.
 *
 *  Comments:
 *      Many of the variable names in this code, especially the
 *      single character names, were used because those were the
 *      names used in the publication.
 *
 *
 */
void SHA1ProcessMessageBlock(SHA1Context *context) {
  const uint32_t K[] =    {       /* Constants defined in SHA-1   */
                            0x5A827999,
                            0x6ED9EBA1,
                            0x8F1BBCDC,
                            0xCA62C1D6
                          };
  int           t;                 /* Loop counter                */
  uint32_t      temp;              /* Temporary word value        */
  uint32_t      W[80];             /* Word sequence               */
  uint32_t      A, B, C, D, E;     /* Word buffers                */

  /*
   *  Initialize the first 16 words in the array W
   */
  for (t = 0; t < 16; t++) {
    W[t]  = (uint32_t)context->Message_Block[t * 4    ] << 24;
    W[t] |= (uint32_t)context->Message_Block[t * 4 + 1] << 16;
    W[t] |= (uint32_t)context->Message_Block[t * 4 + 2] <<  8;
    W[t] |= (uint32_t)context->Message_Block[t * 4 + 3];
  }
 
  for (t = 16; t < 80; t++) {
    W[t] = SHA1CircularShift(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
  }

  A = context->Intermediate_Hash[0];
  B = context->Intermediate_Hash[1];
  C = context->Intermediate_Hash[2];
  D = context->Intermediate_Hash[3];
  E = context->Intermediate_Hash[4];

  for (t = 0; t < 20; t++) {
    temp =  SHA1CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
    E = D;
    D = C;
    C = SHA1CircularShift(30, B);
    B = A;
    A = temp;
  }

  for (t = 20; t < 40; t++) {
    temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1];
    E = D;
    D = C;
    C = SHA1CircularShift(30, B);
    B = A;
    A = temp;
  }

  for (t = 40; t < 60; t++) {
    temp = SHA1CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
    E = D;
    D = C;
    C = SHA1CircularShift(30, B);
    B = A;
    A = temp;
  }

  for (t = 60; t < 80; t++) {
    temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3];
    E = D;
    D = C;
    C = SHA1CircularShift(30, B);
    B = A;
    A = temp;
  }

  context->Intermediate_Hash[0] += A;
  context->Intermediate_Hash[1] += B;
  context->Intermediate_Hash[2] += C;
  context->Intermediate_Hash[3] += D;
  context->Intermediate_Hash[4] += E;

  context->Message_Block_Index = 0;
}


/*
 *  SHA1PadMessage
 *
 *  Description:
 *      According to the standard, the message must be padded to an even
 *      512 bits.  The first padding bit must be a '1'.  The last 64
 *      bits represent the length of the original message.  All bits in
 *      between should be 0.  This function will pad the message
 *      according to those rules by filling the Message_Block array
 *      accordingly.  It will also call the ProcessMessageBlock function
 *      provided appropriately.  When it returns, it can be assumed that
 *      the message digest has been computed.
 *
 *  Parameters:
 *      context: [in/out]
 *          The context to pad
 *      ProcessMessageBlock: [in]
 *          The appropriate SHA*ProcessMessageBlock function
 *  Returns:
 *      Nothing.
 *
 */

void SHA1PadMessage(SHA1Context *context) {
  /*
   *  Check to see if the current message block is too small to hold
   *  the initial padding bits and length.  If so, we will pad the
   *  block, process it, and then continue padding into a second
   *  block.
   */
  if (context->Message_Block_Index > 55) {
    context->Message_Block[context->Message_Block_Index++] = 0x80;
    while (context->Message_Block_Index < 64) {
      context->Message_Block[context->Message_Block_Index++] = 0;
    }

    SHA1ProcessMessageBlock(context);

    while(context->Message_Block_Index < 56) {
      context->Message_Block[context->Message_Block_Index++] = 0;
    }
  } else {
    context->Message_Block[context->Message_Block_Index++] = 0x80;
    while (context->Message_Block_Index < 56) {
      context->Message_Block[context->Message_Block_Index++] = 0;
    }
  }

  /*
   *  Store the message length as the last 8 octets
   */
  context->Message_Block[56] = context->Length_High >> 24;
  context->Message_Block[57] = context->Length_High >> 16;
  context->Message_Block[58] = context->Length_High >> 8;
  context->Message_Block[59] = context->Length_High;
  context->Message_Block[60] = context->Length_Low >> 24;
  context->Message_Block[61] = context->Length_Low >> 16;
  context->Message_Block[62] = context->Length_Low >> 8;
  context->Message_Block[63] = context->Length_Low;

  SHA1ProcessMessageBlock(context);
}

int SHA1ResultL( SHA1Context *context, uint8_t Message_Digest[SHA1HashSize]) {
  int i;

  if (!context || !Message_Digest) {
    return shaNull;
  }

  if (context->Corrupted) {
    return context->Corrupted;
  }

  if (!context->Computed) {
    SHA1PadMessage(context);
    for(i=0; i<64; ++i) {
      context->Message_Block[i] = 0;            /* message may be sensitive, clear it out */
    }
    context->Length_Low = 0;    /* and clear length */
    context->Length_High = 0;
    context->Computed = 1;
  }
    
  for (int i=0; i<5; i++) {
    uint32_t a,b;
    a = context->Intermediate_Hash[i];
    b  = (a << 24);
    b |= (a <<  8) & 0x00ff0000;
    b |= (a >>  8) & 0x0000ff00;
    b |= (a >> 24);
    context->Intermediate_Hash[i] = b;
  }


  for(i = 0; i < SHA1HashSize; ++i) {
        Message_Digest[i] = context->Intermediate_Hash[i >> 2] >> 8 * (3 - (i & 0x03));
  }

  return shaSuccess;
}

static void longReverse(uint32_t *buffer, uint8_t byteCount) {
  uint32_t value;

  byteCount /= sizeof(uint32_t);
  while (byteCount--) {
    value = *buffer;
    value = ((value & 0xFF00FF00L) >> 8) | ((value & 0x00FF00FFL) << 8);
    *buffer++ = (value << 16) | (value >> 16);
  }
}

バージョン

Arduino 1.5.7/Arduino Uno



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2014 garretlab all rights reserved.
inserted by FC2 system