Split up GCM into a start/update/finish cycle
diff --git a/library/gcm.c b/library/gcm.c
index f1daaff..f0bacc6 100644
--- a/library/gcm.c
+++ b/library/gcm.c
@@ -169,41 +169,24 @@
     PUT_UINT32_BE( zl, output, 12 );
 }
 
-int gcm_crypt_and_tag( gcm_context *ctx,
-                       int mode,
-                       size_t length,
-                       const unsigned char *iv,
-                       size_t iv_len,
-                       const unsigned char *add,
-                       size_t add_len,
-                       const unsigned char *input,
-                       unsigned char *output,
-                       size_t tag_len,
-                       unsigned char *tag )
+int gcm_starts( gcm_context *ctx,
+                int mode,
+                const unsigned char *iv,
+                size_t iv_len,
+                const unsigned char *add,
+                size_t add_len )
 {
-    unsigned char y[16];
-    unsigned char ectr[16];
-    unsigned char buf[16];
     unsigned char work_buf[16];
     size_t i;
     const unsigned char *p;
-    unsigned char *out_p = output;
     size_t use_len;
-    uint64_t orig_len = length * 8;
-    uint64_t orig_add_len = add_len * 8;
 
-    memset( y, 0x00, 16 );
-    memset( work_buf, 0x00, 16 );
-    memset( tag, 0x00, tag_len );
-    memset( buf, 0x00, 16 );
-
-    if( output > input && (size_t) ( output - input ) < length )
-        return( POLARSSL_ERR_GCM_BAD_INPUT );
+    ctx->mode = mode;
 
     if( iv_len == 12 )
     {
-        memcpy( y, iv, iv_len );
-        y[15] = 1;
+        memcpy( ctx->y, iv, iv_len );
+        ctx->y[15] = 1;
     }
     else
     {
@@ -216,64 +199,100 @@
             use_len = ( iv_len < 16 ) ? iv_len : 16;
 
             for( i = 0; i < use_len; i++ )
-                y[i] ^= p[i];
+                ctx->y[i] ^= p[i];
 
-            gcm_mult( ctx, y, y );
+            gcm_mult( ctx, ctx->y, ctx->y );
 
             iv_len -= use_len;
             p += use_len;
         }
 
         for( i = 0; i < 16; i++ )
-            y[i] ^= work_buf[i];
+            ctx->y[i] ^= work_buf[i];
 
-        gcm_mult( ctx, y, y );
+        gcm_mult( ctx, ctx->y, ctx->y );
     }
 
-    aes_crypt_ecb( &ctx->aes_ctx, AES_ENCRYPT, y, ectr );
-    memcpy( tag, ectr, tag_len );
+    aes_crypt_ecb( &ctx->aes_ctx, AES_ENCRYPT, ctx->y, ctx->base_ectr );
 
+    ctx->add_len = add_len;
     p = add;
     while( add_len > 0 )
     {
         use_len = ( add_len < 16 ) ? add_len : 16;
 
         for( i = 0; i < use_len; i++ )
-            buf[i] ^= p[i];
+            ctx->buf[i] ^= p[i];
 
-        gcm_mult( ctx, buf, buf );
+        gcm_mult( ctx, ctx->buf, ctx->buf );
 
         add_len -= use_len;
         p += use_len;
     }
 
+    return( 0 );
+}
+
+int gcm_update( gcm_context *ctx,
+                size_t length,
+                const unsigned char *input,
+                unsigned char *output )
+{
+    unsigned char ectr[16];
+    size_t i;
+    const unsigned char *p;
+    unsigned char *out_p = output;
+    size_t use_len;
+
+    if( output > input && (size_t) ( output - input ) < length )
+        return( POLARSSL_ERR_GCM_BAD_INPUT );
+
+    ctx->len += length;
+
     p = input;
     while( length > 0 )
     {
         use_len = ( length < 16 ) ? length : 16;
 
         for( i = 16; i > 12; i-- )
-            if( ++y[i - 1] != 0 )
+            if( ++ctx->y[i - 1] != 0 )
                 break;
 
-        aes_crypt_ecb( &ctx->aes_ctx, AES_ENCRYPT, y, ectr );
+        aes_crypt_ecb( &ctx->aes_ctx, AES_ENCRYPT, ctx->y, ectr );
 
         for( i = 0; i < use_len; i++ )
         {
-            if( mode == GCM_DECRYPT )
-                buf[i] ^= p[i];
+            if( ctx->mode == GCM_DECRYPT )
+                ctx->buf[i] ^= p[i];
             out_p[i] = ectr[i] ^ p[i];
-            if( mode == GCM_ENCRYPT )
-                buf[i] ^= out_p[i];
+            if( ctx->mode == GCM_ENCRYPT )
+                ctx->buf[i] ^= out_p[i];
         }
 
-        gcm_mult( ctx, buf, buf );
+        gcm_mult( ctx, ctx->buf, ctx->buf );
 
         length -= use_len;
         p += use_len;
         out_p += use_len;
     }
 
+    return( 0 );
+}
+
+int gcm_finish( gcm_context *ctx,
+                unsigned char *tag,
+                size_t tag_len )
+{
+    unsigned char work_buf[16];
+    size_t i;
+    uint64_t orig_len = ctx->len * 8;
+    uint64_t orig_add_len = ctx->add_len * 8;
+
+    memcpy( tag, ctx->base_ectr, tag_len );
+
+    if( tag_len > 16 )
+        return( POLARSSL_ERR_GCM_BAD_INPUT );
+
     if( orig_len || orig_add_len )
     {
         memset( work_buf, 0x00, 16 );
@@ -284,17 +303,43 @@
         PUT_UINT32_BE( ( orig_len           ), work_buf, 12 );
 
         for( i = 0; i < 16; i++ )
-            buf[i] ^= work_buf[i];
+            ctx->buf[i] ^= work_buf[i];
 
-        gcm_mult( ctx, buf, buf );
+        gcm_mult( ctx, ctx->buf, ctx->buf );
 
         for( i = 0; i < tag_len; i++ )
-            tag[i] ^= buf[i];
+            tag[i] ^= ctx->buf[i];
     }
 
     return( 0 );
 }
 
+int gcm_crypt_and_tag( gcm_context *ctx,
+                       int mode,
+                       size_t length,
+                       const unsigned char *iv,
+                       size_t iv_len,
+                       const unsigned char *add,
+                       size_t add_len,
+                       const unsigned char *input,
+                       unsigned char *output,
+                       size_t tag_len,
+                       unsigned char *tag )
+{
+    int ret;
+
+    if( ( ret = gcm_starts( ctx, mode, iv, iv_len, add, add_len ) ) != 0 )
+        return( ret );
+
+    if( ( ret = gcm_update( ctx, length, input, output ) ) != 0 )
+        return( ret );
+
+    if( ( ret = gcm_finish( ctx, tag, tag_len ) ) != 0 )
+        return( ret );
+
+    return( 0 );
+}
+
 int gcm_auth_decrypt( gcm_context *ctx,
                       size_t length,
                       const unsigned char *iv,
@@ -562,7 +607,9 @@
 
         for( i = 0; i < MAX_TESTS; i++ )
         {
-            printf( "  AES-GCM-%3d #%d (%s): ", key_len, i, "enc" );
+            if( verbose != 0 )
+                printf( "  AES-GCM-%3d #%d (%s): ", key_len, i, "enc" );
+
             gcm_init( &ctx, key[key_index[i]], key_len );
 
             ret = gcm_crypt_and_tag( &ctx, GCM_ENCRYPT,
@@ -584,7 +631,9 @@
             if( verbose != 0 )
                 printf( "passed\n" );
 
-            printf( "  AES-GCM-%3d #%d (%s): ", key_len, i, "dec" );
+            if( verbose != 0 )
+                printf( "  AES-GCM-%3d #%d (%s): ", key_len, i, "dec" );
+
             gcm_init( &ctx, key[key_index[i]], key_len );
 
             ret = gcm_crypt_and_tag( &ctx, GCM_DECRYPT,
@@ -605,10 +654,138 @@
 
             if( verbose != 0 )
                 printf( "passed\n" );
+
+            if( verbose != 0 )
+                printf( "  AES-GCM-%3d #%d split (%s): ", key_len, i, "enc" );
+
+            gcm_init( &ctx, key[key_index[i]], key_len );
+
+            ret = gcm_starts( &ctx, GCM_ENCRYPT,
+                              iv[iv_index[i]], iv_len[i],
+                              additional[add_index[i]], add_len[i] );
+            if( ret != 0 )
+            {
+                if( verbose != 0 )
+                    printf( "failed\n" );
+
+                return( 1 );
+            }
+
+            if( pt_len[i] > 32 )
+            {
+                size_t rest_len = pt_len[i] - 32;
+                ret = gcm_update( &ctx, 32, pt[pt_index[i]], buf );
+                if( ret != 0 )
+                {
+                    if( verbose != 0 )
+                        printf( "failed\n" );
+
+                    return( 1 );
+                }
+
+                ret = gcm_update( &ctx, rest_len, pt[pt_index[i]] + 32, buf + 32 );
+                if( ret != 0 )
+                {
+                    if( verbose != 0 )
+                        printf( "failed\n" );
+
+                    return( 1 );
+                }
+            }
+            else
+            {
+                ret = gcm_update( &ctx, pt_len[i], pt[pt_index[i]], buf );
+                if( ret != 0 )
+                {
+                    if( verbose != 0 )
+                        printf( "failed\n" );
+
+                    return( 1 );
+                }
+            }
+
+            ret = gcm_finish( &ctx, tag_buf, 16 );
+            if( ret != 0 ||
+                memcmp( buf, ct[j * 6 + i], pt_len[i] ) != 0 ||
+                memcmp( tag_buf, tag[j * 6 + i], 16 ) != 0 )
+            {
+                if( verbose != 0 )
+                    printf( "failed\n" );
+
+                return( 1 );
+            }
+
+            if( verbose != 0 )
+                printf( "passed\n" );
+
+            if( verbose != 0 )
+                printf( "  AES-GCM-%3d #%d split (%s): ", key_len, i, "dec" );
+
+            gcm_init( &ctx, key[key_index[i]], key_len );
+
+            ret = gcm_starts( &ctx, GCM_DECRYPT,
+                              iv[iv_index[i]], iv_len[i],
+                              additional[add_index[i]], add_len[i] );
+            if( ret != 0 )
+            {
+                if( verbose != 0 )
+                    printf( "failed\n" );
+
+                return( 1 );
+            }
+
+            if( pt_len[i] > 32 )
+            {
+                size_t rest_len = pt_len[i] - 32;
+                ret = gcm_update( &ctx, 32, ct[j * 6 + i], buf );
+                if( ret != 0 )
+                {
+                    if( verbose != 0 )
+                        printf( "failed\n" );
+
+                    return( 1 );
+                }
+
+                ret = gcm_update( &ctx, rest_len, ct[j * 6 + i] + 32, buf + 32 );
+                if( ret != 0 )
+                {
+                    if( verbose != 0 )
+                        printf( "failed\n" );
+
+                    return( 1 );
+                }
+            }
+            else
+            {
+                ret = gcm_update( &ctx, pt_len[i], ct[j * 6 + i], buf );
+                if( ret != 0 )
+                {
+                    if( verbose != 0 )
+                        printf( "failed\n" );
+
+                    return( 1 );
+                }
+            }
+
+            ret = gcm_finish( &ctx, tag_buf, 16 );
+            if( ret != 0 ||
+                memcmp( buf, pt[pt_index[i]], pt_len[i] ) != 0 ||
+                memcmp( tag_buf, tag[j * 6 + i], 16 ) != 0 )
+            {
+                if( verbose != 0 )
+                    printf( "failed\n" );
+
+                return( 1 );
+            }
+
+            if( verbose != 0 )
+                printf( "passed\n" );
+
         }
     }
 
-    printf( "\n" );
+    if( verbose != 0 )
+        printf( "\n" );
 
     return( 0 );
 }